diff options
1163 files changed, 59248 insertions, 9447 deletions
diff --git a/.gitignore b/.gitignore index 270fb560fe..faec15b3de 100644 --- a/.gitignore +++ b/.gitignore @@ -199,3 +199,7 @@ dists/msvc*/** #Ignore bison debug output *.output + +#Ignore Xcode output/project files +out/ +scummvm.xcodeproj diff --git a/.travis.yml b/.travis.yml index 0c88ec8f24..0ec65ee859 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,14 @@ language: - cpp -sudo: false +sudo: required addons: apt: packages: - g++ make - - libsdl1.2-dev - - libjpeg62-turbo-dev + - libsdl2-dev + - libjpeg-turbo8-dev - libmpeg2-4-dev - libogg-dev - libvorbis-dev @@ -32,6 +32,8 @@ compiler: os: - linux +dist: trusty + script: - ./configure --enable-all-engines - make -j 2 @@ -681,7 +681,7 @@ Special thanks to ***************** Daniel Balsom - For the original Reinherit (SAGA) code Sander Buskens - For his work on the initial reversing of Monkey2 - Canadacow - For the original MT-32 emulator + Dean Beeler - For the original MT-32 emulator Kevin Carnes - For Scumm16, the basis of ScummVM's older gfx codecs Curt Coder - For the original TrollVM (preAGI) code Patrick Combet - For the original Gobliiins ADL player @@ -693,12 +693,12 @@ Special thanks to DOSBox Team - For their awesome OPL2 and OPL3 emulator Yusuke Kamiyamane - For contributing some GUI icons Till Kresslein - For design of modern ScummVM GUI - Jezar - For his freeverb filter implementation + Jezar Wakefield - For his freeverb filter implementation Jim Leiterman - Various info on his FM-TOWNS/Marty SCUMM ports - lloyd - For deep tech details about C64 Zak & MM + Lloyd Rosen - For deep tech details about C64 Zak & MM Sarien Team - Original AGI engine code Jimmi Thogersen - For ScummRev, and much obscure code/documentation - Tristan - For additional work on the original MT-32 emulator + Tristan Matthews - For additional work on the original MT-32 emulator James Woodcock - Soundtrack enhancements Anton Yartsev - For the original re-implementation of the Z-Vision engine @@ -9,6 +9,7 @@ For a more comprehensive changelog of the latest experimental code, see: General: - Fixed audio corruption in the MS ADPCM decoder. + - Switched SDL backend to SDL2 by default. SDL1 is still a fallback. AGI: - Added support for Hercules rendering. Both green and amber modes are @@ -44,15 +45,15 @@ For a more comprehensive changelog of the latest experimental code, see: interacting with the armor in room 37 (main house, downstairs). This bug is also present in the original game. - Fixed auto-saving in the fan-made Cascade Quest. - - Fixed a bug in Conquests of the Longbow: The Adventures of Robin Hood that caused - the game to crash while wandering through the Sherwood Forest. - - Added a detection entry for the ImagiNation Network (INN) demo. + - Fixed a game bug in the Conquests of Longbow scripts that could cause crashes in Sherwood Forest. + - Added support for the ImagiNation Network (INN) demo. SCUMM: - Fixed missing translations in the in-game quit and restart dialogs in Pajama Sam 1. - Fixed visual glitches in DOTT that occured after loading a savegame with the stereo in Green Tentacle's room turned on. - Improved timing and pathfinding in Maniac Mansion (C64 and Apple II versions) + - Added support for the Dutch demo of Let's Explore the Airport with Buzzy. WAGE: - Closed memory leak. @@ -61,6 +62,9 @@ For a more comprehensive changelog of the latest experimental code, see: - Fixed taskbar support on Windows 10 onwards. - Fixed keymapping for non-QWERTY keyboards. + Linux port: + - Added basic support for the snap packaging system. + 1.8.1 (2016-05-25) New ports: - Added Nintendo 3DS port. diff --git a/backends/base-backend.cpp b/backends/base-backend.cpp index 3e95c3e26a..dfb9e284ce 100644 --- a/backends/base-backend.cpp +++ b/backends/base-backend.cpp @@ -39,6 +39,20 @@ void BaseBackend::displayMessageOnOSD(const char *msg) { dialog.runModal(); } +void BaseBackend::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) { + warning("BaseBackend::copyRectToOSD not implemented"); //TODO +} + +void BaseBackend::clearOSD() { + warning("BaseBackend::clearOSD not implemented"); //TODO + //what should I do? Remove all TimedMessageDialogs? +} + +Graphics::PixelFormat BaseBackend::getOSDFormat() { + warning("BaseBackend::getOSDFormat not implemented"); + return Graphics::PixelFormat(); +} + void BaseBackend::initBackend() { // Init Event manager #ifndef DISABLE_DEFAULT_EVENT_MANAGER diff --git a/backends/base-backend.h b/backends/base-backend.h index 598f682b32..2394edaf38 100644 --- a/backends/base-backend.h +++ b/backends/base-backend.h @@ -33,6 +33,9 @@ public: virtual void initBackend(); virtual void displayMessageOnOSD(const char *msg); + virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h); + virtual void clearOSD(); + virtual Graphics::PixelFormat getOSDFormat(); virtual void fillScreen(uint32 col); }; diff --git a/backends/cloud/box/boxlistdirectorybyidrequest.cpp b/backends/cloud/box/boxlistdirectorybyidrequest.cpp new file mode 100644 index 0000000000..c013f1eb2a --- /dev/null +++ b/backends/cloud/box/boxlistdirectorybyidrequest.cpp @@ -0,0 +1,195 @@ +/* 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 "backends/cloud/box/boxlistdirectorybyidrequest.h" +#include "backends/cloud/box/boxstorage.h" +#include "backends/cloud/box/boxtokenrefresher.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace Box { + +#define BOX_LIST_DIRECTORY_LIMIT 1000 +#define BOX_FOLDERS_API_LINK "https://api.box.com/2.0/folders/%s/items?offset=%u&limit=%u&fields=%s" + +BoxListDirectoryByIdRequest::BoxListDirectoryByIdRequest(BoxStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _requestedId(id), _storage(storage), _listDirectoryCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +BoxListDirectoryByIdRequest::~BoxListDirectoryByIdRequest() { + _ignoreCallback = true; + if (_workingRequest) _workingRequest->finish(); + delete _listDirectoryCallback; +} + +void BoxListDirectoryByIdRequest::start() { + _ignoreCallback = true; + if (_workingRequest) _workingRequest->finish(); + _files.clear(); + _ignoreCallback = false; + + makeRequest(0); +} + +void BoxListDirectoryByIdRequest::makeRequest(uint32 offset) { + Common::String url = Common::String::format( + BOX_FOLDERS_API_LINK, + _requestedId.c_str(), + offset, + BOX_LIST_DIRECTORY_LIMIT, + "id,type,name,size,modified_at" + ); + + Networking::JsonCallback callback = new Common::Callback<BoxListDirectoryByIdRequest, Networking::JsonResponse>(this, &BoxListDirectoryByIdRequest::responseCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<BoxListDirectoryByIdRequest, Networking::ErrorResponse>(this, &BoxListDirectoryByIdRequest::errorCallback); + Networking::CurlJsonRequest *request = new BoxTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + _workingRequest = ConnMan.addRequest(request); +} + +void BoxListDirectoryByIdRequest::responseCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) { + delete response.value; + return; + } + + if (response.request) + _date = response.request->date(); + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + Common::JSONValue *json = response.value; + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + Common::JSONObject responseObject = json->asObject(); + //debug(9, "%s", json->stringify(true).c_str()); + + //TODO: handle error messages passed as JSON + /* + if (responseObject.contains("error") || responseObject.contains("error_summary")) { + warning("Box returned error: %s", responseObject.getVal("error_summary")->asString().c_str()); + error.failed = true; + error.response = json->stringify(); + finishError(error); + delete json; + return; + } + */ + + //check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults + if (responseObject.contains("entries")) { + if (!responseObject.getVal("entries")->isArray()) { + error.response = Common::String::format( + "\"entries\" found, but that's not an array!\n%s", + responseObject.getVal("entries")->stringify(true).c_str() + ); + finishError(error); + delete json; + return; + } + + Common::JSONArray items = responseObject.getVal("entries")->asArray(); + for (uint32 i = 0; i < items.size(); ++i) { + if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "BoxListDirectoryByIdRequest")) continue; + + Common::JSONObject item = items[i]->asObject(); + + if (!Networking::CurlJsonRequest::jsonContainsString(item, "id", "BoxListDirectoryByIdRequest")) continue; + if (!Networking::CurlJsonRequest::jsonContainsString(item, "name", "BoxListDirectoryByIdRequest")) continue; + if (!Networking::CurlJsonRequest::jsonContainsString(item, "type", "BoxListDirectoryByIdRequest")) continue; + if (!Networking::CurlJsonRequest::jsonContainsString(item, "modified_at", "BoxListDirectoryByIdRequest")) continue; + if (!Networking::CurlJsonRequest::jsonContainsStringOrIntegerNumber(item, "size", "BoxListDirectoryByIdRequest")) continue; + + Common::String id = item.getVal("id")->asString(); + Common::String name = item.getVal("name")->asString(); + bool isDirectory = (item.getVal("type")->asString() == "folder"); + uint32 size; + if (item.getVal("size")->isString()) { + size = item.getVal("size")->asString().asUint64(); + } else { + size = item.getVal("size")->asIntegerNumber(); + } + uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("modified_at")->asString()); + + //as we list directory by id, we can't determine full path for the file, so we leave it empty + _files.push_back(StorageFile(id, "", name, size, timestamp, isDirectory)); + } + } + + uint32 received = 0; + uint32 totalCount = 0; + if (responseObject.contains("total_count") && responseObject.getVal("total_count")->isIntegerNumber()) + totalCount = responseObject.getVal("total_count")->asIntegerNumber(); + if (responseObject.contains("offset") && responseObject.getVal("offset")->isIntegerNumber()) + received = responseObject.getVal("offset")->asIntegerNumber(); + if (responseObject.contains("limit") && responseObject.getVal("limit")->isIntegerNumber()) + received += responseObject.getVal("limit")->asIntegerNumber(); + bool hasMore = (received < totalCount); + + if (hasMore) makeRequest(received); + else finishListing(_files); + + delete json; +} + +void BoxListDirectoryByIdRequest::errorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) return; + if (error.request) _date = error.request->date(); + finishError(error); +} + +void BoxListDirectoryByIdRequest::handle() {} + +void BoxListDirectoryByIdRequest::restart() { start(); } + +Common::String BoxListDirectoryByIdRequest::date() const { return _date; } + +void BoxListDirectoryByIdRequest::finishListing(Common::Array<StorageFile> &files) { + Request::finishSuccess(); + if (_listDirectoryCallback) (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files)); +} + +} // End of namespace Box +} // End of namespace Cloud diff --git a/backends/cloud/box/boxlistdirectorybyidrequest.h b/backends/cloud/box/boxlistdirectorybyidrequest.h new file mode 100644 index 0000000000..13f1ba056c --- /dev/null +++ b/backends/cloud/box/boxlistdirectorybyidrequest.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_BOX_BOXLISTDIRECTORYBYIDREQUEST_H +#define BACKENDS_CLOUD_BOX_BOXLISTDIRECTORYBYIDREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Box { + +class BoxStorage; + +class BoxListDirectoryByIdRequest: public Networking::Request { + Common::String _requestedId; + BoxStorage *_storage; + + Storage::ListDirectoryCallback _listDirectoryCallback; + Common::Array<StorageFile> _files; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void makeRequest(uint32 offset); + void responseCallback(Networking::JsonResponse response); + void errorCallback(Networking::ErrorResponse error); + void finishListing(Common::Array<StorageFile> &files); +public: + BoxListDirectoryByIdRequest(BoxStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb); + virtual ~BoxListDirectoryByIdRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace Box +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/box/boxstorage.cpp b/backends/cloud/box/boxstorage.cpp new file mode 100644 index 0000000000..70864679e7 --- /dev/null +++ b/backends/cloud/box/boxstorage.cpp @@ -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 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/box/boxstorage.h" +#include "backends/cloud/box/boxlistdirectorybyidrequest.h" +#include "backends/cloud/box/boxtokenrefresher.h" +#include "backends/cloud/box/boxuploadrequest.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +#ifdef ENABLE_RELEASE +#include "dists/clouds/cloud_keys.h" +#endif + +namespace Cloud { +namespace Box { + +#define BOX_OAUTH2_TOKEN "https://api.box.com/oauth2/token" +#define BOX_API_FOLDERS "https://api.box.com/2.0/folders" +#define BOX_API_FILES_CONTENT "https://api.box.com/2.0/files/%s/content" +#define BOX_API_USERS_ME "https://api.box.com/2.0/users/me" + +char *BoxStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth +char *BoxStorage::SECRET = nullptr; + +void BoxStorage::loadKeyAndSecret() { +#ifdef ENABLE_RELEASE + KEY = RELEASE_BOX_KEY; + SECRET = RELEASE_BOX_SECRET; +#else + Common::String k = ConfMan.get("BOX_KEY", ConfMan.kCloudDomain); + KEY = new char[k.size() + 1]; + memcpy(KEY, k.c_str(), k.size()); + KEY[k.size()] = 0; + + k = ConfMan.get("BOX_SECRET", ConfMan.kCloudDomain); + SECRET = new char[k.size() + 1]; + memcpy(SECRET, k.c_str(), k.size()); + SECRET[k.size()] = 0; +#endif +} + +BoxStorage::BoxStorage(Common::String accessToken, Common::String refreshToken): + _token(accessToken), _refreshToken(refreshToken) {} + +BoxStorage::BoxStorage(Common::String code) { + getAccessToken( + new Common::Callback<BoxStorage, BoolResponse>(this, &BoxStorage::codeFlowComplete), + new Common::Callback<BoxStorage, Networking::ErrorResponse>(this, &BoxStorage::codeFlowFailed), + code + ); +} + +BoxStorage::~BoxStorage() {} + +void BoxStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) { + if (!KEY || !SECRET) + loadKeyAndSecret(); + bool codeFlow = (code != ""); + + if (!codeFlow && _refreshToken == "") { + warning("BoxStorage: no refresh token available to get new access token."); + if (callback) (*callback)(BoolResponse(nullptr, false)); + return; + } + + Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, BoolResponse, Networking::JsonResponse>(this, &BoxStorage::tokenRefreshed, callback); + if (errorCallback == nullptr) + errorCallback = getErrorPrintingCallback(); + + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, BOX_OAUTH2_TOKEN); + if (codeFlow) { + request->addPostField("grant_type=authorization_code"); + request->addPostField("code=" + code); + } else { + request->addPostField("grant_type=refresh_token"); + request->addPostField("refresh_token=" + _refreshToken); + } + request->addPostField("client_id=" + Common::String(KEY)); + request->addPostField("client_secret=" + Common::String(SECRET)); + /* + if (Cloud::CloudManager::couldUseLocalServer()) { + request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345"); + } else { + request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code"); + } + */ + addRequest(request); +} + +void BoxStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("BoxStorage: got NULL instead of JSON"); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + delete callback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage")) { + if (callback) + (*callback)(BoolResponse(nullptr, false)); + delete json; + delete callback; + return; + } + + Common::JSONObject result = json->asObject(); + if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "BoxStorage") || + !Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "BoxStorage")) { + warning("BoxStorage: bad response, no token passed"); + debug(9, "%s", json->stringify().c_str()); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + } else { + _token = result.getVal("access_token")->asString(); + _refreshToken = result.getVal("refresh_token")->asString(); + CloudMan.save(); //ask CloudManager to save our new refreshToken + if (callback) + (*callback)(BoolResponse(nullptr, true)); + } + delete json; + delete callback; +} + +void BoxStorage::codeFlowComplete(BoolResponse response) { + if (!response.value) { + warning("BoxStorage: failed to get access token through code flow"); + CloudMan.removeStorage(this); + return; + } + + CloudMan.replaceStorage(this, kStorageBoxId); + ConfMan.flushToDisk(); +} + +void BoxStorage::codeFlowFailed(Networking::ErrorResponse error) { + debug(9, "BoxStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode); + debug(9, "%s", error.response.c_str()); + CloudMan.removeStorage(this); +} + +void BoxStorage::saveConfig(Common::String keyPrefix) { + ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain); + ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain); +} + +Common::String BoxStorage::name() const { + return "Box"; +} + +void BoxStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("BoxStorage::infoInnerCallback: NULL passed instead of JSON"); + delete outerCallback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage::infoInnerCallback")) { + delete json; + delete outerCallback; + return; + } + + Common::JSONObject info = json->asObject(); + + Common::String uid, name, email; + uint64 quotaUsed = 0, quotaAllocated = 0; + + // can check that "type": "user" + // there is also "max_upload_size", "phone" and "avatar_url" + + if (Networking::CurlJsonRequest::jsonContainsString(info, "id", "BoxStorage::infoInnerCallback")) + uid = info.getVal("id")->asString(); + + if (Networking::CurlJsonRequest::jsonContainsString(info, "name", "BoxStorage::infoInnerCallback")) + name = info.getVal("name")->asString(); + + if (Networking::CurlJsonRequest::jsonContainsString(info, "login", "BoxStorage::infoInnerCallback")) + email = info.getVal("login")->asString(); + + if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "space_amount", "BoxStorage::infoInnerCallback")) + quotaAllocated = info.getVal("space_amount")->asIntegerNumber(); + + if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "space_used", "BoxStorage::infoInnerCallback")) + quotaUsed = info.getVal("space_used")->asIntegerNumber(); + + Common::String username = email; + if (username == "") username = name; + if (username == "") username = uid; + CloudMan.setStorageUsername(kStorageBoxId, username); + + if (outerCallback) { + (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated))); + delete outerCallback; + } + + delete json; +} + +Networking::Request *BoxStorage::listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + if (!callback) + callback = getPrintFilesCallback(); + return addRequest(new BoxListDirectoryByIdRequest(this, id, callback, errorCallback)); +} + +void BoxStorage::createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("BoxStorage::createDirectoryInnerCallback: NULL passed instead of JSON"); + delete outerCallback; + return; + } + + if (outerCallback) { + if (Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage::createDirectoryInnerCallback")) { + Common::JSONObject info = json->asObject(); + (*outerCallback)(BoolResponse(nullptr, info.contains("id"))); + } else { + (*outerCallback)(BoolResponse(nullptr, false)); + } + delete outerCallback; + } + + delete json; +} + +Networking::Request *BoxStorage::createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + + Common::String url = BOX_API_FOLDERS; + Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, BoolResponse, Networking::JsonResponse>(this, &BoxStorage::createDirectoryInnerCallback, callback); + Networking::CurlJsonRequest *request = new BoxTokenRefresher(this, innerCallback, errorCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + accessToken()); + request->addHeader("Content-Type: application/json"); + + Common::JSONObject parentObject; + parentObject.setVal("id", new Common::JSONValue(parentId)); + + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("name", new Common::JSONValue(name)); + jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject)); + + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + return addRequest(request); +} + +Networking::Request *BoxStorage::upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + return addRequest(new BoxUploadRequest(this, remotePath, localPath, callback, errorCallback)); +} + +Networking::Request *BoxStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) { + warning("BoxStorage::upload(ReadStream) not implemented"); + if (errorCallback) + (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "BoxStorage::upload(ReadStream) not implemented", -1)); + delete callback; + delete errorCallback; + return nullptr; +} + +bool BoxStorage::uploadStreamSupported() { + return false; +} + +Networking::Request *BoxStorage::streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) { + if (callback) { + Common::String url = Common::String::format(BOX_API_FILES_CONTENT, id.c_str()); + Common::String header = "Authorization: Bearer " + _token; + curl_slist *headersList = curl_slist_append(nullptr, header.c_str()); + Networking::NetworkReadStream *stream = new Networking::NetworkReadStream(url.c_str(), headersList, ""); + (*callback)(Networking::NetworkReadStreamResponse(nullptr, stream)); + } + delete callback; + delete errorCallback; + return nullptr; +} + +Networking::Request *BoxStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) { + Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, StorageInfoResponse, Networking::JsonResponse>(this, &BoxStorage::infoInnerCallback, callback); + Networking::CurlJsonRequest *request = new BoxTokenRefresher(this, innerCallback, errorCallback, BOX_API_USERS_ME); + request->addHeader("Authorization: Bearer " + _token); + return addRequest(request); +} + +Common::String BoxStorage::savesDirectoryPath() { return "scummvm/saves/"; } + +BoxStorage *BoxStorage::loadFromConfig(Common::String keyPrefix) { + loadKeyAndSecret(); + + if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) { + warning("BoxStorage: no access_token found"); + return nullptr; + } + + if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) { + warning("BoxStorage: no refresh_token found"); + return nullptr; + } + + Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain); + Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain); + return new BoxStorage(accessToken, refreshToken); +} + +Common::String BoxStorage::getRootDirectoryId() { + return "0"; +} + +} // End of namespace Box +} // End of namespace Cloud diff --git a/backends/cloud/box/boxstorage.h b/backends/cloud/box/boxstorage.h new file mode 100644 index 0000000000..2dd516d894 --- /dev/null +++ b/backends/cloud/box/boxstorage.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_BOX_BOXSTORAGE_H +#define BACKENDS_CLOUD_BOX_BOXSTORAGE_H + +#include "backends/cloud/id/idstorage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace Box { + +class BoxStorage: public Id::IdStorage { + static char *KEY, *SECRET; + + static void loadKeyAndSecret(); + + Common::String _token, _refreshToken; + + /** This private constructor is called from loadFromConfig(). */ + BoxStorage(Common::String token, Common::String refreshToken); + + void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response); + void codeFlowComplete(BoolResponse response); + void codeFlowFailed(Networking::ErrorResponse error); + + /** Constructs StorageInfo based on JSON response from cloud. */ + void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json); + + void createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response); +public: + /** This constructor uses OAuth code flow to get tokens. */ + BoxStorage(Common::String code); + virtual ~BoxStorage(); + + /** + * Storage methods, which are used by CloudManager to save + * storage in configuration file. + */ + + /** + * Save storage data using ConfMan. + * @param keyPrefix all saved keys must start with this prefix. + * @note every Storage must write keyPrefix + "type" key + * with common value (e.g. "Dropbox"). + */ + virtual void saveConfig(Common::String keyPrefix); + + /** + * Return unique storage name. + * @returns some unique storage name (for example, "Dropbox (user@example.com)") + */ + virtual Common::String name() const; + + /** Public Cloud API comes down there. */ + + virtual Networking::Request *listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback); + virtual Networking::Request *createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns UploadStatus struct with info about uploaded file. */ + virtual Networking::Request *upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback); + virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns whether Storage supports upload(ReadStream). */ + virtual bool uploadStreamSupported(); + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns the StorageInfo struct. */ + virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns storage's saves directory path with the trailing slash. */ + virtual Common::String savesDirectoryPath(); + + /** + * Load token and user id from configs and return BoxStorage for those. + * @return pointer to the newly created BoxStorage or 0 if some problem occured. + */ + static BoxStorage *loadFromConfig(Common::String keyPrefix); + + virtual Common::String getRootDirectoryId(); + + /** + * Gets new access_token. If <code> passed is "", refresh_token is used. + * Use "" in order to refresh token and pass a callback, so you could + * continue your work when new token is available. + */ + void getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr, Common::String code = ""); + + Common::String accessToken() const { return _token; } +}; + +} // End of namespace Box +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/box/boxtokenrefresher.cpp b/backends/cloud/box/boxtokenrefresher.cpp new file mode 100644 index 0000000000..ca05eef838 --- /dev/null +++ b/backends/cloud/box/boxtokenrefresher.cpp @@ -0,0 +1,137 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/box/boxtokenrefresher.h" +#include "backends/cloud/box/boxstorage.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +namespace Cloud { +namespace Box { + +BoxTokenRefresher::BoxTokenRefresher(BoxStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url): + CurlJsonRequest(callback, ecb, url), _parentStorage(parent) {} + +BoxTokenRefresher::~BoxTokenRefresher() {} + +void BoxTokenRefresher::tokenRefreshed(Storage::BoolResponse response) { + if (!response.value) { + //failed to refresh token, notify user with NULL in original callback + warning("BoxTokenRefresher: failed to refresh token"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + //update headers: first change header with token, then pass those to request + for (uint32 i = 0; i < _headers.size(); ++i) { + if (_headers[i].contains("Authorization")) { + _headers[i] = "Authorization: Bearer " + _parentStorage->accessToken(); + } + } + setHeaders(_headers); + + //successfully received refreshed token, can restart the original request now + retry(0); +} + +void BoxTokenRefresher::finishJson(Common::JSONValue *json) { + if (!json) { + //that's probably not an error (200 OK) + CurlJsonRequest::finishJson(nullptr); + return; + } + + if (jsonIsObject(json, "BoxTokenRefresher")) { + Common::JSONObject result = json->asObject(); + if (result.contains("type") && result.getVal("type")->isString() && result.getVal("type")->asString() == "error") { + //new token needed => request token & then retry original request + long httpCode = -1; + if (_stream) { + httpCode = _stream->httpResponseCode(); + debug(9, "BoxTokenRefresher: code %ld", httpCode); + } + + bool irrecoverable = true; + + Common::String code, message; + if (jsonContainsString(result, "code", "BoxTokenRefresher")) { + code = result.getVal("code")->asString(); + debug(9, "BoxTokenRefresher: code = %s", code.c_str()); + } + + if (jsonContainsString(result, "message", "BoxTokenRefresher")) { + message = result.getVal("message")->asString(); + debug(9, "BoxTokenRefresher: message = %s", message.c_str()); + } + + //TODO: decide when token refreshment will help + //for now refreshment is used only when HTTP 401 is passed in finishError() + //if (code == "unauthenticated") irrecoverable = false; + + if (irrecoverable) { + finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpCode)); + delete json; + return; + } + + pause(); + delete json; + _parentStorage->getAccessToken(new Common::Callback<BoxTokenRefresher, Storage::BoolResponse>(this, &BoxTokenRefresher::tokenRefreshed)); + return; + } + } + + //notify user of success + CurlJsonRequest::finishJson(json); +} + +void BoxTokenRefresher::finishError(Networking::ErrorResponse error) { + if (error.httpResponseCode == 401) { // invalid_token + pause(); + _parentStorage->getAccessToken(new Common::Callback<BoxTokenRefresher, Storage::BoolResponse>(this, &BoxTokenRefresher::tokenRefreshed)); + return; + } + + // there are also 400 == invalid_request and 403 == insufficient_scope + // but TokenRefresher is there to refresh token when it's invalid only + + Request::finishError(error); +} + +void BoxTokenRefresher::setHeaders(Common::Array<Common::String> &headers) { + _headers = headers; + curl_slist_free_all(_headersList); + _headersList = 0; + for (uint32 i = 0; i < headers.size(); ++i) + CurlJsonRequest::addHeader(headers[i]); +} + +void BoxTokenRefresher::addHeader(Common::String header) { + _headers.push_back(header); + CurlJsonRequest::addHeader(header); +} + +} // End of namespace Box +} // End of namespace Cloud diff --git a/backends/cloud/box/boxtokenrefresher.h b/backends/cloud/box/boxtokenrefresher.h new file mode 100644 index 0000000000..c08e8468c3 --- /dev/null +++ b/backends/cloud/box/boxtokenrefresher.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_BOX_BOXTOKENREFRESHER_H +#define BACKENDS_CLOUD_BOX_BOXTOKENREFRESHER_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace Box { + +class BoxStorage; + +class BoxTokenRefresher: public Networking::CurlJsonRequest { + BoxStorage *_parentStorage; + Common::Array<Common::String> _headers; + + void tokenRefreshed(Storage::BoolResponse response); + + virtual void finishJson(Common::JSONValue *json); + virtual void finishError(Networking::ErrorResponse error); +public: + BoxTokenRefresher(BoxStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url); + virtual ~BoxTokenRefresher(); + + virtual void setHeaders(Common::Array<Common::String> &headers); + virtual void addHeader(Common::String header); +}; + +} // End of namespace Box +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/box/boxuploadrequest.cpp b/backends/cloud/box/boxuploadrequest.cpp new file mode 100644 index 0000000000..5084aa5652 --- /dev/null +++ b/backends/cloud/box/boxuploadrequest.cpp @@ -0,0 +1,232 @@ +/* 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 "backends/cloud/box/boxuploadrequest.h" +#include "backends/cloud/box/boxstorage.h" +#include "backends/cloud/box/boxtokenrefresher.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace Box { + +#define BOX_API_FILES "https://upload.box.com/api/2.0/files" + +BoxUploadRequest::BoxUploadRequest(BoxStorage *storage, Common::String path, Common::String localPath, Storage::UploadCallback callback, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _localPath(localPath), _uploadCallback(callback), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +BoxUploadRequest::~BoxUploadRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _uploadCallback; +} + +void BoxUploadRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _resolvedId = ""; //used to update file contents + _parentId = ""; //used to create file within parent directory + _ignoreCallback = false; + + resolveId(); +} + +void BoxUploadRequest::resolveId() { + //check whether such file already exists + Storage::UploadCallback innerCallback = new Common::Callback<BoxUploadRequest, Storage::UploadResponse>(this, &BoxUploadRequest::idResolvedCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<BoxUploadRequest, Networking::ErrorResponse>(this, &BoxUploadRequest::idResolveFailedCallback); + _workingRequest = _storage->resolveFileId(_savePath, innerCallback, innerErrorCallback); +} + +void BoxUploadRequest::idResolvedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) return; + _resolvedId = response.value.id(); + upload(); +} + +void BoxUploadRequest::idResolveFailedCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) return; + + //not resolved => error or no such file + if (error.response.contains("no such file found in its parent directory")) { + //parent's id after the '\n' + Common::String parentId = error.response; + for (uint32 i = 0; i < parentId.size(); ++i) + if (parentId[i] == '\n') { + parentId.erase(0, i + 1); + break; + } + + _parentId = parentId; + upload(); + return; + } + + finishError(error); +} + +void BoxUploadRequest::upload() { + Common::String name = _savePath; + for (uint32 i = name.size(); i > 0; --i) { + if (name[i - 1] == '/' || name[i - 1] == '\\') { + name.erase(0, i); + break; + } + } + + Common::String url = BOX_API_FILES; + if (_resolvedId != "") + url += "/" + _resolvedId; + url += "/content"; + Networking::JsonCallback callback = new Common::Callback<BoxUploadRequest, Networking::JsonResponse>(this, &BoxUploadRequest::uploadedCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<BoxUploadRequest, Networking::ErrorResponse>(this, &BoxUploadRequest::notUploadedCallback); + Networking::CurlJsonRequest *request = new BoxTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + + Common::JSONObject jsonRequestParameters; + if (_resolvedId == "") { + Common::JSONObject parentObject; + parentObject.setVal("id", new Common::JSONValue(_parentId)); + jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject)); + jsonRequestParameters.setVal("name", new Common::JSONValue(name)); + } + + Common::JSONValue value(jsonRequestParameters); + request->addFormField("attributes", Common::JSON::stringify(&value)); + request->addFormFile("file", _localPath); + + _workingRequest = ConnMan.addRequest(request); +} + +void BoxUploadRequest::uploadedCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) return; + + Networking::ErrorResponse error(this, false, true, "", -1); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq) { + const Networking::NetworkReadStream *stream = rq->getNetworkReadStream(); + if (stream) { + long code = stream->httpResponseCode(); + error.httpResponseCode = code; + } + } + + if (error.httpResponseCode != 200 && error.httpResponseCode != 201) + warning("BoxUploadRequest: looks like an error (bad HTTP code)"); + + //check JSON and show warnings if it's malformed + Common::JSONValue *json = response.value; + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + Common::JSONObject object = json->asObject(); + if (Networking::CurlJsonRequest::jsonContainsArray(object, "entries", "BoxUploadRequest")) { + Common::JSONArray entries = object.getVal("entries")->asArray(); + if (entries.size() == 0) { + warning("BoxUploadRequest: 'entries' found, but it's empty"); + } else if (!Networking::CurlJsonRequest::jsonIsObject(entries[0], "BoxUploadRequest")) { + warning("BoxUploadRequest: 'entries' first item is not an object"); + } else { + Common::JSONObject item = entries[0]->asObject(); + + if (Networking::CurlJsonRequest::jsonContainsString(item, "id", "BoxUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(item, "name", "BoxUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(item, "type", "BoxUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(item, "modified_at", "BoxUploadRequest") && + Networking::CurlJsonRequest::jsonContainsStringOrIntegerNumber(item, "size", "BoxUploadRequest")) { + + //finished + Common::String id = item.getVal("id")->asString(); + Common::String name = item.getVal("name")->asString(); + bool isDirectory = (item.getVal("type")->asString() == "folder"); + uint32 size; + if (item.getVal("size")->isString()) { + size = item.getVal("size")->asString().asUint64(); + } else { + size = item.getVal("size")->asIntegerNumber(); + } + uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("modified_at")->asString()); + + finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory)); + delete json; + return; + } + } + } + + //TODO: check errors + /* + if (object.contains("error")) { + warning("Box returned error: %s", json->stringify(true).c_str()); + delete json; + error.response = json->stringify(true); + finishError(error); + return; + } + */ + + warning("BoxUploadRequest: no file info to return"); + finishUpload(StorageFile(_savePath, 0, 0, false)); + + delete json; +} + +void BoxUploadRequest::notUploadedCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) return; + finishError(error); +} + +void BoxUploadRequest::handle() {} + +void BoxUploadRequest::restart() { start(); } + +void BoxUploadRequest::finishUpload(StorageFile file) { + Request::finishSuccess(); + if (_uploadCallback) + (*_uploadCallback)(Storage::UploadResponse(this, file)); +} + +} // End of namespace Box +} // End of namespace Cloud diff --git a/backends/cloud/box/boxuploadrequest.h b/backends/cloud/box/boxuploadrequest.h new file mode 100644 index 0000000000..1bc8690210 --- /dev/null +++ b/backends/cloud/box/boxuploadrequest.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_BOX_BOXUPLOADREQUEST_H +#define BACKENDS_CLOUD_BOX_BOXUPLOADREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Box { +class BoxStorage; + +class BoxUploadRequest: public Networking::Request { + BoxStorage *_storage; + Common::String _savePath, _localPath; + Storage::UploadCallback _uploadCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _resolvedId, _parentId; + + void start(); + void resolveId(); + void idResolvedCallback(Storage::UploadResponse response); + void idResolveFailedCallback(Networking::ErrorResponse error); + void upload(); + void uploadedCallback(Networking::JsonResponse response); + void notUploadedCallback(Networking::ErrorResponse error); + void finishUpload(StorageFile status); + +public: + BoxUploadRequest(BoxStorage *storage, Common::String path, Common::String localPath, Storage::UploadCallback callback, Networking::ErrorCallback ecb); + virtual ~BoxUploadRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace Box +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/cloudmanager.cpp b/backends/cloud/cloudmanager.cpp new file mode 100644 index 0000000000..826fc6103d --- /dev/null +++ b/backends/cloud/cloudmanager.cpp @@ -0,0 +1,456 @@ +/* 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 "backends/cloud/cloudmanager.h" +#include "backends/cloud/box/boxstorage.h" +#include "backends/cloud/dropbox/dropboxstorage.h" +#include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/cloud/googledrive/googledrivestorage.h" +#include "common/translation.h" +#include "common/config-manager.h" +#include "common/str.h" +#ifdef USE_SDL_NET +#include "backends/networking/sdl_net/localwebserver.h" +#endif + +namespace Common { + +DECLARE_SINGLETON(Cloud::CloudManager); + +} + +namespace Cloud { + +const char *const CloudManager::kStoragePrefix = "storage_"; + +CloudManager::CloudManager() : _currentStorageIndex(0), _activeStorage(nullptr) {} + +CloudManager::~CloudManager() { + delete _activeStorage; + freeStorages(); +} + +Common::String CloudManager::getStorageConfigName(uint32 index) const { + switch (index) { + case kStorageNoneId: return "<none>"; + case kStorageDropboxId: return "Dropbox"; + case kStorageOneDriveId: return "OneDrive"; + case kStorageGoogleDriveId: return "GoogleDrive"; + case kStorageBoxId: return "Box"; + } + assert(false); // Unhandled StorageID value + return ""; +} + +void CloudManager::loadStorage() { + switch (_currentStorageIndex) { + case kStorageDropboxId: + _activeStorage = Dropbox::DropboxStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_"); + break; + case kStorageOneDriveId: + _activeStorage = OneDrive::OneDriveStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_"); + break; + case kStorageGoogleDriveId: + _activeStorage = GoogleDrive::GoogleDriveStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_"); + break; + case kStorageBoxId: + _activeStorage = Box::BoxStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_"); + break; + default: + _activeStorage = nullptr; + } + + if (!_activeStorage) { + _currentStorageIndex = kStorageNoneId; + } +} + +void CloudManager::init() { + //init configs structs + for (uint32 i = 0; i < kStorageTotal; ++i) { + Common::String name = getStorageConfigName(i); + StorageConfig config; + config.name = _(name); + config.username = ""; + config.lastSyncDate = ""; + config.usedBytes = 0; + if (ConfMan.hasKey(kStoragePrefix + name + "_username", ConfMan.kCloudDomain)) + config.username = ConfMan.get(kStoragePrefix + name + "_username", ConfMan.kCloudDomain); + if (ConfMan.hasKey(kStoragePrefix + name + "_lastSync", ConfMan.kCloudDomain)) + config.lastSyncDate = ConfMan.get(kStoragePrefix + name + "_lastSync", ConfMan.kCloudDomain); + if (ConfMan.hasKey(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain)) + config.usedBytes = ConfMan.get(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain).asUint64(); + _storages.push_back(config); + } + + //load an active storage if there is any + _currentStorageIndex = kStorageNoneId; + if (ConfMan.hasKey("current_storage", ConfMan.kCloudDomain)) + _currentStorageIndex = ConfMan.getInt("current_storage", ConfMan.kCloudDomain); + + loadStorage(); +} + +void CloudManager::save() { + for (uint32 i = 0; i < _storages.size(); ++i) { + if (i == kStorageNoneId) + continue; + Common::String name = getStorageConfigName(i); + ConfMan.set(kStoragePrefix + name + "_username", _storages[i].username, ConfMan.kCloudDomain); + ConfMan.set(kStoragePrefix + name + "_lastSync", _storages[i].lastSyncDate, ConfMan.kCloudDomain); + ConfMan.set(kStoragePrefix + name + "_usedBytes", Common::String::format("%lu", _storages[i].usedBytes), ConfMan.kCloudDomain); + } + + ConfMan.set("current_storage", Common::String::format("%u", _currentStorageIndex), ConfMan.kCloudDomain); + if (_activeStorage) + _activeStorage->saveConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_"); + ConfMan.flushToDisk(); +} + +void CloudManager::replaceStorage(Storage *storage, uint32 index) { + freeStorages(); + if (!storage) + error("CloudManager::replaceStorage: NULL storage passed"); + if (index >= kStorageTotal) + error("CloudManager::replaceStorage: invalid index passed"); + if (_activeStorage != nullptr && _activeStorage->isWorking()) { + warning("CloudManager::replaceStorage: replacing Storage while the other is working"); + if (_activeStorage->isDownloading()) + _activeStorage->cancelDownload(); + if (_activeStorage->isSyncing()) + _activeStorage->cancelSync(); + removeStorage(_activeStorage); + } else { + delete _activeStorage; + } + _activeStorage = storage; + _currentStorageIndex = index; + save(); + + //do what should be done on first Storage connect + if (_activeStorage) { + _activeStorage->info(nullptr, nullptr); //automatically calls setStorageUsername() + _activeStorage->syncSaves(nullptr, nullptr); + } +} + +void CloudManager::removeStorage(Storage *storage) { + // can't just delete it as it's mostly likely the one who calls the method + // it would be freed on freeStorages() call (on next Storage connect or replace) + _storagesToRemove.push_back(storage); +} + +void CloudManager::freeStorages() { + for (uint32 i = 0; i < _storagesToRemove.size(); ++i) + delete _storagesToRemove[i]; + _storagesToRemove.clear(); +} + +void CloudManager::passNoStorageConnected(Networking::ErrorCallback errorCallback) const { + if (errorCallback == nullptr) + return; + (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "No Storage connected!", -1)); +} + +Storage *CloudManager::getCurrentStorage() const { + return _activeStorage; +} + +uint32 CloudManager::getStorageIndex() const { + return _currentStorageIndex; +} + +Common::StringArray CloudManager::listStorages() const { + Common::StringArray result; + for (uint32 i = 0; i < _storages.size(); ++i) { + result.push_back(_storages[i].name); + } + return result; +} + +bool CloudManager::switchStorage(uint32 index) { + if (index >= _storages.size()) { + warning("CloudManager::switchStorage: invalid index passed"); + return false; + } + + Storage *storage = getCurrentStorage(); + if (storage && storage->isWorking()) { + warning("CloudManager::switchStorage: another storage is working now"); + return false; + } + + _currentStorageIndex = index; + loadStorage(); + save(); + return true; +} + +Common::String CloudManager::getStorageUsername(uint32 index) { + if (index >= _storages.size()) + return ""; + return _storages[index].username; +} + +uint64 CloudManager::getStorageUsedSpace(uint32 index) { + if (index >= _storages.size()) + return 0; + return _storages[index].usedBytes; +} + +Common::String CloudManager::getStorageLastSync(uint32 index) { + if (index >= _storages.size()) + return ""; + if (index == _currentStorageIndex && isSyncing()) + return ""; + return _storages[index].lastSyncDate; +} + +void CloudManager::setStorageUsername(uint32 index, Common::String name) { + if (index >= _storages.size()) + return; + _storages[index].username = name; + save(); +} + +void CloudManager::setStorageUsedSpace(uint32 index, uint64 used) { + if (index >= _storages.size()) + return; + _storages[index].usedBytes = used; + save(); +} + +void CloudManager::setStorageLastSync(uint32 index, Common::String date) { + if (index >= _storages.size()) + return; + _storages[index].lastSyncDate = date; + save(); +} + +void CloudManager::connectStorage(uint32 index, Common::String code) { + freeStorages(); + + Storage *storage = nullptr; + switch (index) { + case kStorageDropboxId: + storage = new Dropbox::DropboxStorage(code); + break; + case kStorageOneDriveId: + storage = new OneDrive::OneDriveStorage(code); + break; + case kStorageGoogleDriveId: + storage = new GoogleDrive::GoogleDriveStorage(code); + break; + case kStorageBoxId: + storage = new Box::BoxStorage(code); + break; + } + // in these constructors Storages request token using the passed code + // when the token is received, they call replaceStorage() + // or removeStorage(), if some error occurred + // thus, no memory leak happens +} + +Networking::Request *CloudManager::listDirectory(Common::String path, Storage::ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) { + Storage *storage = getCurrentStorage(); + if (storage) { + return storage->listDirectory(path, callback, errorCallback, recursive); + } else { + passNoStorageConnected(errorCallback); + delete callback; + delete errorCallback; + } + return nullptr; +} + +Networking::Request *CloudManager::downloadFolder(Common::String remotePath, Common::String localPath, Storage::FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive) { + Storage *storage = getCurrentStorage(); + if (storage) { + return storage->downloadFolder(remotePath, localPath, callback, errorCallback, recursive); + } else { + passNoStorageConnected(errorCallback); + delete callback; + delete errorCallback; + } + return nullptr; +} + +Networking::Request *CloudManager::info(Storage::StorageInfoCallback callback, Networking::ErrorCallback errorCallback) { + Storage *storage = getCurrentStorage(); + if (storage) { + return storage->info(callback, errorCallback); + } else { + passNoStorageConnected(errorCallback); + delete callback; + delete errorCallback; + } + return nullptr; +} + +Common::String CloudManager::savesDirectoryPath() { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->savesDirectoryPath(); + return ""; +} + +SavesSyncRequest *CloudManager::syncSaves(Storage::BoolCallback callback, Networking::ErrorCallback errorCallback) { + Storage *storage = getCurrentStorage(); + if (storage) { + setStorageLastSync(_currentStorageIndex, "???"); //TODO get the date + return storage->syncSaves(callback, errorCallback); + } else { + passNoStorageConnected(errorCallback); + delete callback; + delete errorCallback; + } + return nullptr; +} + +bool CloudManager::isWorking() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->isWorking(); + return false; +} + +bool CloudManager::couldUseLocalServer() { +#ifdef USE_SDL_NET + return Networking::LocalWebserver::getPort() == Networking::LocalWebserver::DEFAULT_SERVER_PORT; +#else + return false; +#endif +} + +///// SavesSyncRequest-related ///// + +bool CloudManager::isSyncing() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->isSyncing(); + return false; +} + +double CloudManager::getSyncDownloadingProgress() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getSyncDownloadingProgress(); + return 1; +} + +double CloudManager::getSyncProgress() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getSyncProgress(); + return 1; +} + +Common::Array<Common::String> CloudManager::getSyncingFiles() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getSyncingFiles(); + return Common::Array<Common::String>(); +} + +void CloudManager::cancelSync() const { + Storage *storage = getCurrentStorage(); + if (storage) + storage->cancelSync(); +} + +void CloudManager::setSyncTarget(GUI::CommandReceiver *target) const { + Storage *storage = getCurrentStorage(); + if (storage) + storage->setSyncTarget(target); +} + +///// DownloadFolderRequest-related ///// + +bool CloudManager::startDownload(Common::String remotePath, Common::String localPath) const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->startDownload(remotePath, localPath); + return false; +} + +void CloudManager::cancelDownload() const { + Storage *storage = getCurrentStorage(); + if (storage) + storage->cancelDownload(); +} + +void CloudManager::setDownloadTarget(GUI::CommandReceiver *target) const { + Storage *storage = getCurrentStorage(); + if (storage) + storage->setDownloadTarget(target); +} + +bool CloudManager::isDownloading() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->isDownloading(); + return false; +} + +double CloudManager::getDownloadingProgress() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getDownloadingProgress(); + return 1; +} + +uint64 CloudManager::getDownloadBytesNumber() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getDownloadBytesNumber(); + return 0; +} + +uint64 CloudManager::getDownloadTotalBytesNumber() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getDownloadTotalBytesNumber(); + return 0; +} + +uint64 CloudManager::getDownloadSpeed() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getDownloadSpeed(); + return 0; +} + +Common::String CloudManager::getDownloadRemoteDirectory() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getDownloadRemoteDirectory(); + return ""; +} + +Common::String CloudManager::getDownloadLocalDirectory() const { + Storage *storage = getCurrentStorage(); + if (storage) + return storage->getDownloadLocalDirectory(); + return ""; +} + +} // End of namespace Cloud diff --git a/backends/cloud/cloudmanager.h b/backends/cloud/cloudmanager.h new file mode 100644 index 0000000000..c504ff39cb --- /dev/null +++ b/backends/cloud/cloudmanager.h @@ -0,0 +1,274 @@ +/* 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. +* +*/ + +#ifndef CLOUD_CLOUDMANAGER_H +#define CLOUD_CLOUDMANAGER_H + +#include "backends/cloud/storage.h" +#include "common/array.h" +#include "common/singleton.h" +#include "common/str-array.h" + +namespace GUI { + +class CommandReceiver; + +} + +namespace Cloud { + +// The actual indexes in CloudManager's array +enum StorageID { + kStorageNoneId = 0, + kStorageDropboxId = 1, + kStorageOneDriveId = 2, + kStorageGoogleDriveId = 3, + kStorageBoxId = 4, + + kStorageTotal +}; + +class CloudManager : public Common::Singleton<CloudManager> { + static const char *const kStoragePrefix; + + struct StorageConfig { + Common::String name, username; + uint64 usedBytes; + Common::String lastSyncDate; + }; + + Common::Array<StorageConfig> _storages; + uint _currentStorageIndex; + Storage *_activeStorage; + Common::Array<Storage *> _storagesToRemove; + + void loadStorage(); + + Common::String getStorageConfigName(uint32 index) const; + + /** Frees memory used by storages which failed to connect. */ + void freeStorages(); + + /** Calls the error callback with a special "no storage connected" message. */ + void passNoStorageConnected(Networking::ErrorCallback errorCallback) const; + +public: + CloudManager(); + virtual ~CloudManager(); + + /** + * Loads all information from configs and creates current Storage instance. + * + * @note It's called once on startup in scummvm_main(). + */ + void init(); + + /** + * Saves all information into configuration file. + */ + void save(); + + /** + * Replace active Storage. + * @note this method automatically saves the changes with ConfMan. + * + * @param storage Cloud::Storage to replace active storage with. + * @param index one of Cloud::StorageID enum values to indicate what storage type is replaced. + */ + void replaceStorage(Storage *storage, uint32 index); + + /** Adds storage in the list of storages to remove later. */ + void removeStorage(Storage *storage); + + /** + * Returns active Storage, which could be used to interact + * with cloud storage. + * + * @return active Cloud::Storage or null, if there is no active Storage. + */ + Cloud::Storage *getCurrentStorage() const; + + /** + * Return active Storage's index. + * + * @return active Storage's index. + */ + uint32 getStorageIndex() const; + + /** + * Return Storages names as list. + * + * @return a list of Storages names. + */ + Common::StringArray listStorages() const; + + /** + * Changes the storage to the one with given index. + * + * @param new Storage's index. + */ + bool switchStorage(uint32 index); + + /** + * Return username used by Storage. + * + * @param Storage's index. + * @returns username or "" if index is invalid (no such Storage). + */ + Common::String getStorageUsername(uint32 index); + + /** + * Return space used by Storage. + * + * @param Storage's index. + * @returns used space in bytes or 0 if index is invalid (no such Storage). + */ + uint64 getStorageUsedSpace(uint32 index); + + /** + * Return Storage's last sync date. + * + * @param Storage's index. + * @returns last sync date or "" if index is invalid (no such Storage). + It also returns "" if there never was any sync + or if storage is syncing right now. + */ + Common::String getStorageLastSync(uint32 index); + + /** + * Set Storage's username. + * Automatically saves changes to the config. + * + * @param index Storage's index. + * @param name username to set + */ + void setStorageUsername(uint32 index, Common::String name); + + /** + * Set Storage's used space field. + * Automatically saves changes to the config. + * + * @param index Storage's index. + * @param used value to set + */ + void setStorageUsedSpace(uint32 index, uint64 used); + + /** + * Set Storage's last sync date. + * Automatically saves changes to the config. + * + * @param index Storage's index. + * @param date date to set + */ + void setStorageLastSync(uint32 index, Common::String date); + + /** + * Replace Storage which has given index with a + * storage created with given code. + * + * @param index Storage's index + * @param code OAuth2 code received from user + */ + void connectStorage(uint32 index, Common::String code); + + /** Returns ListDirectoryResponse with list of files. */ + Networking::Request *listDirectory(Common::String path, Storage::ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + + /** Returns Common::Array<StorageFile> with list of files, which were not downloaded. */ + Networking::Request *downloadFolder(Common::String remotePath, Common::String localPath, Storage::FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + + /** Return the StorageInfo struct. */ + Networking::Request *info(Storage::StorageInfoCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns storage's saves directory path with the trailing slash. */ + Common::String savesDirectoryPath(); + + /** + * Starts saves syncing process in currently active storage if there is any. + */ + SavesSyncRequest *syncSaves(Cloud::Storage::BoolCallback callback = nullptr, Networking::ErrorCallback errorCallback = nullptr); + + /** Returns whether there are any requests running. */ + bool isWorking() const; + + /** Returns whether LocalWebserver is available to use for auth. */ + static bool couldUseLocalServer(); + + ///// SavesSyncRequest-related ///// + + /** Returns whether there is a SavesSyncRequest running. */ + bool isSyncing() const; + + /** Returns a number in [0, 1] range which represents current sync downloading progress (1 = complete). */ + double getSyncDownloadingProgress() const; + + /** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */ + double getSyncProgress() const; + + /** Returns an array of saves names which are not yet synced (thus cannot be used). */ + Common::Array<Common::String> getSyncingFiles() const; + + /** Cancels running sync. */ + void cancelSync() const; + + /** Sets SavesSyncRequest's target to given CommandReceiver. */ + void setSyncTarget(GUI::CommandReceiver *target) const; + + ///// DownloadFolderRequest-related ///// + + /** Starts a folder download. */ + bool startDownload(Common::String remotePath, Common::String localPath) const; + + /** Cancels running download. */ + void cancelDownload() const; + + /** Sets FolderDownloadRequest's target to given CommandReceiver. */ + void setDownloadTarget(GUI::CommandReceiver *target) const; + + /** Returns whether there is a FolderDownloadRequest running. */ + bool isDownloading() const; + + /** Returns a number in [0, 1] range which represents current download progress (1 = complete). */ + double getDownloadingProgress() const; + + /** Returns a number of bytes that is downloaded in current download progress. */ + uint64 getDownloadBytesNumber() const; + + /** Returns a total number of bytes to be downloaded in current download progress. */ + uint64 getDownloadTotalBytesNumber() const; + + /** Returns download speed of current download progress. */ + uint64 getDownloadSpeed() const; + + /** Returns remote directory path. */ + Common::String getDownloadRemoteDirectory() const; + + /** Returns local directory path. */ + Common::String getDownloadLocalDirectory() const; +}; + +/** Shortcut for accessing the connection manager. */ +#define CloudMan Cloud::CloudManager::instance() + +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/downloadrequest.cpp b/backends/cloud/downloadrequest.cpp new file mode 100644 index 0000000000..e28670fc7b --- /dev/null +++ b/backends/cloud/downloadrequest.cpp @@ -0,0 +1,138 @@ +/* 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 "backends/cloud/downloadrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#include "common/textconsole.h" + +namespace Cloud { + +DownloadRequest::DownloadRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb, Common::String remoteFileId, Common::DumpFile *dumpFile): + Request(nullptr, ecb), _boolCallback(callback), _localFile(dumpFile), _remoteFileId(remoteFileId), _storage(storage), + _remoteFileStream(nullptr), _workingRequest(nullptr), _ignoreCallback(false), _buffer(new byte[DOWNLOAD_REQUEST_BUFFER_SIZE]) { + start(); +} + +DownloadRequest::~DownloadRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _boolCallback; + delete _localFile; + delete[] _buffer; +} + +void DownloadRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _remoteFileStream = nullptr; + //TODO: add some way to reopen DumpFile, so DownloadRequest could be restarted + _ignoreCallback = false; + + _workingRequest = _storage->streamFileById( + _remoteFileId, + new Common::Callback<DownloadRequest, Networking::NetworkReadStreamResponse>(this, &DownloadRequest::streamCallback), + new Common::Callback<DownloadRequest, Networking::ErrorResponse>(this, &DownloadRequest::streamErrorCallback) + ); +} + +void DownloadRequest::streamCallback(Networking::NetworkReadStreamResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + _remoteFileStream = (Networking::NetworkReadStream *)response.value; +} + +void DownloadRequest::streamErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void DownloadRequest::handle() { + if (!_localFile) { + warning("DownloadRequest: no file to write"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + if (!_localFile->isOpen()) { + warning("DownloadRequest: failed to open file to write"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + if (!_remoteFileStream) { + //waiting for callback + return; + } + + uint32 readBytes = _remoteFileStream->read(_buffer, DOWNLOAD_REQUEST_BUFFER_SIZE); + + if (readBytes != 0) + if (_localFile->write(_buffer, readBytes) != readBytes) { + warning("DownloadRequest: unable to write all received bytes into output file"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + if (_remoteFileStream->eos()) { + if (_remoteFileStream->httpResponseCode() != 200) { + warning("DownloadRequest: HTTP response code is not 200 OK (it's %ld)", _remoteFileStream->httpResponseCode()); + //TODO: do something about it actually + // the problem is file's already downloaded, stream is over + // so we can't return error message anymore + } + + finishDownload(_remoteFileStream->httpResponseCode() == 200); + + _localFile->close(); //yes, I know it's closed automatically in ~DumpFile() + } +} + +void DownloadRequest::restart() { + warning("DownloadRequest: can't restart as there are no means to reopen DumpFile"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + //start(); +} + +void DownloadRequest::finishDownload(bool success) { + Request::finishSuccess(); + if (_boolCallback) + (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +void DownloadRequest::finishError(Networking::ErrorResponse error) { + if (_localFile) + _localFile->close(); + Request::finishError(error); +} + +double DownloadRequest::getProgress() const { + if (_remoteFileStream) + return _remoteFileStream->getProgress(); + return 0; +} + +} // End of namespace Cloud diff --git a/backends/cloud/downloadrequest.h b/backends/cloud/downloadrequest.h new file mode 100644 index 0000000000..7e58098849 --- /dev/null +++ b/backends/cloud/downloadrequest.h @@ -0,0 +1,64 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 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. +* +*/ + +#ifndef BACKENDS_CLOUD_DOWNLOADREQUEST_H +#define BACKENDS_CLOUD_DOWNLOADREQUEST_H + +#include "backends/networking/curl/request.h" +#include "backends/networking/curl/networkreadstream.h" +#include "backends/cloud/storage.h" +#include "common/file.h" + +namespace Cloud { + +#define DOWNLOAD_REQUEST_BUFFER_SIZE 1 * 1024 * 1024 + +class DownloadRequest: public Networking::Request { + Storage::BoolCallback _boolCallback; + Common::DumpFile *_localFile; + Common::String _remoteFileId; + Storage *_storage; + Networking::NetworkReadStream *_remoteFileStream; + Request *_workingRequest; + bool _ignoreCallback; + byte *_buffer; + + void start(); + void streamCallback(Networking::NetworkReadStreamResponse response); + void streamErrorCallback(Networking::ErrorResponse error); + void finishDownload(bool success); + virtual void finishError(Networking::ErrorResponse error); + +public: + DownloadRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb, Common::String remoteFileId, Common::DumpFile *dumpFile); + virtual ~DownloadRequest(); + + virtual void handle(); + virtual void restart(); + + /** Returns a number in range [0, 1], where 1 is "complete". */ + double getProgress() const; +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp new file mode 100644 index 0000000000..97090b44f8 --- /dev/null +++ b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp @@ -0,0 +1,137 @@ +/* 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 "backends/cloud/dropbox/dropboxcreatedirectoryrequest.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace Dropbox { + +#define DROPBOX_API_CREATE_FOLDER "https://api.dropboxapi.com/2/files/create_folder" + +DropboxCreateDirectoryRequest::DropboxCreateDirectoryRequest(Common::String token, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _token(token), _path(path), _boolCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +DropboxCreateDirectoryRequest::~DropboxCreateDirectoryRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _boolCallback; +} + +void DropboxCreateDirectoryRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _ignoreCallback = false; + + Networking::JsonCallback innerCallback = new Common::Callback<DropboxCreateDirectoryRequest, Networking::JsonResponse>(this, &DropboxCreateDirectoryRequest::responseCallback); + Networking::ErrorCallback errorCallback = new Common::Callback<DropboxCreateDirectoryRequest, Networking::ErrorResponse>(this, &DropboxCreateDirectoryRequest::errorCallback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, DROPBOX_API_CREATE_FOLDER); + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Content-Type: application/json"); + + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("path", new Common::JSONValue(_path)); + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + _workingRequest = ConnMan.addRequest(request); +} + +void DropboxCreateDirectoryRequest::responseCallback(Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + _workingRequest = nullptr; + if (_ignoreCallback) { + delete json; + return; + } + if (response.request) _date = response.request->date(); + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + Common::JSONObject info = json->asObject(); + if (info.contains("id")) { + finishCreation(true); + } else { + if (Networking::CurlJsonRequest::jsonContainsString(info, "error_summary", "DropboxCreateDirectoryRequest")) { + Common::String summary = info.getVal("error_summary")->asString(); + if (summary.contains("path") && summary.contains("conflict") && summary.contains("folder")) { + // existing directory - not an error for CreateDirectoryRequest + finishCreation(false); + delete json; + return; + } + } + error.response = json->stringify(true); + finishError(error); + } + + delete json; +} + +void DropboxCreateDirectoryRequest::errorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void DropboxCreateDirectoryRequest::handle() {} + +void DropboxCreateDirectoryRequest::restart() { start(); } + +Common::String DropboxCreateDirectoryRequest::date() const { return _date; } + +void DropboxCreateDirectoryRequest::finishCreation(bool success) { + Request::finishSuccess(); + if (_boolCallback) + (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +} // End of namespace Dropbox +} // End of namespace Cloud diff --git a/backends/cloud/dropbox/dropboxcreatedirectoryrequest.h b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.h new file mode 100644 index 0000000000..0ef6a22a04 --- /dev/null +++ b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 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. +* +*/ + +#ifndef BACKENDS_CLOUD_DROPBOX_DROPBOXCREATEDIRECTORYREQUEST_H +#define BACKENDS_CLOUD_DROPBOX_DROPBOXCREATEDIRECTORYREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace Dropbox { + +class DropboxCreateDirectoryRequest: public Networking::Request { + Common::String _token; + Common::String _path; + Storage::BoolCallback _boolCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void responseCallback(Networking::JsonResponse response); + void errorCallback(Networking::ErrorResponse error); + void finishCreation(bool success); +public: + DropboxCreateDirectoryRequest(Common::String token, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb); + virtual ~DropboxCreateDirectoryRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace Dropbox +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/dropbox/dropboxinforequest.cpp b/backends/cloud/dropbox/dropboxinforequest.cpp new file mode 100644 index 0000000000..6cdbe3321b --- /dev/null +++ b/backends/cloud/dropbox/dropboxinforequest.cpp @@ -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 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 "backends/cloud/dropbox/dropboxinforequest.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace Dropbox { + +#define DROPBOX_API_GET_CURRENT_ACCOUNT "https://api.dropboxapi.com/2/users/get_current_account" +#define DROPBOX_API_GET_SPACE_USAGE "https://api.dropboxapi.com/2/users/get_space_usage" + +DropboxInfoRequest::DropboxInfoRequest(Common::String token, Storage::StorageInfoCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _token(token), _infoCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +DropboxInfoRequest::~DropboxInfoRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _infoCallback; +} + +void DropboxInfoRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _ignoreCallback = false; + + Networking::JsonCallback innerCallback = new Common::Callback<DropboxInfoRequest, Networking::JsonResponse>(this, &DropboxInfoRequest::userResponseCallback); + Networking::ErrorCallback errorCallback = new Common::Callback<DropboxInfoRequest, Networking::ErrorResponse>(this, &DropboxInfoRequest::errorCallback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, DROPBOX_API_GET_CURRENT_ACCOUNT); + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Content-Type: application/json"); + request->addPostField("null"); //use POST + + _workingRequest = ConnMan.addRequest(request); +} + +void DropboxInfoRequest::userResponseCallback(Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + _workingRequest = nullptr; + if (_ignoreCallback) { + delete json; + return; + } + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + //Dropbox documentation states there are no errors for this API method + Common::JSONObject info = json->asObject(); + if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "name", "DropboxInfoRequest") && + Networking::CurlJsonRequest::jsonIsObject(info.getVal("name"), "DropboxInfoRequest")) { + Common::JSONObject nameInfo = info.getVal("name")->asObject(); + if (Networking::CurlJsonRequest::jsonContainsString(nameInfo, "display_name", "DropboxInfoRequest")) { + _name = nameInfo.getVal("display_name")->asString(); + } + } + if (Networking::CurlJsonRequest::jsonContainsString(info, "account_id", "DropboxInfoRequest")) { + _uid = info.getVal("account_id")->asString(); + } + if (Networking::CurlJsonRequest::jsonContainsString(info, "email", "DropboxInfoRequest")) { + _email = info.getVal("email")->asString(); + } + CloudMan.setStorageUsername(kStorageDropboxId, _email); + delete json; + + Networking::JsonCallback innerCallback = new Common::Callback<DropboxInfoRequest, Networking::JsonResponse>(this, &DropboxInfoRequest::quotaResponseCallback); + Networking::ErrorCallback errorCallback = new Common::Callback<DropboxInfoRequest, Networking::ErrorResponse>(this, &DropboxInfoRequest::errorCallback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, DROPBOX_API_GET_SPACE_USAGE); + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Content-Type: application/json"); + request->addPostField("null"); //use POST + + _workingRequest = ConnMan.addRequest(request); +} + +void DropboxInfoRequest::quotaResponseCallback(Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + _workingRequest = nullptr; + if (_ignoreCallback) { + delete json; + return; + } + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + //Dropbox documentation states there are no errors for this API method + Common::JSONObject info = json->asObject(); + + if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "used", "DropboxInfoRequest")) { + error.response = "Passed JSON misses 'used' attribute!"; + finishError(error); + delete json; + return; + } + + uint64 used = info.getVal("used")->asIntegerNumber(), allocated = 0; + + if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "allocation", "DropboxInfoRequest") && + Networking::CurlJsonRequest::jsonIsObject(info.getVal("allocation"), "DropboxInfoRequest")) { + Common::JSONObject allocation = info.getVal("allocation")->asObject(); + if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(allocation, "allocated", "DropboxInfoRequest")) { + error.response = "Passed JSON misses 'allocation/allocated' attribute!"; + finishError(error); + delete json; + return; + } + + allocated = allocation.getVal("allocated")->asIntegerNumber(); + } + + finishInfo(StorageInfo(_uid, _name, _email, used, allocated)); + delete json; +} + +void DropboxInfoRequest::errorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) return; + finishError(error); +} + +void DropboxInfoRequest::handle() {} + +void DropboxInfoRequest::restart() { start(); } + +void DropboxInfoRequest::finishInfo(StorageInfo info) { + Request::finishSuccess(); + if (_infoCallback) + (*_infoCallback)(Storage::StorageInfoResponse(this, info)); +} + +} // End of namespace Dropbox +} // End of namespace Cloud diff --git a/backends/cloud/dropbox/dropboxinforequest.h b/backends/cloud/dropbox/dropboxinforequest.h new file mode 100644 index 0000000000..68a3e135de --- /dev/null +++ b/backends/cloud/dropbox/dropboxinforequest.h @@ -0,0 +1,56 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_CLOUD_DROPBOX_DROPBOXINFOREQUEST_H +#define BACKENDS_CLOUD_DROPBOX_DROPBOXINFOREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace Dropbox { + +class DropboxInfoRequest: public Networking::Request { + Common::String _token; + Common::String _uid, _name, _email; + Storage::StorageInfoCallback _infoCallback; + Request *_workingRequest; + bool _ignoreCallback; + + void start(); + void userResponseCallback(Networking::JsonResponse response); + void quotaResponseCallback(Networking::JsonResponse response); + void errorCallback(Networking::ErrorResponse error); + void finishInfo(StorageInfo info); +public: + DropboxInfoRequest(Common::String token, Storage::StorageInfoCallback cb, Networking::ErrorCallback ecb); + virtual ~DropboxInfoRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace Dropbox +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp b/backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp new file mode 100644 index 0000000000..8b00f7c2bf --- /dev/null +++ b/backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp @@ -0,0 +1,222 @@ +/* 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 "backends/cloud/dropbox/dropboxlistdirectoryrequest.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace Dropbox { + +#define DROPBOX_API_LIST_FOLDER "https://api.dropboxapi.com/2/files/list_folder" +#define DROPBOX_API_LIST_FOLDER_CONTINUE "https://api.dropboxapi.com/2/files/list_folder/continue" + +DropboxListDirectoryRequest::DropboxListDirectoryRequest(Common::String token, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive): + Networking::Request(nullptr, ecb), _requestedPath(path), _requestedRecursive(recursive), _listDirectoryCallback(cb), + _token(token), _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +DropboxListDirectoryRequest::~DropboxListDirectoryRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _listDirectoryCallback; +} + +void DropboxListDirectoryRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _files.clear(); + _ignoreCallback = false; + + Networking::JsonCallback callback = new Common::Callback<DropboxListDirectoryRequest, Networking::JsonResponse>(this, &DropboxListDirectoryRequest::responseCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<DropboxListDirectoryRequest, Networking::ErrorResponse>(this, &DropboxListDirectoryRequest::errorCallback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, failureCallback, DROPBOX_API_LIST_FOLDER); + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Content-Type: application/json"); + + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("path", new Common::JSONValue(_requestedPath)); + jsonRequestParameters.setVal("recursive", new Common::JSONValue(_requestedRecursive)); + jsonRequestParameters.setVal("include_media_info", new Common::JSONValue(false)); + jsonRequestParameters.setVal("include_deleted", new Common::JSONValue(false)); + + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + _workingRequest = ConnMan.addRequest(request); +} + +void DropboxListDirectoryRequest::responseCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + + if (_ignoreCallback) { + delete response.value; + return; + } + + if (response.request) + _date = response.request->date(); + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + Common::JSONValue *json = response.value; + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + Common::JSONObject responseObject = json->asObject(); + + if (responseObject.contains("error") || responseObject.contains("error_summary")) { + if (responseObject.contains("error_summary") && responseObject.getVal("error_summary")->isString()) { + warning("Dropbox returned error: %s", responseObject.getVal("error_summary")->asString().c_str()); + } + error.failed = true; + error.response = json->stringify(); + finishError(error); + delete json; + return; + } + + //check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults + if (responseObject.contains("entries")) { + if (!responseObject.getVal("entries")->isArray()) { + error.response = Common::String::format( + "\"entries\" found, but that's not an array!\n%s", + responseObject.getVal("entries")->stringify(true).c_str() + ); + finishError(error); + delete json; + return; + } + + Common::JSONArray items = responseObject.getVal("entries")->asArray(); + for (uint32 i = 0; i < items.size(); ++i) { + if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "DropboxListDirectoryRequest")) + continue; + + Common::JSONObject item = items[i]->asObject(); + + if (!Networking::CurlJsonRequest::jsonContainsString(item, "path_lower", "DropboxListDirectoryRequest")) + continue; + if (!Networking::CurlJsonRequest::jsonContainsString(item, ".tag", "DropboxListDirectoryRequest")) + continue; + + Common::String path = item.getVal("path_lower")->asString(); + bool isDirectory = (item.getVal(".tag")->asString() == "folder"); + uint32 size = 0, timestamp = 0; + if (!isDirectory) { + if (!Networking::CurlJsonRequest::jsonContainsString(item, "server_modified", "DropboxListDirectoryRequest")) + continue; + if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(item, "size", "DropboxListDirectoryRequest")) + continue; + + size = item.getVal("size")->asIntegerNumber(); + timestamp = ISO8601::convertToTimestamp(item.getVal("server_modified")->asString()); + } + _files.push_back(StorageFile(path, size, timestamp, isDirectory)); + } + } + + bool hasMore = false; + if (responseObject.contains("has_more")) { + if (!responseObject.getVal("has_more")->isBool()) { + warning("DropboxListDirectoryRequest: \"has_more\" is not a boolean"); + debug(9, "%s", responseObject.getVal("has_more")->stringify(true).c_str()); + error.response = "\"has_more\" is not a boolean!"; + finishError(error); + delete json; + return; + } + + hasMore = responseObject.getVal("has_more")->asBool(); + } + + if (hasMore) { + if (!Networking::CurlJsonRequest::jsonContainsString(responseObject, "cursor", "DropboxListDirectoryRequest")) { + error.response = "\"has_more\" found, but \"cursor\" is not (or it's not a string)!"; + finishError(error); + delete json; + return; + } + + Networking::JsonCallback callback = new Common::Callback<DropboxListDirectoryRequest, Networking::JsonResponse>(this, &DropboxListDirectoryRequest::responseCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<DropboxListDirectoryRequest, Networking::ErrorResponse>(this, &DropboxListDirectoryRequest::errorCallback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, failureCallback, DROPBOX_API_LIST_FOLDER_CONTINUE); + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Content-Type: application/json"); + + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("cursor", new Common::JSONValue(responseObject.getVal("cursor")->asString())); + + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + _workingRequest = ConnMan.addRequest(request); + } else { + finishListing(_files); + } + + delete json; +} + +void DropboxListDirectoryRequest::errorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void DropboxListDirectoryRequest::handle() {} + +void DropboxListDirectoryRequest::restart() { start(); } + +Common::String DropboxListDirectoryRequest::date() const { return _date; } + +void DropboxListDirectoryRequest::finishListing(Common::Array<StorageFile> &files) { + Request::finishSuccess(); + if (_listDirectoryCallback) + (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files)); +} + +} // End of namespace Dropbox +} // End of namespace Cloud diff --git a/backends/cloud/dropbox/dropboxlistdirectoryrequest.h b/backends/cloud/dropbox/dropboxlistdirectoryrequest.h new file mode 100644 index 0000000000..5c0d8dfa21 --- /dev/null +++ b/backends/cloud/dropbox/dropboxlistdirectoryrequest.h @@ -0,0 +1,61 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_CLOUD_DROPBOX_DROPBOXLISTDIRECTORYREQUEST_H +#define BACKENDS_CLOUD_DROPBOX_DROPBOXLISTDIRECTORYREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace Dropbox { + +class DropboxListDirectoryRequest: public Networking::Request { + Common::String _requestedPath; + bool _requestedRecursive; + + Storage::ListDirectoryCallback _listDirectoryCallback; + Common::String _token; + Common::Array<StorageFile> _files; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void responseCallback(Networking::JsonResponse response); + void errorCallback(Networking::ErrorResponse error); + void finishListing(Common::Array<StorageFile> &files); +public: + DropboxListDirectoryRequest(Common::String token, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false); + virtual ~DropboxListDirectoryRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace Dropbox +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/dropbox/dropboxstorage.cpp b/backends/cloud/dropbox/dropboxstorage.cpp new file mode 100644 index 0000000000..d12070316b --- /dev/null +++ b/backends/cloud/dropbox/dropboxstorage.cpp @@ -0,0 +1,198 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/dropbox/dropboxstorage.h" +#include "backends/cloud/dropbox/dropboxcreatedirectoryrequest.h" +#include "backends/cloud/dropbox/dropboxinforequest.h" +#include "backends/cloud/dropbox/dropboxlistdirectoryrequest.h" +#include "backends/cloud/dropbox/dropboxuploadrequest.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +#ifdef ENABLE_RELEASE +#include "dists/clouds/cloud_keys.h" +#endif + +namespace Cloud { +namespace Dropbox { + +#define DROPBOX_OAUTH2_TOKEN "https://api.dropboxapi.com/oauth2/token" +#define DROPBOX_API_FILES_DOWNLOAD "https://content.dropboxapi.com/2/files/download" + +char *DropboxStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth +char *DropboxStorage::SECRET = nullptr; + +void DropboxStorage::loadKeyAndSecret() { +#ifdef ENABLE_RELEASE + KEY = RELEASE_DROPBOX_KEY; + SECRET = RELEASE_DROPBOX_SECRET; +#else + Common::String k = ConfMan.get("DROPBOX_KEY", ConfMan.kCloudDomain); + KEY = new char[k.size() + 1]; + memcpy(KEY, k.c_str(), k.size()); + KEY[k.size()] = 0; + + k = ConfMan.get("DROPBOX_SECRET", ConfMan.kCloudDomain); + SECRET = new char[k.size() + 1]; + memcpy(SECRET, k.c_str(), k.size()); + SECRET[k.size()] = 0; +#endif +} + +DropboxStorage::DropboxStorage(Common::String accessToken, Common::String userId): _token(accessToken), _uid(userId) {} + +DropboxStorage::DropboxStorage(Common::String code) { + getAccessToken(code); +} + +DropboxStorage::~DropboxStorage() {} + +void DropboxStorage::getAccessToken(Common::String code) { + if (!KEY || !SECRET) + loadKeyAndSecret(); + Networking::JsonCallback callback = new Common::Callback<DropboxStorage, Networking::JsonResponse>(this, &DropboxStorage::codeFlowComplete); + Networking::ErrorCallback errorCallback = new Common::Callback<DropboxStorage, Networking::ErrorResponse>(this, &DropboxStorage::codeFlowFailed); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, errorCallback, DROPBOX_OAUTH2_TOKEN); + request->addPostField("code=" + code); + request->addPostField("grant_type=authorization_code"); + request->addPostField("client_id=" + Common::String(KEY)); + request->addPostField("client_secret=" + Common::String(SECRET)); + if (Cloud::CloudManager::couldUseLocalServer()) { + request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2F"); + } else { + request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code"); + } + addRequest(request); +} + +void DropboxStorage::codeFlowComplete(Networking::JsonResponse response) { + Common::JSONValue *json = (Common::JSONValue *)response.value; + if (json == nullptr) { + debug(9, "DropboxStorage::codeFlowComplete: got NULL instead of JSON!"); + CloudMan.removeStorage(this); + return; + } + + if (!json->isObject()) { + debug(9, "DropboxStorage::codeFlowComplete: Passed JSON is not an object!"); + CloudMan.removeStorage(this); + delete json; + return; + } + + Common::JSONObject result = json->asObject(); + if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "DropboxStorage::codeFlowComplete") || + !Networking::CurlJsonRequest::jsonContainsString(result, "uid", "DropboxStorage::codeFlowComplete")) { + warning("DropboxStorage: bad response, no token/uid passed"); + debug(9, "%s", json->stringify(true).c_str()); + CloudMan.removeStorage(this); + } else { + _token = result.getVal("access_token")->asString(); + _uid = result.getVal("uid")->asString(); + ConfMan.removeKey("dropbox_code", ConfMan.kCloudDomain); + CloudMan.replaceStorage(this, kStorageDropboxId); + ConfMan.flushToDisk(); + } + + delete json; +} + +void DropboxStorage::codeFlowFailed(Networking::ErrorResponse error) { + debug(9, "DropboxStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode); + debug(9, "%s", error.response.c_str()); + CloudMan.removeStorage(this); +} + +void DropboxStorage::saveConfig(Common::String keyPrefix) { + ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain); + ConfMan.set(keyPrefix + "user_id", _uid, ConfMan.kCloudDomain); +} + +Common::String DropboxStorage::name() const { + return "Dropbox"; +} + +Networking::Request *DropboxStorage::listDirectory(Common::String path, ListDirectoryCallback outerCallback, Networking::ErrorCallback errorCallback, bool recursive) { + return addRequest(new DropboxListDirectoryRequest(_token, path, outerCallback, errorCallback, recursive)); +} + +Networking::Request *DropboxStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) { + return addRequest(new DropboxUploadRequest(_token, path, contents, callback, errorCallback)); +} + +Networking::Request *DropboxStorage::streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) { + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("path", new Common::JSONValue(path)); + Common::JSONValue value(jsonRequestParameters); + + Networking::CurlRequest *request = new Networking::CurlRequest(nullptr, nullptr, DROPBOX_API_FILES_DOWNLOAD); //TODO: is it OK to pass no callbacks? + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Dropbox-API-Arg: " + Common::JSON::stringify(&value)); + request->addHeader("Content-Type: "); //required to be empty (as we do POST, it's usually app/form-url-encoded) + + Networking::NetworkReadStreamResponse response = request->execute(); + if (callback) + (*callback)(response); + return response.request; // no leak here, response.request == request +} + +Networking::Request *DropboxStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + return addRequest(new DropboxCreateDirectoryRequest(_token, path, callback, errorCallback)); +} + +Networking::Request *DropboxStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + return addRequest(new DropboxInfoRequest(_token, callback, errorCallback)); +} + +Common::String DropboxStorage::savesDirectoryPath() { return "/saves/"; } + +DropboxStorage *DropboxStorage::loadFromConfig(Common::String keyPrefix) { + loadKeyAndSecret(); + + if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) { + warning("DropboxStorage: no access_token found"); + return nullptr; + } + + if (!ConfMan.hasKey(keyPrefix + "user_id", ConfMan.kCloudDomain)) { + warning("DropboxStorage: no user_id found"); + return nullptr; + } + + Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain); + Common::String userId = ConfMan.get(keyPrefix + "user_id", ConfMan.kCloudDomain); + + return new DropboxStorage(accessToken, userId); +} + +} // End of namespace Dropbox +} // End of namespace Cloud diff --git a/backends/cloud/dropbox/dropboxstorage.h b/backends/cloud/dropbox/dropboxstorage.h new file mode 100644 index 0000000000..0a0043abee --- /dev/null +++ b/backends/cloud/dropbox/dropboxstorage.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_DROPBOX_STORAGE_H +#define BACKENDS_CLOUD_DROPBOX_STORAGE_H + +#include "backends/cloud/storage.h" +#include "common/callback.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace Dropbox { + +class DropboxStorage: public Cloud::Storage { + static char *KEY, *SECRET; + + static void loadKeyAndSecret(); + + Common::String _token, _uid; + + /** This private constructor is called from loadFromConfig(). */ + DropboxStorage(Common::String token, Common::String uid); + + void getAccessToken(Common::String code); + void codeFlowComplete(Networking::JsonResponse response); + void codeFlowFailed(Networking::ErrorResponse error); + +public: + /** This constructor uses OAuth code flow to get tokens. */ + DropboxStorage(Common::String code); + virtual ~DropboxStorage(); + + /** + * Storage methods, which are used by CloudManager to save + * storage in configuration file. + */ + + /** + * Save storage data using ConfMan. + * @param keyPrefix all saved keys must start with this prefix. + * @note every Storage must write keyPrefix + "type" key + * with common value (e.g. "Dropbox"). + */ + virtual void saveConfig(Common::String keyPrefix); + + /** + * Return unique storage name. + * @returns some unique storage name (for example, "Dropbox (user@example.com)") + */ + virtual Common::String name() const; + + /** Public Cloud API comes down there. */ + + /** Returns ListDirectoryStatus struct with list of files. */ + virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + + /** Returns UploadStatus struct with info about uploaded file. */ + virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + + /** Calls the callback when finished. */ + virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns the StorageInfo struct. */ + virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns storage's saves directory path with the trailing slash. */ + virtual Common::String savesDirectoryPath(); + + /** + * Load token and user id from configs and return DropboxStorage for those. + * @return pointer to the newly created DropboxStorage or 0 if some problem occured. + */ + static DropboxStorage *loadFromConfig(Common::String keyPrefix); +}; + +} // End of namespace Dropbox +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/dropbox/dropboxuploadrequest.cpp b/backends/cloud/dropbox/dropboxuploadrequest.cpp new file mode 100644 index 0000000000..2c9dcc4109 --- /dev/null +++ b/backends/cloud/dropbox/dropboxuploadrequest.cpp @@ -0,0 +1,204 @@ +/* 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 "backends/cloud/dropbox/dropboxuploadrequest.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace Dropbox { + +#define DROPBOX_API_FILES_UPLOAD "https://content.dropboxapi.com/2/files/upload" +#define DROPBOX_API_FILES_UPLOAD_SESSION "https://content.dropboxapi.com/2/files/upload_session/" + +DropboxUploadRequest::DropboxUploadRequest(Common::String token, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _token(token), _savePath(path), _contentsStream(contents), _uploadCallback(callback), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +DropboxUploadRequest::~DropboxUploadRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _contentsStream; + delete _uploadCallback; +} + +void DropboxUploadRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + if (!_contentsStream) { + warning("DropboxUploadRequest: cannot start because stream is invalid"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + if (!_contentsStream->seek(0)) { + warning("DropboxUploadRequest: cannot restart because stream couldn't seek(0)"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + _ignoreCallback = false; + + uploadNextPart(); +} + +void DropboxUploadRequest::uploadNextPart() { + const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024; + + Common::String url = DROPBOX_API_FILES_UPLOAD_SESSION; + Common::JSONObject jsonRequestParameters; + + if (_contentsStream->pos() == 0 || _sessionId == "") { + if ((uint32)_contentsStream->size() <= UPLOAD_PER_ONE_REQUEST) { + url = DROPBOX_API_FILES_UPLOAD; + jsonRequestParameters.setVal("path", new Common::JSONValue(_savePath)); + jsonRequestParameters.setVal("mode", new Common::JSONValue("overwrite")); + jsonRequestParameters.setVal("autorename", new Common::JSONValue(false)); + jsonRequestParameters.setVal("mute", new Common::JSONValue(false)); + } else { + url += "start"; + jsonRequestParameters.setVal("close", new Common::JSONValue(false)); + } + } else { + if ((uint32)(_contentsStream->size() - _contentsStream->pos()) <= UPLOAD_PER_ONE_REQUEST) { + url += "finish"; + Common::JSONObject jsonCursor, jsonCommit; + jsonCursor.setVal("session_id", new Common::JSONValue(_sessionId)); + jsonCursor.setVal("offset", new Common::JSONValue((long long int)_contentsStream->pos())); + jsonCommit.setVal("path", new Common::JSONValue(_savePath)); + jsonCommit.setVal("mode", new Common::JSONValue("overwrite")); + jsonCommit.setVal("autorename", new Common::JSONValue(false)); + jsonCommit.setVal("mute", new Common::JSONValue(false)); + jsonRequestParameters.setVal("cursor", new Common::JSONValue(jsonCursor)); + jsonRequestParameters.setVal("commit", new Common::JSONValue(jsonCommit)); + } else { + url += "append_v2"; + Common::JSONObject jsonCursor; + jsonCursor.setVal("session_id", new Common::JSONValue(_sessionId)); + jsonCursor.setVal("offset", new Common::JSONValue((long long int)_contentsStream->pos())); + jsonRequestParameters.setVal("cursor", new Common::JSONValue(jsonCursor)); + jsonRequestParameters.setVal("close", new Common::JSONValue(false)); + } + } + + Common::JSONValue value(jsonRequestParameters); + Networking::JsonCallback callback = new Common::Callback<DropboxUploadRequest, Networking::JsonResponse>(this, &DropboxUploadRequest::partUploadedCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<DropboxUploadRequest, Networking::ErrorResponse>(this, &DropboxUploadRequest::partUploadedErrorCallback); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, failureCallback, url); + request->addHeader("Authorization: Bearer " + _token); + request->addHeader("Content-Type: application/octet-stream"); + request->addHeader("Dropbox-API-Arg: " + Common::JSON::stringify(&value)); + + byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST]; + uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST); + request->setBuffer(buffer, size); + + _workingRequest = ConnMan.addRequest(request); +} + +void DropboxUploadRequest::partUploadedCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Networking::ErrorResponse error(this, false, true, "", -1); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + Common::JSONValue *json = response.value; + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + bool needsFinishRequest = false; + + if (json->isObject()) { + Common::JSONObject object = json->asObject(); + + //debug(9, "%s", json->stringify(true).c_str()); + + if (object.contains("error") || object.contains("error_summary")) { + if (Networking::CurlJsonRequest::jsonContainsString(object, "error_summary", "DropboxUploadRequest")) { + warning("Dropbox returned error: %s", object.getVal("error_summary")->asString().c_str()); + } + error.response = json->stringify(true); + finishError(error); + delete json; + return; + } + + if (Networking::CurlJsonRequest::jsonContainsString(object, "path_lower", "DropboxUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(object, "server_modified", "DropboxUploadRequest") && + Networking::CurlJsonRequest::jsonContainsIntegerNumber(object, "size", "DropboxUploadRequest")) { + //finished + Common::String path = object.getVal("path_lower")->asString(); + uint32 size = object.getVal("size")->asIntegerNumber(); + uint32 timestamp = ISO8601::convertToTimestamp(object.getVal("server_modified")->asString()); + finishUpload(StorageFile(path, size, timestamp, false)); + return; + } + + if (_sessionId == "") { + if (Networking::CurlJsonRequest::jsonContainsString(object, "session_id", "DropboxUploadRequest")) + _sessionId = object.getVal("session_id")->asString(); + needsFinishRequest = true; + } + } + + if (!needsFinishRequest && (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1)) { + warning("DropboxUploadRequest: no file info to return"); + finishUpload(StorageFile(_savePath, 0, 0, false)); + } else { + uploadNextPart(); + } + + delete json; +} + +void DropboxUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void DropboxUploadRequest::handle() {} + +void DropboxUploadRequest::restart() { start(); } + +void DropboxUploadRequest::finishUpload(StorageFile file) { + Request::finishSuccess(); + if (_uploadCallback) + (*_uploadCallback)(Storage::UploadResponse(this, file)); +} + +} // End of namespace Dropbox +} // End of namespace Cloud diff --git a/backends/cloud/dropbox/dropboxuploadrequest.h b/backends/cloud/dropbox/dropboxuploadrequest.h new file mode 100644 index 0000000000..5adf5a6df2 --- /dev/null +++ b/backends/cloud/dropbox/dropboxuploadrequest.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_DROPBOX_DROPBOXUPLOADREQUEST_H +#define BACKENDS_CLOUD_DROPBOX_DROPBOXUPLOADREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Dropbox { + +class DropboxUploadRequest: public Networking::Request { + Common::String _token; + Common::String _savePath; + Common::SeekableReadStream *_contentsStream; + Storage::UploadCallback _uploadCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _sessionId; + + void start(); + void uploadNextPart(); + void partUploadedCallback(Networking::JsonResponse response); + void partUploadedErrorCallback(Networking::ErrorResponse error); + void finishUpload(StorageFile status); + +public: + DropboxUploadRequest(Common::String token, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb); + virtual ~DropboxUploadRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace Dropbox +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/folderdownloadrequest.cpp b/backends/cloud/folderdownloadrequest.cpp new file mode 100644 index 0000000000..7eeee0c6d6 --- /dev/null +++ b/backends/cloud/folderdownloadrequest.cpp @@ -0,0 +1,197 @@ +/* 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 "backends/cloud/folderdownloadrequest.h" +#include "backends/cloud/downloadrequest.h" +#include "backends/cloud/id/iddownloadrequest.h" +#include "common/debug.h" +#include "gui/downloaddialog.h" +#include <backends/networking/curl/connectionmanager.h> + +namespace Cloud { + +FolderDownloadRequest::FolderDownloadRequest(Storage *storage, Storage::FileArrayCallback callback, Networking::ErrorCallback ecb, Common::String remoteDirectoryPath, Common::String localDirectoryPath, bool recursive): + Request(nullptr, ecb), CommandSender(nullptr), _storage(storage), _fileArrayCallback(callback), + _remoteDirectoryPath(remoteDirectoryPath), _localDirectoryPath(localDirectoryPath), _recursive(recursive), + _workingRequest(nullptr), _ignoreCallback(false), _totalFiles(0) { + start(); +} + +FolderDownloadRequest::~FolderDownloadRequest() { + sendCommand(GUI::kDownloadEndedCmd, 0); + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _fileArrayCallback; +} + +void FolderDownloadRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _currentFile = StorageFile(); + _pendingFiles.clear(); + _failedFiles.clear(); + _ignoreCallback = false; + _totalFiles = 0; + _downloadedBytes = _totalBytes = _wasDownloadedBytes = _currentDownloadSpeed = 0; + + //list directory first + _workingRequest = _storage->listDirectory( + _remoteDirectoryPath, + new Common::Callback<FolderDownloadRequest, Storage::ListDirectoryResponse>(this, &FolderDownloadRequest::directoryListedCallback), + new Common::Callback<FolderDownloadRequest, Networking::ErrorResponse>(this, &FolderDownloadRequest::directoryListedErrorCallback), + _recursive + ); +} + +void FolderDownloadRequest::directoryListedCallback(Storage::ListDirectoryResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + _pendingFiles = response.value; + + // remove all directories + // non-empty directories would be created by DumpFile, and empty ones are just ignored + for (Common::Array<StorageFile>::iterator i = _pendingFiles.begin(); i != _pendingFiles.end();) + if (i->isDirectory()) + _pendingFiles.erase(i); + else { + _totalBytes += i->size(); + ++i; + } + + _totalFiles = _pendingFiles.size(); + downloadNextFile(); +} + +void FolderDownloadRequest::directoryListedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void FolderDownloadRequest::fileDownloadedCallback(Storage::BoolResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (!response.value) _failedFiles.push_back(_currentFile); + _downloadedBytes += _currentFile.size(); + downloadNextFile(); +} + +void FolderDownloadRequest::fileDownloadedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + fileDownloadedCallback(Storage::BoolResponse(error.request, false)); +} + +void FolderDownloadRequest::downloadNextFile() { + do { + if (_pendingFiles.empty()) { + sendCommand(GUI::kDownloadEndedCmd, 0); + finishDownload(_failedFiles); + return; + } + + _currentFile = _pendingFiles.back(); + _pendingFiles.pop_back(); + } while (_currentFile.isDirectory()); // directories are actually removed earlier, in the directoryListedCallback() + + sendCommand(GUI::kDownloadProgressCmd, (int)(getProgress() * 100)); + + Common::String remotePath = _currentFile.path(); + Common::String localPath = remotePath; + if (_remoteDirectoryPath == "" || remotePath.hasPrefix(_remoteDirectoryPath)) { + localPath.erase(0, _remoteDirectoryPath.size()); + if (_remoteDirectoryPath != "" && (_remoteDirectoryPath.lastChar() != '/' && _remoteDirectoryPath.lastChar() != '\\')) + localPath.erase(0, 1); + } else { + warning("FolderDownloadRequest: Can't process the following paths:"); + warning("remote directory: %s", _remoteDirectoryPath.c_str()); + warning("remote file under that directory: %s", remotePath.c_str()); + } + if (_localDirectoryPath != "") { + if (_localDirectoryPath.lastChar() == '/' || _localDirectoryPath.lastChar() == '\\') + localPath = _localDirectoryPath + localPath; + else + localPath = _localDirectoryPath + "/" + localPath; + } + debug(9, "FolderDownloadRequest: %s -> %s", remotePath.c_str(), localPath.c_str()); + _workingRequest = _storage->downloadById( + _currentFile.id(), localPath, + new Common::Callback<FolderDownloadRequest, Storage::BoolResponse>(this, &FolderDownloadRequest::fileDownloadedCallback), + new Common::Callback<FolderDownloadRequest, Networking::ErrorResponse>(this, &FolderDownloadRequest::fileDownloadedErrorCallback) + ); +} + +void FolderDownloadRequest::handle() { + uint32 microsecondsPassed = Networking::ConnectionManager::getCloudRequestsPeriodInMicroseconds(); + uint64 currentDownloadedBytes = getDownloadedBytes(); + uint64 downloadedThisPeriod = currentDownloadedBytes - _wasDownloadedBytes; + _currentDownloadSpeed = downloadedThisPeriod * (1000000L / microsecondsPassed); + _wasDownloadedBytes = currentDownloadedBytes; +} + +void FolderDownloadRequest::restart() { start(); } + +void FolderDownloadRequest::finishDownload(Common::Array<StorageFile> &files) { + Request::finishSuccess(); + if (_fileArrayCallback) + (*_fileArrayCallback)(Storage::FileArrayResponse(this, files)); +} + +double FolderDownloadRequest::getProgress() const { + if (_totalFiles == 0 || _totalBytes == 0) + return 0; + return (double)getDownloadedBytes() / (double)getTotalBytesToDownload(); +} + +uint64 FolderDownloadRequest::getDownloadedBytes() const { + if (_totalFiles == 0) + return 0; + + double currentFileProgress = 0; + DownloadRequest *downloadRequest = dynamic_cast<DownloadRequest *>(_workingRequest); + if (downloadRequest != nullptr) { + currentFileProgress = downloadRequest->getProgress(); + } else { + Id::IdDownloadRequest *idDownloadRequest = dynamic_cast<Id::IdDownloadRequest *>(_workingRequest); + if (idDownloadRequest != nullptr) + currentFileProgress = idDownloadRequest->getProgress(); + } + + return _downloadedBytes + (uint64)(currentFileProgress * _currentFile.size()); +} + +uint64 FolderDownloadRequest::getTotalBytesToDownload() const { + return _totalBytes; +} + +uint64 FolderDownloadRequest::getDownloadSpeed() const { + return _currentDownloadSpeed; +} + +} // End of namespace Cloud diff --git a/backends/cloud/folderdownloadrequest.h b/backends/cloud/folderdownloadrequest.h new file mode 100644 index 0000000000..08fa193e07 --- /dev/null +++ b/backends/cloud/folderdownloadrequest.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_FOLDERDOWNLOADREQUEST_H +#define BACKENDS_CLOUD_FOLDERDOWNLOADREQUEST_H + +#include "backends/networking/curl/request.h" +#include "backends/cloud/storage.h" +#include "gui/object.h" + +namespace Cloud { + +class FolderDownloadRequest: public Networking::Request, public GUI::CommandSender { + Storage *_storage; + Storage::FileArrayCallback _fileArrayCallback; + Common::String _remoteDirectoryPath, _localDirectoryPath; + bool _recursive; + Common::Array<StorageFile> _pendingFiles, _failedFiles; + StorageFile _currentFile; + Request *_workingRequest; + bool _ignoreCallback; + uint32 _totalFiles; + uint64 _downloadedBytes, _totalBytes, _wasDownloadedBytes, _currentDownloadSpeed; + + void start(); + void directoryListedCallback(Storage::ListDirectoryResponse response); + void directoryListedErrorCallback(Networking::ErrorResponse error); + void fileDownloadedCallback(Storage::BoolResponse response); + void fileDownloadedErrorCallback(Networking::ErrorResponse error); + void downloadNextFile(); + void finishDownload(Common::Array<StorageFile> &files); +public: + FolderDownloadRequest(Storage *storage, Storage::FileArrayCallback callback, Networking::ErrorCallback ecb, Common::String remoteDirectoryPath, Common::String localDirectoryPath, bool recursive); + virtual ~FolderDownloadRequest(); + + virtual void handle(); + virtual void restart(); + + /** Returns a number in range [0, 1], where 1 is "complete". */ + double getProgress() const; + + /** Returns a number of downloaded bytes. */ + uint64 getDownloadedBytes() const; + + /** Returns a total number of bytes to download. */ + uint64 getTotalBytesToDownload() const; + + /** Returns average download speed for the last second. */ + uint64 getDownloadSpeed() const; + + /** Returns remote directory path. */ + Common::String getRemotePath() const { return _remoteDirectoryPath; } + + /** Returns local directory path. */ + Common::String getLocalPath() const { return _localDirectoryPath; } +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp new file mode 100644 index 0000000000..52611126a0 --- /dev/null +++ b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp @@ -0,0 +1,163 @@ +/* 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 "backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h" +#include "backends/cloud/googledrive/googledrivestorage.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" +#include "googledrivetokenrefresher.h" + +namespace Cloud { +namespace GoogleDrive { + +#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/drive/v3/files?spaces=drive&fields=files%28id,mimeType,modifiedTime,name,size%29,nextPageToken&orderBy=folder,name" +//files(id,mimeType,modifiedTime,name,size),nextPageToken + +GoogleDriveListDirectoryByIdRequest::GoogleDriveListDirectoryByIdRequest(GoogleDriveStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _requestedId(id), _storage(storage), _listDirectoryCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +GoogleDriveListDirectoryByIdRequest::~GoogleDriveListDirectoryByIdRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _listDirectoryCallback; +} + +void GoogleDriveListDirectoryByIdRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _files.clear(); + _ignoreCallback = false; + + makeRequest(""); +} + +void GoogleDriveListDirectoryByIdRequest::makeRequest(Common::String pageToken) { + Common::String url = GOOGLEDRIVE_API_FILES; + if (pageToken != "") + url += "&pageToken=" + pageToken; + url += "&q=%27" + _requestedId + "%27+in+parents"; + + Networking::JsonCallback callback = new Common::Callback<GoogleDriveListDirectoryByIdRequest, Networking::JsonResponse>(this, &GoogleDriveListDirectoryByIdRequest::responseCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveListDirectoryByIdRequest, Networking::ErrorResponse>(this, &GoogleDriveListDirectoryByIdRequest::errorCallback); + Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + _workingRequest = ConnMan.addRequest(request); +} + +void GoogleDriveListDirectoryByIdRequest::responseCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) { + delete response.value; + return; + } + if (response.request) + _date = response.request->date(); + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + Common::JSONValue *json = response.value; + if (json) { + Common::JSONObject responseObject = json->asObject(); + + ///debug("%s", json->stringify(true).c_str()); + + if (responseObject.contains("error") || responseObject.contains("error_summary")) { + warning("GoogleDrive returned error: %s", responseObject.getVal("error_summary")->asString().c_str()); + error.failed = true; + error.response = json->stringify(); + finishError(error); + delete json; + return; + } + + //TODO: check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults + + if (responseObject.contains("files") && responseObject.getVal("files")->isArray()) { + Common::JSONArray items = responseObject.getVal("files")->asArray(); + for (uint32 i = 0; i < items.size(); ++i) { + Common::JSONObject item = items[i]->asObject(); + Common::String id = item.getVal("id")->asString(); + Common::String name = item.getVal("name")->asString(); + bool isDirectory = (item.getVal("mimeType")->asString() == "application/vnd.google-apps.folder"); + uint32 size = 0, timestamp = 0; + if (item.contains("size") && item.getVal("size")->isString()) + size = item.getVal("size")->asString().asUint64(); + if (item.contains("modifiedTime") && item.getVal("modifiedTime")->isString()) + timestamp = ISO8601::convertToTimestamp(item.getVal("modifiedTime")->asString()); + + //as we list directory by id, we can't determine full path for the file, so we leave it empty + _files.push_back(StorageFile(id, "", name, size, timestamp, isDirectory)); + } + } + + bool hasMore = (responseObject.contains("nextPageToken")); + + if (hasMore) { + Common::String token = responseObject.getVal("nextPageToken")->asString(); + makeRequest(token); + } else { + finishListing(_files); + } + } else { + warning("null, not json"); + error.failed = true; + finishError(error); + } + + delete json; +} + +void GoogleDriveListDirectoryByIdRequest::errorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void GoogleDriveListDirectoryByIdRequest::handle() {} + +void GoogleDriveListDirectoryByIdRequest::restart() { start(); } + +Common::String GoogleDriveListDirectoryByIdRequest::date() const { return _date; } + +void GoogleDriveListDirectoryByIdRequest::finishListing(Common::Array<StorageFile> &files) { + Request::finishSuccess(); + if (_listDirectoryCallback) + (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files)); +} + +} // End of namespace GoogleDrive +} // End of namespace Cloud diff --git a/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h new file mode 100644 index 0000000000..e94c6b1f4e --- /dev/null +++ b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVELISTDIRECTORYBYIDREQUEST_H +#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVELISTDIRECTORYBYIDREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace GoogleDrive { + +class GoogleDriveStorage; + +class GoogleDriveListDirectoryByIdRequest: public Networking::Request { + Common::String _requestedId; + GoogleDriveStorage *_storage; + + Storage::ListDirectoryCallback _listDirectoryCallback; + Common::Array<StorageFile> _files; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void makeRequest(Common::String pageToken); + void responseCallback(Networking::JsonResponse response); + void errorCallback(Networking::ErrorResponse error); + void finishListing(Common::Array<StorageFile> &files); +public: + GoogleDriveListDirectoryByIdRequest(GoogleDriveStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb); + virtual ~GoogleDriveListDirectoryByIdRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace GoogleDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/googledrive/googledrivestorage.cpp b/backends/cloud/googledrive/googledrivestorage.cpp new file mode 100644 index 0000000000..1b4b8baf56 --- /dev/null +++ b/backends/cloud/googledrive/googledrivestorage.cpp @@ -0,0 +1,348 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/googledrive/googledrivestorage.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/cloud/googledrive/googledrivetokenrefresher.h" +#include "backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h" +#include "backends/cloud/googledrive/googledriveuploadrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +#ifdef ENABLE_RELEASE +#include "dists/clouds/cloud_keys.h" +#endif + +namespace Cloud { +namespace GoogleDrive { + +#define GOOGLEDRIVE_OAUTH2_TOKEN "https://accounts.google.com/o/oauth2/token" +#define GOOGLEDRIVE_API_FILES_ALT_MEDIA "https://www.googleapis.com/drive/v3/files/%s?alt=media" +#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/drive/v3/files" +#define GOOGLEDRIVE_API_ABOUT "https://www.googleapis.com/drive/v3/about?fields=storageQuota,user" + +char *GoogleDriveStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth +char *GoogleDriveStorage::SECRET = nullptr; + +void GoogleDriveStorage::loadKeyAndSecret() { +#ifdef ENABLE_RELEASE + KEY = RELEASE_GOOGLE_DRIVE_KEY; + SECRET = RELEASE_GOOGLE_DRIVE_SECRET; +#else + Common::String k = ConfMan.get("GOOGLE_DRIVE_KEY", ConfMan.kCloudDomain); + KEY = new char[k.size() + 1]; + memcpy(KEY, k.c_str(), k.size()); + KEY[k.size()] = 0; + + k = ConfMan.get("GOOGLE_DRIVE_SECRET", ConfMan.kCloudDomain); + SECRET = new char[k.size() + 1]; + memcpy(SECRET, k.c_str(), k.size()); + SECRET[k.size()] = 0; +#endif +} + +GoogleDriveStorage::GoogleDriveStorage(Common::String accessToken, Common::String refreshToken): + _token(accessToken), _refreshToken(refreshToken) {} + +GoogleDriveStorage::GoogleDriveStorage(Common::String code) { + getAccessToken( + new Common::Callback<GoogleDriveStorage, BoolResponse>(this, &GoogleDriveStorage::codeFlowComplete), + new Common::Callback<GoogleDriveStorage, Networking::ErrorResponse>(this, &GoogleDriveStorage::codeFlowFailed), + code + ); +} + +GoogleDriveStorage::~GoogleDriveStorage() {} + +void GoogleDriveStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) { + if (!KEY || !SECRET) loadKeyAndSecret(); + bool codeFlow = (code != ""); + + if (!codeFlow && _refreshToken == "") { + warning("GoogleDriveStorage: no refresh token available to get new access token."); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + return; + } + + Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, BoolResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::tokenRefreshed, callback); + if (errorCallback == nullptr) + errorCallback = getErrorPrintingCallback(); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, GOOGLEDRIVE_OAUTH2_TOKEN); + if (codeFlow) { + request->addPostField("code=" + code); + request->addPostField("grant_type=authorization_code"); + } else { + request->addPostField("refresh_token=" + _refreshToken); + request->addPostField("grant_type=refresh_token"); + } + request->addPostField("client_id=" + Common::String(KEY)); + request->addPostField("client_secret=" + Common::String(SECRET)); + if (Cloud::CloudManager::couldUseLocalServer()) { + request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345"); + } else { + request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code"); + } + addRequest(request); +} + +void GoogleDriveStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("GoogleDriveStorage: got NULL instead of JSON"); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + delete callback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "GoogleDriveStorage")) { + if (callback) + (*callback)(BoolResponse(nullptr, false)); + delete json; + delete callback; + return; + } + + Common::JSONObject result = json->asObject(); + if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "GoogleDriveStorage")) { + warning("GoogleDriveStorage: bad response, no token passed"); + debug(9, "%s", json->stringify().c_str()); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + } else { + _token = result.getVal("access_token")->asString(); + if (!Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "GoogleDriveStorage")) + warning("GoogleDriveStorage: no refresh_token passed"); + else + _refreshToken = result.getVal("refresh_token")->asString(); + CloudMan.save(); //ask CloudManager to save our new refreshToken + if (callback) + (*callback)(BoolResponse(nullptr, true)); + } + delete json; + delete callback; +} + +void GoogleDriveStorage::codeFlowComplete(BoolResponse response) { + if (!response.value) { + warning("GoogleDriveStorage: failed to get access token through code flow"); + CloudMan.removeStorage(this); + return; + } + + ConfMan.removeKey("googledrive_code", ConfMan.kCloudDomain); + CloudMan.replaceStorage(this, kStorageGoogleDriveId); + ConfMan.flushToDisk(); +} + +void GoogleDriveStorage::codeFlowFailed(Networking::ErrorResponse error) { + debug(9, "GoogleDriveStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode); + debug(9, "%s", error.response.c_str()); + CloudMan.removeStorage(this); +} + +void GoogleDriveStorage::saveConfig(Common::String keyPrefix) { + ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain); + ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain); +} + +Common::String GoogleDriveStorage::name() const { + return "Google Drive"; +} + +void GoogleDriveStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("GoogleDriveStorage::infoInnerCallback: NULL passed instead of JSON"); + delete outerCallback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "GoogleDriveStorage::infoInnerCallback")) { + delete json; + delete outerCallback; + return; + } + + Common::JSONObject info = json->asObject(); + + Common::String uid, name, email; + uint64 quotaUsed = 0, quotaAllocated = 0; + + if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "user", "GoogleDriveStorage::infoInnerCallback") && + Networking::CurlJsonRequest::jsonIsObject(info.getVal("user"), "GoogleDriveStorage::infoInnerCallback")) { + //"me":true, "kind":"drive#user","photoLink": "", + //"displayName":"Alexander Tkachev","emailAddress":"alexander@tkachov.ru","permissionId":"" + Common::JSONObject user = info.getVal("user")->asObject(); + if (Networking::CurlJsonRequest::jsonContainsString(user, "permissionId", "GoogleDriveStorage::infoInnerCallback")) + uid = user.getVal("permissionId")->asString(); //not sure it's user's id, but who cares anyway? + if (Networking::CurlJsonRequest::jsonContainsString(user, "displayName", "GoogleDriveStorage::infoInnerCallback")) + name = user.getVal("displayName")->asString(); + if (Networking::CurlJsonRequest::jsonContainsString(user, "emailAddress", "GoogleDriveStorage::infoInnerCallback")) + email = user.getVal("emailAddress")->asString(); + } + + if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "storageQuota", "GoogleDriveStorage::infoInnerCallback") && + Networking::CurlJsonRequest::jsonIsObject(info.getVal("storageQuota"), "GoogleDriveStorage::infoInnerCallback")) { + //"usageInDrive":"6332462","limit":"18253611008","usage":"6332462","usageInDriveTrash":"0" + Common::JSONObject storageQuota = info.getVal("storageQuota")->asObject(); + + if (Networking::CurlJsonRequest::jsonContainsString(storageQuota, "usage", "GoogleDriveStorage::infoInnerCallback")) { + Common::String usage = storageQuota.getVal("usage")->asString(); + quotaUsed = usage.asUint64(); + } + + if (Networking::CurlJsonRequest::jsonContainsString(storageQuota, "limit", "GoogleDriveStorage::infoInnerCallback")) { + Common::String limit = storageQuota.getVal("limit")->asString(); + quotaAllocated = limit.asUint64(); + } + } + + CloudMan.setStorageUsername(kStorageGoogleDriveId, email); + + if (outerCallback) { + (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated))); + delete outerCallback; + } + + delete json; +} + +void GoogleDriveStorage::createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("GoogleDriveStorage::createDirectoryInnerCallback: NULL passed instead of JSON"); + delete outerCallback; + return; + } + + if (outerCallback) { + if (Networking::CurlJsonRequest::jsonIsObject(json, "GoogleDriveStorage::createDirectoryInnerCallback")) { + Common::JSONObject info = json->asObject(); + (*outerCallback)(BoolResponse(nullptr, info.contains("id"))); + } else { + (*outerCallback)(BoolResponse(nullptr, false)); + } + delete outerCallback; + } + + delete json; +} + +Networking::Request *GoogleDriveStorage::listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + if (!callback) + callback = new Common::Callback<GoogleDriveStorage, FileArrayResponse>(this, &GoogleDriveStorage::printFiles); + return addRequest(new GoogleDriveListDirectoryByIdRequest(this, id, callback, errorCallback)); +} + +Networking::Request *GoogleDriveStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) { + return addRequest(new GoogleDriveUploadRequest(this, path, contents, callback, errorCallback)); +} + +Networking::Request *GoogleDriveStorage::streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) { + if (callback) { + Common::String url = Common::String::format(GOOGLEDRIVE_API_FILES_ALT_MEDIA, ConnMan.urlEncode(id).c_str()); + Common::String header = "Authorization: Bearer " + _token; + curl_slist *headersList = curl_slist_append(nullptr, header.c_str()); + Networking::NetworkReadStream *stream = new Networking::NetworkReadStream(url.c_str(), headersList, ""); + (*callback)(Networking::NetworkReadStreamResponse(nullptr, stream)); + } + delete callback; + delete errorCallback; + return nullptr; +} + +void GoogleDriveStorage::printInfo(StorageInfoResponse response) { + debug(9, "\nGoogleDriveStorage: user info:"); + debug(9, "\tname: %s", response.value.name().c_str()); + debug(9, "\temail: %s", response.value.email().c_str()); + debug(9, "\tdisk usage: %lu/%lu", response.value.used(), response.value.available()); +} + +Networking::Request *GoogleDriveStorage::createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + + Common::String url = GOOGLEDRIVE_API_FILES; + Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, BoolResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::createDirectoryInnerCallback, callback); + Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(this, innerCallback, errorCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + accessToken()); + request->addHeader("Content-Type: application/json"); + + Common::JSONArray parentsArray; + parentsArray.push_back(new Common::JSONValue(parentId)); + + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("mimeType", new Common::JSONValue("application/vnd.google-apps.folder")); + jsonRequestParameters.setVal("name", new Common::JSONValue(name)); + jsonRequestParameters.setVal("parents", new Common::JSONValue(parentsArray)); + + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + return addRequest(request); +} + +Networking::Request *GoogleDriveStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) { + if (!callback) + callback = new Common::Callback<GoogleDriveStorage, StorageInfoResponse>(this, &GoogleDriveStorage::printInfo); + Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, StorageInfoResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::infoInnerCallback, callback); + Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(this, innerCallback, errorCallback, GOOGLEDRIVE_API_ABOUT); + request->addHeader("Authorization: Bearer " + _token); + return addRequest(request); +} + +Common::String GoogleDriveStorage::savesDirectoryPath() { return "scummvm/saves/"; } + +GoogleDriveStorage *GoogleDriveStorage::loadFromConfig(Common::String keyPrefix) { + loadKeyAndSecret(); + + if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) { + warning("GoogleDriveStorage: no access_token found"); + return nullptr; + } + + if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) { + warning("GoogleDriveStorage: no refresh_token found"); + return nullptr; + } + + Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain); + Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain); + return new GoogleDriveStorage(accessToken, refreshToken); +} + +Common::String GoogleDriveStorage::getRootDirectoryId() { + return "root"; +} + +} // End of namespace GoogleDrive +} // End of namespace Cloud diff --git a/backends/cloud/googledrive/googledrivestorage.h b/backends/cloud/googledrive/googledrivestorage.h new file mode 100644 index 0000000000..6a834c44ca --- /dev/null +++ b/backends/cloud/googledrive/googledrivestorage.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVESTORAGE_H +#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVESTORAGE_H + +#include "backends/cloud/id/idstorage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace GoogleDrive { + +class GoogleDriveStorage: public Id::IdStorage { + static char *KEY, *SECRET; + + static void loadKeyAndSecret(); + + Common::String _token, _refreshToken; + + /** This private constructor is called from loadFromConfig(). */ + GoogleDriveStorage(Common::String token, Common::String refreshToken); + + void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response); + void codeFlowComplete(BoolResponse response); + void codeFlowFailed(Networking::ErrorResponse error); + + /** Constructs StorageInfo based on JSON response from cloud. */ + void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json); + + /** Returns bool based on JSON response from cloud. */ + void createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse json); + + void printInfo(StorageInfoResponse response); +public: + /** This constructor uses OAuth code flow to get tokens. */ + GoogleDriveStorage(Common::String code); + virtual ~GoogleDriveStorage(); + + /** + * Storage methods, which are used by CloudManager to save + * storage in configuration file. + */ + + /** + * Save storage data using ConfMan. + * @param keyPrefix all saved keys must start with this prefix. + * @note every Storage must write keyPrefix + "type" key + * with common value (e.g. "Dropbox"). + */ + virtual void saveConfig(Common::String keyPrefix); + + /** + * Return unique storage name. + * @returns some unique storage name (for example, "Dropbox (user@example.com)") + */ + virtual Common::String name() const; + + /** Public Cloud API comes down there. */ + + /** Returns Array<StorageFile> - the list of files. */ + virtual Networking::Request *listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns UploadStatus struct with info about uploaded file. */ + virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + + /** Calls the callback when finished. */ + virtual Networking::Request *createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns the StorageInfo struct. */ + virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns storage's saves directory path with the trailing slash. */ + virtual Common::String savesDirectoryPath(); + + /** + * Load token and user id from configs and return GoogleDriveStorage for those. + * @return pointer to the newly created GoogleDriveStorage or 0 if some problem occured. + */ + static GoogleDriveStorage *loadFromConfig(Common::String keyPrefix); + + virtual Common::String getRootDirectoryId(); + + /** + * Gets new access_token. If <code> passed is "", refresh_token is used. + * Use "" in order to refresh token and pass a callback, so you could + * continue your work when new token is available. + */ + void getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr, Common::String code = ""); + + Common::String accessToken() const { return _token; } +}; + +} // End of namespace GoogleDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/googledrive/googledrivetokenrefresher.cpp b/backends/cloud/googledrive/googledrivetokenrefresher.cpp new file mode 100644 index 0000000000..8cc492d6b4 --- /dev/null +++ b/backends/cloud/googledrive/googledrivetokenrefresher.cpp @@ -0,0 +1,125 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/googledrive/googledrivetokenrefresher.h" +#include "backends/cloud/googledrive/googledrivestorage.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +namespace Cloud { +namespace GoogleDrive { + +GoogleDriveTokenRefresher::GoogleDriveTokenRefresher(GoogleDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url): + CurlJsonRequest(callback, ecb, url), _parentStorage(parent) {} + +GoogleDriveTokenRefresher::~GoogleDriveTokenRefresher() {} + +void GoogleDriveTokenRefresher::tokenRefreshed(Storage::BoolResponse response) { + if (!response.value) { + //failed to refresh token, notify user with NULL in original callback + warning("GoogleDriveTokenRefresher: failed to refresh token"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + //update headers: first change header with token, then pass those to request + for (uint32 i = 0; i < _headers.size(); ++i) { + if (_headers[i].contains("Authorization")) { + _headers[i] = "Authorization: Bearer " + _parentStorage->accessToken(); + } + } + setHeaders(_headers); + + //successfully received refreshed token, can restart the original request now + retry(0); +} + +void GoogleDriveTokenRefresher::finishJson(Common::JSONValue *json) { + if (!json) { + //that's probably not an error (200 OK) + CurlJsonRequest::finishJson(nullptr); + return; + } + + if (jsonIsObject(json, "GoogleDriveTokenRefresher")) { + Common::JSONObject result = json->asObject(); + long httpResponseCode = -1; + if (result.contains("error") && jsonIsObject(result.getVal("error"), "GoogleDriveTokenRefresher")) { + //new token needed => request token & then retry original request + if (_stream) { + httpResponseCode = _stream->httpResponseCode(); + debug(9, "GoogleDriveTokenRefresher: code = %ld", httpResponseCode); + } + + Common::JSONObject error = result.getVal("error")->asObject(); + bool irrecoverable = true; + + uint32 code = -1; + Common::String message; + if (jsonContainsIntegerNumber(error, "code", "GoogleDriveTokenRefresher")) { + code = error.getVal("code")->asIntegerNumber(); + debug(9, "GoogleDriveTokenRefresher: code = %u", code); + } + + if (jsonContainsString(error, "message", "GoogleDriveTokenRefresher")) { + message = error.getVal("message")->asString(); + debug(9, "GoogleDriveTokenRefresher: message = %s", message.c_str()); + } + + if (code == 401 || message == "Invalid Credentials") + irrecoverable = false; + + if (irrecoverable) { + finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpResponseCode)); + delete json; + return; + } + + pause(); + delete json; + _parentStorage->getAccessToken(new Common::Callback<GoogleDriveTokenRefresher, Storage::BoolResponse>(this, &GoogleDriveTokenRefresher::tokenRefreshed)); + return; + } + } + + //notify user of success + CurlJsonRequest::finishJson(json); +} + +void GoogleDriveTokenRefresher::setHeaders(Common::Array<Common::String> &headers) { + _headers = headers; + curl_slist_free_all(_headersList); + _headersList = 0; + for (uint32 i = 0; i < headers.size(); ++i) + CurlJsonRequest::addHeader(headers[i]); +} + +void GoogleDriveTokenRefresher::addHeader(Common::String header) { + _headers.push_back(header); + CurlJsonRequest::addHeader(header); +} + +} // End of namespace GoogleDrive +} // End of namespace Cloud diff --git a/backends/cloud/googledrive/googledrivetokenrefresher.h b/backends/cloud/googledrive/googledrivetokenrefresher.h new file mode 100644 index 0000000000..6cb3e41849 --- /dev/null +++ b/backends/cloud/googledrive/googledrivetokenrefresher.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVETOKENREFRESHER_H +#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVETOKENREFRESHER_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace GoogleDrive { + +class GoogleDriveStorage; + +class GoogleDriveTokenRefresher: public Networking::CurlJsonRequest { + GoogleDriveStorage *_parentStorage; + Common::Array<Common::String> _headers; + + void tokenRefreshed(Storage::BoolResponse response); + + virtual void finishJson(Common::JSONValue *json); +public: + GoogleDriveTokenRefresher(GoogleDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url); + virtual ~GoogleDriveTokenRefresher(); + + virtual void setHeaders(Common::Array<Common::String> &headers); + virtual void addHeader(Common::String header); +}; + +} // End of namespace GoogleDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/googledrive/googledriveuploadrequest.cpp b/backends/cloud/googledrive/googledriveuploadrequest.cpp new file mode 100644 index 0000000000..5f61dcd2a8 --- /dev/null +++ b/backends/cloud/googledrive/googledriveuploadrequest.cpp @@ -0,0 +1,353 @@ +/* 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 "backends/cloud/googledrive/googledriveuploadrequest.h" +#include "backends/cloud/googledrive/googledrivestorage.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" +#include "googledrivetokenrefresher.h" + +namespace Cloud { +namespace GoogleDrive { + +#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/upload/drive/v3/files" + +GoogleDriveUploadRequest::GoogleDriveUploadRequest(GoogleDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _contentsStream(contents), _uploadCallback(callback), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +GoogleDriveUploadRequest::~GoogleDriveUploadRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _contentsStream; + delete _uploadCallback; +} + +void GoogleDriveUploadRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + if (_contentsStream == nullptr || !_contentsStream->seek(0)) { + warning("GoogleDriveUploadRequest: cannot restart because stream couldn't seek(0)"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + _resolvedId = ""; //used to update file contents + _parentId = ""; //used to create file within parent directory + _serverReceivedBytes = 0; + _ignoreCallback = false; + + resolveId(); +} + +void GoogleDriveUploadRequest::resolveId() { + //check whether such file already exists + Storage::UploadCallback innerCallback = new Common::Callback<GoogleDriveUploadRequest, Storage::UploadResponse>(this, &GoogleDriveUploadRequest::idResolvedCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<GoogleDriveUploadRequest, Networking::ErrorResponse>(this, &GoogleDriveUploadRequest::idResolveFailedCallback); + _workingRequest = _storage->resolveFileId(_savePath, innerCallback, innerErrorCallback); +} + +void GoogleDriveUploadRequest::idResolvedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + _resolvedId = response.value.id(); + startUpload(); +} + +void GoogleDriveUploadRequest::idResolveFailedCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //not resolved => error or no such file + if (error.response.contains("no such file found in its parent directory")) { + //parent's id after the '\n' + Common::String parentId = error.response; + for (uint32 i = 0; i < parentId.size(); ++i) + if (parentId[i] == '\n') { + parentId.erase(0, i + 1); + break; + } + + _parentId = parentId; + startUpload(); + return; + } + + finishError(error); +} + +void GoogleDriveUploadRequest::startUpload() { + Common::String name = _savePath; + for (uint32 i = name.size(); i > 0; --i) { + if (name[i - 1] == '/' || name[i - 1] == '\\') { + name.erase(0, i); + break; + } + } + + Common::String url = GOOGLEDRIVE_API_FILES; + if (_resolvedId != "") + url += "/" + ConnMan.urlEncode(_resolvedId); + url += "?uploadType=resumable&fields=id,mimeType,modifiedTime,name,size"; + Networking::JsonCallback callback = new Common::Callback<GoogleDriveUploadRequest, Networking::JsonResponse>(this, &GoogleDriveUploadRequest::startUploadCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveUploadRequest, Networking::ErrorResponse>(this, &GoogleDriveUploadRequest::startUploadErrorCallback); + Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + request->addHeader("Content-Type: application/json"); + if (_resolvedId != "") + request->usePatch(); + + Common::JSONObject jsonRequestParameters; + if (_resolvedId != "") { + jsonRequestParameters.setVal("id", new Common::JSONValue(_resolvedId)); + } else { + Common::JSONArray parentsArray; + parentsArray.push_back(new Common::JSONValue(_parentId)); + jsonRequestParameters.setVal("parents", new Common::JSONValue(parentsArray)); + } + jsonRequestParameters.setVal("name", new Common::JSONValue(name)); + + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + _workingRequest = ConnMan.addRequest(request); +} + +void GoogleDriveUploadRequest::startUploadCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Networking::ErrorResponse error(this, false, true, "", -1); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq) { + const Networking::NetworkReadStream *stream = rq->getNetworkReadStream(); + if (stream) { + long code = stream->httpResponseCode(); + Common::String headers = stream->responseHeaders(); + if (code == 200) { + const char *cstr = headers.c_str(); + const char *position = strstr(cstr, "Location: "); + + if (position) { + Common::String result = ""; + char c; + for (const char *i = position + 10; c = *i, c != 0; ++i) { + if (c == '\n' || c == '\r') + break; + result += c; + } + _uploadUrl = result; + uploadNextPart(); + return; + } + } + + error.httpResponseCode = code; + } + } + + Common::JSONValue *json = response.value; + delete json; + + finishError(error); +} + +void GoogleDriveUploadRequest::startUploadErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void GoogleDriveUploadRequest::uploadNextPart() { + const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024; + Common::String url = _uploadUrl; + + Networking::JsonCallback callback = new Common::Callback<GoogleDriveUploadRequest, Networking::JsonResponse>(this, &GoogleDriveUploadRequest::partUploadedCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveUploadRequest, Networking::ErrorResponse>(this, &GoogleDriveUploadRequest::partUploadedErrorCallback); + Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + request->usePut(); + + uint32 oldPos = _contentsStream->pos(); + if (oldPos != _serverReceivedBytes) { + if (!_contentsStream->seek(_serverReceivedBytes)) { + warning("GoogleDriveUploadRequest: cannot upload because stream couldn't seek(%lu)", _serverReceivedBytes); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + oldPos = _serverReceivedBytes; + } + + byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST]; + uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST); + if (size != 0) + request->setBuffer(buffer, size); + + if (_uploadUrl != "") { + if (_contentsStream->pos() == 0) + request->addHeader(Common::String::format("Content-Length: 0")); + else + request->addHeader(Common::String::format("Content-Range: bytes %u-%u/%u", oldPos, _contentsStream->pos() - 1, _contentsStream->size())); + } + + _workingRequest = ConnMan.addRequest(request); +} + +bool GoogleDriveUploadRequest::handleHttp308(const Networking::NetworkReadStream *stream) { + //308 Resume Incomplete, with Range: X-Y header + if (!stream) + return false; + if (stream->httpResponseCode() != 308) + return false; //seriously + + Common::String headers = stream->responseHeaders(); + const char *cstr = headers.c_str(); + for (int rangeTry = 0; rangeTry < 2; ++rangeTry) { + const char *needle = (rangeTry == 0 ? "Range: 0-" : "Range: bytes=0-"); + uint32 needleLength = (rangeTry == 0 ? 9 : 15); + + const char *position = strstr(cstr, needle); //if it lost the first part, I refuse to talk with it + + if (position) { + Common::String result = ""; + char c; + for (const char *i = position + needleLength; c = *i, c != 0; ++i) { + if (c == '\n' || c == '\r') + break; + result += c; + } + _serverReceivedBytes = result.asUint64() + 1; + uploadNextPart(); + return true; + } + } + + return false; +} + +void GoogleDriveUploadRequest::partUploadedCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Networking::ErrorResponse error(this, false, true, "", -1); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq) { + const Networking::NetworkReadStream *stream = rq->getNetworkReadStream(); + if (stream) { + long code = stream->httpResponseCode(); + error.httpResponseCode = code; + if (code == 308 && handleHttp308(stream)) { + delete (Common::JSONValue *)response.value; + return; + } + } + } + + Common::JSONValue *json = response.value; + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (json->isObject()) { + Common::JSONObject object = json->asObject(); + + if (object.contains("error")) { + warning("GoogleDrive returned error: %s", json->stringify(true).c_str()); + error.response = json->stringify(true); + finishError(error); + delete json; + return; + } + + if (Networking::CurlJsonRequest::jsonContainsString(object, "id", "GoogleDriveUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(object, "name", "GoogleDriveUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(object, "mimeType", "GoogleDriveUploadRequest")) { + //finished + Common::String id = object.getVal("id")->asString(); + Common::String name = object.getVal("name")->asString(); + bool isDirectory = (object.getVal("mimeType")->asString() == "application/vnd.google-apps.folder"); + uint32 size = 0, timestamp = 0; + if (Networking::CurlJsonRequest::jsonContainsString(object, "size", "GoogleDriveUploadRequest", true)) + size = object.getVal("size")->asString().asUint64(); + if (Networking::CurlJsonRequest::jsonContainsString(object, "modifiedTime", "GoogleDriveUploadRequest", true)) + timestamp = ISO8601::convertToTimestamp(object.getVal("modifiedTime")->asString()); + + finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory)); + return; + } + } + + if (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1) { + warning("GoogleDriveUploadRequest: no file info to return"); + finishUpload(StorageFile(_savePath, 0, 0, false)); + } else { + uploadNextPart(); + } + + delete json; +} + +void GoogleDriveUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)error.request; + if (rq) { + const Networking::NetworkReadStream *stream = rq->getNetworkReadStream(); + if (stream) { + long code = stream->httpResponseCode(); + if (code == 308 && handleHttp308(stream)) { + return; + } + } + } + + finishError(error); +} + +void GoogleDriveUploadRequest::handle() {} + +void GoogleDriveUploadRequest::restart() { start(); } + +void GoogleDriveUploadRequest::finishUpload(StorageFile file) { + Request::finishSuccess(); + if (_uploadCallback) + (*_uploadCallback)(Storage::UploadResponse(this, file)); +} + +} // End of namespace GoogleDrive +} // End of namespace Cloud diff --git a/backends/cloud/googledrive/googledriveuploadrequest.h b/backends/cloud/googledrive/googledriveuploadrequest.h new file mode 100644 index 0000000000..73acab5bbd --- /dev/null +++ b/backends/cloud/googledrive/googledriveuploadrequest.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVEUPLOADREQUEST_H +#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVEUPLOADREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace GoogleDrive { +class GoogleDriveStorage; + +class GoogleDriveUploadRequest: public Networking::Request { + GoogleDriveStorage *_storage; + Common::String _savePath; + Common::SeekableReadStream *_contentsStream; + Storage::UploadCallback _uploadCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _resolvedId, _parentId; + Common::String _uploadUrl; + uint64 _serverReceivedBytes; + + void start(); + void resolveId(); + void idResolvedCallback(Storage::UploadResponse response); + void idResolveFailedCallback(Networking::ErrorResponse error); + void startUpload(); + void startUploadCallback(Networking::JsonResponse response); + void startUploadErrorCallback(Networking::ErrorResponse error); + void uploadNextPart(); + void partUploadedCallback(Networking::JsonResponse response); + void partUploadedErrorCallback(Networking::ErrorResponse error); + bool handleHttp308(const Networking::NetworkReadStream *stream); + void finishUpload(StorageFile status); + +public: + GoogleDriveUploadRequest(GoogleDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb); + virtual ~GoogleDriveUploadRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace GoogleDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/id/idcreatedirectoryrequest.cpp b/backends/cloud/id/idcreatedirectoryrequest.cpp new file mode 100644 index 0000000000..37f417f806 --- /dev/null +++ b/backends/cloud/id/idcreatedirectoryrequest.cpp @@ -0,0 +1,163 @@ +/* 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 "backends/cloud/id/idcreatedirectoryrequest.h" +#include "backends/cloud/id/idstorage.h" +#include "common/debug.h" + +namespace Cloud { +namespace Id { + +IdCreateDirectoryRequest::IdCreateDirectoryRequest(IdStorage *storage, Common::String parentPath, Common::String directoryName, Storage::BoolCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), + _requestedParentPath(parentPath), _requestedDirectoryName(directoryName), _storage(storage), _boolCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +IdCreateDirectoryRequest::~IdCreateDirectoryRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _boolCallback; +} + +void IdCreateDirectoryRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _workingRequest = nullptr; + _ignoreCallback = false; + + //the only exception when we create parent folder - is when it's ScummVM/ base folder + Common::String prefix = _requestedParentPath; + if (prefix.size() > 7) + prefix.erase(7); + if (prefix.equalsIgnoreCase("ScummVM")) { + Storage::BoolCallback callback = new Common::Callback<IdCreateDirectoryRequest, Storage::BoolResponse>(this, &IdCreateDirectoryRequest::createdBaseDirectoryCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::createdBaseDirectoryErrorCallback); + _workingRequest = _storage->createDirectory("ScummVM", callback, failureCallback); + return; + } + + resolveId(); +} + +void IdCreateDirectoryRequest::createdBaseDirectoryCallback(Storage::BoolResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (response.request) + _date = response.request->date(); + resolveId(); +} + +void IdCreateDirectoryRequest::createdBaseDirectoryErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void IdCreateDirectoryRequest::resolveId() { + //check whether such folder already exists + Storage::UploadCallback innerCallback = new Common::Callback<IdCreateDirectoryRequest, Storage::UploadResponse>(this, &IdCreateDirectoryRequest::idResolvedCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::idResolveFailedCallback); + Common::String path = _requestedParentPath; + if (_requestedParentPath != "") + path += "/"; + path += _requestedDirectoryName; + _workingRequest = _storage->resolveFileId(path, innerCallback, innerErrorCallback); +} + +void IdCreateDirectoryRequest::idResolvedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (response.request) + _date = response.request->date(); + + //resolved => folder already exists + finishCreation(false); +} + +void IdCreateDirectoryRequest::idResolveFailedCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + + //not resolved => folder not exists + if (error.response.contains("no such file found in its parent directory")) { + //parent's id after the '\n' + Common::String parentId = error.response; + for (uint32 i = 0; i < parentId.size(); ++i) + if (parentId[i] == '\n') { + parentId.erase(0, i + 1); + break; + } + + Storage::BoolCallback callback = new Common::Callback<IdCreateDirectoryRequest, Storage::BoolResponse>(this, &IdCreateDirectoryRequest::createdDirectoryCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::createdDirectoryErrorCallback); + _workingRequest = _storage->createDirectoryWithParentId(parentId, _requestedDirectoryName, callback, failureCallback); + return; + } + + finishError(error); +} + +void IdCreateDirectoryRequest::createdDirectoryCallback(Storage::BoolResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (response.request) + _date = response.request->date(); + finishCreation(response.value); +} + +void IdCreateDirectoryRequest::createdDirectoryErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void IdCreateDirectoryRequest::handle() {} + +void IdCreateDirectoryRequest::restart() { start(); } + +Common::String IdCreateDirectoryRequest::date() const { return _date; } + +void IdCreateDirectoryRequest::finishCreation(bool success) { + Request::finishSuccess(); + if (_boolCallback) + (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +} // End of namespace Id +} // End of namespace Cloud diff --git a/backends/cloud/id/idcreatedirectoryrequest.h b/backends/cloud/id/idcreatedirectoryrequest.h new file mode 100644 index 0000000000..241bcd30be --- /dev/null +++ b/backends/cloud/id/idcreatedirectoryrequest.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_ID_IDCREATEDIRECTORYREQUEST_H +#define BACKENDS_CLOUD_ID_IDCREATEDIRECTORYREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Id { + +class IdStorage; + +class IdCreateDirectoryRequest: public Networking::Request { + Common::String _requestedParentPath; + Common::String _requestedDirectoryName; + IdStorage *_storage; + Storage::BoolCallback _boolCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void createdBaseDirectoryCallback(Storage::BoolResponse response); + void createdBaseDirectoryErrorCallback(Networking::ErrorResponse error); + void resolveId(); + void idResolvedCallback(Storage::UploadResponse response); + void idResolveFailedCallback(Networking::ErrorResponse error); + void createdDirectoryCallback(Storage::BoolResponse response); + void createdDirectoryErrorCallback(Networking::ErrorResponse error); + void finishCreation(bool success); +public: + IdCreateDirectoryRequest(IdStorage *storage, Common::String parentPath, Common::String directoryName, Storage::BoolCallback cb, Networking::ErrorCallback ecb); + virtual ~IdCreateDirectoryRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace Id +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/id/iddownloadrequest.cpp b/backends/cloud/id/iddownloadrequest.cpp new file mode 100644 index 0000000000..2532d611b8 --- /dev/null +++ b/backends/cloud/id/iddownloadrequest.cpp @@ -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 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 "backends/cloud/id/iddownloadrequest.h" +#include "backends/cloud/id/idstorage.h" +#include "backends/cloud/downloadrequest.h" + +namespace Cloud { +namespace Id { + +IdDownloadRequest::IdDownloadRequest(IdStorage *storage, Common::String remotePath, Common::String localPath, Storage::BoolCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _requestedFile(remotePath), _requestedLocalFile(localPath), _storage(storage), _boolCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +IdDownloadRequest::~IdDownloadRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _boolCallback; +} + +void IdDownloadRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _workingRequest = nullptr; + _ignoreCallback = false; + + //find file's id + Storage::UploadCallback innerCallback = new Common::Callback<IdDownloadRequest, Storage::UploadResponse>(this, &IdDownloadRequest::idResolvedCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdDownloadRequest, Networking::ErrorResponse>(this, &IdDownloadRequest::idResolveFailedCallback); + _workingRequest = _storage->resolveFileId(_requestedFile, innerCallback, innerErrorCallback); +} + +void IdDownloadRequest::idResolvedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Storage::BoolCallback innerCallback = new Common::Callback<IdDownloadRequest, Storage::BoolResponse>(this, &IdDownloadRequest::downloadCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdDownloadRequest, Networking::ErrorResponse>(this, &IdDownloadRequest::downloadErrorCallback); + _workingRequest = _storage->downloadById(response.value.id(), _requestedLocalFile, innerCallback, innerErrorCallback); +} + +void IdDownloadRequest::idResolveFailedCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void IdDownloadRequest::downloadCallback(Storage::BoolResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishDownload(response.value); +} + +void IdDownloadRequest::downloadErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void IdDownloadRequest::handle() {} + +void IdDownloadRequest::restart() { start(); } + +void IdDownloadRequest::finishDownload(bool success) { + Request::finishSuccess(); + if (_boolCallback) + (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +double IdDownloadRequest::getProgress() const { + DownloadRequest *downloadRequest = dynamic_cast<DownloadRequest *>(_workingRequest); + if (downloadRequest == nullptr) + return 0; // resolving id still + + // id resolve is 10 % and download is the other 90 % + return 0.1 + 0.9 * downloadRequest->getProgress(); // downloading +} + +} // End of namespace Id +} // End of namespace Cloud diff --git a/backends/cloud/id/iddownloadrequest.h b/backends/cloud/id/iddownloadrequest.h new file mode 100644 index 0000000000..65e05c00b3 --- /dev/null +++ b/backends/cloud/id/iddownloadrequest.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_ID_IDDOWNLOADREQUEST_H +#define BACKENDS_CLOUD_ID_IDDOWNLOADREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Id { + +class IdStorage; + +class IdDownloadRequest: public Networking::Request { + Common::String _requestedFile, _requestedLocalFile; + IdStorage *_storage; + Storage::BoolCallback _boolCallback; + Request *_workingRequest; + bool _ignoreCallback; + + void start(); + void idResolvedCallback(Storage::UploadResponse response); + void idResolveFailedCallback(Networking::ErrorResponse error); + void downloadCallback(Storage::BoolResponse response); + void downloadErrorCallback(Networking::ErrorResponse error); + void finishDownload(bool success); +public: + IdDownloadRequest(IdStorage *storage, Common::String remotePath, Common::String localPath, Storage::BoolCallback cb, Networking::ErrorCallback ecb); + virtual ~IdDownloadRequest(); + + virtual void handle(); + virtual void restart(); + + /** Returns a number in range [0, 1], where 1 is "complete". */ + double getProgress() const; +}; + +} // End of namespace Id +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/id/idlistdirectoryrequest.cpp b/backends/cloud/id/idlistdirectoryrequest.cpp new file mode 100644 index 0000000000..4e63709984 --- /dev/null +++ b/backends/cloud/id/idlistdirectoryrequest.cpp @@ -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 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 "backends/cloud/id/idlistdirectoryrequest.h" +#include "backends/cloud/id/idstorage.h" + +namespace Cloud { +namespace Id { + +IdListDirectoryRequest::IdListDirectoryRequest(IdStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive): + Networking::Request(nullptr, ecb), + _requestedPath(path), _requestedRecursive(recursive), _storage(storage), _listDirectoryCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +IdListDirectoryRequest::~IdListDirectoryRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _listDirectoryCallback; +} + +void IdListDirectoryRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _workingRequest = nullptr; + _files.clear(); + _directoriesQueue.clear(); + _currentDirectory = StorageFile(); + _ignoreCallback = false; + + //find out that directory's id + Storage::UploadCallback innerCallback = new Common::Callback<IdListDirectoryRequest, Storage::UploadResponse>(this, &IdListDirectoryRequest::idResolvedCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdListDirectoryRequest, Networking::ErrorResponse>(this, &IdListDirectoryRequest::idResolveErrorCallback); + _workingRequest = _storage->resolveFileId(_requestedPath, innerCallback, innerErrorCallback); +} + +void IdListDirectoryRequest::idResolvedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (response.request) + _date = response.request->date(); + + StorageFile directory = response.value; + directory.setPath(_requestedPath); + _directoriesQueue.push_back(directory); + listNextDirectory(); +} + +void IdListDirectoryRequest::idResolveErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void IdListDirectoryRequest::listNextDirectory() { + if (_directoriesQueue.empty()) { + finishListing(_files); + return; + } + + _currentDirectory = _directoriesQueue.back(); + _directoriesQueue.pop_back(); + + Storage::FileArrayCallback callback = new Common::Callback<IdListDirectoryRequest, Storage::FileArrayResponse>(this, &IdListDirectoryRequest::listedDirectoryCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<IdListDirectoryRequest, Networking::ErrorResponse>(this, &IdListDirectoryRequest::listedDirectoryErrorCallback); + _workingRequest = _storage->listDirectoryById(_currentDirectory.id(), callback, failureCallback); +} + +void IdListDirectoryRequest::listedDirectoryCallback(Storage::FileArrayResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (response.request) + _date = response.request->date(); + + for (uint32 i = 0; i < response.value.size(); ++i) { + StorageFile &file = response.value[i]; + Common::String path = _currentDirectory.path(); + if (path.size() && path.lastChar() != '/' && path.lastChar() != '\\') + path += '/'; + path += file.name(); + file.setPath(path); + _files.push_back(file); + if (_requestedRecursive && file.isDirectory()) { + _directoriesQueue.push_back(file); + } + } + + listNextDirectory(); +} + +void IdListDirectoryRequest::listedDirectoryErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void IdListDirectoryRequest::handle() {} + +void IdListDirectoryRequest::restart() { start(); } + +Common::String IdListDirectoryRequest::date() const { return _date; } + +void IdListDirectoryRequest::finishListing(Common::Array<StorageFile> &files) { + Request::finishSuccess(); + if (_listDirectoryCallback) + (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files)); +} + +} // End of namespace Id +} // End of namespace Cloud diff --git a/backends/cloud/id/idlistdirectoryrequest.h b/backends/cloud/id/idlistdirectoryrequest.h new file mode 100644 index 0000000000..58c5d2c864 --- /dev/null +++ b/backends/cloud/id/idlistdirectoryrequest.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_ID_IDLISTDIRECTORYREQUEST_H +#define BACKENDS_CLOUD_ID_IDLISTDIRECTORYREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Id { + +class IdStorage; + +class IdListDirectoryRequest: public Networking::Request { + Common::String _requestedPath; + bool _requestedRecursive; + IdStorage *_storage; + Storage::ListDirectoryCallback _listDirectoryCallback; + Common::Array<StorageFile> _files; + Common::Array<StorageFile> _directoriesQueue; + StorageFile _currentDirectory; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void idResolvedCallback(Storage::UploadResponse response); + void idResolveErrorCallback(Networking::ErrorResponse error); + void listNextDirectory(); + void listedDirectoryCallback(Storage::FileArrayResponse response); + void listedDirectoryErrorCallback(Networking::ErrorResponse error); + void finishListing(Common::Array<StorageFile> &files); +public: + IdListDirectoryRequest(IdStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false); + virtual ~IdListDirectoryRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace Id +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/id/idresolveidrequest.cpp b/backends/cloud/id/idresolveidrequest.cpp new file mode 100644 index 0000000000..e8589fc204 --- /dev/null +++ b/backends/cloud/id/idresolveidrequest.cpp @@ -0,0 +1,136 @@ +/* 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 "backends/cloud/id/idresolveidrequest.h" +#include "backends/cloud/id/idstorage.h" + +namespace Cloud { +namespace Id { + +IdResolveIdRequest::IdResolveIdRequest(IdStorage *storage, Common::String path, Storage::UploadCallback cb, Networking::ErrorCallback ecb, bool recursive): + Networking::Request(nullptr, ecb), + _requestedPath(path), _storage(storage), _uploadCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +IdResolveIdRequest::~IdResolveIdRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _uploadCallback; +} + +void IdResolveIdRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _workingRequest = nullptr; + _currentDirectory = ""; + _currentDirectoryId = _storage->getRootDirectoryId(); + _ignoreCallback = false; + + listNextDirectory(StorageFile(_currentDirectoryId, 0, 0, true)); +} + +void IdResolveIdRequest::listNextDirectory(StorageFile fileToReturn) { + if (_currentDirectory.equalsIgnoreCase(_requestedPath)) { + finishFile(fileToReturn); + return; + } + + Storage::FileArrayCallback callback = new Common::Callback<IdResolveIdRequest, Storage::FileArrayResponse>(this, &IdResolveIdRequest::listedDirectoryCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<IdResolveIdRequest, Networking::ErrorResponse>(this, &IdResolveIdRequest::listedDirectoryErrorCallback); + _workingRequest = _storage->listDirectoryById(_currentDirectoryId, callback, failureCallback); +} + +void IdResolveIdRequest::listedDirectoryCallback(Storage::FileArrayResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Common::String currentLevelName = _requestedPath; + ///debug(9, "'%s'", currentLevelName.c_str()); + if (_currentDirectory.size()) + currentLevelName.erase(0, _currentDirectory.size()); + if (currentLevelName.size() && (currentLevelName[0] == '/' || currentLevelName[0] == '\\')) + currentLevelName.erase(0, 1); + ///debug(9, "'%s'", currentLevelName.c_str()); + for (uint32 i = 0; i < currentLevelName.size(); ++i) { + if (currentLevelName[i] == '/' || currentLevelName[i] == '\\') { + currentLevelName.erase(i); + ///debug(9, "'%s'", currentLevelName.c_str()); + break; + } + } + + Common::String path = _currentDirectory; + if (path != "") + path += "/"; + path += currentLevelName; + bool lastLevel = (path.equalsIgnoreCase(_requestedPath)); + + ///debug(9, "IdResolveIdRequest: searching for '%s' in '%s'", currentLevelName.c_str(), _currentDirectory.c_str()); + + Common::Array<StorageFile> &files = response.value; + bool found = false; + for (uint32 i = 0; i < files.size(); ++i) { + if ((files[i].isDirectory() || lastLevel) && files[i].name().equalsIgnoreCase(currentLevelName)) { + if (_currentDirectory != "") + _currentDirectory += "/"; + _currentDirectory += files[i].name(); + _currentDirectoryId = files[i].id(); + ///debug(9, "IdResolveIdRequest: found it! new directory and its id: '%s', '%s'", _currentDirectory.c_str(), _currentDirectoryId.c_str()); + listNextDirectory(files[i]); + found = true; + break; + } + } + + if (!found) { + if (lastLevel) + finishError(Networking::ErrorResponse(this, false, true, Common::String("no such file found in its parent directory\n") + _currentDirectoryId, 404)); + else + finishError(Networking::ErrorResponse(this, false, true, "subdirectory not found", 400)); + } +} + +void IdResolveIdRequest::listedDirectoryErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void IdResolveIdRequest::handle() {} + +void IdResolveIdRequest::restart() { start(); } + +void IdResolveIdRequest::finishFile(StorageFile file) { + Request::finishSuccess(); + if (_uploadCallback) + (*_uploadCallback)(Storage::UploadResponse(this, file)); +} + +} // End of namespace Id +} // End of namespace Cloud diff --git a/backends/cloud/id/idresolveidrequest.h b/backends/cloud/id/idresolveidrequest.h new file mode 100644 index 0000000000..e735e96385 --- /dev/null +++ b/backends/cloud/id/idresolveidrequest.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_ID_IDRESOLVEIDREQUEST_H +#define BACKENDS_CLOUD_ID_IDRESOLVEIDREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Id { + +class IdStorage; + +class IdResolveIdRequest: public Networking::Request { + Common::String _requestedPath; + IdStorage *_storage; + Storage::UploadCallback _uploadCallback; + Common::String _currentDirectory; + Common::String _currentDirectoryId; + Request *_workingRequest; + bool _ignoreCallback; + + void start(); + void listNextDirectory(StorageFile fileToReturn); + void listedDirectoryCallback(Storage::FileArrayResponse response); + void listedDirectoryErrorCallback(Networking::ErrorResponse error); + void finishFile(StorageFile file); +public: + IdResolveIdRequest(IdStorage *storage, Common::String path, Storage::UploadCallback cb, Networking::ErrorCallback ecb, bool recursive = false); //TODO: why upload? + virtual ~IdResolveIdRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace Id +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/id/idstorage.cpp b/backends/cloud/id/idstorage.cpp new file mode 100644 index 0000000000..0ffa8c068c --- /dev/null +++ b/backends/cloud/id/idstorage.cpp @@ -0,0 +1,109 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/id/idstorage.h" +#include "backends/cloud/id/idcreatedirectoryrequest.h" +#include "backends/cloud/id/iddownloadrequest.h" +#include "backends/cloud/id/idlistdirectoryrequest.h" +#include "backends/cloud/id/idresolveidrequest.h" +#include "backends/cloud/id/idstreamfilerequest.h" +#include "common/debug.h" + +namespace Cloud { +namespace Id { + +IdStorage::~IdStorage() {} + +void IdStorage::printFiles(FileArrayResponse response) { + debug(9, "IdStorage: files:"); + Common::Array<StorageFile> &files = response.value; + for (uint32 i = 0; i < files.size(); ++i) { + debug(9, "\t%s%s", files[i].name().c_str(), files[i].isDirectory() ? " (directory)" : ""); + debug(9, "\t%s", files[i].path().c_str()); + debug(9, "\t%s", files[i].id().c_str()); + debug(9, " "); + } +} + +void IdStorage::printBool(BoolResponse response) { + debug(9, "IdStorage: bool: %s", response.value ? "true" : "false"); +} + +void IdStorage::printFile(UploadResponse response) { + debug(9, "\nIdStorage: uploaded file info:"); + debug(9, "\tid: %s", response.value.path().c_str()); + debug(9, "\tname: %s", response.value.name().c_str()); + debug(9, "\tsize: %u", response.value.size()); + debug(9, "\ttimestamp: %u", response.value.timestamp()); +} + +Storage::ListDirectoryCallback IdStorage::getPrintFilesCallback() { + return new Common::Callback<IdStorage, FileArrayResponse>(this, &IdStorage::printFiles); +} + +Networking::Request *IdStorage::resolveFileId(Common::String path, UploadCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + if (!callback) + callback = new Common::Callback<IdStorage, UploadResponse>(this, &IdStorage::printFile); + return addRequest(new IdResolveIdRequest(this, path, callback, errorCallback)); +} + +Networking::Request *IdStorage::listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + if (!callback) + callback = new Common::Callback<IdStorage, FileArrayResponse>(this, &IdStorage::printFiles); + return addRequest(new IdListDirectoryRequest(this, path, callback, errorCallback, recursive)); +} + +Networking::Request *IdStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + if (!callback) + callback = new Common::Callback<IdStorage, BoolResponse>(this, &IdStorage::printBool); + + //find out the parent path and directory name + Common::String parentPath = "", directoryName = path; + for (uint32 i = path.size(); i > 0; --i) { + if (path[i - 1] == '/' || path[i - 1] == '\\') { + parentPath = path; + parentPath.erase(i - 1); + directoryName.erase(0, i); + break; + } + } + + return addRequest(new IdCreateDirectoryRequest(this, parentPath, directoryName, callback, errorCallback)); +} + +Networking::Request *IdStorage::streamFile(Common::String path, Networking::NetworkReadStreamCallback outerCallback, Networking::ErrorCallback errorCallback) { + return addRequest(new IdStreamFileRequest(this, path, outerCallback, errorCallback)); +} + +Networking::Request *IdStorage::download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) { + return addRequest(new IdDownloadRequest(this, remotePath, localPath, callback, errorCallback)); +} + +} // End of namespace Id +} // End of namespace Cloud diff --git a/backends/cloud/id/idstorage.h b/backends/cloud/id/idstorage.h new file mode 100644 index 0000000000..7e64fd4a37 --- /dev/null +++ b/backends/cloud/id/idstorage.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_ID_IDSTORAGE_H +#define BACKENDS_CLOUD_ID_IDSTORAGE_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" + +/* + * Id::IdStorage is a special base class, which is created + * to simplify adding new storages which use ids instead of + * paths in their API. + * + * Some Requests are already implemented, and Storage based + * on IdStorage needs to override/implement a few basic things. + * + * For example, ListDirectoryRequest and ResolveIdRequests are + * based on listDirectoryById() and getRootDirectoryId() methods. + * Implementing these you'll get id resolving and directory + * listing by path. + */ + +namespace Cloud { +namespace Id { + +class IdStorage: public Cloud::Storage { +protected: + void printFiles(FileArrayResponse response); + void printBool(BoolResponse response); + void printFile(UploadResponse response); + + ListDirectoryCallback getPrintFilesCallback(); + +public: + virtual ~IdStorage(); + + /** Public Cloud API comes down there. */ + + /** Returns StorageFile with the resolved file's id. */ + virtual Networking::Request *resolveFileId(Common::String path, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns ListDirectoryStatus struct with list of files. */ + virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + virtual Networking::Request *listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) = 0; + + /** Calls the callback when finished. */ + virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback); + virtual Networking::Request *createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback) = 0; + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + virtual Networking::Request *streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) = 0; + + /** Calls the callback when finished. */ + virtual Networking::Request *download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback); + + virtual Common::String getRootDirectoryId() = 0; +}; + +} // End of namespace Id +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/id/idstreamfilerequest.cpp b/backends/cloud/id/idstreamfilerequest.cpp new file mode 100644 index 0000000000..2e68b15412 --- /dev/null +++ b/backends/cloud/id/idstreamfilerequest.cpp @@ -0,0 +1,98 @@ +/* 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 "backends/cloud/id/idstreamfilerequest.h" +#include "backends/cloud/id/idstorage.h" + +namespace Cloud { +namespace Id { + +IdStreamFileRequest::IdStreamFileRequest(IdStorage *storage, Common::String path, Networking::NetworkReadStreamCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _requestedFile(path), _storage(storage), _streamCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +IdStreamFileRequest::~IdStreamFileRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _streamCallback; +} + +void IdStreamFileRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _workingRequest = nullptr; + _ignoreCallback = false; + + //find file's id + Storage::UploadCallback innerCallback = new Common::Callback<IdStreamFileRequest, Storage::UploadResponse>(this, &IdStreamFileRequest::idResolvedCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdStreamFileRequest, Networking::ErrorResponse>(this, &IdStreamFileRequest::idResolveFailedCallback); + _workingRequest = _storage->resolveFileId(_requestedFile, innerCallback, innerErrorCallback); +} + +void IdStreamFileRequest::idResolvedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Networking::NetworkReadStreamCallback innerCallback = new Common::Callback<IdStreamFileRequest, Networking::NetworkReadStreamResponse>(this, &IdStreamFileRequest::streamFileCallback); + Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdStreamFileRequest, Networking::ErrorResponse>(this, &IdStreamFileRequest::streamFileErrorCallback); + _workingRequest = _storage->streamFileById(response.value.id(), innerCallback, innerErrorCallback); +} + +void IdStreamFileRequest::idResolveFailedCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void IdStreamFileRequest::streamFileCallback(Networking::NetworkReadStreamResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishStream(response.value); +} + +void IdStreamFileRequest::streamFileErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void IdStreamFileRequest::handle() {} + +void IdStreamFileRequest::restart() { start(); } + +void IdStreamFileRequest::finishStream(Networking::NetworkReadStream *stream) { + Request::finishSuccess(); + if (_streamCallback) + (*_streamCallback)(Networking::NetworkReadStreamResponse(this, stream)); +} + +} // End of namespace Id +} // End of namespace Cloud diff --git a/backends/cloud/id/idstreamfilerequest.h b/backends/cloud/id/idstreamfilerequest.h new file mode 100644 index 0000000000..20d7c0c25b --- /dev/null +++ b/backends/cloud/id/idstreamfilerequest.h @@ -0,0 +1,59 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_CLOUD_ID_IDSTREAMFILEREQUEST_H +#define BACKENDS_CLOUD_ID_IDSTREAMFILEREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace Id { + +class IdStorage; + +class IdStreamFileRequest: public Networking::Request { + Common::String _requestedFile; + IdStorage *_storage; + Networking::NetworkReadStreamCallback _streamCallback; + Request *_workingRequest; + bool _ignoreCallback; + + void start(); + void idResolvedCallback(Storage::UploadResponse response); + void idResolveFailedCallback(Networking::ErrorResponse error); + void streamFileCallback(Networking::NetworkReadStreamResponse response); + void streamFileErrorCallback(Networking::ErrorResponse error); + void finishStream(Networking::NetworkReadStream *stream); +public: + IdStreamFileRequest(IdStorage *storage, Common::String path, Networking::NetworkReadStreamCallback cb, Networking::ErrorCallback ecb); + virtual ~IdStreamFileRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace Id +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/iso8601.cpp b/backends/cloud/iso8601.cpp new file mode 100644 index 0000000000..177ef67f11 --- /dev/null +++ b/backends/cloud/iso8601.cpp @@ -0,0 +1,100 @@ +/* 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 "backends/cloud/iso8601.h" +#include "common/str.h" + +namespace { + +Common::String getSubstring(const Common::String &s, uint32 beginning, uint32 ending) { + //beginning inclusive, ending exclusive + Common::String result = s; + result.erase(ending); + result.erase(0, beginning); + return result; +} + +int find(const char *cstr, uint32 startPosition, char needle) { + const char *res = strchr(cstr + startPosition, needle); + if (res == nullptr) + return -1; + return res - cstr; +} + +} + +namespace Cloud { +namespace ISO8601 { + +uint32 convertToTimestamp(const Common::String &iso8601Date) { + //2015-05-12T15:50:38Z + const char *cstr = iso8601Date.c_str(); + int firstHyphen = find(cstr, 0, '-'); + int secondHyphen = find(cstr, firstHyphen + 1, '-'); + int tSeparator = find(cstr, secondHyphen + 1, 'T'); + int firstColon = find(cstr, tSeparator + 1, ':'); + int secondColon = find(cstr, firstColon + 1, ':'); + int zSeparator = find(cstr, secondColon + 1, 'Z'); + if (zSeparator == -1) + zSeparator = find(cstr, secondColon + 1, '-'); // Box's RFC 3339 + //now note '+1' which means if there ever was '-1' result of find(), we still did a valid find() from 0th char + + Common::String year = getSubstring(iso8601Date, 0, firstHyphen); + Common::String month = getSubstring(iso8601Date, firstHyphen + 1, secondHyphen); + Common::String day = getSubstring(iso8601Date, secondHyphen + 1, tSeparator); + Common::String hour = getSubstring(iso8601Date, tSeparator + 1, firstColon); + Common::String minute = getSubstring(iso8601Date, firstColon + 1, secondColon); + Common::String second = getSubstring(iso8601Date, secondColon + 1, zSeparator); + //now note only 'ending' argument was not '+1' (which means I could've make that function such that -1 means 'until the end') + + int Y = atoi(year.c_str()); + int M = atoi(month.c_str()); + int D = atoi(day.c_str()); + int h = atoi(hour.c_str()); + int m = atoi(minute.c_str()); + int s = atoi(second.c_str()); + + //ok, now I compose a timestamp based on my basic perception of time/date + //yeah, I know about leap years and leap seconds and all, but still we don't care there + + uint32 days = D - 1; + for (int i = 1970; i < Y; ++i) + if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0)) + days += 366; + else + days += 365; + + int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + for (int i = 1; i < M; ++i) { + days += mdays[i - 1]; + if (i == 2) + if ((Y % 4 == 0 && Y % 100 != 0) || (Y % 400 == 0)) + days += 1; + } + + uint32 hours = days * 24 + h; + uint32 minutes = hours * 60 + m; + return minutes * 60 + s; +} + +} // End of namespace ISO8601 +} // End of namespace Cloud diff --git a/backends/cloud/iso8601.h b/backends/cloud/iso8601.h new file mode 100644 index 0000000000..cdd817bc07 --- /dev/null +++ b/backends/cloud/iso8601.h @@ -0,0 +1,37 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_CLOUD_ISO8601_H +#define BACKENDS_CLOUD_ISO8601_H + +#include "common/str.h" + +namespace Cloud { +namespace ISO8601 { + +/** Returns timestamp corresponding to given ISO 8601 date */ +uint32 convertToTimestamp(const Common::String &iso8601Date); + +} // End of namespace ISO8601 +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp new file mode 100644 index 0000000000..fc7e4f58b0 --- /dev/null +++ b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp @@ -0,0 +1,150 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 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 "backends/cloud/onedrive/onedrivecreatedirectoryrequest.h" +#include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/cloud/onedrive/onedrivetokenrefresher.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace OneDrive { + +#define ONEDRIVE_API_SPECIAL_APPROOT "https://api.onedrive.com/v1.0/drive/special/approot" + +OneDriveCreateDirectoryRequest::OneDriveCreateDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _storage(storage), _path(path), _boolCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +OneDriveCreateDirectoryRequest::~OneDriveCreateDirectoryRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _boolCallback; +} + +void OneDriveCreateDirectoryRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _ignoreCallback = false; + + Common::String name = _path, parent = _path; + if (name.size() != 0) { + uint32 i = name.size() - 1; + while (true) { + parent.deleteLastChar(); + if (name[i] == '/' || name[i] == '\\') { + name.erase(0, i + 1); + break; + } + if (i == 0) + break; + --i; + } + } + + Common::String url = ONEDRIVE_API_SPECIAL_APPROOT; + if (parent != "") + url += ":/" + ConnMan.urlEncode(parent) + ":"; + url += "/children"; + Networking::JsonCallback innerCallback = new Common::Callback<OneDriveCreateDirectoryRequest, Networking::JsonResponse>(this, &OneDriveCreateDirectoryRequest::responseCallback); + Networking::ErrorCallback errorCallback = new Common::Callback<OneDriveCreateDirectoryRequest, Networking::ErrorResponse>(this, &OneDriveCreateDirectoryRequest::errorCallback); + Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, innerCallback, errorCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + request->addHeader("Content-Type: application/json"); + + Common::JSONObject jsonRequestParameters; + jsonRequestParameters.setVal("name", new Common::JSONValue(name)); + jsonRequestParameters.setVal("folder", new Common::JSONValue(Common::JSONObject())); + Common::JSONValue value(jsonRequestParameters); + request->addPostField(Common::JSON::stringify(&value)); + + _workingRequest = ConnMan.addRequest(request); +} + +void OneDriveCreateDirectoryRequest::responseCallback(Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + _workingRequest = nullptr; + if (_ignoreCallback) { + delete json; + return; + } + if (response.request) + _date = response.request->date(); + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + Common::JSONObject info = json->asObject(); + if (info.contains("id")) { + finishCreation(true); + } else { + error.response = json->stringify(true); + finishError(error); + } + + delete json; +} + +void OneDriveCreateDirectoryRequest::errorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void OneDriveCreateDirectoryRequest::handle() {} + +void OneDriveCreateDirectoryRequest::restart() { start(); } + +Common::String OneDriveCreateDirectoryRequest::date() const { return _date; } + +void OneDriveCreateDirectoryRequest::finishCreation(bool success) { + Request::finishSuccess(); + if (_boolCallback) + (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +} // End of namespace OneDrive +} // End of namespace Cloud diff --git a/backends/cloud/onedrive/onedrivecreatedirectoryrequest.h b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.h new file mode 100644 index 0000000000..acaca2bf00 --- /dev/null +++ b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.h @@ -0,0 +1,59 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_CLOUD_ONEDRIVE_ONEDRIVECREATEDIRECTORYREQUEST_H +#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVECREATEDIRECTORYREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/request.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace OneDrive { + +class OneDriveStorage; + +class OneDriveCreateDirectoryRequest: public Networking::Request { + OneDriveStorage *_storage; + Common::String _path; + Storage::BoolCallback _boolCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void responseCallback(Networking::JsonResponse response); + void errorCallback(Networking::ErrorResponse error); + void finishCreation(bool success); +public: + OneDriveCreateDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb); + virtual ~OneDriveCreateDirectoryRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace OneDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp b/backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp new file mode 100644 index 0000000000..a247a9f234 --- /dev/null +++ b/backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp @@ -0,0 +1,193 @@ +/* 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 "backends/cloud/onedrive/onedrivelistdirectoryrequest.h" +#include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/cloud/onedrive/onedrivetokenrefresher.h" +#include "backends/cloud/iso8601.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" + +namespace Cloud { +namespace OneDrive { + +#define ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/children" + +OneDriveListDirectoryRequest::OneDriveListDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive): + Networking::Request(nullptr, ecb), + _requestedPath(path), _requestedRecursive(recursive), _storage(storage), _listDirectoryCallback(cb), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +OneDriveListDirectoryRequest::~OneDriveListDirectoryRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _listDirectoryCallback; +} + +void OneDriveListDirectoryRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _workingRequest = nullptr; + _files.clear(); + _directoriesQueue.clear(); + _currentDirectory = ""; + _ignoreCallback = false; + + _directoriesQueue.push_back(_requestedPath); + listNextDirectory(); +} + +void OneDriveListDirectoryRequest::listNextDirectory() { + if (_directoriesQueue.empty()) { + finishListing(_files); + return; + } + + _currentDirectory = _directoriesQueue.back(); + _directoriesQueue.pop_back(); + + if (_currentDirectory != "" && _currentDirectory.lastChar() != '/' && _currentDirectory.lastChar() != '\\') + _currentDirectory += '/'; + + Common::String dir = _currentDirectory; + dir.deleteLastChar(); + Common::String url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN, ConnMan.urlEncode(dir).c_str()); + makeRequest(url); +} + +void OneDriveListDirectoryRequest::makeRequest(Common::String url) { + Networking::JsonCallback callback = new Common::Callback<OneDriveListDirectoryRequest, Networking::JsonResponse>(this, &OneDriveListDirectoryRequest::listedDirectoryCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveListDirectoryRequest, Networking::ErrorResponse>(this, &OneDriveListDirectoryRequest::listedDirectoryErrorCallback); + Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + _workingRequest = ConnMan.addRequest(request); +} + +void OneDriveListDirectoryRequest::listedDirectoryCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + Common::JSONValue *json = response.value; + + if (_ignoreCallback) { + delete json; + return; + } + + if (response.request) + _date = response.request->date(); + + Networking::ErrorResponse error(this); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (!json->isObject()) { + error.response = "Passed JSON is not an object!"; + finishError(error); + delete json; + return; + } + + Common::JSONObject object = json->asObject(); + + //check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults + if (!Networking::CurlJsonRequest::jsonContainsArray(object, "value", "OneDriveListDirectoryRequest")) { + error.response = "\"value\" not found or that's not an array!"; + finishError(error); + delete json; + return; + } + + Common::JSONArray items = object.getVal("value")->asArray(); + for (uint32 i = 0; i < items.size(); ++i) { + if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "OneDriveListDirectoryRequest")) continue; + + Common::JSONObject item = items[i]->asObject(); + + if (!Networking::CurlJsonRequest::jsonContainsAttribute(item, "folder", "OneDriveListDirectoryRequest", true)) continue; + if (!Networking::CurlJsonRequest::jsonContainsString(item, "name", "OneDriveListDirectoryRequest")) continue; + if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(item, "size", "OneDriveListDirectoryRequest")) continue; + if (!Networking::CurlJsonRequest::jsonContainsString(item, "lastModifiedDateTime", "OneDriveListDirectoryRequest")) continue; + + Common::String path = _currentDirectory + item.getVal("name")->asString(); + bool isDirectory = item.contains("folder"); + uint32 size = item.getVal("size")->asIntegerNumber(); + uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("lastModifiedDateTime")->asString()); + + StorageFile file(path, size, timestamp, isDirectory); + _files.push_back(file); + if (_requestedRecursive && file.isDirectory()) { + _directoriesQueue.push_back(file.path()); + } + } + + bool hasMore = object.contains("@odata.nextLink"); + if (hasMore) { + if (!Networking::CurlJsonRequest::jsonContainsString(object, "@odata.nextLink", "OneDriveListDirectoryRequest")) { + error.response = "\"@odata.nextLink\" is not a string!"; + finishError(error); + delete json; + return; + } + + makeRequest(object.getVal("@odata.nextLink")->asString()); + } else { + listNextDirectory(); + } + + delete json; +} + +void OneDriveListDirectoryRequest::listedDirectoryErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + if (error.request) + _date = error.request->date(); + finishError(error); +} + +void OneDriveListDirectoryRequest::handle() {} + +void OneDriveListDirectoryRequest::restart() { start(); } + +Common::String OneDriveListDirectoryRequest::date() const { return _date; } + +void OneDriveListDirectoryRequest::finishListing(Common::Array<StorageFile> &files) { + Request::finishSuccess(); + if (_listDirectoryCallback) + (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files)); +} + +} // End of namespace OneDrive +} // End of namespace Cloud diff --git a/backends/cloud/onedrive/onedrivelistdirectoryrequest.h b/backends/cloud/onedrive/onedrivelistdirectoryrequest.h new file mode 100644 index 0000000000..eb510ab257 --- /dev/null +++ b/backends/cloud/onedrive/onedrivelistdirectoryrequest.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_ONEDRIVE_ONEDRIVELISTDIRECTORYREQUEST_H +#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVELISTDIRECTORYREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace OneDrive { + +class OneDriveStorage; + +class OneDriveListDirectoryRequest: public Networking::Request { + Common::String _requestedPath; + bool _requestedRecursive; + OneDriveStorage *_storage; + Storage::ListDirectoryCallback _listDirectoryCallback; + Common::Array<StorageFile> _files; + Common::Array<Common::String> _directoriesQueue; + Common::String _currentDirectory; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _date; + + void start(); + void listNextDirectory(); + void listedDirectoryCallback(Networking::JsonResponse response); + void listedDirectoryErrorCallback(Networking::ErrorResponse error); + void makeRequest(Common::String url); + void finishListing(Common::Array<StorageFile> &files); +public: + OneDriveListDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false); + virtual ~OneDriveListDirectoryRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; +}; + +} // End of namespace OneDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/onedrive/onedrivestorage.cpp b/backends/cloud/onedrive/onedrivestorage.cpp new file mode 100644 index 0000000000..4b70bb73b9 --- /dev/null +++ b/backends/cloud/onedrive/onedrivestorage.cpp @@ -0,0 +1,326 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/cloud/onedrive/onedrivecreatedirectoryrequest.h" +#include "backends/cloud/onedrive/onedrivetokenrefresher.h" +#include "backends/cloud/onedrive/onedrivelistdirectoryrequest.h" +#include "backends/cloud/onedrive/onedriveuploadrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +#ifdef ENABLE_RELEASE +#include "dists/clouds/cloud_keys.h" +#endif + +namespace Cloud { +namespace OneDrive { + +#define ONEDRIVE_OAUTH2_TOKEN "https://login.live.com/oauth20_token.srf" +#define ONEDRIVE_API_SPECIAL_APPROOT_ID "https://api.onedrive.com/v1.0/drive/special/approot:/" +#define ONEDRIVE_API_SPECIAL_APPROOT "https://api.onedrive.com/v1.0/drive/special/approot" + +char *OneDriveStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth +char *OneDriveStorage::SECRET = nullptr; + +void OneDriveStorage::loadKeyAndSecret() { +#ifdef ENABLE_RELEASE + KEY = RELEASE_ONEDRIVE_KEY; + SECRET = RELEASE_ONEDRIVE_SECRET; +#else + Common::String k = ConfMan.get("ONEDRIVE_KEY", ConfMan.kCloudDomain); + KEY = new char[k.size() + 1]; + memcpy(KEY, k.c_str(), k.size()); + KEY[k.size()] = 0; + + k = ConfMan.get("ONEDRIVE_SECRET", ConfMan.kCloudDomain); + SECRET = new char[k.size() + 1]; + memcpy(SECRET, k.c_str(), k.size()); + SECRET[k.size()] = 0; +#endif +} + +OneDriveStorage::OneDriveStorage(Common::String accessToken, Common::String userId, Common::String refreshToken): + _token(accessToken), _uid(userId), _refreshToken(refreshToken) {} + +OneDriveStorage::OneDriveStorage(Common::String code) { + getAccessToken( + new Common::Callback<OneDriveStorage, BoolResponse>(this, &OneDriveStorage::codeFlowComplete), + new Common::Callback<OneDriveStorage, Networking::ErrorResponse>(this, &OneDriveStorage::codeFlowFailed), + code + ); +} + +OneDriveStorage::~OneDriveStorage() {} + +void OneDriveStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) { + if (!KEY || !SECRET) + loadKeyAndSecret(); + bool codeFlow = (code != ""); + + if (!codeFlow && _refreshToken == "") { + warning("OneDriveStorage: no refresh token available to get new access token."); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + return; + } + + Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, BoolResponse, Networking::JsonResponse>(this, &OneDriveStorage::tokenRefreshed, callback); + if (errorCallback == nullptr) + errorCallback = getErrorPrintingCallback(); + Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, ONEDRIVE_OAUTH2_TOKEN); + if (codeFlow) { + request->addPostField("code=" + code); + request->addPostField("grant_type=authorization_code"); + } else { + request->addPostField("refresh_token=" + _refreshToken); + request->addPostField("grant_type=refresh_token"); + } + request->addPostField("client_id=" + Common::String(KEY)); + request->addPostField("client_secret=" + Common::String(SECRET)); + if (Cloud::CloudManager::couldUseLocalServer()) { + request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2F"); + } else { + request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code"); + } + addRequest(request); +} + +void OneDriveStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("OneDriveStorage: got NULL instead of JSON"); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + delete callback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "OneDriveStorage")) { + if (callback) + (*callback)(BoolResponse(nullptr, false)); + delete json; + delete callback; + return; + } + + Common::JSONObject result = json->asObject(); + if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "OneDriveStorage") || + !Networking::CurlJsonRequest::jsonContainsString(result, "user_id", "OneDriveStorage") || + !Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "OneDriveStorage")) { + warning("OneDriveStorage: bad response, no token or user_id passed"); + debug(9, "%s", json->stringify().c_str()); + if (callback) + (*callback)(BoolResponse(nullptr, false)); + } else { + _token = result.getVal("access_token")->asString(); + _uid = result.getVal("user_id")->asString(); + _refreshToken = result.getVal("refresh_token")->asString(); + CloudMan.save(); //ask CloudManager to save our new refreshToken + if (callback) + (*callback)(BoolResponse(nullptr, true)); + } + delete json; + delete callback; +} + +void OneDriveStorage::codeFlowComplete(BoolResponse response) { + if (!response.value) { + warning("OneDriveStorage: failed to get access token through code flow"); + CloudMan.removeStorage(this); + return; + } + + ConfMan.removeKey("onedrive_code", ConfMan.kCloudDomain); + CloudMan.replaceStorage(this, kStorageOneDriveId); + ConfMan.flushToDisk(); +} + +void OneDriveStorage::codeFlowFailed(Networking::ErrorResponse error) { + debug(9, "OneDriveStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode); + debug(9, "%s", error.response.c_str()); + CloudMan.removeStorage(this); +} + +void OneDriveStorage::saveConfig(Common::String keyPrefix) { + ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain); + ConfMan.set(keyPrefix + "user_id", _uid, ConfMan.kCloudDomain); + ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain); +} + +Common::String OneDriveStorage::name() const { + return "OneDrive"; +} + +void OneDriveStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("OneDriveStorage::infoInnerCallback: NULL passed instead of JSON"); + delete outerCallback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "OneDriveStorage::infoInnerCallback")) { + delete json; + delete outerCallback; + return; + } + + Common::JSONObject info = json->asObject(); + + Common::String uid, name, email; + uint64 quotaUsed = 0, quotaAllocated = 26843545600L; // 25 GB, because I actually don't know any way to find out the real one + + if (Networking::CurlJsonRequest::jsonContainsObject(info, "createdBy", "OneDriveStorage::infoInnerCallback")) { + Common::JSONObject createdBy = info.getVal("createdBy")->asObject(); + if (Networking::CurlJsonRequest::jsonContainsObject(createdBy, "user", "OneDriveStorage::infoInnerCallback")) { + Common::JSONObject user = createdBy.getVal("user")->asObject(); + if (Networking::CurlJsonRequest::jsonContainsString(user, "id", "OneDriveStorage::infoInnerCallback")) + uid = user.getVal("id")->asString(); + if (Networking::CurlJsonRequest::jsonContainsString(user, "displayName", "OneDriveStorage::infoInnerCallback")) + name = user.getVal("displayName")->asString(); + } + } + + if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "size", "OneDriveStorage::infoInnerCallback")) { + quotaUsed = info.getVal("size")->asIntegerNumber(); + } + + Common::String username = email; + if (username == "") + username = name; + if (username == "") + username = uid; + CloudMan.setStorageUsername(kStorageOneDriveId, username); + + if (outerCallback) { + (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated))); + delete outerCallback; + } + + delete json; +} + +void OneDriveStorage::fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, Networking::JsonResponse response) { + Common::JSONValue *json = response.value; + if (!json) { + warning("OneDriveStorage::fileInfoCallback: NULL passed instead of JSON"); + if (outerCallback) + (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr)); + delete outerCallback; + return; + } + + if (!Networking::CurlJsonRequest::jsonIsObject(json, "OneDriveStorage::fileInfoCallback")) { + if (outerCallback) + (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr)); + delete json; + delete outerCallback; + return; + } + + Common::JSONObject result = response.value->asObject(); + if (!Networking::CurlJsonRequest::jsonContainsString(result, "@content.downloadUrl", "OneDriveStorage::fileInfoCallback")) { + warning("OneDriveStorage: downloadUrl not found in passed JSON"); + debug(9, "%s", response.value->stringify().c_str()); + if (outerCallback) + (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr)); + delete json; + delete outerCallback; + return; + } + + const char *url = result.getVal("@content.downloadUrl")->asString().c_str(); + if (outerCallback) + (*outerCallback)(Networking::NetworkReadStreamResponse( + response.request, + new Networking::NetworkReadStream(url, nullptr, "") + )); + + delete json; + delete outerCallback; +} + +Networking::Request *OneDriveStorage::listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) { + return addRequest(new OneDriveListDirectoryRequest(this, path, callback, errorCallback, recursive)); +} + +Networking::Request *OneDriveStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) { + return addRequest(new OneDriveUploadRequest(this, path, contents, callback, errorCallback)); +} + +Networking::Request *OneDriveStorage::streamFileById(Common::String path, Networking::NetworkReadStreamCallback outerCallback, Networking::ErrorCallback errorCallback) { + Common::String url = ONEDRIVE_API_SPECIAL_APPROOT_ID + ConnMan.urlEncode(path); + Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, Networking::NetworkReadStreamResponse, Networking::JsonResponse>(this, &OneDriveStorage::fileInfoCallback, outerCallback); + Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(this, innerCallback, errorCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _token); + return addRequest(request); +} + +Networking::Request *OneDriveStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + return addRequest(new OneDriveCreateDirectoryRequest(this, path, callback, errorCallback)); +} + +Networking::Request *OneDriveStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) { + Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, StorageInfoResponse, Networking::JsonResponse>(this, &OneDriveStorage::infoInnerCallback, callback); + Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(this, innerCallback, errorCallback, ONEDRIVE_API_SPECIAL_APPROOT); + request->addHeader("Authorization: bearer " + _token); + return addRequest(request); +} + +Common::String OneDriveStorage::savesDirectoryPath() { return "saves/"; } + +OneDriveStorage *OneDriveStorage::loadFromConfig(Common::String keyPrefix) { + loadKeyAndSecret(); + + if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) { + warning("OneDriveStorage: no access_token found"); + return nullptr; + } + + if (!ConfMan.hasKey(keyPrefix + "user_id", ConfMan.kCloudDomain)) { + warning("OneDriveStorage: no user_id found"); + return nullptr; + } + + if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) { + warning("OneDriveStorage: no refresh_token found"); + return nullptr; + } + + Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain); + Common::String userId = ConfMan.get(keyPrefix + "user_id", ConfMan.kCloudDomain); + Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain); + return new OneDriveStorage(accessToken, userId, refreshToken); +} + +} // End of namespace OneDrive +} // End of namespace Cloud diff --git a/backends/cloud/onedrive/onedrivestorage.h b/backends/cloud/onedrive/onedrivestorage.h new file mode 100644 index 0000000000..59c61074e3 --- /dev/null +++ b/backends/cloud/onedrive/onedrivestorage.h @@ -0,0 +1,113 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_CLOUD_ONEDRIVE_ONEDRIVESTORAGE_H +#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVESTORAGE_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace OneDrive { + +class OneDriveStorage: public Cloud::Storage { + static char *KEY, *SECRET; + + static void loadKeyAndSecret(); + + Common::String _token, _uid, _refreshToken; + + /** This private constructor is called from loadFromConfig(). */ + OneDriveStorage(Common::String token, Common::String uid, Common::String refreshToken); + + void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response); + void codeFlowComplete(BoolResponse response); + void codeFlowFailed(Networking::ErrorResponse error); + + /** Constructs StorageInfo based on JSON response from cloud. */ + void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json); + + void fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, Networking::JsonResponse response); +public: + /** This constructor uses OAuth code flow to get tokens. */ + OneDriveStorage(Common::String code); + virtual ~OneDriveStorage(); + + /** + * Storage methods, which are used by CloudManager to save + * storage in configuration file. + */ + + /** + * Save storage data using ConfMan. + * @param keyPrefix all saved keys must start with this prefix. + * @note every Storage must write keyPrefix + "type" key + * with common value (e.g. "Dropbox"). + */ + virtual void saveConfig(Common::String keyPrefix); + + /** + * Return unique storage name. + * @returns some unique storage name (for example, "Dropbox (user@example.com)") + */ + virtual Common::String name() const; + + /** Public Cloud API comes down there. */ + + /** Returns ListDirectoryStatus struct with list of files. */ + virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + + /** Returns UploadStatus struct with info about uploaded file. */ + virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + + /** Calls the callback when finished. */ + virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns the StorageInfo struct. */ + virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns storage's saves directory path with the trailing slash. */ + virtual Common::String savesDirectoryPath(); + + /** + * Load token and user id from configs and return OneDriveStorage for those. + * @return pointer to the newly created OneDriveStorage or 0 if some problem occured. + */ + static OneDriveStorage *loadFromConfig(Common::String keyPrefix); + + /** + * Gets new access_token. If <code> passed is "", refresh_token is used. + * Use "" in order to refresh token and pass a callback, so you could + * continue your work when new token is available. + */ + void getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr, Common::String code = ""); + + Common::String accessToken() const { return _token; } +}; + +} // End of namespace OneDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/onedrive/onedrivetokenrefresher.cpp b/backends/cloud/onedrive/onedrivetokenrefresher.cpp new file mode 100644 index 0000000000..ce7895f41c --- /dev/null +++ b/backends/cloud/onedrive/onedrivetokenrefresher.cpp @@ -0,0 +1,130 @@ +/* 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. +* +*/ +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/cloud/onedrive/onedrivetokenrefresher.h" +#include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +namespace Cloud { +namespace OneDrive { + +OneDriveTokenRefresher::OneDriveTokenRefresher(OneDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url): + CurlJsonRequest(callback, ecb, url), _parentStorage(parent) {} + +OneDriveTokenRefresher::~OneDriveTokenRefresher() {} + +void OneDriveTokenRefresher::tokenRefreshed(Storage::BoolResponse response) { + if (!response.value) { + //failed to refresh token, notify user with NULL in original callback + warning("OneDriveTokenRefresher: failed to refresh token"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + //update headers: first change header with token, then pass those to request + for (uint32 i = 0; i < _headers.size(); ++i) { + if (_headers[i].contains("Authorization")) { + _headers[i] = "Authorization: bearer " + _parentStorage->accessToken(); + } + } + setHeaders(_headers); + + //successfully received refreshed token, can restart the original request now + retry(0); +} + +void OneDriveTokenRefresher::finishJson(Common::JSONValue *json) { + if (!json) { + //that's probably not an error (200 OK) + CurlJsonRequest::finishJson(nullptr); + return; + } + + if (jsonIsObject(json, "OneDriveTokenRefresher")) { + Common::JSONObject result = json->asObject(); + long httpResponseCode = -1; + if (result.contains("error") && jsonIsObject(result.getVal("error"), "OneDriveTokenRefresher")) { + //new token needed => request token & then retry original request + if (_stream) { + httpResponseCode = _stream->httpResponseCode(); + debug(9, "OneDriveTokenRefresher: code = %ld", httpResponseCode); + } + + Common::JSONObject error = result.getVal("error")->asObject(); + bool irrecoverable = true; + + Common::String code, message; + if (jsonContainsString(error, "code", "OneDriveTokenRefresher")) { + code = error.getVal("code")->asString(); + debug(9, "OneDriveTokenRefresher: code = %s", code.c_str()); + } + + if (jsonContainsString(error, "message", "OneDriveTokenRefresher")) { + message = error.getVal("message")->asString(); + debug(9, "OneDriveTokenRefresher: message = %s", message.c_str()); + } + + //determine whether token refreshing would help in this situation + if (code == "itemNotFound") { + if (message.contains("application ID")) + irrecoverable = false; + } + + if (code == "unauthenticated") + irrecoverable = false; + + if (irrecoverable) { + finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpResponseCode)); + delete json; + return; + } + + pause(); + delete json; + _parentStorage->getAccessToken(new Common::Callback<OneDriveTokenRefresher, Storage::BoolResponse>(this, &OneDriveTokenRefresher::tokenRefreshed)); + return; + } + } + + //notify user of success + CurlJsonRequest::finishJson(json); +} + +void OneDriveTokenRefresher::setHeaders(Common::Array<Common::String> &headers) { + _headers = headers; + curl_slist_free_all(_headersList); + _headersList = 0; + for (uint32 i = 0; i < headers.size(); ++i) + CurlJsonRequest::addHeader(headers[i]); +} + +void OneDriveTokenRefresher::addHeader(Common::String header) { + _headers.push_back(header); + CurlJsonRequest::addHeader(header); +} + +} // End of namespace OneDrive +} // End of namespace Cloud diff --git a/backends/cloud/onedrive/onedrivetokenrefresher.h b/backends/cloud/onedrive/onedrivetokenrefresher.h new file mode 100644 index 0000000000..77e34d4e03 --- /dev/null +++ b/backends/cloud/onedrive/onedrivetokenrefresher.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_ONEDRIVE_ONEDRIVETOKENREFRESHER_H +#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVETOKENREFRESHER_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" + +namespace Cloud { +namespace OneDrive { + +class OneDriveStorage; + +class OneDriveTokenRefresher: public Networking::CurlJsonRequest { + OneDriveStorage *_parentStorage; + Common::Array<Common::String> _headers; + + void tokenRefreshed(Storage::BoolResponse response); + + virtual void finishJson(Common::JSONValue *json); +public: + OneDriveTokenRefresher(OneDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url); + virtual ~OneDriveTokenRefresher(); + + virtual void setHeaders(Common::Array<Common::String> &headers); + virtual void addHeader(Common::String header); +}; + +} // End of namespace OneDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/onedrive/onedriveuploadrequest.cpp b/backends/cloud/onedrive/onedriveuploadrequest.cpp new file mode 100644 index 0000000000..41e6e2a37b --- /dev/null +++ b/backends/cloud/onedrive/onedriveuploadrequest.cpp @@ -0,0 +1,191 @@ +/* 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 "backends/cloud/onedrive/onedriveuploadrequest.h" +#include "backends/cloud/onedrive/onedrivestorage.h" +#include "backends/cloud/iso8601.h" +#include "backends/cloud/storage.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/json.h" +#include "onedrivetokenrefresher.h" + +namespace Cloud { +namespace OneDrive { + +#define ONEDRIVE_API_SPECIAL_APPROOT_UPLOAD "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/upload.createSession" +#define ONEDRIVE_API_SPECIAL_APPROOT_CONTENT "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/content" + +OneDriveUploadRequest::OneDriveUploadRequest(OneDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb): + Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _contentsStream(contents), _uploadCallback(callback), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +OneDriveUploadRequest::~OneDriveUploadRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _contentsStream; + delete _uploadCallback; +} + +void OneDriveUploadRequest::start() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + if (_contentsStream == nullptr) { + warning("OneDriveUploadRequest: cannot restart because no stream given"); + finishError(Networking::ErrorResponse(this, false, true, "No stream given", -1)); + return; + } + if (!_contentsStream->seek(0)) { + warning("OneDriveUploadRequest: cannot restart because stream couldn't seek(0)"); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + _ignoreCallback = false; + + uploadNextPart(); +} + +void OneDriveUploadRequest::uploadNextPart() { + const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024; + + if (_uploadUrl == "" && (uint32)_contentsStream->size() > UPLOAD_PER_ONE_REQUEST) { + Common::String url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_UPLOAD, ConnMan.urlEncode(_savePath).c_str()); //folder must exist + Networking::JsonCallback callback = new Common::Callback<OneDriveUploadRequest, Networking::JsonResponse>(this, &OneDriveUploadRequest::partUploadedCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveUploadRequest, Networking::ErrorResponse>(this, &OneDriveUploadRequest::partUploadedErrorCallback); + Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + request->setBuffer(new byte[1], 0); //use POST + _workingRequest = ConnMan.addRequest(request); + return; + } + + Common::String url; + if (_uploadUrl == "") { + url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_CONTENT, ConnMan.urlEncode(_savePath).c_str()); + } else { + url = _uploadUrl; + } + + Networking::JsonCallback callback = new Common::Callback<OneDriveUploadRequest, Networking::JsonResponse>(this, &OneDriveUploadRequest::partUploadedCallback); + Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveUploadRequest, Networking::ErrorResponse>(this, &OneDriveUploadRequest::partUploadedErrorCallback); + Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str()); + request->addHeader("Authorization: Bearer " + _storage->accessToken()); + request->usePut(); + + uint32 oldPos = _contentsStream->pos(); + + byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST]; + uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST); + request->setBuffer(buffer, size); + + if (_uploadUrl != "") { + request->addHeader(Common::String::format("Content-Range: bytes %u-%u/%u", oldPos, _contentsStream->pos() - 1, _contentsStream->size())); + } else if (_contentsStream->size() == 0) { + warning("\"Sorry, OneDrive can't upload empty files\""); + finishUpload(StorageFile(_savePath, 0, 0, false)); + delete request; + return; + } + + _workingRequest = ConnMan.addRequest(request); +} + +void OneDriveUploadRequest::partUploadedCallback(Networking::JsonResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + Networking::ErrorResponse error(this, false, true, "", -1); + Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request; + if (rq && rq->getNetworkReadStream()) + error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode(); + + Common::JSONValue *json = response.value; + if (json == nullptr) { + error.response = "Failed to parse JSON, null passed!"; + finishError(error); + return; + } + + if (json->isObject()) { + Common::JSONObject object = json->asObject(); + + if (object.contains("error")) { + warning("OneDriveUploadRequest: error: %s", json->stringify(true).c_str()); + error.response = json->stringify(true); + finishError(error); + delete json; + return; + } + + if (Networking::CurlJsonRequest::jsonContainsString(object, "id", "OneDriveUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(object, "name", "OneDriveUploadRequest") && + Networking::CurlJsonRequest::jsonContainsIntegerNumber(object, "size", "OneDriveUploadRequest") && + Networking::CurlJsonRequest::jsonContainsString(object, "lastModifiedDateTime", "OneDriveUploadRequest")) { + //finished + Common::String path = _savePath; + uint32 size = object.getVal("size")->asIntegerNumber(); + uint32 timestamp = ISO8601::convertToTimestamp(object.getVal("lastModifiedDateTime")->asString()); + finishUpload(StorageFile(path, size, timestamp, false)); + return; + } + + if (_uploadUrl == "") { + if (Networking::CurlJsonRequest::jsonContainsString(object, "uploadUrl", "OneDriveUploadRequest")) + _uploadUrl = object.getVal("uploadUrl")->asString(); + } + } + + if (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1) { + warning("OneDriveUploadRequest: no file info to return"); + finishUpload(StorageFile(_savePath, 0, 0, false)); + } else { + uploadNextPart(); + } + + delete json; +} + +void OneDriveUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + finishError(error); +} + +void OneDriveUploadRequest::handle() {} + +void OneDriveUploadRequest::restart() { start(); } + +void OneDriveUploadRequest::finishUpload(StorageFile file) { + Request::finishSuccess(); + if (_uploadCallback) + (*_uploadCallback)(Storage::UploadResponse(this, file)); +} + +} // End of namespace OneDrive +} // End of namespace Cloud diff --git a/backends/cloud/onedrive/onedriveuploadrequest.h b/backends/cloud/onedrive/onedriveuploadrequest.h new file mode 100644 index 0000000000..4e2cb24a7f --- /dev/null +++ b/backends/cloud/onedrive/onedriveuploadrequest.h @@ -0,0 +1,61 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_CLOUD_ONEDRIVE_ONEDRIVEUPLOADREQUEST_H +#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVEUPLOADREQUEST_H + +#include "backends/cloud/storage.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/request.h" +#include "common/callback.h" + +namespace Cloud { +namespace OneDrive { +class OneDriveStorage; + +class OneDriveUploadRequest: public Networking::Request { + OneDriveStorage *_storage; + Common::String _savePath; + Common::SeekableReadStream *_contentsStream; + Storage::UploadCallback _uploadCallback; + Request *_workingRequest; + bool _ignoreCallback; + Common::String _uploadUrl; + + void start(); + void uploadNextPart(); + void partUploadedCallback(Networking::JsonResponse response); + void partUploadedErrorCallback(Networking::ErrorResponse error); + void finishUpload(StorageFile status); + +public: + OneDriveUploadRequest(OneDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb); + virtual ~OneDriveUploadRequest(); + + virtual void handle(); + virtual void restart(); +}; + +} // End of namespace OneDrive +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/savessyncrequest.cpp b/backends/cloud/savessyncrequest.cpp new file mode 100644 index 0000000000..fff46c3a83 --- /dev/null +++ b/backends/cloud/savessyncrequest.cpp @@ -0,0 +1,403 @@ +/* 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 "backends/cloud/savessyncrequest.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/saves/default/default-saves.h" +#include "common/config-manager.h" +#include "common/debug.h" +#include "common/file.h" +#include "common/json.h" +#include "common/savefile.h" +#include "common/system.h" +#include "gui/saveload-dialog.h" + +namespace Cloud { + +SavesSyncRequest::SavesSyncRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb): + Request(nullptr, ecb), CommandSender(nullptr), _storage(storage), _boolCallback(callback), + _workingRequest(nullptr), _ignoreCallback(false) { + start(); +} + +SavesSyncRequest::~SavesSyncRequest() { + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + delete _boolCallback; +} + +void SavesSyncRequest::start() { + //cleanup + _ignoreCallback = true; + if (_workingRequest) + _workingRequest->finish(); + _currentDownloadingFile = StorageFile(); + _currentUploadingFile = ""; + _filesToDownload.clear(); + _filesToUpload.clear(); + _localFilesTimestamps.clear(); + _totalFilesToHandle = 0; + _ignoreCallback = false; + + //load timestamps + _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps(); + + //list saves directory + Common::String dir = _storage->savesDirectoryPath(); + if (dir.lastChar() == '/') + dir.deleteLastChar(); + _workingRequest = _storage->listDirectory( + dir, + new Common::Callback<SavesSyncRequest, Storage::ListDirectoryResponse>(this, &SavesSyncRequest::directoryListedCallback), + new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::directoryListedErrorCallback) + ); + if (!_workingRequest) finishError(Networking::ErrorResponse(this)); +} + +void SavesSyncRequest::directoryListedCallback(Storage::ListDirectoryResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + if (response.request) _date = response.request->date(); + + Common::HashMap<Common::String, bool> localFileNotAvailableInCloud; + for (Common::HashMap<Common::String, uint32>::iterator i = _localFilesTimestamps.begin(); i != _localFilesTimestamps.end(); ++i) { + localFileNotAvailableInCloud[i->_key] = true; + } + + //determine which files to download and which files to upload + Common::Array<StorageFile> &remoteFiles = response.value; + uint64 totalSize = 0; + for (uint32 i = 0; i < remoteFiles.size(); ++i) { + StorageFile &file = remoteFiles[i]; + if (file.isDirectory()) + continue; + totalSize += file.size(); + if (file.name() == DefaultSaveFileManager::TIMESTAMPS_FILENAME) + continue; + + Common::String name = file.name(); + if (!_localFilesTimestamps.contains(name)) { + _filesToDownload.push_back(file); + } else { + localFileNotAvailableInCloud[name] = false; + + if (_localFilesTimestamps[name] == file.timestamp()) + continue; + + //we actually can have some files not only with timestamp < remote + //but also with timestamp > remote (when we have been using ANOTHER CLOUD and then switched back) + if (_localFilesTimestamps[name] > file.timestamp() || _localFilesTimestamps[name] == DefaultSaveFileManager::INVALID_TIMESTAMP) + _filesToUpload.push_back(file.name()); + else + _filesToDownload.push_back(file); + } + } + + CloudMan.setStorageUsedSpace(CloudMan.getStorageIndex(), totalSize); + + //upload files which are unavailable in cloud + for (Common::HashMap<Common::String, bool>::iterator i = localFileNotAvailableInCloud.begin(); i != localFileNotAvailableInCloud.end(); ++i) { + if (i->_key == DefaultSaveFileManager::TIMESTAMPS_FILENAME) + continue; + if (i->_value) + _filesToUpload.push_back(i->_key); + } + + debug(9, "\nSavesSyncRequest: download files:"); + for (uint32 i = 0; i < _filesToDownload.size(); ++i) { + debug(9, "%s", _filesToDownload[i].name().c_str()); + } + debug(9, "\nSavesSyncRequest: upload files:"); + for (uint32 i = 0; i < _filesToUpload.size(); ++i) { + debug(9, "%s", _filesToUpload[i].c_str()); + } + _totalFilesToHandle = _filesToDownload.size() + _filesToUpload.size(); + + //start downloading files + downloadNextFile(); +} + +void SavesSyncRequest::directoryListedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + bool irrecoverable = error.interrupted || error.failed; + if (error.failed) { + Common::JSONValue *value = Common::JSON::parse(error.response.c_str()); + if (value) { + if (value->isObject()) { + Common::JSONObject object = value->asObject(); + + //Dropbox-related error: + if (object.contains("error_summary") && object.getVal("error_summary")->isString()) { + Common::String summary = object.getVal("error_summary")->asString(); + if (summary.contains("not_found")) { + irrecoverable = false; + } + } + + //OneDrive-related error: + if (object.contains("error") && object.getVal("error")->isObject()) { + Common::JSONObject errorNode = object.getVal("error")->asObject(); + if (Networking::CurlJsonRequest::jsonContainsString(errorNode, "code", "SavesSyncRequest")) { + Common::String code = errorNode.getVal("code")->asString(); + if (code == "itemNotFound") { + irrecoverable = false; + } + } + } + } + delete value; + } + + //Google Drive and Box-related ScummVM-based error + if (error.response.contains("subdirectory not found")) { + irrecoverable = false; //base "/ScummVM/" folder not found + } else if (error.response.contains("no such file found in its parent directory")) { + irrecoverable = false; //"Saves" folder within "/ScummVM/" not found + } + } + + if (irrecoverable) { + finishError(error); + return; + } + + //we're lucky - user just lacks his "/cloud/" folder - let's create one + Common::String dir = _storage->savesDirectoryPath(); + if (dir.lastChar() == '/') + dir.deleteLastChar(); + debug(9, "SavesSyncRequest: creating %s", dir.c_str()); + _workingRequest = _storage->createDirectory( + dir, + new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::directoryCreatedCallback), + new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::directoryCreatedErrorCallback) + ); + if (!_workingRequest) + finishError(Networking::ErrorResponse(this)); +} + +void SavesSyncRequest::directoryCreatedCallback(Storage::BoolResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //stop syncing if failed to create saves directory + if (!response.value) { + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + //continue with empty files list + Common::Array<StorageFile> files; + directoryListedCallback(Storage::ListDirectoryResponse(response.request, files)); +} + +void SavesSyncRequest::directoryCreatedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //stop syncing if failed to create saves directory + finishError(error); +} + +void SavesSyncRequest::downloadNextFile() { + if (_filesToDownload.empty()) { + _currentDownloadingFile = StorageFile("", 0, 0, false); //so getFilesToDownload() would return an empty array + sendCommand(GUI::kSavesSyncEndedCmd, 0); + uploadNextFile(); + return; + } + + _currentDownloadingFile = _filesToDownload.back(); + _filesToDownload.pop_back(); + + sendCommand(GUI::kSavesSyncProgressCmd, (int)(getDownloadingProgress() * 100)); + + debug(9, "SavesSyncRequest: downloading %s (%d %%)", _currentDownloadingFile.name().c_str(), (int)(getProgress() * 100)); + _workingRequest = _storage->downloadById( + _currentDownloadingFile.id(), + DefaultSaveFileManager::concatWithSavesPath(_currentDownloadingFile.name()), + new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::fileDownloadedCallback), + new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileDownloadedErrorCallback) + ); + if (!_workingRequest) + finishError(Networking::ErrorResponse(this)); +} + +void SavesSyncRequest::fileDownloadedCallback(Storage::BoolResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //stop syncing if download failed + if (!response.value) { + //delete the incomplete file + g_system->getSavefileManager()->removeSavefile(_currentDownloadingFile.name()); + finishError(Networking::ErrorResponse(this, false, true, "", -1)); + return; + } + + //update local timestamp for downloaded file + _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps(); + _localFilesTimestamps[_currentDownloadingFile.name()] = _currentDownloadingFile.timestamp(); + DefaultSaveFileManager::saveTimestamps(_localFilesTimestamps); + + //continue downloading files + downloadNextFile(); +} + +void SavesSyncRequest::fileDownloadedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //stop syncing if download failed + finishError(error); +} + +void SavesSyncRequest::uploadNextFile() { + if (_filesToUpload.empty()) { + finishSync(true); + return; + } + + _currentUploadingFile = _filesToUpload.back(); + _filesToUpload.pop_back(); + + debug(9, "SavesSyncRequest: uploading %s (%d %%)", _currentUploadingFile.c_str(), (int)(getProgress() * 100)); + if (_storage->uploadStreamSupported()) { + _workingRequest = _storage->upload( + _storage->savesDirectoryPath() + _currentUploadingFile, + g_system->getSavefileManager()->openRawFile(_currentUploadingFile), + new Common::Callback<SavesSyncRequest, Storage::UploadResponse>(this, &SavesSyncRequest::fileUploadedCallback), + new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileUploadedErrorCallback) + ); + } else { + _workingRequest = _storage->upload( + _storage->savesDirectoryPath() + _currentUploadingFile, + DefaultSaveFileManager::concatWithSavesPath(_currentUploadingFile), + new Common::Callback<SavesSyncRequest, Storage::UploadResponse>(this, &SavesSyncRequest::fileUploadedCallback), + new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileUploadedErrorCallback) + ); + } + if (!_workingRequest) finishError(Networking::ErrorResponse(this)); +} + +void SavesSyncRequest::fileUploadedCallback(Storage::UploadResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //update local timestamp for the uploaded file + _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps(); + _localFilesTimestamps[_currentUploadingFile] = response.value.timestamp(); + DefaultSaveFileManager::saveTimestamps(_localFilesTimestamps); + + //continue uploading files + uploadNextFile(); +} + +void SavesSyncRequest::fileUploadedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + //stop syncing if upload failed + finishError(error); +} + +void SavesSyncRequest::handle() {} + +void SavesSyncRequest::restart() { start(); } + +double SavesSyncRequest::getDownloadingProgress() const { + if (_totalFilesToHandle == 0) { + if (_state == Networking::FINISHED) + return 1; //nothing to upload and download => Request ends soon + return 0; //directory not listed yet + } + + if (_totalFilesToHandle == _filesToUpload.size()) + return 1; //nothing to download => download complete + + uint32 totalFilesToDownload = _totalFilesToHandle - _filesToUpload.size(); + uint32 filesLeftToDownload = _filesToDownload.size() + (_currentDownloadingFile.name() != "" ? 1 : 0); + return (double)(totalFilesToDownload - filesLeftToDownload) / (double)(totalFilesToDownload); +} + +double SavesSyncRequest::getProgress() const { + if (_totalFilesToHandle == 0) { + if (_state == Networking::FINISHED) + return 1; //nothing to upload and download => Request ends soon + return 0; //directory not listed yet + } + + return (double)(_totalFilesToHandle - _filesToDownload.size() - _filesToUpload.size()) / (double)(_totalFilesToHandle); +} + +Common::Array<Common::String> SavesSyncRequest::getFilesToDownload() { + Common::Array<Common::String> result; + for (uint32 i = 0; i < _filesToDownload.size(); ++i) + result.push_back(_filesToDownload[i].name()); + if (_currentDownloadingFile.name() != "") + result.push_back(_currentDownloadingFile.name()); + return result; +} + +void SavesSyncRequest::finishError(Networking::ErrorResponse error) { + debug(9, "SavesSync::finishError"); + //if we were downloading a file - remember the name + //and make the Request close() it, so we can delete it + Common::String name = _currentDownloadingFile.name(); + if (_workingRequest) { + _ignoreCallback = true; + _workingRequest->finish(); + _workingRequest = nullptr; + _ignoreCallback = false; + } + //unlock all the files by making getFilesToDownload() return empty array + _currentDownloadingFile = StorageFile(); + _filesToDownload.clear(); + //delete the incomplete file + if (name != "") + g_system->getSavefileManager()->removeSavefile(name); + Request::finishError(error); +} + +void SavesSyncRequest::finishSync(bool success) { + Request::finishSuccess(); + + //update last successful sync date + CloudMan.setStorageLastSync(CloudMan.getStorageIndex(), _date); + + if (_boolCallback) + (*_boolCallback)(Storage::BoolResponse(this, success)); +} + +} // End of namespace Cloud diff --git a/backends/cloud/savessyncrequest.h b/backends/cloud/savessyncrequest.h new file mode 100644 index 0000000000..e891e93969 --- /dev/null +++ b/backends/cloud/savessyncrequest.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_SAVESSYNCREQUEST_H +#define BACKENDS_CLOUD_SAVESSYNCREQUEST_H + +#include "backends/networking/curl/request.h" +#include "backends/cloud/storage.h" +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "gui/object.h" + +namespace Cloud { + +class SavesSyncRequest: public Networking::Request, public GUI::CommandSender { + Storage *_storage; + Storage::BoolCallback _boolCallback; + Common::HashMap<Common::String, uint32> _localFilesTimestamps; + Common::Array<StorageFile> _filesToDownload; + Common::Array<Common::String> _filesToUpload; + StorageFile _currentDownloadingFile; + Common::String _currentUploadingFile; + Request *_workingRequest; + bool _ignoreCallback; + uint32 _totalFilesToHandle; + Common::String _date; + + void start(); + void directoryListedCallback(Storage::ListDirectoryResponse response); + void directoryListedErrorCallback(Networking::ErrorResponse error); + void directoryCreatedCallback(Storage::BoolResponse response); + void directoryCreatedErrorCallback(Networking::ErrorResponse error); + void fileDownloadedCallback(Storage::BoolResponse response); + void fileDownloadedErrorCallback(Networking::ErrorResponse error); + void fileUploadedCallback(Storage::UploadResponse response); + void fileUploadedErrorCallback(Networking::ErrorResponse error); + void downloadNextFile(); + void uploadNextFile(); + virtual void finishError(Networking::ErrorResponse error); + void finishSync(bool success); + +public: + SavesSyncRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb); + virtual ~SavesSyncRequest(); + + virtual void handle(); + virtual void restart(); + + /** Returns a number in range [0, 1], where 1 is "complete". */ + double getDownloadingProgress() const; + + /** Returns a number in range [0, 1], where 1 is "complete". */ + double getProgress() const; + + /** Returns an array of saves names which are not downloaded yet. */ + Common::Array<Common::String> getFilesToDownload(); +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/storage.cpp b/backends/cloud/storage.cpp new file mode 100644 index 0000000000..910d80d153 --- /dev/null +++ b/backends/cloud/storage.cpp @@ -0,0 +1,342 @@ +/* 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 "backends/cloud/storage.h" +#include "backends/cloud/downloadrequest.h" +#include "backends/cloud/folderdownloadrequest.h" +#include "backends/cloud/savessyncrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#include "common/debug.h" +#include "common/file.h" +#include <common/translation.h> + +namespace Cloud { + +Storage::Storage(): + _runningRequestsCount(0), _savesSyncRequest(nullptr), _syncRestartRequestsed(false), + _downloadFolderRequest(nullptr) {} + +Storage::~Storage() {} + +Networking::ErrorCallback Storage::getErrorPrintingCallback() { + return new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::printErrorResponse); +} + +void Storage::printErrorResponse(Networking::ErrorResponse error) { + debug(9, "Storage: error response (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode); + debug(9, "%s", error.response.c_str()); +} + +Networking::Request *Storage::addRequest(Networking::Request *request) { + _runningRequestsMutex.lock(); + ++_runningRequestsCount; + if (_runningRequestsCount == 1) + debug(9, "Storage is working now"); + _runningRequestsMutex.unlock(); + return ConnMan.addRequest(request, new Common::Callback<Storage, Networking::Request *>(this, &Storage::requestFinishedCallback)); +} + +void Storage::requestFinishedCallback(Networking::Request *invalidRequestPointer) { + bool restartSync = false; + + _runningRequestsMutex.lock(); + if (invalidRequestPointer == _savesSyncRequest) + _savesSyncRequest = nullptr; + --_runningRequestsCount; + if (_syncRestartRequestsed) + restartSync = true; + if (_runningRequestsCount == 0 && !restartSync) + debug(9, "Storage is not working now"); + _runningRequestsMutex.unlock(); + + if (restartSync) + syncSaves(nullptr, nullptr); +} + +Networking::Request *Storage::upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) errorCallback = getErrorPrintingCallback(); + + Common::File *f = new Common::File(); + if (!f->open(localPath)) { + warning("Storage: unable to open file to upload from"); + if (errorCallback) + (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "", -1)); + delete errorCallback; + delete callback; + delete f; + return nullptr; + } + + return upload(remotePath, f, callback, errorCallback); +} + +bool Storage::uploadStreamSupported() { + return true; +} + +Networking::Request *Storage::streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) { + //most Storages use paths instead of ids, so this should work + return streamFileById(path, callback, errorCallback); +} + +Networking::Request *Storage::download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) { + //most Storages use paths instead of ids, so this should work + return downloadById(remotePath, localPath, callback, errorCallback); +} + +Networking::Request *Storage::downloadById(Common::String remoteId, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) { + if (!errorCallback) errorCallback = getErrorPrintingCallback(); + + Common::DumpFile *f = new Common::DumpFile(); + if (!f->open(localPath, true)) { + warning("Storage: unable to open file to download into"); + if (errorCallback) (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "", -1)); + delete errorCallback; + delete callback; + delete f; + return nullptr; + } + + return addRequest(new DownloadRequest(this, callback, errorCallback, remoteId, f)); +} + +Networking::Request *Storage::downloadFolder(Common::String remotePath, Common::String localPath, FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive) { + if (!errorCallback) + errorCallback = getErrorPrintingCallback(); + return addRequest(new FolderDownloadRequest(this, callback, errorCallback, remotePath, localPath, recursive)); +} + +SavesSyncRequest *Storage::syncSaves(BoolCallback callback, Networking::ErrorCallback errorCallback) { + _runningRequestsMutex.lock(); + if (_savesSyncRequest) { + warning("Storage::syncSaves: there is a sync in progress already"); + _syncRestartRequestsed = true; + _runningRequestsMutex.unlock(); + return _savesSyncRequest; + } + if (!callback) + callback = new Common::Callback<Storage, BoolResponse>(this, &Storage::savesSyncDefaultCallback); + if (!errorCallback) + errorCallback = new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::savesSyncDefaultErrorCallback); + _savesSyncRequest = new SavesSyncRequest(this, callback, errorCallback); + _syncRestartRequestsed = false; + _runningRequestsMutex.unlock(); + return (SavesSyncRequest *)addRequest(_savesSyncRequest); //who knows what that ConnMan could return in the future +} + +bool Storage::isWorking() { + _runningRequestsMutex.lock(); + bool working = _runningRequestsCount > 0; + _runningRequestsMutex.unlock(); + return working; +} + +///// SavesSyncRequest-related ///// + +bool Storage::isSyncing() { + _runningRequestsMutex.lock(); + bool syncing = _savesSyncRequest != nullptr; + _runningRequestsMutex.unlock(); + return syncing; +} + +double Storage::getSyncDownloadingProgress() { + double result = 1; + _runningRequestsMutex.lock(); + if (_savesSyncRequest) + result = _savesSyncRequest->getDownloadingProgress(); + _runningRequestsMutex.unlock(); + return result; +} + +double Storage::getSyncProgress() { + double result = 1; + _runningRequestsMutex.lock(); + if (_savesSyncRequest) + result = _savesSyncRequest->getProgress(); + _runningRequestsMutex.unlock(); + return result; +} + +Common::Array<Common::String> Storage::getSyncingFiles() { + Common::Array<Common::String> result; + _runningRequestsMutex.lock(); + if (_savesSyncRequest) + result = _savesSyncRequest->getFilesToDownload(); + _runningRequestsMutex.unlock(); + return result; +} + +void Storage::cancelSync() { + _runningRequestsMutex.lock(); + if (_savesSyncRequest) + _savesSyncRequest->finish(); + _runningRequestsMutex.unlock(); +} + +void Storage::setSyncTarget(GUI::CommandReceiver *target) { + _runningRequestsMutex.lock(); + if (_savesSyncRequest) + _savesSyncRequest->setTarget(target); + _runningRequestsMutex.unlock(); +} + +void Storage::savesSyncDefaultCallback(BoolResponse response) { + _runningRequestsMutex.lock(); + _savesSyncRequest = nullptr; + _runningRequestsMutex.unlock(); + + if (!response.value) + warning("SavesSyncRequest called success callback with `false` argument"); + g_system->displayMessageOnOSD(_("Saves sync complete.")); +} + +void Storage::savesSyncDefaultErrorCallback(Networking::ErrorResponse error) { + _runningRequestsMutex.lock(); + _savesSyncRequest = nullptr; + _runningRequestsMutex.unlock(); + + printErrorResponse(error); + + if (error.interrupted) + g_system->displayMessageOnOSD(_("Saves sync was cancelled.")); + else + g_system->displayMessageOnOSD(_("Saves sync failed.\nCheck your Internet connection.")); +} + +///// DownloadFolderRequest-related ///// + +bool Storage::startDownload(Common::String remotePath, Common::String localPath) { + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) { + warning("Storage::startDownload: there is a download in progress already"); + _runningRequestsMutex.unlock(); + return false; + } + _downloadFolderRequest = (FolderDownloadRequest *)downloadFolder( + remotePath, localPath, + new Common::Callback<Storage, FileArrayResponse>(this, &Storage::directoryDownloadedCallback), + new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::directoryDownloadedErrorCallback), + true + ); + _runningRequestsMutex.unlock(); + return true; +} + +void Storage::cancelDownload() { + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + _downloadFolderRequest->finish(); + _runningRequestsMutex.unlock(); +} + +void Storage::setDownloadTarget(GUI::CommandReceiver *target) { + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + _downloadFolderRequest->setTarget(target); + _runningRequestsMutex.unlock(); +} + +bool Storage::isDownloading() { + _runningRequestsMutex.lock(); + bool syncing = _downloadFolderRequest != nullptr; + _runningRequestsMutex.unlock(); + return syncing; +} + +double Storage::getDownloadingProgress() { + double result = 1; + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + result = _downloadFolderRequest->getProgress(); + _runningRequestsMutex.unlock(); + return result; +} + +uint64 Storage::getDownloadBytesNumber() { + uint64 result = 0; + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + result = _downloadFolderRequest->getDownloadedBytes(); + _runningRequestsMutex.unlock(); + return result; +} + +uint64 Storage::getDownloadTotalBytesNumber() { + uint64 result = 0; + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + result = _downloadFolderRequest->getTotalBytesToDownload(); + _runningRequestsMutex.unlock(); + return result; +} + +uint64 Storage::getDownloadSpeed() { + uint64 result = 0; + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + result = _downloadFolderRequest->getDownloadSpeed(); + _runningRequestsMutex.unlock(); + return result; +} + +Common::String Storage::getDownloadRemoteDirectory() { + Common::String result = ""; + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + result = _downloadFolderRequest->getRemotePath(); + _runningRequestsMutex.unlock(); + return result; +} + +Common::String Storage::getDownloadLocalDirectory() { + Common::String result = ""; + _runningRequestsMutex.lock(); + if (_downloadFolderRequest) + result = _downloadFolderRequest->getLocalPath(); + _runningRequestsMutex.unlock(); + return result; +} + +void Storage::directoryDownloadedCallback(FileArrayResponse response) { + _runningRequestsMutex.lock(); + _downloadFolderRequest = nullptr; + _runningRequestsMutex.unlock(); + + Common::String message; + if (response.value.size()) { + message = Common::String::format(_("Download complete.\nFailed to download %u files."), response.value.size()); + } else { + message = _("Download complete."); + } + g_system->displayMessageOnOSD(message.c_str()); +} + +void Storage::directoryDownloadedErrorCallback(Networking::ErrorResponse error) { + _runningRequestsMutex.lock(); + _downloadFolderRequest = nullptr; + _runningRequestsMutex.unlock(); + + g_system->displayMessageOnOSD(_("Download failed.")); +} + +} // End of namespace Cloud diff --git a/backends/cloud/storage.h b/backends/cloud/storage.h new file mode 100644 index 0000000000..6a5765f13a --- /dev/null +++ b/backends/cloud/storage.h @@ -0,0 +1,238 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_CLOUD_STORAGE_H +#define BACKENDS_CLOUD_STORAGE_H + +#include "backends/cloud/storagefile.h" +#include "backends/cloud/storageinfo.h" +#include "backends/networking/curl/request.h" +#include "backends/networking/curl/curlrequest.h" +#include "common/array.h" +#include "common/callback.h" +#include "common/mutex.h" +#include "common/stream.h" +#include "common/str.h" + +namespace GUI { + +class CommandReceiver; + +} + +namespace Cloud { + +class SavesSyncRequest; +class FolderDownloadRequest; + +class Storage { +public: + typedef Networking::Response<Common::Array<StorageFile>&> FileArrayResponse; + typedef Networking::Response<StorageInfo> StorageInfoResponse; + typedef Networking::Response<bool> BoolResponse; + typedef Networking::Response<StorageFile> UploadResponse; + typedef Networking::Response<Common::Array<StorageFile> &> ListDirectoryResponse; + + typedef Common::BaseCallback<FileArrayResponse> *FileArrayCallback; + typedef Common::BaseCallback<StorageInfoResponse> *StorageInfoCallback; + typedef Common::BaseCallback<BoolResponse> *BoolCallback; + typedef Common::BaseCallback<UploadResponse> *UploadCallback; + typedef Common::BaseCallback<ListDirectoryResponse> *ListDirectoryCallback; + +protected: + /** Keeps track of running requests. */ + uint32 _runningRequestsCount; + Common::Mutex _runningRequestsMutex; + + /** SavesSyncRequest-related */ + SavesSyncRequest *_savesSyncRequest; + bool _syncRestartRequestsed; + + /** FolderDownloadRequest-related */ + FolderDownloadRequest *_downloadFolderRequest; + + /** Returns default error callback (printErrorResponse). */ + virtual Networking::ErrorCallback getErrorPrintingCallback(); + + /** Prints ErrorResponse contents with debug(). */ + virtual void printErrorResponse(Networking::ErrorResponse error); + + /** + * Adds request to the ConnMan, but also increases _runningRequestsCount. + * This method should be used by Storage implementations instead of + * direct ConnMan.addRequest() call. + * + * @return the same Request pointer, just as a shortcut + */ + virtual Networking::Request *addRequest(Networking::Request *request); + + /** + * Decreases _runningRequestCount. It's called from ConnMan automatically. + * Passed pointer is dangling, but one can use the address to determine + * some special Requests (which addresses were remembered somewhere). + */ + virtual void requestFinishedCallback(Networking::Request *invalidRequestPointer); + +public: + Storage(); + virtual ~Storage(); + + /** + * Storage methods, which are used by CloudManager to save + * storage in configuration file. + */ + + /** + * Save storage data using ConfMan. + * @param keyPrefix all saved keys must start with this prefix. + * @note every Storage must write keyPrefix + "type" key + * with common value (e.g. "Dropbox"). + */ + virtual void saveConfig(Common::String keyPrefix) = 0; + + /** + * Return unique storage name. + * @returns some unique storage name (for example, "Dropbox (user@example.com)") + */ + virtual Common::String name() const = 0; + + /** + * Public Cloud API comes down there. + * + * All Cloud API methods return Networking::Request *, which + * might be used to control request. All methods also accept + * a callback, which is called, when request is complete. + */ + + /** Returns ListDirectoryResponse with list of files. */ + virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false) = 0; + + /** Returns StorageFile with info about uploaded file. */ + virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) = 0; + virtual Networking::Request *upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns whether Storage supports upload(ReadStream). */ + virtual bool uploadStreamSupported(); + + /** Returns pointer to Networking::NetworkReadStream. */ + virtual Networking::Request *streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback); + virtual Networking::Request *streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) = 0; + + /** Calls the callback when finished. */ + virtual Networking::Request *download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback); + virtual Networking::Request *downloadById(Common::String remoteId, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Returns Common::Array<StorageFile> with list of files, which were not downloaded. */ + virtual Networking::Request *downloadFolder(Common::String remotePath, Common::String localPath, FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false); + + /** Calls the callback when finished. */ + virtual SavesSyncRequest *syncSaves(BoolCallback callback, Networking::ErrorCallback errorCallback); + + /** Calls the callback when finished. */ + virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) = 0; + + /** + * Returns the StorageInfo struct via <callback>. + * Calls the <errorCallback> if failed to get information. + * + * @note on success Storage should also call + * CloudMan.setStorageUsername(). + */ + virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) = 0; + + /** Returns storage's saves directory path with the trailing slash. */ + virtual Common::String savesDirectoryPath() = 0; + + /** Returns whether there are any requests running. */ + virtual bool isWorking(); + + ///// SavesSyncRequest-related ///// + + /** Returns whether there is a SavesSyncRequest running. */ + virtual bool isSyncing(); + + /** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */ + virtual double getSyncDownloadingProgress(); + + /** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */ + virtual double getSyncProgress(); + + /** Returns an array of saves names which are not yet synced (thus cannot be used). */ + virtual Common::Array<Common::String> getSyncingFiles(); + + /** Cancels running sync. */ + virtual void cancelSync(); + + /** Sets SavesSyncRequest's target to given CommandReceiver. */ + virtual void setSyncTarget(GUI::CommandReceiver *target); + +protected: + /** Finishes the sync. Shows an OSD message. */ + virtual void savesSyncDefaultCallback(BoolResponse response); + + /** Finishes the sync. Shows an OSD message. */ + virtual void savesSyncDefaultErrorCallback(Networking::ErrorResponse error); + +public: + ///// DownloadFolderRequest-related ///// + + /** Starts a folder download. */ + virtual bool startDownload(Common::String remotePath, Common::String localPath); + + /** Cancels running download. */ + virtual void cancelDownload(); + + /** Sets FolderDownloadRequest's target to given CommandReceiver. */ + virtual void setDownloadTarget(GUI::CommandReceiver *target); + + /** Returns whether there is a FolderDownloadRequest running. */ + virtual bool isDownloading(); + + /** Returns a number in [0, 1] range which represents current download progress (1 = complete). */ + virtual double getDownloadingProgress(); + + /** Returns a number of bytes that is downloaded in current download progress. */ + virtual uint64 getDownloadBytesNumber(); + + /** Returns a total number of bytes to be downloaded in current download progress. */ + virtual uint64 getDownloadTotalBytesNumber(); + + /** Returns download speed of current download progress. */ + virtual uint64 getDownloadSpeed(); + + /** Returns remote directory path. */ + virtual Common::String getDownloadRemoteDirectory(); + + /** Returns local directory path. */ + virtual Common::String getDownloadLocalDirectory(); + +protected: + /** Finishes the download. Shows an OSD message. */ + virtual void directoryDownloadedCallback(FileArrayResponse response); + + /** Finishes the download. Shows an OSD message. */ + virtual void directoryDownloadedErrorCallback(Networking::ErrorResponse error); +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/storagefile.cpp b/backends/cloud/storagefile.cpp new file mode 100644 index 0000000000..62d492292d --- /dev/null +++ b/backends/cloud/storagefile.cpp @@ -0,0 +1,68 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 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 "backends/cloud/storagefile.h" + +namespace Cloud { + +StorageFile::StorageFile() { + _id = ""; + _path = ""; + _name = ""; + _size = 0; + _timestamp = 0; + _isDirectory = false; +} + +StorageFile::StorageFile(Common::String pth, uint32 sz, uint32 ts, bool dir) { + _id = pth; + _path = pth; + + _name = pth; + if (_name.size() != 0) { + uint32 i = _name.size() - 1; + while (true) { + if (_name[i] == '/' || _name[i] == '\\') { + _name.erase(0, i + 1); + break; + } + if (i == 0) + break; + --i; + } + } + + _size = sz; + _timestamp = ts; + _isDirectory = dir; +} + +StorageFile::StorageFile(Common::String id, Common::String path, Common::String name, uint32 sz, uint32 ts, bool dir) { + _id = id; + _path = path; + _name = name; + _size = sz; + _timestamp = ts; + _isDirectory = dir; +} + +} // End of namespace Cloud diff --git a/backends/cloud/storagefile.h b/backends/cloud/storagefile.h new file mode 100644 index 0000000000..1183524f48 --- /dev/null +++ b/backends/cloud/storagefile.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_STORAGEFILE_H +#define BACKENDS_CLOUD_STORAGEFILE_H + +#include "common/str.h" + +namespace Cloud { + +/** + * StorageFile represents a file storaged on remote cloud storage. + * It contains basic information about a file, and might be used + * when listing directories or syncing files. + * + * Some storages (Google Drive, for example) don't have an actual + * path notation to address files. Instead, they are using ids. + * As resolving id by path is not a fast operation, it's required + * to use ids if they are known, but user-friendly paths are + * necessary too, because these are used by Requests. + * + * If storage supports path notation, id would actually contain path. + */ +class StorageFile { + Common::String _id, _path, _name; + uint32 _size, _timestamp; + bool _isDirectory; + +public: + StorageFile(); //invalid empty file + StorageFile(Common::String pth, uint32 sz, uint32 ts, bool dir); + StorageFile(Common::String id, Common::String path, Common::String name, uint32 sz, uint32 ts, bool dir); + + Common::String id() const { return _id; } + Common::String path() const { return _path; } + Common::String name() const { return _name; } + uint32 size() const { return _size; } + uint32 timestamp() const { return _timestamp; } + bool isDirectory() const { return _isDirectory; } + + void setPath(Common::String path_) { _path = path_; } +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/cloud/storageinfo.h b/backends/cloud/storageinfo.h new file mode 100644 index 0000000000..f1fb540974 --- /dev/null +++ b/backends/cloud/storageinfo.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_CLOUD_STORAGEINFO_H +#define BACKENDS_CLOUD_STORAGEINFO_H + +#include "common/str.h" + +namespace Cloud { + +/** +* StorageInfo contains information about remote cloud storage. +* It's disk quota usage, owner name, and such. +*/ + +class StorageInfo { + Common::String _uid, _name, _email; + uint64 _usedBytes, _allocatedBytes; + +public: + StorageInfo(Common::String uid_, Common::String name_, Common::String email_, uint64 used_, uint64 allocated): + _uid(uid_), _name(name_), _email(email_), _usedBytes(used_), _allocatedBytes(allocated) {} + + Common::String uid() const { return _uid; } + Common::String name() const { return _name; } + Common::String email() const { return _email; } + uint64 used() const { return _usedBytes; } + uint64 available() const { return _allocatedBytes; } + +}; + +} // End of namespace Cloud + +#endif diff --git a/backends/events/androidsdl/androidsdl-events.cpp b/backends/events/androidsdl/androidsdl-events.cpp index bd8045ec62..7ea8ff1dc1 100644 --- a/backends/events/androidsdl/androidsdl-events.cpp +++ b/backends/events/androidsdl/androidsdl-events.cpp @@ -43,17 +43,16 @@ bool AndroidSdlEventSource::handleMouseButtonDown(SDL_Event &ev, Common::Event & else if (ev.button.button == SDL_BUTTON_MIDDLE) { event.type = Common::EVENT_MBUTTONDOWN; - static int show_onscreen=0; - if (show_onscreen==0) { - SDL_ANDROID_SetScreenKeyboardShown(0); - show_onscreen++; + static int show_onscreen = 0; + if (show_onscreen == 0) { + SDL_ANDROID_SetScreenKeyboardShown(0); + show_onscreen++; + } else if (show_onscreen==1) { + SDL_ANDROID_SetScreenKeyboardShown(1); + show_onscreen++; } - else if (show_onscreen==1) { - SDL_ANDROID_SetScreenKeyboardShown(1); - show_onscreen++; - } - if (show_onscreen==2) - show_onscreen=0; + if (show_onscreen == 2) + show_onscreen = 0; } #endif else @@ -68,7 +67,9 @@ bool AndroidSdlEventSource::remapKey(SDL_Event &ev, Common::Event &event) { if (false) {} if (ev.key.keysym.sym == SDLK_LCTRL) { - ev.key.keysym.sym = SDLK_F5; + event.type = Common::EVENT_KEYDOWN; + event.kbd.keycode = Common::KEYCODE_F5; + return true; } else { // Let the events fall through if we didn't change them, this may not be the best way to // set it up, but i'm not sure how sdl would like it if we let if fall through then redid it though. diff --git a/backends/fs/abstract-fs.h b/backends/fs/abstract-fs.h index dcfdc08975..24286b649d 100644 --- a/backends/fs/abstract-fs.h +++ b/backends/fs/abstract-fs.h @@ -191,6 +191,15 @@ public: * @return pointer to the stream object, 0 in case of a failure */ virtual Common::WriteStream *createWriteStream() = 0; + + /** + * Creates a file referred by this node. + * + * @param isDirectory true if created file must be a directory + * + * @return true if file is created successfully + */ + virtual bool create(bool isDirectory) = 0; }; diff --git a/backends/fs/amigaos4/amigaos4-fs.cpp b/backends/fs/amigaos4/amigaos4-fs.cpp index 6d5b099736..24a8fb98ad 100644 --- a/backends/fs/amigaos4/amigaos4-fs.cpp +++ b/backends/fs/amigaos4/amigaos4-fs.cpp @@ -443,4 +443,9 @@ Common::WriteStream *AmigaOSFilesystemNode::createWriteStream() { return StdioStream::makeFromPath(getPath(), true); } +bool AmigaOSFilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + #endif //defined(__amigaos4__) diff --git a/backends/fs/amigaos4/amigaos4-fs.h b/backends/fs/amigaos4/amigaos4-fs.h index 408354888e..3ed45d3f77 100644 --- a/backends/fs/amigaos4/amigaos4-fs.h +++ b/backends/fs/amigaos4/amigaos4-fs.h @@ -116,6 +116,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); }; diff --git a/backends/fs/chroot/chroot-fs.cpp b/backends/fs/chroot/chroot-fs.cpp index 2cbb4af9d6..be6805bbd8 100644 --- a/backends/fs/chroot/chroot-fs.cpp +++ b/backends/fs/chroot/chroot-fs.cpp @@ -108,6 +108,11 @@ Common::WriteStream *ChRootFilesystemNode::createWriteStream() { return _realNode->createWriteStream(); } +bool ChRootFilesystemNode::create(bool /*isDir*/) { + error("Not supported"); + return false; +} + Common::String ChRootFilesystemNode::addPathComponent(const Common::String &path, const Common::String &component) { const char sep = '/'; if (path.lastChar() == sep && component.firstChar() == sep) { diff --git a/backends/fs/chroot/chroot-fs.h b/backends/fs/chroot/chroot-fs.h index 9ff913be31..e0ecc1c47e 100644 --- a/backends/fs/chroot/chroot-fs.h +++ b/backends/fs/chroot/chroot-fs.h @@ -49,6 +49,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); private: static Common::String addPathComponent(const Common::String &path, const Common::String &component); diff --git a/backends/fs/ds/ds-fs.cpp b/backends/fs/ds/ds-fs.cpp index 3782caf432..83b1295838 100644 --- a/backends/fs/ds/ds-fs.cpp +++ b/backends/fs/ds/ds-fs.cpp @@ -211,6 +211,11 @@ Common::WriteStream *DSFileSystemNode::createWriteStream() { return Common::wrapBufferedWriteStream(stream, WRITE_BUFFER_SIZE); } +bool DSFileSystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + ////////////////////////////////////////////////////////////////////////// // GBAMPFileSystemNode - File system using GBA Movie Player and CF card // ////////////////////////////////////////////////////////////////////////// @@ -393,6 +398,11 @@ Common::WriteStream *GBAMPFileSystemNode::createWriteStream() { return Common::wrapBufferedWriteStream(stream, WRITE_BUFFER_SIZE); } +bool GBAMPFileSystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + DSFileStream::DSFileStream(void *handle) : _handle(handle) { diff --git a/backends/fs/ds/ds-fs.h b/backends/fs/ds/ds-fs.h index b9ccfcbe9c..939d1a1555 100644 --- a/backends/fs/ds/ds-fs.h +++ b/backends/fs/ds/ds-fs.h @@ -91,6 +91,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); /** * Returns the zip file this node points to. @@ -156,6 +157,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); }; struct fileHandle { diff --git a/backends/fs/n64/n64-fs.cpp b/backends/fs/n64/n64-fs.cpp index fe37dad467..d568500d30 100644 --- a/backends/fs/n64/n64-fs.cpp +++ b/backends/fs/n64/n64-fs.cpp @@ -160,4 +160,9 @@ Common::WriteStream *N64FilesystemNode::createWriteStream() { return RomfsStream::makeFromPath(getPath(), true); } +bool N64FilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + #endif //#ifdef __N64__ diff --git a/backends/fs/n64/n64-fs.h b/backends/fs/n64/n64-fs.h index d503a6601e..d520ad5be6 100644 --- a/backends/fs/n64/n64-fs.h +++ b/backends/fs/n64/n64-fs.h @@ -73,6 +73,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); }; #endif diff --git a/backends/fs/posix/posix-fs.cpp b/backends/fs/posix/posix-fs.cpp index 1a6c4e40a5..746ae6a5a0 100644 --- a/backends/fs/posix/posix-fs.cpp +++ b/backends/fs/posix/posix-fs.cpp @@ -39,6 +39,7 @@ #include <dirent.h> #include <stdio.h> #include <errno.h> +#include <fcntl.h> #ifdef __OS2__ #define INCL_DOS @@ -252,6 +253,35 @@ Common::WriteStream *POSIXFilesystemNode::createWriteStream() { return StdioStream::makeFromPath(getPath(), true); } +bool POSIXFilesystemNode::create(bool isDir) { + bool success; + + if (isDir) { + success = mkdir(_path.c_str(), 0755) == 0; + } else { + int fd = open(_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0755); + success = fd >= 0; + + if (fd >= 0) { + close(fd); + } + } + + if (success) { + setFlags(); + if (_isValid) { + if (_isDirectory != isDir) warning("failed to create %s: got %s", isDir ? "directory" : "file", _isDirectory ? "directory" : "file"); + return _isDirectory == isDir; + } + + warning("POSIXFilesystemNode: %s() was a success, but stat indicates there is no such %s", + isDir ? "mkdir" : "creat", isDir ? "directory" : "file"); + return false; + } + + return false; +} + namespace Posix { bool assureDirectoryExists(const Common::String &dir, const char *prefix) { diff --git a/backends/fs/posix/posix-fs.h b/backends/fs/posix/posix-fs.h index 0703ac5bf5..4ebce7e9d9 100644 --- a/backends/fs/posix/posix-fs.h +++ b/backends/fs/posix/posix-fs.h @@ -73,6 +73,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); private: /** diff --git a/backends/fs/ps2/ps2-fs.cpp b/backends/fs/ps2/ps2-fs.cpp index 9b6e1270f1..8391e063a0 100644 --- a/backends/fs/ps2/ps2-fs.cpp +++ b/backends/fs/ps2/ps2-fs.cpp @@ -441,4 +441,9 @@ Common::WriteStream *Ps2FilesystemNode::createWriteStream() { return PS2FileStream::makeFromPath(getPath(), true); } +bool Ps2FilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + #endif diff --git a/backends/fs/ps2/ps2-fs.h b/backends/fs/ps2/ps2-fs.h index 63b866ba5b..c9da434535 100644 --- a/backends/fs/ps2/ps2-fs.h +++ b/backends/fs/ps2/ps2-fs.h @@ -96,6 +96,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); int getDev() { return 0; } }; diff --git a/backends/fs/psp/psp-fs.cpp b/backends/fs/psp/psp-fs.cpp index e8aad9fa98..6bd5e93435 100644 --- a/backends/fs/psp/psp-fs.cpp +++ b/backends/fs/psp/psp-fs.cpp @@ -239,4 +239,9 @@ Common::WriteStream *PSPFilesystemNode::createWriteStream() { return Common::wrapBufferedWriteStream(stream, WRITE_BUFFER_SIZE); } +bool PSPFilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + #endif //#ifdef __PSP__ diff --git a/backends/fs/psp/psp-fs.h b/backends/fs/psp/psp-fs.h index 1bb4543d19..648544650b 100644 --- a/backends/fs/psp/psp-fs.h +++ b/backends/fs/psp/psp-fs.h @@ -65,6 +65,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); }; #endif diff --git a/backends/fs/symbian/symbian-fs.cpp b/backends/fs/symbian/symbian-fs.cpp index 8fbc3a402a..e4163caa33 100644 --- a/backends/fs/symbian/symbian-fs.cpp +++ b/backends/fs/symbian/symbian-fs.cpp @@ -231,4 +231,10 @@ Common::SeekableReadStream *SymbianFilesystemNode::createReadStream() { Common::WriteStream *SymbianFilesystemNode::createWriteStream() { return SymbianStdioStream::makeFromPath(getPath(), true); } + +bool SymbianFilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + #endif //#if defined(__SYMBIAN32__) diff --git a/backends/fs/symbian/symbian-fs.h b/backends/fs/symbian/symbian-fs.h index 339e998a28..1327f9f2e9 100644 --- a/backends/fs/symbian/symbian-fs.h +++ b/backends/fs/symbian/symbian-fs.h @@ -66,6 +66,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); }; #endif diff --git a/backends/fs/wii/wii-fs.cpp b/backends/fs/wii/wii-fs.cpp index 43f4f592b7..46041bf499 100644 --- a/backends/fs/wii/wii-fs.cpp +++ b/backends/fs/wii/wii-fs.cpp @@ -213,4 +213,9 @@ Common::WriteStream *WiiFilesystemNode::createWriteStream() { return StdioStream::makeFromPath(getPath(), true); } +bool WiiFilesystemNode::create(bool isDirectory) { + error("Not supported"); + return false; +} + #endif //#if defined(__WII__) diff --git a/backends/fs/wii/wii-fs.h b/backends/fs/wii/wii-fs.h index c77c543dae..affb765884 100644 --- a/backends/fs/wii/wii-fs.h +++ b/backends/fs/wii/wii-fs.h @@ -69,6 +69,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); }; #endif diff --git a/backends/fs/windows/windows-fs.cpp b/backends/fs/windows/windows-fs.cpp index 49549b83cb..b43686f911 100644 --- a/backends/fs/windows/windows-fs.cpp +++ b/backends/fs/windows/windows-fs.cpp @@ -244,4 +244,36 @@ Common::WriteStream *WindowsFilesystemNode::createWriteStream() { return StdioStream::makeFromPath(getPath(), true); } +bool WindowsFilesystemNode::create(bool isDirectory) { + bool success; + + if (isDirectory) { + success = CreateDirectory(toUnicode(_path.c_str()), NULL) != 0; + } else { + success = CreateFile(toUnicode(_path.c_str()), GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) != INVALID_HANDLE_VALUE; + } + + if (success) { + //this piece is copied from constructor, it checks that file exists and detects whether it's a directory + DWORD fileAttribs = GetFileAttributes(toUnicode(_path.c_str())); + if (fileAttribs != INVALID_FILE_ATTRIBUTES) { + _isDirectory = ((fileAttribs & FILE_ATTRIBUTE_DIRECTORY) != 0); + _isValid = true; + // Add a trailing slash, if necessary. + if (_isDirectory && _path.lastChar() != '\\') { + _path += '\\'; + } + + if (_isDirectory != isDirectory) warning("failed to create %s: got %s", isDirectory ? "directory" : "file", _isDirectory ? "directory" : "file"); + return _isDirectory == isDirectory; + } + + warning("WindowsFilesystemNode: Create%s() was a success, but GetFileAttributes() indicates there is no such %s", + isDirectory ? "Directory" : "File", isDirectory ? "directory" : "file"); + return false; + } + + return false; +} + #endif //#ifdef WIN32 diff --git a/backends/fs/windows/windows-fs.h b/backends/fs/windows/windows-fs.h index d06044603a..7c9f2c1e1a 100644 --- a/backends/fs/windows/windows-fs.h +++ b/backends/fs/windows/windows-fs.h @@ -88,6 +88,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream(); + virtual bool create(bool isDirectory); private: /** diff --git a/backends/graphics/graphics.h b/backends/graphics/graphics.h index 3671b9f0b9..921dfca61c 100644 --- a/backends/graphics/graphics.h +++ b/backends/graphics/graphics.h @@ -84,6 +84,10 @@ public: virtual void setCursorPalette(const byte *colors, uint start, uint num) = 0; virtual void displayMessageOnOSD(const char *msg) {} + virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) {} + virtual void clearOSD() {} + virtual Graphics::PixelFormat getOSDFormat() { return Graphics::PixelFormat(); } + // Graphics::PaletteManager interface //virtual void setPalette(const byte *colors, uint start, uint num) = 0; diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp index 4d6a00a3b3..c491b03f1f 100644 --- a/backends/graphics/opengl/opengl-graphics.cpp +++ b/backends/graphics/opengl/opengl-graphics.cpp @@ -751,6 +751,31 @@ void OpenGLGraphicsManager::displayMessageOnOSD(const char *msg) { #endif } +void OpenGLGraphicsManager::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) { +#ifdef USE_OSD + warning("implement copyRectToOSD"); //TODO +#endif +} + +void OpenGLGraphicsManager::clearOSD() { +#ifdef USE_OSD + // HACK: Actually no client code should use graphics functions from + // another thread. But the MT-32 emulator still does, thus we need to + // make sure this doesn't happen while a updateScreen call is done. + Common::StackLock lock(_osdMutex); + + Graphics::Surface *dst = _osd->getSurface(); + _osd->fill(0); + _osd->flagDirty(); + + // Init the OSD display parameters. + _osdAlpha = kOSDInitialAlpha; + _osdFadeStartTime = g_system->getMillis() + kOSDFadeOutDelay; +#endif +} + +Graphics::PixelFormat OpenGLGraphicsManager::getOSDFormat() { return Graphics::PixelFormat(); } //TODO + void OpenGLGraphicsManager::setPalette(const byte *colors, uint start, uint num) { assert(_gameScreen->hasPalette()); diff --git a/backends/graphics/opengl/opengl-graphics.h b/backends/graphics/opengl/opengl-graphics.h index 35435c156e..55d2c5c826 100644 --- a/backends/graphics/opengl/opengl-graphics.h +++ b/backends/graphics/opengl/opengl-graphics.h @@ -115,6 +115,9 @@ public: virtual void setCursorPalette(const byte *colors, uint start, uint num); virtual void displayMessageOnOSD(const char *msg); + virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h); + virtual void clearOSD(); + virtual Graphics::PixelFormat getOSDFormat(); // PaletteManager interface virtual void setPalette(const byte *colors, uint start, uint num); diff --git a/backends/graphics/opengl/opengl-sys.h b/backends/graphics/opengl/opengl-sys.h index 4495128f32..7b531cc140 100644 --- a/backends/graphics/opengl/opengl-sys.h +++ b/backends/graphics/opengl/opengl-sys.h @@ -48,9 +48,15 @@ // 0 - Force OpenGL context // 1 - Force OpenGL ES context // 2 - Force OpenGL ES 2.0 context -#define USE_FORCED_GL (defined(USE_GLES_MODE) && USE_GLES_MODE == 0) -#define USE_FORCED_GLES (defined(USE_GLES_MODE) && USE_GLES_MODE == 1) -#define USE_FORCED_GLES2 (defined(USE_GLES_MODE) && USE_GLES_MODE == 2) +#ifdef USE_GLES_MODE + #define USE_FORCED_GL (USE_GLES_MODE == 0) + #define USE_FORCED_GLES (USE_GLES_MODE == 1) + #define USE_FORCED_GLES2 (USE_GLES_MODE == 2) +#else + #define USE_FORCED_GL 0 + #define USE_FORCED_GLES 0 + #define USE_FORCED_GLES2 0 +#endif // On Tizen we include the toolchain's OpenGL file. This is something we // actually want to avoid. However, since Tizen uses eglGetProcAddress which diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp index 5b591e77ff..fdf21010e7 100644 --- a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp +++ b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp @@ -121,7 +121,7 @@ SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSou : SdlGraphicsManager(sdlEventSource, window), #ifdef USE_OSD - _osdSurface(0), _osdAlpha(SDL_ALPHA_TRANSPARENT), _osdFadeStartTime(0), + _osdSurface(0), _osdMessageSurface(nullptr), _osdMessageAlpha(SDL_ALPHA_TRANSPARENT), _osdMessageFadeStartTime(0), #endif _hwscreen(0), #if SDL_VERSION_ATLEAST(2, 0, 0) @@ -883,13 +883,16 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() { _osdSurface = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, _hwscreen->w, _hwscreen->h, - 16, - _hwscreen->format->Rmask, - _hwscreen->format->Gmask, - _hwscreen->format->Bmask, - _hwscreen->format->Amask); + 32, + 0xFF000000, + 0x00FF0000, + 0x0000FF00, + 0x000000FF + ); if (_osdSurface == NULL) error("allocating _osdSurface failed"); + + _osdFormat = getSurfaceFormat(_osdSurface); SDL_SetColorKey(_osdSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, kOSDColorKey); #endif @@ -946,6 +949,11 @@ void SurfaceSdlGraphicsManager::unloadGFXMode() { SDL_FreeSurface(_osdSurface); _osdSurface = NULL; } + + if (_osdMessageSurface) { + SDL_FreeSurface(_osdMessageSurface); + _osdMessageSurface = NULL; + } #endif DestroyScalers(); @@ -1059,21 +1067,33 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() { #ifdef USE_OSD // OSD visible (i.e. non-transparent)? - if (_osdAlpha != SDL_ALPHA_TRANSPARENT) { + if (_osdMessageAlpha != SDL_ALPHA_TRANSPARENT) { // Updated alpha value - const int diff = SDL_GetTicks() - _osdFadeStartTime; + const int diff = SDL_GetTicks() - _osdMessageFadeStartTime; if (diff > 0) { if (diff >= kOSDFadeOutDuration) { // Back to full transparency - _osdAlpha = SDL_ALPHA_TRANSPARENT; + _osdMessageAlpha = SDL_ALPHA_TRANSPARENT; } else { // Do a linear fade out... const int startAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100; - _osdAlpha = startAlpha + diff * (SDL_ALPHA_TRANSPARENT - startAlpha) / kOSDFadeOutDuration; + _osdMessageAlpha = startAlpha + diff * (SDL_ALPHA_TRANSPARENT - startAlpha) / kOSDFadeOutDuration; } - SDL_SetAlpha(_osdSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, _osdAlpha); _forceFull = true; } + + if (_osdMessageAlpha == SDL_ALPHA_TRANSPARENT) { + removeOSDMessage(); + } else { + if (_osdMessageSurface && _osdSurface) { + SDL_Rect dstRect; + dstRect.x = (_osdSurface->w - _osdMessageSurface->w) / 2; + dstRect.y = (_osdSurface->h - _osdMessageSurface->h) / 2; + dstRect.w = _osdMessageSurface->w; + dstRect.h = _osdMessageSurface->h; + blitOSDMessage(dstRect); + } + } } #endif @@ -1179,9 +1199,7 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() { drawMouse(); #ifdef USE_OSD - if (_osdAlpha != SDL_ALPHA_TRANSPARENT) { - SDL_BlitSurface(_osdSurface, 0, _hwscreen, 0); - } + SDL_BlitSurface(_osdSurface, 0, _hwscreen, 0); #endif #ifdef USE_SDL_DEBUG_FOCUSRECT @@ -2121,26 +2139,11 @@ void SurfaceSdlGraphicsManager::displayMessageOnOSD(const char *msg) { Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends - uint i; - - // Lock the OSD surface for drawing - if (SDL_LockSurface(_osdSurface)) - error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError()); - - Graphics::Surface dst; - dst.init(_osdSurface->w, _osdSurface->h, _osdSurface->pitch, _osdSurface->pixels, - Graphics::PixelFormat(_osdSurface->format->BytesPerPixel, - 8 - _osdSurface->format->Rloss, 8 - _osdSurface->format->Gloss, - 8 - _osdSurface->format->Bloss, 8 - _osdSurface->format->Aloss, - _osdSurface->format->Rshift, _osdSurface->format->Gshift, - _osdSurface->format->Bshift, _osdSurface->format->Ashift)); + removeOSDMessage(); // The font we are going to use: const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont); - // Clear everything with the "transparent" color, i.e. the colorkey - SDL_FillRect(_osdSurface, 0, kOSDColorKey); - // Split the message into separate lines. Common::Array<Common::String> lines; const char *ptr; @@ -2159,44 +2162,184 @@ void SurfaceSdlGraphicsManager::displayMessageOnOSD(const char *msg) { const int lineHeight = font->getFontHeight() + 2 * lineSpacing; int width = 0; int height = lineHeight * lines.size() + 2 * vOffset; + uint i; for (i = 0; i < lines.size(); i++) { width = MAX(width, font->getStringWidth(lines[i]) + 14); } // Clip the rect - if (width > dst.w) - width = dst.w; - if (height > dst.h) - height = dst.h; + if (width > _osdSurface->w) + width = _osdSurface->w; + if (height > _osdSurface->h) + height = _osdSurface->h; + + _osdMessageSurface = SDL_CreateRGBSurface( + SDL_SWSURFACE | SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, + width, height, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF + ); + + // Lock the surface + if (SDL_LockSurface(_osdMessageSurface)) + error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError()); // Draw a dark gray rect - // TODO: Rounded corners ? Border? - SDL_Rect osdRect; - osdRect.x = (dst.w - width) / 2; - osdRect.y = (dst.h - height) / 2; - osdRect.w = width; - osdRect.h = height; - SDL_FillRect(_osdSurface, &osdRect, SDL_MapRGB(_osdSurface->format, 64, 64, 64)); + // TODO: Rounded corners ? Border? + SDL_FillRect(_osdMessageSurface, nullptr, SDL_MapRGB(_osdMessageSurface->format, 64, 64, 64)); + + Graphics::Surface dst; + dst.init(_osdMessageSurface->w, _osdMessageSurface->h, _osdMessageSurface->pitch, _osdMessageSurface->pixels, + Graphics::PixelFormat(_osdMessageSurface->format->BytesPerPixel, + 8 - _osdMessageSurface->format->Rloss, 8 - _osdMessageSurface->format->Gloss, + 8 - _osdMessageSurface->format->Bloss, 8 - _osdMessageSurface->format->Aloss, + _osdMessageSurface->format->Rshift, _osdMessageSurface->format->Gshift, + _osdMessageSurface->format->Bshift, _osdMessageSurface->format->Ashift)); // Render the message, centered, and in white for (i = 0; i < lines.size(); i++) { font->drawString(&dst, lines[i], - osdRect.x, osdRect.y + i * lineHeight + vOffset + lineSpacing, osdRect.w, - SDL_MapRGB(_osdSurface->format, 255, 255, 255), - Graphics::kTextAlignCenter); + 0, 0 + i * lineHeight + vOffset + lineSpacing, width, + SDL_MapRGB(_osdMessageSurface->format, 255, 255, 255), + Graphics::kTextAlignCenter); + } + + // Finished drawing, so unlock the OSD message surface + SDL_UnlockSurface(_osdMessageSurface); + + // Init the OSD display parameters, and the fade out + _osdMessageAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100; + _osdMessageFadeStartTime = SDL_GetTicks() + kOSDFadeOutDelay; + + // Ensure a full redraw takes place next time the screen is updated + _forceFull = true; +} + +void SurfaceSdlGraphicsManager::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) { + assert(_transactionMode == kTransactionNone); + assert(buf); + + Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends + + // Lock the OSD surface for drawing + if (SDL_LockSurface(_osdSurface)) + error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError()); + + // Mark that area as "dirty" + addDirtyRect(x, y, w, h, true); + +#ifdef USE_RGB_COLOR + byte *dst = (byte *)_osdSurface->pixels + y * _osdSurface->pitch + x * _osdSurface->format->BytesPerPixel; + if (_videoMode.screenWidth == w && pitch == _osdSurface->pitch) { + memcpy(dst, buf, h*pitch); + } else { + const byte *src = (const byte *)buf; + do { + memcpy(dst, src, w * _osdSurface->format->BytesPerPixel); + src += pitch; + dst += _osdSurface->pitch; + } while (--h); } +#else + byte *dst = (byte *)_osdSurface->pixels + y * _osdSurface->pitch + x; + if (_osdSurface->pitch == pitch && pitch == w) { + memcpy(dst, buf, h*w); + } else { + const byte *src = (const byte *)buf; + do { + memcpy(dst, src, w); + src += pitch; + dst += _osdSurface->pitch; + } while (--h); + } +#endif // Finished drawing, so unlock the OSD surface again SDL_UnlockSurface(_osdSurface); +} - // Init the OSD display parameters, and the fade out - _osdAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100; - _osdFadeStartTime = SDL_GetTicks() + kOSDFadeOutDelay; - SDL_SetAlpha(_osdSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, _osdAlpha); +void SurfaceSdlGraphicsManager::clearOSD() { + assert(_transactionMode == kTransactionNone); + + Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends + + // Lock the OSD surface for drawing + if (SDL_LockSurface(_osdSurface)) + error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError()); + + // Clear everything with the "transparent" color, i.e. the colorkey + SDL_FillRect(_osdSurface, 0, kOSDColorKey); + + // Finished drawing, so unlock the OSD surface again + SDL_UnlockSurface(_osdSurface); + + // Remove OSD message as well + removeOSDMessage(); // Ensure a full redraw takes place next time the screen is updated _forceFull = true; } + +void SurfaceSdlGraphicsManager::removeOSDMessage() { + // Lock the OSD surface for drawing + if (SDL_LockSurface(_osdSurface)) + error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError()); + + //remove previous message + if (_osdMessageSurface) { + SDL_Rect osdRect; + osdRect.x = (_osdSurface->w - _osdMessageSurface->w) / 2; + osdRect.y = (_osdSurface->h - _osdMessageSurface->h) / 2; + osdRect.w = _osdMessageSurface->w; + osdRect.h = _osdMessageSurface->h; + SDL_FillRect(_osdSurface, &osdRect, kOSDColorKey); + SDL_FreeSurface(_osdMessageSurface); + } + + _osdMessageSurface = NULL; + _osdMessageAlpha = SDL_ALPHA_TRANSPARENT; + + // Finished drawing, so unlock the OSD surface again + SDL_UnlockSurface(_osdSurface); +} + +void SurfaceSdlGraphicsManager::blitOSDMessage(SDL_Rect dstRect) { + SDL_Surface *src = _osdMessageSurface; + SDL_Surface *dst = _osdSurface; + Graphics::PixelFormat srcFormat = getSurfaceFormat(src); + Graphics::PixelFormat dstFormat = _osdFormat; + for (int y = 0; y < dstRect.h; y++) { + const byte *srcRow = (const byte *)((const byte *)(src->pixels) + y * src->pitch); //src (x, y) == (0, 0) + byte *dstRow = (byte *)((byte *)(dst->pixels) + (dstRect.y + y) * dst->pitch + dstRect.x * dstFormat.bytesPerPixel); + + for (int x = 0; x < dstRect.w; x++) { + uint32 srcColor; + if (dst->format->BytesPerPixel == 2) + srcColor = READ_UINT16(srcRow); + else if (dst->format->BytesPerPixel == 3) + srcColor = READ_UINT24(srcRow); + else + srcColor = READ_UINT32(srcRow); + + srcRow += srcFormat.bytesPerPixel; + + // Convert that color to the new format + byte r, g, b, a; + srcFormat.colorToARGB(srcColor, a, r, g, b); + a = _osdMessageAlpha; //this is the important line, because apart from that this is plain surface copying + uint32 color = dstFormat.ARGBToColor(a, r, g, b); + + if (dstFormat.bytesPerPixel == 2) + *((uint16 *)dstRow) = color; + else + *((uint32 *)dstRow) = color; + + dstRow += dstFormat.bytesPerPixel; + } + } +} + +Graphics::PixelFormat SurfaceSdlGraphicsManager::getOSDFormat() { + return _osdFormat; +} #endif bool SurfaceSdlGraphicsManager::handleScalerHotkeys(Common::KeyCode key) { @@ -2492,4 +2635,22 @@ void SurfaceSdlGraphicsManager::SDL_UpdateRects(SDL_Surface *screen, int numrect } #endif // SDL_VERSION_ATLEAST(2, 0, 0) +Graphics::PixelFormat SurfaceSdlGraphicsManager::getSurfaceFormat(SDL_Surface *surface) { + Graphics::PixelFormat format; + if (surface) { + format.bytesPerPixel = surface->format->BytesPerPixel; + + format.rLoss = surface->format->Rloss; + format.gLoss = surface->format->Gloss; + format.bLoss = surface->format->Bloss; + format.aLoss = surface->format->Aloss; + + format.rShift = surface->format->Rshift; + format.gShift = surface->format->Gshift; + format.bShift = surface->format->Bshift; + format.aShift = surface->format->Ashift; + } + return format; +} + #endif diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.h b/backends/graphics/surfacesdl/surfacesdl-graphics.h index 25d6ff041c..d8f826aca0 100644 --- a/backends/graphics/surfacesdl/surfacesdl-graphics.h +++ b/backends/graphics/surfacesdl/surfacesdl-graphics.h @@ -145,6 +145,9 @@ public: #ifdef USE_OSD virtual void displayMessageOnOSD(const char *msg); + virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h); + virtual void clearOSD(); + virtual Graphics::PixelFormat getOSDFormat(); #endif // Override from Common::EventObserver @@ -160,12 +163,14 @@ public: protected: #ifdef USE_OSD - /** Surface containing the OSD message */ + /** Surface containing the OSD */ SDL_Surface *_osdSurface; - /** Transparency level of the OSD */ - uint8 _osdAlpha; + /** Surface containing the OSD message */ + SDL_Surface *_osdMessageSurface; + /** Transparency level of the OSD message */ + uint8 _osdMessageAlpha; /** When to start the fade out */ - uint32 _osdFadeStartTime; + uint32 _osdMessageFadeStartTime; /** Enum with OSD options */ enum { kOSDFadeOutDelay = 2 * 1000, /** < Delay before the OSD is faded out (in milliseconds) */ @@ -173,6 +178,11 @@ protected: kOSDColorKey = 1, /** < Transparent color key */ kOSDInitialAlpha = 80 /** < Initial alpha level, in percent */ }; + /** OSD pixel format */ + Graphics::PixelFormat _osdFormat; + + void removeOSDMessage(); + void blitOSDMessage(SDL_Rect dstRect); #endif /** Hardware screen */ @@ -358,6 +368,8 @@ protected: Common::Rect _focusRect; #endif + static Graphics::PixelFormat getSurfaceFormat(SDL_Surface *surface); + virtual void addDirtyRect(int x, int y, int w, int h, bool realCoordinates = false); virtual void drawMouse(); diff --git a/backends/modular-backend.cpp b/backends/modular-backend.cpp index d8be9ca7ed..e1bdf15571 100644 --- a/backends/modular-backend.cpp +++ b/backends/modular-backend.cpp @@ -241,6 +241,18 @@ void ModularBackend::displayMessageOnOSD(const char *msg) { _graphicsManager->displayMessageOnOSD(msg); } +void ModularBackend::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) { + _graphicsManager->copyRectToOSD(buf, pitch, x, y, w, h); +} + +void ModularBackend::clearOSD() { + _graphicsManager->clearOSD(); +} + +Graphics::PixelFormat ModularBackend::getOSDFormat() { + return _graphicsManager->getOSDFormat(); +} + void ModularBackend::quit() { exit(0); } diff --git a/backends/modular-backend.h b/backends/modular-backend.h index 20e8b7357d..9cde27915f 100644 --- a/backends/modular-backend.h +++ b/backends/modular-backend.h @@ -127,6 +127,9 @@ public: virtual void quit(); virtual void displayMessageOnOSD(const char *msg); + virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h); + virtual void clearOSD(); + virtual Graphics::PixelFormat getOSDFormat(); //@} diff --git a/backends/module.mk b/backends/module.mk index 4c1ca42f06..c402a10a90 100644 --- a/backends/module.mk +++ b/backends/module.mk @@ -19,6 +19,65 @@ MODULE_OBJS := \ saves/default/default-saves.o \ timer/default/default-timer.o +ifdef USE_LIBCURL +MODULE_OBJS += \ + cloud/cloudmanager.o \ + cloud/iso8601.o \ + cloud/storage.o \ + cloud/storagefile.o \ + cloud/downloadrequest.o \ + cloud/folderdownloadrequest.o \ + cloud/savessyncrequest.o \ + cloud/box/boxstorage.o \ + cloud/box/boxlistdirectorybyidrequest.o \ + cloud/box/boxtokenrefresher.o \ + cloud/box/boxuploadrequest.o \ + cloud/dropbox/dropboxstorage.o \ + cloud/dropbox/dropboxcreatedirectoryrequest.o \ + cloud/dropbox/dropboxinforequest.o \ + cloud/dropbox/dropboxlistdirectoryrequest.o \ + cloud/dropbox/dropboxuploadrequest.o \ + cloud/googledrive/googledrivelistdirectorybyidrequest.o \ + cloud/googledrive/googledrivestorage.o \ + cloud/googledrive/googledrivetokenrefresher.o \ + cloud/googledrive/googledriveuploadrequest.o \ + cloud/id/idstorage.o \ + cloud/id/idcreatedirectoryrequest.o \ + cloud/id/iddownloadrequest.o \ + cloud/id/idlistdirectoryrequest.o \ + cloud/id/idresolveidrequest.o \ + cloud/id/idstreamfilerequest.o \ + cloud/onedrive/onedrivestorage.o \ + cloud/onedrive/onedrivecreatedirectoryrequest.o \ + cloud/onedrive/onedrivetokenrefresher.o \ + cloud/onedrive/onedrivelistdirectoryrequest.o \ + cloud/onedrive/onedriveuploadrequest.o \ + networking/curl/connectionmanager.o \ + networking/curl/networkreadstream.o \ + networking/curl/cloudicon.o \ + networking/curl/curlrequest.o \ + networking/curl/curljsonrequest.o \ + networking/curl/request.o +endif + +ifdef USE_SDL_NET +MODULE_OBJS += \ + networking/sdl_net/client.o \ + networking/sdl_net/getclienthandler.o \ + networking/sdl_net/handlers/createdirectoryhandler.o \ + networking/sdl_net/handlers/downloadfilehandler.o \ + networking/sdl_net/handlers/filesajaxpagehandler.o \ + networking/sdl_net/handlers/filesbasehandler.o \ + networking/sdl_net/handlers/filespagehandler.o \ + networking/sdl_net/handlers/indexpagehandler.o \ + networking/sdl_net/handlers/listajaxhandler.o \ + networking/sdl_net/handlers/resourcehandler.o \ + networking/sdl_net/handlers/uploadfilehandler.o \ + networking/sdl_net/handlerutils.o \ + networking/sdl_net/localwebserver.o \ + networking/sdl_net/reader.o \ + networking/sdl_net/uploadfileclienthandler.o +endif ifdef USE_ELF_LOADER MODULE_OBJS += \ @@ -91,6 +150,42 @@ MODULE_OBJS += \ endif endif +# openUrl +ifeq ($(BACKEND),android) +MODULE_OBJS += \ + networking/browser/openurl-android.o +else +ifdef MACOSX +MODULE_OBJS += \ + networking/browser/openurl-osx.o +else +ifdef WIN32 +MODULE_OBJS += \ + networking/browser/openurl-windows.o +else + ifdef POSIX + MODULE_OBJS += \ + networking/browser/openurl-posix.o + else + # create_project doesn't know something about `else` + ifndef WIN32 + MODULE_OBJS += \ + networking/browser/openurl-default.o + endif + endif +endif +endif +endif + +# Connection::isLimited +ifeq ($(BACKEND),android) +MODULE_OBJS += \ + networking/connection/islimited-android.o +else +MODULE_OBJS += \ + networking/connection/islimited-default.o +endif + ifdef POSIX MODULE_OBJS += \ fs/posix/posix-fs.o \ diff --git a/engines/titanic/game/placeholder/place_holder_item.cpp b/backends/networking/browser/openurl-android.cpp index 365e8cbe50..64e683238b 100644 --- a/engines/titanic/game/placeholder/place_holder_item.cpp +++ b/backends/networking/browser/openurl-android.cpp @@ -20,18 +20,16 @@ * */ -#include "titanic/game/placeholder/place_holder_item.h" +#include "backends/networking/browser/openurl.h" +#include "backends/platform/android/jni.h" -namespace Titanic { +namespace Networking { +namespace Browser { -void CPlaceHolderItem::save(SimpleFile *file, int indent) { - file->writeNumberLine(1, indent); - CGameObject::save(file, indent); +bool openUrl(const Common::String &url) { + return JNI::openUrl(url.c_str()); } -void CPlaceHolderItem::load(SimpleFile *file) { - file->readNumber(); - CGameObject::load(file); -} +} // End of namespace Browser +} // End of namespace Networking -} // End of namespace Titanic diff --git a/engines/titanic/gfx/chev_switch.cpp b/backends/networking/browser/openurl-default.cpp index a6ce93098c..c430953196 100644 --- a/engines/titanic/gfx/chev_switch.cpp +++ b/backends/networking/browser/openurl-default.cpp @@ -20,21 +20,17 @@ * */ -#include "titanic/gfx/chev_switch.h" +#include "backends/networking/browser/openurl.h" +#include "common/textconsole.h" -namespace Titanic { +namespace Networking { +namespace Browser { -CChevSwitch::CChevSwitch() : CToggleSwitch() { +bool openUrl(const Common::String &url) { + warning("Networking::Browser::openUrl(): not implemented"); + return false; } -void CChevSwitch::save(SimpleFile *file, int indent) { - file->writeNumberLine(1, indent); - CToggleSwitch::save(file, indent); -} - -void CChevSwitch::load(SimpleFile *file) { - file->readNumber(); - CToggleSwitch::load(file); -} +} // End of namespace Browser +} // End of namespace Networking -} // End of namespace Titanic diff --git a/backends/networking/browser/openurl-osx.cpp b/backends/networking/browser/openurl-osx.cpp new file mode 100644 index 0000000000..8d786d7fd2 --- /dev/null +++ b/backends/networking/browser/openurl-osx.cpp @@ -0,0 +1,49 @@ +/* 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. + * + */ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/browser/openurl.h" +#include <CoreFoundation/CFBundle.h> +#include <ApplicationServices/ApplicationServices.h> + +namespace Networking { +namespace Browser { + +using namespace std; + +bool openUrl(const Common::String &url) { + CFURLRef urlRef = CFURLCreateWithBytes ( + NULL, + (UInt8*)url.c_str(), + url.size(), + kCFStringEncodingASCII, + NULL + ); + int result = LSOpenCFURLRef(urlRef, 0); + CFRelease(urlRef); + return result == 0; +} + +} // End of namespace Browser +} // End of namespace Networking + diff --git a/backends/networking/browser/openurl-posix.cpp b/backends/networking/browser/openurl-posix.cpp new file mode 100644 index 0000000000..429a379fcf --- /dev/null +++ b/backends/networking/browser/openurl-posix.cpp @@ -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 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. + * + */ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/browser/openurl.h" +#include "common/textconsole.h" +#include <stdlib.h> + +namespace Networking { +namespace Browser { + +namespace { +bool launch(const Common::String client, const Common::String &url) { + // FIXME: system's input must be heavily escaped + // well, when url's specified by user + // it's OK now (urls are hardcoded somewhere in GUI) + Common::String cmd = client + " " + url; + return (system(cmd.c_str()) != -1); +} +} + +bool openUrl(const Common::String &url) { + // inspired by Qt's "qdesktopservices_x11.cpp" + + // try "standards" + if (launch("xdg-open", url)) + return true; + if (launch(getenv("DEFAULT_BROWSER"), url)) + return true; + if (launch(getenv("BROWSER"), url)) + return true; + + // try desktop environment specific tools + if (launch("gnome-open", url)) // gnome + return true; + if (launch("kfmclient openURL", url)) // kde + return true; + if (launch("exo-open", url)) // xfce + return true; + + // try browser names + if (launch("firefox", url)) + return true; + if (launch("mozilla", url)) + return true; + if (launch("netscape", url)) + return true; + if (launch("opera", url)) + return true; + + warning("Networking::Browser::openUrl() (POSIX) failed to open URL"); + return false; +} + +} // End of namespace Browser +} // End of namespace Networking + diff --git a/backends/networking/browser/openurl-windows.cpp b/backends/networking/browser/openurl-windows.cpp new file mode 100644 index 0000000000..b78c9fa7ed --- /dev/null +++ b/backends/networking/browser/openurl-windows.cpp @@ -0,0 +1,42 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 "backends/networking/browser/openurl.h" +#include "common/textconsole.h" +#include <windows.h> +#include <shellapi.h> + +namespace Networking { +namespace Browser { + +bool openUrl(const Common::String &url) { + const uint64 result = (uint64)ShellExecute(0, 0, /*(wchar_t*)nativeFilePath.utf16()*/url.c_str(), 0, 0, SW_SHOWNORMAL); + // ShellExecute returns a value greater than 32 if successful + if (result <= 32) { + warning("ShellExecute failed: error = %u", result); + return false; + } + return true; +} + +} // End of namespace Browser +} // End of namespace Networking diff --git a/backends/networking/browser/openurl.h b/backends/networking/browser/openurl.h new file mode 100644 index 0000000000..15b4bf385b --- /dev/null +++ b/backends/networking/browser/openurl.h @@ -0,0 +1,41 @@ +/* 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. + * + */ + +#ifndef NETWORKING_BROWSER_OPENURL_H +#define NETWORKING_BROWSER_OPENURL_H + +#include "common/str.h" + +namespace Networking { +namespace Browser { + +/** + * Opens URL in default browser (if available on the target system). + * + * Returns true on success. + */ +bool openUrl(const Common::String &url); + +} // End of namespace Browser +} // End of namespace Networking + +#endif /*NETWORKING_BROWSER_OPENURL_H*/ diff --git a/backends/networking/connection/islimited-android.cpp b/backends/networking/connection/islimited-android.cpp new file mode 100644 index 0000000000..8989f218ec --- /dev/null +++ b/backends/networking/connection/islimited-android.cpp @@ -0,0 +1,35 @@ +/* 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 "backends/networking/connection/islimited.h" +#include "backends/platform/android/jni.h" + +namespace Networking { +namespace Connection { + +bool isLimited() { + return JNI::isConnectionLimited(); +} + +} // End of namespace Connection +} // End of namespace Networking + diff --git a/engines/titanic/sound/music_handler.cpp b/backends/networking/connection/islimited-default.cpp index 41545347b8..a993077fff 100644 --- a/engines/titanic/sound/music_handler.cpp +++ b/backends/networking/connection/islimited-default.cpp @@ -20,26 +20,17 @@ * */ -#include "titanic/sound/music_handler.h" -#include "titanic/sound/sound_manager.h" -#include "titanic/core/project_item.h" +#include "backends/networking/connection/islimited.h" +#include "common/textconsole.h" -namespace Titanic { +namespace Networking { +namespace Connection { -CMusicHandler::CMusicHandler(CProjectItem *project, CSoundManager *soundManager) : - _project(project), _soundManager(soundManager), - _field124(0) { - -} - -CMusicWave *CMusicHandler::createMusicWave(int v1, int v2) { - // TODO - return nullptr; -} - -bool CMusicHandler::isBusy() { - // TODO +bool isLimited() { + warning("Networking::Connection::isLimited(): not limited by default"); return false; } -} // End of namespace Titanic +} // End of namespace Connection +} // End of namespace Networking + diff --git a/backends/networking/connection/islimited.h b/backends/networking/connection/islimited.h new file mode 100644 index 0000000000..b23d31d157 --- /dev/null +++ b/backends/networking/connection/islimited.h @@ -0,0 +1,39 @@ +/* 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. + * + */ + +#ifndef NETWORKING_CONNECTION_ISLIMITED_H +#define NETWORKING_CONNECTION_ISLIMITED_H + +namespace Networking { +namespace Connection { + +/** +* Returns whether connection's limited (if available on the target system). +* +* Returns true if connection seems limited. +*/ +bool isLimited(); + +} // End of namespace Connection +} // End of namespace Networking + +#endif /*NETWORKING_CONNECTION_ISLIMITED_H*/ diff --git a/backends/networking/curl/cloudicon.cpp b/backends/networking/curl/cloudicon.cpp new file mode 100644 index 0000000000..1c1ecf2f85 --- /dev/null +++ b/backends/networking/curl/cloudicon.cpp @@ -0,0 +1,171 @@ +/* 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 "backends/networking/curl/cloudicon.h" +#include "backends/cloud/cloudmanager.h" +#include "common/memstream.h" +#include "gui/ThemeEngine.h" +#include "gui/gui-manager.h" +#include "image/png.h" + +namespace Networking { + +const float CloudIcon::ALPHA_STEP = 0.025; +const float CloudIcon::ALPHA_MAX = 1; +const float CloudIcon::ALPHA_MIN = 0.6; + +CloudIcon::CloudIcon(): + _wasVisible(false), _iconsInited(false), _showingDisabled(false), + _currentAlpha(0), _alphaRising(true), _disabledFrames(0) { + initIcons(); +} + +CloudIcon::~CloudIcon() {} + +bool CloudIcon::draw() { + bool stop = false; + initIcons(); + + if (CloudMan.isWorking() || _disabledFrames > 0) { + if (g_system) { + if (!_wasVisible) { + g_system->clearOSD(); + _wasVisible = true; + } + --_disabledFrames; + if (_alphaRising) { + if (_currentAlpha < ALPHA_MIN) + _currentAlpha += 5 * ALPHA_STEP; + else + _currentAlpha += ALPHA_STEP; + if (_currentAlpha > ALPHA_MAX) { + _currentAlpha = ALPHA_MAX; + _alphaRising = false; + } + } else { + _currentAlpha -= ALPHA_STEP; + if (_currentAlpha < ALPHA_MIN) { + _currentAlpha = ALPHA_MIN; + _alphaRising = true; + } + } + } else { + _wasVisible = false; + } + } else { + _wasVisible = false; + _currentAlpha -= 5 * ALPHA_STEP; + if (_currentAlpha <= 0) { + _currentAlpha = 0; + stop = true; + } + } + + if (g_system) { + Graphics::TransparentSurface *surface = &_icon; + makeAlphaIcon((_showingDisabled ? _disabledIcon : _icon), _currentAlpha); + if (_alphaIcon.getPixels()) + surface = &_alphaIcon; + if (surface && surface->getPixels()) { + int x = g_system->getOverlayWidth() - surface->w - 10, y = 10; + g_system->copyRectToOSD(surface->getPixels(), surface->pitch, x, y, surface->w, surface->h); + } + } + + if (stop) + _showingDisabled = false; + return stop; +} + +void CloudIcon::showDisabled() { + _showingDisabled = true; + _disabledFrames = 20 * 3; //3 seconds 20 fps +} + +#include "backends/networking/curl/cloudicon_data.h" +#include "backends/networking/curl/cloudicon_disabled_data.h" + +void CloudIcon::initIcons() { + if (_iconsInited) + return; + loadIcon(_icon, cloudicon_data, ARRAYSIZE(cloudicon_data)); + loadIcon(_disabledIcon, cloudicon_disabled_data, ARRAYSIZE(cloudicon_disabled_data)); + _iconsInited = true; +} + +void CloudIcon::loadIcon(Graphics::TransparentSurface &icon, byte *data, uint32 size) { + Image::PNGDecoder decoder; + Common::MemoryReadStream stream(data, size); + if (!decoder.loadStream(stream)) + error("CloudIcon::loadIcon: error decoding PNG"); + + Graphics::TransparentSurface *s = new Graphics::TransparentSurface(*decoder.getSurface(), true); + if (s) { + Graphics::PixelFormat f = g_system->getOSDFormat(); + if (f != s->format) { + Graphics::TransparentSurface *s2 = s->convertTo(f); + if (s2) + icon.copyFrom(*s2); + else + warning("CloudIcon::loadIcon: failed converting TransparentSurface"); + delete s2; + } else { + icon.copyFrom(*s); + } + delete s; + } else { + warning("CloudIcon::loadIcon: failed reading TransparentSurface from PNGDecoder"); + } +} + +void CloudIcon::makeAlphaIcon(Graphics::TransparentSurface &icon, float alpha) { + _alphaIcon.copyFrom(icon); + + byte *pixels = (byte *)_alphaIcon.getPixels(); + for (int y = 0; y < _alphaIcon.h; y++) { + byte *row = pixels + y * _alphaIcon.pitch; + for (int x = 0; x < _alphaIcon.w; x++) { + uint32 srcColor; + if (_alphaIcon.format.bytesPerPixel == 2) + srcColor = READ_UINT16(row); + else if (_alphaIcon.format.bytesPerPixel == 3) + srcColor = READ_UINT24(row); + else + srcColor = READ_UINT32(row); + + // Update color's alpha + byte r, g, b, a; + _alphaIcon.format.colorToARGB(srcColor, a, r, g, b); + a = (byte)(a * alpha); + uint32 color = _alphaIcon.format.ARGBToColor(a, r, g, b); + + if (_alphaIcon.format.bytesPerPixel == 2) + *((uint16 *)row) = color; + else + *((uint32 *)row) = color; + + row += _alphaIcon.format.bytesPerPixel; + } + } +} + +} // End of namespace Networking diff --git a/backends/networking/curl/cloudicon.h b/backends/networking/curl/cloudicon.h new file mode 100644 index 0000000000..7e4d7387a3 --- /dev/null +++ b/backends/networking/curl/cloudicon.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_CURL_CLOUDICON_H +#define BACKENDS_NETWORKING_CURL_CLOUDICON_H + +#include "graphics/transparent_surface.h" + +namespace Networking { + +class CloudIcon { + static const float ALPHA_STEP, ALPHA_MAX, ALPHA_MIN; + + bool _wasVisible, _iconsInited, _showingDisabled; + Graphics::TransparentSurface _icon, _disabledIcon, _alphaIcon; + float _currentAlpha; + bool _alphaRising; + int _disabledFrames; + + void initIcons(); + void loadIcon(Graphics::TransparentSurface &icon, byte *data, uint32 size); + void makeAlphaIcon(Graphics::TransparentSurface &icon, float alpha); + +public: + CloudIcon(); + ~CloudIcon(); + + /** + * This method is called from ConnectionManager every time + * its own timer calls the handle() method. The primary + * responsibility of this draw() method is to draw cloud icon + * on ScummVM's OSD when current cloud Storage is working. + * + * As we don't want ConnectionManager to work when no + * Requests are running, we'd like to stop the timer. But then + * this icon wouldn't have time to disappear smoothly. So, + * in order to do that, ConnectionManager stop its timer + * only when this draw() method returns true, indicating that + * the CloudIcon has disappeared and the timer could be stopped. + * + * @return true if ConnMan's timer could be stopped. + */ + bool draw(); + + /** Draw a "cloud disabled" icon instead of "cloud syncing" one. */ + void showDisabled(); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/curl/cloudicon_data.h b/backends/networking/curl/cloudicon_data.h new file mode 100644 index 0000000000..21d88182a3 --- /dev/null +++ b/backends/networking/curl/cloudicon_data.h @@ -0,0 +1,111 @@ +/* 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. + * + */ + +// This is a PNG file dumped into array. +// $ recode data..d1 <dists/cloudicon.png >cloudicon_data.h +// The tool is from https://github.com/pinard/Recode + +byte cloudicon_data[] = { + 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, + 82, 0, 0, 0, 32, 0, 0, 0, 32, 8, 6, 0, 0, 0, 115, + 122, 122, 244, 0, 0, 0, 4, 115, 66, 73, 84, 8, 8, 8, 8, + 124, 8, 100, 136, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 11, + 18, 0, 0, 11, 18, 1, 210, 221, 126, 252, 0, 0, 0, 22, 116, + 69, 88, 116, 67, 114, 101, 97, 116, 105, 111, 110, 32, 84, 105, 109, + 101, 0, 48, 54, 47, 48, 51, 47, 49, 54, 159, 192, 233, 192, 0, + 0, 0, 28, 116, 69, 88, 116, 83, 111, 102, 116, 119, 97, 114, 101, + 0, 65, 100, 111, 98, 101, 32, 70, 105, 114, 101, 119, 111, 114, 107, + 115, 32, 67, 83, 54, 232, 188, 178, 140, 0, 0, 4, 50, 73, 68, + 65, 84, 88, 133, 197, 151, 109, 104, 150, 101, 20, 199, 127, 247, 227, + 179, 105, 51, 23, 65, 181, 150, 224, 154, 214, 132, 194, 249, 33, 165, + 22, 189, 231, 194, 210, 250, 16, 171, 180, 55, 42, 152, 68, 65, 100, + 52, 233, 5, 146, 144, 144, 26, 249, 169, 62, 164, 80, 89, 152, 25, + 18, 226, 42, 49, 87, 88, 180, 94, 96, 96, 246, 234, 180, 70, 50, + 66, 214, 55, 247, 22, 133, 247, 255, 244, 225, 58, 247, 158, 107, 143, + 219, 243, 60, 186, 192, 3, 135, 251, 220, 215, 117, 223, 231, 127, 238, + 235, 252, 239, 235, 58, 39, 105, 250, 206, 56, 147, 146, 55, 85, 252, + 108, 13, 112, 59, 176, 12, 88, 2, 204, 7, 230, 248, 220, 48, 208, + 15, 244, 2, 221, 64, 23, 48, 86, 137, 211, 228, 146, 158, 178, 43, + 80, 7, 172, 3, 218, 129, 179, 43, 241, 9, 140, 0, 155, 129, 87, + 128, 193, 146, 15, 47, 248, 178, 100, 0, 237, 64, 39, 112, 14, 96, + 174, 20, 217, 153, 228, 162, 0, 50, 25, 2, 58, 128, 45, 83, 1, + 228, 53, 121, 10, 170, 129, 183, 128, 213, 126, 47, 215, 49, 224, 39, + 224, 19, 160, 7, 56, 2, 204, 0, 154, 128, 27, 128, 229, 110, 215, + 120, 64, 181, 132, 149, 184, 30, 120, 4, 248, 183, 24, 40, 185, 248, + 243, 147, 86, 160, 154, 144, 195, 91, 252, 43, 5, 140, 2, 31, 2, + 27, 129, 195, 83, 125, 141, 75, 19, 240, 2, 129, 47, 179, 61, 144, + 4, 216, 7, 172, 44, 14, 34, 105, 232, 62, 41, 128, 109, 192, 189, + 14, 126, 2, 24, 0, 30, 3, 246, 150, 1, 46, 150, 54, 15, 184, + 1, 200, 251, 216, 123, 192, 253, 19, 2, 152, 183, 119, 66, 0, 237, + 192, 27, 110, 159, 0, 250, 128, 187, 128, 67, 167, 8, 158, 201, 98, + 224, 3, 160, 209, 131, 72, 128, 53, 68, 156, 200, 153, 192, 181, 206, + 68, 167, 219, 50, 49, 96, 226, 78, 19, 135, 162, 103, 138, 117, 169, + 137, 46, 19, 163, 38, 82, 19, 63, 152, 120, 220, 196, 12, 159, 63, + 104, 98, 149, 137, 99, 238, 211, 28, 163, 46, 243, 145, 147, 192, 117, + 157, 68, 173, 219, 195, 18, 143, 74, 28, 137, 230, 139, 181, 77, 162, + 71, 98, 165, 68, 141, 68, 78, 98, 145, 196, 107, 18, 59, 252, 30, + 137, 3, 18, 207, 72, 140, 249, 125, 173, 99, 33, 65, 114, 209, 110, + 131, 192, 218, 65, 2, 105, 4, 108, 245, 165, 74, 125, 165, 206, 5, + 214, 2, 151, 3, 7, 128, 119, 128, 95, 253, 189, 169, 228, 105, 224, + 85, 183, 171, 128, 29, 192, 29, 4, 82, 142, 18, 246, 151, 177, 164, + 126, 151, 1, 220, 3, 188, 79, 32, 222, 40, 97, 167, 235, 243, 151, + 207, 2, 190, 5, 154, 35, 231, 223, 0, 45, 101, 242, 127, 12, 152, + 75, 97, 191, 88, 12, 124, 237, 254, 18, 96, 21, 176, 35, 227, 192, + 50, 207, 15, 38, 126, 49, 113, 56, 202, 243, 221, 38, 154, 139, 114, + 223, 82, 130, 23, 153, 214, 155, 88, 20, 221, 255, 104, 226, 104, 116, + 223, 106, 42, 144, 112, 169, 137, 196, 131, 248, 56, 10, 166, 222, 196, + 141, 21, 128, 77, 165, 223, 155, 120, 42, 34, 246, 158, 200, 247, 18, + 19, 228, 21, 178, 60, 223, 151, 41, 1, 190, 112, 251, 60, 224, 171, + 104, 238, 116, 36, 33, 240, 224, 32, 240, 25, 176, 31, 120, 194, 199, + 27, 161, 112, 26, 102, 167, 154, 1, 127, 186, 253, 98, 9, 240, 81, + 2, 97, 43, 149, 231, 61, 128, 129, 104, 108, 78, 28, 64, 44, 25, + 105, 218, 74, 56, 156, 13, 252, 76, 248, 43, 42, 145, 140, 176, 249, + 226, 137, 188, 133, 20, 12, 123, 68, 9, 225, 171, 127, 7, 46, 40, + 227, 180, 82, 112, 128, 153, 126, 189, 148, 194, 105, 57, 12, 5, 18, + 246, 71, 68, 185, 214, 237, 191, 166, 65, 190, 98, 237, 243, 235, 205, + 209, 88, 127, 188, 19, 246, 250, 53, 39, 177, 194, 237, 157, 37, 118, + 193, 83, 213, 183, 37, 102, 73, 92, 39, 145, 196, 152, 57, 75, 193, + 82, 246, 249, 21, 75, 89, 104, 41, 205, 150, 178, 222, 82, 250, 163, + 241, 211, 213, 253, 150, 178, 201, 82, 174, 180, 148, 185, 150, 146, 248, + 120, 183, 165, 133, 20, 116, 153, 24, 113, 123, 150, 137, 103, 77, 28, + 55, 113, 141, 137, 173, 38, 134, 60, 61, 167, 178, 236, 3, 38, 54, + 152, 184, 213, 255, 253, 39, 221, 55, 142, 213, 101, 42, 252, 5, 99, + 132, 202, 101, 45, 97, 175, 94, 14, 172, 0, 118, 1, 15, 185, 78, + 71, 86, 19, 138, 217, 196, 117, 179, 99, 146, 204, 126, 125, 188, 30, + 168, 35, 236, 255, 181, 132, 3, 233, 15, 224, 54, 202, 87, 64, 229, + 164, 5, 216, 9, 92, 232, 224, 67, 192, 66, 188, 88, 205, 69, 185, + 26, 180, 148, 14, 207, 81, 206, 82, 230, 89, 202, 110, 75, 185, 108, + 26, 249, 191, 202, 82, 222, 181, 148, 243, 163, 220, 119, 56, 22, 150, + 78, 172, 7, 144, 216, 34, 177, 205, 237, 188, 196, 2, 137, 61, 18, + 15, 158, 6, 243, 31, 144, 216, 37, 209, 224, 190, 18, 137, 237, 142, + 49, 254, 92, 50, 115, 211, 164, 69, 233, 71, 64, 43, 140, 23, 165, + 127, 19, 26, 142, 231, 8, 117, 192, 84, 82, 69, 56, 118, 215, 3, + 55, 17, 54, 160, 172, 40, 253, 148, 80, 168, 78, 44, 74, 171, 59, + 39, 237, 11, 170, 129, 55, 129, 251, 40, 108, 205, 2, 254, 1, 126, + 243, 96, 122, 129, 163, 62, 223, 0, 92, 237, 160, 141, 17, 112, 38, + 219, 129, 135, 139, 193, 1, 146, 170, 151, 43, 106, 76, 106, 139, 198, + 229, 192, 73, 20, 96, 12, 152, 177, 253, 56, 101, 26, 147, 36, 191, + 177, 226, 214, 108, 13, 133, 214, 172, 220, 75, 35, 14, 90, 190, 53, + 203, 189, 84, 113, 119, 156, 53, 167, 173, 192, 21, 252, 95, 205, 105, + 178, 225, 204, 182, 231, 255, 1, 200, 91, 112, 221, 160, 249, 68, 42, + 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130 +}; diff --git a/backends/networking/curl/cloudicon_disabled_data.h b/backends/networking/curl/cloudicon_disabled_data.h new file mode 100644 index 0000000000..4340a8a37c --- /dev/null +++ b/backends/networking/curl/cloudicon_disabled_data.h @@ -0,0 +1,117 @@ +/* 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. + * + */ + +// This is a PNG file dumped into array. +// $ recode data..d1 <dists/cloudicon_disabled.png >cloudicon_disabled_data.h +// The tool is from https://github.com/pinard/Recode + +byte cloudicon_disabled_data[] = { + 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, + 82, 0, 0, 0, 32, 0, 0, 0, 32, 8, 6, 0, 0, 0, 115, + 122, 122, 244, 0, 0, 0, 4, 115, 66, 73, 84, 8, 8, 8, 8, + 124, 8, 100, 136, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 11, + 18, 0, 0, 11, 18, 1, 210, 221, 126, 252, 0, 0, 0, 22, 116, + 69, 88, 116, 67, 114, 101, 97, 116, 105, 111, 110, 32, 84, 105, 109, + 101, 0, 48, 54, 47, 48, 51, 47, 49, 54, 159, 192, 233, 192, 0, + 0, 0, 28, 116, 69, 88, 116, 83, 111, 102, 116, 119, 97, 114, 101, + 0, 65, 100, 111, 98, 101, 32, 70, 105, 114, 101, 119, 111, 114, 107, + 115, 32, 67, 83, 54, 232, 188, 178, 140, 0, 0, 4, 139, 73, 68, + 65, 84, 88, 133, 197, 215, 91, 168, 86, 69, 20, 7, 240, 223, 254, + 244, 120, 236, 120, 57, 26, 94, 10, 31, 34, 35, 11, 36, 203, 74, + 212, 160, 204, 212, 110, 166, 248, 208, 205, 172, 151, 144, 136, 158, 82, + 80, 168, 32, 233, 165, 160, 32, 130, 236, 165, 34, 204, 74, 18, 52, + 36, 169, 232, 34, 24, 221, 243, 218, 133, 74, 77, 195, 44, 200, 91, + 122, 188, 156, 172, 102, 159, 221, 195, 204, 246, 219, 126, 231, 226, 17, + 138, 22, 12, 123, 246, 236, 153, 245, 95, 107, 205, 127, 205, 172, 157, + 21, 254, 95, 233, 27, 122, 63, 183, 5, 179, 48, 13, 87, 98, 52, + 6, 167, 111, 71, 176, 11, 27, 177, 14, 107, 209, 222, 27, 165, 217, + 137, 211, 207, 25, 137, 197, 152, 143, 65, 189, 209, 137, 163, 120, 1, + 79, 98, 111, 143, 147, 143, 247, 172, 108, 62, 158, 194, 16, 20, 169, + 105, 232, 151, 82, 171, 24, 80, 74, 27, 22, 225, 197, 110, 13, 104, + 235, 122, 188, 31, 94, 194, 93, 21, 192, 14, 49, 172, 223, 226, 109, + 124, 130, 29, 232, 131, 49, 184, 22, 55, 166, 126, 75, 131, 65, 175, + 225, 94, 252, 213, 201, 128, 131, 93, 131, 191, 137, 27, 42, 192, 199, + 241, 6, 158, 192, 246, 238, 188, 73, 50, 6, 143, 98, 78, 50, 164, + 140, 200, 123, 34, 135, 78, 49, 34, 219, 215, 89, 193, 171, 152, 151, + 192, 3, 246, 224, 1, 188, 123, 26, 224, 170, 92, 134, 143, 49, 160, + 50, 86, 96, 5, 238, 174, 78, 108, 204, 130, 249, 98, 216, 75, 240, + 109, 184, 13, 63, 156, 1, 248, 68, 44, 21, 189, 47, 165, 67, 140, + 196, 60, 172, 87, 225, 68, 182, 167, 62, 105, 100, 2, 106, 77, 11, + 118, 139, 123, 186, 163, 59, 164, 53, 76, 16, 195, 125, 29, 250, 15, + 227, 167, 73, 12, 237, 27, 73, 91, 75, 142, 28, 21, 185, 51, 60, + 141, 181, 225, 98, 41, 59, 106, 33, 185, 26, 88, 28, 104, 77, 253, + 163, 129, 251, 3, 59, 42, 223, 79, 105, 171, 184, 53, 240, 73, 224, + 150, 64, 203, 32, 106, 99, 185, 160, 224, 236, 16, 245, 118, 4, 54, + 5, 166, 4, 22, 6, 218, 211, 218, 214, 132, 37, 32, 75, 238, 181, + 224, 55, 12, 76, 222, 191, 140, 251, 144, 39, 79, 135, 98, 1, 198, + 98, 11, 150, 227, 251, 50, 204, 67, 113, 121, 90, 156, 57, 185, 127, + 135, 154, 184, 9, 95, 160, 9, 43, 49, 59, 69, 225, 24, 206, 65, + 123, 150, 54, 247, 14, 188, 158, 214, 30, 23, 79, 186, 109, 9, 252, + 44, 124, 142, 113, 149, 232, 127, 134, 201, 196, 88, 79, 66, 115, 5, + 252, 24, 182, 114, 224, 32, 35, 230, 212, 207, 139, 75, 241, 169, 168, + 47, 195, 157, 88, 89, 110, 193, 180, 64, 145, 250, 223, 5, 182, 87, + 194, 125, 123, 96, 92, 195, 22, 76, 14, 201, 227, 177, 226, 65, 144, + 227, 111, 28, 198, 151, 113, 131, 135, 5, 46, 169, 172, 249, 38, 176, + 187, 242, 62, 35, 160, 150, 199, 197, 19, 114, 178, 156, 34, 231, 173, + 244, 180, 154, 115, 115, 166, 166, 57, 167, 180, 193, 34, 3, 7, 165, + 61, 11, 137, 105, 27, 112, 160, 62, 111, 235, 106, 22, 166, 126, 71, + 206, 59, 165, 238, 156, 43, 114, 100, 155, 98, 120, 218, 146, 206, 2, + 83, 241, 225, 26, 134, 165, 253, 27, 173, 65, 134, 138, 137, 222, 154, + 222, 139, 228, 249, 87, 233, 217, 133, 76, 159, 19, 47, 169, 89, 226, + 129, 214, 71, 188, 192, 134, 148, 231, 64, 121, 171, 21, 248, 85, 244, + 232, 177, 30, 192, 59, 90, 82, 6, 193, 9, 108, 234, 30, 28, 30, + 9, 209, 128, 74, 214, 71, 204, 190, 121, 231, 201, 5, 228, 220, 218, + 248, 97, 136, 200, 246, 102, 106, 29, 234, 73, 190, 5, 135, 186, 7, + 135, 201, 9, 167, 111, 227, 135, 50, 2, 71, 146, 69, 153, 232, 245, + 206, 192, 136, 234, 196, 86, 49, 13, 154, 162, 113, 39, 211, 101, 51, + 126, 239, 25, 28, 154, 19, 206, 133, 234, 119, 195, 17, 234, 36, 220, + 85, 33, 202, 213, 169, 191, 175, 36, 92, 139, 152, 106, 3, 212, 9, + 119, 76, 100, 251, 126, 157, 9, 218, 69, 219, 150, 158, 211, 42, 99, + 187, 114, 245, 147, 112, 83, 122, 214, 2, 51, 83, 127, 85, 16, 89, + 62, 94, 60, 61, 202, 20, 58, 36, 38, 244, 126, 93, 159, 146, 93, + 180, 101, 129, 254, 129, 107, 2, 89, 26, 219, 24, 42, 6, 188, 95, + 153, 124, 81, 202, 251, 37, 3, 249, 101, 188, 120, 114, 148, 223, 219, + 197, 212, 56, 208, 123, 240, 245, 129, 167, 3, 19, 3, 163, 42, 6, + 172, 11, 234, 36, 92, 43, 242, 105, 32, 250, 227, 161, 89, 44, 45, + 98, 196, 139, 14, 178, 146, 112, 27, 210, 179, 23, 178, 7, 203, 240, + 248, 156, 152, 251, 15, 38, 221, 146, 138, 181, 212, 73, 216, 46, 214, + 112, 11, 82, 180, 103, 138, 71, 237, 40, 145, 52, 29, 216, 221, 194, + 220, 41, 49, 0, 103, 36, 129, 185, 152, 158, 116, 101, 9, 171, 29, + 178, 85, 245, 121, 213, 235, 184, 90, 215, 229, 248, 89, 172, 112, 190, + 62, 83, 112, 209, 145, 85, 226, 229, 147, 105, 188, 142, 43, 172, 220, + 155, 179, 40, 29, 201, 85, 6, 239, 204, 153, 155, 243, 117, 47, 216, + 222, 216, 38, 229, 188, 146, 51, 188, 162, 119, 81, 194, 82, 205, 130, + 178, 189, 24, 120, 173, 97, 108, 80, 34, 102, 111, 73, 87, 182, 123, + 2, 107, 2, 231, 133, 184, 213, 89, 96, 69, 194, 56, 57, 47, 91, + 222, 57, 100, 253, 68, 130, 92, 175, 94, 148, 254, 129, 15, 240, 176, + 88, 7, 116, 39, 77, 226, 181, 187, 68, 172, 146, 154, 69, 78, 101, + 98, 77, 57, 91, 99, 81, 250, 82, 215, 138, 202, 178, 188, 44, 78, + 37, 67, 254, 196, 143, 201, 152, 141, 98, 217, 86, 224, 60, 92, 149, + 64, 207, 175, 0, 151, 178, 66, 119, 101, 249, 243, 61, 184, 163, 254, + 99, 210, 218, 48, 94, 94, 5, 101, 13, 162, 1, 176, 100, 251, 97, + 167, 249, 49, 233, 115, 179, 250, 111, 78, 23, 109, 115, 193, 178, 130, + 62, 5, 151, 20, 52, 23, 241, 76, 200, 10, 106, 149, 103, 173, 50, + 158, 21, 28, 45, 120, 174, 96, 110, 193, 71, 61, 232, 151, 61, 219, + 115, 4, 170, 82, 254, 156, 206, 16, 47, 197, 127, 231, 231, 244, 153, + 222, 27, 240, 159, 200, 63, 153, 185, 24, 191, 162, 246, 71, 153, 0, + 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130 +}; diff --git a/backends/networking/curl/connectionmanager.cpp b/backends/networking/curl/connectionmanager.cpp new file mode 100644 index 0000000000..f3dc91ad60 --- /dev/null +++ b/backends/networking/curl/connectionmanager.cpp @@ -0,0 +1,204 @@ +/* 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. +* +*/ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/debug.h" +#include "common/system.h" +#include "common/timer.h" +#include <curl/curl.h> + +namespace Common { + +DECLARE_SINGLETON(Networking::ConnectionManager); + +} + +namespace Networking { + +ConnectionManager::ConnectionManager(): _multi(0), _timerStarted(false), _frame(0) { + curl_global_init(CURL_GLOBAL_ALL); + _multi = curl_multi_init(); +} + +ConnectionManager::~ConnectionManager() { + stopTimer(); + + //terminate all requests + _handleMutex.lock(); + for (Common::Array<RequestWithCallback>::iterator i = _requests.begin(); i != _requests.end(); ++i) { + Request *request = i->request; + RequestCallback callback = i->onDeleteCallback; + if (request) + request->finish(); + delete request; + if (callback) + (*callback)(request); + } + _requests.clear(); + + //cleanup + curl_multi_cleanup(_multi); + curl_global_cleanup(); + _multi = nullptr; + _handleMutex.unlock(); +} + +void ConnectionManager::registerEasyHandle(CURL *easy) const { + curl_multi_add_handle(_multi, easy); +} + +Request *ConnectionManager::addRequest(Request *request, RequestCallback callback) { + _addedRequestsMutex.lock(); + _addedRequests.push_back(RequestWithCallback(request, callback)); + if (!_timerStarted) + startTimer(); + _addedRequestsMutex.unlock(); + return request; +} + +void ConnectionManager::showCloudDisabledIcon() { + _icon.showDisabled(); + startTimer(); +} + +Common::String ConnectionManager::urlEncode(Common::String s) const { + if (!_multi) + return ""; + char *output = curl_easy_escape(_multi, s.c_str(), s.size()); + if (output) { + Common::String result = output; + curl_free(output); + return result; + } + return ""; +} + +uint32 ConnectionManager::getCloudRequestsPeriodInMicroseconds() { + return TIMER_INTERVAL * CLOUD_PERIOD; +} + +//private goes here: + +void connectionsThread(void *ignored) { + ConnMan.handle(); +} + +void ConnectionManager::startTimer(int interval) { + Common::TimerManager *manager = g_system->getTimerManager(); + if (manager->installTimerProc(connectionsThread, interval, 0, "Networking::ConnectionManager's Timer")) { + _timerStarted = true; + } else { + warning("Failed to install Networking::ConnectionManager's timer"); + } +} + +void ConnectionManager::stopTimer() { + debug(9, "timer stopped"); + Common::TimerManager *manager = g_system->getTimerManager(); + manager->removeTimerProc(connectionsThread); + _timerStarted = false; +} + +bool ConnectionManager::hasAddedRequests() { + _addedRequestsMutex.lock(); + bool hasNewRequests = !_addedRequests.empty(); + _addedRequestsMutex.unlock(); + return hasNewRequests; +} + +void ConnectionManager::handle() { + //lock mutex here (in case another handle() would be called before this one ends) + _handleMutex.lock(); + ++_frame; + if (_frame % CLOUD_PERIOD == 0) + interateRequests(); + if (_frame % CURL_PERIOD == 0) + processTransfers(); + + if (_icon.draw() && _requests.empty() && !hasAddedRequests()) + stopTimer(); + _handleMutex.unlock(); +} + +void ConnectionManager::interateRequests() { + //add new requests + _addedRequestsMutex.lock(); + for (Common::Array<RequestWithCallback>::iterator i = _addedRequests.begin(); i != _addedRequests.end(); ++i) { + _requests.push_back(*i); + } + _addedRequests.clear(); + _addedRequestsMutex.unlock(); + + //call handle() of all running requests (so they can do their work) + debug(9, "handling %d request(s)", _requests.size()); + for (Common::Array<RequestWithCallback>::iterator i = _requests.begin(); i != _requests.end();) { + Request *request = i->request; + if (request) { + if (request->state() == PROCESSING) + request->handle(); + else if (request->state() == RETRY) + request->handleRetry(); + } + + if (!request || request->state() == FINISHED) { + delete (i->request); + if (i->onDeleteCallback) + (*i->onDeleteCallback)(i->request); //that's not a mistake (we're passing an address and that method knows there is no object anymore) + _requests.erase(i); + continue; + } + + ++i; + } +} + +void ConnectionManager::processTransfers() { + if (!_multi) return; + + //check libcurl's transfers and notify requests of messages from queue (transfer completion or failure) + int transfersRunning; + curl_multi_perform(_multi, &transfersRunning); + + int messagesInQueue; + CURLMsg *curlMsg; + while ((curlMsg = curl_multi_info_read(_multi, &messagesInQueue))) { + CURL *easyHandle = curlMsg->easy_handle; + + NetworkReadStream *stream; + curl_easy_getinfo(easyHandle, CURLINFO_PRIVATE, &stream); + if (stream) + stream->finished(); + + if (curlMsg->msg == CURLMSG_DONE) { + debug(9, "ConnectionManager: SUCCESS (%d - %s)", curlMsg->data.result, curl_easy_strerror(curlMsg->data.result)); + } else { + warning("ConnectionManager: FAILURE (CURLMsg (%d))", curlMsg->msg); + } + + curl_multi_remove_handle(_multi, easyHandle); + } +} + +} // End of namespace Cloud diff --git a/backends/networking/curl/connectionmanager.h b/backends/networking/curl/connectionmanager.h new file mode 100644 index 0000000000..826bef6d36 --- /dev/null +++ b/backends/networking/curl/connectionmanager.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_CURL_CONNECTIONMANAGER_H +#define BACKENDS_NETWORKING_CURL_CONNECTIONMANAGER_H + +#include "backends/networking/curl/cloudicon.h" +#include "backends/networking/curl/request.h" +#include "common/str.h" +#include "common/singleton.h" +#include "common/hashmap.h" +#include "common/mutex.h" + +typedef void CURL; +typedef void CURLM; +struct curl_slist; + +namespace Networking { + +class NetworkReadStream; + +class ConnectionManager : public Common::Singleton<ConnectionManager> { + static const uint32 FRAMES_PER_SECOND = 20; + static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND; + static const uint32 CLOUD_PERIOD = 20; //every 20th frame + static const uint32 CURL_PERIOD = 1; //every frame + + friend void connectionsThread(void *); //calls handle() + + typedef Common::BaseCallback<Request *> *RequestCallback; + + /** + * RequestWithCallback is used by ConnectionManager to + * storage the Request and a callback which should be + * called on Request delete. + * + * Usually one won't need to pass such callback, but + * in some cases you'd like to know whether Request is + * still running. + * + * For example, Cloud::Storage is keeping track of how + * many Requests are running, and thus it needs to know + * that Request was destroyed to decrease its counter. + * + * onDeleteCallback is called with *invalid* pointer. + * ConnectionManager deletes Request first and then passes + * the pointer to the callback. One may use the address + * to find it in own HashMap or Array and remove it. + * So, again, this pointer is for information only. One + * cannot use it. + */ + struct RequestWithCallback { + Request *request; + RequestCallback onDeleteCallback; + + RequestWithCallback(Request *rq = nullptr, RequestCallback cb = nullptr): request(rq), onDeleteCallback(cb) {} + }; + + CURLM *_multi; + bool _timerStarted; + Common::Array<RequestWithCallback> _requests, _addedRequests; + Common::Mutex _handleMutex, _addedRequestsMutex; + CloudIcon _icon; + uint32 _frame; + + void startTimer(int interval = TIMER_INTERVAL); + void stopTimer(); + void handle(); + void interateRequests(); + void processTransfers(); + bool hasAddedRequests(); + +public: + ConnectionManager(); + virtual ~ConnectionManager(); + + /** + * All libcurl transfers are going through this ConnectionManager. + * So, if you want to start any libcurl transfer, you must create + * an easy handle and register it using this method. + */ + void registerEasyHandle(CURL *easy) const; + + /** + * Use this method to add new Request into manager's queue. + * Manager will periodically call handle() method of these + * Requests until they set their state to FINISHED. + * + * If Request's state is RETRY, handleRetry() is called instead. + * + * The passed callback would be called after Request is deleted. + * + * @note This method starts the timer if it's not started yet. + * + * @return the same Request pointer, just as a shortcut + */ + Request *addRequest(Request *request, RequestCallback callback = nullptr); + + /** Shows a "cloud disabled" icon for a three seconds. */ + void showCloudDisabledIcon(); + + /** Return URL-encoded version of given string. */ + Common::String urlEncode(Common::String s) const; + + static uint32 getCloudRequestsPeriodInMicroseconds(); +}; + +/** Shortcut for accessing the connection manager. */ +#define ConnMan Networking::ConnectionManager::instance() + +} // End of namespace Networking + +#endif diff --git a/backends/networking/curl/curljsonrequest.cpp b/backends/networking/curl/curljsonrequest.cpp new file mode 100644 index 0000000000..1899cbd913 --- /dev/null +++ b/backends/networking/curl/curljsonrequest.cpp @@ -0,0 +1,215 @@ +/* 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. +* +*/ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/curl/curljsonrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/debug.h" +#include "common/json.h" +#include <curl/curl.h> + +namespace Networking { + +CurlJsonRequest::CurlJsonRequest(JsonCallback cb, ErrorCallback ecb, Common::String url) : + CurlRequest(nullptr, ecb, url), _jsonCallback(cb), _contentsStream(DisposeAfterUse::YES), + _buffer(new byte[CURL_JSON_REQUEST_BUFFER_SIZE]) {} + +CurlJsonRequest::~CurlJsonRequest() { + delete _jsonCallback; + delete[] _buffer; +} + +char *CurlJsonRequest::getPreparedContents() { + //write one more byte in the end + byte zero[1] = {0}; + _contentsStream.write(zero, 1); + + //replace all "bad" bytes with '.' character + byte *result = _contentsStream.getData(); + uint32 size = _contentsStream.size(); + for (uint32 i = 0; i < size; ++i) { + if (result[i] == '\n') + result[i] = ' '; //yeah, kinda stupid + else if (result[i] < 0x20 || result[i] > 0x7f) + result[i] = '.'; + } + + //make it zero-terminated string + result[size - 1] = '\0'; + + return (char *)result; +} + +void CurlJsonRequest::handle() { + if (!_stream) _stream = makeStream(); + + if (_stream) { + uint32 readBytes = _stream->read(_buffer, CURL_JSON_REQUEST_BUFFER_SIZE); + if (readBytes != 0) + if (_contentsStream.write(_buffer, readBytes) != readBytes) + warning("CurlJsonRequest: unable to write all the bytes into MemoryWriteStreamDynamic"); + + if (_stream->eos()) { + char *contents = getPreparedContents(); + Common::JSONValue *json = Common::JSON::parse(contents); + if (json) { + finishJson(json); //it's JSON even if's not 200 OK? That's fine!.. + } else { + if (_stream->httpResponseCode() == 200) //no JSON, but 200 OK? That's fine!.. + finishJson(nullptr); + else + finishError(ErrorResponse(this, false, true, contents, _stream->httpResponseCode())); + } + } + } +} + +void CurlJsonRequest::restart() { + if (_stream) + delete _stream; + _stream = nullptr; + _contentsStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES); + //with no stream available next handle() will create another one +} + +void CurlJsonRequest::finishJson(Common::JSONValue *json) { + Request::finishSuccess(); + if (_jsonCallback) + (*_jsonCallback)(JsonResponse(this, json)); //potential memory leak, free it in your callbacks! + else + delete json; +} + +bool CurlJsonRequest::jsonIsObject(Common::JSONValue *item, const char *warningPrefix) { + if (item == nullptr) { + warning("%s: passed item is NULL", warningPrefix); + return false; + } + + if (item->isObject()) return true; + + warning("%s: passed item is not an object", warningPrefix); + debug(9, "%s", item->stringify(true).c_str()); + return false; +} + +bool CurlJsonRequest::jsonContainsObject(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) { + if (!item.contains(key)) { + if (isOptional) { + return true; + } + + warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key); + return false; + } + + if (item.getVal(key)->isObject()) return true; + + warning("%s: passed item's \"%s\" attribute is not an object", warningPrefix, key); + debug(9, "%s", item.getVal(key)->stringify(true).c_str()); + return false; +} + +bool CurlJsonRequest::jsonContainsString(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) { + if (!item.contains(key)) { + if (isOptional) { + return true; + } + + warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key); + return false; + } + + if (item.getVal(key)->isString()) return true; + + warning("%s: passed item's \"%s\" attribute is not a string", warningPrefix, key); + debug(9, "%s", item.getVal(key)->stringify(true).c_str()); + return false; +} + +bool CurlJsonRequest::jsonContainsIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) { + if (!item.contains(key)) { + if (isOptional) { + return true; + } + + warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key); + return false; + } + + if (item.getVal(key)->isIntegerNumber()) return true; + + warning("%s: passed item's \"%s\" attribute is not an integer", warningPrefix, key); + debug(9, "%s", item.getVal(key)->stringify(true).c_str()); + return false; +} + +bool CurlJsonRequest::jsonContainsArray(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) { + if (!item.contains(key)) { + if (isOptional) { + return true; + } + + warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key); + return false; + } + + if (item.getVal(key)->isArray()) return true; + + warning("%s: passed item's \"%s\" attribute is not an array", warningPrefix, key); + debug(9, "%s", item.getVal(key)->stringify(true).c_str()); + return false; +} + +bool CurlJsonRequest::jsonContainsStringOrIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) { + if (!item.contains(key)) { + if (isOptional) { + return true; + } + + warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key); + return false; + } + + if (item.getVal(key)->isString() || item.getVal(key)->isIntegerNumber()) return true; + + warning("%s: passed item's \"%s\" attribute is neither a string or an integer", warningPrefix, key); + debug(9, "%s", item.getVal(key)->stringify(true).c_str()); + return false; +} + +bool CurlJsonRequest::jsonContainsAttribute(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) { + if (!item.contains(key)) { + if (isOptional) { + return true; + } + + warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key); + return false; + } + + return true; +} + +} // End of namespace Networking diff --git a/backends/networking/curl/curljsonrequest.h b/backends/networking/curl/curljsonrequest.h new file mode 100644 index 0000000000..edd523015a --- /dev/null +++ b/backends/networking/curl/curljsonrequest.h @@ -0,0 +1,67 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_CURL_CURLJSONREQUEST_H +#define BACKENDS_NETWORKING_CURL_CURLJSONREQUEST_H + +#include "backends/networking/curl/curlrequest.h" +#include "common/memstream.h" +#include "common/json.h" + +namespace Networking { + +typedef Response<Common::JSONValue *> JsonResponse; +typedef Common::BaseCallback<JsonResponse> *JsonCallback; + +#define CURL_JSON_REQUEST_BUFFER_SIZE 512 * 1024 + +class CurlJsonRequest: public CurlRequest { +protected: + JsonCallback _jsonCallback; + Common::MemoryWriteStreamDynamic _contentsStream; + byte *_buffer; + + /** Prepares raw bytes from _contentsStream to be parsed with Common::JSON::parse(). */ + char *getPreparedContents(); + + /** Sets FINISHED state and passes the JSONValue * into user's callback in JsonResponse. */ + virtual void finishJson(Common::JSONValue *json); + +public: + CurlJsonRequest(JsonCallback cb, ErrorCallback ecb, Common::String url); + virtual ~CurlJsonRequest(); + + virtual void handle(); + virtual void restart(); + + static bool jsonIsObject(Common::JSONValue *item, const char *warningPrefix); + static bool jsonContainsObject(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false); + static bool jsonContainsString(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false); + static bool jsonContainsIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false); + static bool jsonContainsArray(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false); + static bool jsonContainsStringOrIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false); + static bool jsonContainsAttribute(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/curl/curlrequest.cpp b/backends/networking/curl/curlrequest.cpp new file mode 100644 index 0000000000..64fa347023 --- /dev/null +++ b/backends/networking/curl/curlrequest.cpp @@ -0,0 +1,162 @@ +/* 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. +* +*/ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/curl/curlrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#include "backends/networking/curl/networkreadstream.h" +#include "common/textconsole.h" +#include <curl/curl.h> + +namespace Networking { + +CurlRequest::CurlRequest(DataCallback cb, ErrorCallback ecb, Common::String url): + Request(cb, ecb), _url(url), _stream(nullptr), _headersList(nullptr), _bytesBuffer(nullptr), + _bytesBufferSize(0), _uploading(false), _usingPatch(false) {} + +CurlRequest::~CurlRequest() { + delete _stream; + delete _bytesBuffer; +} + +NetworkReadStream *CurlRequest::makeStream() { + if (_bytesBuffer) + return new NetworkReadStream(_url.c_str(), _headersList, _bytesBuffer, _bytesBufferSize, _uploading, _usingPatch, true); + if (!_formFields.empty() || !_formFiles.empty()) + return new NetworkReadStream(_url.c_str(), _headersList, _formFields, _formFiles); + return new NetworkReadStream(_url.c_str(), _headersList, _postFields, _uploading, _usingPatch); +} + +void CurlRequest::handle() { + if (!_stream) _stream = makeStream(); + + if (_stream && _stream->eos()) { + if (_stream->httpResponseCode() != 200) { + warning("CurlRequest: HTTP response code is not 200 OK (it's %ld)", _stream->httpResponseCode()); + ErrorResponse error(this, false, true, "", _stream->httpResponseCode()); + finishError(error); + return; + } + + finishSuccess(); //note that this Request doesn't call its callback on success (that's because it has nothing to return) + } +} + +void CurlRequest::restart() { + if (_stream) + delete _stream; + _stream = nullptr; + //with no stream available next handle() will create another one +} + +Common::String CurlRequest::date() const { + if (_stream) { + Common::String headers = _stream->responseHeaders(); + const char *cstr = headers.c_str(); + const char *position = strstr(cstr, "Date: "); + + if (position) { + Common::String result = ""; + char c; + for (const char *i = position + 6; c = *i, c != 0; ++i) { + if (c == '\n' || c == '\r') + break; + result += c; + } + return result; + } + } + return ""; +} + +void CurlRequest::setHeaders(Common::Array<Common::String> &headers) { + curl_slist_free_all(_headersList); + _headersList = nullptr; + for (uint32 i = 0; i < headers.size(); ++i) + addHeader(headers[i]); +} + +void CurlRequest::addHeader(Common::String header) { + _headersList = curl_slist_append(_headersList, header.c_str()); +} + +void CurlRequest::addPostField(Common::String keyValuePair) { + if (_bytesBuffer) + warning("CurlRequest: added POST fields would be ignored, because there is buffer present"); + + if (!_formFields.empty() || !_formFiles.empty()) + warning("CurlRequest: added POST fields would be ignored, because there are form fields/files present"); + + if (_postFields == "") + _postFields = keyValuePair; + else + _postFields += "&" + keyValuePair; +} + +void CurlRequest::addFormField(Common::String name, Common::String value) { + if (_bytesBuffer) + warning("CurlRequest: added POST form fields would be ignored, because there is buffer present"); + + if (_formFields.contains(name)) + warning("CurlRequest: form field '%s' already had a value", name.c_str()); + + _formFields[name] = value; +} + +void CurlRequest::addFormFile(Common::String name, Common::String filename) { + if (_bytesBuffer) + warning("CurlRequest: added POST form files would be ignored, because there is buffer present"); + + if (_formFields.contains(name)) + warning("CurlRequest: form file field '%s' already had a value", name.c_str()); + + _formFiles[name] = filename; +} + +void CurlRequest::setBuffer(byte *buffer, uint32 size) { + if (_postFields != "") + warning("CurlRequest: added POST fields would be ignored, because buffer added"); + + if (_bytesBuffer) + delete _bytesBuffer; + + _bytesBuffer = buffer; + _bytesBufferSize = size; +} + +void CurlRequest::usePut() { _uploading = true; } + +void CurlRequest::usePatch() { _usingPatch = true; } + +NetworkReadStreamResponse CurlRequest::execute() { + if (!_stream) { + _stream = makeStream(); + ConnMan.addRequest(this); + } + + return NetworkReadStreamResponse(this, _stream); +} + +const NetworkReadStream *CurlRequest::getNetworkReadStream() const { return _stream; } + +} // End of namespace Networking diff --git a/backends/networking/curl/curlrequest.h b/backends/networking/curl/curlrequest.h new file mode 100644 index 0000000000..6ce94f8983 --- /dev/null +++ b/backends/networking/curl/curlrequest.h @@ -0,0 +1,100 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_CURL_CURLREQUEST_H +#define BACKENDS_NETWORKING_CURL_CURLREQUEST_H + +#include "backends/networking/curl/request.h" +#include "common/str.h" +#include "common/array.h" +#include "common/hashmap.h" +#include "common/hash-str.h" + +struct curl_slist; + +namespace Networking { + +class NetworkReadStream; + +typedef Response<NetworkReadStream *> NetworkReadStreamResponse; +typedef Common::BaseCallback<NetworkReadStreamResponse> *NetworkReadStreamCallback; + +class CurlRequest: public Request { +protected: + Common::String _url; + NetworkReadStream *_stream; + curl_slist *_headersList; + Common::String _postFields; + Common::HashMap<Common::String, Common::String> _formFields; + Common::HashMap<Common::String, Common::String> _formFiles; + byte *_bytesBuffer; + uint32 _bytesBufferSize; + bool _uploading; //using PUT method + bool _usingPatch; //using PATCH method + + virtual NetworkReadStream *makeStream(); + +public: + CurlRequest(DataCallback cb, ErrorCallback ecb, Common::String url); + virtual ~CurlRequest(); + + virtual void handle(); + virtual void restart(); + virtual Common::String date() const; + + /** Replaces all headers with the passed array of headers. */ + virtual void setHeaders(Common::Array<Common::String> &headers); + + /** Adds a header into headers list. */ + virtual void addHeader(Common::String header); + + /** Adds a post field (key=value pair). */ + virtual void addPostField(Common::String field); + + /** Adds a form/multipart field (name, value). */ + virtual void addFormField(Common::String name, Common::String value); + + /** Adds a form/multipart file (field name, file name). */ + virtual void addFormFile(Common::String name, Common::String filename); + + /** Sets bytes buffer. */ + virtual void setBuffer(byte *buffer, uint32 size); + + /** Remembers to use PUT method when it would create NetworkReadStream. */ + virtual void usePut(); + + /** Remembers to use PATCH method when it would create NetworkReadStream. */ + virtual void usePatch(); + + /** + * Starts this Request with ConnMan. + * @return its NetworkReadStream in NetworkReadStreamResponse. + */ + virtual NetworkReadStreamResponse execute(); + + /** Returns Request's NetworkReadStream. */ + const NetworkReadStream *getNetworkReadStream() const; +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/curl/networkreadstream.cpp b/backends/networking/curl/networkreadstream.cpp new file mode 100644 index 0000000000..032aef87be --- /dev/null +++ b/backends/networking/curl/networkreadstream.cpp @@ -0,0 +1,257 @@ +/* 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. +* +*/ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/curl/networkreadstream.h" +#include "backends/networking/curl/connectionmanager.h" +#include "base/version.h" +#include <curl/curl.h> + +namespace Networking { + +static size_t curlDataCallback(char *d, size_t n, size_t l, void *p) { + NetworkReadStream *stream = (NetworkReadStream *)p; + if (stream) + return stream->write(d, n * l); + return 0; +} + +static size_t curlReadDataCallback(char *d, size_t n, size_t l, void *p) { + NetworkReadStream *stream = (NetworkReadStream *)p; + if (stream) + return stream->fillWithSendingContents(d, n * l); + return 0; +} + +static size_t curlHeadersCallback(char *d, size_t n, size_t l, void *p) { + NetworkReadStream *stream = (NetworkReadStream *)p; + if (stream) + return stream->addResponseHeaders(d, n * l); + return 0; +} + +static int curlProgressCallback(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { + NetworkReadStream *stream = (NetworkReadStream *)p; + if (stream) + stream->setProgress(dlnow, dltotal); + return 0; +} + +static int curlProgressCallbackOlder(void *p, double dltotal, double dlnow, double ultotal, double ulnow) { + // for libcurl older than 7.32.0 (CURLOPT_PROGRESSFUNCTION) + return curlProgressCallback(p, (curl_off_t)dltotal, (curl_off_t)dlnow, (curl_off_t)ultotal, (curl_off_t)ulnow); +} + +void NetworkReadStream::init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) { + _eos = _requestComplete = false; + _sendingContentsBuffer = nullptr; + _sendingContentsSize = _sendingContentsPos = 0; + _progressDownloaded = _progressTotal = 0; + + _easy = curl_easy_init(); + curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback); + curl_easy_setopt(_easy, CURLOPT_WRITEDATA, this); //so callback can call us + curl_easy_setopt(_easy, CURLOPT_PRIVATE, this); //so ConnectionManager can call us when request is complete + curl_easy_setopt(_easy, CURLOPT_HEADER, 0L); + curl_easy_setopt(_easy, CURLOPT_HEADERDATA, this); + curl_easy_setopt(_easy, CURLOPT_HEADERFUNCTION, curlHeadersCallback); + curl_easy_setopt(_easy, CURLOPT_URL, url); + curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L); + curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); //probably it's OK to have it always on + curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, headersList); + curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion); + curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder); + curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this); +#if LIBCURL_VERSION_NUM >= 0x072000 + // CURLOPT_XFERINFOFUNCTION introduced in libcurl 7.32.0 + // CURLOPT_PROGRESSFUNCTION is used as a backup plan in case older version is used + curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback); + curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this); +#endif + if (uploading) { + curl_easy_setopt(_easy, CURLOPT_UPLOAD, 1L); + curl_easy_setopt(_easy, CURLOPT_READDATA, this); + curl_easy_setopt(_easy, CURLOPT_READFUNCTION, curlReadDataCallback); + _sendingContentsBuffer = buffer; + _sendingContentsSize = bufferSize; + } else if (usingPatch) { + curl_easy_setopt(_easy, CURLOPT_CUSTOMREQUEST, "PATCH"); + } else { + if (post || bufferSize != 0) { + curl_easy_setopt(_easy, CURLOPT_POSTFIELDSIZE, bufferSize); + curl_easy_setopt(_easy, CURLOPT_COPYPOSTFIELDS, buffer); + } + } + ConnMan.registerEasyHandle(_easy); +} + +void NetworkReadStream::init(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) { + _eos = _requestComplete = false; + _sendingContentsBuffer = nullptr; + _sendingContentsSize = _sendingContentsPos = 0; + _progressDownloaded = _progressTotal = 0; + + _easy = curl_easy_init(); + curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback); + curl_easy_setopt(_easy, CURLOPT_WRITEDATA, this); //so callback can call us + curl_easy_setopt(_easy, CURLOPT_PRIVATE, this); //so ConnectionManager can call us when request is complete + curl_easy_setopt(_easy, CURLOPT_HEADER, 0L); + curl_easy_setopt(_easy, CURLOPT_HEADERDATA, this); + curl_easy_setopt(_easy, CURLOPT_HEADERFUNCTION, curlHeadersCallback); + curl_easy_setopt(_easy, CURLOPT_URL, url); + curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L); + curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); //probably it's OK to have it always on + curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, headersList); + curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion); + curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder); + curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this); +#if LIBCURL_VERSION_NUM >= 0x072000 + // CURLOPT_XFERINFOFUNCTION introduced in libcurl 7.32.0 + // CURLOPT_PROGRESSFUNCTION is used as a backup plan in case older version is used + curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback); + curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this); +#endif + + // set POST multipart upload form fields/files + struct curl_httppost *formpost = nullptr; + struct curl_httppost *lastptr = nullptr; + + for (Common::HashMap<Common::String, Common::String>::iterator i = formFields.begin(); i != formFields.end(); ++i) { + CURLFORMcode code = curl_formadd( + &formpost, + &lastptr, + CURLFORM_COPYNAME, i->_key.c_str(), + CURLFORM_COPYCONTENTS, i->_value.c_str(), + CURLFORM_END + ); + + if (code != CURL_FORMADD_OK) + warning("NetworkReadStream: field curl_formadd('%s') failed", i->_key.c_str()); + } + + for (Common::HashMap<Common::String, Common::String>::iterator i = formFiles.begin(); i != formFiles.end(); ++i) { + CURLFORMcode code = curl_formadd( + &formpost, + &lastptr, + CURLFORM_COPYNAME, i->_key.c_str(), + CURLFORM_FILE, i->_value.c_str(), + CURLFORM_END + ); + + if (code != CURL_FORMADD_OK) + warning("NetworkReadStream: file curl_formadd('%s') failed", i->_key.c_str()); + } + + curl_easy_setopt(_easy, CURLOPT_HTTPPOST, formpost); + + ConnMan.registerEasyHandle(_easy); +} + +NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading, bool usingPatch) { + init(url, headersList, (const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false); +} + +NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) { + init(url, headersList, formFields, formFiles); +} + +NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) { + init(url, headersList, buffer, bufferSize, uploading, usingPatch, post); +} + +NetworkReadStream::~NetworkReadStream() { + if (_easy) + curl_easy_cleanup(_easy); +} + +bool NetworkReadStream::eos() const { + return _eos; +} + +uint32 NetworkReadStream::read(void *dataPtr, uint32 dataSize) { + uint32 actuallyRead = MemoryReadWriteStream::read(dataPtr, dataSize); + + if (actuallyRead == 0) { + if (_requestComplete) + _eos = true; + return 0; + } + + return actuallyRead; +} + +void NetworkReadStream::finished() { + _requestComplete = true; +} + +long NetworkReadStream::httpResponseCode() const { + long responseCode = -1; + if (_easy) + curl_easy_getinfo(_easy, CURLINFO_RESPONSE_CODE, &responseCode); + return responseCode; +} + +Common::String NetworkReadStream::currentLocation() const { + Common::String result = ""; + if (_easy) { + char *pointer; + curl_easy_getinfo(_easy, CURLINFO_EFFECTIVE_URL, &pointer); + result = Common::String(pointer); + } + return result; +} + +Common::String NetworkReadStream::responseHeaders() const { + return _responseHeaders; +} + +uint32 NetworkReadStream::fillWithSendingContents(char *bufferToFill, uint32 maxSize) { + uint32 size = _sendingContentsSize - _sendingContentsPos; + if (size > maxSize) + size = maxSize; + for (uint32 i = 0; i < size; ++i) { + bufferToFill[i] = _sendingContentsBuffer[_sendingContentsPos + i]; + } + _sendingContentsPos += size; + return size; +} + +uint32 NetworkReadStream::addResponseHeaders(char *buffer, uint32 size) { + _responseHeaders += Common::String(buffer, size); + return size; +} + +double NetworkReadStream::getProgress() const { + if (_progressTotal < 1) + return 0; + return (double)_progressDownloaded / (double)_progressTotal; +} + +void NetworkReadStream::setProgress(uint64 downloaded, uint64 total) { + _progressDownloaded = downloaded; + _progressTotal = total; +} + +} // End of namespace Cloud diff --git a/backends/networking/curl/networkreadstream.h b/backends/networking/curl/networkreadstream.h new file mode 100644 index 0000000000..2be6d591cb --- /dev/null +++ b/backends/networking/curl/networkreadstream.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_CURL_NETWORKREADSTREAM_H +#define BACKENDS_NETWORKING_CURL_NETWORKREADSTREAM_H + +#include "common/memstream.h" +#include "common/stream.h" +#include "common/str.h" +#include "common/hashmap.h" +#include "common/hash-str.h" + +typedef void CURL; +struct curl_slist; + +namespace Networking { + +class NetworkReadStream: public Common::MemoryReadWriteStream { + CURL *_easy; + bool _eos, _requestComplete; + const byte *_sendingContentsBuffer; + uint32 _sendingContentsSize; + uint32 _sendingContentsPos; + Common::String _responseHeaders; + uint64 _progressDownloaded, _progressTotal; + void init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post); + void init(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles); + +public: + /** Send <postFields>, using POST by default. */ + NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading = false, bool usingPatch = false); + /** Send <formFields>, <formFiles>, using POST multipart/form. */ + NetworkReadStream( + const char *url, curl_slist *headersList, + Common::HashMap<Common::String, Common::String> formFields, + Common::HashMap<Common::String, Common::String> formFiles); + /** Send <buffer, using POST by default. */ + NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true); + virtual ~NetworkReadStream(); + + /** + * Returns true if a read failed because the stream end has been reached. + * This flag is cleared by clearErr(). + * For a SeekableReadStream, it is also cleared by a successful seek. + * + * @note The semantics of any implementation of this method are + * supposed to match those of ISO C feof(). In particular, in a stream + * with N bytes, reading exactly N bytes from the start should *not* + * set eos; only reading *beyond* the available data should set it. + */ + virtual bool eos() const; + + /** + * Read data from the stream. Subclasses must implement this + * method; all other read methods are implemented using it. + * + * @note The semantics of any implementation of this method are + * supposed to match those of ISO C fread(), in particular where + * it concerns setting error and end of file/stream flags. + * + * @param dataPtr pointer to a buffer into which the data is read + * @param dataSize number of bytes to be read + * @return the number of bytes which were actually read. + */ + virtual uint32 read(void *dataPtr, uint32 dataSize); + + /** + * This method is called by ConnectionManager to indicate + * that transfer is finished. + * + * @note It's called on failure too. + */ + void finished(); + + /** + * Returns HTTP response code from inner CURL handle. + * It returns -1 to indicate there is no inner handle. + * + * @note This method should be called when eos() == true. + */ + long httpResponseCode() const; + + /** + * Return current location URL from inner CURL handle. + * "" is returned to indicate there is no inner handle. + * + * @note This method should be called when eos() == true. + */ + Common::String currentLocation() const; + + /** + * Return response headers. + * + * @note This method should be called when eos() == true. + */ + Common::String responseHeaders() const; + + /** + * Fills the passed buffer with _sendingContentsBuffer contents. + * It works similarly to read(), expect it's not for reading + * Stream's contents, but for sending our own data to the server. + * + * @returns how many bytes were actually read (filled in) + */ + uint32 fillWithSendingContents(char *bufferToFill, uint32 maxSize); + + /** + * Remembers headers returned to CURL in server's response. + * + * @returns how many bytes were actually read + */ + uint32 addResponseHeaders(char *buffer, uint32 size); + + /** Returns a number in range [0, 1], where 1 is "complete". */ + double getProgress() const; + + /** Used in curl progress callback to pass current downloaded/total values. */ + void setProgress(uint64 downloaded, uint64 total); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/curl/request.cpp b/backends/networking/curl/request.cpp new file mode 100644 index 0000000000..30af48a478 --- /dev/null +++ b/backends/networking/curl/request.cpp @@ -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 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 "backends/networking/curl/request.h" + +namespace Networking { + +ErrorResponse::ErrorResponse(Request *rq): + request(rq), interrupted(false), failed(true), response(""), httpResponseCode(-1) {} + +ErrorResponse::ErrorResponse(Request *rq, bool interrupt, bool failure, Common::String resp, long httpCode): + request(rq), interrupted(interrupt), failed(failure), response(resp), httpResponseCode(httpCode) {} + +Request::Request(DataCallback cb, ErrorCallback ecb): + _callback(cb), _errorCallback(ecb), _state(PROCESSING), _retryInSeconds(0) {} + +Request::~Request() { + delete _callback; + delete _errorCallback; +} + +void Request::handleRetry() { + if (_retryInSeconds > 0) { + --_retryInSeconds; + } else { + _state = PROCESSING; + restart(); + } +} + +void Request::pause() { _state = PAUSED; } + +void Request::finish() { + ErrorResponse error(this, true, false, "", -1); + finishError(error); +} + +void Request::retry(uint32 seconds) { + _state = RETRY; + _retryInSeconds = seconds; +} + +RequestState Request::state() const { return _state; } + +Common::String Request::date() const { return ""; } + +void Request::finishError(ErrorResponse error) { + _state = FINISHED; + if (_errorCallback) + (*_errorCallback)(error); +} + +void Request::finishSuccess() { _state = FINISHED; } + +} // End of namespace Networking diff --git a/backends/networking/curl/request.h b/backends/networking/curl/request.h new file mode 100644 index 0000000000..9b366ea40c --- /dev/null +++ b/backends/networking/curl/request.h @@ -0,0 +1,203 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_CURL_REQUEST_H +#define BACKENDS_NETWORKING_CURL_REQUEST_H + +#include "common/callback.h" +#include "common/scummsys.h" +#include "common/str.h" + +namespace Networking { + +class Request; + +/** + * Response<T> is a struct to be returned from Request + * to user's callbacks. It's a type safe way to indicate + * which "return value" Request has and user awaits. + * + * It just keeps a Request pointer together with + * some T value (which might be a pointer, a reference + * or a plain type (copied by value)). + * + * To make it more convenient, typedefs are used. + * For example, Response<void *> is called DataResponse + * and corresponding callback pointer is DataCallback. + */ + +template<typename T> struct Response { + Request *request; + T value; + + Response(Request *rq, T v) : request(rq), value(v) {} +}; + +/** + * ErrorResponse is a struct to be returned from Request + * to user's failure callbacks. + * + * It keeps a Request pointer together with some useful + * information fields, which would explain why failure + * callback was called. + * + * <interrupted> flag is set when Request was interrupted, + * i.e. finished by user with finish() call. + * + * <failed> flag is set when Request has failed because of + * some error (bad server response, for example). + * + * <response> contains server's original response. + * + * <httpResponseCode> contains server's HTTP response code. + */ + +struct ErrorResponse { + Request *request; + bool interrupted; + bool failed; + Common::String response; + long httpResponseCode; + + ErrorResponse(Request *rq); + ErrorResponse(Request *rq, bool interrupt, bool failure, Common::String resp, long httpCode); +}; + +typedef Response<void *> DataReponse; +typedef Common::BaseCallback<DataReponse> *DataCallback; +typedef Common::BaseCallback<ErrorResponse> *ErrorCallback; + +/** + * RequestState is used to indicate current Request state. + * ConnectionManager uses it to decide what to do with the Request. + * + * PROCESSING state indicates that Request is working. + * ConnectionManager calls handle() method of Requests in that state. + * + * PAUSED state indicates that Request is not working. + * ConnectionManager keeps Requests in that state and doesn't call any methods of those. + * + * RETRY state indicates that Request must restart after a few seconds. + * ConnectionManager calls handleRetry() method of Requests in that state. + * Default handleRetry() implementation decreases _retryInSeconds value + * until it reaches zero. When it does, Request's restart() method is called. + * + * FINISHED state indicates that Request did the work and might be deleted. + * ConnectionManager deletes Requests in that state. + * After this state is set, but before ConnectionManager deletes the Request, + * Request calls user's callback. User can ask Request to change its state + * by calling retry() or pause() methods and Request won't be deleted. + * + * Request get a success and failure callbacks. Request must call one + * (and only one!) of these callbacks when it sets FINISHED state. + */ +enum RequestState { + PROCESSING, + PAUSED, + RETRY, + FINISHED +}; + +class Request { +protected: + /** + * Callback, which should be called when Request is finished. + * That's the way Requests pass the result to the code which asked to create this request. + * + * @note some Requests use their own callbacks to return something but void *. + * @note callback must be called in finish() or similar method. + */ + DataCallback _callback; + + /** + * Callback, which should be called when Request is failed/interrupted. + * That's the way Requests pass error information to the code which asked to create this request. + * @note callback must be called in finish() or similar method. + */ + ErrorCallback _errorCallback; + + /** + * Request state, which is used by ConnectionManager to determine + * whether request might be deleted or it's still working. + * + * State might be changed from outside with finish(), pause() or + * retry() methods. Override these if you want to react to these + * changes correctly. + */ + RequestState _state; + + /** In RETRY state this indicates whether it's time to call restart(). */ + uint32 _retryInSeconds; + + /** Sets FINISHED state and calls the _errorCallback with given error. */ + virtual void finishError(ErrorResponse error); + + /** Sets FINISHED state. Implementations might extend it if needed. */ + virtual void finishSuccess(); + +public: + Request(DataCallback cb, ErrorCallback ecb); + virtual ~Request(); + + /** Method, which does actual work. Depends on what this Request is doing. */ + virtual void handle() = 0; + + /** Method, which is called by ConnectionManager when Request's state is RETRY. */ + virtual void handleRetry(); + + /** Method, which is used to restart the Request. */ + virtual void restart() = 0; + + /** Method, which is called to pause the Request. */ + virtual void pause(); + + /** + * Method, which is called to *interrupt* the Request. + * When it's called, Request must stop its work and + * call the failure callback to notify user. + */ + virtual void finish(); + + /** Method, which is called to retry the Request. */ + virtual void retry(uint32 seconds); + + /** Returns Request's current state. */ + RequestState state() const; + + /** + * Return date this Request received from server. + * It could be extracted from "Date" header, + * which is kept in NetworkReadStream. + * + * @note not all Requests do that, so "" is returned + * to indicate the date is unknown. That's also true + * if no server response available or no "Date" header + * was passed. + * + * @returns date from "Date" response header. + */ + virtual Common::String date() const; +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/make_archive.py b/backends/networking/make_archive.py new file mode 100644 index 0000000000..64d314bedd --- /dev/null +++ b/backends/networking/make_archive.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# encoding: utf-8 +import sys +import re +import os +import zipfile + +ARCHIVE_FILE_EXTENSIONS = ('.html', '.css', '.js', '.ico', '.png') + +def buildArchive(archiveName): + if not os.path.isdir(archiveName): + print ("Invalid archive name: " + archiveName) + return + + zf = zipfile.ZipFile(archiveName + ".zip", 'w') + + print ("Building '" + archiveName + "' archive:") + os.chdir(archiveName) + + directories = ['.', './icons'] + for d in directories: + filenames = os.listdir(d) + filenames.sort() + for filename in filenames: + if os.path.isfile(d + '/' + filename) and filename.endswith(ARCHIVE_FILE_EXTENSIONS): + zf.write(d + '/' + filename, d + '/' + filename) + print (" Adding file: " + d + '/' + filename) + + os.chdir('../') + + zf.close() + +def main(): + buildArchive("wwwroot") + +if __name__ == "__main__": + sys.exit(main()) diff --git a/backends/networking/sdl_net/client.cpp b/backends/networking/sdl_net/client.cpp new file mode 100644 index 0000000000..dab38ba5c0 --- /dev/null +++ b/backends/networking/sdl_net/client.cpp @@ -0,0 +1,190 @@ +/* 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. +* +*/ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/sdl_net/client.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/memstream.h" +#include <SDL/SDL_net.h> + +namespace Networking { + +Client::Client(): + _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr), + _previousHandler(nullptr), _stream(nullptr), _buffer(new byte[CLIENT_BUFFER_SIZE]) {} + +Client::Client(SDLNet_SocketSet set, TCPsocket socket): + _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr), + _previousHandler(nullptr), _stream(nullptr), _buffer(new byte[CLIENT_BUFFER_SIZE]) { + open(set, socket); +} + +Client::~Client() { + close(); + delete[] _buffer; +} + +void Client::open(SDLNet_SocketSet set, TCPsocket socket) { + if (_state != INVALID) + close(); + _state = READING_HEADERS; + _socket = socket; + _set = set; + Reader cleanReader; + _reader = cleanReader; + if (_handler) + delete _handler; + _handler = nullptr; + if (_previousHandler) + delete _previousHandler; + _previousHandler = nullptr; + _stream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES); + if (set) { + int numused = SDLNet_TCP_AddSocket(set, socket); + if (numused == -1) { + error("Client: SDLNet_AddSocket: %s\n", SDLNet_GetError()); + } + } +} + +bool Client::readMoreIfNeeded() { + if (_stream == nullptr) + return false; //nothing to read into + if (_stream->size() - _stream->pos() > 0) + return true; //not needed, some data left in the stream + if (!_socket) + return false; + if (!SDLNet_SocketReady(_socket)) + return false; + + int bytes = SDLNet_TCP_Recv(_socket, _buffer, CLIENT_BUFFER_SIZE); + if (bytes <= 0) { + warning("Client::readMoreIfNeeded: recv fail"); + close(); + return false; + } + + if (_stream->write(_buffer, bytes) != bytes) { + warning("Client::readMoreIfNeeded: failed to write() into MemoryReadWriteStream"); + close(); + return false; + } + + return true; +} + +void Client::readHeaders() { + if (!readMoreIfNeeded()) + return; + _reader.setContent(_stream); + if (_reader.readFirstHeaders()) + _state = (_reader.badRequest() ? BAD_REQUEST : READ_HEADERS); +} + +bool Client::readContent(Common::WriteStream *stream) { + if (!readMoreIfNeeded()) + return false; + _reader.setContent(_stream); + return _reader.readFirstContent(stream); +} + +bool Client::readBlockHeaders(Common::WriteStream *stream) { + if (!readMoreIfNeeded()) + return false; + _reader.setContent(_stream); + return _reader.readBlockHeaders(stream); +} + +bool Client::readBlockContent(Common::WriteStream *stream) { + if (!readMoreIfNeeded()) + return false; + _reader.setContent(_stream); + return _reader.readBlockContent(stream); +} + +void Client::setHandler(ClientHandler *handler) { + if (_handler) { + if (_previousHandler) + delete _previousHandler; + _previousHandler = _handler; //can't just delete it, as setHandler() could've been called by handler itself + } + _state = BEING_HANDLED; + _handler = handler; +} + +void Client::handle() { + if (_state != BEING_HANDLED) + warning("handle() called in a wrong Client's state"); + if (!_handler) + warning("Client doesn't have handler to be handled by"); + if (_handler) + _handler->handle(this); +} + +void Client::close() { + if (_set) { + if (_socket) { + int numused = SDLNet_TCP_DelSocket(_set, _socket); + if (numused == -1) + error("Client: SDLNet_DelSocket: %s\n", SDLNet_GetError()); + } + _set = nullptr; + } + + if (_socket) { + SDLNet_TCP_Close(_socket); + _socket = nullptr; + } + + if (_stream) { + delete _stream; + _stream = nullptr; + } + + _state = INVALID; +} + + +ClientState Client::state() const { return _state; } + +Common::String Client::headers() const { return _reader.headers(); } + +Common::String Client::method() const { return _reader.method(); } + +Common::String Client::path() const { return _reader.path(); } + +Common::String Client::query() const { return _reader.query(); } + +Common::String Client::queryParameter(Common::String name) const { return _reader.queryParameter(name); } + +Common::String Client::anchor() const { return _reader.anchor(); } + +bool Client::noMoreContent() const { return _reader.noMoreContent(); } + +bool Client::socketIsReady() { return SDLNet_SocketReady(_socket); } + +int Client::recv(void *data, int maxlen) { return SDLNet_TCP_Recv(_socket, data, maxlen); } + +int Client::send(void *data, int len) { return SDLNet_TCP_Send(_socket, data, len); } + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/client.h b/backends/networking/sdl_net/client.h new file mode 100644 index 0000000000..134c1be05d --- /dev/null +++ b/backends/networking/sdl_net/client.h @@ -0,0 +1,125 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_CLIENT_H +#define BACKENDS_NETWORKING_SDL_NET_CLIENT_H + +#include "backends/networking/sdl_net/reader.h" +#include "common/str.h" + +namespace Common { +class MemoryReadWriteStream; +} + +typedef struct _SDLNet_SocketSet *SDLNet_SocketSet; +typedef struct _TCPsocket *TCPsocket; + +namespace Networking { + +enum ClientState { + INVALID, + READING_HEADERS, + READ_HEADERS, + BAD_REQUEST, + BEING_HANDLED +}; + +class Client; + +#define CLIENT_BUFFER_SIZE 1 * 1024 * 1024 + +class ClientHandler { +public: + virtual ~ClientHandler() {}; + virtual void handle(Client *client) = 0; +}; + +/** + * Client class represents one client's HTTP request + * to the LocalWebserver. + * + * While in READING_HEADERS state, it's kept in LocalWebserver. + * Client must read the headers and decide whether it's + * READ_HEADERS (could be handled) or BAD_REQUEST (failed). + * + * If it's READ_HEADERS, LocalWebserver searches for a corresponding + * BaseHandler. These classes use the information from headers - + * like method, path, GET parameters - to build the response + * for this client's request. When they do, they call setHandler() + * and pass a special ClientHandler. Client becomes BEING_HANDLED. + * + * While in that state, LocalWebserver calls Client's handle() and + * it's passed to ClientHandler. The latter does the job: it commands + * Client to read or write bytes with its socket or calls + * readContent() methods, so Client reads the request through Reader. + */ + +class Client { + ClientState _state; + SDLNet_SocketSet _set; + TCPsocket _socket; + Reader _reader; + ClientHandler *_handler, *_previousHandler; + Common::MemoryReadWriteStream *_stream; + byte *_buffer; + + bool readMoreIfNeeded(); + +public: + Client(); + Client(SDLNet_SocketSet set, TCPsocket socket); + virtual ~Client(); + + void open(SDLNet_SocketSet set, TCPsocket socket); + void readHeaders(); + bool readContent(Common::WriteStream *stream); + bool readBlockHeaders(Common::WriteStream *stream); + bool readBlockContent(Common::WriteStream *stream); + void setHandler(ClientHandler *handler); + void handle(); + void close(); + + ClientState state() const; + Common::String headers() const; + Common::String method() const; + Common::String path() const; + Common::String query() const; + Common::String queryParameter(Common::String name) const; + Common::String anchor() const; + + bool noMoreContent() const; + + /** + * Return SDLNet_SocketReady(_socket). + * + * It's "ready" when it has something + * to read (recv()). You can send() + * when this is false. + */ + bool socketIsReady(); + int recv(void *data, int maxlen); + int send(void *data, int len); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/getclienthandler.cpp b/backends/networking/sdl_net/getclienthandler.cpp new file mode 100644 index 0000000000..1c4f5db8a8 --- /dev/null +++ b/backends/networking/sdl_net/getclienthandler.cpp @@ -0,0 +1,162 @@ +/* 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 "backends/networking/sdl_net/getclienthandler.h" +#include "common/textconsole.h" + +namespace Networking { + +GetClientHandler::GetClientHandler(Common::SeekableReadStream *stream): + _responseCode(200), _headersPrepared(false), + _stream(stream), _buffer(new byte[CLIENT_HANDLER_BUFFER_SIZE]) {} + +GetClientHandler::~GetClientHandler() { + delete _stream; + delete[] _buffer; +} + +const char *GetClientHandler::responseMessage(long responseCode) { + switch (responseCode) { + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 102: return "Processing"; + + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 226: return "IM Used"; + + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Moved Temporarily"; //case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "RESERVED"; + case 307: return "Temporary Redirect"; + + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Request Entity Too Large"; + case 414: return "Request-URI Too Large"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 425: return "Unordered Collection"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 434: return "Requested Host Unavailable"; + case 449: return "Retry With"; + case 451: return "Unavailable For Legal Reasons"; + + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 509: return "Bandwidth Limit Exceeded"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + } + return "Unknown"; +} + +void GetClientHandler::prepareHeaders() { + if (!_specialHeaders.contains("Content-Type")) + setHeader("Content-Type", "text/html"); + + if (!_specialHeaders.contains("Content-Length") && _stream) + setHeader("Content-Length", Common::String::format("%u", _stream->size())); + + _headers = Common::String::format("HTTP/1.1 %ld %s\r\n", _responseCode, responseMessage(_responseCode)); + for (Common::HashMap<Common::String, Common::String>::iterator i = _specialHeaders.begin(); i != _specialHeaders.end(); ++i) + _headers += i->_key + ": " + i->_value + "\r\n"; + _headers += "\r\n"; + + _headersPrepared = true; +} + +void GetClientHandler::handle(Client *client) { + if (!client) + return; + if (!_headersPrepared) + prepareHeaders(); + + uint32 readBytes; + + // send headers first + if (_headers.size() > 0) { + readBytes = _headers.size(); + if (readBytes > CLIENT_HANDLER_BUFFER_SIZE) + readBytes = CLIENT_HANDLER_BUFFER_SIZE; + memcpy(_buffer, _headers.c_str(), readBytes); + _headers.erase(0, readBytes); + } else { + if (!_stream) { + client->close(); + return; + } + + readBytes = _stream->read(_buffer, CLIENT_HANDLER_BUFFER_SIZE); + } + + if (readBytes != 0) + if (client->send(_buffer, readBytes) != readBytes) { + warning("GetClientHandler: unable to send all bytes to the client"); + client->close(); + return; + } + + // we're done here! + if (_stream->eos()) + client->close(); +} + +void GetClientHandler::setHeader(Common::String name, Common::String value) { _specialHeaders[name] = value; } +void GetClientHandler::setResponseCode(long code) { _responseCode = code; } + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/getclienthandler.h b/backends/networking/sdl_net/getclienthandler.h new file mode 100644 index 0000000000..3486ceef8a --- /dev/null +++ b/backends/networking/sdl_net/getclienthandler.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_GETCLIENTHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_GETCLIENTHANDLER_H + +#include "backends/networking/sdl_net/client.h" +#include "common/hashmap.h" +#include "common/stream.h" +#include "common/hash-str.h" + +namespace Networking { + +#define CLIENT_HANDLER_BUFFER_SIZE 1 * 1024 * 1024 + +class GetClientHandler: public ClientHandler { + Common::HashMap<Common::String, Common::String> _specialHeaders; + long _responseCode; + bool _headersPrepared; + Common::String _headers; + Common::SeekableReadStream *_stream; + byte *_buffer; + + static const char *responseMessage(long responseCode); + void prepareHeaders(); + +public: + GetClientHandler(Common::SeekableReadStream *stream); + virtual ~GetClientHandler(); + + virtual void handle(Client *client); + void setHeader(Common::String name, Common::String value); + void setResponseCode(long code); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/basehandler.h b/backends/networking/sdl_net/handlers/basehandler.h new file mode 100644 index 0000000000..dec5e955bd --- /dev/null +++ b/backends/networking/sdl_net/handlers/basehandler.h @@ -0,0 +1,41 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_BASEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_BASEHANDLER_H + +#include "backends/networking/sdl_net/client.h" + +namespace Networking { + +class BaseHandler { +public: + BaseHandler() {} + virtual ~BaseHandler() {} + + virtual void handle(Client &) = 0; + virtual bool minimalModeSupported() { return false; } +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/createdirectoryhandler.cpp b/backends/networking/sdl_net/handlers/createdirectoryhandler.cpp new file mode 100644 index 0000000000..284bf16651 --- /dev/null +++ b/backends/networking/sdl_net/handlers/createdirectoryhandler.cpp @@ -0,0 +1,129 @@ +/* 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 "backends/networking/sdl_net/handlers/createdirectoryhandler.h" +#include "backends/fs/fs-factory.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/translation.h" +#include <common/callback.h> + +namespace Networking { + +CreateDirectoryHandler::CreateDirectoryHandler() {} + +CreateDirectoryHandler::~CreateDirectoryHandler() {} + +void CreateDirectoryHandler::handleError(Client &client, Common::String message) const { + if (client.queryParameter("answer_json") == "true") + setJsonResponseHandler(client, "error", message); + else + HandlerUtils::setFilesManagerErrorMessageHandler(client, message); +} + +void CreateDirectoryHandler::setJsonResponseHandler(Client &client, Common::String type, Common::String message) const { + Common::JSONObject response; + response.setVal("type", new Common::JSONValue(type)); + response.setVal("message", new Common::JSONValue(message)); + + Common::JSONValue json = response; + LocalWebserver::setClientGetHandler(client, json.stringify(true)); +} + +/// public + +void CreateDirectoryHandler::handle(Client &client) { + Common::String path = client.queryParameter("path"); + Common::String name = client.queryParameter("directory_name"); + + // check that <path> is not an absolute root + if (path == "" || path == "/") { + handleError(client, _("Can't create directory here!")); + return; + } + + // check that <path> contains no '../' + if (HandlerUtils::hasForbiddenCombinations(path)) { + handleError(client, _("Invalid path!")); + return; + } + + // transform virtual path to actual file system one + Common::String prefixToRemove = "", prefixToAdd = ""; + if (!transformPath(path, prefixToRemove, prefixToAdd) || path.empty()) { + handleError(client, _("Invalid path!")); + return; + } + + // check that <path> exists, is directory and isn't forbidden + AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path); + if (!HandlerUtils::permittedPath(node->getPath())) { + handleError(client, _("Invalid path!")); + return; + } + if (!node->exists()) { + handleError(client, _("Parent directory doesn't exists!")); + return; + } + if (!node->isDirectory()) { + handleError(client, _("Can't create a directory within a file!")); + return; + } + + // check that <directory_name> doesn't exist or is directory + if (path.lastChar() != '/' && path.lastChar() != '\\') + path += '/'; + node = g_system->getFilesystemFactory()->makeFileNodePath(path + name); + if (node->exists()) { + if (!node->isDirectory()) { + handleError(client, _("There is a file with that name in the parent directory!")); + return; + } + } else { + // create the <directory_name> in <path> + if (!node->create(true)) { + handleError(client, _("Failed to create the directory!")); + return; + } + } + + // if json requested, respond with it + if (client.queryParameter("answer_json") == "true") { + setJsonResponseHandler(client, "success", _("Directory created successfully!")); + return; + } + + // set redirect on success + HandlerUtils::setMessageHandler( + client, + Common::String::format( + "%s<br/><a href=\"files?path=%s\">%s</a>", + _("Directory created successfully!"), + client.queryParameter("path").c_str(), + _("Back to parent directory") + ), + (client.queryParameter("ajax") == "true" ? "/filesAJAX?path=" : "/files?path=") + + LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path")) + ); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/createdirectoryhandler.h b/backends/networking/sdl_net/handlers/createdirectoryhandler.h new file mode 100644 index 0000000000..2a18d5c4aa --- /dev/null +++ b/backends/networking/sdl_net/handlers/createdirectoryhandler.h @@ -0,0 +1,42 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_CREATEDIRECTORYHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_CREATEDIRECTORYHANDLER_H + +#include "backends/networking/sdl_net/handlers/filesbasehandler.h" + +namespace Networking { + +class CreateDirectoryHandler: public FilesBaseHandler { + void handleError(Client &client, Common::String message) const; + void setJsonResponseHandler(Client &client, Common::String type, Common::String message) const; +public: + CreateDirectoryHandler(); + virtual ~CreateDirectoryHandler(); + + virtual void handle(Client &client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/downloadfilehandler.cpp b/backends/networking/sdl_net/handlers/downloadfilehandler.cpp new file mode 100644 index 0000000000..9e212b1a6c --- /dev/null +++ b/backends/networking/sdl_net/handlers/downloadfilehandler.cpp @@ -0,0 +1,88 @@ +/* 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 "backends/networking/sdl_net/handlers/downloadfilehandler.h" +#include "backends/fs/fs-factory.h" +#include "backends/networking/sdl_net/getclienthandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/translation.h" + +namespace Networking { + +DownloadFileHandler::DownloadFileHandler() {} + +DownloadFileHandler::~DownloadFileHandler() {} + +/// public + +void DownloadFileHandler::handle(Client &client) { + Common::String path = client.queryParameter("path"); + + // check that <path> is not an absolute root + if (path == "" || path == "/") { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + + // check that <path> contains no '../' + if (HandlerUtils::hasForbiddenCombinations(path)) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + + // transform virtual path to actual file system one + Common::String prefixToRemove = "", prefixToAdd = ""; + if (!transformPath(path, prefixToRemove, prefixToAdd, false) || path.empty()) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + + // check that <path> exists, is directory and isn't forbidden + AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path); + if (!HandlerUtils::permittedPath(node->getPath())) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + if (!node->exists()) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The file doesn't exist!")); + return; + } + if (node->isDirectory()) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Can't download a directory!")); + return; + } + Common::SeekableReadStream *stream = node->createReadStream(); + if (stream == nullptr) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Failed to read the file!")); + return; + } + + GetClientHandler *handler = new GetClientHandler(stream); + handler->setResponseCode(200); + handler->setHeader("Content-Type", "application/force-download"); + handler->setHeader("Content-Disposition", "attachment; filename=\"" + node->getDisplayName() + "\""); + handler->setHeader("Content-Transfer-Encoding", "binary"); + client.setHandler(handler); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/downloadfilehandler.h b/backends/networking/sdl_net/handlers/downloadfilehandler.h new file mode 100644 index 0000000000..5fa5e5d55a --- /dev/null +++ b/backends/networking/sdl_net/handlers/downloadfilehandler.h @@ -0,0 +1,40 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_DOWNLOADFILEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_DOWNLOADFILEHANDLER_H + +#include "backends/networking/sdl_net/handlers/filesbasehandler.h" + +namespace Networking { + +class DownloadFileHandler: public FilesBaseHandler { +public: + DownloadFileHandler(); + virtual ~DownloadFileHandler(); + + virtual void handle(Client &client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp b/backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp new file mode 100644 index 0000000000..8c5ee29b70 --- /dev/null +++ b/backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp @@ -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 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 "backends/networking/sdl_net/handlers/filesajaxpagehandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/translation.h" + +namespace Networking { + +#define FILES_PAGE_NAME ".filesAJAX.html" + +FilesAjaxPageHandler::FilesAjaxPageHandler() {} + +FilesAjaxPageHandler::~FilesAjaxPageHandler() {} + +namespace { + +Common::String encodeDoubleQuotesAndSlashes(Common::String s) { + Common::String result = ""; + for (uint32 i = 0; i < s.size(); ++i) + if (s[i] == '"') { + result += "\\\""; + } else if (s[i] == '\\') { + result += "\\\\"; + } else { + result += s[i]; + } + return result; +} + +} + +/// public + +void FilesAjaxPageHandler::handle(Client &client) { + // load stylish response page from the archive + Common::SeekableReadStream *const stream = HandlerUtils::getArchiveFile(FILES_PAGE_NAME); + if (stream == nullptr) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The page is not available without the resources.")); + return; + } + + Common::String response = HandlerUtils::readEverythingFromStream(stream); + Common::String path = client.queryParameter("path"); + + //these occur twice: + replace(response, "{create_directory_button}", _("Create directory")); + replace(response, "{create_directory_button}", _("Create directory")); + replace(response, "{upload_files_button}", _("Upload files")); //tab + replace(response, "{upload_file_button}", _("Upload files")); //button in the tab + replace(response, "{create_directory_desc}", _("Type new directory name:")); + replace(response, "{upload_file_desc}", _("Select a file to upload:")); + replace(response, "{or_upload_directory_desc}", _("Or select a directory (works in Chrome only):")); + replace(response, "{index_of}", _("Index of ")); + replace(response, "{loading}", _("Loading...")); + replace(response, "{error}", _("Error occurred")); + replace(response, "{start_path}", encodeDoubleQuotesAndSlashes(path)); + LocalWebserver::setClientGetHandler(client, response); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/filesajaxpagehandler.h b/backends/networking/sdl_net/handlers/filesajaxpagehandler.h new file mode 100644 index 0000000000..1d9b125c2e --- /dev/null +++ b/backends/networking/sdl_net/handlers/filesajaxpagehandler.h @@ -0,0 +1,40 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_FILESAJAXPAGEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_FILESAJAXPAGEHANDLER_H + +#include "backends/networking/sdl_net/handlers/filesbasehandler.h" + +namespace Networking { + +class FilesAjaxPageHandler: public FilesBaseHandler { +public: + FilesAjaxPageHandler(); + virtual ~FilesAjaxPageHandler(); + + virtual void handle(Client &client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/filesbasehandler.cpp b/backends/networking/sdl_net/handlers/filesbasehandler.cpp new file mode 100644 index 0000000000..a585af9b5a --- /dev/null +++ b/backends/networking/sdl_net/handlers/filesbasehandler.cpp @@ -0,0 +1,91 @@ +/* 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 "backends/networking/sdl_net/handlers/filesbasehandler.h" +#include "backends/saves/default/default-saves.h" +#include "common/config-manager.h" +#include "common/system.h" + +namespace Networking { + +FilesBaseHandler::FilesBaseHandler() {} + +FilesBaseHandler::~FilesBaseHandler() {} + +Common::String FilesBaseHandler::parentPath(Common::String path) { + if (path.size() && (path.lastChar() == '/' || path.lastChar() == '\\')) path.deleteLastChar(); + if (!path.empty()) { + for (int i = path.size() - 1; i >= 0; --i) + if (i == 0 || path[i] == '/' || path[i] == '\\') { + path.erase(i); + break; + } + } + if (path.size() && path.lastChar() != '/' && path.lastChar() != '\\') + path += '/'; + return path; +} + +bool FilesBaseHandler::transformPath(Common::String &path, Common::String &prefixToRemove, Common::String &prefixToAdd, bool isDirectory) { + // <path> is not empty, but could lack the trailing slash + if (isDirectory && path.lastChar() != '/' && path.lastChar() != '\\') + path += '/'; + + if (path.hasPrefix("/root") && ConfMan.hasKey("rootpath", "cloud")) { + prefixToAdd = "/root/"; + prefixToRemove = ConfMan.get("rootpath", "cloud"); + if (prefixToRemove.size() && prefixToRemove.lastChar() != '/' && prefixToRemove.lastChar() != '\\') + prefixToRemove += '/'; + if (prefixToRemove == "/") prefixToRemove = ""; + path.erase(0, 5); + if (path.size() && (path[0] == '/' || path[0] == '\\')) + path.deleteChar(0); // if that was "/root/ab/c", it becomes "/ab/c", but we need "ab/c" + path = prefixToRemove + path; + if (path == "") + path = "/"; // absolute root is '/' + return true; + } + + if (path.hasPrefix("/saves")) { + prefixToAdd = "/saves/"; + + // determine savepath (prefix to remove) +#ifdef USE_LIBCURL + DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager()); + prefixToRemove = (manager ? manager->concatWithSavesPath("") : ConfMan.get("savepath")); +#else + prefixToRemove = ConfMan.get("savepath"); +#endif + if (prefixToRemove.size() && prefixToRemove.lastChar() != '/' && prefixToRemove.lastChar() != '\\') + prefixToRemove += '/'; + + path.erase(0, 6); + if (path.size() && (path[0] == '/' || path[0] == '\\')) + path.deleteChar(0); + path = prefixToRemove + path; + return true; + } + + return false; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/filesbasehandler.h b/backends/networking/sdl_net/handlers/filesbasehandler.h new file mode 100644 index 0000000000..1c7f4dd799 --- /dev/null +++ b/backends/networking/sdl_net/handlers/filesbasehandler.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_FILESBASEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_FILESBASEHANDLER_H + +#include "backends/networking/sdl_net/handlers/basehandler.h" + +namespace Networking { + +class FilesBaseHandler: public BaseHandler { +protected: + Common::String parentPath(Common::String path); + + /** + * Transforms virtual <path> into actual file system path. + * + * Fills prefixes with actual file system prefix ("to remove") + * and virtual path prefix ("to add"). + * + * Returns true on success. + */ + bool transformPath(Common::String &path, Common::String &prefixToRemove, Common::String &prefixToAdd, bool isDirectory = true); +public: + FilesBaseHandler(); + virtual ~FilesBaseHandler(); + + virtual void handle(Client &client) = 0; +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/filespagehandler.cpp b/backends/networking/sdl_net/handlers/filespagehandler.cpp new file mode 100644 index 0000000000..e117b4922c --- /dev/null +++ b/backends/networking/sdl_net/handlers/filespagehandler.cpp @@ -0,0 +1,237 @@ +/* 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 "backends/networking/sdl_net/handlers/filespagehandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/config-manager.h" +#include "common/translation.h" + +namespace Networking { + +#define INDEX_PAGE_NAME ".index.html" +#define FILES_PAGE_NAME ".files.html" + +FilesPageHandler::FilesPageHandler() {} + +FilesPageHandler::~FilesPageHandler() {} + +namespace { +Common::String encodeDoubleQuotes(Common::String s) { + Common::String result = ""; + for (uint32 i = 0; i < s.size(); ++i) + if (s[i] == '"') { + result += "\\\""; + } else { + result += s[i]; + } + return result; +} + +Common::String encodeHtmlEntities(Common::String s) { + Common::String result = ""; + for (uint32 i = 0; i < s.size(); ++i) + if (s[i] == '<') + result += "<"; + else if (s[i] == '>') + result += ">"; + else if (s[i] == '&') + result += "&"; + else if (s[i] > 0x7F) + result += Common::String::format("&#%d;", (int)s[i]); + else result += s[i]; + return result; +} + +Common::String getDisplayPath(Common::String s) { + Common::String result = ""; + for (uint32 i = 0; i < s.size(); ++i) + if (s[i] == '\\') + result += '/'; + else + result += s[i]; + if (result == "") + return "/"; + return result; +} +} + +bool FilesPageHandler::listDirectory(Common::String path, Common::String &content, const Common::String &itemTemplate) { + if (path == "" || path == "/") { + if (ConfMan.hasKey("rootpath", "cloud")) + addItem(content, itemTemplate, IT_DIRECTORY, "/root/", _("File system root")); + addItem(content, itemTemplate, IT_DIRECTORY, "/saves/", _("Saved games")); + return true; + } + + if (HandlerUtils::hasForbiddenCombinations(path)) + return false; + + Common::String prefixToRemove = "", prefixToAdd = ""; + if (!transformPath(path, prefixToRemove, prefixToAdd)) + return false; + + Common::FSNode node = Common::FSNode(path); + if (path == "/") + node = node.getParent(); // absolute root + + if (!HandlerUtils::permittedPath(node.getPath())) + return false; + + if (!node.isDirectory()) + return false; + + // list directory + Common::FSList _nodeContent; + if (!node.getChildren(_nodeContent, Common::FSNode::kListAll, false)) // do not show hidden files + _nodeContent.clear(); + else + Common::sort(_nodeContent.begin(), _nodeContent.end()); + + // add parent directory link + { + Common::String filePath = path; + if (filePath.hasPrefix(prefixToRemove)) + filePath.erase(0, prefixToRemove.size()); + if (filePath == "" || filePath == "/" || filePath == "\\") + filePath = "/"; + else + filePath = parentPath(prefixToAdd + filePath); + addItem(content, itemTemplate, IT_PARENT_DIRECTORY, filePath, _("Parent directory")); + } + + // fill the content + for (Common::FSList::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) { + Common::String name = i->getDisplayName(); + if (i->isDirectory()) + name += "/"; + + Common::String filePath = i->getPath(); + if (filePath.hasPrefix(prefixToRemove)) + filePath.erase(0, prefixToRemove.size()); + filePath = prefixToAdd + filePath; + + addItem(content, itemTemplate, detectType(i->isDirectory(), name), filePath, name); + } + + return true; +} + +FilesPageHandler::ItemType FilesPageHandler::detectType(bool isDirectory, const Common::String &name) { + if (isDirectory) + return IT_DIRECTORY; + if (name.hasSuffix(".txt")) + return IT_TXT; + if (name.hasSuffix(".zip")) + return IT_ZIP; + if (name.hasSuffix(".7z")) + return IT_7Z; + return IT_UNKNOWN; +} + +void FilesPageHandler::addItem(Common::String &content, const Common::String &itemTemplate, ItemType itemType, Common::String path, Common::String name, Common::String size) const { + Common::String item = itemTemplate, icon; + bool isDirectory = (itemType == IT_DIRECTORY || itemType == IT_PARENT_DIRECTORY); + switch (itemType) { + case IT_DIRECTORY: + icon = "dir.png"; + break; + case IT_PARENT_DIRECTORY: + icon = "up.png"; + break; + case IT_TXT: + icon = "txt.png"; + break; + case IT_ZIP: + icon = "zip.png"; + break; + case IT_7Z: + icon = "7z.png"; + break; + default: + icon = "unk.png"; + } + replace(item, "{icon}", icon); + replace(item, "{link}", (isDirectory ? "files?path=" : "download?path=") + LocalWebserver::urlEncodeQueryParameterValue(path)); + replace(item, "{name}", encodeHtmlEntities(name)); + replace(item, "{size}", size); + content += item; +} + +/// public + +void FilesPageHandler::handle(Client &client) { + Common::String response = + "<html>" \ + "<head><title>ScummVM</title></head>" \ + "<body>" \ + "<p>{create_directory_desc}</p>" \ + "<form action=\"create\">" \ + "<input type=\"hidden\" name=\"path\" value=\"{path}\"/>" \ + "<input type=\"text\" name=\"directory_name\" value=\"\"/>" \ + "<input type=\"submit\" value=\"{create_directory_button}\"/>" \ + "</form>" \ + "<hr/>" \ + "<p>{upload_file_desc}</p>" \ + "<form action=\"upload?path={path}\" method=\"post\" enctype=\"multipart/form-data\">" \ + "<input type=\"file\" name=\"upload_file-f\" allowdirs multiple/>" \ + "<span>{or_upload_directory_desc}</span>" \ + "<input type=\"file\" name=\"upload_file-d\" directory webkitdirectory multiple/>" \ + "<input type=\"submit\" value=\"{upload_file_button}\"/>" \ + "</form>" + "<hr/>" \ + "<h1>{index_of_directory}</h1>" \ + "<table>{content}</table>" \ + "</body>" \ + "</html>"; + Common::String itemTemplate = "<tr><td><img src=\"icons/{icon}\"/></td><td><a href=\"{link}\">{name}</a></td><td>{size}</td></tr>\n"; //TODO: load this template too? + + // load stylish response page from the archive + Common::SeekableReadStream *const stream = HandlerUtils::getArchiveFile(FILES_PAGE_NAME); + if (stream) + response = HandlerUtils::readEverythingFromStream(stream); + + Common::String path = client.queryParameter("path"); + Common::String content = ""; + + // show an error message if failed to list directory + if (!listDirectory(path, content, itemTemplate)) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("ScummVM couldn't list the directory you specified.")); + return; + } + + //these occur twice: + replace(response, "{create_directory_button}", _("Create directory")); + replace(response, "{create_directory_button}", _("Create directory")); + replace(response, "{path}", encodeDoubleQuotes(client.queryParameter("path"))); + replace(response, "{path}", encodeDoubleQuotes(client.queryParameter("path"))); + replace(response, "{upload_files_button}", _("Upload files")); //tab + replace(response, "{upload_file_button}", _("Upload files")); //button in the tab + replace(response, "{create_directory_desc}", _("Type new directory name:")); + replace(response, "{upload_file_desc}", _("Select a file to upload:")); + replace(response, "{or_upload_directory_desc}", _("Or select a directory (works in Chrome only):")); + replace(response, "{index_of_directory}", Common::String::format(_("Index of %s"), encodeHtmlEntities(getDisplayPath(client.queryParameter("path"))).c_str())); + replace(response, "{content}", content); + LocalWebserver::setClientGetHandler(client, response); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/filespagehandler.h b/backends/networking/sdl_net/handlers/filespagehandler.h new file mode 100644 index 0000000000..e404036cb6 --- /dev/null +++ b/backends/networking/sdl_net/handlers/filespagehandler.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_FILESPAGEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_FILESPAGEHANDLER_H + +#include "backends/networking/sdl_net/handlers/filesbasehandler.h" + +namespace Networking { + +class FilesPageHandler: public FilesBaseHandler { + enum ItemType { + IT_DIRECTORY, + IT_PARENT_DIRECTORY, + IT_TXT, + IT_ZIP, + IT_7Z, + IT_UNKNOWN + }; + + /** + * Lists the directory <path>. + * + * Returns true on success. + */ + bool listDirectory(Common::String path, Common::String &content, const Common::String &itemTemplate); + + /** Helper method for detecting items' type. */ + static ItemType detectType(bool isDirectory, const Common::String &name); + + /** Helper method for adding items into the files list. */ + void addItem(Common::String &content, const Common::String &itemTemplate, ItemType itemType, Common::String path, Common::String name, Common::String size = "") const; + +public: + FilesPageHandler(); + virtual ~FilesPageHandler(); + + virtual void handle(Client &client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/indexpagehandler.cpp b/backends/networking/sdl_net/handlers/indexpagehandler.cpp new file mode 100644 index 0000000000..985bd6635e --- /dev/null +++ b/backends/networking/sdl_net/handlers/indexpagehandler.cpp @@ -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 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 "backends/networking/sdl_net/handlers/indexpagehandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/translation.h" +#include "gui/storagewizarddialog.h" + +namespace Networking { + +IndexPageHandler::IndexPageHandler(): CommandSender(nullptr) {} + +IndexPageHandler::~IndexPageHandler() {} + +/// public + +Common::String IndexPageHandler::code() const { return _code; } + +void IndexPageHandler::handle(Client &client) { + Common::String code = client.queryParameter("code"); + + if (code == "") { + // redirect to "/filesAJAX" + HandlerUtils::setMessageHandler( + client, + Common::String::format( + "%s<br/><a href=\"files\">%s</a>", + _("This is a local webserver index page."), + _("Open Files manager") + ), + "/filesAJAX" + ); + return; + } + + _code = code; + sendCommand(GUI::kStorageCodePassedCmd, 0); + HandlerUtils::setMessageHandler(client, _("ScummVM got the code and already connects to your cloud storage!")); +} + +bool IndexPageHandler::minimalModeSupported() { + return true; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/indexpagehandler.h b/backends/networking/sdl_net/handlers/indexpagehandler.h new file mode 100644 index 0000000000..8065954b27 --- /dev/null +++ b/backends/networking/sdl_net/handlers/indexpagehandler.h @@ -0,0 +1,45 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_INDEXPAGEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_INDEXPAGEHANDLER_H + +#include "backends/networking/sdl_net/handlers/basehandler.h" +#include "gui/object.h" + +namespace Networking { +class LocalWebserver; + +class IndexPageHandler: public BaseHandler, public GUI::CommandSender { + Common::String _code; +public: + IndexPageHandler(); + virtual ~IndexPageHandler(); + + Common::String code() const; + virtual void handle(Client &client); + virtual bool minimalModeSupported(); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/listajaxhandler.cpp b/backends/networking/sdl_net/handlers/listajaxhandler.cpp new file mode 100644 index 0000000000..f94b674a3c --- /dev/null +++ b/backends/networking/sdl_net/handlers/listajaxhandler.cpp @@ -0,0 +1,157 @@ +/* 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 "backends/networking/sdl_net/handlers/listajaxhandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/config-manager.h" +#include "common/json.h" +#include "common/translation.h" + +namespace Networking { + +ListAjaxHandler::ListAjaxHandler() {} + +ListAjaxHandler::~ListAjaxHandler() {} + +Common::JSONObject ListAjaxHandler::listDirectory(Common::String path) { + Common::JSONArray itemsList; + Common::JSONObject errorResult; + Common::JSONObject successResult; + successResult.setVal("type", new Common::JSONValue("success")); + errorResult.setVal("type", new Common::JSONValue("error")); + + if (path == "" || path == "/") { + if (ConfMan.hasKey("rootpath", "cloud")) + addItem(itemsList, IT_DIRECTORY, "/root/", _("File system root")); + addItem(itemsList, IT_DIRECTORY, "/saves/", _("Saved games")); + successResult.setVal("items", new Common::JSONValue(itemsList)); + return successResult; + } + + if (HandlerUtils::hasForbiddenCombinations(path)) + return errorResult; + + Common::String prefixToRemove = "", prefixToAdd = ""; + if (!transformPath(path, prefixToRemove, prefixToAdd)) + return errorResult; + + Common::FSNode node = Common::FSNode(path); + if (path == "/") + node = node.getParent(); // absolute root + + if (!HandlerUtils::permittedPath(node.getPath())) + return errorResult; + + if (!node.isDirectory()) + return errorResult; + + // list directory + Common::FSList _nodeContent; + if (!node.getChildren(_nodeContent, Common::FSNode::kListAll, false)) // do not show hidden files + _nodeContent.clear(); + else + Common::sort(_nodeContent.begin(), _nodeContent.end()); + + // add parent directory link + { + Common::String filePath = path; + if (filePath.hasPrefix(prefixToRemove)) + filePath.erase(0, prefixToRemove.size()); + if (filePath == "" || filePath == "/" || filePath == "\\") + filePath = "/"; + else + filePath = parentPath(prefixToAdd + filePath); + addItem(itemsList, IT_PARENT_DIRECTORY, filePath, _("Parent directory")); + } + + // fill the content + for (Common::FSList::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) { + Common::String name = i->getDisplayName(); + if (i->isDirectory()) name += "/"; + + Common::String filePath = i->getPath(); + if (filePath.hasPrefix(prefixToRemove)) + filePath.erase(0, prefixToRemove.size()); + filePath = prefixToAdd + filePath; + + addItem(itemsList, detectType(i->isDirectory(), name), filePath, name); + } + + successResult.setVal("items", new Common::JSONValue(itemsList)); + return successResult; +} + +ListAjaxHandler::ItemType ListAjaxHandler::detectType(bool isDirectory, const Common::String &name) { + if (isDirectory) + return IT_DIRECTORY; + if (name.hasSuffix(".txt")) + return IT_TXT; + if (name.hasSuffix(".zip")) + return IT_ZIP; + if (name.hasSuffix(".7z")) + return IT_7Z; + return IT_UNKNOWN; +} + +void ListAjaxHandler::addItem(Common::JSONArray &responseItemsList, ItemType itemType, Common::String path, Common::String name, Common::String size) { + Common::String icon; + bool isDirectory = (itemType == IT_DIRECTORY || itemType == IT_PARENT_DIRECTORY); + switch (itemType) { + case IT_DIRECTORY: + icon = "dir.png"; + break; + case IT_PARENT_DIRECTORY: + icon = "up.png"; + break; + case IT_TXT: + icon = "txt.png"; + break; + case IT_ZIP: + icon = "zip.png"; + break; + case IT_7Z: + icon = "7z.png"; + break; + default: + icon = "unk.png"; + } + + Common::JSONObject item; + item.setVal("name", new Common::JSONValue(name)); + item.setVal("path", new Common::JSONValue(path)); + item.setVal("isDirectory", new Common::JSONValue(isDirectory)); + item.setVal("size", new Common::JSONValue(size)); + item.setVal("icon", new Common::JSONValue(icon)); + responseItemsList.push_back(new Common::JSONValue(item)); +} + +/// public + +void ListAjaxHandler::handle(Client &client) { + Common::String path = client.queryParameter("path"); + Common::JSONValue jsonResponse = listDirectory(path); + Common::String response = jsonResponse.stringify(true); + LocalWebserver::setClientGetHandler(client, response); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/listajaxhandler.h b/backends/networking/sdl_net/handlers/listajaxhandler.h new file mode 100644 index 0000000000..40840ad6c3 --- /dev/null +++ b/backends/networking/sdl_net/handlers/listajaxhandler.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_LISTAJAXHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_LISTAJAXHANDLER_H + +#include "backends/networking/sdl_net/handlers/filesbasehandler.h" +#include "common/json.h" + +namespace Networking { + +class ListAjaxHandler: public FilesBaseHandler { + enum ItemType { + IT_DIRECTORY, + IT_PARENT_DIRECTORY, + IT_TXT, + IT_ZIP, + IT_7Z, + IT_UNKNOWN + }; + + /** + * Lists the directory <path>. + * + * Returns JSON with either listed directory or error response. + */ + Common::JSONObject listDirectory(Common::String path); + + /** Helper method for detecting items' type. */ + static ItemType detectType(bool isDirectory, const Common::String &name); + + /** Helper method for adding items into the files list. */ + static void addItem(Common::JSONArray &responseItemsList, ItemType itemType, Common::String path, Common::String name, Common::String size = ""); + +public: + ListAjaxHandler(); + virtual ~ListAjaxHandler(); + + virtual void handle(Client &client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/resourcehandler.cpp b/backends/networking/sdl_net/handlers/resourcehandler.cpp new file mode 100644 index 0000000000..631eb63351 --- /dev/null +++ b/backends/networking/sdl_net/handlers/resourcehandler.cpp @@ -0,0 +1,75 @@ +/* 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 "backends/networking/sdl_net/handlers/resourcehandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" + +namespace Networking { + +ResourceHandler::ResourceHandler() {} + +ResourceHandler::~ResourceHandler() {} + +const char *ResourceHandler::determineMimeType(Common::String &filename) { + // text + if (filename.hasSuffix(".html")) return "text/html"; + if (filename.hasSuffix(".css")) return "text/css"; + if (filename.hasSuffix(".txt")) return "text/plain"; + if (filename.hasSuffix(".js")) return "application/javascript"; + + // images + if (filename.hasSuffix(".jpeg") || filename.hasSuffix(".jpg") || filename.hasSuffix(".jpe")) return "image/jpeg"; + if (filename.hasSuffix(".gif")) return "image/gif"; + if (filename.hasSuffix(".png")) return "image/png"; + if (filename.hasSuffix(".svg")) return "image/svg+xml"; + if (filename.hasSuffix(".tiff")) return "image/tiff"; + if (filename.hasSuffix(".ico")) return "image/vnd.microsoft.icon"; + if (filename.hasSuffix(".wbmp")) return "image/vnd.wap.wbmp"; + + if (filename.hasSuffix(".zip")) return "application/zip"; + return "application/octet-stream"; +} + +/// public + +void ResourceHandler::handle(Client &client) { + Common::String filename = client.path(); + filename.deleteChar(0); + + // if archive hidden file is requested, ignore + if (filename.size() && filename[0] == '.') + return; + + // if file not found, don't set handler either + Common::SeekableReadStream *file = HandlerUtils::getArchiveFile(filename); + if (file == nullptr) + return; + + LocalWebserver::setClientGetHandler(client, file, 200, determineMimeType(filename)); +} + +bool ResourceHandler::minimalModeSupported() { + return true; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/resourcehandler.h b/backends/networking/sdl_net/handlers/resourcehandler.h new file mode 100644 index 0000000000..2ec4c5bb19 --- /dev/null +++ b/backends/networking/sdl_net/handlers/resourcehandler.h @@ -0,0 +1,42 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_RESOURCEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_RESOURCEHANDLER_H + +#include "backends/networking/sdl_net/handlers/basehandler.h" + +namespace Networking { + +class ResourceHandler: public BaseHandler { + static const char *determineMimeType(Common::String &filename); +public: + ResourceHandler(); + virtual ~ResourceHandler(); + + virtual void handle(Client &client); + virtual bool minimalModeSupported(); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlers/uploadfilehandler.cpp b/backends/networking/sdl_net/handlers/uploadfilehandler.cpp new file mode 100644 index 0000000000..a0e992c25e --- /dev/null +++ b/backends/networking/sdl_net/handlers/uploadfilehandler.cpp @@ -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 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 "backends/networking/sdl_net/handlers/uploadfilehandler.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/uploadfileclienthandler.h" +#include "backends/fs/fs-factory.h" +#include "common/system.h" +#include "common/translation.h" + +namespace Networking { + +UploadFileHandler::UploadFileHandler() {} + +UploadFileHandler::~UploadFileHandler() {} + +/// public + +void UploadFileHandler::handle(Client &client) { + Common::String path = client.queryParameter("path"); + + // check that <path> is not an absolute root + if (path == "" || path == "/" || path == "\\") { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + + // check that <path> contains no '../' + if (HandlerUtils::hasForbiddenCombinations(path)) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + + // transform virtual path to actual file system one + Common::String prefixToRemove = "", prefixToAdd = ""; + if (!transformPath(path, prefixToRemove, prefixToAdd, false) || path.empty()) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + + // check that <path> exists, is directory and isn't forbidden + AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path); + if (!HandlerUtils::permittedPath(node->getPath())) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!")); + return; + } + if (!node->exists()) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The parent directory doesn't exist!")); + return; + } + if (!node->isDirectory()) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Can't upload into a file!")); + return; + } + + // if all OK, set special handler + client.setHandler(new UploadFileClientHandler(path)); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlers/uploadfilehandler.h b/backends/networking/sdl_net/handlers/uploadfilehandler.h new file mode 100644 index 0000000000..cbff215156 --- /dev/null +++ b/backends/networking/sdl_net/handlers/uploadfilehandler.h @@ -0,0 +1,40 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_UPLOADFILEHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_UPLOADFILEHANDLER_H + +#include "backends/networking/sdl_net/handlers/filesbasehandler.h" + +namespace Networking { + +class UploadFileHandler: public FilesBaseHandler { +public: + UploadFileHandler(); + virtual ~UploadFileHandler(); + + virtual void handle(Client &client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/handlerutils.cpp b/backends/networking/sdl_net/handlerutils.cpp new file mode 100644 index 0000000000..fba00aef59 --- /dev/null +++ b/backends/networking/sdl_net/handlerutils.cpp @@ -0,0 +1,204 @@ +/* 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 "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "backends/saves/default/default-saves.h" +#include "common/archive.h" +#include "common/config-manager.h" +#include "common/file.h" +#include "common/translation.h" +#include "common/unzip.h" + +namespace Networking { + +#define ARCHIVE_NAME "wwwroot.zip" + +#define INDEX_PAGE_NAME ".index.html" + +Common::Archive *HandlerUtils::getZipArchive() { + // first search in themepath + if (ConfMan.hasKey("themepath")) { + const Common::FSNode &node = Common::FSNode(ConfMan.get("themepath")); + if (!node.exists() || !node.isReadable() || !node.isDirectory()) + return nullptr; + + Common::FSNode fileNode = node.getChild(ARCHIVE_NAME); + if (fileNode.exists() && fileNode.isReadable() && !fileNode.isDirectory()) { + Common::SeekableReadStream *const stream = fileNode.createReadStream(); + Common::Archive *zipArchive = Common::makeZipArchive(stream); + if (zipArchive) + return zipArchive; + } + } + + // then use SearchMan to find it + Common::ArchiveMemberList fileList; + SearchMan.listMatchingMembers(fileList, ARCHIVE_NAME); + for (Common::ArchiveMemberList::iterator it = fileList.begin(); it != fileList.end(); ++it) { + Common::ArchiveMember const &m = **it; + Common::SeekableReadStream *const stream = m.createReadStream(); + Common::Archive *zipArchive = Common::makeZipArchive(stream); + if (zipArchive) + return zipArchive; + } + + return nullptr; +} + +Common::ArchiveMemberList HandlerUtils::listArchive() { + Common::ArchiveMemberList resultList; + Common::Archive *zipArchive = getZipArchive(); + if (zipArchive) { + zipArchive->listMembers(resultList); + delete zipArchive; + } + return resultList; +} + +Common::SeekableReadStream *HandlerUtils::getArchiveFile(Common::String name) { + Common::SeekableReadStream *result = nullptr; + Common::Archive *zipArchive = getZipArchive(); + if (zipArchive) { + const Common::ArchiveMemberPtr ptr = zipArchive->getMember(name); + if (ptr.get() == nullptr) + return nullptr; + result = ptr->createReadStream(); + delete zipArchive; + } + return result; +} + +Common::String HandlerUtils::readEverythingFromStream(Common::SeekableReadStream *const stream) { + Common::String result; + char buf[1024]; + uint32 readBytes; + while (!stream->eos()) { + readBytes = stream->read(buf, 1024); + result += Common::String(buf, readBytes); + } + return result; +} + +Common::String HandlerUtils::normalizePath(const Common::String &path) { + Common::String normalized; + bool slash = false; + for (uint32 i = 0; i < path.size(); ++i) { + char c = path[i]; + if (c == '\\' || c == '/') { + slash = true; + continue; + } + + if (slash) { + normalized += '/'; + slash = false; + } + + if ('A' <= c && c <= 'Z') { + normalized += c - 'A' + 'a'; + } else { + normalized += c; + } + } + if (slash) normalized += '/'; + return normalized; +} + +bool HandlerUtils::hasForbiddenCombinations(const Common::String &path) { + return (path.contains("/../") || path.contains("\\..\\") || path.contains("\\../") || path.contains("/..\\")); +} + +bool HandlerUtils::isBlacklisted(const Common::String &path) { + const char *blacklist[] = { + "/etc", + "/bin", + "c:/windows" // just saying: I know guys who install windows on another drives + }; + + // normalize path + Common::String normalized = normalizePath(path); + + uint32 size = sizeof(blacklist) / sizeof(const char *); + for (uint32 i = 0; i < size; ++i) + if (normalized.hasPrefix(blacklist[i])) + return true; + + return false; +} + +bool HandlerUtils::hasPermittedPrefix(const Common::String &path) { + // normalize path + Common::String normalized = normalizePath(path); + + // prefix for /root/ + Common::String prefix; + if (ConfMan.hasKey("rootpath", "cloud")) { + prefix = normalizePath(ConfMan.get("rootpath", "cloud")); + if (prefix == "/" || normalized.hasPrefix(prefix)) + return true; + } + + // prefix for /saves/ +#ifdef USE_LIBCURL + DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager()); + prefix = (manager ? manager->concatWithSavesPath("") : ConfMan.get("savepath")); +#else + prefix = ConfMan.get("savepath"); +#endif + return (normalized.hasPrefix(normalizePath(prefix))); +} + +bool HandlerUtils::permittedPath(const Common::String path) { + return hasPermittedPrefix(path) && !isBlacklisted(path); +} + +void HandlerUtils::setMessageHandler(Client &client, Common::String message, Common::String redirectTo) { + Common::String response = "<html><head><title>ScummVM</title></head><body>{message}</body></html>"; + + // load stylish response page from the archive + Common::SeekableReadStream *const stream = getArchiveFile(INDEX_PAGE_NAME); + if (stream) + response = readEverythingFromStream(stream); + + replace(response, "{message}", message); + if (redirectTo.empty()) + LocalWebserver::setClientGetHandler(client, response); + else + LocalWebserver::setClientRedirectHandler(client, response, redirectTo); +} + +void HandlerUtils::setFilesManagerErrorMessageHandler(Client &client, Common::String message, Common::String redirectTo) { + setMessageHandler( + client, + Common::String::format( + "%s<br/><a href=\"files%s?path=%s\">%s</a>", + message.c_str(), + client.queryParameter("ajax") == "true" ? "AJAX" : "", + "%2F", //that's encoded "/" + _("Back to the files manager") + ), + redirectTo + ); +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/handlerutils.h b/backends/networking/sdl_net/handlerutils.h new file mode 100644 index 0000000000..4c2eff49b6 --- /dev/null +++ b/backends/networking/sdl_net/handlerutils.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_HANDLERUTILS_H +#define BACKENDS_NETWORKING_SDL_NET_HANDLERUTILS_H + +#include "backends/networking/sdl_net/client.h" +#include "common/archive.h" + +namespace Networking { + +class HandlerUtils { +public: + static Common::Archive *getZipArchive(); + static Common::ArchiveMemberList listArchive(); + static Common::SeekableReadStream *getArchiveFile(Common::String name); + static Common::String readEverythingFromStream(Common::SeekableReadStream *const stream); + + static Common::String normalizePath(const Common::String &path); + static bool hasForbiddenCombinations(const Common::String &path); + static bool isBlacklisted(const Common::String &path); + static bool hasPermittedPrefix(const Common::String &path); + static bool permittedPath(const Common::String path); + + static void setMessageHandler(Client &client, Common::String message, Common::String redirectTo = ""); + static void setFilesManagerErrorMessageHandler(Client &client, Common::String message, Common::String redirectTo = ""); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/localwebserver.cpp b/backends/networking/sdl_net/localwebserver.cpp new file mode 100644 index 0000000000..6557c7a88d --- /dev/null +++ b/backends/networking/sdl_net/localwebserver.cpp @@ -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 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. +* +*/ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/networking/sdl_net/localwebserver.h" +#include "backends/networking/sdl_net/getclienthandler.h" +#include "common/memstream.h" +#include "common/str.h" +#include "common/system.h" +#include "common/timer.h" +#include "common/translation.h" +#include <SDL/SDL_net.h> +#include <common/config-manager.h> + +#ifdef POSIX +#include <sys/types.h> +#include <ifaddrs.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif + +namespace Common { +class MemoryReadWriteStream; + +DECLARE_SINGLETON(Networking::LocalWebserver); + +} + +namespace Networking { + +LocalWebserver::LocalWebserver(): _set(nullptr), _serverSocket(nullptr), _timerStarted(false), + _stopOnIdle(false), _minimalMode(false), _clients(0), _idlingFrames(0), _serverPort(DEFAULT_SERVER_PORT) { + addPathHandler("/", &_indexPageHandler); + addPathHandler("/files", &_filesPageHandler); + addPathHandler("/create", &_createDirectoryHandler); + addPathHandler("/download", &_downloadFileHandler); + addPathHandler("/upload", &_uploadFileHandler); + addPathHandler("/list", &_listAjaxHandler); + addPathHandler("/filesAJAX", &_filesAjaxPageHandler); + _defaultHandler = &_resourceHandler; +} + +LocalWebserver::~LocalWebserver() { + stop(); +} + +void localWebserverTimer(void *ignored) { + LocalServer.handle(); +} + +void LocalWebserver::startTimer(int interval) { + Common::TimerManager *manager = g_system->getTimerManager(); + if (manager->installTimerProc(localWebserverTimer, interval, 0, "Networking::LocalWebserver's Timer")) { + _timerStarted = true; + } else { + warning("Failed to install Networking::LocalWebserver's timer"); + } +} + +void LocalWebserver::stopTimer() { + Common::TimerManager *manager = g_system->getTimerManager(); + manager->removeTimerProc(localWebserverTimer); + _timerStarted = false; +} + +void LocalWebserver::start(bool useMinimalMode) { + _handleMutex.lock(); + _serverPort = getPort(); + _stopOnIdle = false; + if (_timerStarted) { + _handleMutex.unlock(); + return; + } + _minimalMode = useMinimalMode; + startTimer(); + + // Create a listening TCP socket + IPaddress ip; + if (SDLNet_ResolveHost(&ip, NULL, _serverPort) == -1) { + error("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError()); + } + + resolveAddress(&ip); + + _serverSocket = SDLNet_TCP_Open(&ip); + if (!_serverSocket) { + warning("LocalWebserver: SDLNet_TCP_Open: %s", SDLNet_GetError()); + stopTimer(); + g_system->displayMessageOnOSD(_("Failed to start local webserver.\nCheck whether selected port is not used by another application and try again.")); + _handleMutex.unlock(); + return; + } + + // Create a socket set + _set = SDLNet_AllocSocketSet(MAX_CONNECTIONS + 1); //one more for our server socket + if (!_set) { + error("LocalWebserver: SDLNet_AllocSocketSet: %s\n", SDLNet_GetError()); + } + + int numused = SDLNet_TCP_AddSocket(_set, _serverSocket); + if (numused == -1) { + error("LocalWebserver: SDLNet_AddSocket: %s\n", SDLNet_GetError()); + } + _handleMutex.unlock(); +} + +void LocalWebserver::stop() { + _handleMutex.lock(); + if (_timerStarted) + stopTimer(); + + if (_serverSocket) { + SDLNet_TCP_Close(_serverSocket); + _serverSocket = nullptr; + } + + for (uint32 i = 0; i < MAX_CONNECTIONS; ++i) + _client[i].close(); + + _clients = 0; + + if (_set) { + SDLNet_FreeSocketSet(_set); + _set = nullptr; + } + _handleMutex.unlock(); +} + +void LocalWebserver::stopOnIdle() { _stopOnIdle = true; } + +void LocalWebserver::addPathHandler(Common::String path, BaseHandler *handler) { + if (_pathHandlers.contains(path)) + warning("LocalWebserver::addPathHandler: path already had a handler"); + _pathHandlers[path] = handler; +} + +Common::String LocalWebserver::getAddress() { return _address; } + +IndexPageHandler &LocalWebserver::indexPageHandler() { return _indexPageHandler; } + +bool LocalWebserver::isRunning() { + bool result = false; + _handleMutex.lock(); + result = _timerStarted; + _handleMutex.unlock(); + return result; +} + +uint32 LocalWebserver::getPort() { +#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE + if (ConfMan.hasKey("local_server_port")) + return ConfMan.getInt("local_server_port"); +#endif + return DEFAULT_SERVER_PORT; +} + +void LocalWebserver::handle() { + _handleMutex.lock(); + int numready = SDLNet_CheckSockets(_set, 0); + if (numready == -1) { + error("LocalWebserver: SDLNet_CheckSockets: %s\n", SDLNet_GetError()); + } else if (numready) { + acceptClient(); + } + + for (uint32 i = 0; i < MAX_CONNECTIONS; ++i) + handleClient(i); + + _clients = 0; + for (uint32 i = 0; i < MAX_CONNECTIONS; ++i) + if (_client[i].state() != INVALID) + ++_clients; + + if (_clients == 0) + ++_idlingFrames; + else + _idlingFrames = 0; + + if (_idlingFrames > FRAMES_PER_SECOND && _stopOnIdle) { + _handleMutex.unlock(); + stop(); + return; + } + + _handleMutex.unlock(); +} + +void LocalWebserver::handleClient(uint32 i) { + switch (_client[i].state()) { + case INVALID: + return; + case READING_HEADERS: + _client[i].readHeaders(); + break; + case READ_HEADERS: { + // decide what to do next with that client + // check whether we know a handler for such URL + BaseHandler *handler = nullptr; + if (_pathHandlers.contains(_client[i].path())) { + handler = _pathHandlers[_client[i].path()]; + } else { + // try default handler + handler = _defaultHandler; + } + + // if server's in "minimal mode", only handlers which support it are used + if (handler && (!_minimalMode || handler->minimalModeSupported())) + handler->handle(_client[i]); + + if (_client[i].state() == BEING_HANDLED || _client[i].state() == INVALID) + break; + + // if no handler, answer with default BAD REQUEST + // fallthrough + } + + case BAD_REQUEST: + setClientGetHandler(_client[i], "<html><head><title>ScummVM - Bad Request</title></head><body>BAD REQUEST</body></html>", 400); + break; + case BEING_HANDLED: + _client[i].handle(); + break; + } +} + +void LocalWebserver::acceptClient() { + if (!SDLNet_SocketReady(_serverSocket)) + return; + + TCPsocket client = SDLNet_TCP_Accept(_serverSocket); + if (!client) + return; + + if (_clients == MAX_CONNECTIONS) { //drop the connection + SDLNet_TCP_Close(client); + return; + } + + ++_clients; + for (uint32 i = 0; i < MAX_CONNECTIONS; ++i) + if (_client[i].state() == INVALID) { + _client[i].open(_set, client); + break; + } +} + +void LocalWebserver::resolveAddress(void *ipAddress) { + IPaddress *ip = (IPaddress *)ipAddress; + + // not resolved + _address = Common::String::format("http://127.0.0.1:%u/ (unresolved)", _serverPort); + + // default way (might work everywhere, surely works on Windows) + const char *name = SDLNet_ResolveIP(ip); + if (name == NULL) { + warning("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError()); + } else { + IPaddress localIp; + if (SDLNet_ResolveHost(&localIp, name, _serverPort) == -1) { + warning("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError()); + } else { + _address = Common::String::format( + "http://%u.%u.%u.%u:%u/", + localIp.host & 0xFF, (localIp.host >> 8) & 0xFF, (localIp.host >> 16) & 0xFF, (localIp.host >> 24) & 0xFF, + _serverPort + ); + } + } + + // check that our trick worked + if (_address.contains("/127.0.0.1:") || _address.contains("localhost") || _address.contains("/0.0.0.0:")) + warning("LocalWebserver: Failed to resolve IP with the default way"); + else + return; + + // if not - try platform-specific +#ifdef POSIX + struct ifaddrs *ifAddrStruct = NULL; + void *tmpAddrPtr = NULL; + + getifaddrs(&ifAddrStruct); + + for (struct ifaddrs *i = ifAddrStruct; i != NULL; i = i->ifa_next) { + if (!i->ifa_addr) { + continue; + } + + Common::String addr; + + // IPv4 + if (i->ifa_addr->sa_family == AF_INET) { + tmpAddrPtr = &((struct sockaddr_in *)i->ifa_addr)->sin_addr; + char addressBuffer[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN); + debug(9, "%s IP Address %s", i->ifa_name, addressBuffer); + addr = addressBuffer; + } + + // IPv6 + /* + if (i->ifa_addr->sa_family == AF_INET6) { + tmpAddrPtr = &((struct sockaddr_in6 *)i->ifa_addr)->sin6_addr; + char addressBuffer[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN); + debug(9, "%s IP Address %s", i->ifa_name, addressBuffer); + addr = addressBuffer; + } + */ + + if (addr.empty()) + continue; + + // ignored IPv4 addresses + if (addr.equals("127.0.0.1") || addr.equals("0.0.0.0") || addr.equals("localhost")) + continue; + + // ignored IPv6 addresses + /* + if (addr.equals("::1")) + continue; + */ + + // use the address found + _address = "http://" + addr + Common::String::format(":%u/", _serverPort); + } + + if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct); +#endif +} + +void LocalWebserver::setClientGetHandler(Client &client, Common::String response, long code, const char *mimeType) { + byte *data = new byte[response.size()]; + memcpy(data, response.c_str(), response.size()); + Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES); + setClientGetHandler(client, stream, code, mimeType); +} + +void LocalWebserver::setClientGetHandler(Client &client, Common::SeekableReadStream *responseStream, long code, const char *mimeType) { + GetClientHandler *handler = new GetClientHandler(responseStream); + handler->setResponseCode(code); + if (mimeType) + handler->setHeader("Content-Type", mimeType); + client.setHandler(handler); +} + +void LocalWebserver::setClientRedirectHandler(Client &client, Common::String response, Common::String location, const char *mimeType) { + byte *data = new byte[response.size()]; + memcpy(data, response.c_str(), response.size()); + Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES); + setClientRedirectHandler(client, stream, location, mimeType); +} + +void LocalWebserver::setClientRedirectHandler(Client &client, Common::SeekableReadStream *responseStream, Common::String location, const char *mimeType) { + GetClientHandler *handler = new GetClientHandler(responseStream); + handler->setResponseCode(302); //redirect + handler->setHeader("Location", location); + if (mimeType) + handler->setHeader("Content-Type", mimeType); + client.setHandler(handler); +} + +namespace { +int hexDigit(char c) { + if ('0' <= c && c <= '9') return c - '0'; + if ('A' <= c && c <= 'F') return c - 'A' + 10; + if ('a' <= c && c <= 'f') return c - 'a' + 10; + return -1; +} +} + +Common::String LocalWebserver::urlDecode(Common::String value) { + Common::String result = ""; + uint32 size = value.size(); + for (uint32 i = 0; i < size; ++i) { + if (value[i] == '+') { + result += ' '; + continue; + } + + if (value[i] == '%' && i + 2 < size) { + int d1 = hexDigit(value[i + 1]); + int d2 = hexDigit(value[i + 2]); + if (0 <= d1 && d1 < 16 && 0 <= d2 && d2 < 16) { + result += (char)(d1 * 16 + d2); + i = i + 2; + continue; + } + } + + result += value[i]; + } + return result; +} + +namespace { +bool isQueryUnreserved(char c) { + return ( + ('0' <= c && c <= '9') || + ('A' <= c && c <= 'Z') || + ('a' <= c && c <= 'z') || + c == '-' || c == '_' || c == '.' || c == '!' || + c == '~' || c == '*' || c == '\'' || c == '(' || c == ')' + ); +} +} + +Common::String LocalWebserver::urlEncodeQueryParameterValue(Common::String value) { + //OK chars = alphanum | "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" + //reserved for query are ";", "/", "?", ":", "@", "&", "=", "+", "," + //that means these must be encoded too or otherwise they could malform the query + Common::String result = ""; + char hexChar[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + for (uint32 i = 0; i < value.size(); ++i) { + char c = value[i]; + if (isQueryUnreserved(c)) + result += c; + else { + result += '%'; + result += hexChar[(c >> 4) & 0xF]; + result += hexChar[c & 0xF]; + } + } + return result; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/localwebserver.h b/backends/networking/sdl_net/localwebserver.h new file mode 100644 index 0000000000..c6cf8485c3 --- /dev/null +++ b/backends/networking/sdl_net/localwebserver.h @@ -0,0 +1,115 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_LOCALWEBSERVER_H +#define BACKENDS_NETWORKING_SDL_NET_LOCALWEBSERVER_H + +#include "backends/networking/sdl_net/client.h" +#include "backends/networking/sdl_net/handlers/basehandler.h" +#include "backends/networking/sdl_net/handlers/createdirectoryhandler.h" +#include "backends/networking/sdl_net/handlers/downloadfilehandler.h" +#include "backends/networking/sdl_net/handlers/filesajaxpagehandler.h" +#include "backends/networking/sdl_net/handlers/filespagehandler.h" +#include "backends/networking/sdl_net/handlers/indexpagehandler.h" +#include "backends/networking/sdl_net/handlers/listajaxhandler.h" +#include "backends/networking/sdl_net/handlers/resourcehandler.h" +#include "backends/networking/sdl_net/handlers/uploadfilehandler.h" +#include "common/hash-str.h" +#include "common/mutex.h" +#include "common/singleton.h" +#include "common/scummsys.h" + +namespace Common { +class SeekableReadStream; +} + +typedef struct _SDLNet_SocketSet *SDLNet_SocketSet; +typedef struct _TCPsocket *TCPsocket; + +namespace Networking { + +#define NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE + +class LocalWebserver : public Common::Singleton<LocalWebserver> { + static const uint32 FRAMES_PER_SECOND = 20; + static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND; + static const uint32 MAX_CONNECTIONS = 10; + + friend void localWebserverTimer(void *); //calls handle() + + SDLNet_SocketSet _set; + TCPsocket _serverSocket; + Client _client[MAX_CONNECTIONS]; + int _clients; + bool _timerStarted, _stopOnIdle, _minimalMode; + Common::HashMap<Common::String, BaseHandler*> _pathHandlers; + BaseHandler *_defaultHandler; + IndexPageHandler _indexPageHandler; + FilesPageHandler _filesPageHandler; + CreateDirectoryHandler _createDirectoryHandler; + DownloadFileHandler _downloadFileHandler; + UploadFileHandler _uploadFileHandler; + ListAjaxHandler _listAjaxHandler; + FilesAjaxPageHandler _filesAjaxPageHandler; + ResourceHandler _resourceHandler; + uint32 _idlingFrames; + Common::Mutex _handleMutex; + Common::String _address; + uint32 _serverPort; + + void startTimer(int interval = TIMER_INTERVAL); + void stopTimer(); + void handle(); + void handleClient(uint32 i); + void acceptClient(); + void resolveAddress(void *ipAddress); + void addPathHandler(Common::String path, BaseHandler *handler); + +public: + static const uint32 DEFAULT_SERVER_PORT = 12345; + + LocalWebserver(); + virtual ~LocalWebserver(); + + void start(bool useMinimalMode = false); + void stop(); + void stopOnIdle(); + + Common::String getAddress(); + IndexPageHandler &indexPageHandler(); + bool isRunning(); + static uint32 getPort(); + + static void setClientGetHandler(Client &client, Common::String response, long code = 200, const char *mimeType = nullptr); + static void setClientGetHandler(Client &client, Common::SeekableReadStream *responseStream, long code = 200, const char *mimeType = nullptr); + static void setClientRedirectHandler(Client &client, Common::String response, Common::String location, const char *mimeType = nullptr); + static void setClientRedirectHandler(Client &client, Common::SeekableReadStream *responseStream, Common::String location, const char *mimeType = nullptr); + static Common::String urlDecode(Common::String value); + static Common::String urlEncodeQueryParameterValue(Common::String value); +}; + +/** Shortcut for accessing the local webserver. */ +#define LocalServer Networking::LocalWebserver::instance() + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/reader.cpp b/backends/networking/sdl_net/reader.cpp new file mode 100644 index 0000000000..8f3199f51c --- /dev/null +++ b/backends/networking/sdl_net/reader.cpp @@ -0,0 +1,462 @@ +/* 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 "backends/networking/sdl_net/reader.h" +#include "backends/fs/fs-factory.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/memstream.h" +#include "common/stream.h" + +namespace Networking { + +Reader::Reader() { + _state = RS_NONE; + _content = nullptr; + _bytesLeft = 0; + + _window = nullptr; + _windowUsed = 0; + _windowSize = 0; + + _headersStream = nullptr; + _firstBlock = true; + + _contentLength = 0; + _availableBytes = 0; + _isBadRequest = false; + _allContentRead = false; +} + +Reader::~Reader() { + cleanup(); +} + +Reader &Reader::operator=(Reader &r) { + if (this == &r) + return *this; + cleanup(); + + _state = r._state; + _content = r._content; + _bytesLeft = r._bytesLeft; + r._state = RS_NONE; + + _window = r._window; + _windowUsed = r._windowUsed; + _windowSize = r._windowSize; + r._window = nullptr; + + _headersStream = r._headersStream; + r._headersStream = nullptr; + + _headers = r._headers; + _method = r._method; + _path = r._path; + _query = r._query; + _anchor = r._anchor; + _queryParameters = r._queryParameters; + _contentLength = r._contentLength; + _boundary = r._boundary; + _availableBytes = r._availableBytes; + _firstBlock = r._firstBlock; + _isBadRequest = r._isBadRequest; + _allContentRead = r._allContentRead; + + return *this; +} + +void Reader::cleanup() { + //_content is not to be freed, it's not owned by Reader + + if (_headersStream != nullptr) + delete _headersStream; + + if (_window != nullptr) + freeWindow(); +} + +bool Reader::readAndHandleFirstHeaders() { + Common::String boundary = "\r\n\r\n"; + if (_window == nullptr) { + makeWindow(boundary.size()); + } + if (_headersStream == nullptr) { + _headersStream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES); + } + + while (readOneByteInStream(_headersStream, boundary)) { + if (_headersStream->size() > SUSPICIOUS_HEADERS_SIZE) { + _isBadRequest = true; + return true; + } + if (!bytesLeft()) + return false; + } + handleFirstHeaders(_headersStream); + + freeWindow(); + _state = RS_READING_CONTENT; + return true; +} + +bool Reader::readBlockHeadersIntoStream(Common::WriteStream *stream) { + Common::String boundary = "\r\n\r\n"; + if (_window == nullptr) makeWindow(boundary.size()); + + while (readOneByteInStream(stream, boundary)) { + if (!bytesLeft()) + return false; + } + if (stream) stream->flush(); + + freeWindow(); + _state = RS_READING_CONTENT; + return true; +} + +namespace { +void readFromThatUntilLineEnd(const char *cstr, Common::String needle, Common::String &result) { + const char *position = strstr(cstr, needle.c_str()); + + if (position) { + char c; + for (const char *i = position + needle.size(); c = *i, c != 0; ++i) { + if (c == '\n' || c == '\r') + break; + result += c; + } + } +} +} + +void Reader::handleFirstHeaders(Common::MemoryReadWriteStream *headersStream) { + if (!_boundary.empty()) { + warning("Reader: handleFirstHeaders() called when first headers were already handled"); + return; + } + + //parse method, path, query, fragment + _headers = readEverythingFromMemoryStream(headersStream); + parseFirstLine(_headers); + + //find boundary + _boundary = ""; + readFromThatUntilLineEnd(_headers.c_str(), "boundary=", _boundary); + + //find content length + Common::String contentLength = ""; + readFromThatUntilLineEnd(_headers.c_str(), "Content-Length: ", contentLength); + _contentLength = contentLength.asUint64(); + _availableBytes = _contentLength; +} + +void Reader::parseFirstLine(const Common::String &headers) { + uint32 headersSize = headers.size(); + bool bad = false; + + if (headersSize > 0) { + const char *cstr = headers.c_str(); + const char *position = strstr(cstr, "\r\n"); + if (position) { //we have at least one line - and we want the first one + //"<METHOD> <path> HTTP/<VERSION>\r\n" + Common::String method, path, http, buf; + uint32 length = position - cstr; + if (headersSize > length) + headersSize = length; + for (uint32 i = 0; i < headersSize; ++i) { + if (headers[i] != ' ') + buf += headers[i]; + if (headers[i] == ' ' || i == headersSize - 1) { + if (method == "") { + method = buf; + } else if (path == "") { + path = buf; + } else if (http == "") { + http = buf; + } else { + bad = true; + break; + } + buf = ""; + } + } + + //check that method is supported + if (method != "GET" && method != "PUT" && method != "POST") + bad = true; + + //check that HTTP/<VERSION> is OK + if (!http.hasPrefix("HTTP/")) + bad = true; + + _method = method; + parsePathQueryAndAnchor(path); + } + } + + if (bad) _isBadRequest = true; +} + +void Reader::parsePathQueryAndAnchor(Common::String path) { + //<path>[?query][#anchor] + bool readingPath = true; + bool readingQuery = false; + _path = ""; + _query = ""; + _anchor = ""; + for (uint32 i = 0; i < path.size(); ++i) { + if (readingPath) { + if (path[i] == '?') { + readingPath = false; + readingQuery = true; + } else { + _path += path[i]; + } + } else if (readingQuery) { + if (path[i] == '#') { + readingQuery = false; + } else { + _query += path[i]; + } + } else { + _anchor += path[i]; + } + } + + parseQueryParameters(); +} + +void Reader::parseQueryParameters() { + Common::String key = ""; + Common::String value = ""; + bool readingKey = true; + for (uint32 i = 0; i < _query.size(); ++i) { + if (readingKey) { + if (_query[i] == '=') { + readingKey = false; + value = ""; + } else { + key += _query[i]; + } + } else { + if (_query[i] == '&') { + if (_queryParameters.contains(key)) + warning("Reader: query parameter \"%s\" is already set!", key.c_str()); + else + _queryParameters[key] = LocalWebserver::urlDecode(value); + readingKey = true; + key = ""; + } else { + value += _query[i]; + } + } + } + + if (!key.empty()) { + if (_queryParameters.contains(key)) + warning("Reader: query parameter \"%s\" is already set!", key.c_str()); + else + _queryParameters[key] = LocalWebserver::urlDecode(value); + } +} + +bool Reader::readContentIntoStream(Common::WriteStream *stream) { + Common::String boundary = "--" + _boundary; + if (!_firstBlock) + boundary = "\r\n" + boundary; + if (_boundary.empty()) + boundary = "\r\n"; + if (_window == nullptr) + makeWindow(boundary.size()); + + while (readOneByteInStream(stream, boundary)) { + if (!bytesLeft()) + return false; + } + + _firstBlock = false; + if (stream) + stream->flush(); + + freeWindow(); + _state = RS_READING_HEADERS; + return true; +} + +void Reader::makeWindow(uint32 size) { + freeWindow(); + + _window = new byte[size]; + _windowUsed = 0; + _windowSize = size; +} + +void Reader::freeWindow() { + delete[] _window; + _window = nullptr; + _windowUsed = _windowSize = 0; +} + +namespace { +bool windowEqualsString(const byte *window, uint32 windowSize, const Common::String &boundary) { + if (boundary.size() != windowSize) + return false; + + for (uint32 i = 0; i < windowSize; ++i) { + if (window[i] != boundary[i]) + return false; + } + + return true; +} +} + +bool Reader::readOneByteInStream(Common::WriteStream *stream, const Common::String &boundary) { + byte b = readOne(); + _window[_windowUsed++] = b; + if (_windowUsed < _windowSize) + return true; + + //when window is filled, check whether that's the boundary + if (windowEqualsString(_window, _windowSize, boundary)) + return false; + + //if not, add the first byte of the window to the string + if (stream) + stream->writeByte(_window[0]); + for (uint32 i = 1; i < _windowSize; ++i) + _window[i - 1] = _window[i]; + --_windowUsed; + return true; +} + +byte Reader::readOne() { + byte b; + _content->read(&b, 1); + --_availableBytes; + --_bytesLeft; + return b; +} + +/// public + +bool Reader::readFirstHeaders() { + if (_state == RS_NONE) + _state = RS_READING_HEADERS; + + if (!bytesLeft()) + return false; + + if (_state == RS_READING_HEADERS) + return readAndHandleFirstHeaders(); + + warning("Reader::readFirstHeaders(): bad state"); + return false; +} + +bool Reader::readFirstContent(Common::WriteStream *stream) { + if (_state != RS_READING_CONTENT) { + warning("Reader::readFirstContent(): bad state"); + return false; + } + + // no difference, actually + return readBlockContent(stream); +} + +bool Reader::readBlockHeaders(Common::WriteStream *stream) { + if (_state != RS_READING_HEADERS) { + warning("Reader::readBlockHeaders(): bad state"); + return false; + } + + if (!bytesLeft()) + return false; + + return readBlockHeadersIntoStream(stream); +} + +bool Reader::readBlockContent(Common::WriteStream *stream) { + if (_state != RS_READING_CONTENT) { + warning("Reader::readBlockContent(): bad state"); + return false; + } + + if (!bytesLeft()) + return false; + + if (!readContentIntoStream(stream)) + return false; + + if (_availableBytes >= 2) { + Common::String bts; + bts += readOne(); + bts += readOne(); + if (bts == "--") + _allContentRead = true; + else if (bts != "\r\n") + warning("Reader: strange bytes: \"%s\"", bts.c_str()); + } else { + warning("Reader: strange ending"); + _allContentRead = true; + } + + return true; +} + +uint32 Reader::bytesLeft() const { return _bytesLeft; } + +void Reader::setContent(Common::MemoryReadWriteStream *stream) { + _content = stream; + _bytesLeft = stream->size() - stream->pos(); +} + +bool Reader::badRequest() const { return _isBadRequest; } + +bool Reader::noMoreContent() const { return _allContentRead; } + +Common::String Reader::headers() const { return _headers; } + +Common::String Reader::method() const { return _method; } + +Common::String Reader::path() const { return _path; } + +Common::String Reader::query() const { return _query; } + +Common::String Reader::queryParameter(Common::String name) const { return _queryParameters[name]; } + +Common::String Reader::anchor() const { return _anchor; } + +Common::String Reader::readEverythingFromMemoryStream(Common::MemoryReadWriteStream *stream) { + Common::String result; + char buf[1024]; + uint32 readBytes; + while (true) { + readBytes = stream->read(buf, 1024); + if (readBytes == 0) + break; + result += Common::String(buf, readBytes); + } + return result; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/reader.h b/backends/networking/sdl_net/reader.h new file mode 100644 index 0000000000..16d62a27eb --- /dev/null +++ b/backends/networking/sdl_net/reader.h @@ -0,0 +1,143 @@ +/* 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_READER_H +#define BACKENDS_NETWORKING_SDL_NET_READER_H + +#include "common/str.h" +#include "common/hashmap.h" +#include "common/hash-str.h" + +namespace Common { +class MemoryReadWriteStream; +class WriteStream; +} + +namespace Networking { + +enum ReaderState { + RS_NONE, + RS_READING_HEADERS, + RS_READING_CONTENT +}; + +/** + * This is a helper class for Client. + * + * It parses HTTP request and finds headers + * and content. It also supports POST form/multipart. + * + * One might pass the request even byte by byte, + * Reader will always be able to continue from the + * state it stopped on. + * + * Main headers/content must be read with + * readFirstHeaders() and readFirstContent() methods. + * Further headers/content blocks (POST form/multipart) + * must be read with readBlockHeaders() and readBlockContent(). + * + * Main headers and parsed URL components could be accessed + * with special methods after reading. + * + * To use the object, call setContent() and then one of those + * reading methods. It would return whether reading is over + * or not. If reading is over, content stream still could + * contain bytes to read with other methods. + * + * If reading is not over, Reader awaits you to call the + * same reading method when you'd get more content. + * + * If it's over, you should check whether Reader awaits + * more content with noMoreContent() and call the other + * reading method, if it is. When headers are read, one + * must read contents, and vice versa. + */ + +class Reader { + ReaderState _state; + Common::MemoryReadWriteStream *_content; + uint32 _bytesLeft; + + byte *_window; + uint32 _windowUsed, _windowSize; + + Common::MemoryReadWriteStream *_headersStream; + + Common::String _headers; + Common::String _method, _path, _query, _anchor; + Common::HashMap<Common::String, Common::String> _queryParameters; + uint32 _contentLength; + Common::String _boundary; + uint32 _availableBytes; + bool _firstBlock; + bool _isBadRequest; + bool _allContentRead; + + void cleanup(); + + bool readAndHandleFirstHeaders(); //true when ended reading + bool readBlockHeadersIntoStream(Common::WriteStream *stream); //true when ended reading + bool readContentIntoStream(Common::WriteStream *stream); //true when ended reading + + void handleFirstHeaders(Common::MemoryReadWriteStream *headers); + void parseFirstLine(const Common::String &headers); + void parsePathQueryAndAnchor(Common::String path); + void parseQueryParameters(); + + void makeWindow(uint32 size); + void freeWindow(); + bool readOneByteInStream(Common::WriteStream *stream, const Common::String &boundary); + + byte readOne(); + uint32 bytesLeft() const; + +public: + static const uint32 SUSPICIOUS_HEADERS_SIZE = 1024 * 1024; // 1 MB is really a lot + + Reader(); + ~Reader(); + + Reader &operator=(Reader &r); + + bool readFirstHeaders(); //true when ended reading + bool readFirstContent(Common::WriteStream *stream); //true when ended reading + bool readBlockHeaders(Common::WriteStream *stream); //true when ended reading + bool readBlockContent(Common::WriteStream *stream); //true when ended reading + + void setContent(Common::MemoryReadWriteStream *stream); + + bool badRequest() const; + bool noMoreContent() const; + + Common::String headers() const; + Common::String method() const; + Common::String path() const; + Common::String query() const; + Common::String queryParameter(Common::String name) const; + Common::String anchor() const; + + static Common::String readEverythingFromMemoryStream(Common::MemoryReadWriteStream *stream); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/sdl_net/uploadfileclienthandler.cpp b/backends/networking/sdl_net/uploadfileclienthandler.cpp new file mode 100644 index 0000000000..ebf341682c --- /dev/null +++ b/backends/networking/sdl_net/uploadfileclienthandler.cpp @@ -0,0 +1,212 @@ +/* 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 "backends/networking/sdl_net/uploadfileclienthandler.h" +#include "backends/fs/fs-factory.h" +#include "backends/networking/sdl_net/handlerutils.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "backends/networking/sdl_net/reader.h" +#include "common/file.h" +#include "common/memstream.h" +#include "common/translation.h" + +namespace Networking { + +UploadFileClientHandler::UploadFileClientHandler(Common::String parentDirectoryPath): + _state(UFH_READING_CONTENT), _headersStream(nullptr), _contentStream(nullptr), + _parentDirectoryPath(parentDirectoryPath), _uploadedFiles(0) {} + +UploadFileClientHandler::~UploadFileClientHandler() { + delete _headersStream; + delete _contentStream; +} + +void UploadFileClientHandler::handle(Client *client) { + if (client == nullptr) { + warning("UploadFileClientHandler::handle(): empty client pointer"); + return; + } + + while (true) { + switch (_state) { + case UFH_READING_CONTENT: + if (client->readContent(nullptr)) { + _state = UFH_READING_BLOCK_HEADERS; + continue; + } + break; + + case UFH_READING_BLOCK_HEADERS: + if (_headersStream == nullptr) + _headersStream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES); + + if (client->readBlockHeaders(_headersStream)) { + handleBlockHeaders(client); + continue; + } + + // fail on suspicious headers + if (_headersStream->size() > Reader::SUSPICIOUS_HEADERS_SIZE) { + setErrorMessageHandler(*client, _("Invalid request: headers are too long!")); + } + break; + + case UFH_READING_BLOCK_CONTENT: + // _contentStream is created by handleBlockHeaders() if needed + + if (client->readBlockContent(_contentStream)) { + handleBlockContent(client); + continue; + } + break; + + case UFH_ERROR: + case UFH_STOP: + return; + } + + break; + } +} + +namespace { +void readFromThatUntilDoubleQuote(const char *cstr, Common::String needle, Common::String &result) { + const char *position = strstr(cstr, needle.c_str()); + + if (position) { + char c; + for (const char *i = position + needle.size(); c = *i, c != 0; ++i) { + if (c == '"') + break; + result += c; + } + } +} +} + +void UploadFileClientHandler::handleBlockHeaders(Client *client) { + _state = UFH_READING_BLOCK_CONTENT; + + // fail on suspicious headers + if (_headersStream->size() > Reader::SUSPICIOUS_HEADERS_SIZE) { + setErrorMessageHandler(*client, _("Invalid request: headers are too long!")); + } + + // search for "upload_file" field + Common::String headers = Reader::readEverythingFromMemoryStream(_headersStream); + Common::String fieldName = ""; + readFromThatUntilDoubleQuote(headers.c_str(), "name=\"", fieldName); + if (!fieldName.hasPrefix("upload_file")) + return; + + Common::String filename = ""; + readFromThatUntilDoubleQuote(headers.c_str(), "filename=\"", filename); + + // skip block if <filename> is empty + if (filename.empty()) + return; + + if (HandlerUtils::hasForbiddenCombinations(filename)) + return; + + // check that <path>/<filename> doesn't exist + Common::String path = _parentDirectoryPath; + if (path.lastChar() != '/' && path.lastChar() != '\\') + path += '/'; + AbstractFSNode *originalNode = g_system->getFilesystemFactory()->makeFileNodePath(path + filename); + if (!HandlerUtils::permittedPath(originalNode->getPath())) { + setErrorMessageHandler(*client, _("Invalid path!")); + return; + } + if (originalNode->exists()) { + setErrorMessageHandler(*client, _("There is a file with that name in the parent directory!")); + return; + } + + // remove previous stream (if there is one) + if (_contentStream) { + delete _contentStream; + _contentStream = nullptr; + } + + // create file stream (and necessary subdirectories) + Common::DumpFile *f = new Common::DumpFile(); + if (!f->open(originalNode->getPath(), true)) { + delete f; + setErrorMessageHandler(*client, _("Failed to upload the file!")); + return; + } + + _contentStream = f; +} + +void UploadFileClientHandler::handleBlockContent(Client *client) { + _state = UFH_READING_BLOCK_HEADERS; + + // if previous block headers were file-related and created a stream + if (_contentStream) { + _contentStream->flush(); + ++_uploadedFiles; + + delete _contentStream; + _contentStream = nullptr; + + if (client->noMoreContent()) { + // success - redirect back to directory listing + setSuccessHandler(*client); + return; + } + } + + // no more content avaiable + if (client->noMoreContent()) { + // if no file field was found - failure + if (_uploadedFiles == 0) { + setErrorMessageHandler(*client, _("No file was passed!")); + } else { + setSuccessHandler(*client); + } + } +} + +void UploadFileClientHandler::setErrorMessageHandler(Client &client, Common::String message) { + HandlerUtils::setFilesManagerErrorMessageHandler(client, message); + _state = UFH_ERROR; +} + +void UploadFileClientHandler::setSuccessHandler(Client &client) { + // success - redirect back to directory listing + HandlerUtils::setMessageHandler( + client, + Common::String::format( + "%s<br/><a href=\"files?path=%s\">%s</a>", + _("Uploaded successfully!"), + client.queryParameter("path").c_str(), + _("Back to parent directory") + ), + (client.queryParameter("ajax") == "true" ? "/filesAJAX?path=" : "/files?path=") + + LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path")) + ); + _state = UFH_STOP; +} + +} // End of namespace Networking diff --git a/backends/networking/sdl_net/uploadfileclienthandler.h b/backends/networking/sdl_net/uploadfileclienthandler.h new file mode 100644 index 0000000000..b6481cf18f --- /dev/null +++ b/backends/networking/sdl_net/uploadfileclienthandler.h @@ -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 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. +* +*/ + +#ifndef BACKENDS_NETWORKING_SDL_NET_UPLOADFILECLIENTHANDLER_H +#define BACKENDS_NETWORKING_SDL_NET_UPLOADFILECLIENTHANDLER_H + +#include "backends/networking/sdl_net/client.h" +#include "common/stream.h" + +namespace Networking { + +enum UploadFileHandlerState { + UFH_READING_CONTENT, + UFH_READING_BLOCK_HEADERS, + UFH_READING_BLOCK_CONTENT, + UFH_ERROR, + UFH_STOP +}; + +/** + * This class handles POST form/multipart upload. + * + * handleBlockHeaders() looks for filename and, if it's found, + * handleBlockContent() saves content into the file with such name. + * + * If no file found or other error occurs, it sets + * default error message handler. + */ + +class UploadFileClientHandler: public ClientHandler { + UploadFileHandlerState _state; + Common::MemoryReadWriteStream *_headersStream; + Common::WriteStream *_contentStream; + Common::String _parentDirectoryPath; + uint32 _uploadedFiles; + + void handleBlockHeaders(Client *client); + void handleBlockContent(Client *client); + void setErrorMessageHandler(Client &client, Common::String message); + void setSuccessHandler(Client &client); + +public: + UploadFileClientHandler(Common::String parentDirectoryPath); + virtual ~UploadFileClientHandler(); + + virtual void handle(Client *client); +}; + +} // End of namespace Networking + +#endif diff --git a/backends/networking/wwwroot.zip b/backends/networking/wwwroot.zip Binary files differnew file mode 100644 index 0000000000..b767d7c5d7 --- /dev/null +++ b/backends/networking/wwwroot.zip diff --git a/backends/networking/wwwroot/.files.html b/backends/networking/wwwroot/.files.html new file mode 100644 index 0000000000..f05c0113f8 --- /dev/null +++ b/backends/networking/wwwroot/.files.html @@ -0,0 +1,60 @@ +<!doctype html> +<html> + <head> + <title>ScummVM</title> + <meta charset="utf-8"/> + <link rel="stylesheet" type="text/css" href="style.css"/> + </head> + <body> + <div class="container"> + <div class='header'> + <center><img src="logo.png"/></center> + </div> + <div class="controls"> + <table class="buttons"><tr> + <td><a href="javascript:show('create_directory');">{create_directory_button}</a></td> + <td><a href="javascript:show('upload_file');">{upload_files_button}</a></td> + </tr></table> + <div id="create_directory" class="modal"> + <p>{create_directory_desc}</p> + <form action="create"> + <input type="hidden" name="path" value="{path}"/> + <input type="text" name="directory_name" value=""/> + <input type="submit" value="{create_directory_button}"/> + </form> + </div> + <div id="upload_file" class="modal"> + <p>{upload_file_desc}</p> + <form action="upload?path={path}" method="post" enctype="multipart/form-data"> + <!-- we don't need "[]" in the name, as our webserver is not using PHP --> + <!-- "allowdirs" is a proposal, not implemented yet --> + <input type="file" name="upload_file-f" allowdirs multiple/> + <br/><br/> + <p>{or_upload_directory_desc}</p> + <!-- "directory"/"webkitdirectory" works in Chrome only yet, "multiple" is just in case here --> + <input type="file" name="upload_file-d" directory webkitdirectory multiple/> + <input type="submit" value="{upload_file_button}"/> + </form> + </div> + </div> + <div class="content"> + <table class="files_list"> + <td></td><td><b class="directory_name">{index_of_directory}</b></td><td></td> + {content} + </table> + </div> + </div> + <script> + function show(id) { + var e = document.getElementById(id); + var visible = (e.style.display == "block"); + if (visible) id = ""; //hide + + e = document.getElementById("create_directory"); + e.style.display = (e.id == id ? "block" : "none"); + e = document.getElementById("upload_file"); + e.style.display = (e.id == id ? "block" : "none"); + } + </script> + </body> +</html>
\ No newline at end of file diff --git a/backends/networking/wwwroot/.filesAJAX.html b/backends/networking/wwwroot/.filesAJAX.html new file mode 100644 index 0000000000..d45c73069d --- /dev/null +++ b/backends/networking/wwwroot/.filesAJAX.html @@ -0,0 +1,240 @@ +<!doctype html> +<html> + <head> + <title>ScummVM</title> + <meta charset="utf-8"/> + <link rel="stylesheet" type="text/css" href="style.css"/> + </head> + <body> + <div class="container"> + <div class='header'> + <center><img src="logo.png"/></center> + </div> + <div class="controls"> + <table class="buttons"><tr> + <td><a href="javascript:show('create_directory');">{create_directory_button}</a></td> + <td><a href="javascript:show('upload_file');">{upload_files_button}</a></td> + </tr></table> + <div id="create_directory" class="modal"> + <p>{create_directory_desc}</p> + <form action="create" id="create_directory_form" onsubmit="return createDirectory();"> + <input type="hidden" name="path" value="{path}"/> + <input type="hidden" name="ajax" value="true"/> + <input type="text" name="directory_name" value=""/> + <input type="submit" value="{create_directory_button}"/> + </form> + </div> + <div id="upload_file" class="modal"> + <p>{upload_file_desc}</p> + <form action="upload?path={path}&ajax=true" method="post" enctype="multipart/form-data" id="files_upload_form"> + <!-- we don't need "[]" in the name, as our webserver is not using PHP --> + <!-- "allowdirs" is a proposal, not implemented yet --> + <input type="file" name="upload_file-f" allowdirs multiple/> + <br/><br/> + <p>{or_upload_directory_desc}</p> + <!-- "directory"/"webkitdirectory" works in Chrome only yet, "multiple" is just in case here --> + <input type="file" name="upload_file-d" directory webkitdirectory multiple/> + <input type="submit" value="{upload_file_button}"/> + </form> + </div> + </div> + <div class="content"> + <div id="loading_message">{loading}</div> + <div id="error_message">{error}</div> + <table class="files_list" id="files_list"> + </table> + </div> + </div> + <script> + function show(id) { + var e = document.getElementById(id); + var visible = (e.style.display == "block"); + if (visible) id = ""; //hide + + e = document.getElementById("create_directory"); + e.style.display = (e.id == id ? "block" : "none"); + e = document.getElementById("upload_file"); + e.style.display = (e.id == id ? "block" : "none"); + } + </script> + <script src="ajax.js"></script> + <script> + window.onload = function () { + showDirectory("{start_path}"); + } + + function showDirectory(path) { + if (isLoading) return; + showLoading(); + ajax.getAndParseJson("./list", {"path": path}, getCallback(path)); + } + + function getCallback(path) { + return function (jsonResponse) { + if (jsonResponse.type == "error") { + showError(); + return; + } + + openDirectory(path, jsonResponse.items); + hideLoading(); + }; + } + + function createDirectory() { + if (isLoading) return; + showLoading(); + + var data = {"answer_json": "true"}; + var elements = document.getElementById("create_directory_form").elements; + for (var el in elements) + data[elements[el].name] = elements[el].value; + + ajax.getAndParseJson("./create", data, getCreateDirectoryCallback(data["path"])); + show("create_directory"); + return false; // invalidate form, so it won't submit + } + + function getCreateDirectoryCallback(path) { + return function (jsonResponse) { + console.log(jsonResponse); + + if (jsonResponse.type == "error") { + showError(); + return; + } + + hideLoading(); + showDirectory(path); + }; + } + + var isLoading = false; + + function showLoading() { + isLoading = true; + var e = document.getElementById("loading_message"); + e.style.display = "block"; + e = document.getElementById("error_message"); + e.style.display = "none"; + } + + function showError() { + isLoading = false; + var e = document.getElementById("loading_message"); + e.style.display = "none"; + e = document.getElementById("error_message"); + e.style.display = "block"; + //TODO: pass the actual message there? + } + + function hideLoading() { + isLoading = false; + var e = document.getElementById("loading_message"); + e.style.display = "none"; + e = document.getElementById("error_message"); + e.style.display = "none"; + } + + function openDirectory(path, items) { + // update path + document.getElementById("create_directory_form").elements["path"].value = path; + document.getElementById("files_upload_form").action = "upload?path=" + path + "&ajax=true"; + + // update table contents + listDirectory(path, items); + } + + function makeBreadcrumb(name, path) { + var a = createElementWithContents("a", name); + a.onclick = function () { showDirectory(path); }; + a.href = "javascript:void(0);"; + return a; + } + + function makeBreadcrumbs(path) { + var b = document.createElement("b"); + b.className = "directory_name"; + + b.appendChild(createElementWithContents("span", "{index_of}")); + var slashes = true; + var crumb = ""; + var currentPath = ""; + path += ' '; //so the last slash is added + for (var i=0; i<path.length; ++i) { + if (path[i] == '/' || path[i] == '\\') { + if (!slashes) { + currentPath += crumb; + b.appendChild(makeBreadcrumb(crumb, currentPath+'/')); + slashes = true; + } + } else { + if (slashes) { + currentPath += "/"; + if (currentPath == "/") { //make special '/' crumb here + b.appendChild(makeBreadcrumb('/', '/')); + } else { + b.appendChild(createElementWithContents("span", "/")); + } + slashes = false; + crumb = ""; + } + crumb += path[i]; + } + } + return b; + } + + function listDirectory(path, items) { + // cleanup the list + var files_list = document.getElementById("files_list"); + while (files_list.hasChildNodes()) + files_list.removeChild(files_list.firstChild); + var tbody = document.createElement("tbody"); + + // add header item + var tr = document.createElement("tr"); + tr.appendChild(createElementWithContents("td", "")); + var td = document.createElement("td"); + td.appendChild(makeBreadcrumbs(path)); + tr.appendChild(td); + tr.appendChild(createElementWithContents("td", "")); + tbody.appendChild(tr); + + // add items + for (var i in items) + addItem(tbody, items[i]); + + files_list.appendChild(tbody); + } + + function addItem(tbody, item) { + var tr = document.createElement("tr"); + var td = document.createElement("td"); + var img = document.createElement("img"); + img.src = "./icons/" + item.icon; + td.appendChild(img); + tr.appendChild(td); + + td = document.createElement("td"); + var a = createElementWithContents("a", item.name); + if (item.isDirectory) { + a.onclick = function () { showDirectory(item.path); }; + a.href = "javascript:void(0);"; + } else + a.href = "./download?path=" + encodeURIComponent(item.path); + td.appendChild(a); + tr.appendChild(td); + + tr.appendChild(createElementWithContents("td", "")); + tbody.appendChild(tr); + } + + function createElementWithContents(type, innerHTML) { + var e = document.createElement(type); + e.innerHTML = innerHTML; + return e; + } + </script> + </body> +</html>
\ No newline at end of file diff --git a/backends/networking/wwwroot/.index.html b/backends/networking/wwwroot/.index.html new file mode 100644 index 0000000000..2a3d9d382d --- /dev/null +++ b/backends/networking/wwwroot/.index.html @@ -0,0 +1,18 @@ +<!doctype html> +<html> + <head> + <title>ScummVM</title> + <meta charset="utf-8"/> + <link rel="stylesheet" type="text/css" href="style.css"/> + </head> + <body> + <div class="container"> + <div class='header'> + <center><img src="logo.png"/></center> + </div> + <div class="content"> + <p>{message}</p> + </div> + </div> + </body> +</html>
\ No newline at end of file diff --git a/backends/networking/wwwroot/ajax.js b/backends/networking/wwwroot/ajax.js new file mode 100644 index 0000000000..c01d7e93fc --- /dev/null +++ b/backends/networking/wwwroot/ajax.js @@ -0,0 +1,48 @@ +// the following is snippet from http://stackoverflow.com/a/18078705 +// I changed a few things though + +var ajax = {}; +ajax.x = function () { return new XMLHttpRequest(); }; // "no one uses IE6" + +ajax.send = function (url, callback, errorCallback, method, data, async) { + if (async === undefined) async = true; + + var x = ajax.x(); + x.open(method, url, async); + x.onreadystatechange = function () { + if (x.readyState == XMLHttpRequest.DONE) { + if (x.status == 200) + callback(x.responseText); + else + errorCallback(x); + } + }; + if (method == 'POST') { + x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + } + x.send(data) +}; + +ajax.get = function (url, data, callback, errorCallback, async) { + var query = []; + for (var key in data) { + query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key])); + } + ajax.send(url + (query.length ? '?' + query.join('&') : ''), callback, errorCallback, 'GET', null, async) +}; + +ajax.post = function (url, data, callback, errorCallback, async) { + var query = []; + for (var key in data) { + query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key])); + } + ajax.send(url, callback, errorCallback, 'POST', query.join('&'), async) +}; + +ajax.getAndParseJson = function (url, data, callback) { + ajax.get( + url, data, + function (responseText) { callback(JSON.parse(responseText)); }, + function (x) { console.log("error: " + x.status); } + ); +};
\ No newline at end of file diff --git a/backends/networking/wwwroot/favicon.ico b/backends/networking/wwwroot/favicon.ico Binary files differnew file mode 100644 index 0000000000..0283e8432e --- /dev/null +++ b/backends/networking/wwwroot/favicon.ico diff --git a/backends/networking/wwwroot/icons/7z.png b/backends/networking/wwwroot/icons/7z.png Binary files differnew file mode 100644 index 0000000000..656e7e7c62 --- /dev/null +++ b/backends/networking/wwwroot/icons/7z.png diff --git a/backends/networking/wwwroot/icons/dir.png b/backends/networking/wwwroot/icons/dir.png Binary files differnew file mode 100644 index 0000000000..bcdec04a57 --- /dev/null +++ b/backends/networking/wwwroot/icons/dir.png diff --git a/backends/networking/wwwroot/icons/txt.png b/backends/networking/wwwroot/icons/txt.png Binary files differnew file mode 100644 index 0000000000..023d2ee24a --- /dev/null +++ b/backends/networking/wwwroot/icons/txt.png diff --git a/backends/networking/wwwroot/icons/unk.png b/backends/networking/wwwroot/icons/unk.png Binary files differnew file mode 100644 index 0000000000..346eebecc3 --- /dev/null +++ b/backends/networking/wwwroot/icons/unk.png diff --git a/backends/networking/wwwroot/icons/up.png b/backends/networking/wwwroot/icons/up.png Binary files differnew file mode 100644 index 0000000000..2dc3df022b --- /dev/null +++ b/backends/networking/wwwroot/icons/up.png diff --git a/backends/networking/wwwroot/icons/zip.png b/backends/networking/wwwroot/icons/zip.png Binary files differnew file mode 100644 index 0000000000..cdfc5763dd --- /dev/null +++ b/backends/networking/wwwroot/icons/zip.png diff --git a/backends/networking/wwwroot/logo.png b/backends/networking/wwwroot/logo.png Binary files differnew file mode 100644 index 0000000000..9fdd2d0d1e --- /dev/null +++ b/backends/networking/wwwroot/logo.png diff --git a/backends/networking/wwwroot/style.css b/backends/networking/wwwroot/style.css new file mode 100644 index 0000000000..ba31587c4d --- /dev/null +++ b/backends/networking/wwwroot/style.css @@ -0,0 +1,113 @@ +html { + background: rgb(212, 117, 11); + background: linear-gradient(to bottom, rgb(212, 117, 11) 0%, rgb(212, 117, 11) 36%, rgb(239, 196, 24) 100%); + min-height: 100vh; +} + +.container { + width: 80%; + margin: 0 auto; +} + +.header { + padding: 10pt; + margin-bottom: 0; +} + +.content { + padding: 8pt; + background: rgb(251, 241, 206); + font-family: Tahoma; + font-size: 16pt; +} + +.content p { margin: 0 0 6pt 0; } + +.controls { + padding: 8pt; + background: #FFF; + font-family: Tahoma; + font-size: 16pt; +} + +.controls .buttons { + width: 100%; + max-width: 500pt; + margin: -8pt auto; + border: 0; + border-spacing: 0; +} + +.controls .buttons td { + width: 50%; + text-align: center; + margin: 0; + padding: 0; +} + +.controls .buttons a { + display: block; + height: 40pt; + line-height: 38pt; + vertical-align: middle; + color: #000; + text-decoration: none; +} + +.controls .buttons a:hover { + background: #F3F3F3; +} + +.modal { + margin-top: 10pt; + display: none; +} + +.modal p { margin: 0 0 6pt 0; } + +#create_directory input[type="text"], #upload_file input[type="file"] { + width: calc(100% - 2 * 5pt); +} + +.modal input { + border: 1px solid #EEE; + padding: 5pt; + font-size: 12pt; +} + +.modal input[type="submit"] { + display: block; + margin: 6pt auto; + background: #DDD; + border: 0; +} + +.modal input[type="submit"]:hover { + background: #F3F3F3; + cursor: pointer; +} + +td img { vertical-align: middle; height: 20px; } + +.directory_name { + display: block; + padding-bottom: 6px; +} + +.directory_name a { color: black; } +.directory_name a:hover { color: blue; } + +#loading_message, #error_message { + margin: -8pt; + margin-bottom: 5pt; + padding: 4pt; + text-align: center; +} + +#loading_message { + background: #99FF99; +} + +#error_message { + background: #FF9999; +} diff --git a/backends/platform/android/asset-archive.cpp b/backends/platform/android/asset-archive.cpp index 6680081c16..0ee6efc111 100644 --- a/backends/platform/android/asset-archive.cpp +++ b/backends/platform/android/asset-archive.cpp @@ -22,8 +22,6 @@ #if defined(__ANDROID__) -#include <jni.h> - #include <sys/types.h> #include <unistd.h> @@ -37,443 +35,112 @@ #include "backends/platform/android/jni.h" #include "backends/platform/android/asset-archive.h" -// Must match android.content.res.AssetManager.ACCESS_* -const jint ACCESS_UNKNOWN = 0; -const jint ACCESS_RANDOM = 1; +#include <android/asset_manager.h> +#include <android/asset_manager_jni.h> -// This might be useful to someone else. Assumes markSupported() == true. -class JavaInputStream : public Common::SeekableReadStream { +class AssetInputStream : public Common::SeekableReadStream { public: - JavaInputStream(JNIEnv *env, jobject is); - virtual ~JavaInputStream(); - - virtual bool eos() const { - return _eos; - } + AssetInputStream(AAssetManager *as, const Common::String &path); + virtual ~AssetInputStream(); - virtual bool err() const { - return _err; - } + virtual bool eos() const { return _eos; } - virtual void clearErr() { - _eos = _err = false; - } + virtual void clearErr() {_eos = false; } virtual uint32 read(void *dataPtr, uint32 dataSize); - virtual int32 pos() const { - return _pos; - } + virtual int32 pos() const { return _pos; } - virtual int32 size() const { - return _len; - } + virtual int32 size() const { return _len; } virtual bool seek(int32 offset, int whence = SEEK_SET); private: - void close(JNIEnv *env); - - jmethodID MID_mark; - jmethodID MID_available; - jmethodID MID_close; - jmethodID MID_read; - jmethodID MID_reset; - jmethodID MID_skip; + void close(); + AAsset *_asset; - jobject _input_stream; - jsize _buflen; - jbyteArray _buf; uint32 _pos; - jint _len; + uint32 _len; bool _eos; - bool _err; }; -JavaInputStream::JavaInputStream(JNIEnv *env, jobject is) : - _eos(false), - _err(false), - _pos(0) -{ - _input_stream = env->NewGlobalRef(is); - _buflen = 8192; - jobject buf = env->NewByteArray(_buflen); - _buf = (jbyteArray)env->NewGlobalRef(buf); - env->DeleteLocalRef(buf); - - jclass cls = env->GetObjectClass(_input_stream); - MID_mark = env->GetMethodID(cls, "mark", "(I)V"); - assert(MID_mark); - MID_available = env->GetMethodID(cls, "available", "()I"); - assert(MID_available); - MID_close = env->GetMethodID(cls, "close", "()V"); - assert(MID_close); - MID_read = env->GetMethodID(cls, "read", "([BII)I"); - assert(MID_read); - MID_reset = env->GetMethodID(cls, "reset", "()V"); - assert(MID_reset); - MID_skip = env->GetMethodID(cls, "skip", "(J)J"); - assert(MID_skip); - env->DeleteLocalRef(cls); - - // Mark start of stream, so we can reset back to it. - // readlimit is set to something bigger than anything we might - // want to seek within. - env->CallVoidMethod(_input_stream, MID_mark, 10 * 1024 * 1024); - _len = env->CallIntMethod(_input_stream, MID_available); +AssetInputStream::AssetInputStream(AAssetManager *as, const Common::String &path) : + _eos(false), _pos(0) { + _asset = AAssetManager_open(as, path.c_str(), AASSET_MODE_RANDOM); + _len = AAsset_getLength(_asset); } -JavaInputStream::~JavaInputStream() { - JNIEnv *env = JNI::getEnv(); - close(env); - - env->DeleteGlobalRef(_buf); - env->DeleteGlobalRef(_input_stream); +AssetInputStream::~AssetInputStream() { } -void JavaInputStream::close(JNIEnv *env) { - env->CallVoidMethod(_input_stream, MID_close); - - if (env->ExceptionCheck()) - env->ExceptionClear(); +void AssetInputStream::close() { + AAsset_close(_asset); } -uint32 JavaInputStream::read(void *dataPtr, uint32 dataSize) { - JNIEnv *env = JNI::getEnv(); - - if (_buflen < jint(dataSize)) { - _buflen = dataSize; - - env->DeleteGlobalRef(_buf); - jobject buf = env->NewByteArray(_buflen); - _buf = static_cast<jbyteArray>(env->NewGlobalRef(buf)); - env->DeleteLocalRef(buf); - } - - jint ret = env->CallIntMethod(_input_stream, MID_read, _buf, 0, dataSize); - - if (env->ExceptionCheck()) { - warning("Exception during JavaInputStream::read(%p, %d)", - dataPtr, dataSize); - - env->ExceptionDescribe(); - env->ExceptionClear(); - - _err = true; - ret = -1; - } else if (ret == -1) { +uint32 AssetInputStream::read(void *dataPtr, uint32 dataSize) { + uint32 readlen = AAsset_read(_asset, dataPtr, dataSize); + _pos += readlen; + if (readlen != dataSize) { _eos = true; - ret = 0; - } else { - env->GetByteArrayRegion(_buf, 0, ret, static_cast<jbyte *>(dataPtr)); - _pos += ret; } - - return ret; + return readlen; } -bool JavaInputStream::seek(int32 offset, int whence) { - JNIEnv *env = JNI::getEnv(); - uint32 newpos; - - switch (whence) { - case SEEK_SET: - newpos = offset; - break; - case SEEK_CUR: - newpos = _pos + offset; - break; - case SEEK_END: - newpos = _len + offset; - break; - default: - debug("Unknown 'whence' arg %d", whence); +bool AssetInputStream::seek(int32 offset, int whence) { + int res = AAsset_seek(_asset, offset, whence); + if (res == -1) { return false; } - - jlong skip_bytes; - if (newpos > _pos) { - skip_bytes = newpos - _pos; - } else { - // Can't skip backwards, so jump back to start and skip from there. - env->CallVoidMethod(_input_stream, MID_reset); - - if (env->ExceptionCheck()) { - warning("Failed to rewind to start of asset stream"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - - return false; - } - - _pos = 0; - skip_bytes = newpos; - } - - while (skip_bytes > 0) { - jlong ret = env->CallLongMethod(_input_stream, MID_skip, skip_bytes); - - if (env->ExceptionCheck()) { - warning("Failed to skip %ld bytes into asset stream", - static_cast<long>(skip_bytes)); - - env->ExceptionDescribe(); - env->ExceptionClear(); - - return false; - } else if (ret == 0) { - warning("InputStream->skip(%ld) didn't skip any bytes. Aborting seek.", - static_cast<long>(skip_bytes)); - - // No point looping forever... - return false; - } - - _pos += ret; - skip_bytes -= ret; - } - - _eos = false; - return true; -} - -// Must match android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH -const jlong UNKNOWN_LENGTH = -1; - -// Reading directly from a fd is so much more efficient, that it is -// worth optimising for. -class AssetFdReadStream : public Common::SeekableReadStream { -public: - AssetFdReadStream(JNIEnv *env, jobject assetfd); - virtual ~AssetFdReadStream(); - - virtual bool eos() const { - return _eos; - } - - virtual bool err() const { - return _err; - } - - virtual void clearErr() { - _eos = _err = false; - } - - virtual uint32 read(void *dataPtr, uint32 dataSize); - - virtual int32 pos() const { - return _pos; - } - - virtual int32 size() const { - return _declared_len; - } - - virtual bool seek(int32 offset, int whence = SEEK_SET); - -private: - void close(JNIEnv *env); - - int _fd; - jmethodID MID_close; - jobject _assetfd; - jlong _start_off; - jlong _declared_len; - uint32 _pos; - bool _eos; - bool _err; -}; - -AssetFdReadStream::AssetFdReadStream(JNIEnv *env, jobject assetfd) : - _eos(false), - _err(false), - _pos(0) -{ - _assetfd = env->NewGlobalRef(assetfd); - - jclass cls = env->GetObjectClass(_assetfd); - MID_close = env->GetMethodID(cls, "close", "()V"); - assert(MID_close); - - jmethodID MID_getStartOffset = - env->GetMethodID(cls, "getStartOffset", "()J"); - assert(MID_getStartOffset); - _start_off = env->CallLongMethod(_assetfd, MID_getStartOffset); - - jmethodID MID_getDeclaredLength = - env->GetMethodID(cls, "getDeclaredLength", "()J"); - assert(MID_getDeclaredLength); - _declared_len = env->CallLongMethod(_assetfd, MID_getDeclaredLength); - - jmethodID MID_getFileDescriptor = - env->GetMethodID(cls, "getFileDescriptor", - "()Ljava/io/FileDescriptor;"); - assert(MID_getFileDescriptor); - jobject javafd = env->CallObjectMethod(_assetfd, MID_getFileDescriptor); - assert(javafd); - - jclass fd_cls = env->GetObjectClass(javafd); - jfieldID FID_descriptor = env->GetFieldID(fd_cls, "descriptor", "I"); - assert(FID_descriptor); - env->DeleteLocalRef(fd_cls); - - _fd = env->GetIntField(javafd, FID_descriptor); - env->DeleteLocalRef(javafd); - - env->DeleteLocalRef(cls); -} - -AssetFdReadStream::~AssetFdReadStream() { - JNIEnv *env = JNI::getEnv(); - env->CallVoidMethod(_assetfd, MID_close); - - if (env->ExceptionCheck()) - env->ExceptionClear(); - - env->DeleteGlobalRef(_assetfd); -} - -uint32 AssetFdReadStream::read(void *dataPtr, uint32 dataSize) { - if (_declared_len != UNKNOWN_LENGTH) { - jlong cap = _declared_len - _pos; - if (dataSize > cap) - dataSize = cap; - } - - int ret = ::read(_fd, dataPtr, dataSize); - - if (ret == 0) - _eos = true; - else if (ret == -1) - _err = true; - else - _pos += ret; - - return ret; -} - -bool AssetFdReadStream::seek(int32 offset, int whence) { - if (whence == SEEK_SET) { - if (_declared_len != UNKNOWN_LENGTH && offset > _declared_len) - offset = _declared_len; - - offset += _start_off; - } else if (whence == SEEK_END && _declared_len != UNKNOWN_LENGTH) { - whence = SEEK_SET; - offset = _start_off + _declared_len + offset; + if (whence == SEEK_CUR) { + _pos += offset; + } else if (whence == SEEK_SET) { + _pos = offset; + } else if (whence == SEEK_END) { + _pos = _len + offset; } - - int ret = lseek(_fd, offset, whence); - - if (ret == -1) - return false; - - _pos = ret - _start_off; + assert(_pos <= _len); _eos = false; - return true; } -AndroidAssetArchive::AndroidAssetArchive(jobject am) { +AndroidAssetArchive::AndroidAssetArchive(jobject am) : _hasCached(false) { JNIEnv *env = JNI::getEnv(); - _am = env->NewGlobalRef(am); - jclass cls = env->GetObjectClass(_am); - MID_open = env->GetMethodID(cls, "open", - "(Ljava/lang/String;I)Ljava/io/InputStream;"); - assert(MID_open); - - MID_openFd = env->GetMethodID(cls, "openFd", "(Ljava/lang/String;)" - "Landroid/content/res/AssetFileDescriptor;"); - assert(MID_openFd); - - MID_list = env->GetMethodID(cls, "list", - "(Ljava/lang/String;)[Ljava/lang/String;"); - assert(MID_list); - env->DeleteLocalRef(cls); + _am = AAssetManager_fromJava(env, am); } AndroidAssetArchive::~AndroidAssetArchive() { - JNIEnv *env = JNI::getEnv(); - env->DeleteGlobalRef(_am); } bool AndroidAssetArchive::hasFile(const Common::String &name) const { - JNIEnv *env = JNI::getEnv(); - jstring path = env->NewStringUTF(name.c_str()); - jobject result = env->CallObjectMethod(_am, MID_open, path, ACCESS_UNKNOWN); - if (env->ExceptionCheck()) { - // Assume FileNotFoundException - //warning("Error while calling AssetManager->open(%s)", name.c_str()); - //env->ExceptionDescribe(); - env->ExceptionClear(); - env->DeleteLocalRef(path); - - return false; + AAsset *asset = AAssetManager_open(_am, name.c_str(), AASSET_MODE_RANDOM); + bool exists = false; + if (asset != NULL) { + exists = true; + AAsset_close(asset); } - - env->DeleteLocalRef(result); - env->DeleteLocalRef(path); - - return true; + return exists; } int AndroidAssetArchive::listMembers(Common::ArchiveMemberList &member_list) const { - JNIEnv *env = JNI::getEnv(); - Common::List<Common::String> dirlist; - dirlist.push_back(""); + if (_hasCached) { + member_list.insert(member_list.end(), _cachedMembers.begin(), _cachedMembers.end()); + return _cachedMembers.size(); + } int count = 0; - while (!dirlist.empty()) { - const Common::String dir = dirlist.back(); - dirlist.pop_back(); - - jstring jpath = env->NewStringUTF(dir.c_str()); - jobjectArray jpathlist = - (jobjectArray)env->CallObjectMethod(_am, MID_list, jpath); - - if (env->ExceptionCheck()) { - warning("Error while calling AssetManager->list(%s). Ignoring.", - dir.c_str()); - env->ExceptionDescribe(); - env->ExceptionClear(); - - // May as well keep going ... - continue; - } - - env->DeleteLocalRef(jpath); + AAssetDir *dir = AAssetManager_openDir(_am, ""); + const char *file = AAssetDir_getNextFileName(dir); - for (jsize i = 0; i < env->GetArrayLength(jpathlist); ++i) { - jstring elem = (jstring)env->GetObjectArrayElement(jpathlist, i); - const char *p = env->GetStringUTFChars(elem, 0); - - if (strlen(p)) { - Common::String thispath = dir; - - if (!thispath.empty()) - thispath += "/"; - - thispath += p; - - // Assume files have a . in them, and directories don't - if (strchr(p, '.')) { - member_list.push_back(getMember(thispath)); - ++count; - } else { - // AssetManager is ridiculously slow and we don't care - // about subdirectories at the moment, so ignore them. - // dirlist.push_back(thispath); - } - } - - env->ReleaseStringUTFChars(elem, p); - env->DeleteLocalRef(elem); - } - - env->DeleteLocalRef(jpathlist); + while (file) { + member_list.push_back(getMember(file)); + ++count; + file = AAssetDir_getNextFileName(dir); } + AAssetDir_close(dir); + + _cachedMembers = Common::ArchiveMemberList(member_list); + _hasCached = true; return count; } @@ -483,39 +150,10 @@ const Common::ArchiveMemberPtr AndroidAssetArchive::getMember(const Common::Stri } Common::SeekableReadStream *AndroidAssetArchive::createReadStreamForMember(const Common::String &path) const { - JNIEnv *env = JNI::getEnv(); - jstring jpath = env->NewStringUTF(path.c_str()); - - // Try openFd() first ... - jobject afd = env->CallObjectMethod(_am, MID_openFd, jpath); - - if (env->ExceptionCheck()) - env->ExceptionClear(); - else if (afd != 0) { - // success :) - Common::SeekableReadStream *stream = new AssetFdReadStream(env, afd); - env->DeleteLocalRef(jpath); - env->DeleteLocalRef(afd); - return stream; + if (!hasFile(path)) { + return nullptr; } - - // ... and fallback to normal open() if that doesn't work - jobject is = env->CallObjectMethod(_am, MID_open, jpath, ACCESS_RANDOM); - - if (env->ExceptionCheck()) { - // Assume FileNotFoundException - //warning("Error opening %s", path.c_str()); - //env->ExceptionDescribe(); - env->ExceptionClear(); - env->DeleteLocalRef(jpath); - - return 0; - } - - Common::SeekableReadStream *stream = new JavaInputStream(env, is); - env->DeleteLocalRef(jpath); - env->DeleteLocalRef(is); - return stream; + return new AssetInputStream(_am, path); } #endif diff --git a/backends/platform/android/asset-archive.h b/backends/platform/android/asset-archive.h index 6a0033d24e..8ae55b22c9 100644 --- a/backends/platform/android/asset-archive.h +++ b/backends/platform/android/asset-archive.h @@ -32,6 +32,8 @@ #include "common/util.h" #include "common/archive.h" +#include <android/asset_manager.h> + class AndroidAssetArchive : public Common::Archive { public: AndroidAssetArchive(jobject am); @@ -43,11 +45,9 @@ public: virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const; private: - jmethodID MID_open; - jmethodID MID_openFd; - jmethodID MID_list; - - jobject _am; + AAssetManager *_am; + mutable Common::ArchiveMemberList _cachedMembers; + mutable bool _hasCached; }; #endif diff --git a/backends/platform/android/jni.cpp b/backends/platform/android/jni.cpp index 22e6a749c2..256ae09ef8 100644 --- a/backends/platform/android/jni.cpp +++ b/backends/platform/android/jni.cpp @@ -76,6 +76,8 @@ bool JNI::_ready_for_events = 0; jmethodID JNI::_MID_getDPI = 0; jmethodID JNI::_MID_displayMessageOnOSD = 0; +jmethodID JNI::_MID_openUrl = 0; +jmethodID JNI::_MID_isConnectionLimited = 0; jmethodID JNI::_MID_setWindowCaption = 0; jmethodID JNI::_MID_showVirtualKeyboard = 0; jmethodID JNI::_MID_getSysArchives = 0; @@ -232,6 +234,41 @@ void JNI::displayMessageOnOSD(const char *msg) { env->DeleteLocalRef(java_msg); } +bool JNI::openUrl(const char *url) { + bool success = true; + JNIEnv *env = JNI::getEnv(); + jstring javaUrl = env->NewStringUTF(url); + + env->CallVoidMethod(_jobj, _MID_openUrl, javaUrl); + + if (env->ExceptionCheck()) { + LOGE("Failed to open URL"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + success = false; + } + + env->DeleteLocalRef(javaUrl); + return success; +} + +bool JNI::isConnectionLimited() { + bool limited = false; + JNIEnv *env = JNI::getEnv(); + limited = env->CallBooleanMethod(_jobj, _MID_isConnectionLimited); + + if (env->ExceptionCheck()) { + LOGE("Failed to check whether connection's limited"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + limited = true; + } + + return limited; +} + void JNI::setWindowCaption(const char *caption) { JNIEnv *env = JNI::getEnv(); jstring java_caption = env->NewStringUTF(caption); @@ -411,6 +448,8 @@ void JNI::create(JNIEnv *env, jobject self, jobject asset_manager, FIND_METHOD(, setWindowCaption, "(Ljava/lang/String;)V"); FIND_METHOD(, getDPI, "([F)V"); FIND_METHOD(, displayMessageOnOSD, "(Ljava/lang/String;)V"); + FIND_METHOD(, openUrl, "(Ljava/lang/String;)V"); + FIND_METHOD(, isConnectionLimited, "()Z"); FIND_METHOD(, showVirtualKeyboard, "(Z)V"); FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;"); FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;"); diff --git a/backends/platform/android/jni.h b/backends/platform/android/jni.h index 70feaaf72a..0798db448a 100644 --- a/backends/platform/android/jni.h +++ b/backends/platform/android/jni.h @@ -58,6 +58,8 @@ public: static void setWindowCaption(const char *caption); static void getDPI(float *values); static void displayMessageOnOSD(const char *msg); + static bool openUrl(const char *url); + static bool isConnectionLimited(); static void showVirtualKeyboard(bool enable); static void addSysArchivesToSearchSet(Common::SearchSet &s, int priority); @@ -89,6 +91,8 @@ private: static jmethodID _MID_getDPI; static jmethodID _MID_displayMessageOnOSD; + static jmethodID _MID_openUrl; + static jmethodID _MID_isConnectionLimited; static jmethodID _MID_setWindowCaption; static jmethodID _MID_showVirtualKeyboard; static jmethodID _MID_getSysArchives; diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVM.java b/backends/platform/android/org/scummvm/scummvm/ScummVM.java index 3b370a583d..47dcb32b22 100644 --- a/backends/platform/android/org/scummvm/scummvm/ScummVM.java +++ b/backends/platform/android/org/scummvm/scummvm/ScummVM.java @@ -53,6 +53,8 @@ public abstract class ScummVM implements SurfaceHolder.Callback, Runnable { // Callbacks from C++ peer instance abstract protected void getDPI(float[] values); abstract protected void displayMessageOnOSD(String msg); + abstract protected void openUrl(String url); + abstract protected boolean isConnectionLimited(); abstract protected void setWindowCaption(String caption); abstract protected void showVirtualKeyboard(boolean enable); abstract protected String[] getSysArchives(); diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java index 5b2dcae175..225496ca0d 100644 --- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java +++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java @@ -2,9 +2,13 @@ package org.scummvm.scummvm; import android.app.Activity; import android.app.AlertDialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.media.AudioManager; +import android.net.Uri; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiInfo; import android.os.Build; import android.os.Bundle; import android.os.Environment; @@ -75,6 +79,21 @@ public class ScummVMActivity extends Activity { } @Override + protected void openUrl(String url) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); + } + + @Override + protected boolean isConnectionLimited() { + WifiManager wifiMgr = (WifiManager)getSystemService(Context.WIFI_SERVICE); + if (wifiMgr != null && wifiMgr.isWifiEnabled()) { + WifiInfo wifiInfo = wifiMgr.getConnectionInfo(); + return (wifiInfo == null || wifiInfo.getNetworkId() == -1); //WiFi is on, but it's not connected to any network + } + return true; + } + + @Override protected void setWindowCaption(final String caption) { runOnUiThread(new Runnable() { public void run() { diff --git a/backends/platform/dc/dc-fs.cpp b/backends/platform/dc/dc-fs.cpp index 77fe4143dd..4bd7e5a777 100644 --- a/backends/platform/dc/dc-fs.cpp +++ b/backends/platform/dc/dc-fs.cpp @@ -57,6 +57,7 @@ public: virtual Common::SeekableReadStream *createReadStream(); virtual Common::WriteStream *createWriteStream() { return 0; } + virtual bool create(bool isDirectory) { return false; } static AbstractFSNode *makeFileNodePath(const Common::String &path); }; diff --git a/backends/platform/dc/vmsave.cpp b/backends/platform/dc/vmsave.cpp index d896ba1299..5e8f50ca89 100644 --- a/backends/platform/dc/vmsave.cpp +++ b/backends/platform/dc/vmsave.cpp @@ -266,7 +266,7 @@ public: { return ::readSaveGame(buffer, _size, filename); } }; -class OutVMSave : public Common::OutSaveFile { +class OutVMSave : public Common::WriteStream { private: char *buffer; int _pos, size, committed; @@ -293,11 +293,24 @@ public: class VMSaveManager : public Common::SaveFileManager { public: + virtual void updateSavefilesList(Common::StringArray &lockedFiles) { + // TODO: implement this (locks files, preventing them from being listed, saved or loaded) + } - virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true) { - OutVMSave *s = new OutVMSave(filename.c_str()); - return compress ? Common::wrapCompressedWriteStream(s) : s; - } + virtual Common::InSaveFile *openRawFile(const Common::String &filename) { + InVMSave *s = new InVMSave(); + if (s->readSaveGame(filename.c_str())) { + return s; + } else { + delete s; + return NULL; + } + } + + virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true) { + OutVMSave *s = new OutVMSave(filename.c_str()); + return new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(s) : s); + } virtual Common::InSaveFile *openForLoading(const Common::String &filename) { InVMSave *s = new InVMSave(); diff --git a/backends/platform/ds/arm9/source/gbampsave.cpp b/backends/platform/ds/arm9/source/gbampsave.cpp index ef6091e2a2..9991a3253a 100644 --- a/backends/platform/ds/arm9/source/gbampsave.cpp +++ b/backends/platform/ds/arm9/source/gbampsave.cpp @@ -45,6 +45,17 @@ static Common::String getSavePath() { // GBAMP Save File Manager ////////////////////////// +void GBAMPSaveFileManager::updateSavefilesList(Common::StringArray &lockedFiles) { + // TODO: implement this + // in this method manager should remember lockedFiles + // these files must not be opened for loading or saving, or listed by listSavefiles() +} + +Common::InSaveFile *GBAMPSaveFileManager::openRawFile(const Common::String &filename) { + // TODO: make sure it returns raw file, not uncompressed save contents + return openForLoading(filename); +} + Common::OutSaveFile *GBAMPSaveFileManager::openForSaving(const Common::String &filename, bool compress) { Common::String fileSpec = getSavePath(); if (fileSpec.lastChar() != '/') @@ -56,7 +67,7 @@ Common::OutSaveFile *GBAMPSaveFileManager::openForSaving(const Common::String &f Common::WriteStream *stream = DS::DSFileStream::makeFromPath(fileSpec, true); // Use a write buffer stream = Common::wrapBufferedWriteStream(stream, SAVE_BUFFER_SIZE); - return stream; + return new Common::OutSaveFile(stream); } Common::InSaveFile *GBAMPSaveFileManager::openForLoading(const Common::String &filename) { diff --git a/backends/platform/ds/arm9/source/gbampsave.h b/backends/platform/ds/arm9/source/gbampsave.h index d86db2ec70..d30e3ab177 100644 --- a/backends/platform/ds/arm9/source/gbampsave.h +++ b/backends/platform/ds/arm9/source/gbampsave.h @@ -27,6 +27,9 @@ class GBAMPSaveFileManager : public Common::SaveFileManager { public: + virtual void updateSavefilesList(Common::StringArray &lockedFiles); + virtual Common::InSaveFile *openRawFile(const Common::String &filename); + virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true); virtual Common::InSaveFile *openForLoading(const Common::String &filename); diff --git a/backends/platform/n64/framfs_save_manager.h b/backends/platform/n64/framfs_save_manager.h index 9bd4ee579e..24f9bf10ce 100644 --- a/backends/platform/n64/framfs_save_manager.h +++ b/backends/platform/n64/framfs_save_manager.h @@ -65,7 +65,7 @@ public: } }; -class OutFRAMSave : public Common::OutSaveFile { +class OutFRAMSave : public Common::WriteStream { private: FRAMFILE *fd; @@ -102,11 +102,26 @@ public: class FRAMSaveManager : public Common::SaveFileManager { public: + virtual void updateSavefilesList(Common::StringArray &lockedFiles) { + // this method is used to lock saves while cloud syncing + // as there is no network on N64, this method wouldn't be used + // thus it's not implemtented + } + + virtual Common::InSaveFile *openRawFile(const Common::String &filename) { + InFRAMSave *s = new InFRAMSave(); + if (s->readSaveGame(filename.c_str())) { + return s; + } else { + delete s; + return 0; + } + } virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true) { OutFRAMSave *s = new OutFRAMSave(filename.c_str()); if (!s->err()) { - return compress ? Common::wrapCompressedWriteStream(s) : s; + return new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(s) : s); } else { delete s; return 0; diff --git a/backends/platform/n64/pakfs_save_manager.h b/backends/platform/n64/pakfs_save_manager.h index 0c08f0c506..8e16d1fce4 100644 --- a/backends/platform/n64/pakfs_save_manager.h +++ b/backends/platform/n64/pakfs_save_manager.h @@ -65,7 +65,7 @@ public: } }; -class OutPAKSave : public Common::OutSaveFile { +class OutPAKSave : public Common::WriteStream { private: PAKFILE *fd; @@ -104,11 +104,26 @@ public: class PAKSaveManager : public Common::SaveFileManager { public: + virtual void updateSavefilesList(Common::StringArray &lockedFiles) { + // this method is used to lock saves while cloud syncing + // as there is no network on N64, this method wouldn't be used + // thus it's not implemtented + } + + virtual Common::InSaveFile *openRawFile(const Common::String &filename) { + InPAKSave *s = new InPAKSave(); + if (s->readSaveGame(filename.c_str())) { + return s; + } else { + delete s; + return NULL; + } + } virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true) { OutPAKSave *s = new OutPAKSave(filename.c_str()); if (!s->err()) { - return compress ? Common::wrapCompressedWriteStream(s) : s; + return new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(s) : s); } else { delete s; return NULL; diff --git a/backends/platform/ps2/savefilemgr.cpp b/backends/platform/ps2/savefilemgr.cpp index 4fd2b1c72b..4cd988074e 100644 --- a/backends/platform/ps2/savefilemgr.cpp +++ b/backends/platform/ps2/savefilemgr.cpp @@ -82,7 +82,11 @@ void Ps2SaveFileManager::mcSplit(char *full, char *game, char *ext) { // TODO } -Common::InSaveFile *Ps2SaveFileManager::openForLoading(const Common::String &filename) { +void Ps2SaveFileManager::updateSavefilesList(Common::StringArray &lockedFiles) { + // TODO: implement this (locks files, preventing them from being listed, saved or loaded) +} + +Common::InSaveFile *Ps2SaveFileManager::openRawFile(const Common::String &filename) { Common::FSNode savePath(ConfMan.get("savepath")); // TODO: is this fast? Common::SeekableReadStream *sf; @@ -141,7 +145,12 @@ Common::InSaveFile *Ps2SaveFileManager::openForLoading(const Common::String &fil // _screen->wantAnim(false); - return Common::wrapCompressedReadStream(sf); + return sf; +} + +Common::InSaveFile *Ps2SaveFileManager::openForLoading(const Common::String &filename) { + Common::SeekableReadStream *sf = openRawFile(filename); + return (sf == NULL ? NULL : Common::wrapCompressedReadStream(sf)); } Common::OutSaveFile *Ps2SaveFileManager::openForSaving(const Common::String &filename, bool compress) { @@ -192,7 +201,7 @@ Common::OutSaveFile *Ps2SaveFileManager::openForSaving(const Common::String &fil } _screen->wantAnim(false); - return compress ? Common::wrapCompressedWriteStream(sf) : sf; + return new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(sf) : sf); } bool Ps2SaveFileManager::removeSavefile(const Common::String &filename) { diff --git a/backends/platform/ps2/savefilemgr.h b/backends/platform/ps2/savefilemgr.h index 547f16fa77..3d45382c64 100644 --- a/backends/platform/ps2/savefilemgr.h +++ b/backends/platform/ps2/savefilemgr.h @@ -34,6 +34,9 @@ public: Ps2SaveFileManager(OSystem_PS2 *system, Gs2dScreen *screen); virtual ~Ps2SaveFileManager(); + virtual void updateSavefilesList(Common::StringArray &lockedFiles); + virtual Common::InSaveFile *openRawFile(const Common::String &filename); + virtual Common::InSaveFile *openForLoading(const Common::String &filename); virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true); virtual Common::StringArray listSavefiles(const Common::String &pattern); diff --git a/backends/platform/sdl/macosx/macosx.cpp b/backends/platform/sdl/macosx/macosx.cpp index 7652c0d833..c4e83dc9d3 100644 --- a/backends/platform/sdl/macosx/macosx.cpp +++ b/backends/platform/sdl/macosx/macosx.cpp @@ -33,6 +33,7 @@ #include "backends/platform/sdl/macosx/macosx.h" #include "backends/updates/macosx/macosx-updates.h" #include "backends/taskbar/macosx/macosx-taskbar.h" +#include "backends/platform/sdl/macosx/macosx_wrapper.h" #include "common/archive.h" #include "common/config-manager.h" @@ -106,7 +107,7 @@ void OSystem_MacOSX::addSysArchivesToSearchSet(Common::SearchSet &s, int priorit } bool OSystem_MacOSX::hasFeature(Feature f) { - if (f == kFeatureDisplayLogFile) + if (f == kFeatureDisplayLogFile || f == kFeatureClipboardSupport) return true; return OSystem_POSIX::hasFeature(f); } @@ -124,6 +125,14 @@ bool OSystem_MacOSX::displayLogFile() { return err != noErr; } +bool OSystem_MacOSX::hasTextInClipboard() { + return hasTextInClipboardMacOSX(); +} + +Common::String OSystem_MacOSX::getTextFromClipboard() { + return getTextFromClipboardMacOSX(); +} + Common::String OSystem_MacOSX::getSystemLanguage() const { #if defined(USE_DETECTLANG) && defined(USE_TRANSLATION) CFArrayRef availableLocalizations = CFBundleCopyBundleLocalizations(CFBundleGetMainBundle()); diff --git a/backends/platform/sdl/macosx/macosx.h b/backends/platform/sdl/macosx/macosx.h index 6905284a5f..0c755cbf6d 100644 --- a/backends/platform/sdl/macosx/macosx.h +++ b/backends/platform/sdl/macosx/macosx.h @@ -33,6 +33,9 @@ public: virtual bool displayLogFile(); + virtual bool hasTextInClipboard(); + virtual Common::String getTextFromClipboard(); + virtual Common::String getSystemLanguage() const; virtual void init(); diff --git a/backends/platform/sdl/macosx/macosx_wrapper.h b/backends/platform/sdl/macosx/macosx_wrapper.h new file mode 100644 index 0000000000..3b346fc486 --- /dev/null +++ b/backends/platform/sdl/macosx/macosx_wrapper.h @@ -0,0 +1,31 @@ +/* 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. + * + */ + +#ifndef PLATFORM_SDL_MACOSX_WRAPPER_H +#define PLATFORM_SDL_MACOSX_WRAPPER_H + +#include <common/str.h> + +bool hasTextInClipboardMacOSX(); +Common::String getTextFromClipboardMacOSX(); + +#endif diff --git a/backends/platform/sdl/macosx/macosx_wrapper.mm b/backends/platform/sdl/macosx/macosx_wrapper.mm new file mode 100644 index 0000000000..8ec9eac5ac --- /dev/null +++ b/backends/platform/sdl/macosx/macosx_wrapper.mm @@ -0,0 +1,48 @@ +/* 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. + * + */ + +// Disable symbol overrides so that we can use system headers. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "backends/platform/sdl/macosx/macosx_wrapper.h" + +#include <AppKit/NSPasteboard.h> +#include <Foundation/NSArray.h> + +bool hasTextInClipboardMacOSX() { + return [[NSPasteboard generalPasteboard] availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]] != nil; +} + +Common::String getTextFromClipboardMacOSX() { + if (!hasTextInClipboardMacOSX()) + return Common::String(); + // Note: on OS X 10.6 and above it is recommanded to use NSPasteboardTypeString rather than NSStringPboardType. + // But since we still target older version use NSStringPboardType. + NSPasteboard *pb = [NSPasteboard generalPasteboard]; + NSString* str = [pb stringForType:NSStringPboardType]; + if (str == nil) + return Common::String(); + // If the string cannot be represented using the requested encoding we get a null pointer below. + // This is fine as ScummVM would not know what to do with non-ASCII characters (although maybe + // we should use NSISOLatin1StringEncoding?). + return Common::String([str cStringUsingEncoding:NSASCIIStringEncoding]); +} diff --git a/backends/platform/sdl/module.mk b/backends/platform/sdl/module.mk index 74dd506d31..84ce272d3c 100644 --- a/backends/platform/sdl/module.mk +++ b/backends/platform/sdl/module.mk @@ -14,6 +14,7 @@ ifdef MACOSX MODULE_OBJS += \ macosx/macosx-main.o \ macosx/macosx.o \ + macosx/macosx_wrapper.o \ macosx/appmenu_osx.o endif diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp index dca6891fef..6862bb349f 100644 --- a/backends/platform/sdl/sdl.cpp +++ b/backends/platform/sdl/sdl.cpp @@ -60,6 +60,15 @@ #endif // !WIN32 #endif +#ifdef USE_SDL_NET +#include <SDL/SDL_net.h> +#endif + +#if SDL_VERSION_ATLEAST(2, 0, 0) +#include <SDL2/SDL.h> +#include <SDL2/SDL_clipboard.h> +#endif + OSystem_SDL::OSystem_SDL() : #ifdef USE_OPENGL @@ -73,6 +82,9 @@ OSystem_SDL::OSystem_SDL() #endif _inited(false), _initedSDL(false), +#ifdef USE_SDL_NET + _initedSDLnet(false), +#endif _logger(0), _mixerManager(0), _eventSource(0), @@ -120,6 +132,10 @@ OSystem_SDL::~OSystem_SDL() { delete _logger; _logger = 0; +#ifdef USE_SDL_NET + if (_initedSDLnet) SDLNet_Quit(); +#endif + SDL_Quit(); } @@ -160,6 +176,13 @@ void OSystem_SDL::init() { } +bool OSystem_SDL::hasFeature(Feature f) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + if (f == kFeatureClipboardSupport) return true; +#endif + return ModularBackend::hasFeature(f); +} + void OSystem_SDL::initBackend() { // Check if backend has not been initialized assert(!_inited); @@ -294,6 +317,17 @@ void OSystem_SDL::initSDL() { _initedSDL = true; } + +#ifdef USE_SDL_NET + // Check if SDL_net has not been initialized + if (!_initedSDLnet) { + // Initialize SDL_net + if (SDLNet_Init() == -1) + error("Could not initialize SDL_net: %s", SDLNet_GetError()); + + _initedSDLnet = true; + } +#endif } void OSystem_SDL::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) { @@ -431,6 +465,26 @@ Common::String OSystem_SDL::getSystemLanguage() const { #endif // USE_DETECTLANG } +bool OSystem_SDL::hasTextInClipboard() { +#if SDL_VERSION_ATLEAST(2, 0, 0) + return SDL_HasClipboardText() == SDL_TRUE; +#else + return false; +#endif +} + +Common::String OSystem_SDL::getTextFromClipboard() { + if (!hasTextInClipboard()) return ""; + +#if SDL_VERSION_ATLEAST(2, 0, 0) + char *text = SDL_GetClipboardText(); + if (text == nullptr) return ""; + return text; +#else + return ""; +#endif +} + uint32 OSystem_SDL::getMillis(bool skipRecord) { uint32 millis = SDL_GetTicks(); diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h index 1fe670c5c3..17b4e9b001 100644 --- a/backends/platform/sdl/sdl.h +++ b/backends/platform/sdl/sdl.h @@ -55,6 +55,8 @@ public: */ virtual SdlMixerManager *getMixerManager(); + virtual bool hasFeature(Feature f); + // Override functions from ModularBackend and OSystem virtual void initBackend(); #if defined(USE_TASKBAR) @@ -69,6 +71,10 @@ public: virtual Common::String getSystemLanguage() const; + // Clipboard + virtual bool hasTextInClipboard(); + virtual Common::String getTextFromClipboard(); + virtual void setWindowCaption(const char *caption); virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0); virtual uint32 getMillis(bool skipRecord = false); @@ -81,6 +87,9 @@ public: protected: bool _inited; bool _initedSDL; +#ifdef USE_SDL_NET + bool _initedSDLnet; +#endif /** * Mixer manager that configures and setups SDL for diff --git a/backends/saves/default/default-saves.cpp b/backends/saves/default/default-saves.cpp index daec36ae72..8a7fba46f7 100644 --- a/backends/saves/default/default-saves.cpp +++ b/backends/saves/default/default-saves.cpp @@ -27,6 +27,11 @@ #include "common/scummsys.h" +#ifdef USE_LIBCURL +#include "backends/cloud/cloudmanager.h" +#include "common/file.h" +#endif + #if !defined(DISABLE_DEFAULT_SAVEFILEMANAGER) #include "backends/saves/default/default-saves.h" @@ -42,6 +47,10 @@ #include <errno.h> // for removeSavefile() #endif +#ifdef USE_LIBCURL +const char *DefaultSaveFileManager::TIMESTAMPS_FILENAME = "timestamps"; +#endif + DefaultSaveFileManager::DefaultSaveFileManager() { } @@ -59,28 +68,62 @@ void DefaultSaveFileManager::checkPath(const Common::FSNode &dir) { } } +void DefaultSaveFileManager::updateSavefilesList(Common::StringArray &lockedFiles) { + //make it refresh the cache next time it lists the saves + _cachedDirectory = ""; + + //remember the locked files list because some of these files don't exist yet + _lockedFiles = lockedFiles; +} + Common::StringArray DefaultSaveFileManager::listSavefiles(const Common::String &pattern) { // Assure the savefile name cache is up-to-date. assureCached(getSavePath()); if (getError().getCode() != Common::kNoError) return Common::StringArray(); + Common::HashMap<Common::String, bool> locked; + for (Common::StringArray::const_iterator i = _lockedFiles.begin(), end = _lockedFiles.end(); i != end; ++i) { + locked[*i] = true; + } + Common::StringArray results; for (SaveFileCache::const_iterator file = _saveFileCache.begin(), end = _saveFileCache.end(); file != end; ++file) { - if (file->_key.matchString(pattern, true)) { + if (!locked.contains(file->_key) && file->_key.matchString(pattern, true)) { results.push_back(file->_key); } } - return results; } +Common::InSaveFile *DefaultSaveFileManager::openRawFile(const Common::String &filename) { + // Assure the savefile name cache is up-to-date. + assureCached(getSavePath()); + if (getError().getCode() != Common::kNoError) + return nullptr; + + SaveFileCache::const_iterator file = _saveFileCache.find(filename); + if (file == _saveFileCache.end()) { + return nullptr; + } else { + // Open the file for loading. + Common::SeekableReadStream *sf = file->_value.createReadStream(); + return sf; + } +} + Common::InSaveFile *DefaultSaveFileManager::openForLoading(const Common::String &filename) { // Assure the savefile name cache is up-to-date. assureCached(getSavePath()); if (getError().getCode() != Common::kNoError) return nullptr; + for (Common::StringArray::const_iterator i = _lockedFiles.begin(), end = _lockedFiles.end(); i != end; ++i) { + if (filename == *i) { + return nullptr; //file is locked, no loading available + } + } + SaveFileCache::const_iterator file = _saveFileCache.find(filename); if (file == _saveFileCache.end()) { return nullptr; @@ -98,6 +141,19 @@ Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const Common::String if (getError().getCode() != Common::kNoError) return nullptr; + for (Common::StringArray::const_iterator i = _lockedFiles.begin(), end = _lockedFiles.end(); i != end; ++i) { + if (filename == *i) { + return nullptr; //file is locked, no saving available + } + } + +#ifdef USE_LIBCURL + // Update file's timestamp + Common::HashMap<Common::String, uint32> timestamps = loadTimestamps(); + timestamps[filename] = INVALID_TIMESTAMP; + saveTimestamps(timestamps); +#endif + // Obtain node. SaveFileCache::const_iterator file = _saveFileCache.find(filename); Common::FSNode fileNode; @@ -112,7 +168,7 @@ Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const Common::String // Open the file for saving. Common::WriteStream *const sf = fileNode.createWriteStream(); - Common::OutSaveFile *const result = compress ? Common::wrapCompressedWriteStream(sf) : sf; + Common::OutSaveFile *const result = new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(sf) : sf); // Add file to cache now that it exists. _saveFileCache[filename] = Common::FSNode(fileNode.getPath()); @@ -181,6 +237,12 @@ void DefaultSaveFileManager::assureCached(const Common::String &savePathName) { // Check that path exists and is usable. checkPath(Common::FSNode(savePathName)); +#ifdef USE_LIBCURL + Common::Array<Common::String> files = CloudMan.getSyncingFiles(); //returns empty array if not syncing + if (!files.empty()) updateSavefilesList(files); //makes this cache invalid + else _lockedFiles = files; +#endif + if (_cachedDirectory == savePathName) { return; } @@ -216,4 +278,102 @@ void DefaultSaveFileManager::assureCached(const Common::String &savePathName) { _cachedDirectory = savePathName; } +#ifdef USE_LIBCURL + +Common::HashMap<Common::String, uint32> DefaultSaveFileManager::loadTimestamps() { + Common::HashMap<Common::String, uint32> timestamps; + + //refresh the files list + Common::Array<Common::String> files; + g_system->getSavefileManager()->updateSavefilesList(files); + + //start with listing all the files in saves/ directory and setting invalid timestamp to them + Common::StringArray localFiles = g_system->getSavefileManager()->listSavefiles("*"); + for (uint32 i = 0; i < localFiles.size(); ++i) + timestamps[localFiles[i]] = INVALID_TIMESTAMP; + + //now actually load timestamps from file + Common::InSaveFile *file = g_system->getSavefileManager()->openRawFile(TIMESTAMPS_FILENAME); + if (!file) { + warning("DefaultSaveFileManager: failed to open '%s' file to load timestamps", TIMESTAMPS_FILENAME); + return timestamps; + } + + while (!file->eos()) { + //read filename into buffer (reading until the first ' ') + Common::String buffer; + while (!file->eos()) { + byte b = file->readByte(); + if (b == ' ') break; + buffer += (char)b; + } + + //read timestamp info buffer (reading until ' ' or some line ending char) + Common::String filename = buffer; + while (true) { + bool lineEnded = false; + buffer = ""; + while (!file->eos()) { + byte b = file->readByte(); + if (b == ' ' || b == '\n' || b == '\r') { + lineEnded = (b == '\n'); + break; + } + buffer += (char)b; + } + + if (buffer == "" && file->eos()) break; + if (!lineEnded) filename += " " + buffer; + else break; + } + + //parse timestamp + uint32 timestamp = buffer.asUint64(); + if (buffer == "" || timestamp == 0) break; + timestamps[filename] = timestamp; + } + + delete file; + return timestamps; +} + +void DefaultSaveFileManager::saveTimestamps(Common::HashMap<Common::String, uint32> ×tamps) { + Common::DumpFile f; + Common::String filename = concatWithSavesPath(TIMESTAMPS_FILENAME); + if (!f.open(filename, true)) { + warning("DefaultSaveFileManager: failed to open '%s' file to save timestamps", filename.c_str()); + return; + } + + for (Common::HashMap<Common::String, uint32>::iterator i = timestamps.begin(); i != timestamps.end(); ++i) { + Common::String data = i->_key + Common::String::format(" %u\n", i->_value); + if (f.write(data.c_str(), data.size()) != data.size()) { + warning("DefaultSaveFileManager: failed to write timestamps data into '%s'", filename.c_str()); + return; + } + } + + f.flush(); + f.finalize(); + f.close(); +} + +Common::String DefaultSaveFileManager::concatWithSavesPath(Common::String name) { + DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager()); + Common::String path = (manager ? manager->getSavePath() : ConfMan.get("savepath")); + if (path.size() > 0 && (path.lastChar() == '/' || path.lastChar() == '\\')) + return path + name; + + //simple heuristic to determine which path separator to use + int backslashes = 0; + for (uint32 i = 0; i < path.size(); ++i) + if (path[i] == '/') --backslashes; + else if (path[i] == '\\') ++backslashes; + + if (backslashes > 0) return path + '\\' + name; + return path + '/' + name; +} + +#endif // ifdef USE_LIBCURL + #endif // !defined(DISABLE_DEFAULT_SAVEFILEMANAGER) diff --git a/backends/saves/default/default-saves.h b/backends/saves/default/default-saves.h index bf4ca0229d..f2453810bf 100644 --- a/backends/saves/default/default-saves.h +++ b/backends/saves/default/default-saves.h @@ -27,7 +27,8 @@ #include "common/savefile.h" #include "common/str.h" #include "common/fs.h" -#include "common/hashmap.h" +#include "common/hash-str.h" +#include <limits.h> /** * Provides a default savefile manager implementation for common platforms. @@ -37,11 +38,24 @@ public: DefaultSaveFileManager(); DefaultSaveFileManager(const Common::String &defaultSavepath); + virtual void updateSavefilesList(Common::StringArray &lockedFiles); virtual Common::StringArray listSavefiles(const Common::String &pattern); + virtual Common::InSaveFile *openRawFile(const Common::String &filename); virtual Common::InSaveFile *openForLoading(const Common::String &filename); virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true); virtual bool removeSavefile(const Common::String &filename); +#ifdef USE_LIBCURL + + static const uint32 INVALID_TIMESTAMP = UINT_MAX; + static const char *TIMESTAMPS_FILENAME; + + static Common::HashMap<Common::String, uint32> loadTimestamps(); + static void saveTimestamps(Common::HashMap<Common::String, uint32> ×tamps); + static Common::String concatWithSavesPath(Common::String name); + +#endif + protected: /** * Get the path to the savegame directory. @@ -74,6 +88,12 @@ protected: */ SaveFileCache _saveFileCache; + /** + * List of "locked" files. These cannot be used for saving/loading + * because CloudManager is downloading those. + */ + Common::StringArray _lockedFiles; + private: /** * The currently cached directory. diff --git a/backends/saves/savefile.cpp b/backends/saves/savefile.cpp index b04c53d832..e21ea16ad8 100644 --- a/backends/saves/savefile.cpp +++ b/backends/saves/savefile.cpp @@ -23,9 +23,37 @@ #include "common/util.h" #include "common/savefile.h" #include "common/str.h" +#ifdef USE_LIBCURL +#include "backends/cloud/cloudmanager.h" +#endif namespace Common { +OutSaveFile::OutSaveFile(WriteStream *w): _wrapped(w) {} + +OutSaveFile::~OutSaveFile() {} + +bool OutSaveFile::err() const { return _wrapped->err(); } + +void OutSaveFile::clearErr() { _wrapped->clearErr(); } + +void OutSaveFile::finalize() { + _wrapped->finalize(); +#ifdef USE_LIBCURL + CloudMan.syncSaves(); +#endif +} + +bool OutSaveFile::flush() { return _wrapped->flush(); } + +uint32 OutSaveFile::write(const void *dataPtr, uint32 dataSize) { + return _wrapped->write(dataPtr, dataSize); +} + +int32 OutSaveFile::pos() const { + return _wrapped->pos(); +} + bool SaveFileManager::copySavefile(const String &oldFilename, const String &newFilename) { InSaveFile *inFile = 0; OutSaveFile *outFile = 0; diff --git a/base/commandLine.cpp b/base/commandLine.cpp index 2c24c018ee..c2b4ea765f 100644 --- a/base/commandLine.cpp +++ b/base/commandLine.cpp @@ -97,6 +97,7 @@ static const char HELP_STRING[] = " -d, --debuglevel=NUM Set debug verbosity level\n" " --debugflags=FLAGS Enable engine specific debug flags\n" " (separated by commas)\n" + " --debug-channels-only Show only the specified debug channels\n" " -u, --dump-scripts Enable script dumping if a directory called 'dumps'\n" " exists in the current directory\n" "\n" @@ -426,6 +427,9 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha DO_LONG_OPTION("debugflags") END_OPTION + DO_LONG_OPTION_BOOL("debug-channels-only") + END_OPTION + DO_OPTION('e', "music-driver") END_OPTION diff --git a/base/main.cpp b/base/main.cpp index 349f719ed5..6b6868b78a 100644 --- a/base/main.cpp +++ b/base/main.cpp @@ -66,6 +66,13 @@ #endif #include "backends/keymapper/keymapper.h" +#ifdef USE_LIBCURL +#include "backends/cloud/cloudmanager.h" +#include "backends/networking/curl/connectionmanager.h" +#endif +#ifdef USE_SDL_NET +#include "backends/networking/sdl_net/localwebserver.h" +#endif #if defined(_WIN32_WCE) #include "backends/platform/wince/CELauncherDialog.h" @@ -391,6 +398,10 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) { } else if (ConfMan.hasKey("debugflags")) specialDebug = ConfMan.get("debugflags"); + if (settings.contains("debug-channels-only")) + gDebugChannelsOnly = true; + + PluginManager::instance().init(); PluginManager::instance().loadAllPlugins(); // load plugins for cached plugin manager @@ -471,6 +482,11 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) { dlg.runModal(); } #endif + +#ifdef USE_LIBCURL + CloudMan.init(); + CloudMan.syncSaves(); +#endif // Unless a game was specified, show the launcher dialog if (0 == ConfMan.getActiveDomain()) @@ -580,6 +596,14 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) { launcherDialog(); } } +#ifdef USE_SDL_NET + Networking::LocalWebserver::destroy(); +#endif +#ifdef USE_LIBCURL + Networking::ConnectionManager::destroy(); + //I think it's important to destroy it after ConnectionManager + Cloud::CloudManager::destroy(); +#endif PluginManager::instance().unloadAllPlugins(); PluginManager::destroy(); GUI::GuiManager::destroy(); diff --git a/base/plugins.h b/base/plugins.h index 6037fc2d71..2793ff3ffd 100644 --- a/base/plugins.h +++ b/base/plugins.h @@ -79,8 +79,12 @@ extern int pluginTypeVersions[PLUGIN_TYPE_MAX]; #define PLUGIN_ENABLED_STATIC(ID) \ (ENABLE_##ID && !PLUGIN_ENABLED_DYNAMIC(ID)) -#define PLUGIN_ENABLED_DYNAMIC(ID) \ - (ENABLE_##ID && (ENABLE_##ID == DYNAMIC_PLUGIN) && defined(DYNAMIC_MODULES)) +#ifdef DYNAMIC_MODULES + #define PLUGIN_ENABLED_DYNAMIC(ID) \ + (ENABLE_##ID && (ENABLE_##ID == DYNAMIC_PLUGIN)) +#else + #define PLUGIN_ENABLED_DYNAMIC(ID) 0 +#endif // see comments in backends/plugins/elf/elf-provider.cpp #if defined(USE_ELF_LOADER) && defined(ELF_LOADER_CXA_ATEXIT) diff --git a/base/version.cpp b/base/version.cpp index 299e4ce325..d40dd573f0 100644 --- a/base/version.cpp +++ b/base/version.cpp @@ -154,4 +154,25 @@ const char *gScummVMFeatures = "" #ifdef ENABLE_VKEYBD "virtual keyboard " #endif + +#ifdef USE_CLOUD + "cloud (" +#ifdef USE_LIBCURL + "servers" +#ifdef USE_SDL_NET + ", " +#endif +#endif +#ifdef USE_SDL_NET + "local" +#endif + ") " +#else +#ifdef USE_LIBCURL + "libcurl " +#endif +#ifdef USE_SDL_NET + "SDL_net " +#endif +#endif ; diff --git a/common/callback.h b/common/callback.h new file mode 100644 index 0000000000..c6c249a511 --- /dev/null +++ b/common/callback.h @@ -0,0 +1,138 @@ +/* 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. + * + */ + +#ifndef COMMON_CALLBACK_H +#define COMMON_CALLBACK_H + +namespace Common { + +/** + * BaseCallback<S> is a simple base class for object-oriented callbacks. + * + * Object-oriented callbacks are such callbacks that know exact instance + * which method must be called. + * + * For backwards compatibility purposes, there is a GlobalFunctionCallback, + * which is BaseCallback<void *>, so it can be used with global C-like + * functions too. + * + * <S> is the type, which is passed to operator() of this callback. + * This allows you to specify that you accept a callback, which wants + * to receive an <S> object. + */ +template<typename S = void *> class BaseCallback { +public: + BaseCallback() {} + virtual ~BaseCallback() {} + virtual void operator()(S data) = 0; +}; + +/** + * GlobalFunctionCallback<T> is a simple wrapper for global C functions. + * + * If there is a method, which accepts BaseCallback<T>, you can + * easily pass your C function by passing + * new GlobalFunctionCallback<T>(yourFunction) + */ +template<typename T> class GlobalFunctionCallback: public BaseCallback<T> { + typedef void(*GlobalFunction)(T result); + GlobalFunction _callback; + +public: + GlobalFunctionCallback(GlobalFunction cb): _callback(cb) {} + virtual ~GlobalFunctionCallback() {} + virtual void operator()(T data) { + if (_callback) _callback(data); + } +}; + +/** + * Callback<T, S> implements an object-oriented callback. + * + * <T> stands for a class which method you want to call. + * <S>, again, is the type of an object passed to operator(). + * + * So, if you have void MyClass::myMethod(AnotherClass) method, + * the corresponding callback is Callback<MyClass, AnotherClass>. + * You create it similarly to this: + * new Callback<MyClass, AnotherClass>( + * pointerToMyClassObject, + * &MyClass::myMethod + * ) + */ +template<class T, typename S = void *> class Callback: public BaseCallback<S> { +protected: + typedef void(T::*TMethod)(S); + T *_object; + TMethod _method; +public: + Callback(T *object, TMethod method): _object(object), _method(method) {} + virtual ~Callback() {} + void operator()(S data) { (_object->*_method)(data); } +}; + +/** + * CallbackBridge<T, OS, S> helps you to chain callbacks. + * + * CallbackBridge keeps a pointer to BaseCallback<OS>. + * When its operator() is called, it passes this pointer + * along with the actual data (of type <S>) to the method + * of <T> class. + * + * This is needed when you have to call a callback only + * when your own callback is called. So, your callback + * is "inner" and the other one is "outer". + * + * CallbackBridge implements the "inner" one and calls + * the method you wanted. It passes the "outer", so you + * can call it from your method. You can ignore it, but + * probably there is no point in using CallbackBridge then. + * + * So, if you receive a BaseCallback<SomeClass> callback + * and you want to call it from your MyClass::myMethod method, + * you should create CallbackBridge<MyClass, SomeClass, S>, + * where <S> is data type you want to receive in MyClass::myMethod. + * + * You create it similarly to this: + * new Callback<MyClass, SomeClass, AnotherClass>( + * pointerToMyClassObject, + * &MyClass::myMethod, + * outerCallback + * ) + * where `outerCallback` is BaseCallback<SomeClass> and `myMethod` is: + * void MyClass::myMethod(BaseCallback<SomeClass> *, AnotherClass) + */ +template<class T, typename OS, typename S = void *> class CallbackBridge: public BaseCallback<S> { + typedef void(T::*TCallbackMethod)(BaseCallback<OS> *, S); + T *_object; + TCallbackMethod _method; + BaseCallback<OS> *_outerCallback; +public: + CallbackBridge(T *object, TCallbackMethod method, BaseCallback<OS> *outerCallback): + _object(object), _method(method), _outerCallback(outerCallback) {} + virtual ~CallbackBridge() {} + void operator()(S data) { (_object->*_method)(_outerCallback, data); } +}; + +} // End of namespace Common + +#endif diff --git a/common/config-manager.cpp b/common/config-manager.cpp index 24c877a4e9..fdd0c6f033 100644 --- a/common/config-manager.cpp +++ b/common/config-manager.cpp @@ -45,6 +45,10 @@ char const *const ConfigManager::kTransientDomain = "__TRANSIENT"; char const *const ConfigManager::kKeymapperDomain = "keymapper"; #endif +#ifdef USE_CLOUD +char const *const ConfigManager::kCloudDomain = "cloud"; +#endif + #pragma mark - @@ -67,6 +71,9 @@ void ConfigManager::copyFrom(ConfigManager &source) { #ifdef ENABLE_KEYMAPPER _keymapperDomain = source._keymapperDomain; #endif +#ifdef USE_CLOUD + _cloudDomain = source._cloudDomain; +#endif _domainSaveOrder = source._domainSaveOrder; _activeDomainName = source._activeDomainName; _activeDomain = &_gameDomains[_activeDomainName]; @@ -121,6 +128,10 @@ void ConfigManager::addDomain(const String &domainName, const ConfigManager::Dom } else if (domainName == kKeymapperDomain) { _keymapperDomain = domain; #endif +#ifdef USE_CLOUD + } else if (domainName == kCloudDomain) { + _cloudDomain = domain; +#endif } else if (domain.contains("gameid")) { // If the domain contains "gameid" we assume it's a game domain if (_gameDomains.contains(domainName)) @@ -160,6 +171,9 @@ void ConfigManager::loadFromStream(SeekableReadStream &stream) { #ifdef ENABLE_KEYMAPPER _keymapperDomain.clear(); #endif +#ifdef USE_CLOUD + _cloudDomain.clear(); +#endif // TODO: Detect if a domain occurs multiple times (or likewise, if // a key occurs multiple times inside one domain). @@ -272,6 +286,10 @@ void ConfigManager::flushToDisk() { // Write the keymapper domain writeDomain(*stream, kKeymapperDomain, _keymapperDomain); #endif +#ifdef USE_CLOUD + // Write the cloud domain + writeDomain(*stream, kCloudDomain, _cloudDomain); +#endif DomainMap::const_iterator d; @@ -359,6 +377,10 @@ const ConfigManager::Domain *ConfigManager::getDomain(const String &domName) con if (domName == kKeymapperDomain) return &_keymapperDomain; #endif +#ifdef USE_CLOUD + if (domName == kCloudDomain) + return &_cloudDomain; +#endif if (_gameDomains.contains(domName)) return &_gameDomains[domName]; if (_miscDomains.contains(domName)) @@ -379,6 +401,10 @@ ConfigManager::Domain *ConfigManager::getDomain(const String &domName) { if (domName == kKeymapperDomain) return &_keymapperDomain; #endif +#ifdef USE_CLOUD + if (domName == kCloudDomain) + return &_cloudDomain; +#endif if (_gameDomains.contains(domName)) return &_gameDomains[domName]; if (_miscDomains.contains(domName)) diff --git a/common/config-manager.h b/common/config-manager.h index 14f911f69d..669faaaf88 100644 --- a/common/config-manager.h +++ b/common/config-manager.h @@ -94,6 +94,11 @@ public: static char const *const kKeymapperDomain; #endif +#ifdef USE_CLOUD + /** The name of cloud domain used to store user's tokens */ + static char const *const kCloudDomain; +#endif + void loadDefaultConfigFile(); void loadConfigFile(const String &filename); @@ -188,6 +193,10 @@ private: Domain _keymapperDomain; #endif +#ifdef USE_CLOUD + Domain _cloudDomain; +#endif + Array<String> _domainSaveOrder; String _activeDomainName; diff --git a/common/debug.cpp b/common/debug.cpp index 182b28afdf..c61fc63dea 100644 --- a/common/debug.cpp +++ b/common/debug.cpp @@ -30,6 +30,7 @@ // TODO: Move gDebugLevel into namespace Common. int gDebugLevel = -1; +bool gDebugChannelsOnly = false; namespace Common { @@ -119,6 +120,18 @@ bool DebugManager::isDebugChannelEnabled(uint32 channel) { } // End of namespace Common +bool debugLevelSet(int level) { + return level <= gDebugLevel; +} + +bool debugChannelSet(int level, uint32 debugChannels) { + if (gDebugLevel != 11) + if (level > gDebugLevel || !(DebugMan.isDebugChannelEnabled(debugChannels))) + return false; + + return true; +} + #ifndef DISABLE_TEXT_CONSOLE @@ -137,6 +150,9 @@ static void debugHelper(const char *s, va_list va, bool caret = true) { void debug(const char *s, ...) { va_list va; + if (gDebugChannelsOnly) + return; + va_start(va, s); debugHelper(s, va); va_end(va); @@ -145,7 +161,7 @@ void debug(const char *s, ...) { void debug(int level, const char *s, ...) { va_list va; - if (level > gDebugLevel) + if (level > gDebugLevel || gDebugChannelsOnly) return; va_start(va, s); @@ -157,6 +173,9 @@ void debug(int level, const char *s, ...) { void debugN(const char *s, ...) { va_list va; + if (gDebugChannelsOnly) + return; + va_start(va, s); debugHelper(s, va, false); va_end(va); @@ -165,7 +184,7 @@ void debugN(const char *s, ...) { void debugN(int level, const char *s, ...) { va_list va; - if (level > gDebugLevel) + if (level > gDebugLevel || gDebugChannelsOnly) return; va_start(va, s); diff --git a/common/debug.h b/common/debug.h index b6e0679a12..883a0bf29d 100644 --- a/common/debug.h +++ b/common/debug.h @@ -31,11 +31,10 @@ inline void debug(const char *s, ...) {} inline void debug(int level, const char *s, ...) {} inline void debugN(const char *s, ...) {} inline void debugN(int level, const char *s, ...) {} -inline void debugC(int level, uint32 engineChannel, const char *s, ...) {} -inline void debugC(uint32 engineChannel, const char *s, ...) {} -inline void debugCN(int level, uint32 engineChannel, const char *s, ...) {} -inline void debugCN(uint32 engineChannel, const char *s, ...) {} - +inline void debugC(int level, uint32 debugChannels, const char *s, ...) {} +inline void debugC(uint32 debugChannels, const char *s, ...) {} +inline void debugCN(int level, uint32 debugChannels, const char *s, ...) {} +inline void debugCN(uint32 debugChannels, const char *s, ...) {} #else @@ -111,6 +110,18 @@ void debugCN(uint32 debugChannels, const char *s, ...) GCC_PRINTF(2, 3); #endif /** + * Returns true if the debug level is set to the specified level + */ +bool debugLevelSet(int level); + +/** + * Returns true if the debug level and channel are active + * + * @see enableDebugChannel + */ +bool debugChannelSet(int level, uint32 debugChannels); + +/** * The debug level. Initially set to -1, indicating that no debug output * should be shown. Positive values usually imply an increasing number of * debug output shall be generated, the higher the value, the more verbose the @@ -118,6 +129,15 @@ void debugCN(uint32 debugChannels, const char *s, ...) GCC_PRINTF(2, 3); */ extern int gDebugLevel; +/** + * Specify if we want to show only the debug channels and suppress + * the non-channeled output. + * + * This option is useful when you want to have higher levels of channels + * visible without the noise from other subsystems or OSystem. + */ +extern bool gDebugChannelsOnly; + //Global constant for EventRecorder debug channel enum GlobalDebugLevels { kDebugLevelEventRec = 1 << 30 diff --git a/common/file.cpp b/common/file.cpp index 4d9c630076..9797bcaa69 100644 --- a/common/file.cpp +++ b/common/file.cpp @@ -25,6 +25,8 @@ #include "common/file.h" #include "common/fs.h" #include "common/textconsole.h" +#include "common/system.h" +#include "backends/fs/fs-factory.h" namespace Common { @@ -149,10 +151,23 @@ DumpFile::~DumpFile() { close(); } -bool DumpFile::open(const String &filename) { +bool DumpFile::open(const String &filename, bool createPath) { assert(!filename.empty()); assert(!_handle); + if (createPath) { + for (uint32 i = 0; i < filename.size(); ++i) { + if (filename[i] == '/' || filename[i] == '\\') { + Common::String subpath = filename; + subpath.erase(i); + if (subpath.empty()) continue; + AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(subpath); + if (node->exists()) continue; + if (!node->create(true)) warning("DumpFile: unable to create directories from path prefix"); + } + } + } + FSNode node(filename); return open(node); } diff --git a/common/file.h b/common/file.h index 3d174834e9..8ad6249d6d 100644 --- a/common/file.h +++ b/common/file.h @@ -143,7 +143,7 @@ public: DumpFile(); virtual ~DumpFile(); - virtual bool open(const String &filename); + virtual bool open(const String &filename, bool createPath = false); virtual bool open(const FSNode &node); virtual void close(); diff --git a/common/gui_options.h b/common/gui_options.h index ec3eccd161..d17f45cac1 100644 --- a/common/gui_options.h +++ b/common/gui_options.h @@ -68,7 +68,7 @@ #define GUIO_GAMEOPTIONS6 "\055" #define GUIO_GAMEOPTIONS7 "\056" #define GUIO_GAMEOPTIONS8 "\057" -#define GUIO_GAMEOPTIONS9 "\058" +#define GUIO_GAMEOPTIONS9 "\060" #define GUIO0() (GUIO_NONE) #define GUIO1(a) (a) diff --git a/common/json.cpp b/common/json.cpp new file mode 100644 index 0000000000..f198e30b76 --- /dev/null +++ b/common/json.cpp @@ -0,0 +1,1099 @@ +/* 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. +* +*/ + +/* +* Files JSON.cpp and JSONValue.cpp part of the SimpleJSON Library - https://github.com/MJPA/SimpleJSON +* +* Copyright (C) 2010 Mike Anchor +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +#include "common/json.h" + +#ifdef __MINGW32__ +#define wcsncasecmp wcsnicmp +#endif + +// Macros to free an array/object +#define FREE_ARRAY(x) { JSONArray::iterator iter; for (iter = x.begin(); iter != x.end(); iter++) { delete *iter; } } +#define FREE_OBJECT(x) { JSONObject::iterator iter; for (iter = x.begin(); iter != x.end(); iter++) { delete (*iter)._value; } } + +namespace Common { + +/** +* Blocks off the public constructor +* +* @access private +* +*/ +JSON::JSON() {} + +/** +* Parses a complete JSON encoded string (UNICODE input version) +* +* @access public +* +* @param char* data The JSON text +* +* @return JSONValue* Returns a JSON Value representing the root, or NULL on error +*/ +JSONValue *JSON::parse(const char *data) { + // Skip any preceding whitespace, end of data = no JSON = fail + if (!skipWhitespace(&data)) + return NULL; + + // We need the start of a value here now... + JSONValue *value = JSONValue::parse(&data); + if (value == NULL) + return NULL; + + // Can be white space now and should be at the end of the string then... + if (skipWhitespace(&data)) { + delete value; + return NULL; + } + + // We're now at the end of the string + return value; +} + +/** +* Turns the passed in JSONValue into a JSON encode string +* +* @access public +* +* @param JSONValue* value The root value +* +* @return String Returns a JSON encoded string representation of the given value +*/ +String JSON::stringify(const JSONValue *value) { + if (value != NULL) + return value->stringify(); + else + return ""; +} + +/** +* Skips over any whitespace characters (space, tab, \r or \n) defined by the JSON spec +* +* @access protected +* +* @param char** data Pointer to a char* that contains the JSON text +* +* @return bool Returns true if there is more data, or false if the end of the text was reached +*/ +bool JSON::skipWhitespace(const char **data) { + while (**data != 0 && (**data == ' ' || **data == '\t' || **data == '\r' || **data == '\n')) + (*data)++; + + return **data != 0; +} + +/** +* Extracts a JSON String as defined by the spec - "<some chars>" +* Any escaped characters are swapped out for their unescaped values +* +* @access protected +* +* @param char** data Pointer to a char* that contains the JSON text +* @param String& str Reference to a String to receive the extracted string +* +* @return bool Returns true on success, false on failure +*/ +bool JSON::extractString(const char **data, String &str) { + str = ""; + + while (**data != 0) { + // Save the char so we can change it if need be + char next_char = **data; + + // Escaping something? + if (next_char == '\\') { + // Move over the escape char + (*data)++; + + // Deal with the escaped char + switch (**data) { + case '"': next_char = '"'; + break; + case '\\': next_char = '\\'; + break; + case '/': next_char = '/'; + break; + case 'b': next_char = '\b'; + break; + case 'f': next_char = '\f'; + break; + case 'n': next_char = '\n'; + break; + case 'r': next_char = '\r'; + break; + case 't': next_char = '\t'; + break; + case 'u': { + // We need 5 chars (4 hex + the 'u') or its not valid + if (!simplejson_wcsnlen(*data, 5)) + return false; + + // Deal with the chars + next_char = 0; + for (int i = 0; i < 4; i++) { + // Do it first to move off the 'u' and leave us on the + // final hex digit as we move on by one later on + (*data)++; + + next_char <<= 4; + + // Parse the hex digit + if (**data >= '0' && **data <= '9') + next_char |= (**data - '0'); + else if (**data >= 'A' && **data <= 'F') + next_char |= (10 + (**data - 'A')); + else if (**data >= 'a' && **data <= 'f') + next_char |= (10 + (**data - 'a')); + else { + // Invalid hex digit = invalid JSON + return false; + } + } + break; + } + + // By the spec, only the above cases are allowed + default: + return false; + } + } + + // End of the string? + else if (next_char == '"') { + (*data)++; + //str.reserve(); // Remove unused capacity //TODO + return true; + } + + // Disallowed char? + else if (next_char < ' ' && next_char != '\t') { + // SPEC Violation: Allow tabs due to real world cases + return false; + } + + // Add the next char + str += next_char; + + // Move on + (*data)++; + } + + // If we're here, the string ended incorrectly + return false; +} + +/** +* Parses some text as though it is an integer +* +* @access protected +* +* @param char** data Pointer to a char* that contains the JSON text +* +* @return double Returns the double value of the number found +*/ +double JSON::parseInt(const char **data) { + double integer = 0; + while (**data != 0 && **data >= '0' && **data <= '9') + integer = integer * 10 + (*(*data)++ - '0'); + + return integer; +} + +/** +* Parses some text as though it is a decimal +* +* @access protected +* +* @param char** data Pointer to a char* that contains the JSON text +* +* @return double Returns the double value of the decimal found +*/ +double JSON::parseDecimal(const char **data) { + double decimal = 0.0; + double factor = 0.1; + while (**data != 0 && **data >= '0' && **data <= '9') { + int digit = (*(*data)++ - '0'); + decimal = decimal + digit * factor; + factor *= 0.1; + } + return decimal; +} + +/** +* Parses a JSON encoded value to a JSONValue object +* +* @access protected +* +* @param char** data Pointer to a char* that contains the data +* +* @return JSONValue* Returns a pointer to a JSONValue object on success, NULL on error +*/ +JSONValue *JSONValue::parse(const char **data) { + // Is it a string? + if (**data == '"') { + String str; + if (!JSON::extractString(&(++(*data)), str)) + return NULL; + else + return new JSONValue(str); + } + + // Is it a boolean? + else if ((simplejson_wcsnlen(*data, 4) && scumm_strnicmp(*data, "true", 4) == 0) || (simplejson_wcsnlen(*data, 5) && scumm_strnicmp(*data, "false", 5) == 0)) { + bool value = scumm_strnicmp(*data, "true", 4) == 0; + (*data) += value ? 4 : 5; + return new JSONValue(value); + } + + // Is it a null? + else if (simplejson_wcsnlen(*data, 4) && scumm_strnicmp(*data, "null", 4) == 0) { + (*data) += 4; + return new JSONValue(); + } + + // Is it a number? + else if (**data == '-' || (**data >= '0' && **data <= '9')) { + // Negative? + bool neg = **data == '-'; + if (neg) (*data)++; + + long long int integer = 0; + double number = 0.0; + bool onlyInteger = true; + + // Parse the whole part of the number - only if it wasn't 0 + if (**data == '0') + (*data)++; + else if (**data >= '1' && **data <= '9') + number = integer = JSON::parseInt(data); + else + return NULL; + + // Could be a decimal now... + if (**data == '.') { + (*data)++; + + // Not get any digits? + if (!(**data >= '0' && **data <= '9')) + return NULL; + + // Find the decimal and sort the decimal place out + // Use ParseDecimal as ParseInt won't work with decimals less than 0.1 + // thanks to Javier Abadia for the report & fix + double decimal = JSON::parseDecimal(data); + + // Save the number + number += decimal; + onlyInteger = false; + } + + // Could be an exponent now... + if (**data == 'E' || **data == 'e') { + (*data)++; + + // Check signage of expo + bool neg_expo = false; + if (**data == '-' || **data == '+') { + neg_expo = **data == '-'; + (*data)++; + } + + // Not get any digits? + if (!(**data >= '0' && **data <= '9')) + return NULL; + + // Sort the expo out + double expo = JSON::parseInt(data); + for (double i = 0.0; i < expo; i++) + number = neg_expo ? (number / 10.0) : (number * 10.0); + onlyInteger = false; + } + + // Was it neg? + if (neg) number *= -1; + + if (onlyInteger) + return new JSONValue(neg ? -integer : integer); + + return new JSONValue(number); + } + + // An object? + else if (**data == '{') { + JSONObject object; + + (*data)++; + + while (**data != 0) { + // Whitespace at the start? + if (!JSON::skipWhitespace(data)) { + FREE_OBJECT(object); + return NULL; + } + + // Special case - empty object + if (object.size() == 0 && **data == '}') { + (*data)++; + return new JSONValue(object); + } + + // We want a string now... + String name; + if (!JSON::extractString(&(++(*data)), name)) { + FREE_OBJECT(object); + return NULL; + } + + // More whitespace? + if (!JSON::skipWhitespace(data)) { + FREE_OBJECT(object); + return NULL; + } + + // Need a : now + if (*((*data)++) != ':') { + FREE_OBJECT(object); + return NULL; + } + + // More whitespace? + if (!JSON::skipWhitespace(data)) { + FREE_OBJECT(object); + return NULL; + } + + // The value is here + JSONValue *value = parse(data); + if (value == NULL) { + FREE_OBJECT(object); + return NULL; + } + + // Add the name:value + if (object.find(name) != object.end()) + delete object[name]; + object[name] = value; + + // More whitespace? + if (!JSON::skipWhitespace(data)) { + FREE_OBJECT(object); + return NULL; + } + + // End of object? + if (**data == '}') { + (*data)++; + return new JSONValue(object); + } + + // Want a , now + if (**data != ',') { + FREE_OBJECT(object); + return NULL; + } + + (*data)++; + } + + // Only here if we ran out of data + FREE_OBJECT(object); + return NULL; + } + + // An array? + else if (**data == '[') { + JSONArray array; + + (*data)++; + + while (**data != 0) { + // Whitespace at the start? + if (!JSON::skipWhitespace(data)) { + FREE_ARRAY(array); + return NULL; + } + + // Special case - empty array + if (array.size() == 0 && **data == ']') { + (*data)++; + return new JSONValue(array); + } + + // Get the value + JSONValue *value = parse(data); + if (value == NULL) { + FREE_ARRAY(array); + return NULL; + } + + // Add the value + array.push_back(value); + + // More whitespace? + if (!JSON::skipWhitespace(data)) { + FREE_ARRAY(array); + return NULL; + } + + // End of array? + if (**data == ']') { + (*data)++; + return new JSONValue(array); + } + + // Want a , now + if (**data != ',') { + FREE_ARRAY(array); + return NULL; + } + + (*data)++; + } + + // Only here if we ran out of data + FREE_ARRAY(array); + return NULL; + } + + // Ran out of possibilites, it's bad! + else { + return NULL; + } +} + +/** +* Basic constructor for creating a JSON Value of type NULL +* +* @access public +*/ +JSONValue::JSONValue(/*NULL*/) { + _type = JSONType_Null; +} + +/** +* Basic constructor for creating a JSON Value of type String +* +* @access public +* +* @param char* m_char_value The string to use as the value +*/ +JSONValue::JSONValue(const char *charValue) { + _type = JSONType_String; + _stringValue = new String(String(charValue)); +} + +/** +* Basic constructor for creating a JSON Value of type String +* +* @access public +* +* @param String m_string_value The string to use as the value +*/ +JSONValue::JSONValue(const String &stringValue) { + _type = JSONType_String; + _stringValue = new String(stringValue); +} + +/** +* Basic constructor for creating a JSON Value of type Bool +* +* @access public +* +* @param bool m_bool_value The bool to use as the value +*/ +JSONValue::JSONValue(bool boolValue) { + _type = JSONType_Bool; + _boolValue = boolValue; +} + +/** +* Basic constructor for creating a JSON Value of type Number +* +* @access public +* +* @param double m_number_value The number to use as the value +*/ +JSONValue::JSONValue(double numberValue) { + _type = JSONType_Number; + _numberValue = numberValue; +} + +/** +* Basic constructor for creating a JSON Value of type Number (Integer) +* +* @access public +* +* @param int numberValue The number to use as the value +*/ +JSONValue::JSONValue(long long int numberValue) { + _type = JSONType_IntegerNumber; + _integerValue = numberValue; +} + +/** +* Basic constructor for creating a JSON Value of type Array +* +* @access public +* +* @param JSONArray m_array_value The JSONArray to use as the value +*/ +JSONValue::JSONValue(const JSONArray &arrayValue) { + _type = JSONType_Array; + _arrayValue = new JSONArray(arrayValue); +} + +/** +* Basic constructor for creating a JSON Value of type Object +* +* @access public +* +* @param JSONObject m_object_value The JSONObject to use as the value +*/ +JSONValue::JSONValue(const JSONObject &objectValue) { + _type = JSONType_Object; + _objectValue = new JSONObject(objectValue); +} + +/** +* Copy constructor to perform a deep copy of array / object values +* +* @access public +* +* @param JSONValue m_source The source JSONValue that is being copied +*/ +JSONValue::JSONValue(const JSONValue &source) { + _type = source._type; + + switch (_type) { + case JSONType_String: + _stringValue = new String(*source._stringValue); + break; + + case JSONType_Bool: + _boolValue = source._boolValue; + break; + + case JSONType_Number: + _numberValue = source._numberValue; + break; + + case JSONType_IntegerNumber: + _integerValue = source._integerValue; + break; + + case JSONType_Array: { + JSONArray source_array = *source._arrayValue; + JSONArray::iterator iter; + _arrayValue = new JSONArray(); + for (iter = source_array.begin(); iter != source_array.end(); iter++) + _arrayValue->push_back(new JSONValue(**iter)); + break; + } + + case JSONType_Object: { + JSONObject source_object = *source._objectValue; + _objectValue = new JSONObject(); + JSONObject::iterator iter; + for (iter = source_object.begin(); iter != source_object.end(); iter++) { + String name = (*iter)._key; + (*_objectValue)[name] = new JSONValue(*((*iter)._value)); + } + break; + } + + case JSONType_Null: + // Nothing to do. + break; + } +} + +/** +* The destructor for the JSON Value object +* Handles deleting the objects in the array or the object value +* +* @access public +*/ +JSONValue::~JSONValue() { + if (_type == JSONType_Array) { + JSONArray::iterator iter; + for (iter = _arrayValue->begin(); iter != _arrayValue->end(); iter++) + delete *iter; + delete _arrayValue; + } else if (_type == JSONType_Object) { + JSONObject::iterator iter; + for (iter = _objectValue->begin(); iter != _objectValue->end(); iter++) { + delete (*iter)._value; + } + delete _objectValue; + } else if (_type == JSONType_String) { + delete _stringValue; + } +} + +/** +* Checks if the value is a NULL +* +* @access public +* +* @return bool Returns true if it is a NULL value, false otherwise +*/ +bool JSONValue::isNull() const { + return _type == JSONType_Null; +} + +/** +* Checks if the value is a String +* +* @access public +* +* @return bool Returns true if it is a String value, false otherwise +*/ +bool JSONValue::isString() const { + return _type == JSONType_String; +} + +/** +* Checks if the value is a Bool +* +* @access public +* +* @return bool Returns true if it is a Bool value, false otherwise +*/ +bool JSONValue::isBool() const { + return _type == JSONType_Bool; +} + +/** +* Checks if the value is a Number +* +* @access public +* +* @return bool Returns true if it is a Number value, false otherwise +*/ +bool JSONValue::isNumber() const { + return _type == JSONType_Number; +} + +/** +* Checks if the value is an Integer +* +* @access public +* +* @return bool Returns true if it is an Integer value, false otherwise +*/ +bool JSONValue::isIntegerNumber() const { + return _type == JSONType_IntegerNumber; +} + +/** +* Checks if the value is an Array +* +* @access public +* +* @return bool Returns true if it is an Array value, false otherwise +*/ +bool JSONValue::isArray() const { + return _type == JSONType_Array; +} + +/** +* Checks if the value is an Object +* +* @access public +* +* @return bool Returns true if it is an Object value, false otherwise +*/ +bool JSONValue::isObject() const { + return _type == JSONType_Object; +} + +/** +* Retrieves the String value of this JSONValue +* Use isString() before using this method. +* +* @access public +* +* @return String Returns the string value +*/ +const String &JSONValue::asString() const { + return (*_stringValue); +} + +/** +* Retrieves the Bool value of this JSONValue +* Use isBool() before using this method. +* +* @access public +* +* @return bool Returns the bool value +*/ +bool JSONValue::asBool() const { + return _boolValue; +} + +/** +* Retrieves the Number value of this JSONValue +* Use isNumber() before using this method. +* +* @access public +* +* @return double Returns the number value +*/ +double JSONValue::asNumber() const { + return _numberValue; +} + +/** +* Retrieves the Integer value of this JSONValue +* Use isIntegerNumber() before using this method. +* +* @access public +* +* @return int Returns the number value +*/ +long long int JSONValue::asIntegerNumber() const { + return _integerValue; +} + +/** +* Retrieves the Array value of this JSONValue +* Use isArray() before using this method. +* +* @access public +* +* @return JSONArray Returns the array value +*/ +const JSONArray &JSONValue::asArray() const { + return (*_arrayValue); +} + +/** +* Retrieves the Object value of this JSONValue +* Use isObject() before using this method. +* +* @access public +* +* @return JSONObject Returns the object value +*/ +const JSONObject &JSONValue::asObject() const { + return (*_objectValue); +} + +/** +* Retrieves the number of children of this JSONValue. +* This number will be 0 or the actual number of children +* if isArray() or isObject(). +* +* @access public +* +* @return The number of children. +*/ +std::size_t JSONValue::countChildren() const { + switch (_type) { + case JSONType_Array: + return _arrayValue->size(); + case JSONType_Object: + return _objectValue->size(); + default: + return 0; + } +} + +/** +* Checks if this JSONValue has a child at the given index. +* Use isArray() before using this method. +* +* @access public +* +* @return bool Returns true if the array has a value at the given index. +*/ +bool JSONValue::hasChild(std::size_t index) const { + if (_type == JSONType_Array) { + return index < _arrayValue->size(); + } else { + return false; + } +} + +/** +* Retrieves the child of this JSONValue at the given index. +* Use isArray() before using this method. +* +* @access public +* +* @return JSONValue* Returns JSONValue at the given index or NULL +* if it doesn't exist. +*/ +JSONValue *JSONValue::child(std::size_t index) { + if (index < _arrayValue->size()) { + return (*_arrayValue)[index]; + } else { + return NULL; + } +} + +/** +* Checks if this JSONValue has a child at the given key. +* Use isObject() before using this method. +* +* @access public +* +* @return bool Returns true if the object has a value at the given key. +*/ +bool JSONValue::hasChild(const char *name) const { + if (_type == JSONType_Object) { + return _objectValue->find(name) != _objectValue->end(); + } else { + return false; + } +} + +/** +* Retrieves the child of this JSONValue at the given key. +* Use isObject() before using this method. +* +* @access public +* +* @return JSONValue* Returns JSONValue for the given key in the object +* or NULL if it doesn't exist. +*/ +JSONValue *JSONValue::child(const char *name) { + JSONObject::const_iterator it = _objectValue->find(name); + if (it != _objectValue->end()) { + return it->_value; + } else { + return NULL; + } +} + +/** +* Retrieves the keys of the JSON Object or an empty vector +* if this value is not an object. +* +* @access public +* +* @return std::vector<String> A vector containing the keys. +*/ +Array<String> JSONValue::objectKeys() const { + Array<String> keys; + + if (_type == JSONType_Object) { + JSONObject::const_iterator iter = _objectValue->begin(); + while (iter != _objectValue->end()) { + keys.push_back(iter->_key); + + iter++; + } + } + + return keys; +} + +/** +* Creates a JSON encoded string for the value with all necessary characters escaped +* +* @access public +* +* @param bool prettyprint Enable prettyprint +* +* @return String Returns the JSON string +*/ +String JSONValue::stringify(bool const prettyprint) const { + size_t const indentDepth = prettyprint ? 1 : 0; + return stringifyImpl(indentDepth); +} + + +/** +* Creates a JSON encoded string for the value with all necessary characters escaped +* +* @access private +* +* @param size_t indentDepth The prettyprint indentation depth (0 : no prettyprint) +* +* @return String Returns the JSON string +*/ +String JSONValue::stringifyImpl(size_t const indentDepth) const { + String ret_string; + size_t const indentDepth1 = indentDepth ? indentDepth + 1 : 0; + String const indentStr = indent(indentDepth); + String const indentStr1 = indent(indentDepth1); + + switch (_type) { + case JSONType_Null: + ret_string = "null"; + break; + + case JSONType_String: + ret_string = stringifyString(*_stringValue); + break; + + case JSONType_Bool: + ret_string = _boolValue ? "true" : "false"; + break; + + case JSONType_Number: { + if (isinf(_numberValue) || isnan(_numberValue)) + ret_string = "null"; + else { + char str[80]; + sprintf(str, "%lg", _numberValue); + ret_string = str; + } + break; + } + + case JSONType_IntegerNumber: { + char str[80]; + sprintf(str, "%lld", _integerValue); + ret_string = str; + break; + } + + case JSONType_Array: { + ret_string = indentDepth ? "[\n" + indentStr1 : "["; + JSONArray::const_iterator iter = _arrayValue->begin(); + while (iter != _arrayValue->end()) { + ret_string += (*iter)->stringifyImpl(indentDepth1); + + // Not at the end - add a separator + if (++iter != _arrayValue->end()) + ret_string += ","; + } + ret_string += indentDepth ? "\n" + indentStr + "]" : "]"; + break; + } + + case JSONType_Object: { + ret_string = indentDepth ? "{\n" + indentStr1 : "{"; + JSONObject::const_iterator iter = _objectValue->begin(); + while (iter != _objectValue->end()) { + ret_string += stringifyString((*iter)._key); + ret_string += ":"; + ret_string += (*iter)._value->stringifyImpl(indentDepth1); + + // Not at the end - add a separator + if (++iter != _objectValue->end()) + ret_string += ","; + } + ret_string += indentDepth ? "\n" + indentStr + "}" : "}"; + break; + } + } + + return ret_string; +} + +/** +* Creates a JSON encoded string with all required fields escaped +* Works from http://www.ecma-internationl.org/publications/files/ECMA-ST/ECMA-262.pdf +* Section 15.12.3. +* +* @access private +* +* @param String str The string that needs to have the characters escaped +* +* @return String Returns the JSON string +*/ +String JSONValue::stringifyString(const String &str) { + String str_out = "\""; + + String::const_iterator iter = str.begin(); + while (iter != str.end()) { + char chr = *iter; + + if (chr == '"' || chr == '\\' || chr == '/') { + str_out += '\\'; + str_out += chr; + } else if (chr == '\b') { + str_out += "\\b"; + } else if (chr == '\f') { + str_out += "\\f"; + } else if (chr == '\n') { + str_out += "\\n"; + } else if (chr == '\r') { + str_out += "\\r"; + } else if (chr == '\t') { + str_out += "\\t"; + } else if (chr < ' ' || chr > 126) { + str_out += "\\u"; + for (int i = 0; i < 4; i++) { + int value = (chr >> 12) & 0xf; + if (value >= 0 && value <= 9) + str_out += (char)('0' + value); + else if (value >= 10 && value <= 15) + str_out += (char)('A' + (value - 10)); + chr <<= 4; + } + } else { + str_out += chr; + } + + iter++; + } + + str_out += "\""; + return str_out; +} + +/** +* Creates the indentation string for the depth given +* +* @access private +* +* @param size_t indent The prettyprint indentation depth (0 : no indentation) +* +* @return String Returns the string +*/ +String JSONValue::indent(size_t depth) { + const size_t indent_step = 2; + depth ? --depth : 0; + String indentStr; + for (size_t i = 0; i < depth * indent_step; ++i) indentStr += ' '; + return indentStr; +} + +} // End of namespace Common diff --git a/common/json.h b/common/json.h new file mode 100644 index 0000000000..8e2eade669 --- /dev/null +++ b/common/json.h @@ -0,0 +1,166 @@ +/* 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. +* +*/ + +/* +* Files JSON.h and JSONValue.h part of the SimpleJSON Library - https://github.com/MJPA/SimpleJSON +* +* Copyright (C) 2010 Mike Anchor +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +#ifndef COMMON_JSON_H +#define COMMON_JSON_H + +#include "common/array.h" +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/str.h" + +// Win32 incompatibilities +#if defined(WIN32) && !defined(__GNUC__) +static inline bool isnan(double x) { + return x != x; +} + +static inline bool isinf(double x) { + return !isnan(x) && isnan(x - x); +} +#endif + +// Simple function to check a string 's' has at least 'n' characters +static inline bool simplejson_wcsnlen(const char *s, size_t n) { + if (s == 0) + return false; + + const char *save = s; + while (n-- > 0) { + if (*(save++) == 0) return false; + } + + return true; +} + +namespace Common { + +// Custom types +class JSONValue; +typedef Array<JSONValue*> JSONArray; +typedef HashMap<String, JSONValue*> JSONObject; + +class JSON; + +enum JSONType { JSONType_Null, JSONType_String, JSONType_Bool, JSONType_Number, JSONType_IntegerNumber, JSONType_Array, JSONType_Object }; + +class JSONValue { + friend class JSON; + +public: + JSONValue(/*NULL*/); + JSONValue(const char *charValue); + JSONValue(const String &stringValue); + JSONValue(bool boolValue); + JSONValue(double numberValue); + JSONValue(long long int numberValue); + JSONValue(const JSONArray &arrayValue); + JSONValue(const JSONObject &objectValue); + JSONValue(const JSONValue &source); + ~JSONValue(); + + bool isNull() const; + bool isString() const; + bool isBool() const; + bool isNumber() const; + bool isIntegerNumber() const; + bool isArray() const; + bool isObject() const; + + const String &asString() const; + bool asBool() const; + double asNumber() const; + long long int asIntegerNumber() const; + const JSONArray &asArray() const; + const JSONObject &asObject() const; + + size_t countChildren() const; + bool hasChild(size_t index) const; + JSONValue *child(size_t index); + bool hasChild(const char *name) const; + JSONValue *child(const char *name); + Array<String> objectKeys() const; + + String stringify(bool const prettyprint = false) const; +protected: + static JSONValue *parse(const char **data); + +private: + static String stringifyString(const String &str); + String stringifyImpl(size_t const indentDepth) const; + static String indent(size_t depth); + + JSONType _type; + + union { + bool _boolValue; + double _numberValue; + long long int _integerValue; + String *_stringValue; + JSONArray *_arrayValue; + JSONObject *_objectValue; + }; + +}; + +class JSON { + friend class JSONValue; + +public: + static JSONValue *parse(const char *data); + static String stringify(const JSONValue *value); +protected: + static bool skipWhitespace(const char **data); + static bool extractString(const char **data, String &str); + static double parseInt(const char **data); + static double parseDecimal(const char **data); +private: + JSON(); +}; + +} // End of namespace Common + +#endif diff --git a/common/memstream.h b/common/memstream.h index 59d5f15b1a..25fdde91c7 100644 --- a/common/memstream.h +++ b/common/memstream.h @@ -209,6 +209,89 @@ public: bool seek(int32 offset, int whence = SEEK_SET); }; +/** +* MemoryStream based on RingBuffer. Grows if has insufficient buffer size. +*/ +class MemoryReadWriteStream : public WriteStream { +private: + uint32 _capacity; + uint32 _size; + byte *_data; + uint32 _writePos, _readPos, _pos, _length; + DisposeAfterUse::Flag _disposeMemory; + + void ensureCapacity(uint32 new_len) { + if (new_len <= _capacity) + return; + + byte *old_data = _data; + uint32 oldCapacity = _capacity; + + _capacity = MAX(new_len + 32, _capacity * 2); + _data = (byte *)malloc(_capacity); + + if (old_data) { + // Copy old data + if (_readPos < _writePos) { + memcpy(_data, old_data + _readPos, _writePos - _readPos); + _writePos = _length; + _readPos = 0; + } else { + memcpy(_data, old_data + _readPos, oldCapacity - _readPos); + memcpy(_data + oldCapacity - _readPos, old_data, _writePos); + _writePos = _length; + _readPos = 0; + } + free(old_data); + } + } +public: + MemoryReadWriteStream(DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO) : _capacity(0), _size(0), _data(0), _writePos(0), _readPos(0), _pos(0), _length(0), _disposeMemory(disposeMemory) {} + + ~MemoryReadWriteStream() { + if (_disposeMemory) + free(_data); + } + + uint32 write(const void *dataPtr, uint32 dataSize) { + ensureCapacity(_length + dataSize); + if (_writePos + dataSize < _capacity) { + memcpy(_data + _writePos, dataPtr, dataSize); + } else { + memcpy(_data + _writePos, dataPtr, _capacity - _writePos); + const byte *shiftedPtr = (const byte *)dataPtr + _capacity - _writePos; + memcpy(_data, shiftedPtr, dataSize - (_capacity - _writePos)); + } + _writePos = (_writePos + dataSize) % _capacity; + _pos += dataSize; + _length += dataSize; + if (_pos > _size) + _size = _pos; + return dataSize; + } + + virtual uint32 read(void *dataPtr, uint32 dataSize) { + uint32 length = _length; + if (length < dataSize) dataSize = length; + if (dataSize == 0 || _capacity == 0) return 0; + if (_readPos + dataSize < _capacity) { + memcpy(dataPtr, _data + _readPos, dataSize); + } else { + memcpy(dataPtr, _data + _readPos, _capacity - _readPos); + byte *shiftedPtr = (byte *)dataPtr + _capacity - _readPos; + memcpy(shiftedPtr, _data, dataSize - (_capacity - _readPos)); + } + _readPos = (_readPos + dataSize) % _capacity; + _length -= dataSize; + return dataSize; + } + + int32 pos() const { return _pos - _length; } //'read' position in the stream + uint32 size() const { return _size; } //that's also 'write' position in the stream, as it's append-only + + byte *getData() { return _data; } +}; + } // End of namespace Common #endif diff --git a/common/module.mk b/common/module.mk index 570040c8e1..54aa16f557 100644 --- a/common/module.mk +++ b/common/module.mk @@ -16,6 +16,7 @@ MODULE_OBJS := \ iff_container.o \ ini-file.o \ installshield_cab.o \ + json.o \ language.o \ localization.o \ macresman.o \ diff --git a/common/savefile.h b/common/savefile.h index 9fca07f9d5..eb7e6f946e 100644 --- a/common/savefile.h +++ b/common/savefile.h @@ -28,6 +28,7 @@ #include "common/stream.h" #include "common/str-array.h" #include "common/error.h" +#include "common/ptr.h" namespace Common { @@ -44,8 +45,21 @@ typedef SeekableReadStream InSaveFile; * That typically means "save games", but also includes things like the * IQ points in Indy3. */ -typedef WriteStream OutSaveFile; +class OutSaveFile: public WriteStream { +protected: + ScopedPtr<WriteStream> _wrapped; +public: + OutSaveFile(WriteStream *w); + virtual ~OutSaveFile(); + + virtual bool err() const; + virtual void clearErr(); + virtual void finalize(); + virtual bool flush(); + virtual uint32 write(const void *dataPtr, uint32 dataSize); + virtual int32 pos() const; +}; /** * The SaveFileManager is serving as a factory for InSaveFile @@ -137,6 +151,15 @@ public: virtual InSaveFile *openForLoading(const String &name) = 0; /** + * Open the file with the specified name in the given directory for loading. + * In contrast to openForLoading(), it returns raw file instead of unpacked. + * + * @param name The name of the savefile. + * @return Pointer to an InSaveFile, or NULL if an error occurred. + */ + virtual InSaveFile *openRawFile(const String &name) = 0; + + /** * Removes the given savefile from the system. * * @param name The name of the savefile to be removed. @@ -174,6 +197,13 @@ public: * @see Common::matchString() */ virtual StringArray listSavefiles(const String &pattern) = 0; + + /** + * Refreshes the save files list (because some new files could've been added) + * and remembers the "locked" files list. These files could not be used + * for saving or loading because they are being synced by CloudManager. + */ + virtual void updateSavefilesList(StringArray &lockedFiles) = 0; }; } // End of namespace Common diff --git a/common/scummsys.h b/common/scummsys.h index 5e1069fb46..959c67a404 100644 --- a/common/scummsys.h +++ b/common/scummsys.h @@ -29,7 +29,11 @@ // This is a convenience macro to test whether the compiler used is a GCC // version, which is at least major.minor. -#define GCC_ATLEAST(major, minor) (defined(__GNUC__) && (__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor)))) +#ifdef __GNUC__ + #define GCC_ATLEAST(major, minor) (__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor))) +#else + #define GCC_ATLEAST(major, minor) 0 +#endif #if defined(_WIN32_WCE) && _WIN32_WCE < 300 #define NONSTANDARD_PORT diff --git a/common/str.cpp b/common/str.cpp index 3ff49a90c6..90bd539790 100644 --- a/common/str.cpp +++ b/common/str.cpp @@ -335,6 +335,15 @@ bool String::contains(char x) const { return strchr(c_str(), x) != NULL; } +uint64 String::asUint64() const { + uint64 result = 0; + for (uint32 i = 0; i < _size; ++i) { + if (_str[i] < '0' || _str[i] > '9') break; + result = result * 10L + (_str[i] - '0'); + } + return result; +} + bool String::matchString(const char *pat, bool ignoreCase, bool pathMode) const { return Common::matchString(c_str(), pat, ignoreCase, pathMode); } @@ -829,6 +838,15 @@ bool matchString(const char *str, const char *pat, bool ignoreCase, bool pathMod } } +void replace(Common::String &source, const Common::String &what, const Common::String &with) { + const char *cstr = source.c_str(); + const char *position = strstr(cstr, what.c_str()); + if (position) { + uint32 index = position - cstr; + source.replace(index, what.size(), with); + } +} + String tag2string(uint32 tag) { char str[5]; str[0] = (char)(tag >> 24); diff --git a/common/str.h b/common/str.h index 9ada8aaaa0..d55ba072a9 100644 --- a/common/str.h +++ b/common/str.h @@ -162,6 +162,9 @@ public: bool contains(const char *x) const; bool contains(char x) const; + /** Return uint64 corrensponding to String's contents. */ + uint64 asUint64() const; + /** * Simple DOS-style pattern matching function (understands * and ? like used in DOS). * Taken from exult/files/listfiles.cc @@ -233,12 +236,12 @@ public: void trim(); uint hash() const; - + /**@{ * Functions to replace some amount of chars with chars from some other string. * * @note The implementation follows that of the STL's std::string: - * http://www.cplusplus.com/reference/string/string/replace/ + * http://www.cplusplus.com/reference/string/string/replace/ * * @param pos Starting position for the replace in the original string. * @param count Number of chars to replace from the original string. @@ -247,7 +250,7 @@ public: * @param countOri Same as count * @param posDest Initial position to read str from. * @param countDest Number of chars to read from str. npos by default. - */ + */ // Replace 'count' bytes, starting from 'pos' with str. void replace(uint32 pos, uint32 count, const String &str); // The same as above, but accepts a C-like array of characters. @@ -264,7 +267,7 @@ public: // str[posDest, posDest + countDest) void replace(uint32 posOri, uint32 countOri, const char *str, uint32 posDest, uint32 countDest); - /**@}*/ + /**@}*/ /** * Print formatted data into a String object. Similar to sprintf, @@ -387,6 +390,15 @@ String normalizePath(const String &path, const char sep); */ bool matchString(const char *str, const char *pat, bool ignoreCase = false, bool pathMode = false); +/** + * Function which replaces substring with the other. It happens in place. + * If there is no substring found, original string is not changed. + * + * @param source String to search and replace substring in. + * @param what Substring to replace. + * @param with String to replace with. + */ +void replace(Common::String &source, const Common::String &what, const Common::String &with); /** * Take a 32 bit value and turn it into a four character string, where each of diff --git a/common/system.h b/common/system.h index 6d185d3075..805eba68ed 100644 --- a/common/system.h +++ b/common/system.h @@ -314,7 +314,15 @@ public: * * This feature has no associated state. */ - kFeatureDisplayLogFile + kFeatureDisplayLogFile, + + /** + * The presence of this feature indicates whether the hasTextInClipboard() + * and getTextFromClipboard() calls are supported. + * + * This feature has no associated state. + */ + kFeatureClipboardSupport }; /** @@ -1086,6 +1094,45 @@ public: virtual void displayMessageOnOSD(const char *msg) = 0; /** + * Blit a bitmap to the 'on screen display'. + * + * If the current pixel format has one byte per pixel, the graphics data + * uses 8 bits per pixel, using the palette specified via setPalette. + * If more than one byte per pixel is in use, the graphics data uses the + * pixel format returned by getScreenFormat. + * + * @param buf the buffer containing the graphics data source + * @param pitch the pitch of the buffer (number of bytes in a scanline) + * @param x the x coordinate of the destination rectangle + * @param y the y coordinate of the destination rectangle + * @param w the width of the destination rectangle + * @param h the height of the destination rectangle + * + * @note The specified destination rectangle must be completly contained + * in the visible screen space, and must be non-empty. If not, a + * backend may or may not perform clipping, trigger an assert or + * silently corrupt memory. + * + * @see updateScreen + * @see getScreenFormat + * @see copyRectToScreen + */ + + virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) = 0; + + /** + * Clears 'on screen display' from everything drawn on it. + */ + + virtual void clearOSD() = 0; + + /** + * Returns 'on screen display' pixel format. + */ + + virtual Graphics::PixelFormat getOSDFormat() = 0; + + /** * Return the SaveFileManager, used to store and load savestates * and other modifiable persistent game data. For more information, * refer to the SaveFileManager documentation. @@ -1200,6 +1247,28 @@ public: virtual bool displayLogFile() { return false; } /** + * Returns whether there is text available in the clipboard. + * + * The kFeatureClipboardSupport feature flag can be used to + * test whether this call has been implemented by the active + * backend. + * + * @return true if there is text in the clipboard, false otherwise + */ + virtual bool hasTextInClipboard() { return false; } + + /** + * Returns clipboard contents as a String. + * + * The kFeatureClipboardSupport feature flag can be used to + * test whether this call has been implemented by the active + * backend. + * + * @return clipboard contents ("" if hasTextInClipboard() == false) + */ + virtual Common::String getTextFromClipboard() { return ""; } + + /** * Returns the locale of the system. * * This returns the currently set up locale of the system, on which diff --git a/common/xmlparser.cpp b/common/xmlparser.cpp index da4f577e3c..4470180710 100644 --- a/common/xmlparser.cpp +++ b/common/xmlparser.cpp @@ -128,7 +128,7 @@ bool XMLParser::parserError(const String &errStr) { while (currentPosition--) errorMessage += (char)_stream->readByte(); } - + errorMessage += "\n\nParser error: "; errorMessage += errStr; errorMessage += "\n\n"; @@ -117,6 +117,8 @@ done # # Default lib behavior yes/no/auto _vorbis=auto +_sdlnet=auto +_libcurl=auto _tremor=auto _tremolo=no _flac=auto @@ -153,6 +155,7 @@ _build_hq_scalers=yes _enable_prof=no _global_constructors=no _bink=yes +_cloud=auto # Default vkeybd/keymapper/eventrec options _vkeybd=no _keymapper=no @@ -184,10 +187,12 @@ _amigaospath="Games:ScummVM" _staticlibpath= _xcodetoolspath= _sparklepath= -_sdlconfig=sdl-config +_sdlconfig=sdl2-config +_libcurlconfig=curl-config _freetypeconfig=freetype-config _sdlpath="$PATH" _freetypepath="$PATH" +_libcurlpath="$PATH" _nasmpath="$PATH" NASMFLAGS="" NASM="" @@ -201,6 +206,7 @@ _have_x86=no # Add (virtual) features add_feature 16bit "16bit color" "_16bit" +add_feature cloud "cloud" "_cloud" add_feature faad "libfaad" "_faad" add_feature flac "FLAC" "_flac" add_feature freetype2 "FreeType2" "_freetype2" @@ -368,7 +374,7 @@ define_in_config_if_yes() { # TODO: small bit of code to test sdl usability find_sdlconfig() { echo_n "Looking for sdl-config... " - sdlconfigs="$SDL_CONFIG:$_sdlconfig:sdl-config:sdl11-config:sdl12-config" + sdlconfigs="$SDL_CONFIG:$_sdlconfig:sdl2-config:sdl12-config:sdl11-config:sdl-config" _sdlconfig= IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="$SEPARATOR" @@ -433,6 +439,40 @@ find_freetypeconfig() { } # +# Determine curl-config +# +find_libcurlconfig() { + echo_n "Looking for curl-config... " + libcurlconfigs="$_libcurlconfig" + _libcurlconfig= + + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="$SEPARATOR" + for path_dir in $_libcurlpath; do + #reset separator to parse sdlconfigs + IFS=":" + for libcurlconfig in $libcurlconfigs; do + if test -f "$path_dir/$libcurlconfig" ; then + _libcurlconfig="$path_dir/$libcurlconfig" + echo $_libcurlconfig + # Save the prefix + _libcurlpath=$path_dir + if test `basename $path_dir` = bin ; then + _libcurlpath=`dirname $path_dir` + fi + # break at first curl-config found in path + break 2 + fi + done + done + + IFS="$ac_save_ifs" + + if test -z "$_libcurlconfig"; then + echo "none found!" + fi +} + +# # Determine extension used for executables # get_system_exe_extension() { @@ -922,6 +962,7 @@ Optional Features: --disable-hq-scalers exclude HQ2x and HQ3x scalers --disable-translation don't build support for translated messages --disable-taskbar don't build support for taskbar and launcher integration + --disable-cloud don't build cloud support --enable-vkeybd build virtual keyboard support --enable-keymapper build key mapper support --enable-eventrecorder enable event recording functionality @@ -1004,6 +1045,11 @@ Optional Libraries: --with-sndio-prefix=DIR Prefix where sndio is installed (optional) --disable-sndio disable sndio MIDI driver [autodetect] + --with-sdlnet-prefix=DIR Prefix where SDL_Net is + installed (optional) + --disable-sdlnet disable SDL_Net networking library [autodetect] + --disable-libcurl disable libcurl networking library [autodetect] + Some influential environment variables: LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a nonstandard directory <lib dir> @@ -1082,6 +1128,12 @@ for ac_option in $@; do --disable-freetype2) _freetype2=no ;; --enable-taskbar) _taskbar=yes ;; --disable-taskbar) _taskbar=no ;; + --enable-sdlnet) _sdlnet=yes ;; + --disable-sdlnet) _sdlnet=no ;; + --enable-libcurl) _libcurl=yes ;; + --disable-libcurl) _libcurl=no ;; + --enable-cloud) _cloud=yes ;; + --disable-cloud) _cloud=no ;; --enable-updates) _updates=yes ;; --disable-updates) _updates=no ;; --enable-libunity) _libunity=yes ;; @@ -1190,6 +1242,11 @@ for ac_option in $@; do LIBUNITY_CFLAGS="-I$arg/include" LIBUNITY_LIBS="-L$arg/lib" ;; + --with-sdlnet-prefix=*) + arg=`echo $ac_option | cut -d '=' -f 2` + SDL_NET_CFLAGS="-I$arg/include" + SDL_NET_LIBS="-L$arg/lib" + ;; --backend=*) _backend=`echo $ac_option | cut -d '=' -f 2` ;; @@ -2214,7 +2271,7 @@ case $_host_os in append_var CXXFLAGS "-mtune=xscale" append_var CXXFLAGS "-msoft-float" ABI="armeabi" - ANDROID_PLATFORM=4 + ANDROID_PLATFORM=9 ANDROID_PLATFORM_ARCH="arm" ;; android-v7a | android-arm-v7a) @@ -2223,7 +2280,7 @@ case $_host_os in append_var CXXFLAGS "-mfpu=vfp" append_var LDFLAGS "-Wl,--fix-cortex-a8" ABI="armeabi-v7a" - ANDROID_PLATFORM=4 + ANDROID_PLATFORM=9 ANDROID_PLATFORM_ARCH="arm" ;; android-mips) @@ -2248,7 +2305,7 @@ case $_host_os in append_var CXXFLAGS "-mfloat-abi=softfp" append_var CXXFLAGS "-mfpu=neon" ABI="armeabi-v7a" - ANDROID_PLATFORM=4 + ANDROID_PLATFORM=16 ANDROID_PLATFORM_ARCH="arm" ;; esac @@ -2540,9 +2597,9 @@ case $_host_os in ;; ps3) # Force use of SDL and freetype from the ps3 toolchain - _sdlconfig=sdl2-config _sdlpath="$PS3DEV/portlibs/ppu:$PS3DEV/portlibs/ppu/bin" _freetypepath="$PS3DEV/portlibs/ppu:$PS3DEV/portlibs/ppu/bin" + _libcurlpath="$PS3DEV/portlibs/ppu:$PS3DEV/portlibs/ppu/bin" append_var DEFINES "-DPLAYSTATION3" append_var CXXFLAGS "-mcpu=cell -mminimal-toc -I$PSL1GHT/ppu/include -I$PS3DEV/portlibs/ppu/include" @@ -2814,6 +2871,7 @@ if test -n "$_host"; then _vkeybd=yes _keymapper=yes _vorbis=no + _sdlconfig=sdl-config _port_mk="backends/platform/dingux/dingux.mk" ;; gp2x) @@ -3122,7 +3180,7 @@ case $_backend in append_var INCLUDES "-I$ANDROID_NDK/sources/cxx-stl/system/include" ;; androidsdl) - ;; + ;; dc) append_var INCLUDES '-I$(srcdir)/backends/platform/dc' append_var INCLUDES '-isystem $(ronindir)/include' @@ -4071,6 +4129,115 @@ EOF esac # +# Check for SDL_Net +# +echocheck "SDL_Net" +if test "$_sdlnet" = auto ; then + _sdlnet=no + cat > $TMPC << EOF +#include "SDL/SDL_net.h" +int main(int argc, char *argv[]) { SDLNet_Init(); return 0; } +EOF + cc_check $LIBS $INCLUDES $SDL_NET_CFLAGS $SDL_NET_LIBS -lSDL_net && _sdlnet=yes +fi +if test "$_sdlnet" = yes ; then + append_var LIBS "$SDL_NET_LIBS -lSDL_net" + append_var INCLUDES "$SDL_NET_CFLAGS" +fi +define_in_config_if_yes "$_sdlnet" 'USE_SDL_NET' +echo "$_sdlnet" + +# +# Check for libcurl to be present +# +if test "$_libcurl" != "no"; then + + # Look for the curl-config script + find_libcurlconfig + + if test -z "$_libcurlconfig"; then + _libcurl=no + else + LIBCURL_LIBS=`$_libcurlconfig --libs` + LIBCURL_CFLAGS=`$_libcurlconfig --cflags` + + if test "$_libcurl" = "auto"; then + _libcurl=no + + cat > $TMPC << EOF + #include <curl/curl.h> + int main(int argc, char *argv[]) { + int x; + curl_easy_setopt(NULL,CURLOPT_URL,NULL); + x=CURL_ERROR_SIZE; + x=CURLOPT_WRITEFUNCTION; + x=CURLOPT_WRITEDATA; + x=CURLOPT_ERRORBUFFER; + x=CURLOPT_STDERR; + x=CURLOPT_VERBOSE; + + curl_version_info_data *data = curl_version_info(CURLVERSION_NOW); + if (data->features & CURL_VERSION_SSL) + return 0; + return 1; + } +EOF + + cc_check_no_clean $LIBCURL_CFLAGS $LIBCURL_LIBS + if test "$?" -eq 0; then + if test -n "$_host"; then + # In cross-compiling mode, we cannot run the result, assume SSL is available + _libcurl=yes + else + $TMPO$HOSTEXEEXT + if test "$?" -eq 0; then + _libcurl=yes + else + _libcurl="no SSL support" + fi + fi + fi + cc_check_clean + fi + + if test "$_libcurl" = "yes"; then + append_var LIBS "$LIBCURL_LIBS" + append_var INCLUDES "$LIBCURL_CFLAGS" + fi + fi + +fi + +echocheck "libcurl" +echo "$_libcurl" + +define_in_config_if_yes "$_libcurl" "USE_LIBCURL" + +# +# Check whether to build cloud integration support +# +echo_n "Cloud integration... " +if test "$_cloud" = "no"; then + echo "no" +else + _cloud=no + if test "$_sdlnet" = "yes"; then + _cloud=yes + echo_n "local" + fi + if test "$_libcurl" = "yes"; then + if test "$_cloud" = "yes"; then echo_n ", "; fi + _cloud=yes + echo_n "servers" + fi + if test "$_cloud" = "no"; then + echo_n "no" + fi + echo # newline +fi +define_in_config_if_yes $_cloud 'USE_CLOUD' + +# # Check is NSDockTilePlugIn protocol is supported # case $_host_os in @@ -4651,7 +4818,11 @@ if test "$_keymapper" = yes ; then fi if test "$_eventrec" = yes ; then - echo ", event recorder" + echo_n ", event recorder" +fi + +if test "$_cloud" = yes ; then + echo ", cloud" else echo fi @@ -4693,7 +4864,7 @@ case $_backend in # -lgcc is carefully placed here - we want to catch # all toolchain symbols in *our* libraries rather # than pick up anything unhygenic from the Android libs. - LIBS="-Wl,-Bstatic $static_libs -Wl,-Bdynamic -lgcc $system_libs -llog -lGLESv1_CM" + LIBS="-Wl,-Bstatic $static_libs -Wl,-Bdynamic -lgcc $system_libs -llog -landroid -lGLESv1_CM" ;; n64) # Move some libs down here, otherwise some symbols requires by libvorbis aren't found diff --git a/devtools/create_project/create_project.cpp b/devtools/create_project/create_project.cpp index 7e2cad0901..91690c2128 100644 --- a/devtools/create_project/create_project.cpp +++ b/devtools/create_project/create_project.cpp @@ -284,8 +284,8 @@ int main(int argc, char *argv[]) { setup.devTools = true; } else if (!std::strcmp(argv[i], "--tests")) { setup.tests = true; - } else if (!std::strcmp(argv[i], "--sdl2")) { - setup.useSDL2 = true; + } else if (!std::strcmp(argv[i], "--sdl1")) { + setup.useSDL2 = false; } else { std::cerr << "ERROR: Unknown parameter \"" << argv[i] << "\"\n"; return -1; @@ -524,6 +524,8 @@ int main(int argc, char *argv[]) { // 4355 ('this' : used in base member initializer list) // only disabled for specific engines where it is used in a safe way // + // 4373 (previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers) + // // 4510 ('class' : default constructor could not be generated) // // 4511 ('class' : copy constructor could not be generated) @@ -573,6 +575,8 @@ int main(int argc, char *argv[]) { projectWarnings["m4"].push_back("4355"); + projectWarnings["sci"].push_back("4373"); + if (msvcVersion == 9) provider = new CreateProjectTool::VisualStudioProvider(globalWarnings, projectWarnings, msvcVersion); else @@ -696,7 +700,7 @@ void displayHelp(const char *exe) { " --disable-<name> disable inclusion of the feature \"name\"\n" "\n" "SDL settings:\n" - " --sdl2 link to SDL 2.0, instead of SDL 1.2\n" + " --sdl1 link to SDL 1.2, instead of SDL 2.0\n" "\n" " There are the following features available:\n" "\n"; @@ -996,10 +1000,12 @@ const Feature s_features[] = { { "png", "USE_PNG", "libpng16", true, "libpng support" }, { "faad", "USE_FAAD", "libfaad", false, "AAC support" }, { "mpeg2", "USE_MPEG2", "libmpeg2", false, "MPEG-2 support" }, - { "theora", "USE_THEORADEC", "libtheora_static", true, "Theora decoding support" }, - { "freetype", "USE_FREETYPE2", "freetype", true, "FreeType support" }, - { "jpeg", "USE_JPEG", "jpeg-static", true, "libjpeg support" }, - {"fluidsynth", "USE_FLUIDSYNTH", "libfluidsynth", true, "FluidSynth support" }, + { "theora", "USE_THEORADEC", "libtheora_static", true, "Theora decoding support" }, + { "freetype", "USE_FREETYPE2", "freetype", true, "FreeType support" }, + { "jpeg", "USE_JPEG", "jpeg-static", true, "libjpeg support" }, + {"fluidsynth", "USE_FLUIDSYNTH", "libfluidsynth", true, "FluidSynth support" }, + { "libcurl", "USE_LIBCURL", "libcurl", true, "libcurl support" }, + { "sdlnet", "USE_SDL_NET", "SDL_net", true, "SDL_net support" }, // Feature flags { "bink", "USE_BINK", "", true, "Bink video support" }, @@ -1011,6 +1017,7 @@ const Feature s_features[] = { { "opengl", "USE_OPENGL", "", true, "OpenGL support" }, { "opengles", "USE_GLES", "", true, "forced OpenGL ES mode" }, { "taskbar", "USE_TASKBAR", "", true, "Taskbar integration support" }, + { "cloud", "USE_CLOUD", "", true, "Cloud integration support" }, { "translation", "USE_TRANSLATION", "", true, "Translation support" }, { "vkeybd", "ENABLE_VKEYBD", "", false, "Virtual keyboard support"}, { "keymapper", "ENABLE_KEYMAPPER", "", false, "Keymapper support"}, diff --git a/devtools/create_project/create_project.h b/devtools/create_project/create_project.h index 1e417d485b..bd0dcf6be9 100644 --- a/devtools/create_project/create_project.h +++ b/devtools/create_project/create_project.h @@ -239,7 +239,7 @@ struct BuildSetup { tests = false; runBuildEvents = false; createInstaller = false; - useSDL2 = false; + useSDL2 = true; } }; diff --git a/devtools/create_project/xcode.cpp b/devtools/create_project/xcode.cpp index 62969d1436..07dc24e315 100644 --- a/devtools/create_project/xcode.cpp +++ b/devtools/create_project/xcode.cpp @@ -786,6 +786,8 @@ void XcodeProvider::setupBuildConfiguration(const BuildSetup &setup) { ADD_SETTING(scummvm_Debug, "GCC_ENABLE_CPP_RTTI", "YES"); ADD_SETTING(scummvm_Debug, "GCC_INPUT_FILETYPE", "automatic"); ADD_SETTING(scummvm_Debug, "GCC_OPTIMIZATION_LEVEL", "0"); + ADD_SETTING(scummvm_Debug, "GCC_WARN_SIGN_COMPARE", "YES"); + ADD_SETTING(scummvm_Debug, "WARNING_CFLAGS", "-Wno-multichar"); ValueList scummvm_defines(_defines); REMOVE_DEFINE(scummvm_defines, "MACOSX"); REMOVE_DEFINE(scummvm_defines, "IPHONE"); @@ -844,7 +846,6 @@ void XcodeProvider::setupBuildConfiguration(const BuildSetup &setup) { ADD_SETTING(iPhone_Debug, "GCC_OPTIMIZATION_LEVEL", "0"); ADD_SETTING(iPhone_Debug, "GCC_PRECOMPILE_PREFIX_HEADER", "NO"); ADD_SETTING(iPhone_Debug, "GCC_WARN_64_TO_32_BIT_CONVERSION", "NO"); - ADD_SETTING(iPhone_Debug, "WARNING_CFLAGS", "-Wno-multichar"); ADD_SETTING_QUOTE(iPhone_Debug, "GCC_PREFIX_HEADER", ""); ADD_SETTING(iPhone_Debug, "GCC_UNROLL_LOOPS", "YES"); ValueList iPhone_HeaderSearchPaths; diff --git a/devtools/create_project/xcode/create_project.xcodeproj/project.pbxproj b/devtools/create_project/xcode/create_project.xcodeproj/project.pbxproj index 4f06a5e469..55266a875f 100644 --- a/devtools/create_project/xcode/create_project.xcodeproj/project.pbxproj +++ b/devtools/create_project/xcode/create_project.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 076583601D660492006CBB9B /* cmake.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0765835E1D660492006CBB9B /* cmake.cpp */; }; F9A66C691396D4DF00CEE494 /* codeblocks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A66C5F1396D4DF00CEE494 /* codeblocks.cpp */; }; F9A66C6A1396D4DF00CEE494 /* create_project.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A66C621396D4DF00CEE494 /* create_project.cpp */; }; F9A66C6B1396D4DF00CEE494 /* msbuild.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A66C651396D4DF00CEE494 /* msbuild.cpp */; }; @@ -41,6 +42,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0765835E1D660492006CBB9B /* cmake.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = cmake.cpp; path = ../cmake.cpp; sourceTree = "<group>"; }; + 0765835F1D660492006CBB9B /* cmake.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cmake.h; path = ../cmake.h; sourceTree = "<group>"; }; F9A66C271396D36100CEE494 /* create_project */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = create_project; sourceTree = BUILT_PRODUCTS_DIR; }; F9A66C491396D47500CEE494 /* installer.vbs */ = {isa = PBXFileReference; lastKnownFileType = text; name = installer.vbs; path = ../scripts/installer.vbs; sourceTree = "<group>"; }; F9A66C4A1396D47500CEE494 /* postbuild.cmd */ = {isa = PBXFileReference; lastKnownFileType = text; name = postbuild.cmd; path = ../scripts/postbuild.cmd; sourceTree = "<group>"; }; @@ -76,6 +79,8 @@ F9A66C1C1396D36100CEE494 = { isa = PBXGroup; children = ( + 0765835E1D660492006CBB9B /* cmake.cpp */, + 0765835F1D660492006CBB9B /* cmake.h */, F9A66C861396E2F500CEE494 /* xcode.cpp */, F9A66C841396E2D800CEE494 /* xcode.h */, F9A66C6D1396D4E800CEE494 /* visualstudio.cpp */, @@ -169,6 +174,7 @@ F9A66C6A1396D4DF00CEE494 /* create_project.cpp in Sources */, F9A66C6B1396D4DF00CEE494 /* msbuild.cpp in Sources */, F9A66C6C1396D4DF00CEE494 /* msvc.cpp in Sources */, + 076583601D660492006CBB9B /* cmake.cpp in Sources */, F9A66C6F1396D4E800CEE494 /* visualstudio.cpp in Sources */, F9A66C871396E2F500CEE494 /* xcode.cpp in Sources */, ); diff --git a/devtools/create_titanic/create_titanic_dat.cpp b/devtools/create_titanic/create_titanic_dat.cpp index 72c7f1ef46..61e5de1149 100644 --- a/devtools/create_titanic/create_titanic_dat.cpp +++ b/devtools/create_titanic/create_titanic_dat.cpp @@ -61,9 +61,25 @@ Common::File inputFile, outputFile; Common::PEResources res; uint headerOffset = 6; uint dataOffset = HEADER_SIZE; -#define SEGMENT_OFFSET 0x401C00 -const int FILE_DIFF = 0x401C00; +#define ENGLISH_10042C_FILESIZE 4099072 +#define ENGLISH_10042B_FILESIZE 4095488 +#define ENGLISH_10042_FILESIZE 4094976 +enum { + ENGLISH_10042C_DIFF = 0x401C00, + ENGLISH_10042B_DIFF = 0x401400, + ENGLISH_10042_DIFF = 0x402000 +}; +enum Version { + ENGLISH_10042C = 0, + ENGLISH_10042B = 1, + ENGLISH_10042 = 2 +}; +Version _version; + +const int FILE_DIFF[3] = { + ENGLISH_10042C_DIFF, ENGLISH_10042B_DIFF, ENGLISH_10042_DIFF +}; static const char *const ITEM_NAMES[46] = { "LeftArmWith", "LeftArmWithout", "RightArmWith", "RightArmWithout", "BridgeRed", @@ -249,6 +265,155 @@ static const CommonPhrase BELLBOT_COMMON_PHRASES[] = { { nullptr, 0, 0, 0 } }; +struct FrameRange { + int _startFrame; + int _endFrame; +}; + +static const FrameRange BARBOT_FRAME_RANGES[60] = { + { 558, 585 }, { 659, 692 }, { 802, 816 }, { 1941, 1977 }, { 1901, 1941 }, + { 810, 816 }, { 857, 865}, { 842, 857 }, { 821, 842 }, { 682, 692 }, + { 1977, 2018 }, { 2140, 2170 }, { 2101, 2139 }, { 2018, 2099}, { 1902, 2015 }, + { 1811, 1901 }, { 1751, 1810 }, { 1703, 1750 }, { 1681, 1702 }, { 1642, 1702 }, + { 1571, 1641 }, { 1499, 1570 }, { 1403, 1463 }, { 1464, 1499 }, { 1288, 1295 }, + { 1266, 1287 }, { 1245, 1265 }, { 1208, 1244 }, { 1171, 1207 }, { 1120, 1170 }, + { 1092, 1120 }, { 1092, 1092 }, { 1044, 1091 }, { 1011, 1043 }, { 1001, 1010 }, + { 985, 1001 }, { 927, 984 }, { 912, 926 }, { 898, 906 }, { 802, 896 }, + { 865, 896 }, { 842, 865 }, { 816, 842 }, { 802, 842 }, { 740, 801 }, + { 692, 740 }, { 610, 692 }, { 558, 610 }, { 500, 558 }, { 467, 500 }, + { 421, 466 }, { 349, 420 }, { 306, 348 }, { 305, 306 }, { 281, 305 }, + { 202, 281 }, { 182, 202 }, { 165, 182 }, { 96, 165 }, { 0, 95 } +}; + +static const char *const MISSIVEOMAT_MESSAGES[3] = { + "Welcome, Leovinus.\n" + "\n" + "This is your Missive-O-Mat.\n" + "\n" + "You have received 1827 Electric Missives.\n" + "\n" + "For your convenience I have deleted:\n" + " 453 things that people you don't know thought it would be " + "terribly witty to forward to you,\n" + " 63 Missives containing double or triple exclamation marks,\n" + " 846 Missives from mailing-lists you once thought might be quite " + "interesting and now can't figure out how to cancel,\n" + " 962 Chain Missives,\n" + " 1034 instructions on how to become a millionaire using butter,\n" + " 3 Yassaccan Death Threats (slightly down on last week which is" + " pleasing news),\n" + " and a Missive from your Mother which I have answered reassuringly.\n" + "\n" + "I have selected the following Missives for your particular attention. " + "You will not need to run Fib-Finder to see why. Something Is Up and I " + "suspect those two slippery urchins Brobostigon and Scraliontis are behind it.", + + "Hello Droot. I have evaluated your recent missives.\n" + "Contents break down as follows:\n" + "\n" + "Good news 49%\n" + "Bad news 48%\n" + "Indifferent news 4%\n" + "Petty mailings and Family Missives 5%\n" + "Special Offers from the Blerontin Sand Society 1% (note - there's" + " a rather pretty dune for hire on p4)\n" + "\n" + "In general terms you Thrive. You continue to Prosper. Your shares are" + " Secure. Your hair, as always, looks Good. Carpet 14 needs cleaning. \n" + "\n" + "I am pleased to report there have been no further comments about " + "foot odor.\n" + "\n" + "Recommend urgently you sell all fish paste shares as Market jittery.\n" + "\n" + "As your Great Scheme nears completion I have taken the liberty of" + " replying to all non-urgent Missives and list below only communic" + "ations with Manager Brobostigon and His Pain in the Ass Loftiness" + " Leovinus. \n" + "\n" + "Beware - Leovinus grows suspicious. Don't take your eye off B" + "robostigon. \n" + "\n" + "Weather for the Launch tomorrow is bright and sunny. Hazy clouds" + " will be turned on at eleven. I suggest the red suit with the st" + "reamers.\n" + "\n" + "All money transfers will be completed through alias accounts by m" + "oonsup.\n" + "\n" + "Eat well. Your fish levels are down and you may suffer indecisio" + "n flutters mid-morning.\n" + "\n" + "Here are your Missives...", + + "Hello Antar, this is your Missive-o-Mat.\n" + "Not that you need reminding but today is the Glorious Dawning of " + "a New Age in Luxury Space Travel.\n" + "\n" + "Generally my assessment of your position this morning is that you" + " are well, albeit not as rich as you would like to be. I hope yo" + "ur interesting collaboration with Mr Scraliontis will soon bear f" + "ruit. \n" + "\n" + "I trust your flatulence has eased during the night. Such a distr" + "essing condition for a man in your position.\n" + "\n" + "Most of your Missives are routine construction matters which I ha" + "ve dealt with and deleted. All Missives from Mr Scraliontis and " + "His Loftiness Leovinus are here." +}; + +struct BedheadEntry { + const char *_name1; + const char *_name2; + const char *_name3; + const char *_name4; + int _startFrame; + int _endFrame; +}; + +static const BedheadEntry ON_CLOSED[4] = { + { "Closed", "Closed", "Open", "Open", 0, 12 }, + { "Open", "Any", "Any", "RestingUTV", 0, 4 }, + { "Closed", "Open", "Any", "RestingV", 0, 6 }, + { "Closed", "Closed", "Closed", "RestingG", 0, 21 } +}; +static const BedheadEntry ON_RESTING_TV[2] = { + { "Any", "Closed", "Open", "Open", 6, 12 }, + { "Any", "Closed", "Closed", "RestingG", 6, 21 } +}; +static const BedheadEntry ON_RESTING_UV[2] = { + { "Any", "Any", "Open", "Open", 8, 12 }, + { "Any", "Any", "Closed", "RestingG", 8, 21 } +}; +static const BedheadEntry ON_CLOSED_WRONG[2] = { + { "Any", "Any", "Closed", "OpenWrong", 42, 56 }, + { "Any", "Any", "Open", "RestingDWrong", 42, 52 } +}; + +static const BedheadEntry OFF_OPEN[3] = { + { "Closed", "Closed", "Open", "Closed", 27, 41 }, + { "Any", "Open", "Any", "RestingUV", 27, 29 }, + { "Open", "Closed", "Any", "RestingTV", 27, 33 } +}; +static const BedheadEntry OFF_RESTING_UTV[1] = { + { "Any", "Any", "Any", "Closed", 36, 41 } +}; +static const BedheadEntry OFF_RESTING_V[1] = { + { "Closed", "Any", "Any", "Closed", 32, 41 } +}; +static const BedheadEntry OFF_RESTING_G[3] = { + { "Closed", "Closed", "Closed", "Closed", 21, 41 }, + { "Any", "Open", "Closed", "RestingUV", 21, 29 }, + { "Open", "Closed", "Closed", "RestingTV", 21, 33 } +}; +static const BedheadEntry OFF_OPEN_WRONG[1] = { + { "Any", "Any", "Any", "ClosedWrong", 56, 70 } +}; +static const BedheadEntry OFF_RESTING_D_WRONG[1] = { + { "Any", "Any", "Any", "ClosedWrong", 59, 70 } +}; + void NORETURN_PRE error(const char *s, ...) { printf("%s\n", s); @@ -275,7 +440,7 @@ void writeFinalEntryHeader() { void writeStringArray(const char *name, uint offset, int count) { outputFile.seek(dataOffset); - inputFile.seek(offset); + inputFile.seek(offset - FILE_DIFF[_version]); uint *offsets = new uint[count]; for (int idx = 0; idx < count; ++idx) offsets[idx] = inputFile.readLong(); @@ -283,7 +448,7 @@ void writeStringArray(const char *name, uint offset, int count) { // Iterate through reading each string for (int idx = 0; idx < count; ++idx) { if (offsets[idx]) { - inputFile.seek(offsets[idx] - SEGMENT_OFFSET); + inputFile.seek(offsets[idx] - FILE_DIFF[_version]); outputFile.writeString(inputFile); } else { outputFile.writeString(""); @@ -359,7 +524,7 @@ void writeBitmap(const char *name, Common::File *file) { outputFile.writeLong(0); // res1 & res2 outputFile.writeLong(0x436); // image offset - outputFile.write(*file, file->size() + 14); + outputFile.write(*file, file->size()); writeEntryHeader(name, dataOffset, file->size() + 14); dataOffset += file->size() + 14; @@ -402,7 +567,7 @@ void writeNumbers() { } void writeString(uint offset) { - inputFile.seek(offset - FILE_DIFF); + inputFile.seek(offset - FILE_DIFF[_version]); char c; do { c = inputFile.readByte(); @@ -413,26 +578,19 @@ void writeString(uint offset) { void writeResponseTree() { outputFile.seek(dataOffset); - inputFile.seek(0x619500 - FILE_DIFF); - char buffer[32]; - inputFile.read(buffer, 32); - if (strcmp(buffer, "ReadInt(): No number to read")) { - printf("Could not find tree data at expected position\n"); - exit(1); - } - + const int OFFSETS[3] = { 0x619520, 0x618340, 0x617380 }; for (int idx = 0; idx < 1022; ++idx) { - inputFile.seek(0x619520 - FILE_DIFF + idx * 8); + inputFile.seek(OFFSETS[_version] - FILE_DIFF[_version] + idx * 8); uint id = inputFile.readLong(); uint offset = inputFile.readLong(); outputFile.writeLong(id); if (!id) { // An end of list id - } else if (offset >= 0x619520 && offset <= 0x61B510) { + } else if (offset >= OFFSETS[_version] && offset <= (OFFSETS[_version] + 0x1FF0)) { // Offset to another table outputFile.writeByte(0); - outputFile.writeLong((offset - 0x619520) / 8); + outputFile.writeLong((offset - OFFSETS[_version]) / 8); } else { // Offset to ASCIIZ string outputFile.writeByte(1); @@ -452,7 +610,7 @@ void writeSentenceEntries(const char *name, uint tableOffset) { uint offset3, offset5, offset6, offset7, offset8, offset10; for (uint idx = 0; ; ++idx) { - inputFile.seek(tableOffset - FILE_DIFF + idx * 0x34); + inputFile.seek(tableOffset - FILE_DIFF[_version] + idx * 0x34); v1 = inputFile.readLong(); if (!v1) // Reached end of list @@ -498,7 +656,7 @@ void writeWords(const char *name, uint tableOffset, int recordCount = 2) { uint val, strOffset; for (uint idx = 0; ; ++idx) { - inputFile.seek(tableOffset - FILE_DIFF + idx * recordSize); + inputFile.seek(tableOffset - FILE_DIFF[_version] + idx * recordSize); val = inputFile.readLong(); strOffset = inputFile.readLong(); @@ -516,7 +674,7 @@ void writeWords(const char *name, uint tableOffset, int recordCount = 2) { } void writeSentenceMappings(const char *name, uint offset, int numValues) { - inputFile.seek(offset - FILE_DIFF); + inputFile.seek(offset - FILE_DIFF[_version]); outputFile.seek(dataOffset); uint id; @@ -535,7 +693,8 @@ void writeSentenceMappings(const char *name, uint offset, int numValues) { void writeStarfieldPoints() { outputFile.seek(dataOffset); - inputFile.seek(0x59DE4C - FILE_DIFF); + const int OFFSETS[3] = { 0x59DE4C, 0x59DBEC, 0x59CC1C }; + inputFile.seek(OFFSETS[_version] - FILE_DIFF[_version]); uint size = 876 * 12; outputFile.write(inputFile, size); @@ -546,13 +705,14 @@ void writeStarfieldPoints() { void writeStarfieldPoints2() { outputFile.seek(dataOffset); + const int OFFSETS[3] = { 0x5A2F28, 0x5A2CC8, 0x5A1CF8 }; for (int rootCtr = 0; rootCtr < 80; ++rootCtr) { - inputFile.seek(0x5A2F28 - FILE_DIFF + rootCtr * 8); + inputFile.seek(OFFSETS[_version] - FILE_DIFF[_version] + rootCtr * 8); uint offset = inputFile.readUint32LE(); uint count = inputFile.readUint32LE(); outputFile.writeLong(count); - inputFile.seek(offset - FILE_DIFF); + inputFile.seek(offset - FILE_DIFF[_version]); outputFile.write(inputFile, count * 4 * 4); } @@ -576,6 +736,99 @@ void writePhrases(const char *name, const CommonPhrase *phrases) { dataOffset += size; } +void writeBarbotFrameRanges() { + outputFile.seek(dataOffset); + + for (int idx = 0; idx < 60; ++idx) { + outputFile.writeLong(BARBOT_FRAME_RANGES[idx]._startFrame); + outputFile.writeLong(BARBOT_FRAME_RANGES[idx]._endFrame); + } + + uint size = outputFile.size() - dataOffset; + writeEntryHeader("FRAMES/BARBOT", dataOffset, size); + dataOffset += size; +} + +void writeMissiveOMatMessages() { + outputFile.seek(dataOffset); + + for (int idx = 0; idx < 3; ++idx) + outputFile.writeString(MISSIVEOMAT_MESSAGES[idx]); + + uint size = outputFile.size() - dataOffset; + writeEntryHeader("TEXT/MISSIVEOMAT/WELCOME", dataOffset, size); + dataOffset += size; + + static const int MESSAGES[3] = { 0x5A63C0, 0x5A5BA8, 0x5A4A18 }; + writeStringArray("TEXT/MISSIVEOMAT/MESSAGES", MESSAGES[_version], 58); + static const int FROM[3] = { 0x5A61F0, 0x5A59D8, 0x5A4BE8 }; + writeStringArray("TEXT/MISSIVEOMAT/FROM", FROM[_version], 58); + static const int TO[3] = { 0x5A62D8, 0x5A5AC0, 0x5A4B00 }; + writeStringArray("TEXT/MISSIVEOMAT/TO", TO[_version], 58); +} + +void writeBedheadGroup(const BedheadEntry *data, int count) { + for (int idx = 0; idx < count; ++idx, ++data) { + outputFile.writeString(data->_name1); + outputFile.writeString(data->_name2); + outputFile.writeString(data->_name3); + outputFile.writeString(data->_name4); + outputFile.writeLong(data->_startFrame); + outputFile.writeLong(data->_endFrame); + } +} + +void writeBedheadData() { + outputFile.seek(dataOffset); + + writeBedheadGroup(ON_CLOSED, 4); + writeBedheadGroup(ON_RESTING_TV, 2); + writeBedheadGroup(ON_RESTING_UV, 2); + writeBedheadGroup(ON_CLOSED_WRONG, 2); + writeBedheadGroup(OFF_OPEN, 3); + writeBedheadGroup(OFF_RESTING_UTV, 1); + writeBedheadGroup(OFF_RESTING_V, 1); + writeBedheadGroup(OFF_RESTING_G, 3); + writeBedheadGroup(OFF_OPEN_WRONG, 1); + writeBedheadGroup(OFF_RESTING_D_WRONG, 1); + + uint size = outputFile.size() - dataOffset; + writeEntryHeader("DATA/BEDHEAD", dataOffset, size); + dataOffset += size; +} + +void writeParrotLobbyLinkUpdaterEntries() { + static const int OFFSETS[3] = { 0x5A5B38, 0x5A5320, 0x5A4360 }; + static const int COUNTS[5] = { 7, 5, 6, 9, 1 }; + static const int SKIP[5] = { 36, 36, 40, 36, 0 }; + uint recordOffset = OFFSETS[_version], linkOffset; + byte vals[8]; + + outputFile.seek(dataOffset); + + for (int groupNum = 0; groupNum < 4; ++groupNum) { + for (int entryNum = 0; entryNum < COUNTS[groupNum]; + ++entryNum, recordOffset += 36) { + inputFile.seek(recordOffset - FILE_DIFF[_version]); + linkOffset = inputFile.readUint32LE(); + for (int idx = 0; idx < 8; ++idx) + vals[idx] = inputFile.readUint32LE(); + + // Write out the entry + inputFile.seek(linkOffset - FILE_DIFF[_version]); + outputFile.writeString(inputFile); + outputFile.write(vals, 8); + } + + // Skip space between groups + recordOffset += SKIP[groupNum]; + } + + uint size = outputFile.size() - dataOffset; + writeEntryHeader("DATA/PARROT_LOBBY_LINK_UPDATOR", dataOffset, size); + dataOffset += size; +} + void writeHeader() { // Write out magic string const char *MAGIC_STR = "SVTN"; @@ -617,71 +870,123 @@ void writeData() { writeStringArray("TEXT/ITEM_IDS", ITEM_IDS, 40); writeStringArray("TEXT/ROOM_NAMES", ROOM_NAMES, 34); - writeStringArray("TEXT/PHRASES", 0x21B7C8, 376); - writeStringArray("TEXT/REPLACEMENTS1", 0x21BDB0, 218); - writeStringArray("TEXT/REPLACEMENTS2", 0x21C120, 1576); - writeStringArray("TEXT/REPLACEMENTS3", 0x21D9C8, 82); - writeStringArray("TEXT/PRONOUNS", 0x22F718, 15); - - writeSentenceEntries("Sentences/Default", 0x5C0130); - writeSentenceEntries("Sentences/Barbot", 0x5ABE60); - writeSentenceEntries("Sentences/Barbot2", 0x5BD4E8); - writeSentenceEntries("Sentences/Bellbot", 0x5C2230); - writeSentenceEntries("Sentences/Bellbot/1", 0x5D1670); - writeSentenceEntries("Sentences/Bellbot/2", 0x5D1A80); - writeSentenceEntries("Sentences/Bellbot/3", 0x5D1AE8); - writeSentenceEntries("Sentences/Bellbot/4", 0x5D1B88); - writeSentenceEntries("Sentences/Bellbot/5", 0x5D2A60); - writeSentenceEntries("Sentences/Bellbot/6", 0x5D2CD0); - writeSentenceEntries("Sentences/Bellbot/7", 0x5D3488); - writeSentenceEntries("Sentences/Bellbot/8", 0x5D3900); - writeSentenceEntries("Sentences/Bellbot/9", 0x5D3968); - writeSentenceEntries("Sentences/Bellbot/10", 0x5D4668); - writeSentenceEntries("Sentences/Bellbot/11", 0x5D47A0); - writeSentenceEntries("Sentences/Bellbot/12", 0x5D4EC0); - writeSentenceEntries("Sentences/Bellbot/13", 0x5D5100); - writeSentenceEntries("Sentences/Bellbot/14", 0x5D5370); - writeSentenceEntries("Sentences/Bellbot/15", 0x5D5548); - writeSentenceEntries("Sentences/Bellbot/16", 0x5D56B8); - writeSentenceEntries("Sentences/Bellbot/17", 0x5D57C0); - writeSentenceEntries("Sentences/Bellbot/18", 0x5D5B38); - writeSentenceEntries("Sentences/Bellbot/19", 0x5D61B8); - - writeSentenceEntries("Sentences/Deskbot", 0x5DCD10); - writeSentenceEntries("Sentences/Deskbot/2", 0x5E8E18); - writeSentenceEntries("Sentences/Deskbot/3", 0x5E8BA8); + const int TEXT_PHRASES[3] = { 0x61D3C8, 0x618340, 0x61B1E0 }; + const int TEXT_REPLACEMENTS1[3] = { 0x61D9B0, 0x61C788, 0x61B7C8 }; + const int TEXT_REPLACEMENTS2[3] = { 0x61DD20, 0x61CAF8, 0x61BB38 }; + const int TEXT_REPLACEMENTS3[3] = { 0x61F5C8, 0x61E3A0, 0x61D3E0 }; + const int TEXT_PRONOUNS[3] = { 0x631318, 0x6300F8, 0x62F138 }; + writeStringArray("TEXT/PHRASES", TEXT_PHRASES[_version], 376); + writeStringArray("TEXT/REPLACEMENTS1", TEXT_REPLACEMENTS1[_version], 218); + writeStringArray("TEXT/REPLACEMENTS2", TEXT_REPLACEMENTS2[_version], 1576); + writeStringArray("TEXT/REPLACEMENTS3", TEXT_REPLACEMENTS3[_version], 82); + writeStringArray("TEXT/PRONOUNS", TEXT_PRONOUNS[_version], 15); + + const int SENTENCES_DEFAULT[3] = { 0x5C0130, 0x5BEFC8, 0x5BE008 }; + const int SENTENCES_BARBOT[2][3] = { + { 0x5ABE60, 0x5AACF8, 0x5A9D38 }, { 0x5BD4E8, 0x5BC380, 0x5BB3C0 } + }; + const int SENTENCES_BELLBOT[20][3] = { + { 0x5C2230, 0x5C10C8, 0X5C0108 }, { 0x5D1670, 0x5D0508, 0x5CF548 }, + { 0x5D1A80, 0x5D0918, 0x5CF958 }, { 0x5D1AE8, 0x5D0980, 0x5CF9C0 }, + { 0x5D1B88, 0x5D0A20, 0x5CFA60 }, { 0x5D2A60, 0x5D18F8, 0x5D0938 }, + { 0x5D2CD0, 0x5D1B68, 0x5D0BA8 }, { 0x5D3488, 0x5D2320, 0x5D1360 }, + { 0x5D3900, 0x5D2798, 0x5D17D8 }, { 0x5D3968, 0x5D2800, 0x5D1840 }, + { 0x5D4668, 0x5D3500, 0x5D2540 }, { 0x5D47A0, 0x5D3638, 0x5D2678 }, + { 0x5D4EC0, 0x5D3D58, 0x5D2D98 }, { 0x5D5100, 0x5D3F98, 0x5D2FD8 }, + { 0x5D5370, 0x5D4208, 0x5D3248 }, { 0x5D5548, 0x5D43E0, 0x5D3420 }, + { 0x5D56B8, 0x5D4550, 0x5D3590 }, { 0x5D57C0, 0x5D4658, 0x5D3698 }, + { 0x5D5B38, 0x5D49D0, 0x5D3A10 }, { 0x5D61B8, 0x5D5050, 0x5D4090 } + }; + writeSentenceEntries("Sentences/Default", SENTENCES_DEFAULT[_version]); + writeSentenceEntries("Sentences/Barbot", SENTENCES_BARBOT[0][_version]); + writeSentenceEntries("Sentences/Barbot2", SENTENCES_BARBOT[1][_version]); + writeSentenceEntries("Sentences/Bellbot", SENTENCES_BELLBOT[0][_version]); + writeSentenceEntries("Sentences/Bellbot/1", SENTENCES_BELLBOT[1][_version]); + writeSentenceEntries("Sentences/Bellbot/2", SENTENCES_BELLBOT[2][_version]); + writeSentenceEntries("Sentences/Bellbot/3", SENTENCES_BELLBOT[3][_version]); + writeSentenceEntries("Sentences/Bellbot/4", SENTENCES_BELLBOT[4][_version]); + writeSentenceEntries("Sentences/Bellbot/5", SENTENCES_BELLBOT[5][_version]); + writeSentenceEntries("Sentences/Bellbot/6", SENTENCES_BELLBOT[6][_version]); + writeSentenceEntries("Sentences/Bellbot/7", SENTENCES_BELLBOT[7][_version]); + writeSentenceEntries("Sentences/Bellbot/8", SENTENCES_BELLBOT[8][_version]); + writeSentenceEntries("Sentences/Bellbot/9", SENTENCES_BELLBOT[9][_version]); + writeSentenceEntries("Sentences/Bellbot/10", SENTENCES_BELLBOT[10][_version]); + writeSentenceEntries("Sentences/Bellbot/11", SENTENCES_BELLBOT[11][_version]); + writeSentenceEntries("Sentences/Bellbot/12", SENTENCES_BELLBOT[12][_version]); + writeSentenceEntries("Sentences/Bellbot/13", SENTENCES_BELLBOT[13][_version]); + writeSentenceEntries("Sentences/Bellbot/14", SENTENCES_BELLBOT[14][_version]); + writeSentenceEntries("Sentences/Bellbot/15", SENTENCES_BELLBOT[15][_version]); + writeSentenceEntries("Sentences/Bellbot/16", SENTENCES_BELLBOT[16][_version]); + writeSentenceEntries("Sentences/Bellbot/17", SENTENCES_BELLBOT[17][_version]); + writeSentenceEntries("Sentences/Bellbot/18", SENTENCES_BELLBOT[18][_version]); + writeSentenceEntries("Sentences/Bellbot/19", SENTENCES_BELLBOT[19][_version]); + + const int SENTENCES_DESKBOT[3][3] = { + { 0x5DCD10, 0x5DBBA8, 0x5DABE8 }, { 0x5E8E18, 0x5E7CB0, 0x5E6CF0 }, + { 0x5E8BA8, 0x5E7A40, 0x5E6A80 } + }; + writeSentenceEntries("Sentences/Deskbot", SENTENCES_DESKBOT[0][_version]); + writeSentenceEntries("Sentences/Deskbot/2", SENTENCES_DESKBOT[1][_version]); + writeSentenceEntries("Sentences/Deskbot/3", SENTENCES_DESKBOT[2][_version]); - writeSentenceEntries("Sentences/Doorbot", 0x5EC110); - writeSentenceEntries("Sentences/Doorbot/2", 0x5FD930); - writeSentenceEntries("Sentences/Doorbot/100", 0x5FD930); - writeSentenceEntries("Sentences/Doorbot/101", 0x5FE668); - writeSentenceEntries("Sentences/Doorbot/102", 0x5FDD40); - writeSentenceEntries("Sentences/Doorbot/107", 0x5FFF08); - writeSentenceEntries("Sentences/Doorbot/110", 0x5FE3C0); - writeSentenceEntries("Sentences/Doorbot/111", 0x5FF0C8); - writeSentenceEntries("Sentences/Doorbot/124", 0x5FF780); - writeSentenceEntries("Sentences/Doorbot/129", 0x5FFAC0); - writeSentenceEntries("Sentences/Doorbot/131", 0x5FFC30); - writeSentenceEntries("Sentences/Doorbot/132", 0x6000E0); - - writeSentenceEntries("Sentences/Liftbot", 0x6026B0); - writeSentenceEntries("Sentences/MaitreD", 0x60CFD8); - writeSentenceEntries("Sentences/MaitreD/1", 0x614288); - writeSentenceEntries("Sentences/Parrot", 0x615858); - writeSentenceEntries("Sentences/SuccUBus", 0x616698); - writeSentenceMappings("Mappings/Barbot", 0x5B28A0, 8); - writeSentenceMappings("Mappings/Bellbot", 0x5CD830, 1); - writeSentenceMappings("Mappings/Deskbot", 0x5E2BB8, 4); - writeSentenceMappings("Mappings/Doorbot", 0x5F7950, 4); - writeSentenceMappings("Mappings/Liftbot", 0x608660, 4); - writeSentenceMappings("Mappings/MaitreD", 0x6125C8, 1); - writeSentenceMappings("Mappings/Parrot", 0x615B68, 1); - writeSentenceMappings("Mappings/SuccUBus", 0x6189F0, 1); - writeWords("Words/Barbot", 0x5BE2E0); - writeWords("Words/Bellbot", 0x5D8230); - writeWords("Words/Deskbot", 0x5EAAA8); - writeWords("Words/Doorbot", 0x601098, 3); - writeWords("Words/Liftbot", 0x60C788); + const int SENTENCES_DOORBOT[12][3] = { + { 0x5EC110, 0x5EAFA8, 0x5E9FE8 }, { 0x5FD930, 0x5FC7C8, 0x5FB808 }, + { 0x5FDD0C, 0x5FCBA4, 0x5FBBE4 }, { 0x5FE668, 0x5FD500, 0x5FC540 }, + { 0x5FDD40, 0x5FCBD8, 0X5FBC18 }, { 0x5FFF08, 0x5FEDA0, 0x5FDDE0 }, + { 0x5FE3C0, 0x5FD258, 0x5FC298 }, { 0x5FF0C8, 0x5FDF60, 0x5FCFA0 }, + { 0x5FF780, 0x5FE618, 0x5FD658 }, { 0x5FFAC0, 0x5FE958, 0x5FD998 }, + { 0x5FFC30, 0x5FEAC8, 0x5FDB08 }, { 0x6000E0, 0x5FEF78, 0x5FDFB8 } + }; + writeSentenceEntries("Sentences/Doorbot", SENTENCES_DOORBOT[0][_version]); + writeSentenceEntries("Sentences/Doorbot/2", SENTENCES_DOORBOT[1][_version]); + writeSentenceEntries("Sentences/Doorbot/100", SENTENCES_DOORBOT[2][_version]); + writeSentenceEntries("Sentences/Doorbot/101", SENTENCES_DOORBOT[3][_version]); + writeSentenceEntries("Sentences/Doorbot/102", SENTENCES_DOORBOT[4][_version]); + writeSentenceEntries("Sentences/Doorbot/107", SENTENCES_DOORBOT[5][_version]); + writeSentenceEntries("Sentences/Doorbot/110", SENTENCES_DOORBOT[6][_version]); + writeSentenceEntries("Sentences/Doorbot/111", SENTENCES_DOORBOT[7][_version]); + writeSentenceEntries("Sentences/Doorbot/124", SENTENCES_DOORBOT[8][_version]); + writeSentenceEntries("Sentences/Doorbot/129", SENTENCES_DOORBOT[9][_version]); + writeSentenceEntries("Sentences/Doorbot/131", SENTENCES_DOORBOT[10][_version]); + writeSentenceEntries("Sentences/Doorbot/132", SENTENCES_DOORBOT[11][_version]); + + const int SENTENCES_LIFTBOT[3] = { 0x6026B0, 0x601548, 0x600588 }; + const int SENTENCES_MAITRED[2][3] = { + { 0x60CFD8, 0x60BE70, 0x60AEB0 }, { 0x614288, 0x613120, 0x612160 } + }; + const int SENTENCES_PARROT[3] = { 0x615858, 0x6146F0, 0x613730 }; + const int SENTENCES_SUCCUBUS[3] = { 0x616698, 0x615530, 0x614570 }; + const int MAPPINGS_BARBOT[3] = { 0x5B28A0, 0x5B173E, 0x5B0778 }; + const int MAPPINGS_BELLBOT[3] = { 0x5CD830, 0x5CC6C8, 0x5CB708 }; + const int MAPPINGS_DESKBOT[3] = { 0x5E2BB8, 0x5E1A50, 0x5E0A90 }; + const int MAPPINGS_DOORBOT[3] = { 0x5F7950, 0x5F67E8, 0x5F5828 }; + const int MAPPINGS_LIFTBOT[3] = { 0x608660, 0x6074F8, 0x606538 }; + const int MAPPINGS_MAITRED[3] = { 0x6125C8, 0x611460, 0x6104A0 }; + const int MAPPINGS_PARROT[3] = { 0x615B68, 0x614A00, 0x613A40 }; + const int MAPPINGS_SUCCUBUS[3] = { 0x6189F0, 0x617888, 0x6168C8 }; + const int WORDS_BARBOT[3] = { 0x5BE2E0, 0x5BD178, 0x5BC1B8 }; + const int WORDS_BELLBOT[3] = { 0x5D8230, 0x5D70C8, 0x5D6108 }; + const int WORDS_DESKBOT[3] = { 0x5EAAA8, 0x5E9940, 0x5E8980 }; + const int WORDS_DOORBOT[3] = { 0x601098, 0x5FFF30, 0x5FEF70 }; + const int WORDS_LIFTBOT[3] = { 0x60C788, 0x60B620, 0x60A660 }; + writeSentenceEntries("Sentences/Liftbot", SENTENCES_LIFTBOT[_version]); + writeSentenceEntries("Sentences/MaitreD", SENTENCES_MAITRED[0][_version]); + writeSentenceEntries("Sentences/MaitreD/1", SENTENCES_MAITRED[1][_version]); + writeSentenceEntries("Sentences/Parrot", SENTENCES_PARROT[_version]); + writeSentenceEntries("Sentences/SuccUBus", SENTENCES_SUCCUBUS[_version]); + writeSentenceMappings("Mappings/Barbot", MAPPINGS_BARBOT[_version], 8); + writeSentenceMappings("Mappings/Bellbot", MAPPINGS_BELLBOT[_version], 1); + writeSentenceMappings("Mappings/Deskbot", MAPPINGS_DESKBOT[_version], 4); + writeSentenceMappings("Mappings/Doorbot", MAPPINGS_DOORBOT[_version], 4); + writeSentenceMappings("Mappings/Liftbot", MAPPINGS_LIFTBOT[_version], 4); + writeSentenceMappings("Mappings/MaitreD", MAPPINGS_MAITRED[_version], 1); + writeSentenceMappings("Mappings/Parrot", MAPPINGS_PARROT[_version], 1); + writeSentenceMappings("Mappings/SuccUBus", MAPPINGS_SUCCUBUS[_version], 1); + writeWords("Words/Barbot", WORDS_BARBOT[_version]); + writeWords("Words/Bellbot", WORDS_BELLBOT[_version]); + writeWords("Words/Deskbot", WORDS_DESKBOT[_version]); + writeWords("Words/Doorbot", WORDS_DOORBOT[_version], 3); + writeWords("Words/Liftbot", WORDS_LIFTBOT[_version]); writePhrases("Phrases/Bellbot", BELLBOT_COMMON_PHRASES); writeResponseTree(); @@ -689,9 +994,14 @@ void writeData() { writeAllScriptQuotes(); writeAllScriptResponses(); writeAllScriptRanges(); + writeAllTagMappings(); writeAllUpdateStates(); writeAllScriptPreResponses(); + writeBarbotFrameRanges(); + writeMissiveOMatMessages(); + writeBedheadData(); + writeParrotLobbyLinkUpdaterEntries(); } void createScriptMap() { @@ -745,6 +1055,17 @@ int main(int argc, char *argv[]) { } res.loadFromEXE(argv[1]); + if (inputFile.size() == ENGLISH_10042C_FILESIZE) + _version = ENGLISH_10042C; + else if (inputFile.size() == ENGLISH_10042B_FILESIZE) + _version = ENGLISH_10042B; + else if (inputFile.size() == ENGLISH_10042_FILESIZE) + _version = ENGLISH_10042; + else { + printf("Unknown version of ST.exe specified"); + exit(0); + } + if (!outputFile.open(argv[2], Common::kFileWriteMode)) { error("Could not open output file"); } diff --git a/devtools/credits.pl b/devtools/credits.pl index 9cc5ad4227..3f1511c399 100755 --- a/devtools/credits.pl +++ b/devtools/credits.pl @@ -1244,7 +1244,7 @@ begin_credits("Credits"); begin_persons(); add_person("Daniel Balsom", "DanielFox", "For the original Reinherit (SAGA) code"); add_person("Sander Buskens", "", "For his work on the initial reversing of Monkey2"); - add_person("", "Canadacow", "For the original MT-32 emulator"); + add_person("Dean Beeler", "Canadacow", "For the original MT-32 emulator"); add_person("Kevin Carnes", "", "For Scumm16, the basis of ScummVM's older gfx codecs"); add_person("Curt Coder", "", "For the original TrollVM (preAGI) code"); add_person("Patrick Combet", "Dorian Gray", "For the original Gobliiins ADL player"); @@ -1253,12 +1253,12 @@ begin_credits("Credits"); add_person("DOSBox Team", "", "For their awesome OPL2 and OPL3 emulator"); add_person("Yusuke Kamiyamane", "", "For contributing some GUI icons"); add_person("Till Kresslein", "Krest", "For design of modern ScummVM GUI"); - add_person("", "Jezar", "For his freeverb filter implementation"); + add_person("Jezar Wakefield", "", "For his freeverb filter implementation"); add_person("Jim Leiterman", "", "Various info on his FM-TOWNS/Marty SCUMM ports"); - add_person("", "lloyd", "For deep tech details about C64 Zak & MM"); + add_person("Lloyd Rosen", "", "For deep tech details about C64 Zak & MM"); add_person("Sarien Team", "", "Original AGI engine code"); add_person("Jimmi Thøgersen", "", "For ScummRev, and much obscure code/documentation"); - add_person("", "Tristan", "For additional work on the original MT-32 emulator"); + add_person("Tristan Matthews", "", "For additional work on the original MT-32 emulator"); add_person("James Woodcock", "", "Soundtrack enhancements"); add_person("Anton Yartsev", "Zidane", "For the original re-implementation of the Z-Vision engine"); end_persons(); diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt index 92754a27b4..98f8774dd7 100644 --- a/devtools/scumm-md5.txt +++ b/devtools/scumm-md5.txt @@ -633,6 +633,7 @@ airport Let's Explore the Airport with Buzzy 8ffd618a776a4c0d8922bb28b09f8ce8 -1 en Windows - Demo - khalek e144f5f49d9241d2a9dee2576b3d09cb 51152 en Windows - Demo - khalek 86c9902b7bec1a17926d4dae85beaa45 -1 en Windows HE 71 Demo - khalek + 3c90d2a39cafa60b8ebce70a34a59a41 51152 nl Windows - Demo - Ben Castricum farm Let's Explore the Farm with Buzzy a5c5388da9bf0e6662fdca8813a79d13 86962 en Windows - - - George Kormendi diff --git a/dists/android/AndroidManifest.xml b/dists/android/AndroidManifest.xml index c091039266..64870a459b 100644 --- a/dists/android/AndroidManifest.xml +++ b/dists/android/AndroidManifest.xml @@ -40,6 +40,8 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" android:required="true"/> + <uses-feature android:name="android.hardware.screen.landscape" android:required="false" /> diff --git a/dists/android/AndroidManifest.xml.in b/dists/android/AndroidManifest.xml.in index 7eaece9d1f..9601425c74 100644 --- a/dists/android/AndroidManifest.xml.in +++ b/dists/android/AndroidManifest.xml.in @@ -40,6 +40,8 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" android:required="true"/> + <uses-feature android:name="android.hardware.screen.landscape" android:required="false" /> diff --git a/dists/cloudicon.png b/dists/cloudicon.png Binary files differnew file mode 100644 index 0000000000..21373aea1a --- /dev/null +++ b/dists/cloudicon.png diff --git a/dists/cloudicon_disabled.png b/dists/cloudicon_disabled.png Binary files differnew file mode 100644 index 0000000000..27c9600b0a --- /dev/null +++ b/dists/cloudicon_disabled.png diff --git a/dists/debian/control b/dists/debian/control index 40c0e53470..f101ce3ef8 100644 --- a/dists/debian/control +++ b/dists/debian/control @@ -3,7 +3,7 @@ Section: games Priority: optional Maintainer: Debian Games Team <pkg-games-devel@lists.alioth.debian.org> Uploaders: David Weinehall <tao@debian.org>, Moritz Muehlenhoff <jmm@debian.org> -Build-Depends: debhelper (>= 7.0.50~), nasm [i386], libsdl1.2-dev, libmad0-dev, libasound2-dev [linux-any], libvorbis-dev, libmpeg2-4-dev, libflac-dev, libz-dev, libfluidsynth-dev, python +Build-Depends: debhelper (>= 7.0.50~), nasm [i386], libsdl2-dev, libmad0-dev, libasound2-dev [linux-any], libvorbis-dev, libmpeg2-4-dev, libflac-dev, libz-dev, libfluidsynth-dev, python Standards-Version: 3.9.2 Homepage: http://www.scummvm.org diff --git a/dists/msvc10/create_msvc10.bat b/dists/msvc10/create_msvc10.bat index be0434fc50..53acbff42e 100644 --- a/dists/msvc10/create_msvc10.bat +++ b/dists/msvc10/create_msvc10.bat @@ -55,14 +55,14 @@ goto done echo. echo Creating project files with all engines enabled (stable and unstable) echo. -create_project ..\.. --enable-all-engines --msvc --msvc-version 10 --build-events +create_project ..\.. --enable-all-engines --disable-fluidsynth --msvc --msvc-version 10 --build-events goto done :stable echo. echo Creating normal project files, with only the stable engines enabled echo. -create_project ..\.. --msvc --msvc-version 10 +create_project ..\.. --disable-fluidsynth --msvc --msvc-version 10 goto done :tools diff --git a/dists/msvc11/create_msvc11.bat b/dists/msvc11/create_msvc11.bat index fc5471f46f..3c3052a5b0 100644 --- a/dists/msvc11/create_msvc11.bat +++ b/dists/msvc11/create_msvc11.bat @@ -55,14 +55,14 @@ goto done echo. echo Creating project files with all engines enabled (stable and unstable) echo. -create_project ..\.. --enable-all-engines --msvc --msvc-version 11 --build-events +create_project ..\.. --enable-all-engines --disable-fluidsynth --msvc --msvc-version 11 --build-events goto done :stable echo. echo Creating normal project files, with only the stable engines enabled echo. -create_project ..\.. --msvc --msvc-version 11 +create_project ..\.. --disable-fluidsynth --msvc --msvc-version 11 goto done :tools diff --git a/dists/msvc12/create_msvc12.bat b/dists/msvc12/create_msvc12.bat index d99001edb1..449b50ea54 100644 --- a/dists/msvc12/create_msvc12.bat +++ b/dists/msvc12/create_msvc12.bat @@ -55,14 +55,14 @@ goto done echo. echo Creating project files with all engines enabled (stable and unstable) echo. -create_project ..\.. --enable-all-engines --msvc --msvc-version 12 --build-events +create_project ..\.. --enable-all-engines --disable-fluidsynth --msvc --msvc-version 12 --build-events goto done :stable echo. echo Creating normal project files, with only the stable engines enabled echo. -create_project ..\.. --msvc --msvc-version 12 +create_project ..\.. --disable-fluidsynth --msvc --msvc-version 12 goto done :tools diff --git a/dists/msvc9/create_msvc9.bat b/dists/msvc9/create_msvc9.bat index 34bcccdd7b..005bc084db 100644 --- a/dists/msvc9/create_msvc9.bat +++ b/dists/msvc9/create_msvc9.bat @@ -55,14 +55,14 @@ goto done echo. echo Creating project files with all engines enabled (stable and unstable) echo. -create_project ..\.. --enable-all-engines --msvc --msvc-version 9 +create_project ..\.. --enable-all-engines --disable-fluidsynth --msvc --msvc-version 9 goto done :stable echo. echo Creating normal project files, with only the stable engines enabled echo. -create_project ..\.. --msvc --msvc-version 9 +create_project ..\.. --disable-fluidsynth --msvc --msvc-version 9 goto done :tools diff --git a/dists/scummvm.rc b/dists/scummvm.rc index 873feaa419..00d71ef717 100644 --- a/dists/scummvm.rc +++ b/dists/scummvm.rc @@ -19,6 +19,9 @@ scummmodern.zip FILE "gui/themes/scummmodern.zip" #ifdef USE_TRANSLATION translations.dat FILE "gui/themes/translations.dat" #endif +#ifdef USE_SDL_NET +wwwroot.zip FILE "backends/networking/wwwroot.zip" +#endif #if ENABLE_ACCESS == STATIC_PLUGIN access.dat FILE "dists/engine-data/access.dat" diff --git a/dists/win32/ScummVM.iss b/dists/win32/ScummVM.iss index 740b30d071..6a5b7d91da 100644 --- a/dists/win32/ScummVM.iss +++ b/dists/win32/ScummVM.iss @@ -105,7 +105,7 @@ Source: doc/de/LIESMICH.txt; DestDir: {app}; Flags: ignoreversion isreadme; Lang Source: doc/se/LasMig.txt; DestDir: {app}; Flags: ignoreversion isreadme; Languages: se Source: README-SDL.txt; DestDir: {app}; Flags: ignoreversion Source: scummvm.exe; DestDir: {app}; Flags: ignoreversion -Source: SDL.dll; DestDir: {app}; Flags: replacesameversion +Source: SDL2.dll; DestDir: {app}; Flags: replacesameversion ;Mirgration script for saved games in Windows NT4 onwards Source: migration.bat; DestDir: {app}; Flags: ignoreversion; MinVersion: 0, 1 Source: migration.txt; DestDir: {app}; Flags: ignoreversion; MinVersion: 0, 1 diff --git a/engines/access/detection.cpp b/engines/access/detection.cpp index 368753f117..3e70de3635 100644 --- a/engines/access/detection.cpp +++ b/engines/access/detection.cpp @@ -111,7 +111,8 @@ bool AccessMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsLoadingDuringStartup) || (f == kSupportsDeleteSave) || (f == kSavesSupportMetaInfo) || - (f == kSavesSupportThumbnail); + (f == kSavesSupportThumbnail) || + (f == kSimpleSavesNames); } bool Access::AccessEngine::hasFeature(EngineFeature f) const { diff --git a/engines/adl/adl.cpp b/engines/adl/adl.cpp index 83181c9847..9afb2c6700 100644 --- a/engines/adl/adl.cpp +++ b/engines/adl/adl.cpp @@ -402,6 +402,15 @@ byte AdlEngine::roomArg(byte room) const { return room; } +void AdlEngine::loadDroppedItemOffsets(Common::ReadStream &stream, byte count) { + for (uint i = 0; i < count; ++i) { + Common::Point p; + p.x = stream.readByte(); + p.y = stream.readByte(); + _itemOffsets.push_back(p); + } +} + void AdlEngine::clearScreen() const { _display->setMode(DISPLAY_MODE_MIXED); _display->clear(0x00); @@ -661,6 +670,11 @@ Common::Error AdlEngine::loadGameState(int slot) { _state.rooms[i].isFirstTime = inFile->readByte(); } + // NOTE: _state.curPicture is part of the save state in the original engine. We + // reconstruct it instead. This is believed to be safe for at least hires 0-2, but + // this may need to be re-evaluated for later games. + _state.curPicture = _state.rooms[_state.room].curPicture; + size = inFile->readUint32BE(); if (size != _state.items.size()) error("Item count mismatch (expected %i; found %i)", _state.items.size(), size); @@ -951,7 +965,7 @@ int AdlEngine::o1_isVarEQ(ScriptEnv &e) { int AdlEngine::o1_isCurPicEQ(ScriptEnv &e) { OP_DEBUG_1("\t&& GET_CURPIC() == %d", e.arg(1)); - if (getCurRoom().curPicture == e.arg(1)) + if (_state.curPicture == e.arg(1)) return 1; return -1; @@ -1258,16 +1272,17 @@ Common::String AdlEngine::toAscii(const Common::String &str) { } Common::String AdlEngine::itemStr(uint i) const { - byte desc = getItem(i).description; - byte noun = getItem(i).noun; + const Item &item(getItem(i)); + Common::String name = Common::String::format("%d", i); - if (noun > 0) { + if (item.noun > 0) { name += "/"; - name += _priNouns[noun - 1]; + name += _priNouns[item.noun - 1]; } - if (desc > 0) { + Common::String desc = getItemDescription(item); + if (!desc.empty()) { name += "/"; - name += toAscii(loadMessage(desc)); + name += toAscii(desc); } return name; } diff --git a/engines/adl/adl.h b/engines/adl/adl.h index c9d77fcc62..971336ef50 100644 --- a/engines/adl/adl.h +++ b/engines/adl/adl.h @@ -165,11 +165,12 @@ struct State { Common::Array<byte> vars; byte room; + byte curPicture; uint16 moves; bool isDark; Time time; - State() : room(1), moves(1), isDark(false) { } + State() : room(1), curPicture(0), moves(1), isDark(false) { } }; typedef Common::List<Command> Commands; @@ -246,6 +247,7 @@ protected: virtual void initState(); virtual byte roomArg(byte room) const; virtual void advanceClock() { } + void loadDroppedItemOffsets(Common::ReadStream &stream, byte count); // Opcodes int o1_isItemInRoom(ScriptEnv &e); diff --git a/engines/adl/adl_v2.cpp b/engines/adl/adl_v2.cpp index 4fdf796701..979d794146 100644 --- a/engines/adl/adl_v2.cpp +++ b/engines/adl/adl_v2.cpp @@ -79,9 +79,9 @@ void AdlEngine_v2::setupOpcodeTables() { Opcode(o1_listInv); Opcode(o2_moveItem); Opcode(o1_setRoom); - Opcode(o1_setCurPic); + Opcode(o2_setCurPic); // 0x08 - Opcode(o1_setPic); + Opcode(o2_setPic); Opcode(o1_printMsg); Opcode(o1_setLight); Opcode(o1_setDark); @@ -250,6 +250,8 @@ void AdlEngine_v2::loadRoom(byte roomNr) { void AdlEngine_v2::showRoom() { bool redrawPic = false; + _state.curPicture = getCurRoom().curPicture; + if (_state.room != _roomOnScreen) { loadRoom(_state.room); clearScreen(); @@ -257,15 +259,15 @@ void AdlEngine_v2::showRoom() { if (!_state.isDark) redrawPic = true; } else { - if (getCurRoom().curPicture != _picOnScreen || _itemRemoved) + if (_state.curPicture != _picOnScreen || _itemRemoved) redrawPic = true; } if (redrawPic) { _roomOnScreen = _state.room; - _picOnScreen = getCurRoom().curPicture; + _picOnScreen = _state.curPicture; - drawPic(getCurRoom().curPicture); + drawPic(_state.curPicture); _itemRemoved = false; _itemsOnScreen = 0; @@ -336,7 +338,7 @@ void AdlEngine_v2::drawItems() { Common::Array<byte>::const_iterator pic; for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) { - if (*pic == getCurRoom().curPicture || *pic == IDI_ANY) { + if (*pic == _state.curPicture || *pic == IDI_ANY) { drawItem(*item, item->position); break; } @@ -357,9 +359,83 @@ DataBlockPtr AdlEngine_v2::readDataBlockPtr(Common::ReadStream &f) const { if (track == 0 && sector == 0 && offset == 0 && size == 0) return DataBlockPtr(); + adjustDataBlockPtr(track, sector, offset, size); + return _disk->getDataBlock(track, sector, offset, size); } +void AdlEngine_v2::loadItems(Common::ReadStream &stream) { + byte id; + while ((id = stream.readByte()) != 0xff && !stream.eos() && !stream.err()) { + Item item = Item(); + item.id = id; + item.noun = stream.readByte(); + item.room = stream.readByte(); + item.picture = stream.readByte(); + item.isLineArt = stream.readByte(); // Disk number in later games + item.position.x = stream.readByte(); + item.position.y = stream.readByte(); + item.state = stream.readByte(); + item.description = stream.readByte(); + + stream.readByte(); // Struct size + + byte picListSize = stream.readByte(); + + // Flag to keep track of what has been drawn on the screen + stream.readByte(); + + for (uint i = 0; i < picListSize; ++i) + item.roomPictures.push_back(stream.readByte()); + + _state.items.push_back(item); + } + + if (stream.eos() || stream.err()) + error("Error loading items"); +} + +void AdlEngine_v2::loadRooms(Common::ReadStream &stream, byte count) { + for (uint i = 0; i < count; ++i) { + Room room; + + stream.readByte(); // number + for (uint j = 0; j < 6; ++j) + room.connections[j] = stream.readByte(); + room.data = readDataBlockPtr(stream); + room.picture = stream.readByte(); + room.curPicture = stream.readByte(); + room.isFirstTime = stream.readByte(); + + _state.rooms.push_back(room); + } + + if (stream.eos() || stream.err()) + error("Error loading rooms"); +} + +void AdlEngine_v2::loadMessages(Common::ReadStream &stream, byte count) { + for (uint i = 0; i < count; ++i) + _messages.push_back(readDataBlockPtr(stream)); +} + +void AdlEngine_v2::loadPictures(Common::ReadStream &stream) { + byte picNr; + while ((picNr = stream.readByte()) != 0xff) { + if (stream.eos() || stream.err()) + error("Error reading global pic list"); + + _pictures[picNr] = readDataBlockPtr(stream); + } +} + +void AdlEngine_v2::loadItemPictures(Common::ReadStream &stream, byte count) { + for (uint i = 0; i < count; ++i) { + stream.readByte(); // number + _itemPics.push_back(readDataBlockPtr(stream)); + } +} + int AdlEngine_v2::o2_isFirstTime(ScriptEnv &e) { OP_DEBUG_0("\t&& IS_FIRST_TIME()"); @@ -425,6 +501,20 @@ int AdlEngine_v2::o2_moveItem(ScriptEnv &e) { return 2; } +int AdlEngine_v2::o2_setCurPic(ScriptEnv &e) { + OP_DEBUG_1("\tSET_CURPIC(%d)", e.arg(1)); + + getCurRoom().curPicture = _state.curPicture = e.arg(1); + return 1; +} + +int AdlEngine_v2::o2_setPic(ScriptEnv &e) { + OP_DEBUG_1("\tSET_PIC(%d)", e.arg(1)); + + getCurRoom().picture = getCurRoom().curPicture = _state.curPicture = e.arg(1); + return 1; +} + int AdlEngine_v2::o2_moveAllItems(ScriptEnv &e) { OP_DEBUG_2("\tMOVE_ALL_ITEMS(%s, %s)", itemRoomStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); diff --git a/engines/adl/adl_v2.h b/engines/adl/adl_v2.h index f18972b74b..8f36b5cdb8 100644 --- a/engines/adl/adl_v2.h +++ b/engines/adl/adl_v2.h @@ -25,9 +25,6 @@ #include "adl/adl.h" -// Note: this version of ADL redraws only when necessary, but -// this is not currently implemented. - namespace Common { class RandomSource; } @@ -55,6 +52,12 @@ protected: void takeItem(byte noun); virtual DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const; + virtual void adjustDataBlockPtr(byte &track, byte §or, byte &offset, byte &size) const { } + void loadItems(Common::ReadStream &stream); + void loadRooms(Common::ReadStream &stream, byte count); + void loadMessages(Common::ReadStream &stream, byte count); + void loadPictures(Common::ReadStream &stream); + void loadItemPictures(Common::ReadStream &stream, byte count); void checkTextOverflow(char c); @@ -64,6 +67,8 @@ protected: int o2_isCarryingSomething(ScriptEnv &e); int o2_moveItem(ScriptEnv &e); + int o2_setCurPic(ScriptEnv &e); + int o2_setPic(ScriptEnv &e); int o2_moveAllItems(ScriptEnv &e); int o2_save(ScriptEnv &e); int o2_restore(ScriptEnv &e); diff --git a/engines/adl/adl_v3.cpp b/engines/adl/adl_v3.cpp index 623db661bc..ba9e4a063e 100644 --- a/engines/adl/adl_v3.cpp +++ b/engines/adl/adl_v3.cpp @@ -20,162 +20,50 @@ * */ -#include "common/random.h" -#include "common/error.h" - #include "adl/adl_v3.h" -#include "adl/display.h" -#include "adl/graphics.h" namespace Adl { AdlEngine_v3::AdlEngine_v3(OSystem *syst, const AdlGameDescription *gd) : - AdlEngine_v2(syst, gd), - _curDisk(0) { -} - -Common::String AdlEngine_v3::loadMessage(uint idx) const { - Common::String str = AdlEngine_v2::loadMessage(idx); - - for (uint i = 0; i < str.size(); ++i) { - const char *xorStr = "AVISDURGAN"; - str.setChar(str[i] ^ xorStr[i % strlen(xorStr)], i); - } - - return str; + AdlEngine_v2(syst, gd) { } Common::String AdlEngine_v3::getItemDescription(const Item &item) const { - return _itemDesc[item.id - 1]; + return _itemDesc[item.description - 1]; } -void AdlEngine_v3::applyDiskOffset(byte &track, byte §or) const { - sector += _diskOffsets[_curDisk].sector; - if (sector >= 16) { - sector -= 16; - ++track; - } +void AdlEngine_v3::loadItemDescriptions(Common::SeekableReadStream &stream, byte count) { + int32 startPos = stream.pos(); + uint16 baseAddr = stream.readUint16LE(); - track += _diskOffsets[_curDisk].track; -} - -DataBlockPtr AdlEngine_v3::readDataBlockPtr(Common::ReadStream &f) const { - byte track = f.readByte(); - byte sector = f.readByte(); - byte offset = f.readByte(); - byte size = f.readByte(); - - if (f.eos() || f.err()) - error("Error reading DataBlockPtr"); + // This code assumes that the first pointer points to a string that + // directly follows the pointer table + assert(baseAddr != 0); + baseAddr -= count * 2; - if (track == 0 && sector == 0 && offset == 0 && size == 0) - return DataBlockPtr(); + for (uint i = 0; i < count; ++i) { + stream.seek(startPos + i * 2); + uint16 offset = stream.readUint16LE(); - applyDiskOffset(track, sector); + if (offset > 0) { + stream.seek(startPos + offset - baseAddr); + _itemDesc.push_back(readString(stream, 0xff)); + } else + _itemDesc.push_back(Common::String()); + } - return _disk->getDataBlock(track, sector, offset, size); + if (stream.eos() || stream.err()) + error("Error loading item descriptions"); } typedef Common::Functor1Mem<ScriptEnv &, int, AdlEngine_v3> OpcodeV3; -#define SetOpcodeTable(x) table = &x; -#define Opcode(x) table->push_back(new OpcodeV3(this, &AdlEngine_v3::x)) -#define OpcodeUnImpl() table->push_back(new OpcodeV3(this, 0)) void AdlEngine_v3::setupOpcodeTables() { - Common::Array<const Opcode *> *table = 0; - - SetOpcodeTable(_condOpcodes); - // 0x00 - OpcodeUnImpl(); - Opcode(o2_isFirstTime); - Opcode(o2_isRandomGT); - Opcode(o3_isItemInRoom); - // 0x04 - Opcode(o3_isNounNotInRoom); - Opcode(o1_isMovesGT); - Opcode(o1_isVarEQ); - Opcode(o2_isCarryingSomething); - // 0x08 - Opcode(o3_isVarGT); - Opcode(o1_isCurPicEQ); - Opcode(o3_skipOneCommand); - - SetOpcodeTable(_actOpcodes); - // 0x00 - OpcodeUnImpl(); - Opcode(o1_varAdd); - Opcode(o1_varSub); - Opcode(o1_varSet); - // 0x04 - Opcode(o1_listInv); - Opcode(o3_moveItem); - Opcode(o1_setRoom); - Opcode(o1_setCurPic); - // 0x08 - Opcode(o1_setPic); - Opcode(o1_printMsg); - Opcode(o3_dummy); - Opcode(o3_setTextMode); - // 0x0c - Opcode(o2_moveAllItems); - Opcode(o1_quit); - Opcode(o3_dummy); - Opcode(o2_save); - // 0x10 - Opcode(o2_restore); - Opcode(o1_restart); - Opcode(o3_setDisk); - Opcode(o3_dummy); - // 0x14 - Opcode(o1_resetPic); - Opcode(o1_goDirection<IDI_DIR_NORTH>); - Opcode(o1_goDirection<IDI_DIR_SOUTH>); - Opcode(o1_goDirection<IDI_DIR_EAST>); - // 0x18 - Opcode(o1_goDirection<IDI_DIR_WEST>); - Opcode(o1_goDirection<IDI_DIR_UP>); - Opcode(o1_goDirection<IDI_DIR_DOWN>); - Opcode(o1_takeItem); - // 0x1c - Opcode(o1_dropItem); - Opcode(o1_setRoomPic); - Opcode(o3_sound); - OpcodeUnImpl(); - // 0x20 - Opcode(o2_initDisk); -} - -int AdlEngine_v3::o3_isVarGT(ScriptEnv &e) { - OP_DEBUG_2("\t&& VARS[%d] > %d", e.arg(1), e.arg(2)); - - if (getVar(e.arg(1)) > e.arg(2)) - return 2; - - return -1; -} - -int AdlEngine_v3::o3_skipOneCommand(ScriptEnv &e) { - OP_DEBUG_0("\t&& SKIP_ONE_COMMAND()"); - - _skipOneCommand = true; - setVar(2, 0); - - return -1; -} - -// FIXME: Rename "isLineArt" and look at code duplication -int AdlEngine_v3::o3_isItemInRoom(ScriptEnv &e) { - OP_DEBUG_2("\t&& GET_ITEM_ROOM(%s) == %s", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); - - const Item &item = getItem(e.arg(1)); - - if (e.arg(2) != IDI_ANY && item.isLineArt != _curDisk) - return -1; - - if (item.room == roomArg(e.arg(2))) - return 2; - - return -1; + AdlEngine_v2::setupOpcodeTables(); + delete _condOpcodes[0x04]; + _condOpcodes[0x04] = new OpcodeV3(this, &AdlEngine_v3::o3_isNounNotInRoom); + delete _actOpcodes[0x04]; + _actOpcodes[0x04] = new OpcodeV3(this, &AdlEngine_v3::o3_listInv); } int AdlEngine_v3::o3_isNounNotInRoom(ScriptEnv &e) { @@ -183,75 +71,28 @@ int AdlEngine_v3::o3_isNounNotInRoom(ScriptEnv &e) { Common::List<Item>::const_iterator item; - setVar(24, 0); + bool isAnItem = false; - for (item = _state.items.begin(); item != _state.items.end(); ++item) + for (item = _state.items.begin(); item != _state.items.end(); ++item) { if (item->noun == e.getNoun()) { - setVar(24, 1); + isAnItem = true; if (item->room == roomArg(e.arg(1))) return -1; } - - return 1; -} - -int AdlEngine_v3::o3_moveItem(ScriptEnv &e) { - OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); - - byte room = roomArg(e.arg(2)); - - Item &item = getItem(e.arg(1)); - - if (item.room == _roomOnScreen) - _picOnScreen = 0; - - // Set items that move from inventory to a room to state "dropped" - if (item.room == IDI_ANY && room != IDI_VOID_ROOM) - item.state = IDI_ITEM_DROPPED; - - item.room = room; - item.isLineArt = _curDisk; - return 2; -} - -int AdlEngine_v3::o3_dummy(ScriptEnv &e) { - OP_DEBUG_0("\tDUMMY()"); - - return 0; -} - -int AdlEngine_v3::o3_setTextMode(ScriptEnv &e) { - OP_DEBUG_1("\tSET_TEXT_MODE(%d)", e.arg(1)); - - // TODO - // 1: 4-line mode - // 2: 24-line mode - - switch (e.arg(1)) { - case 3: - // We re-use the restarting flag here, to simulate a long jump - _isRestarting = true; - return -1; } - return 1; + return (isAnItem ? 1 : -1); } -int AdlEngine_v3::o3_setDisk(ScriptEnv &e) { - OP_DEBUG_2("\tSET_DISK(%d, %d)", e.arg(1), e.arg(2)); - - // TODO - // Arg 1: disk - // Arg 2: room - - return 2; -} +int AdlEngine_v3::o3_listInv(ScriptEnv &e) { + OP_DEBUG_0("\tLIST_INVENTORY()"); -int AdlEngine_v3::o3_sound(ScriptEnv &e) { - OP_DEBUG_0("\tSOUND()"); + Common::List<Item>::const_iterator item; - // TODO + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->room == IDI_ANY) + printString(_itemDesc[item->description - 1]); return 0; } diff --git a/engines/adl/adl_v3.h b/engines/adl/adl_v3.h index 61dd5852e7..b0d40f3993 100644 --- a/engines/adl/adl_v3.h +++ b/engines/adl/adl_v3.h @@ -25,18 +25,6 @@ #include "adl/adl_v2.h" -// Note: this version of ADL redraws only when necessary, but -// this is not currently implemented. - -namespace Common { -class RandomSource; -} - -struct DiskOffset { - byte track; - byte sector; -}; - namespace Adl { class AdlEngine_v3 : public AdlEngine_v2 { @@ -48,27 +36,14 @@ protected: // AdlEngine virtual void setupOpcodeTables(); - virtual Common::String loadMessage(uint idx) const; Common::String getItemDescription(const Item &item) const; - // AdlEngine_v2 - virtual DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const; - - void applyDiskOffset(byte &track, byte §or) const; + void loadItemDescriptions(Common::SeekableReadStream &stream, byte count); - int o3_isVarGT(ScriptEnv &e); - int o3_isItemInRoom(ScriptEnv &e); int o3_isNounNotInRoom(ScriptEnv &e); - int o3_skipOneCommand(ScriptEnv &e); - int o3_moveItem(ScriptEnv &e); - int o3_dummy(ScriptEnv &e); - int o3_setTextMode(ScriptEnv &e); - int o3_setDisk(ScriptEnv &e); - int o3_sound(ScriptEnv &e); + int o3_listInv(ScriptEnv &e); Common::Array<Common::String> _itemDesc; - byte _curDisk; - Common::Array<DiskOffset> _diskOffsets; }; } // End of namespace Adl diff --git a/engines/adl/adl_v4.cpp b/engines/adl/adl_v4.cpp new file mode 100644 index 0000000000..ed20c82513 --- /dev/null +++ b/engines/adl/adl_v4.cpp @@ -0,0 +1,258 @@ +/* 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/random.h" +#include "common/error.h" + +#include "adl/adl_v4.h" +#include "adl/display.h" +#include "adl/graphics.h" + +namespace Adl { + +AdlEngine_v4::AdlEngine_v4(OSystem *syst, const AdlGameDescription *gd) : + AdlEngine_v3(syst, gd), + _curDisk(0) { +} + +Common::String AdlEngine_v4::loadMessage(uint idx) const { + Common::String str = AdlEngine_v2::loadMessage(idx); + + for (uint i = 0; i < str.size(); ++i) { + const char *xorStr = "AVISDURGAN"; + str.setChar(str[i] ^ xorStr[i % strlen(xorStr)], i); + } + + return str; +} + +Common::String AdlEngine_v4::getItemDescription(const Item &item) const { + return _itemDesc[item.id - 1]; +} + +void AdlEngine_v4::applyDiskOffset(byte &track, byte §or) const { + sector += _diskOffsets[_curDisk].sector; + if (sector >= 16) { + sector -= 16; + ++track; + } + + track += _diskOffsets[_curDisk].track; +} + +void AdlEngine_v4::adjustDataBlockPtr(byte &track, byte §or, byte &offset, byte &size) const { + applyDiskOffset(track, sector); +} + +typedef Common::Functor1Mem<ScriptEnv &, int, AdlEngine_v4> OpcodeV4; +#define SetOpcodeTable(x) table = &x; +#define Opcode(x) table->push_back(new OpcodeV4(this, &AdlEngine_v4::x)) +#define OpcodeUnImpl() table->push_back(new OpcodeV4(this, 0)) + +void AdlEngine_v4::setupOpcodeTables() { + Common::Array<const Opcode *> *table = 0; + + SetOpcodeTable(_condOpcodes); + // 0x00 + OpcodeUnImpl(); + Opcode(o2_isFirstTime); + Opcode(o2_isRandomGT); + Opcode(o4_isItemInRoom); + // 0x04 + Opcode(o4_isNounNotInRoom); + Opcode(o1_isMovesGT); + Opcode(o1_isVarEQ); + Opcode(o2_isCarryingSomething); + // 0x08 + Opcode(o4_isVarGT); + Opcode(o1_isCurPicEQ); + Opcode(o4_skipOneCommand); + + SetOpcodeTable(_actOpcodes); + // 0x00 + OpcodeUnImpl(); + Opcode(o1_varAdd); + Opcode(o1_varSub); + Opcode(o1_varSet); + // 0x04 + Opcode(o4_listInv); + Opcode(o4_moveItem); + Opcode(o1_setRoom); + Opcode(o2_setCurPic); + // 0x08 + Opcode(o2_setPic); + Opcode(o1_printMsg); + Opcode(o4_dummy); + Opcode(o4_setTextMode); + // 0x0c + Opcode(o2_moveAllItems); + Opcode(o1_quit); + Opcode(o4_dummy); + Opcode(o2_save); + // 0x10 + Opcode(o2_restore); + Opcode(o1_restart); + Opcode(o4_setDisk); + Opcode(o4_dummy); + // 0x14 + Opcode(o1_resetPic); + Opcode(o1_goDirection<IDI_DIR_NORTH>); + Opcode(o1_goDirection<IDI_DIR_SOUTH>); + Opcode(o1_goDirection<IDI_DIR_EAST>); + // 0x18 + Opcode(o1_goDirection<IDI_DIR_WEST>); + Opcode(o1_goDirection<IDI_DIR_UP>); + Opcode(o1_goDirection<IDI_DIR_DOWN>); + Opcode(o1_takeItem); + // 0x1c + Opcode(o1_dropItem); + Opcode(o1_setRoomPic); + Opcode(o4_sound); + OpcodeUnImpl(); + // 0x20 + Opcode(o2_initDisk); +} + +int AdlEngine_v4::o4_isVarGT(ScriptEnv &e) { + OP_DEBUG_2("\t&& VARS[%d] > %d", e.arg(1), e.arg(2)); + + if (getVar(e.arg(1)) > e.arg(2)) + return 2; + + return -1; +} + +int AdlEngine_v4::o4_skipOneCommand(ScriptEnv &e) { + OP_DEBUG_0("\t&& SKIP_ONE_COMMAND()"); + + _skipOneCommand = true; + setVar(2, 0); + + return -1; +} + +// FIXME: Rename "isLineArt" and look at code duplication +int AdlEngine_v4::o4_isItemInRoom(ScriptEnv &e) { + OP_DEBUG_2("\t&& GET_ITEM_ROOM(%s) == %s", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); + + const Item &item = getItem(e.arg(1)); + + if (e.arg(2) != IDI_ANY && item.isLineArt != _curDisk) + return -1; + + if (item.room == roomArg(e.arg(2))) + return 2; + + return -1; +} + +int AdlEngine_v4::o4_isNounNotInRoom(ScriptEnv &e) { + OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str()); + + Common::List<Item>::const_iterator item; + + setVar(24, 0); + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->noun == e.getNoun()) { + setVar(24, 1); + + if (item->room == roomArg(e.arg(1))) + return -1; + } + + return 1; +} + +int AdlEngine_v4::o4_listInv(ScriptEnv &e) { + OP_DEBUG_0("\tLIST_INVENTORY()"); + + Common::List<Item>::const_iterator item; + + for (item = _state.items.begin(); item != _state.items.end(); ++item) + if (item->room == IDI_ANY) + printString(_itemDesc[item->id - 1]); + + return 0; +} + +int AdlEngine_v4::o4_moveItem(ScriptEnv &e) { + OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str()); + + byte room = roomArg(e.arg(2)); + + Item &item = getItem(e.arg(1)); + + if (item.room == _roomOnScreen) + _picOnScreen = 0; + + // Set items that move from inventory to a room to state "dropped" + if (item.room == IDI_ANY && room != IDI_VOID_ROOM) + item.state = IDI_ITEM_DROPPED; + + item.room = room; + item.isLineArt = _curDisk; + return 2; +} + +int AdlEngine_v4::o4_dummy(ScriptEnv &e) { + OP_DEBUG_0("\tDUMMY()"); + + return 0; +} + +int AdlEngine_v4::o4_setTextMode(ScriptEnv &e) { + OP_DEBUG_1("\tSET_TEXT_MODE(%d)", e.arg(1)); + + // TODO + // 1: 4-line mode + // 2: 24-line mode + + switch (e.arg(1)) { + case 3: + // We re-use the restarting flag here, to simulate a long jump + _isRestarting = true; + return -1; + } + + return 1; +} + +int AdlEngine_v4::o4_setDisk(ScriptEnv &e) { + OP_DEBUG_2("\tSET_DISK(%d, %d)", e.arg(1), e.arg(2)); + + // TODO + // Arg 1: disk + // Arg 2: room + + return 2; +} + +int AdlEngine_v4::o4_sound(ScriptEnv &e) { + OP_DEBUG_0("\tSOUND()"); + + // TODO + + return 0; +} + +} // End of namespace Adl diff --git a/engines/adl/hires2.h b/engines/adl/adl_v4.h index 50016725d6..79aa824d92 100644 --- a/engines/adl/hires2.h +++ b/engines/adl/adl_v4.h @@ -20,45 +20,52 @@ * */ -#ifndef ADL_HIRES2_H -#define ADL_HIRES2_H +#ifndef ADL_ADL_V4_H +#define ADL_ADL_V4_H -#include "common/str.h" - -#include "adl/adl_v2.h" -#include "adl/disk.h" +#include "adl/adl_v3.h" namespace Common { -class ReadStream; -struct Point; +class RandomSource; } +struct DiskOffset { + byte track; + byte sector; +}; + namespace Adl { -#define IDS_HR2_DISK_IMAGE "WIZARD.DSK" +class AdlEngine_v4 : public AdlEngine_v3 { +public: + virtual ~AdlEngine_v4() { } -#define IDI_HR2_NUM_ROOMS 135 -#define IDI_HR2_NUM_MESSAGES 255 -#define IDI_HR2_NUM_VARS 40 -#define IDI_HR2_NUM_ITEM_PICS 38 -#define IDI_HR2_NUM_ITEM_OFFSETS 16 +protected: + AdlEngine_v4(OSystem *syst, const AdlGameDescription *gd); -// Messages used outside of scripts -#define IDI_HR2_MSG_CANT_GO_THERE 123 -#define IDI_HR2_MSG_DONT_UNDERSTAND 19 -#define IDI_HR2_MSG_ITEM_DOESNT_MOVE 242 -#define IDI_HR2_MSG_ITEM_NOT_HERE 4 -#define IDI_HR2_MSG_THANKS_FOR_PLAYING 239 + // AdlEngine + virtual void setupOpcodeTables(); + virtual Common::String loadMessage(uint idx) const; + Common::String getItemDescription(const Item &item) const; -class HiRes2Engine : public AdlEngine_v2 { -public: - HiRes2Engine(OSystem *syst, const AdlGameDescription *gd) : AdlEngine_v2(syst, gd) { } + // AdlEngine_v2 + virtual void adjustDataBlockPtr(byte &track, byte §or, byte &offset, byte &size) const; -private: - // AdlEngine - void runIntro() const; - void init(); - void initGameState(); + void applyDiskOffset(byte &track, byte §or) const; + + int o4_isVarGT(ScriptEnv &e); + int o4_isItemInRoom(ScriptEnv &e); + int o4_isNounNotInRoom(ScriptEnv &e); + int o4_skipOneCommand(ScriptEnv &e); + int o4_listInv(ScriptEnv &e); + int o4_moveItem(ScriptEnv &e); + int o4_dummy(ScriptEnv &e); + int o4_setTextMode(ScriptEnv &e); + int o4_setDisk(ScriptEnv &e); + int o4_sound(ScriptEnv &e); + + byte _curDisk; + Common::Array<DiskOffset> _diskOffsets; }; } // End of namespace Adl diff --git a/engines/adl/detection.cpp b/engines/adl/detection.cpp index 6a85f98cf0..10812d79ea 100644 --- a/engines/adl/detection.cpp +++ b/engines/adl/detection.cpp @@ -76,6 +76,7 @@ static const PlainGameDescriptor adlGames[] = { { "hires0", "Hi-Res Adventure #0: Mission Asteroid" }, { "hires1", "Hi-Res Adventure #1: Mystery House" }, { "hires2", "Hi-Res Adventure #2: Wizard and the Princess" }, + { "hires4", "Hi-Res Adventure #4: Ulysses and the Golden Fleece" }, { "hires6", "Hi-Res Adventure #6: The Dark Crystal" }, { 0, 0 } }; @@ -92,7 +93,7 @@ static const AdlGameDescription gameDescriptions[] = { }, Common::EN_ANY, Common::kPlatformApple2, - ADGF_UNSTABLE, + ADGF_TESTING, GUIO2(GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_SCANLINES) }, GAME_TYPE_HIRES1 @@ -106,7 +107,7 @@ static const AdlGameDescription gameDescriptions[] = { }, Common::EN_ANY, Common::kPlatformApple2, - ADGF_UNSTABLE, + ADGF_TESTING, GUIO2(GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_SCANLINES) }, GAME_TYPE_HIRES1 @@ -120,7 +121,7 @@ static const AdlGameDescription gameDescriptions[] = { }, Common::EN_ANY, Common::kPlatformApple2, - ADGF_UNSTABLE, + ADGF_TESTING, GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES) }, GAME_TYPE_HIRES2 @@ -134,11 +135,26 @@ static const AdlGameDescription gameDescriptions[] = { }, Common::EN_ANY, Common::kPlatformApple2, - ADGF_UNSTABLE, + ADGF_TESTING, GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES) }, GAME_TYPE_HIRES0 }, + { // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Atari 8-bit - Re-release + { + "hires4", 0, + { + { "ULYS1A.XFD", 0, "26365d2b06509fd21e7a7919e33f7199", 92160 }, + // FIXME: Add sides 1B and 2C + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformAtariST, // FIXME + ADGF_UNSTABLE, + GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES) + }, + GAME_TYPE_HIRES4 + }, { // Hi-Res Adventure #6: The Dark Crystal - Apple II - Roberta Williams Anthology { "hires6", 0, @@ -189,6 +205,7 @@ bool AdlMetaEngine::hasFeature(MetaEngineFeature f) const { case kSavesSupportThumbnail: case kSavesSupportCreationDate: case kSavesSupportPlayTime: + case kSimpleSavesNames: return true; default: return false; @@ -296,6 +313,8 @@ void AdlMetaEngine::removeSaveState(const char *target, int slot) const { Engine *HiRes1Engine_create(OSystem *syst, const AdlGameDescription *gd); Engine *HiRes2Engine_create(OSystem *syst, const AdlGameDescription *gd); +Engine *HiRes0Engine_create(OSystem *syst, const AdlGameDescription *gd); +Engine *HiRes4Engine_create(OSystem *syst, const AdlGameDescription *gd); Engine *HiRes6Engine_create(OSystem *syst, const AdlGameDescription *gd); bool AdlMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const { @@ -311,6 +330,12 @@ bool AdlMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameD case GAME_TYPE_HIRES2: *engine = HiRes2Engine_create(syst, adlGd); break; + case GAME_TYPE_HIRES0: + *engine = HiRes0Engine_create(syst, adlGd); + break; + case GAME_TYPE_HIRES4: + *engine = HiRes4Engine_create(syst, adlGd); + break; case GAME_TYPE_HIRES6: *engine = HiRes6Engine_create(syst, adlGd); break; diff --git a/engines/adl/detection.h b/engines/adl/detection.h index 533466c094..b4dc3c430f 100644 --- a/engines/adl/detection.h +++ b/engines/adl/detection.h @@ -35,6 +35,7 @@ enum GameType { GAME_TYPE_HIRES0, GAME_TYPE_HIRES1, GAME_TYPE_HIRES2, + GAME_TYPE_HIRES4, GAME_TYPE_HIRES6 }; diff --git a/engines/adl/disk.cpp b/engines/adl/disk.cpp index 214f76aeae..d429556670 100644 --- a/engines/adl/disk.cpp +++ b/engines/adl/disk.cpp @@ -28,98 +28,54 @@ namespace Adl { -const DataBlockPtr DiskImage_DSK::getDataBlock(uint track, uint sector, uint offset, uint size) const { - return Common::SharedPtr<DiskImage::DataBlock>(new DiskImage::DataBlock(this, track, sector, offset, size)); -} - -Common::SeekableReadStream *DiskImage_DSK::createReadStream(uint track, uint sector, uint offset, uint size) const { - _f->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset); - Common::SeekableReadStream *stream = _f->readStream(size * _bytesPerSector + _bytesPerSector - offset); - - if (_f->eos() || _f->err()) - error("Error reading disk image"); - - return stream; -} - -bool DiskImage_DSK::open(const Common::String &filename) { - assert(!_f->isOpen()); - - if (!_f->open(filename)) - return false; +static Common::SeekableReadStream *readImage(const Common::String &filename) { + Common::File *f = new Common::File; - uint filesize = _f->size(); - switch (filesize) { - case 143360: - _tracks = 35; - _sectorsPerTrack = 16; - _bytesPerSector = 256; - break; - default: - warning("Unrecognized disk image '%s' of size %d bytes", filename.c_str(), filesize); - return false; + if (!f->open(filename)) { + delete f; + return nullptr; } - return true; -} - -const DataBlockPtr DiskImage_NIB::getDataBlock(uint track, uint sector, uint offset, uint size) const { - return Common::SharedPtr<DiskImage::DataBlock>(new DiskImage::DataBlock(this, track, sector, offset, size)); -} - -Common::SeekableReadStream *DiskImage_NIB::createReadStream(uint track, uint sector, uint offset, uint size) const { - _memStream->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset); - Common::SeekableReadStream *stream = _memStream->readStream(size * _bytesPerSector + _bytesPerSector - offset); - - if (_memStream->eos() || _memStream->err()) - error("Error reading NIB image"); - - return stream; + return f; } // 4-and-4 encoding (odd-even) -static uint8 read44(Common::SeekableReadStream *f) { +static uint8 read44(Common::SeekableReadStream &f) { // 1s in the other fields, so we can just AND - uint8 ret = f->readByte(); - return ((ret << 1) | 1) & f->readByte(); + uint8 ret = f.readByte(); + return ((ret << 1) | 1) & f.readByte(); } -bool DiskImage_NIB::open(const Common::String &filename) { - assert(!_f->isOpen()); +static Common::SeekableReadStream *readImage_NIB(const Common::String &filename) { + Common::File f; - if (!_f->open(filename)) - return false; + if (!f.open(filename)) + return nullptr; - uint filesize = _f->size(); - switch (filesize) { - case 232960: - _tracks = 35; - _sectorsPerTrack = 16; // we always pad it out - _bytesPerSector = 256; - break; - default: - error("Unrecognized NIB image '%s' of size %d bytes", filename.c_str(), filesize); - } + if (f.size() != 232960) + error("Unrecognized NIB image '%s' of size %d bytes", filename.c_str(), f.size()); // starting at 0xaa, 32 is invalid (see below) const byte c_5and3_lookup[] = { 32, 0, 32, 1, 2, 3, 32, 32, 32, 32, 32, 4, 5, 6, 32, 32, 7, 8, 32, 9, 10, 11, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 12, 13, 32, 32, 14, 15, 32, 16, 17, 18, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 19, 20, 32, 21, 22, 23, 32, 32, 32, 32, 32, 24, 25, 26, 32, 32, 27, 28, 32, 29, 30, 31 }; // starting at 0x96, 64 is invalid (see below) const byte c_6and2_lookup[] = { 0, 1, 64, 64, 2, 3, 64, 4, 5, 6, 64, 64, 64, 64, 64, 64, 7, 8, 64, 64, 64, 9, 10, 11, 12, 13, 64, 64, 14, 15, 16, 17, 18, 19, 64, 20, 21, 22, 23, 24, 25, 26, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 27, 64, 28, 29, 30, 64, 64, 64, 31, 64, 64, 32, 33, 64, 34, 35, 36, 37, 38, 39, 40, 64, 64, 64, 64, 64, 41, 42, 43, 64, 44, 45, 46, 47, 48, 49, 50, 64, 64, 51, 52, 53, 54, 55, 56, 64, 57, 58, 59, 60, 61, 62, 63 }; - uint32 diskSize = _tracks * _sectorsPerTrack * _bytesPerSector; - byte *diskImage = (byte *)calloc(diskSize, 1); - _memStream = new Common::MemoryReadStream(diskImage, diskSize, DisposeAfterUse::YES); + // we always pad it out + const uint sectorsPerTrack = 16; + const uint bytesPerSector = 256; + const uint imageSize = 35 * sectorsPerTrack * bytesPerSector; + byte *const diskImage = (byte *)calloc(imageSize, 1); bool sawAddress = false; uint8 volNo, track, sector; bool newStyle; - while (_f->pos() < _f->size()) { + while (f.pos() < f.size()) { // Read until we find two sync bytes. - if (_f->readByte() != 0xd5 || _f->readByte() != 0xaa) + if (f.readByte() != 0xd5 || f.readByte() != 0xaa) continue; - byte prologue = _f->readByte(); + byte prologue = f.readByte(); if (sawAddress && (prologue == 0xb5 || prologue == 0x96)) { warning("NIB: data for %02x/%02x/%02x missing", volNo, track, sector); @@ -140,21 +96,13 @@ bool DiskImage_NIB::open(const Common::String &filename) { } } - volNo = read44(_f); - track = read44(_f); - sector = read44(_f); - uint8 checksum = read44(_f); + volNo = read44(f); + track = read44(f); + sector = read44(f); + uint8 checksum = read44(f); if ((volNo ^ track ^ sector) != checksum) error("invalid NIB checksum"); - // FIXME: This is a hires0/hires2-specific hack. - if (volNo == 0xfe) { - if (track == 1) - track = 2; - else if (track == 2) - track = 1; - } - // Epilogue is de/aa plus a gap, but we don't care. continue; } @@ -163,17 +111,17 @@ bool DiskImage_NIB::open(const Common::String &filename) { // We should always find the data field after an address field. // TODO: we ignore volNo? - byte *output = diskImage + (track * _sectorsPerTrack + sector) * _bytesPerSector; + byte *output = diskImage + (track * sectorsPerTrack + sector) * bytesPerSector; if (newStyle) { // We hardcode the DOS 3.3 mapping here. TODO: Do we also need raw/prodos? int raw2dos[16] = { 0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15 }; sector = raw2dos[sector]; - output = diskImage + (track * _sectorsPerTrack + sector) * _bytesPerSector; + output = diskImage + (track * sectorsPerTrack + sector) * bytesPerSector; // 6-and-2 uses 342 on-disk bytes byte inbuffer[342]; - _f->read(inbuffer, 342); + f.read(inbuffer, 342); byte oldVal = 0; for (uint n = 0; n < 342; ++n) { @@ -188,7 +136,7 @@ bool DiskImage_NIB::open(const Common::String &filename) { inbuffer[n] = oldVal; } - byte checksum = _f->readByte(); + byte checksum = f.readByte(); if (checksum < 0x96 || oldVal != c_6and2_lookup[checksum - 0x96]) warning("NIB: checksum mismatch @ (%x, %x)", track, sector); @@ -208,7 +156,7 @@ bool DiskImage_NIB::open(const Common::String &filename) { } else { // 5-and-3 uses 410 on-disk bytes, decoding to just over 256 bytes byte inbuffer[410]; - _f->read(inbuffer, 410); + f.read(inbuffer, 410); bool truncated = false; byte oldVal = 0; @@ -218,16 +166,16 @@ bool DiskImage_NIB::open(const Common::String &filename) { if (inbuffer[n] == 0xd5) { // Early end of block. truncated = true; - _f->seek(-(410 - n), SEEK_CUR); - warning("NIB: early end of block @ 0x%x (%x, %x)", _f->pos(), track, sector); + f.seek(-(410 - n), SEEK_CUR); + warning("NIB: early end of block @ 0x%x (%x, %x)", f.pos(), track, sector); break; } byte val = c_5and3_lookup[inbuffer[n] - 0xaa]; if (val == 0x20) { // Badly-encoded nibbles, stop trying to decode here. truncated = true; - warning("NIB: bad nibble %02x @ 0x%x (%x, %x)", inbuffer[n], _f->pos(), track, sector); - _f->seek(-(410 - n), SEEK_CUR); + warning("NIB: bad nibble %02x @ 0x%x (%x, %x)", inbuffer[n], f.pos(), track, sector); + f.seek(-(410 - n), SEEK_CUR); break; } // undo checksum @@ -235,7 +183,7 @@ bool DiskImage_NIB::open(const Common::String &filename) { inbuffer[n] = oldVal; } if (!truncated) { - byte checksum = _f->readByte(); + byte checksum = f.readByte(); if (checksum < 0xaa || oldVal != c_5and3_lookup[checksum - 0xaa]) warning("NIB: checksum mismatch @ (%x, %x)", track, sector); } @@ -259,9 +207,77 @@ bool DiskImage_NIB::open(const Common::String &filename) { } } + return new Common::MemoryReadStream(diskImage, imageSize, DisposeAfterUse::YES); +} + +bool DiskImage::open(const Common::String &filename) { + Common::String lcName(filename); + lcName.toLowercase(); + + if (lcName.hasSuffix(".dsk")) { + _stream = readImage(filename); + _tracks = 35; + _sectorsPerTrack = 16; + _bytesPerSector = 256; + } else if (lcName.hasSuffix(".nib")) { + _stream = readImage_NIB(filename); + _tracks = 35; + _sectorsPerTrack = 16; + _bytesPerSector = 256; + } else if (lcName.hasSuffix(".xfd")) { + _stream = readImage(filename); + _tracks = 40; + _sectorsPerTrack = 18; + _bytesPerSector = 128; + } + + int expectedSize = _tracks * _sectorsPerTrack * _bytesPerSector; + + if (!_stream) + return false; + + if (_stream->size() != expectedSize) + error("Unrecognized disk image '%s' of size %d bytes (expected %d bytes)", filename.c_str(), _stream->size(), expectedSize); + return true; } +const DataBlockPtr DiskImage::getDataBlock(uint track, uint sector, uint offset, uint size) const { + return DataBlockPtr(new DiskImage::DataBlock(this, track, sector, offset, size, _sectorLimit)); +} + +Common::SeekableReadStream *DiskImage::createReadStream(uint track, uint sector, uint offset, uint size, uint sectorLimit) const { + const uint bytesToRead = size * _bytesPerSector + _bytesPerSector - offset; + byte *const data = (byte *)malloc(bytesToRead); + uint dataOffset = 0; + + if (sectorLimit == 0) + sectorLimit = _sectorsPerTrack; + + if (sector >= sectorLimit) + error("Sector %i is out of bounds for %i-sector reading", sector, sectorLimit); + + while (dataOffset < bytesToRead) { + uint bytesRemInTrack = (sectorLimit - 1 - sector) * _bytesPerSector + _bytesPerSector - offset; + _stream->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset); + + if (bytesToRead - dataOffset < bytesRemInTrack) + bytesRemInTrack = bytesToRead - dataOffset; + + if (_stream->read(data + dataOffset, bytesRemInTrack) < bytesRemInTrack) + error("Error reading disk image at track %d; sector %d", track, sector); + + ++track; + + sector = 0; + offset = 0; + + dataOffset += bytesRemInTrack; + } + + return new Common::MemoryReadStream(data, bytesToRead, DisposeAfterUse::YES); +} + const DataBlockPtr Files_Plain::getDataBlock(const Common::String &filename, uint offset) const { return Common::SharedPtr<Files::DataBlock>(new Files::DataBlock(this, filename, offset)); } @@ -449,7 +465,7 @@ Common::SeekableReadStream *Files_DOS33::createReadStream(const Common::String & } bool Files_DOS33::open(const Common::String &filename) { - _disk = new DiskImage_DSK(); + _disk = new DiskImage(); if (!_disk->open(filename)) return false; diff --git a/engines/adl/disk.h b/engines/adl/disk.h index 43b9e387ba..653d76ff10 100644 --- a/engines/adl/disk.h +++ b/engines/adl/disk.h @@ -73,41 +73,45 @@ protected: class DiskImage { public: DiskImage() : + _stream(nullptr), _tracks(0), _sectorsPerTrack(0), - _bytesPerSector(0) { - _f = new Common::File(); - } + _bytesPerSector(0), + _sectorLimit(0) { } - virtual ~DiskImage() { - delete _f; + ~DiskImage() { + delete _stream; } - virtual bool open(const Common::String &filename) = 0; - virtual const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const = 0; - virtual Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0) const = 0; + bool open(const Common::String &filename); + const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const; + Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0, uint sectorsUsed = 0) const; + void setSectorLimit(uint sectorLimit) { _sectorLimit = sectorLimit; } // Maximum number of sectors to read per track before stepping protected: class DataBlock : public Adl::DataBlock { public: - DataBlock(const DiskImage *disk, uint track, uint sector, uint offset, uint size) : + DataBlock(const DiskImage *disk, uint track, uint sector, uint offset, uint size, uint sectorLimit) : _track(track), _sector(sector), _offset(offset), _size(size), + _sectorLimit(sectorLimit), _disk(disk) { } Common::SeekableReadStream *createReadStream() const { - return _disk->createReadStream(_track, _sector, _offset, _size); + return _disk->createReadStream(_track, _sector, _offset, _size, _sectorLimit); } private: uint _track, _sector, _offset, _size; + uint _sectorLimit; const DiskImage *_disk; }; - Common::File *_f; + Common::SeekableReadStream *_stream; uint _tracks, _sectorsPerTrack, _bytesPerSector; + uint _sectorLimit; }; // Data in plain files @@ -117,30 +121,6 @@ public: Common::SeekableReadStream *createReadStream(const Common::String &filename, uint offset = 0) const; }; -// .DSK disk image - 35 tracks, 16 sectors per track, 256 bytes per sector -class DiskImage_DSK : public DiskImage { -public: - bool open(const Common::String &filename); - const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const; - Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0) const; -}; - -// .NIB disk image -class DiskImage_NIB : public DiskImage { -public: - DiskImage_NIB() : _memStream(nullptr) { } - virtual ~DiskImage_NIB() { - delete _memStream; - } - - bool open(const Common::String &filename); - const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const; - Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0) const; - -private: - Common::SeekableReadStream *_memStream; -}; - // Data in files contained in Apple DOS 3.3 disk image class Files_DOS33 : public Files { public: diff --git a/engines/adl/display.cpp b/engines/adl/display.cpp index 858d3ac20b..2cf50f72fc 100644 --- a/engines/adl/display.cpp +++ b/engines/adl/display.cpp @@ -55,6 +55,9 @@ static const byte colorPalette[COLOR_PALETTE_ENTRIES * 3] = { 0xf2, 0x5e, 0x00 }; +// Opacity of the optional scanlines (percentage) +#define SCANLINE_OPACITY 75 + // Corresponding color in second palette #define PAL2(X) ((X) | 0x04) @@ -133,6 +136,8 @@ Display::Display() : _textBufSurface->create(DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 2, Graphics::PixelFormat::createFormatCLUT8()); createFont(); + + _startMillis = g_system->getMillis(); } Display::~Display() { @@ -332,14 +337,16 @@ void Display::writeFrameBuffer(const Common::Point &p, byte color, byte mask) { } void Display::showScanlines(bool enable) { - byte pal[COLOR_PALETTE_ENTRIES * 3] = { }; + byte pal[COLOR_PALETTE_ENTRIES * 3]; - if (enable) - g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES); - else { - g_system->getPaletteManager()->grabPalette(pal, 0, COLOR_PALETTE_ENTRIES); - g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES); + g_system->getPaletteManager()->grabPalette(pal, 0, COLOR_PALETTE_ENTRIES); + + if (enable) { + for (uint i = 0; i < ARRAYSIZE(pal); ++i) + pal[i] = pal[i] * (100 - SCANLINE_OPACITY) / 100; } + + g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES); } static byte processColorBits(uint16 &bits, bool &odd, bool secondPal) { @@ -487,7 +494,11 @@ void Display::updateTextSurface() { r.translate(((c & 0x3f) % 16) * 7 * 2, (c & 0x3f) / 16 * 8 * 2); if (!(c & 0x80)) { - if (!(c & 0x40) || ((g_system->getMillis() / 270) & 1)) + // Blink text. We subtract _startMillis to make this compatible + // with the event recorder, which returns offsetted values on + // playback. + const uint32 millisPassed = g_system->getMillis() - _startMillis; + if (!(c & 0x40) || ((millisPassed / 270) & 1)) r.translate(0, 4 * 8 * 2); } diff --git a/engines/adl/display.h b/engines/adl/display.h index bc27b7cb6b..e761e63f2e 100644 --- a/engines/adl/display.h +++ b/engines/adl/display.h @@ -102,6 +102,7 @@ private: Graphics::Surface *_font; uint _cursorPos; bool _showCursor; + uint32 _startMillis; }; } // End of namespace Adl diff --git a/engines/adl/hires0.cpp b/engines/adl/hires0.cpp new file mode 100644 index 0000000000..9a0af05d20 --- /dev/null +++ b/engines/adl/hires0.cpp @@ -0,0 +1,143 @@ +/* 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/textconsole.h" + +#include "adl/adl_v2.h" +#include "adl/graphics.h" +#include "adl/disk.h" + +namespace Adl { + +#define IDS_HR0_DISK_IMAGE "MISSION.NIB" + +#define IDI_HR0_NUM_ROOMS 43 +#define IDI_HR0_NUM_MESSAGES 142 +#define IDI_HR0_NUM_VARS 40 +#define IDI_HR0_NUM_ITEM_PICS 2 +#define IDI_HR0_NUM_ITEM_OFFSETS 16 + +// Messages used outside of scripts +#define IDI_HR0_MSG_CANT_GO_THERE 110 +#define IDI_HR0_MSG_DONT_UNDERSTAND 112 +#define IDI_HR0_MSG_ITEM_DOESNT_MOVE 114 +#define IDI_HR0_MSG_ITEM_NOT_HERE 115 +#define IDI_HR0_MSG_THANKS_FOR_PLAYING 113 + +class HiRes0Engine : public AdlEngine_v2 { +public: + HiRes0Engine(OSystem *syst, const AdlGameDescription *gd) : + AdlEngine_v2(syst, gd) { } + ~HiRes0Engine() { } + +private: + // AdlEngine + void init(); + void initGameState(); +}; + +void HiRes0Engine::init() { + _graphics = new Graphics_v2(*_display); + + _disk = new DiskImage(); + if (!_disk->open(IDS_HR0_DISK_IMAGE)) + error("Failed to open disk image '" IDS_HR0_DISK_IMAGE "'"); + + _disk->setSectorLimit(13); + + // TODO: all these strings/offsets/etc are the same as hires2 + + StreamPtr stream(_disk->createReadStream(0x1f, 0x2, 0x00, 2)); + loadMessages(*stream, IDI_HR0_NUM_MESSAGES); + + // Read parser messages + stream.reset(_disk->createReadStream(0x1a, 0x1)); + _strings.verbError = readStringAt(*stream, 0x4f); + _strings.nounError = readStringAt(*stream, 0x8e); + _strings.enterCommand = readStringAt(*stream, 0xbc); + + // Read time string + stream.reset(_disk->createReadStream(0x19, 0x7, 0xd7)); + _strings_v2.time = readString(*stream, 0xff); + + // Read line feeds + stream.reset(_disk->createReadStream(0x19, 0xb, 0xf8, 1)); + _strings.lineFeeds = readString(*stream); + + // Read opcode strings + stream.reset(_disk->createReadStream(0x1a, 0x6, 0x00, 2)); + _strings_v2.saveInsert = readStringAt(*stream, 0x5f); + _strings_v2.saveReplace = readStringAt(*stream, 0xe5); + _strings_v2.restoreInsert = readStringAt(*stream, 0x132); + _strings_v2.restoreReplace = readStringAt(*stream, 0x1c2); + _strings.playAgain = readStringAt(*stream, 0x225); + _strings.pressReturn = readStringAt(*stream, 0x25f); + + _messageIds.cantGoThere = IDI_HR0_MSG_CANT_GO_THERE; + _messageIds.dontUnderstand = IDI_HR0_MSG_DONT_UNDERSTAND; + _messageIds.itemDoesntMove = IDI_HR0_MSG_ITEM_DOESNT_MOVE; + _messageIds.itemNotHere = IDI_HR0_MSG_ITEM_NOT_HERE; + _messageIds.thanksForPlaying = IDI_HR0_MSG_THANKS_FOR_PLAYING; + + // Load global picture data + stream.reset(_disk->createReadStream(0x19, 0xa, 0x80, 0)); + loadPictures(*stream); + + // Load item picture data + stream.reset(_disk->createReadStream(0x1e, 0x9, 0x05)); + loadItemPictures(*stream, IDI_HR0_NUM_ITEM_PICS); + + // Load commands from executable + stream.reset(_disk->createReadStream(0x1d, 0x7, 0x00, 2)); + readCommands(*stream, _roomCommands); + + stream.reset(_disk->createReadStream(0x1f, 0x7, 0x00, 3)); + readCommands(*stream, _globalCommands); + + // Load dropped item offsets + stream.reset(_disk->createReadStream(0x1b, 0x4, 0x15)); + loadDroppedItemOffsets(*stream, IDI_HR0_NUM_ITEM_OFFSETS); + + // Load verbs + stream.reset(_disk->createReadStream(0x19, 0x0, 0x00, 3)); + loadWords(*stream, _verbs, _priVerbs); + + // Load nouns + stream.reset(_disk->createReadStream(0x22, 0x2, 0x00, 2)); + loadWords(*stream, _nouns, _priNouns); +} + +void HiRes0Engine::initGameState() { + _state.vars.resize(IDI_HR0_NUM_VARS); + + StreamPtr stream(_disk->createReadStream(0x21, 0x5, 0x0e, 2)); + loadRooms(*stream, IDI_HR0_NUM_ROOMS); + + stream.reset(_disk->createReadStream(0x21, 0x0)); + loadItems(*stream); +} + +Engine *HiRes0Engine_create(OSystem *syst, const AdlGameDescription *gd) { + return new HiRes0Engine(syst, gd); +} + +} // End of namespace Adl diff --git a/engines/adl/hires1.cpp b/engines/adl/hires1.cpp index 096d8ef496..217a9013ba 100644 --- a/engines/adl/hires1.cpp +++ b/engines/adl/hires1.cpp @@ -27,11 +27,105 @@ #include "common/stream.h" #include "common/ptr.h" -#include "adl/hires1.h" +#include "adl/adl.h" +#include "adl/graphics.h" #include "adl/display.h" namespace Adl { +#define IDS_HR1_EXE_0 "AUTO LOAD OBJ" +#define IDS_HR1_EXE_1 "ADVENTURE" +#define IDS_HR1_LOADER "MYSTERY.HELLO" +#define IDS_HR1_MESSAGES "MESSAGES" + +#define IDI_HR1_NUM_ROOMS 41 +#define IDI_HR1_NUM_PICS 97 +#define IDI_HR1_NUM_VARS 20 +#define IDI_HR1_NUM_ITEM_OFFSETS 21 +#define IDI_HR1_NUM_MESSAGES 168 + +// Messages used outside of scripts +#define IDI_HR1_MSG_CANT_GO_THERE 137 +#define IDI_HR1_MSG_DONT_UNDERSTAND 37 +#define IDI_HR1_MSG_ITEM_DOESNT_MOVE 151 +#define IDI_HR1_MSG_ITEM_NOT_HERE 152 +#define IDI_HR1_MSG_THANKS_FOR_PLAYING 140 +#define IDI_HR1_MSG_DONT_HAVE_IT 127 +#define IDI_HR1_MSG_GETTING_DARK 7 + +#define IDI_HR1_OFS_STR_ENTER_COMMAND 0x5bbc +#define IDI_HR1_OFS_STR_VERB_ERROR 0x5b4f +#define IDI_HR1_OFS_STR_NOUN_ERROR 0x5b8e +#define IDI_HR1_OFS_STR_PLAY_AGAIN 0x5f1e +#define IDI_HR1_OFS_STR_CANT_GO_THERE 0x6c0a +#define IDI_HR1_OFS_STR_DONT_HAVE_IT 0x6c31 +#define IDI_HR1_OFS_STR_DONT_UNDERSTAND 0x6c51 +#define IDI_HR1_OFS_STR_GETTING_DARK 0x6c7c +#define IDI_HR1_OFS_STR_PRESS_RETURN 0x5f68 +#define IDI_HR1_OFS_STR_LINE_FEEDS 0x59d4 + +#define IDI_HR1_OFS_PD_TEXT_0 0x005d +#define IDI_HR1_OFS_PD_TEXT_1 0x012b +#define IDI_HR1_OFS_PD_TEXT_2 0x016d +#define IDI_HR1_OFS_PD_TEXT_3 0x0259 + +#define IDI_HR1_OFS_INTRO_TEXT 0x0066 +#define IDI_HR1_OFS_GAME_OR_HELP 0x000f + +#define IDI_HR1_OFS_LOGO_0 0x1003 +#define IDI_HR1_OFS_LOGO_1 0x1800 + +#define IDI_HR1_OFS_ITEMS 0x0100 +#define IDI_HR1_OFS_ROOMS 0x050a +#define IDI_HR1_OFS_PICS 0x4b03 +#define IDI_HR1_OFS_CMDS_0 0x3c00 +#define IDI_HR1_OFS_CMDS_1 0x3d00 +#define IDI_HR1_OFS_MSGS 0x4d00 + +#define IDI_HR1_OFS_ITEM_OFFSETS 0x68ff +#define IDI_HR1_OFS_CORNERS 0x4f00 + +#define IDI_HR1_OFS_VERBS 0x3800 +#define IDI_HR1_OFS_NOUNS 0x0f00 + +class HiRes1Engine : public AdlEngine { +public: + HiRes1Engine(OSystem *syst, const AdlGameDescription *gd) : + AdlEngine(syst, gd), + _files(nullptr), + _messageDelay(true) { } + ~HiRes1Engine() { delete _files; } + +private: + // AdlEngine + void runIntro() const; + void init(); + void initGameState(); + void restartGame(); + void printString(const Common::String &str); + Common::String loadMessage(uint idx) const; + void printMessage(uint idx); + void drawItems(); + void drawItem(Item &item, const Common::Point &pos); + void loadRoom(byte roomNr); + void showRoom(); + + void wordWrap(Common::String &str) const; + + Files *_files; + Common::File _exe; + Common::Array<DataBlockPtr> _corners; + Common::Array<byte> _roomDesc; + bool _messageDelay; + + struct { + Common::String cantGoThere; + Common::String dontHaveIt; + Common::String dontUnderstand; + Common::String gettingDark; + } _gameStrings; +}; + void HiRes1Engine::runIntro() const { StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0)); @@ -183,12 +277,7 @@ void HiRes1Engine::init() { // Load dropped item offsets stream->seek(IDI_HR1_OFS_ITEM_OFFSETS); - for (uint i = 0; i < IDI_HR1_NUM_ITEM_OFFSETS; ++i) { - Common::Point p; - p.x = stream->readByte(); - p.y = stream->readByte(); - _itemOffsets.push_back(p); - } + loadDroppedItemOffsets(*stream, IDI_HR1_NUM_ITEM_OFFSETS); // Load right-angle line art stream->seek(IDI_HR1_OFS_CORNERS); @@ -338,6 +427,7 @@ void HiRes1Engine::loadRoom(byte roomNr) { } void HiRes1Engine::showRoom() { + _state.curPicture = getCurRoom().curPicture; clearScreen(); loadRoom(_state.room); diff --git a/engines/adl/hires1.h b/engines/adl/hires1.h deleted file mode 100644 index c060bc892e..0000000000 --- a/engines/adl/hires1.h +++ /dev/null @@ -1,134 +0,0 @@ -/* 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. - * - */ - -#ifndef ADL_HIRES1_H -#define ADL_HIRES1_H - -#include "common/str.h" - -#include "adl/adl.h" -#include "adl/graphics.h" -#include "adl/disk.h" - -namespace Common { -class ReadStream; -struct Point; -} - -namespace Adl { - -#define IDS_HR1_EXE_0 "AUTO LOAD OBJ" -#define IDS_HR1_EXE_1 "ADVENTURE" -#define IDS_HR1_LOADER "MYSTERY.HELLO" -#define IDS_HR1_MESSAGES "MESSAGES" - -#define IDI_HR1_NUM_ROOMS 41 -#define IDI_HR1_NUM_PICS 97 -#define IDI_HR1_NUM_VARS 20 -#define IDI_HR1_NUM_ITEM_OFFSETS 21 -#define IDI_HR1_NUM_MESSAGES 168 - -// Messages used outside of scripts -#define IDI_HR1_MSG_CANT_GO_THERE 137 -#define IDI_HR1_MSG_DONT_UNDERSTAND 37 -#define IDI_HR1_MSG_ITEM_DOESNT_MOVE 151 -#define IDI_HR1_MSG_ITEM_NOT_HERE 152 -#define IDI_HR1_MSG_THANKS_FOR_PLAYING 140 -#define IDI_HR1_MSG_DONT_HAVE_IT 127 -#define IDI_HR1_MSG_GETTING_DARK 7 - -#define IDI_HR1_OFS_STR_ENTER_COMMAND 0x5bbc -#define IDI_HR1_OFS_STR_VERB_ERROR 0x5b4f -#define IDI_HR1_OFS_STR_NOUN_ERROR 0x5b8e -#define IDI_HR1_OFS_STR_PLAY_AGAIN 0x5f1e -#define IDI_HR1_OFS_STR_CANT_GO_THERE 0x6c0a -#define IDI_HR1_OFS_STR_DONT_HAVE_IT 0x6c31 -#define IDI_HR1_OFS_STR_DONT_UNDERSTAND 0x6c51 -#define IDI_HR1_OFS_STR_GETTING_DARK 0x6c7c -#define IDI_HR1_OFS_STR_PRESS_RETURN 0x5f68 -#define IDI_HR1_OFS_STR_LINE_FEEDS 0x59d4 - -#define IDI_HR1_OFS_PD_TEXT_0 0x005d -#define IDI_HR1_OFS_PD_TEXT_1 0x012b -#define IDI_HR1_OFS_PD_TEXT_2 0x016d -#define IDI_HR1_OFS_PD_TEXT_3 0x0259 - -#define IDI_HR1_OFS_INTRO_TEXT 0x0066 -#define IDI_HR1_OFS_GAME_OR_HELP 0x000f - -#define IDI_HR1_OFS_LOGO_0 0x1003 -#define IDI_HR1_OFS_LOGO_1 0x1800 - -#define IDI_HR1_OFS_ITEMS 0x0100 -#define IDI_HR1_OFS_ROOMS 0x050a -#define IDI_HR1_OFS_PICS 0x4b03 -#define IDI_HR1_OFS_CMDS_0 0x3c00 -#define IDI_HR1_OFS_CMDS_1 0x3d00 -#define IDI_HR1_OFS_MSGS 0x4d00 - -#define IDI_HR1_OFS_ITEM_OFFSETS 0x68ff -#define IDI_HR1_OFS_CORNERS 0x4f00 - -#define IDI_HR1_OFS_VERBS 0x3800 -#define IDI_HR1_OFS_NOUNS 0x0f00 - -class HiRes1Engine : public AdlEngine { -public: - HiRes1Engine(OSystem *syst, const AdlGameDescription *gd) : - AdlEngine(syst, gd), - _files(nullptr), - _messageDelay(true) { } - ~HiRes1Engine() { delete _files; } - -private: - // AdlEngine - void runIntro() const; - void init(); - void initGameState(); - void restartGame(); - void printString(const Common::String &str); - Common::String loadMessage(uint idx) const; - void printMessage(uint idx); - void drawItems(); - void drawItem(Item &item, const Common::Point &pos); - void loadRoom(byte roomNr); - void showRoom(); - - void wordWrap(Common::String &str) const; - - Files *_files; - Common::File _exe; - Common::Array<DataBlockPtr> _corners; - Common::Array<byte> _roomDesc; - bool _messageDelay; - - struct { - Common::String cantGoThere; - Common::String dontHaveIt; - Common::String dontUnderstand; - Common::String gettingDark; - } _gameStrings; -}; - -} // End of namespace Adl - -#endif diff --git a/engines/adl/hires2.cpp b/engines/adl/hires2.cpp index d8e8a65e29..199f457b4f 100644 --- a/engines/adl/hires2.cpp +++ b/engines/adl/hires2.cpp @@ -26,14 +26,44 @@ #include "common/file.h" #include "common/stream.h" -#include "adl/hires2.h" +#include "adl/adl_v2.h" #include "adl/display.h" #include "adl/graphics.h" #include "adl/disk.h" namespace Adl { +#define IDS_HR2_DISK_IMAGE "WIZARD.DSK" + +#define IDI_HR2_NUM_ROOMS 135 +#define IDI_HR2_NUM_MESSAGES 255 +#define IDI_HR2_NUM_VARS 40 +#define IDI_HR2_NUM_ITEM_PICS 38 +#define IDI_HR2_NUM_ITEM_OFFSETS 16 + +// Messages used outside of scripts +#define IDI_HR2_MSG_CANT_GO_THERE 123 +#define IDI_HR2_MSG_DONT_UNDERSTAND 19 +#define IDI_HR2_MSG_ITEM_DOESNT_MOVE 242 +#define IDI_HR2_MSG_ITEM_NOT_HERE 4 +#define IDI_HR2_MSG_THANKS_FOR_PLAYING 239 + +class HiRes2Engine : public AdlEngine_v2 { +public: + HiRes2Engine(OSystem *syst, const AdlGameDescription *gd) : AdlEngine_v2(syst, gd) { } + +private: + // AdlEngine + void runIntro() const; + void init(); + void initGameState(); +}; + void HiRes2Engine::runIntro() const { + // This only works for the 16-sector re-release. The original + // release is not supported at this time, because we don't have + // access to it. + _disk->setSectorLimit(0); StreamPtr stream(_disk->createReadStream(0x00, 0xd, 0x17, 1)); _display->setMode(DISPLAY_MODE_TEXT); @@ -45,19 +75,21 @@ void HiRes2Engine::runIntro() const { _display->printString(str); delay(2000); + + _disk->setSectorLimit(13); } void HiRes2Engine::init() { _graphics = new Graphics_v2(*_display); - _disk = new DiskImage_DSK(); + _disk = new DiskImage(); if (!_disk->open(IDS_HR2_DISK_IMAGE)) error("Failed to open disk image '" IDS_HR2_DISK_IMAGE "'"); - StreamPtr stream(_disk->createReadStream(0x1f, 0x2, 0x00, 4)); + _disk->setSectorLimit(13); - for (uint i = 0; i < IDI_HR2_NUM_MESSAGES; ++i) - _messages.push_back(readDataBlockPtr(*stream)); + StreamPtr stream(_disk->createReadStream(0x1f, 0x2, 0x00, 4)); + loadMessages(*stream, IDI_HR2_NUM_MESSAGES); // Read parser messages stream.reset(_disk->createReadStream(0x1a, 0x1)); @@ -90,20 +122,11 @@ void HiRes2Engine::init() { // Load global picture data stream.reset(_disk->createReadStream(0x19, 0xa, 0x80, 0)); - byte picNr; - while ((picNr = stream->readByte()) != 0xff) { - if (stream->eos() || stream->err()) - error("Error reading global pic list"); - - _pictures[picNr] = readDataBlockPtr(*stream); - } + loadPictures(*stream); // Load item picture data stream.reset(_disk->createReadStream(0x1e, 0x9, 0x05)); - for (uint i = 0; i < IDI_HR2_NUM_ITEM_PICS; ++i) { - stream->readByte(); // number - _itemPics.push_back(readDataBlockPtr(*stream)); - } + loadItemPictures(*stream, IDI_HR2_NUM_ITEM_PICS); // Load commands from executable stream.reset(_disk->createReadStream(0x1d, 0x7, 0x00, 4)); @@ -114,12 +137,7 @@ void HiRes2Engine::init() { // Load dropped item offsets stream.reset(_disk->createReadStream(0x1b, 0x4, 0x15)); - for (uint i = 0; i < IDI_HR2_NUM_ITEM_OFFSETS; ++i) { - Common::Point p; - p.x = stream->readByte(); - p.y = stream->readByte(); - _itemOffsets.push_back(p); - } + loadDroppedItemOffsets(*stream, IDI_HR2_NUM_ITEM_OFFSETS); // Load verbs stream.reset(_disk->createReadStream(0x19, 0x0, 0x00, 3)); @@ -134,46 +152,10 @@ void HiRes2Engine::initGameState() { _state.vars.resize(IDI_HR2_NUM_VARS); StreamPtr stream(_disk->createReadStream(0x21, 0x5, 0x0e, 7)); - - for (uint i = 0; i < IDI_HR2_NUM_ROOMS; ++i) { - Room room; - stream->readByte(); // number - for (uint j = 0; j < 6; ++j) - room.connections[j] = stream->readByte(); - room.data = readDataBlockPtr(*stream); - room.picture = stream->readByte(); - room.curPicture = stream->readByte(); - room.isFirstTime = stream->readByte(); - _state.rooms.push_back(room); - } + loadRooms(*stream, IDI_HR2_NUM_ROOMS); stream.reset(_disk->createReadStream(0x21, 0x0, 0x00, 2)); - - byte id; - while ((id = stream->readByte()) != 0xff) { - Item item = Item(); - item.id = id; - item.noun = stream->readByte(); - item.room = stream->readByte(); - item.picture = stream->readByte(); - item.isLineArt = stream->readByte(); // Is this still used in this way? - item.position.x = stream->readByte(); - item.position.y = stream->readByte(); - item.state = stream->readByte(); - item.description = stream->readByte(); - - stream->readByte(); // Struct size - - byte picListSize = stream->readByte(); - - // Flag to keep track of what has been drawn on the screen - stream->readByte(); - - for (uint i = 0; i < picListSize; ++i) - item.roomPictures.push_back(stream->readByte()); - - _state.items.push_back(item); - } + loadItems(*stream); } Engine *HiRes2Engine_create(OSystem *syst, const AdlGameDescription *gd) { diff --git a/engines/adl/hires4.cpp b/engines/adl/hires4.cpp new file mode 100644 index 0000000000..ddfc868e9a --- /dev/null +++ b/engines/adl/hires4.cpp @@ -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 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/system.h" +#include "common/debug.h" +#include "common/error.h" +#include "common/file.h" +#include "common/stream.h" + +#include "adl/adl_v3.h" +#include "adl/detection.h" +#include "adl/display.h" +#include "adl/graphics.h" +#include "adl/disk.h" + +namespace Adl { + +#define IDI_HR4_NUM_ROOMS 164 +#define IDI_HR4_NUM_MESSAGES 255 +#define IDI_HR4_NUM_VARS 40 +#define IDI_HR4_NUM_ITEM_DESCS 44 +#define IDI_HR4_NUM_ITEM_PICS 41 +#define IDI_HR4_NUM_ITEM_OFFSETS 40 + +// Messages used outside of scripts +#define IDI_HR4_MSG_CANT_GO_THERE 110 +#define IDI_HR4_MSG_DONT_UNDERSTAND 112 +#define IDI_HR4_MSG_ITEM_DOESNT_MOVE 114 +#define IDI_HR4_MSG_ITEM_NOT_HERE 115 +#define IDI_HR4_MSG_THANKS_FOR_PLAYING 113 + +class HiRes4Engine_Atari : public AdlEngine_v3 { +public: + HiRes4Engine_Atari(OSystem *syst, const AdlGameDescription *gd) : + AdlEngine_v3(syst, gd), + _boot(nullptr), + _curDisk(0) { } + ~HiRes4Engine_Atari(); + +private: + // AdlEngine + void init(); + void initGameState(); + void loadRoom(byte roomNr); + Common::String formatVerbError(const Common::String &verb) const; + Common::String formatNounError(const Common::String &verb, const Common::String &noun) const; + + // AdlEngine_v2 + void adjustDataBlockPtr(byte &track, byte §or, byte &offset, byte &size) const; + + Common::SeekableReadStream *createReadStream(DiskImage *disk, byte track, byte sector, byte offset = 0, byte size = 0) const; + void loadCommonData(); + void insertDisk(byte diskNr); + void rebindDisk(); + + DiskImage *_boot; + byte _curDisk; +}; + +static const char *const atariDisks[] = { "ULYS1A.XFD", "ULYS1B.XFD", "ULYS2C.XFD" }; + +HiRes4Engine_Atari::~HiRes4Engine_Atari() { + delete _boot; +} + +void HiRes4Engine_Atari::init() { + _graphics = new Graphics_v2(*_display); + + _boot = new DiskImage(); + if (!_boot->open(atariDisks[0])) + error("Failed to open disk image '%s'", atariDisks[0]); + + insertDisk(1); + loadCommonData(); + + StreamPtr stream(createReadStream(_boot, 0x06, 0x2)); + _strings.verbError = readStringAt(*stream, 0x4f); + _strings.nounError = readStringAt(*stream, 0x83); + _strings.enterCommand = readStringAt(*stream, 0xa6); + + stream.reset(createReadStream(_boot, 0x05, 0xb, 0xd7)); + _strings_v2.time = readString(*stream, 0xff); + + stream.reset(createReadStream(_boot, 0x06, 0x7, 0x00, 2)); + _strings_v2.saveInsert = readStringAt(*stream, 0x62); + _strings_v2.saveReplace = readStringAt(*stream, 0xdd); + _strings_v2.restoreInsert = readStringAt(*stream, 0x12a); + _strings_v2.restoreReplace = readStringAt(*stream, 0x1b8); + _strings.playAgain = readStringAt(*stream, 0x21b); + // TODO: restart sequence has "insert side a/b" strings + + _messageIds.cantGoThere = IDI_HR4_MSG_CANT_GO_THERE; + _messageIds.dontUnderstand = IDI_HR4_MSG_DONT_UNDERSTAND; + _messageIds.itemDoesntMove = IDI_HR4_MSG_ITEM_DOESNT_MOVE; + _messageIds.itemNotHere = IDI_HR4_MSG_ITEM_NOT_HERE; + _messageIds.thanksForPlaying = IDI_HR4_MSG_THANKS_FOR_PLAYING; + + stream.reset(createReadStream(_boot, 0x06, 0xd, 0x12, 2)); + loadItemDescriptions(*stream, IDI_HR4_NUM_ITEM_DESCS); + + stream.reset(createReadStream(_boot, 0x07, 0x1, 0xf4)); + loadDroppedItemOffsets(*stream, IDI_HR4_NUM_ITEM_OFFSETS); + + stream.reset(createReadStream(_boot, 0x08, 0xe, 0xa5, 5)); + readCommands(*stream, _roomCommands); + + stream.reset(createReadStream(_boot, 0x0a, 0x9, 0x00, 3)); + readCommands(*stream, _globalCommands); + + stream.reset(createReadStream(_boot, 0x05, 0x4, 0x00, 3)); + loadWords(*stream, _verbs, _priVerbs); + + stream.reset(createReadStream(_boot, 0x03, 0xb, 0x00, 6)); + loadWords(*stream, _nouns, _priNouns); +} + +void HiRes4Engine_Atari::loadRoom(byte roomNr) { + if (roomNr >= 59 && roomNr < 113) { + if (_curDisk != 2) { + insertDisk(2); + rebindDisk(); + } + } else if (_curDisk != 1) { + insertDisk(1); + rebindDisk(); + } + + if (roomNr == 121) { + // Room 121 is not present in the Atari version. This causes + // problems when we're dumping scripts with the debugger, so + // we intercept this room load here. + // FIXME: Find out if the Apple II version does have this room + // FIXME: Implement more generic handling of invalid rooms? + debug("Warning: attempt to load non-existent room 121"); + _roomData.description.clear(); + _roomData.pictures.clear(); + _roomData.commands.clear(); + return; + } + + AdlEngine_v3::loadRoom(roomNr); +} + +Common::String HiRes4Engine_Atari::formatVerbError(const Common::String &verb) const { + Common::String err = _strings.verbError; + for (uint i = 0; i < verb.size(); ++i) + err.setChar(verb[i], i + 8); + return err; +} + +Common::String HiRes4Engine_Atari::formatNounError(const Common::String &verb, const Common::String &noun) const { + Common::String err = _strings.nounError; + for (uint i = 0; i < verb.size(); ++i) + err.setChar(verb[i], i + 8); + for (uint i = 0; i < noun.size(); ++i) + err.setChar(noun[i], i + 19); + return err; +} + +void HiRes4Engine_Atari::insertDisk(byte diskNr) { + if (_curDisk == diskNr) + return; + + _curDisk = diskNr; + + delete _disk; + _disk = new DiskImage(); + if (!_disk->open(atariDisks[diskNr])) + error("Failed to open disk image '%s'", atariDisks[diskNr]); +} + +void HiRes4Engine_Atari::rebindDisk() { + // As room.data is bound to the DiskImage, we need to rebind them here + // We cannot simply reload the rooms as that would reset their state + + // FIXME: Remove DataBlockPtr-DiskImage coupling? + + StreamPtr stream(createReadStream(_boot, 0x03, 0x1, 0x0e, 9)); + for (uint i = 0; i < IDI_HR4_NUM_ROOMS; ++i) { + stream->skip(7); + _state.rooms[i].data = readDataBlockPtr(*stream); + stream->skip(3); + } + + // Rebind data that is on both side B and C + loadCommonData(); +} + +void HiRes4Engine_Atari::loadCommonData() { + _messages.clear(); + StreamPtr stream(createReadStream(_boot, 0x0a, 0x4, 0x00, 3)); + loadMessages(*stream, IDI_HR4_NUM_MESSAGES); + + _pictures.clear(); + stream.reset(createReadStream(_boot, 0x05, 0xe, 0x80)); + loadPictures(*stream); + + _itemPics.clear(); + stream.reset(createReadStream(_boot, 0x09, 0xe, 0x05)); + loadItemPictures(*stream, IDI_HR4_NUM_ITEM_PICS); +} + +void HiRes4Engine_Atari::initGameState() { + _state.vars.resize(IDI_HR4_NUM_VARS); + + StreamPtr stream(createReadStream(_boot, 0x03, 0x1, 0x0e, 9)); + loadRooms(*stream, IDI_HR4_NUM_ROOMS); + + stream.reset(createReadStream(_boot, 0x02, 0xc, 0x00, 12)); + loadItems(*stream); + + // FIXME + _display->moveCursorTo(Common::Point(0, 23)); +} + +Common::SeekableReadStream *HiRes4Engine_Atari::createReadStream(DiskImage *disk, byte track, byte sector, byte offset, byte size) const { + adjustDataBlockPtr(track, sector, offset, size); + return disk->createReadStream(track, sector, offset, size); +} + +void HiRes4Engine_Atari::adjustDataBlockPtr(byte &track, byte §or, byte &offset, byte &size) const { + // Convert the Apple II disk offsets in the game, to Atari disk offsets + uint sectorIndex = (track * 16 + sector + 1) << 1; + + // Atari uses 128 bytes per sector vs. 256 on the Apple II + // Note that size indicates *additional* sectors to read after reading one sector + size *= 2; + + if (offset >= 128) { + // Offset in the second half of an Apple II sector, skip one sector and adjust offset + ++sectorIndex; + offset -= 128; + } else { + // Offset in the first half of an Apple II sector, we need to read one additional sector + ++size; + } + + // Compute track/sector for Atari's 18 sectors per track (sectorIndex is 1-based) + track = (sectorIndex - 1) / 18; + sector = (sectorIndex - 1) % 18; +} + +Engine *HiRes4Engine_create(OSystem *syst, const AdlGameDescription *gd) { + switch (gd->desc.platform) { + case Common::kPlatformAtariST: + return new HiRes4Engine_Atari(syst, gd); + default: + error("Unsupported platform"); + } +} + +} // End of namespace Adl diff --git a/engines/adl/hires6.cpp b/engines/adl/hires6.cpp index c42b4165a6..a1fea05f19 100644 --- a/engines/adl/hires6.cpp +++ b/engines/adl/hires6.cpp @@ -27,13 +27,65 @@ #include "common/stream.h" #include "common/memstream.h" -#include "adl/hires6.h" +#include "adl/adl_v4.h" #include "adl/display.h" #include "adl/graphics.h" #include "adl/disk.h" namespace Adl { +#define IDI_HR6_NUM_ROOMS 35 +#define IDI_HR6_NUM_MESSAGES 256 +#define IDI_HR6_NUM_VARS 40 +#define IDI_HR6_NUM_ITEM_DESCS 15 +#define IDI_HR6_NUM_ITEM_PICS 15 +#define IDI_HR6_NUM_ITEM_OFFSETS 16 + +// Messages used outside of scripts +#define IDI_HR6_MSG_CANT_GO_THERE 249 +#define IDI_HR6_MSG_DONT_UNDERSTAND 247 +#define IDI_HR6_MSG_ITEM_DOESNT_MOVE 253 +#define IDI_HR6_MSG_ITEM_NOT_HERE 254 +#define IDI_HR6_MSG_THANKS_FOR_PLAYING 252 + +struct DiskDataDesc { + byte track; + byte sector; + byte offset; + byte volume; +}; + +class HiRes6Engine : public AdlEngine_v4 { +public: + HiRes6Engine(OSystem *syst, const AdlGameDescription *gd) : + AdlEngine_v4(syst, gd), + _boot(nullptr), + _currVerb(0), + _currNoun(0) { + } + + ~HiRes6Engine() { delete _boot; } + +private: + // AdlEngine + void runIntro() const; + void init(); + void initGameState(); + void printRoomDescription(); + void showRoom(); + Common::String formatVerbError(const Common::String &verb) const; + Common::String formatNounError(const Common::String &verb, const Common::String &noun) const; + + // AdlEngine_v2 + void printString(const Common::String &str); + + void loadDisk(byte disk); + + DiskImage *_boot; + byte _currVerb, _currNoun; + Common::Array<DiskDataDesc> _diskDataDesc; +}; + static const char *disks[] = { "DARK1A.DSK", "DARK1B.NIB", "DARK2A.NIB", "DARK2B.NIB" }; #define SECTORS_PER_TRACK 16 @@ -69,7 +121,7 @@ static Common::MemoryReadStream *loadSectors(DiskImage *disk, byte track, byte s } void HiRes6Engine::runIntro() const { - DiskImage_DSK *boot(new DiskImage_DSK()); + DiskImage *boot(new DiskImage()); if (!boot->open(disks[0])) error("Failed to open disk image '%s'", disks[0]); @@ -109,7 +161,7 @@ void HiRes6Engine::runIntro() const { } void HiRes6Engine::init() { - _boot = new DiskImage_DSK(); + _boot = new DiskImage(); _graphics = new Graphics_v2(*_display); if (!_boot->open(disks[0])) @@ -141,18 +193,12 @@ void HiRes6Engine::init() { // Item descriptions stream.reset(loadSectors(_boot, 0x6, 0xb, 2)); - stream->seek(0x34); - for (uint i = 0; i < IDI_HR6_NUM_ITEM_DESCS; ++i) - _itemDesc.push_back(readString(*stream, 0xff)); + stream->seek(0x16); + loadItemDescriptions(*stream, IDI_HR6_NUM_ITEM_DESCS); // Load dropped item offsets stream.reset(_boot->createReadStream(0x8, 0x9, 0x16)); - for (uint i = 0; i < IDI_HR6_NUM_ITEM_OFFSETS; ++i) { - Common::Point p; - p.x = stream->readByte(); - p.y = stream->readByte(); - _itemOffsets.push_back(p); - } + loadDroppedItemOffsets(*stream, IDI_HR6_NUM_ITEM_OFFSETS); // Location of game data for each disc stream.reset(_boot->createReadStream(0x5, 0xa, 0x03)); @@ -177,7 +223,7 @@ void HiRes6Engine::init() { void HiRes6Engine::loadDisk(byte disk) { delete _disk; - _disk = new DiskImage_NIB(); + _disk = new DiskImage(); if (!_disk->open(disks[disk])) error("Failed to open disk image '%s'", disks[disk]); @@ -187,10 +233,7 @@ void HiRes6Engine::loadDisk(byte disk) { // Load item picture data (indexed on boot disk) StreamPtr stream(_boot->createReadStream(0xb, 0xd, 0x08)); _itemPics.clear(); - for (uint i = 0; i < IDI_HR6_NUM_ITEM_PICS; ++i) { - stream->readByte(); - _itemPics.push_back(readDataBlockPtr(*stream)); - } + loadItemPictures(*stream, IDI_HR6_NUM_ITEM_PICS); _curDisk = disk; @@ -214,19 +257,13 @@ void HiRes6Engine::loadDisk(byte disk) { // Messages _messages.clear(); uint count = size / 4; - for (uint i = 0; i < count; ++i) - _messages.push_back(readDataBlockPtr(*stream)); + loadMessages(*stream, count); break; } case 0x4a80: { // Global pics _pictures.clear(); - byte picNr; - while ((picNr = stream->readByte()) != 0xff) { - if (stream->eos() || stream->err()) - error("Error reading global pic list"); - _pictures[picNr] = readDataBlockPtr(*stream); - } + loadPictures(*stream); break; } case 0x4000: @@ -243,17 +280,7 @@ void HiRes6Engine::loadDisk(byte disk) { stream->skip(14); // Skip invalid room 0 _state.rooms.clear(); - for (uint i = 0; i < count; ++i) { - Room room; - stream->readByte(); // number - for (uint j = 0; j < 6; ++j) - room.connections[j] = stream->readByte(); - room.data = readDataBlockPtr(*stream); - room.picture = stream->readByte(); - room.curPicture = stream->readByte(); - room.isFirstTime = stream->readByte(); - _state.rooms.push_back(room); - } + loadRooms(*stream, count); break; } case 0x7b00: @@ -287,36 +314,14 @@ void HiRes6Engine::initGameState() { StreamPtr stream(_boot->createReadStream(0x3, 0xe, 0x03)); - byte id; - while ((id = stream->readByte()) != 0xff) { - Item item = Item(); - item.id = id; - item.noun = stream->readByte(); - item.room = stream->readByte(); - item.picture = stream->readByte(); - item.isLineArt = stream->readByte(); // Now seems to be disk number - item.position.x = stream->readByte(); - item.position.y = stream->readByte(); - item.state = stream->readByte(); - item.description = stream->readByte(); - - stream->readByte(); // Struct size - - byte picListSize = stream->readByte(); - - // Flag to keep track of what has been drawn on the screen - stream->readByte(); - - for (uint i = 0; i < picListSize; ++i) - item.roomPictures.push_back(stream->readByte()); - - _state.items.push_back(item); - } + loadItems(*stream); _currVerb = _currNoun = 0; } void HiRes6Engine::showRoom() { + _state.curPicture = getCurRoom().curPicture; + bool redrawPic = false; if (getVar(26) == 0xfe) diff --git a/engines/adl/hires6.h b/engines/adl/hires6.h deleted file mode 100644 index 4bd2bcc7cc..0000000000 --- a/engines/adl/hires6.h +++ /dev/null @@ -1,92 +0,0 @@ -/* 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. - * - */ - -#ifndef ADL_HIRES6_H -#define ADL_HIRES6_H - -#include "common/str.h" - -#include "adl/adl_v3.h" -#include "adl/disk.h" - -namespace Common { -class ReadStream; -struct Point; -} - -namespace Adl { - -#define IDI_HR6_NUM_ROOMS 35 -#define IDI_HR6_NUM_MESSAGES 256 -#define IDI_HR6_NUM_VARS 40 -#define IDI_HR6_NUM_ITEM_DESCS 15 -#define IDI_HR6_NUM_ITEM_PICS 15 -#define IDI_HR6_NUM_ITEM_OFFSETS 16 - -// Messages used outside of scripts -#define IDI_HR6_MSG_CANT_GO_THERE 249 -#define IDI_HR6_MSG_DONT_UNDERSTAND 247 -#define IDI_HR6_MSG_ITEM_DOESNT_MOVE 253 -#define IDI_HR6_MSG_ITEM_NOT_HERE 254 -#define IDI_HR6_MSG_THANKS_FOR_PLAYING 252 - -struct DiskDataDesc { - byte track; - byte sector; - byte offset; - byte volume; -}; - -class HiRes6Engine : public AdlEngine_v3 { -public: - HiRes6Engine(OSystem *syst, const AdlGameDescription *gd) : - AdlEngine_v3(syst, gd), - _boot(nullptr), - _currVerb(0), - _currNoun(0) { - } - - ~HiRes6Engine() { delete _boot; } - -private: - // AdlEngine - void runIntro() const; - void init(); - void initGameState(); - void printRoomDescription(); - void showRoom(); - Common::String formatVerbError(const Common::String &verb) const; - Common::String formatNounError(const Common::String &verb, const Common::String &noun) const; - - // AdlEngine_v2 - void printString(const Common::String &str); - - void loadDisk(byte disk); - - DiskImage_DSK *_boot; - byte _currVerb, _currNoun; - Common::Array<DiskDataDesc> _diskDataDesc; -}; - -} // End of namespace Adl - -#endif diff --git a/engines/adl/module.mk b/engines/adl/module.mk index 7ab37efc67..d1de2a6c02 100644 --- a/engines/adl/module.mk +++ b/engines/adl/module.mk @@ -4,6 +4,7 @@ MODULE_OBJS := \ adl.o \ adl_v2.o \ adl_v3.o \ + adl_v4.o \ console.o \ detection.o \ disk.o \ @@ -11,8 +12,10 @@ MODULE_OBJS := \ graphics.o \ graphics_v1.o \ graphics_v2.o \ + hires0.o \ hires1.o \ hires2.o \ + hires4.o \ hires6.o \ speaker.o diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp index 9f66d78d80..5cb239f8d8 100644 --- a/engines/agi/detection.cpp +++ b/engines/agi/detection.cpp @@ -231,7 +231,8 @@ bool AgiMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || (f == kSavesSupportCreationDate) || - (f == kSavesSupportPlayTime); + (f == kSavesSupportPlayTime) || + (f == kSimpleSavesNames); } bool AgiBase::hasFeature(EngineFeature f) const { diff --git a/engines/agos/detection.cpp b/engines/agos/detection.cpp index 2c89522089..dbc4ee9145 100644 --- a/engines/agos/detection.cpp +++ b/engines/agos/detection.cpp @@ -125,7 +125,8 @@ public: bool AgosMetaEngine::hasFeature(MetaEngineFeature f) const { return - (f == kSupportsListSaves); + (f == kSupportsListSaves) || + (f == kSimpleSavesNames); } bool AGOS::AGOSEngine::hasFeature(EngineFeature f) const { diff --git a/engines/avalanche/detection.cpp b/engines/avalanche/detection.cpp index e35c5d2cac..def395b77f 100644 --- a/engines/avalanche/detection.cpp +++ b/engines/avalanche/detection.cpp @@ -99,7 +99,8 @@ bool AvalancheMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsDeleteSave) || (f == kSupportsLoadingDuringStartup) || (f == kSavesSupportMetaInfo) || - (f == kSavesSupportThumbnail); + (f == kSavesSupportThumbnail) || + (f == kSimpleSavesNames); } SaveStateList AvalancheMetaEngine::listSaves(const char *target) const { diff --git a/engines/bbvs/detection.cpp b/engines/bbvs/detection.cpp index 7c0045ee73..fa735c9ec3 100644 --- a/engines/bbvs/detection.cpp +++ b/engines/bbvs/detection.cpp @@ -97,7 +97,8 @@ bool BbvsMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsLoadingDuringStartup) || (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || - (f == kSavesSupportCreationDate); + (f == kSavesSupportCreationDate) || + (f == kSimpleSavesNames); } void BbvsMetaEngine::removeSaveState(const char *target, int slot) const { diff --git a/engines/cge/detection.cpp b/engines/cge/detection.cpp index 82d27f8d54..0df1e8711e 100644 --- a/engines/cge/detection.cpp +++ b/engines/cge/detection.cpp @@ -180,7 +180,8 @@ bool CGEMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsDeleteSave) || (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || - (f == kSavesSupportCreationDate); + (f == kSavesSupportCreationDate) || + (f == kSimpleSavesNames); } void CGEMetaEngine::removeSaveState(const char *target, int slot) const { diff --git a/engines/cge2/detection.cpp b/engines/cge2/detection.cpp index 2b84d167c7..3701baa40f 100644 --- a/engines/cge2/detection.cpp +++ b/engines/cge2/detection.cpp @@ -185,7 +185,8 @@ bool CGE2MetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSavesSupportThumbnail) || (f == kSavesSupportCreationDate) || (f == kSupportsListSaves) || - (f == kSupportsLoadingDuringStartup); + (f == kSupportsLoadingDuringStartup) || + (f == kSimpleSavesNames); } int CGE2MetaEngine::getMaximumSaveSlot() const { diff --git a/engines/director/archive.cpp b/engines/director/archive.cpp new file mode 100644 index 0000000000..5b22b45734 --- /dev/null +++ b/engines/director/archive.cpp @@ -0,0 +1,248 @@ +/* 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/macresman.h" + +#include "director/director.h" +#include "director/resource.h" +#include "director/lingo/lingo.h" + +namespace Director { + +Archive *DirectorEngine::createArchive() { + if (getPlatform() == Common::kPlatformMacintosh) { + if (getVersion() < 4) + return new MacArchive(); + else + return new RIFXArchive(); + } else { + return new RIFFArchive(); + } +} + +void DirectorEngine::loadMainArchive() { + if (getPlatform() == Common::kPlatformWindows) + loadEXE(); + else + loadMac(); +} + +void DirectorEngine::cleanupMainArchive() { + delete _mainArchive; + delete _macBinary; +} + +void DirectorEngine::loadEXE() { + Common::SeekableReadStream *exeStream = SearchMan.createReadStreamForMember(getEXEName()); + if (!exeStream) + error("Failed to open EXE '%s'", getEXEName().c_str()); + + _lingo->processEvent(kEventStart, 0); + + exeStream->seek(-4, SEEK_END); + exeStream->seek(exeStream->readUint32LE()); + + switch (getVersion()) { + case 3: + loadEXEv3(exeStream); + break; + case 4: + loadEXEv4(exeStream); + break; + case 5: + loadEXEv5(exeStream); + break; + case 7: + loadEXEv7(exeStream); + break; + default: + error("Unhandled Windows EXE version %d", getVersion()); + } +} + +void DirectorEngine::loadEXEv3(Common::SeekableReadStream *stream) { + uint16 entryCount = stream->readUint16LE(); + if (entryCount != 1) + error("Unhandled multiple entry v3 EXE"); + + stream->skip(5); // unknown + + stream->readUint32LE(); // Main MMM size + Common::String mmmFileName = readPascalString(*stream); + Common::String directoryName = readPascalString(*stream); + + debugC(1, kDebugLoading, "Main MMM: '%s'", mmmFileName.c_str()); + debugC(1, kDebugLoading, "Directory Name: '%s'", directoryName.c_str()); + + _mainArchive = new RIFFArchive(); + + if (!_mainArchive->openFile(mmmFileName)) + error("Could not open '%s'", mmmFileName.c_str()); + + delete stream; +} + +void DirectorEngine::loadEXEv4(Common::SeekableReadStream *stream) { + if (stream->readUint32BE() != MKTAG('P', 'J', '9', '3')) + error("Invalid projector tag found in v4 EXE"); + + uint32 rifxOffset = stream->readUint32LE(); + /* uint32 fontMapOffset = */ stream->readUint32LE(); + /* uint32 resourceForkOffset1 = */ stream->readUint32LE(); + /* uint32 resourceForkOffset2 = */ stream->readUint32LE(); + stream->readUint32LE(); // graphics DLL offset + stream->readUint32LE(); // sound DLL offset + /* uint32 rifxOffsetAlt = */ stream->readUint32LE(); // equivalent to rifxOffset + + loadEXERIFX(stream, rifxOffset); +} + +void DirectorEngine::loadEXEv5(Common::SeekableReadStream *stream) { + if (stream->readUint32LE() != MKTAG('P', 'J', '9', '5')) + error("Invalid projector tag found in v5 EXE"); + + uint32 rifxOffset = stream->readUint32LE(); + stream->readUint32LE(); // unknown + stream->readUint32LE(); // unknown + stream->readUint32LE(); // unknown + /* uint16 screenWidth = */ stream->readUint16LE(); + /* uint16 screenHeight = */ stream->readUint16LE(); + stream->readUint32LE(); // unknown + stream->readUint32LE(); // unknown + /* uint32 fontMapOffset = */ stream->readUint32LE(); + + loadEXERIFX(stream, rifxOffset); +} + +void DirectorEngine::loadEXEv7(Common::SeekableReadStream *stream) { + if (stream->readUint32LE() != MKTAG('P', 'J', '0', '0')) + error("Invalid projector tag found in v7 EXE"); + + uint32 rifxOffset = stream->readUint32LE(); + stream->readUint32LE(); // unknown + stream->readUint32LE(); // unknown + stream->readUint32LE(); // unknown + stream->readUint32LE(); // unknown + stream->readUint32LE(); // some DLL offset + + loadEXERIFX(stream, rifxOffset); +} + +void DirectorEngine::loadEXERIFX(Common::SeekableReadStream *stream, uint32 offset) { + _mainArchive = new RIFXArchive(); + + if (!_mainArchive->openStream(stream, offset)) + error("Failed to load RIFX from EXE"); +} + +void DirectorEngine::loadMac() { + if (getVersion() < 4) { + // The data is part of the resource fork of the executable + _mainArchive = new MacArchive(); + + if (!_mainArchive->openFile(getEXEName())) + error("Failed to open Mac binary '%s'", getEXEName().c_str()); + } else { + // The RIFX is located in the data fork of the executable + _macBinary = new Common::MacResManager(); + + if (!_macBinary->open(getEXEName()) || !_macBinary->hasDataFork()) + error("Failed to open Mac binary '%s'", getEXEName().c_str()); + + Common::SeekableReadStream *dataFork = _macBinary->getDataFork(); + _mainArchive = new RIFXArchive(); + + // First we need to detect PPC vs. 68k + + uint32 tag = dataFork->readUint32BE(); + uint32 startOffset; + + if (SWAP_BYTES_32(tag) == MKTAG('P', 'J', '9', '3') || tag == MKTAG('P', 'J', '9', '5') || tag == MKTAG('P', 'J', '0', '0')) { + // PPC: The RIFX shares the data fork with the binary + startOffset = dataFork->readUint32BE(); + } else { + // 68k: The RIFX is the only thing in the data fork + startOffset = 0; + } + + if (!_mainArchive->openStream(dataFork, startOffset)) + error("Failed to load RIFX from Mac binary"); + } +} + +void DirectorEngine::loadSharedCastsFrom(Common::String filename) { + Archive *shardcst = createArchive(); + + debugC(1, kDebugLoading, "Loading Shared cast '%s'", filename.c_str()); + + shardcst->openFile(filename); + + _sharedDIB = new Common::HashMap<int, Common::SeekableSubReadStreamEndian *>; + _sharedSTXT = new Common::HashMap<int, Common::SeekableSubReadStreamEndian *>; + _sharedSound = new Common::HashMap<int, Common::SeekableSubReadStreamEndian *>; + _sharedBMP = new Common::HashMap<int, Common::SeekableSubReadStreamEndian *>; + + Score *castScore = new Score(this, shardcst); + + castScore->loadConfig(*shardcst->getResource(MKTAG('V','W','C','F'), 1024)); + castScore->loadCastData(*shardcst->getResource(MKTAG('V','W','C','R'), 1024)); + + _sharedCasts = &castScore->_casts; + + Common::Array<uint16> dib = shardcst->getResourceIDList(MKTAG('D','I','B',' ')); + if (dib.size() != 0) { + debugC(3, kDebugLoading, "Loading %d DIBs", dib.size()); + + for (Common::Array<uint16>::iterator iterator = dib.begin(); iterator != dib.end(); ++iterator) { + debugC(3, kDebugLoading, "Shared DIB %d", *iterator); + _sharedDIB->setVal(*iterator, shardcst->getResource(MKTAG('D','I','B',' '), *iterator)); + } + } + + Common::Array<uint16> stxt = shardcst->getResourceIDList(MKTAG('S','T','X','T')); + if (stxt.size() != 0) { + debugC(3, kDebugLoading, "Loading %d STXTs", stxt.size()); + + for (Common::Array<uint16>::iterator iterator = stxt.begin(); iterator != stxt.end(); ++iterator) { + debugC(3, kDebugLoading, "Shared STXT %d", *iterator); + _sharedSTXT->setVal(*iterator, shardcst->getResource(MKTAG('S','T','X','T'), *iterator)); + } + } + + Common::Array<uint16> bmp = shardcst->getResourceIDList(MKTAG('B','I','T','D')); + if (bmp.size() != 0) { + debugC(3, kDebugLoading, "Loading %d BITDs", bmp.size()); + for (Common::Array<uint16>::iterator iterator = bmp.begin(); iterator != bmp.end(); ++iterator) { + _sharedBMP->setVal(*iterator, shardcst->getResource(MKTAG('B','I','T','D'), *iterator)); + } + } + + Common::Array<uint16> sound = shardcst->getResourceIDList(MKTAG('S','N','D',' ')); + if (stxt.size() != 0) { + debugC(3, kDebugLoading, "Loading %d SNDs", sound.size()); + for (Common::Array<uint16>::iterator iterator = sound.begin(); iterator != sound.end(); ++iterator) { + _sharedSound->setVal(*iterator, shardcst->getResource(MKTAG('S','N','D',' '), *iterator)); + } + } +} + +} // End of namespace Director diff --git a/engines/director/detection_tables.h b/engines/director/detection_tables.h index 65eff50fc9..e03b831fa3 100644 --- a/engines/director/detection_tables.h +++ b/engines/director/detection_tables.h @@ -40,6 +40,19 @@ static const DirectorGameDescription gameDescriptions[] = { 3 }, + { // Generic D3 entry + { + "director", + "", + AD_ENTRY1("D3", 0), + Common::EN_ANY, + Common::kPlatformMacintosh, + ADGF_MACRESFORK, + GUIO1(GUIO_NOASPECT) + }, + GID_GENERIC, + 3 + }, { { "theapartment", diff --git a/engines/director/director.cpp b/engines/director/director.cpp index 469aeb80cb..c6b51bc452 100644 --- a/engines/director/director.cpp +++ b/engines/director/director.cpp @@ -20,35 +20,26 @@ * */ -#include "audio/mixer.h" - #include "common/config-manager.h" -#include "common/debug.h" -#include "common/scummsys.h" +#include "common/debug-channels.h" #include "common/error.h" -#include "common/events.h" -#include "common/macresman.h" -#include "common/stream.h" -#include "common/system.h" -#include "common/textconsole.h" -#include "common/fs.h" - -#include "engines/util.h" -#include "graphics/surface.h" #include "graphics/macgui/macwindowmanager.h" #include "director/director.h" -#include "director/dib.h" #include "director/resource.h" -#include "director/score.h" -#include "director/lingo/lingo.h" #include "director/sound.h" +#include "director/lingo/lingo.h" namespace Director { DirectorEngine::DirectorEngine(OSystem *syst, const DirectorGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc), _rnd("director") { + DebugMan.addDebugChannel(kDebugLingoExec, "lingoexec", "Lingo Execution"); + DebugMan.addDebugChannel(kDebugLingoCompile, "lingocompile", "Lingo Compilation"); + DebugMan.addDebugChannel(kDebugLoading, "loading", "Loading"); + DebugMan.addDebugChannel(kDebugImages, "images", "Image drawing"); + if (!_mixer->isReady()) error("Sound initialization failed"); @@ -56,6 +47,14 @@ DirectorEngine::DirectorEngine(OSystem *syst, const DirectorGameDescription *gam syncSoundSettings(); _sharedCasts = nullptr; + + _currentScore = nullptr; + _soundManager = nullptr; + _currentPalette = nullptr; + _currentPaletteLength = 0; + _lingo = nullptr; + + _sharedCasts = nullptr; _sharedSound = nullptr; _sharedBMP = nullptr; _sharedSTXT = nullptr; @@ -71,30 +70,28 @@ DirectorEngine::DirectorEngine(OSystem *syst, const DirectorGameDescription *gam const Common::FSNode gameDataDir(ConfMan.get("path")); SearchMan.addSubDirectoryMatching(gameDataDir, "data"); SearchMan.addSubDirectoryMatching(gameDataDir, "install"); + + _colorDepth = 8; // FIXME. Check if it is 8-bit + _keyCode = 0; } DirectorEngine::~DirectorEngine() { - delete _sharedCasts; delete _sharedSound; delete _sharedBMP; delete _sharedSTXT; delete _sharedDIB; - delete _movies; - delete _mainArchive; - delete _macBinary; + delete _currentScore; + + cleanupMainArchive(); + delete _soundManager; delete _lingo; - delete _currentScore; - delete _currentPalette; } Common::Error DirectorEngine::run() { debug("Starting v%d Director game", getVersion()); - //FIXME - _sharedMMM = "SHARDCST.MMM"; - _currentPalette = nullptr; _macBinary = nullptr; @@ -118,12 +115,12 @@ Common::Error DirectorEngine::run() { //_mainArchive = new RIFFArchive(); //_mainArchive->openFile("bookshelf_example.mmm"); - if (getPlatform() == Common::kPlatformWindows) - loadEXE(); - else - loadMac(); + scanMovies(ConfMan.get("path")); + + loadSharedCastsFrom(_sharedCastFile); + loadMainArchive(); - _currentScore = new Score(this); + _currentScore = new Score(this, _mainArchive); debug(0, "Score name %s", _currentScore->getMacName().c_str()); _currentScore->loadArchive(); @@ -132,168 +129,43 @@ Common::Error DirectorEngine::run() { return Common::kNoError; } -Common::HashMap<Common::String, Score *> DirectorEngine::loadMMMNames(Common::String folder) { +Common::HashMap<Common::String, Score *> DirectorEngine::scanMovies(const Common::String &folder) { Common::FSNode directory(folder); Common::FSList movies; + const char *sharedMMMname; + + if (getPlatform() == Common::kPlatformWindows) + sharedMMMname = "SHARDCST.MMM"; + else + sharedMMMname = "Shared Cast*"; + Common::HashMap<Common::String, Score *> nameMap; - directory.getChildren(movies, Common::FSNode::kListFilesOnly); + if (!directory.getChildren(movies, Common::FSNode::kListFilesOnly)) + return nameMap; if (!movies.empty()) { for (Common::FSList::const_iterator i = movies.begin(); i != movies.end(); ++i) { - if (i->getName() == _sharedMMM) { - loadSharedCastsFrom(i->getPath()); + debugC(2, kDebugLoading, "File: %s", i->getName().c_str()); + + if (Common::matchString(i->getName().c_str(), sharedMMMname, true)) { + _sharedCastFile = i->getName(); continue; } - RIFFArchive *arc = new RIFFArchive(); - arc->openFile(i->getPath()); - Score *sc = new Score(this); + Archive *arc = createArchive(); + + arc->openFile(i->getName()); + Score *sc = new Score(this, arc); nameMap[sc->getMacName()] = sc; + + debugC(2, kDebugLoading, "Movie name: \"%s\"", sc->getMacName().c_str()); } } return nameMap; } -void DirectorEngine::loadEXE() { - Common::SeekableReadStream *exeStream = SearchMan.createReadStreamForMember(getEXEName()); - if (!exeStream) - error("Failed to open EXE '%s'", getEXEName().c_str()); - - _lingo->processEvent(kEventStart, 0); - - exeStream->seek(-4, SEEK_END); - exeStream->seek(exeStream->readUint32LE()); - - switch (getVersion()) { - case 3: - loadEXEv3(exeStream); - break; - case 4: - loadEXEv4(exeStream); - break; - case 5: - loadEXEv5(exeStream); - break; - case 7: - loadEXEv7(exeStream); - break; - default: - error("Unhandled Windows EXE version %d", getVersion()); - } -} - -void DirectorEngine::loadEXEv3(Common::SeekableReadStream *stream) { - uint16 entryCount = stream->readUint16LE(); - if (entryCount != 1) - error("Unhandled multiple entry v3 EXE"); - - stream->skip(5); // unknown - - stream->readUint32LE(); // Main MMM size - Common::String mmmFileName = readPascalString(*stream); - Common::String directoryName = readPascalString(*stream); - - debug("Main MMM: '%s'", mmmFileName.c_str()); - debug("Directory Name: '%s'", directoryName.c_str()); - - _mainArchive = new RIFFArchive(); - - if (!_mainArchive->openFile(mmmFileName)) - error("Could not open '%s'", mmmFileName.c_str()); - - delete stream; -} - -void DirectorEngine::loadEXEv4(Common::SeekableReadStream *stream) { - if (stream->readUint32BE() != MKTAG('P', 'J', '9', '3')) - error("Invalid projector tag found in v4 EXE"); - - uint32 rifxOffset = stream->readUint32LE(); - /* uint32 fontMapOffset = */ stream->readUint32LE(); - /* uint32 resourceForkOffset1 = */ stream->readUint32LE(); - /* uint32 resourceForkOffset2 = */ stream->readUint32LE(); - stream->readUint32LE(); // graphics DLL offset - stream->readUint32LE(); // sound DLL offset - /* uint32 rifxOffsetAlt = */ stream->readUint32LE(); // equivalent to rifxOffset - - loadEXERIFX(stream, rifxOffset); -} - -void DirectorEngine::loadEXEv5(Common::SeekableReadStream *stream) { - if (stream->readUint32LE() != MKTAG('P', 'J', '9', '5')) - error("Invalid projector tag found in v5 EXE"); - - uint32 rifxOffset = stream->readUint32LE(); - stream->readUint32LE(); // unknown - stream->readUint32LE(); // unknown - stream->readUint32LE(); // unknown - /* uint16 screenWidth = */ stream->readUint16LE(); - /* uint16 screenHeight = */ stream->readUint16LE(); - stream->readUint32LE(); // unknown - stream->readUint32LE(); // unknown - /* uint32 fontMapOffset = */ stream->readUint32LE(); - - loadEXERIFX(stream, rifxOffset); -} - -void DirectorEngine::loadEXEv7(Common::SeekableReadStream *stream) { - if (stream->readUint32LE() != MKTAG('P', 'J', '0', '0')) - error("Invalid projector tag found in v7 EXE"); - - uint32 rifxOffset = stream->readUint32LE(); - stream->readUint32LE(); // unknown - stream->readUint32LE(); // unknown - stream->readUint32LE(); // unknown - stream->readUint32LE(); // unknown - stream->readUint32LE(); // some DLL offset - - loadEXERIFX(stream, rifxOffset); -} - -void DirectorEngine::loadEXERIFX(Common::SeekableReadStream *stream, uint32 offset) { - _mainArchive = new RIFXArchive(); - - if (!_mainArchive->openStream(stream, offset)) - error("Failed to load RIFX from EXE"); -} - -void DirectorEngine::loadMac() { - if (getVersion() < 4) { - // The data is part of the resource fork of the executable - _mainArchive = new MacArchive(); - - if (!_mainArchive->openFile(getEXEName())) - error("Failed to open Mac binary '%s'", getEXEName().c_str()); - } else { - // The RIFX is located in the data fork of the executable - _macBinary = new Common::MacResManager(); - - if (!_macBinary->open(getEXEName()) || !_macBinary->hasDataFork()) - error("Failed to open Mac binary '%s'", getEXEName().c_str()); - - Common::SeekableReadStream *dataFork = _macBinary->getDataFork(); - _mainArchive = new RIFXArchive(); - - // First we need to detect PPC vs. 68k - - uint32 tag = dataFork->readUint32BE(); - uint32 startOffset; - - if (SWAP_BYTES_32(tag) == MKTAG('P', 'J', '9', '3') || tag == MKTAG('P', 'J', '9', '5') || tag == MKTAG('P', 'J', '0', '0')) { - // PPC: The RIFX shares the data fork with the binary - startOffset = dataFork->readUint32BE(); - } else { - // 68k: The RIFX is the only thing in the data fork - startOffset = 0; - } - - if (!_mainArchive->openStream(dataFork, startOffset)) - error("Failed to load RIFX from Mac binary"); - } -} - Common::String DirectorEngine::readPascalString(Common::SeekableReadStream &stream) { byte length = stream.readByte(); Common::String x; @@ -309,60 +181,4 @@ void DirectorEngine::setPalette(byte *palette, uint16 count) { _currentPaletteLength = count; } -void DirectorEngine::loadSharedCastsFrom(Common::String filename) { - Archive *shardcst; - - if (getVersion() < 4) { - shardcst = new RIFFArchive(); - } else { - shardcst = new RIFXArchive(); - } - - shardcst->openFile(filename); - - Score *castScore = new Score(this); - Common::SeekableSubReadStreamEndian *castStream = shardcst->getResource(MKTAG('V','W','C','R'), 1024); - - castScore->loadCastData(*castStream); - *_sharedCasts = castScore->_casts; - - Common::Array<uint16> dib = shardcst->getResourceIDList(MKTAG('D','I','B',' ')); - - if (dib.size() != 0) { - Common::Array<uint16>::iterator iterator; - for (iterator = dib.begin(); iterator != dib.end(); ++iterator) { - debug(3, "Shared DIB %d", *iterator); - _sharedDIB->setVal(*iterator, shardcst->getResource(MKTAG('D','I','B',' '), *iterator)); - } - } - - Common::Array<uint16> stxt = shardcst->getResourceIDList(MKTAG('S','T','X','T')); - - if (stxt.size() != 0) { - Common::Array<uint16>::iterator iterator; - for (iterator = stxt.begin(); iterator != stxt.end(); ++iterator) { - debug(3, "Shared STXT %d", *iterator); - _sharedSTXT->setVal(*iterator, shardcst->getResource(MKTAG('S','T','X','T'), *iterator)); - } - } - - Common::Array<uint16> bmp = shardcst->getResourceIDList(MKTAG('B','I','T','D')); - - if (bmp.size() != 0) { - Common::Array<uint16>::iterator iterator; - for (iterator = bmp.begin(); iterator != bmp.end(); ++iterator) { - _sharedBMP->setVal(*iterator, shardcst->getResource(MKTAG('B','I','T','D'), *iterator)); - } - } - - Common::Array<uint16> sound = shardcst->getResourceIDList(MKTAG('S','N','D',' ')); - - if (stxt.size() != 0) { - Common::Array<uint16>::iterator iterator; - for (iterator = sound.begin(); iterator != sound.end(); ++iterator) { - _sharedSound->setVal(*iterator, shardcst->getResource(MKTAG('S','N','D',' '), *iterator)); - } - } -} - } // End of namespace Director diff --git a/engines/director/director.h b/engines/director/director.h index 6208df2197..5a28a9e267 100644 --- a/engines/director/director.h +++ b/engines/director/director.h @@ -23,14 +23,11 @@ #ifndef DIRECTOR_DIRECTOR_H #define DIRECTOR_DIRECTOR_H -#include "common/scummsys.h" #include "common/random.h" #include "common/substream.h" -#include "common/str.h" #include "common/hashmap.h" #include "engines/engine.h" -#include "engines/director/sound.h" namespace Common { class MacResManager; @@ -49,10 +46,19 @@ enum DirectorGameID { class Archive; struct DirectorGameDescription; +class DirectorSound; class Lingo; class Score; struct Cast; +enum { + kDebugLingoExec = 1 << 0, + kDebugLingoCompile = 1 << 1, + kDebugLoading = 1 << 2, + kDebugImages = 1 << 3 +}; + + class DirectorEngine : public ::Engine { public: DirectorEngine(OSystem *syst, const DirectorGameDescription *gameDesc); @@ -74,6 +80,11 @@ public: const byte *getPalette() const { return _currentPalette; } uint16 getPaletteColorCount() const { return _currentPaletteLength; } void loadSharedCastsFrom(Common::String filename); + + void loadMainArchive(); + Archive *createArchive(); + void cleanupMainArchive(); + Common::HashMap<int, Common::SeekableSubReadStreamEndian *> *getSharedDIB() const { return _sharedDIB; } Common::HashMap<int, Common::SeekableSubReadStreamEndian *> *getSharedBMP() const { return _sharedBMP; } Common::HashMap<int, Common::SeekableSubReadStreamEndian *> *getSharedSTXT() const { return _sharedSTXT; } @@ -85,13 +96,17 @@ public: Common::RandomSource _rnd; Graphics::MacWindowManager *_wm; +public: + int _colorDepth; + int _keyCode; + protected: virtual Common::Error run(); private: const DirectorGameDescription *_gameDescription; - Common::HashMap<Common::String, Score *> loadMMMNames(Common::String folder); + Common::HashMap<Common::String, Score *> scanMovies(const Common::String &folder); void loadEXE(); void loadEXEv3(Common::SeekableReadStream *stream); void loadEXEv4(Common::SeekableReadStream *stream); @@ -102,7 +117,6 @@ private: Common::String readPascalString(Common::SeekableReadStream &stream); - Common::String _sharedMMM; Common::HashMap<int, Cast *> *_sharedCasts; Common::HashMap<int, Common::SeekableSubReadStreamEndian *> *_sharedDIB; Common::HashMap<int, Common::SeekableSubReadStreamEndian *> *_sharedSTXT; @@ -115,6 +129,8 @@ private: byte *_currentPalette; uint16 _currentPaletteLength; Lingo *_lingo; + + Common::String _sharedCastFile; }; } // End of namespace Director diff --git a/engines/director/frame.cpp b/engines/director/frame.cpp new file mode 100644 index 0000000000..342e524805 --- /dev/null +++ b/engines/director/frame.cpp @@ -0,0 +1,782 @@ +/* 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/system.h" +#include "graphics/font.h" +#include "graphics/macgui/macwindowmanager.h" +#include "image/bmp.h" + +#include "director/director.h" +#include "director/frame.h" +#include "director/images.h" +#include "director/resource.h" +#include "director/score.h" +#include "director/sprite.h" + +namespace Director { + +Frame::Frame(DirectorEngine *vm) { + _vm = vm; + _transDuration = 0; + _transType = kTransNone; + _transArea = 0; + _transChunkSize = 0; + _tempo = 0; + + _sound1 = 0; + _sound2 = 0; + _soundType1 = 0; + _soundType2 = 0; + + _actionId = 0; + _skipFrameFlag = 0; + _blend = 0; + + _palette = NULL; + + _sprites.resize(CHANNEL_COUNT); + + for (uint16 i = 0; i < _sprites.size(); i++) { + Sprite *sp = new Sprite(); + _sprites[i] = sp; + } +} + +Frame::Frame(const Frame &frame) { + _vm = frame._vm; + _actionId = frame._actionId; + _transArea = frame._transArea; + _transDuration = frame._transDuration; + _transType = frame._transType; + _transChunkSize = frame._transChunkSize; + _tempo = frame._tempo; + _sound1 = frame._sound1; + _sound2 = frame._sound2; + _soundType1 = frame._soundType1; + _soundType2 = frame._soundType2; + _skipFrameFlag = frame._skipFrameFlag; + _blend = frame._blend; + _palette = new PaletteInfo(); + + debugC(1, kDebugLoading, "Frame. action: %d transType: %d transDuration: %d", _actionId, _transType, _transDuration); + + _sprites.resize(CHANNEL_COUNT); + + for (uint16 i = 0; i < CHANNEL_COUNT; i++) { + _sprites[i] = new Sprite(*frame._sprites[i]); + } +} + +Frame::~Frame() { + delete _palette; +} + +void Frame::readChannel(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size) { + if (offset >= 32) { + if (size <= 16) + readSprite(stream, offset, size); + else { + // read > 1 sprites channel + while (size > 16) { + byte spritePosition = (offset - 32) / 16; + uint16 nextStart = (spritePosition + 1) * 16 + 32; + uint16 needSize = nextStart - offset; + readSprite(stream, offset, needSize); + offset += needSize; + size -= needSize; + } + readSprite(stream, offset, size); + } + } else { + readMainChannels(stream, offset, size); + } +} + +void Frame::readMainChannels(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size) { + uint16 finishPosition = offset + size; + + while (offset < finishPosition) { + switch(offset) { + case kScriptIdPosition: + _actionId = stream.readByte(); + offset++; + break; + case kSoundType1Position: + _soundType1 = stream.readByte(); + offset++; + break; + case kTransFlagsPosition: { + uint8 transFlags = stream.readByte(); + if (transFlags & 0x80) + _transArea = 1; + else + _transArea = 0; + _transDuration = transFlags & 0x7f; + offset++; + } + break; + case kTransChunkSizePosition: + _transChunkSize = stream.readByte(); + offset++; + break; + case kTempoPosition: + _tempo = stream.readByte(); + offset++; + break; + case kTransTypePosition: + _transType = static_cast<TransitionType>(stream.readByte()); + offset++; + break; + case kSound1Position: + _sound1 = stream.readUint16(); + offset+=2; + break; + case kSkipFrameFlagsPosition: + _skipFrameFlag = stream.readByte(); + offset++; + break; + case kBlendPosition: + _blend = stream.readByte(); + offset++; + break; + case kSound2Position: + _sound2 = stream.readUint16(); + offset += 2; + break; + case kSound2TypePosition: + _soundType2 = stream.readByte(); + offset += 1; + break; + case kPaletePosition: + if (stream.readUint16()) + readPaletteInfo(stream); + offset += 16; + break; + default: + offset++; + stream.readByte(); + debugC(kDebugLoading, "Frame::readMainChannels: Field Position %d, Finish Position %d", offset, finishPosition); + break; + } + } +} + +void Frame::readPaletteInfo(Common::SeekableSubReadStreamEndian &stream) { + _palette->firstColor = stream.readByte(); + _palette->lastColor = stream.readByte(); + _palette->flags = stream.readByte(); + _palette->speed = stream.readByte(); + _palette->frameCount = stream.readUint16(); + stream.skip(8); //unknown +} + +void Frame::readSprite(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size) { + uint16 spritePosition = (offset - 32) / 16; + uint16 spriteStart = spritePosition * 16 + 32; + + uint16 fieldPosition = offset - spriteStart; + uint16 finishPosition = fieldPosition + size; + + Sprite &sprite = *_sprites[spritePosition]; + + while (fieldPosition < finishPosition) { + switch (fieldPosition) { + case kSpritePositionUnk1: + /*byte x1 = */ stream.readByte(); + fieldPosition++; + break; + case kSpritePositionEnabled: + sprite._enabled = (stream.readByte() != 0); + fieldPosition++; + break; + case kSpritePositionUnk2: + /*byte x2 = */ stream.readUint16(); + fieldPosition += 2; + break; + case kSpritePositionFlags: + sprite._flags = stream.readUint16(); + sprite._ink = static_cast<InkType>(sprite._flags & 0x3f); + + if (sprite._flags & 0x40) + sprite._trails = 1; + else + sprite._trails = 0; + + fieldPosition += 2; + break; + case kSpritePositionCastId: + sprite._castId = stream.readUint16(); + fieldPosition += 2; + break; + case kSpritePositionY: + sprite._startPoint.y = stream.readUint16(); + fieldPosition += 2; + break; + case kSpritePositionX: + sprite._startPoint.x = stream.readUint16(); + fieldPosition += 2; + break; + case kSpritePositionWidth: + sprite._width = stream.readUint16(); + fieldPosition += 2; + break; + case kSpritePositionHeight: + sprite._height = stream.readUint16(); + fieldPosition += 2; + break; + default: + // end of channel, go to next sprite channel + readSprite(stream, spriteStart + 16, finishPosition - fieldPosition); + fieldPosition = finishPosition; + break; + } + } +} + +void Frame::prepareFrame(Score *score) { + renderSprites(*score->_surface, false); + renderSprites(*score->_trailSurface, true); + + if (_transType != 0) + //T ODO Handle changing area case + playTransition(score); + + if (_sound1 != 0 || _sound2 != 0) { + playSoundChannel(); + } + + g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, score->_surface->getBounds().width(), score->_surface->getBounds().height()); +} + +void Frame::playSoundChannel() { + debug(0, "Sound1 %d", _sound1); + debug(0, "Sound2 %d", _sound2); +} + +void Frame::playTransition(Score *score) { + uint16 duration = _transDuration * 250; // _transDuration in 1/4 of sec + duration = (duration == 0 ? 250 : duration); // director supports transition duration = 0, but animation play like value = 1, idk. + + if (_transChunkSize == 0) + _transChunkSize = 1; // equal to 1 step + + uint16 stepDuration = duration / _transChunkSize; + uint16 steps = duration / stepDuration; + + switch (_transType) { + case kTransCoverDown: + { + uint16 stepSize = score->_movieRect.height() / steps; + Common::Rect r = score->_movieRect; + + for (uint16 i = 1; i < steps; i++) { + r.setHeight(stepSize * i); + + g_system->delayMillis(stepDuration); + score->processEvents(); + + g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, r.width(), r.height()); + g_system->updateScreen(); + } + } + break; + case kTransCoverUp: + { + uint16 stepSize = score->_movieRect.height() / steps; + Common::Rect r = score->_movieRect; + + for (uint16 i = 1; i < steps; i++) { + r.setHeight(stepSize * i); + + g_system->delayMillis(stepDuration); + score->processEvents(); + + g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, score->_movieRect.height() - stepSize * i, r.width(), r.height()); + g_system->updateScreen(); + } + } + break; + case kTransCoverRight: { + uint16 stepSize = score->_movieRect.width() / steps; + Common::Rect r = score->_movieRect; + + for (uint16 i = 1; i < steps; i++) { + r.setWidth(stepSize * i); + + g_system->delayMillis(stepDuration); + score->processEvents(); + + g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, r.width(), r.height()); + g_system->updateScreen(); + } + } + break; + case kTransCoverLeft: { + uint16 stepSize = score->_movieRect.width() / steps; + Common::Rect r = score->_movieRect; + + for (uint16 i = 1; i < steps; i++) { + r.setWidth(stepSize * i); + + g_system->delayMillis(stepDuration); + score->processEvents(); + + g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, score->_movieRect.width() - stepSize * i, 0, r.width(), r.height()); + g_system->updateScreen(); + } + } + break; + case kTransCoverUpLeft: { + uint16 stepSize = score->_movieRect.width() / steps; + Common::Rect r = score->_movieRect; + + for (uint16 i = 1; i < steps; i++) { + r.setWidth(stepSize * i); + r.setHeight(stepSize * i); + + g_system->delayMillis(stepDuration); + score->processEvents(); + + g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, score->_movieRect.width() - stepSize * i, score->_movieRect.height() - stepSize * i, r.width(), r.height()); + g_system->updateScreen(); + } + } + break; + case kTransCoverUpRight: { + uint16 stepSize = score->_movieRect.width() / steps; + Common::Rect r = score->_movieRect; + + for (uint16 i = 1; i < steps; i++) { + r.setWidth(stepSize * i); + r.setHeight(stepSize * i); + + g_system->delayMillis(stepDuration); + score->processEvents(); + + g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, score->_movieRect.height() - stepSize * i, r.width(), r.height()); + g_system->updateScreen(); + } + } + break; + case kTransCoverDownLeft: { + uint16 stepSize = score->_movieRect.width() / steps; + Common::Rect r = score->_movieRect; + + for (uint16 i = 1; i < steps; i++) { + r.setWidth(stepSize * i); + r.setHeight(stepSize * i); + + g_system->delayMillis(stepDuration); + score->processEvents(); + + g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, score->_movieRect.width() - stepSize * i, 0, r.width(), r.height()); + g_system->updateScreen(); + } + } + break; + case kTransCoverDownRight: { + uint16 stepSize = score->_movieRect.width() / steps; + Common::Rect r = score->_movieRect; + + for (uint16 i = 1; i < steps; i++) { + r.setWidth(stepSize * i); + r.setHeight(stepSize * i); + + g_system->delayMillis(stepDuration); + score->processEvents(); + + g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, r.width(), r.height()); + g_system->updateScreen(); + } + } + break; + default: + warning("Unhandled transition type %d %d %d", _transType, duration, _transChunkSize); + break; + + } +} + +void Frame::renderSprites(Graphics::ManagedSurface &surface, bool renderTrail) { + for (uint16 i = 0; i < CHANNEL_COUNT; i++) { + if (_sprites[i]->_enabled) { + if ((_sprites[i]->_trails == 0 && renderTrail) || (_sprites[i]->_trails == 1 && !renderTrail)) + continue; + + Cast *cast; + if (!_vm->_currentScore->_casts.contains(_sprites[i]->_castId)) { + if (!_vm->getSharedCasts()->contains(_sprites[i]->_castId)) { + warning("Cast id %d not found", _sprites[i]->_castId); + continue; + } else { + warning("Getting cast id %d from shared cast", _sprites[i]->_castId); + cast = _vm->getSharedCasts()->getVal(_sprites[i]->_castId); + } + } else { + cast = _vm->_currentScore->_casts[_sprites[i]->_castId]; + } + + if (cast->type == kCastText) { + renderText(surface, i); + continue; + } + + Image::ImageDecoder *img = getImageFrom(_sprites[i]->_castId); + + if (!img) { + warning("Image with id %d not found", _sprites[i]->_castId); + continue; + } + + if (!img->getSurface()) { + warning("Frame::renderSprites: Could not load image %d", _sprites[i]->_castId); + continue; + } + + uint32 regX = static_cast<BitmapCast *>(_sprites[i]->_cast)->regX; + uint32 regY = static_cast<BitmapCast *>(_sprites[i]->_cast)->regY; + uint32 rectLeft = static_cast<BitmapCast *>(_sprites[i]->_cast)->initialRect.left; + uint32 rectTop = static_cast<BitmapCast *>(_sprites[i]->_cast)->initialRect.top; + + int x = _sprites[i]->_startPoint.x - regX + rectLeft; + int y = _sprites[i]->_startPoint.y - regY + rectTop; + int height = _sprites[i]->_height; + int width = _sprites[i]->_width; + + Common::Rect drawRect = Common::Rect(x, y, x + width, y + height); + _drawRects.push_back(drawRect); + + switch (_sprites[i]->_ink) { + case kInkTypeCopy: + surface.blitFrom(*img->getSurface(), Common::Point(x, y)); + break; + case kInkTypeTransparent: + // FIXME: is it always white (last entry in pallette)? + surface.transBlitFrom(*img->getSurface(), Common::Point(x, y), _vm->getPaletteColorCount() - 1); + break; + case kInkTypeBackgndTrans: + drawBackgndTransSprite(surface, *img->getSurface(), drawRect); + break; + case kInkTypeMatte: + drawMatteSprite(surface, *img->getSurface(), drawRect); + break; + case kInkTypeGhost: + drawGhostSprite(surface, *img->getSurface(), drawRect); + break; + case kInkTypeReverse: + drawReverseSprite(surface, *img->getSurface(), drawRect); + break; + default: + warning("Unhandled ink type %d", _sprites[i]->_ink); + surface.blitFrom(*img->getSurface(), Common::Point(x, y)); + break; + } + } + } +} + +void Frame::renderButton(Graphics::ManagedSurface &surface, uint16 spriteId) { + renderText(surface, spriteId); + + uint16 castID = _sprites[spriteId]->_castId; + ButtonCast *button = static_cast<ButtonCast *>(_vm->_currentScore->_casts[castID]); + + uint32 rectLeft = button->initialRect.left; + uint32 rectTop = button->initialRect.top; + + int x = _sprites[spriteId]->_startPoint.x + rectLeft; + int y = _sprites[spriteId]->_startPoint.y + rectTop; + int height = _sprites[spriteId]->_height; + int width = _sprites[spriteId]->_width; + + switch (button->buttonType) { + case kTypeCheckBox: + // Magic numbers: checkbox square need to move left about 5px from text and 12px side size (D4) + surface.frameRect(Common::Rect(x - 17, y, x + 12, y + 12), 0); + break; + case kTypeButton: + surface.frameRect(Common::Rect(x, y, x + width, y + height), 0); + break; + case kTypeRadio: + warning("STUB: renderButton: kTypeRadio"); + break; + } +} + +Image::ImageDecoder *Frame::getImageFrom(uint16 spriteId) { + uint16 imgId = spriteId + 1024; + Image::ImageDecoder *img = NULL; + + if (_vm->_currentScore->getArchive()->hasResource(MKTAG('D', 'I', 'B', ' '), imgId)) { + img = new DIBDecoder(); + img->loadStream(*_vm->_currentScore->getArchive()->getResource(MKTAG('D', 'I', 'B', ' '), imgId)); + return img; + } + + if (_vm->getSharedDIB() != NULL && _vm->getSharedDIB()->contains(imgId)) { + img = new DIBDecoder(); + img->loadStream(*_vm->getSharedDIB()->getVal(imgId)); + return img; + } + + if (_vm->_currentScore->getArchive()->hasResource(MKTAG('B', 'I', 'T', 'D'), imgId)) { + Common::SeekableReadStream *pic = _vm->_currentScore->getArchive()->getResource(MKTAG('B', 'I', 'T', 'D'), imgId); + + if (_vm->getVersion() < 4) { + BitmapCast *bc = static_cast<BitmapCast *>(_vm->_currentScore->_casts[spriteId]); + int w = bc->initialRect.width(), h = bc->initialRect.height(); + + debugC(2, kDebugImages, "id: %d, w: %d, h: %d, flags: %x, some: %x, unk1: %d, unk2: %d", + imgId, w, h, bc->flags, bc->someFlaggyThing, bc->unk1, bc->unk2); + img = new BITDDecoder(w, h); + } else { + img = new Image::BitmapDecoder(); + } + + if (debugChannelSet(8, kDebugLoading)) { + Common::SeekableReadStream *s = pic; + byte buf[1024]; + int n = s->read(buf, 1024); + Common::hexdump(buf, n); + } + + img->loadStream(*pic); + return img; + } + + if (_vm->getSharedBMP() != NULL && _vm->getSharedBMP()->contains(imgId)) { + img = new Image::BitmapDecoder(); + img->loadStream(*_vm->getSharedBMP()->getVal(imgId)); + return img; + } + + warning("Image %d not found", spriteId); + return img; +} + + +void Frame::renderText(Graphics::ManagedSurface &surface, uint16 spriteID) { + uint16 castID = _sprites[spriteID]->_castId; + + TextCast *textCast = static_cast<TextCast *>(_vm->_currentScore->_casts[castID]); + Common::SeekableSubReadStreamEndian *textStream; + + if (_vm->_currentScore->_movieArchive->hasResource(MKTAG('S','T','X','T'), castID + 1024)) { + textStream = _vm->_currentScore->_movieArchive->getResource(MKTAG('S','T','X','T'), castID + 1024); + } else { + textStream = _vm->getSharedSTXT()->getVal(spriteID + 1024); + } + /*uint32 unk1 = */ textStream->readUint32(); + uint32 strLen = textStream->readUint32(); + /*uin32 dataLen = */ textStream->readUint32(); + Common::String text; + + for (uint32 i = 0; i < strLen; i++) { + byte ch = textStream->readByte(); + if (ch == 0x0d) { + ch = '\n'; + } + text += ch; + } + + uint32 rectLeft = static_cast<TextCast *>(_sprites[spriteID]->_cast)->initialRect.left; + uint32 rectTop = static_cast<TextCast *>(_sprites[spriteID]->_cast)->initialRect.top; + + int x = _sprites[spriteID]->_startPoint.x + rectLeft; + int y = _sprites[spriteID]->_startPoint.y + rectTop; + int height = _sprites[spriteID]->_height; + int width = _sprites[spriteID]->_width; + + const char *fontName; + + if (_vm->_currentScore->_fontMap.contains(textCast->fontId)) { + fontName = _vm->_currentScore->_fontMap[textCast->fontId].c_str(); + } else if ((fontName = _vm->_wm->getFontName(textCast->fontId, textCast->fontSize)) == NULL) { + warning("Unknown font id %d, falling back to default", textCast->fontId); + fontName = _vm->_wm->getFontName(0, 12); + } + + const Graphics::Font *font = _vm->_wm->getFont(fontName, Graphics::FontManager::kBigGUIFont); + + font->drawString(&surface, text, x, y, width, 0); + + if (textCast->borderSize != kSizeNone) { + uint16 size = textCast->borderSize; + + // Indent from borders, measured in d4 + x -= 1; + y -= 4; + + height += 4; + width += 1; + + while (size) { + surface.frameRect(Common::Rect(x, y, x + height, y + width), 0); + x--; + y--; + height += 2; + width += 2; + size--; + } + } + + if (textCast->gutterSize != kSizeNone) { + x -= 1; + y -= 4; + + height += 4; + width += 1; + uint16 size = textCast->gutterSize; + + surface.frameRect(Common::Rect(x, y, x + height, y + width), 0); + + while (size) { + surface.drawLine(x + width, y, x + width, y + height, 0); + surface.drawLine(x, y + height, x + width, y + height, 0); + x++; + y++; + size--; + } + } +} + +void Frame::drawBackgndTransSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) { + uint8 skipColor = _vm->getPaletteColorCount() - 1; // FIXME is it always white (last entry in pallette) ? + + for (int ii = 0; ii < sprite.h; ii++) { + const byte *src = (const byte *)sprite.getBasePtr(0, ii); + byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii); + + for (int j = 0; j < drawRect.width(); j++) { + if (*src != skipColor) + *dst = *src; + + src++; + dst++; + } + } +} + +void Frame::drawGhostSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) { + uint8 skipColor = _vm->getPaletteColorCount() - 1; + for (int ii = 0; ii < sprite.h; ii++) { + const byte *src = (const byte *)sprite.getBasePtr(0, ii); + byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii); + + for (int j = 0; j < drawRect.width(); j++) { + if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0) && (*src != skipColor)) + *dst = (_vm->getPaletteColorCount() - 1) - *src; // Oposite color + + src++; + dst++; + } + } +} + +void Frame::drawReverseSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) { + uint8 skipColor = _vm->getPaletteColorCount() - 1; + for (int ii = 0; ii < sprite.h; ii++) { + const byte *src = (const byte *)sprite.getBasePtr(0, ii); + byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii); + + for (int j = 0; j < drawRect.width(); j++) { + if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0)) + *dst = (_vm->getPaletteColorCount() - 1) - *src; + else if (*src != skipColor) + *dst = *src; + src++; + dst++; + } + } +} + +void Frame::drawMatteSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) { + // Like background trans, but all white pixels NOT ENCLOSED by coloured pixels are transparent + Graphics::Surface tmp; + tmp.copyFrom(sprite); + + // Searching white color in the corners + int whiteColor = -1; + + for (int corner = 0; corner < 4; corner++) { + int x = (corner & 0x1) ? tmp.w - 1 : 0; + int y = (corner & 0x2) ? tmp.h - 1 : 0; + + byte color = *(byte *)tmp.getBasePtr(x, y); + + if (_vm->getPalette()[color * 3 + 0] == 0xff && + _vm->getPalette()[color * 3 + 1] == 0xff && + _vm->getPalette()[color * 3 + 2] == 0xff) { + whiteColor = color; + break; + } + } + + if (whiteColor == -1) { + debugC(1, kDebugImages, "No white color for Matte image"); + + for (int yy = 0; yy < tmp.h; yy++) { + const byte *src = (const byte *)tmp.getBasePtr(0, yy); + byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + yy); + + for (int xx = 0; xx < drawRect.width(); xx++, src++, dst++) + *dst = *src; + } + } else { + Graphics::FloodFill ff(&tmp, whiteColor, 0, true); + + for (int yy = 0; yy < tmp.h; yy++) { + ff.addSeed(0, yy); + ff.addSeed(tmp.w - 1, yy); + } + + for (int xx = 0; xx < tmp.w; xx++) { + ff.addSeed(xx, 0); + ff.addSeed(xx, tmp.h - 1); + } + ff.fillMask(); + + for (int yy = 0; yy < tmp.h; yy++) { + const byte *src = (const byte *)tmp.getBasePtr(0, yy); + const byte *mask = (const byte *)ff.getMask()->getBasePtr(0, yy); + byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + yy); + + for (int xx = 0; xx < drawRect.width(); xx++, src++, dst++, mask++) + if (*mask == 0) + *dst = *src; + } + } + + tmp.free(); +} + +uint16 Frame::getSpriteIDFromPos(Common::Point pos) { + // Find first from top to bottom + for (uint16 i = _drawRects.size() - 1; i > 0; i--) { + if (_drawRects[i].contains(pos)) + return i; + } + + return 0; +} + +} // End of namespace Director diff --git a/engines/director/frame.h b/engines/director/frame.h new file mode 100644 index 0000000000..8c6f82f493 --- /dev/null +++ b/engines/director/frame.h @@ -0,0 +1,149 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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. + * + */ + +#ifndef DIRECTOR_FRAME_H +#define DIRECTOR_FRAME_H + +#include "graphics/managed_surface.h" + +namespace Image { + class ImageDecoder; +} + +namespace Director { + +class Sprite; + +#define CHANNEL_COUNT 24 + +enum TransitionType { + kTransNone, + kTransWipeRight, + kTransWipeLeft, + kTransWipeDown, + kTransWipeUp, + kTransCenterOutHorizontal, + kTransEdgesInHorizontal, + kTransCenterOutVertical, + kTransEdgesInVertical, + kTransCenterOutSquare, + kTransEdgesInSquare, + kTransPushLeft, + kTransPushRight, + kTransPushDown, + kTransPushUp, + kTransRevealUp, + kTransRevealUpRight, + kTransRevealRight, + kTransRevealDown, + kTransRevealDownRight, + kTransRevealDownLeft, + kTransRevealLeft, + kTransRevealUpLeft, + kTransDissolvePixelsFast, + kTransDissolveBoxyRects, + kTransDissolveBoxySquares, + kTransDissolvePatterns, + kTransRandomRows, + kTransRandomColumns, + kTransCoverDown, + kTransCoverDownLeft, + kTransCoverDownRight, + kTransCoverLeft, + kTransCoverRight, + kTransCoverUp, + kTransCoverUpLeft, + kTransCoverUpRight, + kTransTypeVenitianBlind, + kTransTypeCheckerboard, + kTransTypeStripsBottomBuildLeft, + kTransTypeStripsBottomBuildRight, + kTransTypeStripsLeftBuildDown, + kTransTypeStripsLeftBuildUp, + kTransTypeStripsRightBuildDown, + kTransTypeStripsRightBuildUp, + kTransTypeStripsTopBuildLeft, + kTransTypeStripsTopBuildRight, + kTransZoomOpen, + kTransZoomClose, + kTransVerticalBinds, + kTransDissolveBitsTrans, + kTransDissolvePixels, + kTransDissolveBits +}; + +struct PaletteInfo { + uint8 firstColor; + uint8 lastColor; + uint8 flags; + uint8 speed; + uint16 frameCount; +}; + + +class Frame { +public: + Frame(DirectorEngine *vm); + Frame(const Frame &frame); + ~Frame(); + void readChannel(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size); + void prepareFrame(Score *score); + uint16 getSpriteIDFromPos(Common::Point pos); + +private: + void playTransition(Score *score); + void playSoundChannel(); + void renderSprites(Graphics::ManagedSurface &surface, bool renderTrail); + void renderText(Graphics::ManagedSurface &surface, uint16 spriteId); + void renderButton(Graphics::ManagedSurface &surface, uint16 spriteId); + void readPaletteInfo(Common::SeekableSubReadStreamEndian &stream); + void readSprite(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size); + void readMainChannels(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size); + Image::ImageDecoder *getImageFrom(uint16 spriteID); + void drawBackgndTransSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect); + void drawMatteSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect); + void drawGhostSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect); + void drawReverseSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect); +public: + uint8 _actionId; + uint8 _transDuration; + uint8 _transArea; //1 - Whole Stage, 0 - Changing Area + uint8 _transChunkSize; + TransitionType _transType; + PaletteInfo *_palette; + uint8 _tempo; + + uint16 _sound1; + uint8 _soundType1; + uint16 _sound2; + uint8 _soundType2; + + uint8 _skipFrameFlag; + uint8 _blend; + Common::Array<Sprite *> _sprites; + Common::Array<Common::Rect > _drawRects; + DirectorEngine *_vm; +}; + +} //End of namespace Director + +#endif diff --git a/engines/director/dib.cpp b/engines/director/images.cpp index 8c54ba5363..cd8223ae8e 100644 --- a/engines/director/dib.cpp +++ b/engines/director/images.cpp @@ -20,19 +20,12 @@ * */ -#include "director/dib.h" - -#include "common/stream.h" #include "common/substream.h" -#include "common/textconsole.h" -#include "graphics/pixelformat.h" -#include "graphics/surface.h" -#include "graphics/palette.h" -#include "image/codecs/codec.h" -#include "common/util.h" #include "common/debug.h" -#include "image/codecs/bmp_raw.h" -#include "common/system.h" +#include "common/textconsole.h" + +#include "director/director.h" +#include "director/images.h" namespace Director { @@ -62,7 +55,7 @@ void DIBDecoder::loadPalette(Common::SeekableReadStream &stream) { uint16 steps = stream.size() / 6; uint16 index = (steps * 3) - 1; _paletteColorCount = steps; - _palette = new byte[index]; + _palette = new byte[index + 1]; for (uint8 i = 0; i < steps; i++) { _palette[index - 2] = stream.readByte(); @@ -87,7 +80,7 @@ bool DIBDecoder::loadStream(Common::SeekableReadStream &stream) { stream.readUint16LE(); // planes uint16 bitsPerPixel = stream.readUint16LE(); uint32 compression = stream.readUint32BE(); - uint32 imageSize = stream.readUint32LE(); + /* uint32 imageSize = */ stream.readUint32LE(); /* uint32 pixelsPerMeterX = */ stream.readUint32LE(); /* uint32 pixelsPerMeterY = */ stream.readUint32LE(); _paletteColorCount = stream.readUint32LE(); @@ -95,7 +88,6 @@ bool DIBDecoder::loadStream(Common::SeekableReadStream &stream) { _paletteColorCount = (_paletteColorCount == 0) ? 255: _paletteColorCount; - uint16 imageRawSize = stream.size() - 40; Common::SeekableSubReadStream subStream(&stream, 40, stream.size()); _codec = Image::createBitmapCodec(compression, width, height, bitsPerPixel); @@ -108,4 +100,104 @@ bool DIBDecoder::loadStream(Common::SeekableReadStream &stream) { return true; } +/**************************** + * BITD + ****************************/ + +BITDDecoder::BITDDecoder(int w, int h) { + _surface = new Graphics::Surface(); + + // We make the surface pitch a multiple of 16. + int pitch = w; + if (w % 16) + pitch += 16 - (w % 16); + + // HACK: Create a padded surface by adjusting w after create() + _surface->create(pitch, h, Graphics::PixelFormat::createFormatCLUT8()); + _surface->w = w; + + _palette = new byte[256 * 3]; + + _palette[0] = _palette[1] = _palette[2] = 0; + _palette[255 * 3 + 0] = _palette[255 * 3 + 1] = _palette[255 * 3 + 2] = 0xff; + + _paletteColorCount = 2; +} + +BITDDecoder::~BITDDecoder() { + destroy(); +} + +void BITDDecoder::destroy() { + _surface = 0; + + delete[] _palette; + _palette = 0; + _paletteColorCount = 0; +} + +void BITDDecoder::loadPalette(Common::SeekableReadStream &stream) { + // no op +} + +bool BITDDecoder::loadStream(Common::SeekableReadStream &stream) { + int x = 0, y = 0; + + // If the stream has exactly the required number of bits for this image, + // we assume it is uncompressed. + if (stream.size() * 8 == _surface->pitch * _surface->h) { + debugC(3, kDebugImages, "Skipping compression"); + for (y = 0; y < _surface->h; y++) { + for (x = 0; x < _surface->pitch; ) { + byte color = stream.readByte(); + for (int c = 0; c < 8; c++) + *((byte *)_surface->getBasePtr(x++, y)) = (color & (1 << (7 - c))) ? 0 : 0xff; + } + } + + return true; + } + + while (y < _surface->h) { + int n = stream.readSByte(); + int count; + int b = 0; + int state = 0; + + if (stream.eos()) + break; + + if ((n >= 0) && (n <= 127)) { // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + count = n + 1; + state = 1; + } else if ((n >= -127) && (n <= -1)) { // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + b = stream.readByte(); + count = -n + 1; + state = 2; + } else { // Else if n is -128, noop. + count = 0; + } + + for (int i = 0; i < count && y < _surface->h; i++) { + byte color = 0; + if (state == 1) { + color = stream.readByte(); + } else if (state == 2) + color = b; + + for (int c = 0; c < 8; c++) { + *((byte *)_surface->getBasePtr(x, y)) = (color & (1 << (7 - c))) ? 0 : 0xff; + x++; + if (x == _surface->pitch) { + y++; + x = 0; + break; + } + } + } + } + + return true; +} + } // End of namespace Director diff --git a/engines/director/dib.h b/engines/director/images.h index e3763be2bf..54e824588f 100644 --- a/engines/director/dib.h +++ b/engines/director/images.h @@ -20,8 +20,8 @@ * */ -#ifndef DIRECTOR_DIB_H -#define DIRECTOR_DIB_H +#ifndef DIRECTOR_IMAGES_H +#define DIRECTOR_IMAGES_H #include "common/scummsys.h" #include "common/str.h" @@ -62,6 +62,25 @@ private: uint8 _paletteColorCount; }; +class BITDDecoder : public Image::ImageDecoder { +public: + BITDDecoder(int w, int h); + virtual ~BITDDecoder(); + + // ImageDecoder API + void destroy(); + virtual bool loadStream(Common::SeekableReadStream &stream); + virtual const Graphics::Surface *getSurface() const { return _surface; } + const byte *getPalette() const { return _palette; } + void loadPalette(Common::SeekableReadStream &stream); + uint16 getPaletteColorCount() const { return _paletteColorCount; } + +private: + Graphics::Surface *_surface; + byte *_palette; + uint8 _paletteColorCount; +}; + } // End of namespace Director #endif diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp index 5001eeabb9..bad585cfe1 100644 --- a/engines/director/lingo/lingo-builtins.cpp +++ b/engines/director/lingo/lingo-builtins.cpp @@ -20,7 +20,7 @@ * */ -#include "engines/director/lingo/lingo.h" +#include "director/lingo/lingo.h" namespace Director { @@ -32,23 +32,29 @@ static struct BuiltinProto { bool parens; } builtins[] = { // Math - { "abs", Lingo::b_abs, 1, 1, true }, // D2 - { "atan", Lingo::b_atan, 1, 1, true }, // D4 - { "cos", Lingo::b_cos, 1, 1, true }, // D4 - { "exp", Lingo::b_exp, 1, 1, true }, // D4 - { "float", Lingo::b_float, 1, 1, true }, // D4 - { "integer",Lingo::b_integer, 1, 1, true }, - { "log", Lingo::b_log, 1, 1, true }, // D4 - { "pi", Lingo::b_pi, 0, 0, true }, // D4 - { "power", Lingo::b_power, 2, 2, true }, // D4 - { "random", Lingo::b_random, 1, 1, true }, // D2 - { "sin", Lingo::b_sin, 1, 1, true }, - { "sqrt", Lingo::b_sqrt, 1, 1, true }, // D2 - { "tan", Lingo::b_tan, 1, 1, true }, // D4 + { "abs", Lingo::b_abs, 1, 1, true }, // D2 + { "atan", Lingo::b_atan, 1, 1, true }, // D4 + { "cos", Lingo::b_cos, 1, 1, true }, // D4 + { "exp", Lingo::b_exp, 1, 1, true }, // D4 + { "float", Lingo::b_float, 1, 1, true }, // D4 + { "integer", Lingo::b_integer, 1, 1, true }, + { "integerp", Lingo::b_integerp, 1, 1, true }, + { "log", Lingo::b_log, 1, 1, true }, // D4 + { "pi", Lingo::b_pi, 0, 0, true }, // D4 + { "power", Lingo::b_power, 2, 2, true }, // D4 + { "random", Lingo::b_random, 1, 1, true }, // D2 + { "sin", Lingo::b_sin, 1, 1, true }, + { "sqrt", Lingo::b_sqrt, 1, 1, true }, // D2 + { "tan", Lingo::b_tan, 1, 1, true }, // D4 // String - { "chars", Lingo::b_chars, 3, 3, true }, // D2 - { "length", Lingo::b_length, 1, 1, true }, // D2 - { "string", Lingo::b_string, 1, 1, true }, // D2 + { "chars", Lingo::b_chars, 3, 3, true }, // D2 + { "charToNum", Lingo::b_charToNum, 1, 1, true }, // D2 + { "length", Lingo::b_length, 1, 1, true }, // D2 + { "numToChar", Lingo::b_numToChar, 1, 1, true }, // D2 + { "offset", Lingo::b_offset, 2, 2, true }, // D2 + { "string", Lingo::b_string, 1, 1, true }, // D2 + { "stringp", Lingo::b_stringp, 1, 1, true }, // D2 + { "value", Lingo::b_value, 1, 1, true }, // D2 // Files { "closeDA", Lingo::b_closeDA, 0, 0, false }, // D2 { "closeResFile", Lingo::b_closeResFile, 0, 1, false }, // D2 @@ -84,18 +90,25 @@ static struct BuiltinProto { { "ilk", Lingo::b_ilk, 1, 2, true }, // D4 // put // D2 // set // D2 + { "objectp", Lingo::b_objectp, 1, 1, true }, { "showGlobals", Lingo::b_showGlobals, 0, 0, false }, // D2 { "showLocals", Lingo::b_showLocals, 0, 0, false }, // D2 + { "symbolp", Lingo::b_symbolp, 1, 1, true }, // D2 // Score + { "constrainH", Lingo::b_constrainH, 2, 2, true }, // D2 + { "constrainV", Lingo::b_constrainV, 2, 2, true }, // D2 { "editableText", Lingo::b_editableText, 0, 0, false }, // D2 // go // D2 { "installMenu", Lingo::b_installMenu, 1, 1, false }, // D2 + { "label", Lingo::b_label, 1, 1, true }, // D2 + { "marker", Lingo::b_marker, 1, 1, true }, // D2 { "moveableSprite", Lingo::b_moveableSprite,0, 0, false }, // D2 { "puppetPalette", Lingo::b_puppetPalette, -1,0, false }, // D2 { "puppetSound", Lingo::b_puppetSound, -1,0, false }, // D2 { "puppetSprite", Lingo::b_puppetSprite, -1,0, false }, // D2 { "puppetTempo", Lingo::b_puppetTempo, 1, 1, false }, // D2 { "puppetTransition",Lingo::b_puppetTransition,-1,0, false },// D2 + { "rollOver", Lingo::b_rollOver, 1, 1, true }, // D2 { "spriteBox", Lingo::b_spriteBox, -1,0, false }, // D2 { "updateStage", Lingo::b_updateStage, 0, 0, false }, // D2 { "zoomBox", Lingo::b_zoomBox, -1,0, false }, // D2 @@ -104,7 +117,17 @@ static struct BuiltinProto { // Sound { "beep", Lingo::b_beep, 0, 1, false }, // D2 { "mci", Lingo::b_mci, 1, 1, false }, - { "mciwait", Lingo::b_mciwait, 1, 1, false }, + { "mciwait", Lingo::b_mciwait, 1, 1, false }, + // Constants + { "backspace", Lingo::b_backspace, 0, 0, false }, // D2 + { "empty", Lingo::b_empty, 0, 0, false }, // D2 + { "enter", Lingo::b_enter, 0, 0, false }, // D2 + { "false", Lingo::b_false, 0, 0, false }, // D2 + { "quote", Lingo::b_quote, 0, 0, false }, // D2 + { "return", Lingo::b_return, 0, 0, false }, // D2 + { "tab", Lingo::b_tab, 0, 0, false }, // D2 + { "true", Lingo::b_true, 0, 0, false }, // D2 + { 0, 0, 0, 0, false } }; @@ -121,6 +144,8 @@ void Lingo::initBuiltIns() { sym->u.bltin = blt->func; _handlers[blt->name] = sym; + + _functions[(void *)sym->u.s] = new FuncDesc(blt->name, ""); } } @@ -158,6 +183,14 @@ void Lingo::dropStack(int nargs) { pop(); } +void Lingo::drop(int num) { + if (num > _stack.size() - 1) { + warning("Incorrect number of elements to drop from stack: %d > %d", num, _stack.size() - 1); + return; + } + _stack.remove_at(_stack.size() - 1 - num); +} + /////////////////// // Math @@ -207,6 +240,14 @@ void Lingo::b_integer(int nargs) { g_lingo->push(d); } +void Lingo::b_integerp(int nargs) { + Datum d = g_lingo->pop(); + int res = (d.type == INT) ? 1 : 0; + d.toInt(); + d.u.i = res; + g_lingo->push(d); +} + void Lingo::b_log(int nargs) { Datum d = g_lingo->pop(); d.toFloat(); @@ -290,6 +331,20 @@ void Lingo::b_chars(int nargs) { g_lingo->push(s); } +void Lingo::b_charToNum(int nargs) { + Datum d = g_lingo->pop(); + + if (d.type != STRING) + error("Incorrect type for 'charToNum' function: %s", d.type2str()); + + byte chr = d.u.s->c_str()[0]; + delete d.u.s; + + d.u.i = chr; + d.type = INT; + g_lingo->push(d); +} + void Lingo::b_length(int nargs) { Datum d = g_lingo->pop(); @@ -304,12 +359,48 @@ void Lingo::b_length(int nargs) { g_lingo->push(d); } +void Lingo::b_numToChar(int nargs) { + Datum d = g_lingo->pop(); + + d.toInt(); + + g_lingo->push(Datum((char)d.u.i)); +} + +void Lingo::b_offset(int nargs) { + Datum target = g_lingo->pop(); + Datum source = g_lingo->pop(); + + target.toString(); + source.toString(); + + warning("STUB: b_offset()"); + + g_lingo->push(Datum(0)); +} + void Lingo::b_string(int nargs) { Datum d = g_lingo->pop(); d.toString(); g_lingo->push(d); } +void Lingo::b_stringp(int nargs) { + Datum d = g_lingo->pop(); + int res = (d.type == STRING) ? 1 : 0; + d.toInt(); + d.u.i = res; + g_lingo->push(d); +} + +void Lingo::b_value(int nargs) { + Datum d = g_lingo->pop(); + d.toInt(); + warning("STUB: b_value()"); + g_lingo->push(d); +} + + /////////////////// // Files /////////////////// @@ -462,7 +553,7 @@ void Lingo::b_alert(int nargs) { d.toString(); - warning("STUB: b_alert"); + warning("STUB: b_alert(%s)", d.u.s->c_str()); delete d.u.s; } @@ -473,6 +564,14 @@ void Lingo::b_cursor(int nargs) { warning("STUB: b_cursor(%d)", d.u.i); } +void Lingo::b_objectp(int nargs) { + Datum d = g_lingo->pop(); + int res = (d.type == OBJECT) ? 1 : 0; + d.toInt(); + d.u.i = res; + g_lingo->push(d); +} + void Lingo::b_showGlobals(int nargs) { warning("STUB: b_showGlobals"); } @@ -481,13 +580,40 @@ void Lingo::b_showLocals(int nargs) { warning("STUB: b_showLocals"); } +void Lingo::b_symbolp(int nargs) { + Datum d = g_lingo->pop(); + int res = (d.type == SYMBOL) ? 1 : 0; + d.toInt(); + d.u.i = res; + g_lingo->push(d); +} /////////////////// // Score /////////////////// -void Lingo::b_updateStage(int nargs) { - warning("STUB: b_updateStage"); +void Lingo::b_constrainH(int nargs) { + Datum num = g_lingo->pop(); + Datum sprite = g_lingo->pop(); + + num.toInt(); + sprite.toInt(); + + warning("STUB: b_constrainH(%d, %d)", sprite.u.i, num.u.i); + + g_lingo->push(Datum(0)); +} + +void Lingo::b_constrainV(int nargs) { + Datum num = g_lingo->pop(); + Datum sprite = g_lingo->pop(); + + num.toInt(); + sprite.toInt(); + + warning("STUB: b_constrainV(%d, %d)", sprite.u.i, num.u.i); + + g_lingo->push(Datum(0)); } void Lingo::b_editableText(int nargs) { @@ -499,6 +625,22 @@ void Lingo::b_installMenu(int nargs) { warning("STUB: b_installMenu(%d)", d.u.i); } +void Lingo::b_label(int nargs) { + Datum d = g_lingo->pop(); + d.toInt(); + warning("STUB: b_label(%d)", d.u.i); + + g_lingo->push(Datum(0)); +} + +void Lingo::b_marker(int nargs) { + Datum d = g_lingo->pop(); + d.toInt(); + warning("STUB: b_marker(%d)", d.u.i); + + g_lingo->push(Datum(0)); +} + void Lingo::b_moveableSprite(int nargs) { Datum d = g_lingo->pop(); warning("STUB: b_moveableSprite(%d)", d.u.i); @@ -513,6 +655,8 @@ void Lingo::b_puppetPalette(int nargs) { } void Lingo::b_puppetSound(int nargs) { + g_lingo->convertVOIDtoString(0, nargs); + g_lingo->printStubWithArglist("b_puppetSound", nargs); g_lingo->dropStack(nargs); @@ -535,6 +679,13 @@ void Lingo::b_puppetTransition(int nargs) { g_lingo->dropStack(nargs); } +void Lingo::b_rollOver(int nargs) { + Datum d = g_lingo->pop(); + warning("STUB: b_puppetTempo(%d)", d.u.i); + + g_lingo->push(Datum(0)); +} + void Lingo::b_spriteBox(int nargs) { g_lingo->printStubWithArglist("b_spriteBox", nargs); @@ -547,6 +698,10 @@ void Lingo::b_zoomBox(int nargs) { g_lingo->dropStack(nargs); } +void Lingo::b_updateStage(int nargs) { + warning("STUB: b_updateStage"); +} + /////////////////// @@ -594,5 +749,75 @@ void Lingo::b_mciwait(int nargs) { g_lingo->func_mciwait(*d.u.s); } +/////////////////// +// Constants +/////////////////// +void Lingo::b_backspace(int nargs) { + g_lingo->push(Datum(new Common::String("\b"))); +} + +void Lingo::b_empty(int nargs) { + g_lingo->push(Datum(new Common::String(""))); +} + +void Lingo::b_enter(int nargs) { + g_lingo->push(Datum(new Common::String("\n"))); +} + +void Lingo::b_false(int nargs) { + g_lingo->push(Datum(0)); +} + +void Lingo::b_quote(int nargs) { + g_lingo->push(Datum(new Common::String("\""))); +} + +void Lingo::b_return(int nargs) { + g_lingo->push(Datum(new Common::String("\r"))); +} + +void Lingo::b_tab(int nargs) { + g_lingo->push(Datum(new Common::String("\t"))); +} + +void Lingo::b_true(int nargs) { + g_lingo->push(Datum(1)); +} + +/////////////////// +// Factory +/////////////////// +void Lingo::b_factory(int nargs) { + // This is intentionally empty +} + +void Lingo::factoryCall(Common::String &name, int nargs) { + Common::String s("factoryCall: "); + + s += name; + + convertVOIDtoString(0, nargs); + + printStubWithArglist(s.c_str(), nargs); + + Datum method = _stack[_stack.size() - nargs + 0]; + + drop(nargs - 1); + + s = name + "-" + *method.u.s; + + debugC(3, kDebugLingoExec, "Stack size before call: %d, nargs: %d", _stack.size(), nargs); + call(s, nargs); + debugC(3, kDebugLingoExec, "Stack size after call: %d", _stack.size()); + + if (!method.u.s->compareToIgnoreCase("mNew")) { + Datum d; + + d.type = OBJECT; + d.u.s = new Common::String(name); + + g_lingo->push(d); + } +} } // End of namespace Director diff --git a/engines/director/lingo/lingo-code.cpp b/engines/director/lingo/lingo-code.cpp index fe1c62f3df..66f16536f8 100644 --- a/engines/director/lingo/lingo-code.cpp +++ b/engines/director/lingo/lingo-code.cpp @@ -43,14 +43,77 @@ // ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF // THIS SOFTWARE. -#include "engines/director/lingo/lingo.h" -#include "common/file.h" -#include "audio/decoders/wave.h" - +#include "director/lingo/lingo.h" #include "director/lingo/lingo-gr.h" namespace Director { +static struct FuncDescr { + const inst func; + const char *name; + const char *args; +} funcDescr[] = { + { 0, "STOP", "" }, + { Lingo::c_xpop, "c_xpop", "" }, + { Lingo::c_printtop, "c_printtop", "" }, + { Lingo::c_constpush, "c_constpush", "i" }, + { Lingo::c_voidpush, "c_voidpush", "" }, + { Lingo::c_fconstpush, "c_fconstpush", "f" }, + { Lingo::c_stringpush, "c_stringpush", "s" }, + { Lingo::c_varpush, "c_varpush", "s" }, + { Lingo::c_assign, "c_assign", "" }, + { Lingo::c_eval, "c_eval", "s" }, + { Lingo::c_theentitypush,"c_theentitypush","ii" }, // entity, field + { Lingo::c_theentityassign,"c_theentityassign","ii" }, + { Lingo::c_swap, "c_swap", "" }, + { Lingo::c_add, "c_add", "" }, + { Lingo::c_sub, "c_sub", "" }, + { Lingo::c_mul, "c_mul", "" }, + { Lingo::c_div, "c_div", "" }, + { Lingo::c_mod, "c_mod", "" }, + { Lingo::c_negate, "c_negate", "" }, + { Lingo::c_ampersand, "c_ampersand", "" }, + { Lingo::c_concat, "c_concat", "" }, + { Lingo::c_contains, "c_contains", "" }, + { Lingo::c_starts, "c_starts", "" }, + { Lingo::c_intersects, "c_intersects", "" }, + { Lingo::c_within, "c_within", "" }, + { Lingo::c_and, "c_and", "" }, + { Lingo::c_or, "c_or", "" }, + { Lingo::c_not, "c_not", "" }, + { Lingo::c_eq, "c_eq", "" }, + { Lingo::c_neq, "c_neq", "" }, + { Lingo::c_gt, "c_gt", "" }, + { Lingo::c_lt, "c_lt", "" }, + { Lingo::c_ge, "c_ge", "" }, + { Lingo::c_le, "c_le", "" }, + { Lingo::c_repeatwhilecode,"c_repeatwhilecode","oo" }, + { Lingo::c_repeatwithcode,"c_repeatwithcode","ooooos" }, + { Lingo::c_exitRepeat, "c_exitRepeat", "" }, + { Lingo::c_ifcode, "c_ifcode", "oooi" }, + { Lingo::c_whencode, "c_whencode", "os" }, + { Lingo::c_goto, "c_goto", "" }, + { Lingo::c_gotoloop, "c_gotoloop", "" }, + { Lingo::c_gotonext, "c_gotonext", "" }, + { Lingo::c_gotoprevious,"c_gotoprevious","" }, + { Lingo::c_play, "c_play", "" }, + { Lingo::c_playdone, "c_playdone", "" }, + { Lingo::c_call, "c_call", "si" }, + { Lingo::c_procret, "c_procret", "" }, + { Lingo::c_global, "c_global", "s" }, + { Lingo::c_instance, "c_instance", "s" }, + { Lingo::c_open, "c_open", "" }, + { 0, 0, 0 } +}; + +void Lingo::initFuncs() { + Symbol sym; + for (FuncDescr *fnc = funcDescr; fnc->name; fnc++) { + sym.u.func = fnc->func; + _functions[(void *)sym.u.s] = new FuncDesc(fnc->name, fnc->args); + } +} + void Lingo::push(Datum d) { _stack.push_back(d); } @@ -108,6 +171,9 @@ void Lingo::c_printtop(void) { case SYMBOL: warning("%s", d.type2str(true)); break; + case OBJECT: + warning("#%s", d.u.s->c_str()); + break; default: warning("--unknown--"); } @@ -140,19 +206,25 @@ void Lingo::c_fconstpush() { } void Lingo::c_stringpush() { - Datum d; char *s = (char *)&(*g_lingo->_currentScript)[g_lingo->_pc]; g_lingo->_pc += g_lingo->calcStringAlignment(s); - d.u.s = new Common::String(s); - d.type = STRING; - g_lingo->push(d); + g_lingo->push(Datum(new Common::String(s))); } void Lingo::c_varpush() { char *name = (char *)&(*g_lingo->_currentScript)[g_lingo->_pc]; Datum d; + g_lingo->_pc += g_lingo->calcStringAlignment(name); + + if (g_lingo->_handlers.contains(name)) { + d.type = HANDLER; + d.u.s = new Common::String(name); + g_lingo->push(d); + return; + } + d.u.sym = g_lingo->lookupVar(name); if (d.u.sym->type == CASTREF) { d.type = INT; @@ -165,8 +237,6 @@ void Lingo::c_varpush() { d.type = VAR; } - g_lingo->_pc += g_lingo->calcStringAlignment(name); - g_lingo->push(d); } @@ -204,8 +274,11 @@ void Lingo::c_assign() { delete d2.u.arr; } else if (d2.type == SYMBOL) { d1.u.sym->u.i = d2.u.i; + } else if (d2.type == OBJECT) { + d1.u.sym->u.s = d2.u.s; } else { warning("c_assign: unhandled type: %s", d2.type2str()); + d1.u.sym->u.s = d2.u.s; } d1.u.sym->type = d2.type; @@ -232,6 +305,12 @@ void Lingo::c_eval() { Datum d; d = g_lingo->pop(); + if (d.type == HANDLER) { + g_lingo->call(*d.u.s, 0); + delete d.u.s; + return; + } + if (d.type != VAR) { // It could be cast ref g_lingo->push(d); return; @@ -253,7 +332,7 @@ void Lingo::c_eval() { else if (d.u.sym->type == SYMBOL) d.u.i = d.u.sym->u.i; else if (d.u.sym->type == VOID) - d.u.s = new Common::String(*d.u.sym->name); + d.u.s = new Common::String(d.u.sym->name); else warning("c_eval: unhandled type: %s", d.type2str()); @@ -346,6 +425,21 @@ void Lingo::c_div() { g_lingo->push(d1); } +void Lingo::c_mod() { + Datum d2 = g_lingo->pop(); + d2.toInt(); + + if (d2.u.i == 0) + error("division by zero"); + + Datum d1 = g_lingo->pop(); + d1.toInt(); + + d1.u.i %= d2.u.i; + + g_lingo->push(d1); +} + void Lingo::c_negate() { Datum d = g_lingo->pop(); @@ -578,6 +672,11 @@ void Lingo::c_repeatwhilecode(void) { if (g_lingo->_returning) break; + if (g_lingo->_exitRepeat) { + g_lingo->_exitRepeat = false; + break; + } + g_lingo->execute(savepc + 2); /* condition */ d = g_lingo->pop(); d.toInt(); @@ -614,6 +713,11 @@ void Lingo::c_repeatwithcode(void) { if (g_lingo->_returning) break; + if (g_lingo->_exitRepeat) { + g_lingo->_exitRepeat = false; + break; + } + counter->u.i += inc; g_lingo->execute(finish); /* condition */ d = g_lingo->pop(); @@ -627,6 +731,10 @@ void Lingo::c_repeatwithcode(void) { g_lingo->_pc = end; /* next stmt */ } +void Lingo::c_exitRepeat(void) { + g_lingo->_exitRepeat = true; +} + void Lingo::c_ifcode() { Datum d; int savepc = g_lingo->_pc; /* then part */ @@ -636,24 +744,48 @@ void Lingo::c_ifcode() { int end = READ_UINT32(&(*g_lingo->_currentScript)[savepc + 2]); int skipEnd = READ_UINT32(&(*g_lingo->_currentScript)[savepc + 3]); - debug(8, "executing cond (have to %s end)", skipEnd ? "skip" : "execute"); + debugC(8, kDebugLingoExec, "executing cond (have to %s end)", skipEnd ? "skip" : "execute"); g_lingo->execute(savepc + 4); /* condition */ d = g_lingo->pop(); if (d.toInt()) { - debug(8, "executing then"); + debugC(8, kDebugLingoExec, "executing then"); g_lingo->execute(then); } else if (elsep) { /* else part? */ - debug(8, "executing else"); + debugC(8, kDebugLingoExec, "executing else"); g_lingo->execute(elsep); } if (!g_lingo->_returning && !skipEnd) { g_lingo->_pc = end; /* next stmt */ - debug(8, "executing end"); - } else - debug(8, "Skipped end"); + debugC(8, kDebugLingoExec, "executing end"); + } else { + debugC(8, kDebugLingoExec, "Skipped end"); + } +} + +void Lingo::c_whencode() { + Datum d; + int start = g_lingo->_pc; + int end = READ_UINT32(&(*g_lingo->_currentScript)[start]); + Common::String eventname((char *)&(*g_lingo->_currentScript)[start + 1]); + + start += g_lingo->calcStringAlignment(eventname.c_str()) + 1; + + debugC(3, kDebugLingoExec, "c_whencode([%5d][%5d], %s)", start, end, eventname.c_str()); + + g_lingo->define(eventname, start, 0, NULL, end); + + if (debugChannelSet(3, kDebugLingoExec)) { + int pc = start; + while (pc <= end) { + Common::String instr = g_lingo->decodeInstruction(pc, &pc); + debugC(3, kDebugLingoExec, "[%5d] %s", pc, instr.c_str()); + } + } + + g_lingo->_pc = end; } //************************ @@ -718,11 +850,24 @@ void Lingo::c_call() { g_lingo->_pc += g_lingo->calcStringAlignment(name.c_str()); int nargs = READ_UINT32(&(*g_lingo->_currentScript)[g_lingo->_pc++]); + + g_lingo->call(name, nargs); +} + +void Lingo::call(Common::String name, int nargs) { bool drop = false; Symbol *sym; if (!g_lingo->_handlers.contains(name)) { + Symbol *s = g_lingo->lookupVar(name.c_str(), false); + if (s && s->type == OBJECT) { + debugC(3, kDebugLingoExec, "Dereferencing object reference: %s to %s", name.c_str(), s->u.s->c_str()); + name = *s->u.s; + } + } + + if (!g_lingo->_handlers.contains(name)) { warning("Call to undefined handler '%s'. Dropping %d stack items", name.c_str(), nargs); drop = true; } else { @@ -755,7 +900,10 @@ void Lingo::c_call() { } if (sym->type == BLTIN) { - (*sym->u.bltin)(nargs); + if (sym->u.bltin == b_factory) + g_lingo->factoryCall(name, nargs); + else + (*sym->u.bltin)(nargs); return; } @@ -768,6 +916,7 @@ void Lingo::c_call() { g_lingo->push(d); } + debugC(5, kDebugLingoExec, "Pushing frame %d", g_lingo->_callstack.size() + 1); CFrame *fp = new CFrame; fp->sp = sym; @@ -793,7 +942,10 @@ void Lingo::c_procret() { return; } + debugC(5, kDebugLingoExec, "Popping frame %d", g_lingo->_callstack.size() + 1); + CFrame *fp = g_lingo->_callstack.back(); + g_lingo->_callstack.pop_back(); g_lingo->_currentScript = fp->retscript; g_lingo->_pc = fp->retpc; @@ -822,6 +974,14 @@ void Lingo::c_global() { g_lingo->_pc += g_lingo->calcStringAlignment(name.c_str()); } +void Lingo::c_instance() { + Common::String name((char *)&(*g_lingo->_currentScript)[g_lingo->_pc]); + + warning("STUB: c_instance(%s)", name.c_str()); + + g_lingo->_pc += g_lingo->calcStringAlignment(name.c_str()); +} + void Lingo::c_open() { Datum d2 = g_lingo->pop(); Datum d1 = g_lingo->pop(); diff --git a/engines/director/lingo/lingo-codegen.cpp b/engines/director/lingo/lingo-codegen.cpp index 4284fa7452..440efb5b44 100644 --- a/engines/director/lingo/lingo-codegen.cpp +++ b/engines/director/lingo/lingo-codegen.cpp @@ -43,7 +43,7 @@ // ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF // THIS SOFTWARE. -#include "engines/director/lingo/lingo.h" +#include "director/lingo/lingo.h" #include "common/file.h" #include "audio/decoders/wave.h" @@ -53,17 +53,85 @@ namespace Director { void Lingo::execute(int pc) { for(_pc = pc; (*_currentScript)[_pc] != STOP && !_returning;) { + Common::String instr = decodeInstruction(_pc); - for (uint i = 0; i < _stack.size(); i++) { - debugN(5, "%d ", _stack[i].u.i); - } - debug(5, "%s", ""); + if (debugChannelSet(5, kDebugLingoExec)) + printStack("Stack before: "); + + debugC(1, kDebugLingoExec, "[%3d]: %s", _pc, instr.c_str()); _pc++; (*((*_currentScript)[_pc - 1]))(); + + if (debugChannelSet(5, kDebugLingoExec)) + printStack("Stack after: "); } } +void Lingo::printStack(const char *s) { + Common::String stack(s); + + for (uint i = 0; i < _stack.size(); i++) { + Datum d = _stack[i]; + d.toString(); + stack += Common::String::format("<%s> ", d.u.s->c_str()); + } + debugC(5, kDebugLingoExec, "%s", stack.c_str()); +} + +Common::String Lingo::decodeInstruction(int pc, int *newPc) { + Symbol sym; + Common::String res; + + sym.u.func = (*_currentScript)[pc++]; + if (_functions.contains((void *)sym.u.s)) { + res = _functions[(void *)sym.u.s]->name; + const char *pars = _functions[(void *)sym.u.s]->proto; + inst i; + + while (*pars) { + switch (*pars++) { + case 'i': + { + i = (*_currentScript)[pc++]; + int v = READ_UINT32(&i); + + res += Common::String::format(" %d", v); + break; + } + case 'o': + { + i = (*_currentScript)[pc++]; + int v = READ_UINT32(&i); + + res += Common::String::format(" [%5d]", v); + break; + } + case 's': + { + char *s = (char *)&(*_currentScript)[pc]; + pc += calcStringAlignment(s); + + res += Common::String::format(" \"%s\"", s); + break; + } + default: + warning("decodeInstruction: Unknown parameter type: %c", pars[-1]); + } + + if (*pars) + res += ','; + } + } else { + res = "<unknown>"; + } + + if (newPc) + *newPc = pc; + + return res; +} + Symbol *Lingo::lookupVar(const char *name, bool create, bool putInGlobalList) { Symbol *sym; @@ -123,13 +191,13 @@ void Lingo::cleanLocalVars() { delete g_lingo->_localvars; } -void Lingo::define(Common::String &name, int start, int nargs, Common::String *prefix) { +void Lingo::define(Common::String &name, int start, int nargs, Common::String *prefix, int end) { Symbol *sym; if (prefix) name = *prefix + "-" + name; - debug(3, "define(\"%s\", %d, %d, %d)", name.c_str(), start, _currentScript->size() - 1, nargs); + debugC(3, kDebugLingoCompile, "define(\"%s\", %d, %d, %d)", name.c_str(), start, _currentScript->size() - 1, nargs); if (!_handlers.contains(name)) { // Create variable if it was not defined sym = new Symbol; @@ -146,7 +214,10 @@ void Lingo::define(Common::String &name, int start, int nargs, Common::String *p delete sym->u.defn; } - sym->u.defn = new ScriptData(&(*_currentScript)[start], _currentScript->size() - start + 1); + if (end == -1) + end = _currentScript->size(); + + sym->u.defn = new ScriptData(&(*_currentScript)[start], end - start + 1); sym->nargs = nargs; } @@ -221,7 +292,7 @@ int Lingo::codeFunc(Common::String *s, int numpar) { if (s->equalsIgnoreCase("me")) { if (!g_lingo->_currentFactory.empty()) { g_lingo->codeString(g_lingo->_currentFactory.c_str()); - debug(2, "Repaced 'me' with %s", g_lingo->_currentFactory.c_str()); + debugC(2, kDebugLingoCompile, "Replaced 'me' with %s", g_lingo->_currentFactory.c_str()); } else { warning("'me' out of factory method"); g_lingo->codeString(s->c_str()); @@ -270,6 +341,18 @@ void Lingo::processIf(int elselabel, int endlabel) { void Lingo::codeFactory(Common::String &name) { _currentFactory = name; + + Symbol *sym = new Symbol; + + sym->name = (char *)calloc(name.size() + 1, 1); + Common::strlcpy(sym->name, name.c_str(), name.size()); + sym->type = BLTIN; + sym->nargs = -1; + sym->maxArgs = 0; + sym->parens = true; + sym->u.bltin = g_lingo->b_factory; + + _handlers[name] = sym; } } diff --git a/engines/director/lingo/lingo-funcs.cpp b/engines/director/lingo/lingo-funcs.cpp index da2cd5f358..e22044c1e9 100644 --- a/engines/director/lingo/lingo-funcs.cpp +++ b/engines/director/lingo/lingo-funcs.cpp @@ -20,34 +20,12 @@ * */ -// Heavily inspired by hoc -// Copyright (C) AT&T 1995 -// All Rights Reserved -// -// Permission to use, copy, modify, and distribute this software and -// its documentation for any purpose and without fee is hereby -// granted, provided that the above copyright notice appear in all -// copies and that both that the copyright notice and this -// permission notice and warranty disclaimer appear in supporting -// documentation, and that the name of AT&T or any of its entities -// not be used in advertising or publicity pertaining to -// distribution of the software without specific, written prior -// permission. -// -// AT&T DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, -// INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. -// IN NO EVENT SHALL AT&T OR ANY OF ITS ENTITIES BE LIABLE FOR ANY -// SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER -// IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, -// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -// THIS SOFTWARE. - -#include "engines/director/lingo/lingo.h" +#include "director/lingo/lingo.h" #include "common/file.h" #include "audio/decoders/wave.h" #include "common/util.h" #include "director/lingo/lingo-gr.h" +#include "director/sound.h" namespace Director { diff --git a/engines/director/lingo/lingo-gr.cpp b/engines/director/lingo/lingo-gr.cpp index 1758d5c5d6..f92e030c88 100644 --- a/engines/director/lingo/lingo-gr.cpp +++ b/engines/director/lingo/lingo-gr.cpp @@ -74,64 +74,67 @@ RECT = 263, ARRAY = 264, SYMBOL = 265, - INT = 266, - THEENTITY = 267, - THEENTITYWITHID = 268, - FLOAT = 269, - BLTIN = 270, - BLTINNOARGS = 271, - BLTINNOARGSORONE = 272, - BLTINONEARG = 273, - BLTINARGLIST = 274, - ID = 275, - STRING = 276, - HANDLER = 277, - tDOWN = 278, - tELSE = 279, - tNLELSIF = 280, - tEND = 281, - tEXIT = 282, - tFRAME = 283, - tGLOBAL = 284, - tGO = 285, - tIF = 286, - tINTO = 287, - tLOOP = 288, - tMACRO = 289, - tMOVIE = 290, - tNEXT = 291, - tOF = 292, - tPREVIOUS = 293, - tPUT = 294, - tREPEAT = 295, - tSET = 296, - tTHEN = 297, - tTO = 298, - tWHEN = 299, - tWITH = 300, - tWHILE = 301, - tNLELSE = 302, - tFACTORY = 303, - tMETHOD = 304, - tOPEN = 305, - tPLAY = 306, - tDONE = 307, - tPLAYACCEL = 308, - tGE = 309, - tLE = 310, - tGT = 311, - tLT = 312, - tEQ = 313, - tNEQ = 314, - tAND = 315, - tOR = 316, - tNOT = 317, - tCONCAT = 318, - tCONTAINS = 319, - tSTARTS = 320, - tSPRITE = 321, - tINTERSECTS = 322, - tWITHIN = 323 + OBJECT = 266, + INT = 267, + THEENTITY = 268, + THEENTITYWITHID = 269, + FLOAT = 270, + BLTIN = 271, + BLTINNOARGS = 272, + BLTINNOARGSORONE = 273, + BLTINONEARG = 274, + BLTINARGLIST = 275, + ID = 276, + STRING = 277, + HANDLER = 278, + tDOWN = 279, + tELSE = 280, + tNLELSIF = 281, + tEND = 282, + tEXIT = 283, + tFRAME = 284, + tGLOBAL = 285, + tGO = 286, + tIF = 287, + tINTO = 288, + tLOOP = 289, + tMACRO = 290, + tMOVIE = 291, + tNEXT = 292, + tOF = 293, + tPREVIOUS = 294, + tPUT = 295, + tREPEAT = 296, + tSET = 297, + tTHEN = 298, + tTO = 299, + tWHEN = 300, + tWITH = 301, + tWHILE = 302, + tNLELSE = 303, + tFACTORY = 304, + tMETHOD = 305, + tOPEN = 306, + tPLAY = 307, + tDONE = 308, + tPLAYACCEL = 309, + tINSTANCE = 310, + tGE = 311, + tLE = 312, + tGT = 313, + tLT = 314, + tEQ = 315, + tNEQ = 316, + tAND = 317, + tOR = 318, + tNOT = 319, + tMOD = 320, + tCONCAT = 321, + tCONTAINS = 322, + tSTARTS = 323, + tSPRITE = 324, + tINTERSECTS = 325, + tWITHIN = 326 }; #endif /* Tokens. */ @@ -143,64 +146,67 @@ #define RECT 263 #define ARRAY 264 #define SYMBOL 265 -#define INT 266 -#define THEENTITY 267 -#define THEENTITYWITHID 268 -#define FLOAT 269 -#define BLTIN 270 -#define BLTINNOARGS 271 -#define BLTINNOARGSORONE 272 -#define BLTINONEARG 273 -#define BLTINARGLIST 274 -#define ID 275 -#define STRING 276 -#define HANDLER 277 -#define tDOWN 278 -#define tELSE 279 -#define tNLELSIF 280 -#define tEND 281 -#define tEXIT 282 -#define tFRAME 283 -#define tGLOBAL 284 -#define tGO 285 -#define tIF 286 -#define tINTO 287 -#define tLOOP 288 -#define tMACRO 289 -#define tMOVIE 290 -#define tNEXT 291 -#define tOF 292 -#define tPREVIOUS 293 -#define tPUT 294 -#define tREPEAT 295 -#define tSET 296 -#define tTHEN 297 -#define tTO 298 -#define tWHEN 299 -#define tWITH 300 -#define tWHILE 301 -#define tNLELSE 302 -#define tFACTORY 303 -#define tMETHOD 304 -#define tOPEN 305 -#define tPLAY 306 -#define tDONE 307 -#define tPLAYACCEL 308 -#define tGE 309 -#define tLE 310 -#define tGT 311 -#define tLT 312 -#define tEQ 313 -#define tNEQ 314 -#define tAND 315 -#define tOR 316 -#define tNOT 317 -#define tCONCAT 318 -#define tCONTAINS 319 -#define tSTARTS 320 -#define tSPRITE 321 -#define tINTERSECTS 322 -#define tWITHIN 323 +#define OBJECT 266 +#define INT 267 +#define THEENTITY 268 +#define THEENTITYWITHID 269 +#define FLOAT 270 +#define BLTIN 271 +#define BLTINNOARGS 272 +#define BLTINNOARGSORONE 273 +#define BLTINONEARG 274 +#define BLTINARGLIST 275 +#define ID 276 +#define STRING 277 +#define HANDLER 278 +#define tDOWN 279 +#define tELSE 280 +#define tNLELSIF 281 +#define tEND 282 +#define tEXIT 283 +#define tFRAME 284 +#define tGLOBAL 285 +#define tGO 286 +#define tIF 287 +#define tINTO 288 +#define tLOOP 289 +#define tMACRO 290 +#define tMOVIE 291 +#define tNEXT 292 +#define tOF 293 +#define tPREVIOUS 294 +#define tPUT 295 +#define tREPEAT 296 +#define tSET 297 +#define tTHEN 298 +#define tTO 299 +#define tWHEN 300 +#define tWITH 301 +#define tWHILE 302 +#define tNLELSE 303 +#define tFACTORY 304 +#define tMETHOD 305 +#define tOPEN 306 +#define tPLAY 307 +#define tDONE 308 +#define tPLAYACCEL 309 +#define tINSTANCE 310 +#define tGE 311 +#define tLE 312 +#define tGT 313 +#define tLT 314 +#define tEQ 315 +#define tNEQ 316 +#define tAND 317 +#define tOR 318 +#define tNOT 319 +#define tMOD 320 +#define tCONCAT 321 +#define tCONTAINS 322 +#define tSTARTS 323 +#define tSPRITE 324 +#define tINTERSECTS 325 +#define tWITHIN 326 @@ -219,7 +225,7 @@ extern int yylex(); extern int yyparse(); using namespace Director; -void yyerror(char *s) { +void yyerror(const char *s) { g_lingo->_hadError = true; warning("%s at line %d col %d", s, g_lingo->_linenumber, g_lingo->_colnumber); } @@ -258,7 +264,7 @@ typedef union YYSTYPE Common::Array<double> *arr; } /* Line 193 of yacc.c. */ -#line 262 "engines/director/lingo/lingo-gr.cpp" +#line 268 "engines/director/lingo/lingo-gr.cpp" YYSTYPE; # define yystype YYSTYPE /* obsolescent; will be withdrawn */ # define YYSTYPE_IS_DECLARED 1 @@ -271,7 +277,7 @@ typedef union YYSTYPE /* Line 216 of yacc.c. */ -#line 275 "engines/director/lingo/lingo-gr.cpp" +#line 281 "engines/director/lingo/lingo-gr.cpp" #ifdef short # undef short @@ -484,22 +490,22 @@ union yyalloc #endif /* YYFINAL -- State number of the termination state. */ -#define YYFINAL 87 +#define YYFINAL 92 /* YYLAST -- Last index in YYTABLE. */ -#define YYLAST 921 +#define YYLAST 995 /* YYNTOKENS -- Number of terminals. */ -#define YYNTOKENS 82 +#define YYNTOKENS 85 /* YYNNTS -- Number of nonterminals. */ -#define YYNNTS 35 +#define YYNNTS 37 /* YYNRULES -- Number of rules. */ -#define YYNRULES 124 +#define YYNRULES 130 /* YYNRULES -- Number of states. */ -#define YYNSTATES 259 +#define YYNSTATES 269 /* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ #define YYUNDEFTOK 2 -#define YYMAXUTOK 323 +#define YYMAXUTOK 326 #define YYTRANSLATE(YYX) \ ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) @@ -508,12 +514,12 @@ union yyalloc static const yytype_uint8 yytranslate[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 75, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 79, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 74, 80, 2, - 76, 77, 72, 70, 81, 71, 2, 73, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 78, 73, 2, + 80, 81, 76, 74, 84, 75, 2, 77, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 79, 69, 78, 2, 2, 2, 2, 2, 2, 2, + 83, 72, 82, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -539,7 +545,7 @@ static const yytype_uint8 yytranslate[] = 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, - 65, 66, 67, 68 + 65, 66, 67, 68, 69, 70, 71 }; #if YYDEBUG @@ -549,87 +555,91 @@ static const yytype_uint16 yyprhs[] = { 0, 0, 3, 7, 9, 12, 14, 15, 17, 19, 21, 23, 25, 30, 35, 40, 46, 51, 56, 62, - 64, 66, 68, 70, 79, 91, 104, 109, 118, 130, - 142, 149, 160, 171, 172, 176, 179, 181, 184, 186, - 193, 195, 201, 203, 207, 211, 214, 218, 220, 222, - 223, 224, 225, 228, 231, 233, 235, 237, 239, 244, - 246, 248, 251, 253, 257, 261, 265, 269, 273, 277, - 281, 285, 289, 293, 297, 300, 304, 308, 312, 316, - 319, 322, 326, 331, 336, 339, 341, 343, 345, 348, - 351, 354, 356, 359, 364, 367, 369, 373, 376, 379, - 382, 385, 389, 392, 395, 397, 401, 404, 407, 410, - 414, 417, 418, 427, 430, 431, 440, 441, 443, 447, - 452, 453, 457, 458, 460 + 64, 66, 68, 70, 79, 91, 104, 108, 117, 129, + 141, 148, 159, 170, 171, 175, 178, 180, 183, 185, + 192, 194, 200, 202, 206, 210, 213, 217, 219, 221, + 222, 223, 224, 227, 230, 234, 236, 238, 240, 242, + 247, 249, 251, 254, 256, 260, 264, 268, 272, 276, + 280, 284, 288, 292, 296, 300, 304, 307, 311, 315, + 319, 323, 326, 329, 333, 338, 343, 346, 348, 350, + 353, 355, 358, 361, 364, 367, 369, 372, 377, 380, + 382, 386, 388, 392, 395, 398, 401, 404, 408, 411, + 414, 416, 420, 423, 426, 429, 433, 436, 437, 446, + 449, 450, 459, 460, 462, 466, 471, 472, 476, 477, + 479 }; /* YYRHS -- A `-1'-separated list of the rules' RHS. */ static const yytype_int8 yyrhs[] = { - 83, 0, -1, 83, 84, 85, -1, 85, -1, 1, - 84, -1, 75, -1, -1, 110, -1, 104, -1, 115, - -1, 86, -1, 88, -1, 39, 103, 32, 20, -1, - 41, 20, 69, 103, -1, 41, 12, 69, 103, -1, - 41, 13, 103, 69, 103, -1, 41, 20, 43, 103, - -1, 41, 12, 43, 103, -1, 41, 13, 103, 43, - 103, -1, 103, -1, 104, -1, 87, -1, 89, -1, - 96, 76, 95, 77, 102, 101, 26, 40, -1, 97, - 69, 103, 101, 43, 103, 101, 102, 101, 26, 40, - -1, 97, 69, 103, 101, 23, 43, 103, 101, 102, - 101, 26, 40, -1, 44, 20, 42, 103, -1, 98, - 95, 42, 84, 102, 101, 26, 31, -1, 98, 95, - 42, 84, 102, 101, 47, 102, 101, 26, 31, -1, - 98, 95, 42, 84, 102, 101, 100, 91, 101, 26, - 31, -1, 98, 95, 42, 100, 87, 101, -1, 98, - 95, 42, 100, 87, 101, 47, 100, 87, 101, -1, - 98, 95, 42, 100, 87, 101, 92, 101, 90, 101, - -1, -1, 47, 100, 87, -1, 91, 94, -1, 94, - -1, 92, 93, -1, 93, -1, 99, 95, 42, 100, - 88, 101, -1, 92, -1, 99, 95, 42, 102, 101, - -1, 103, -1, 103, 69, 103, -1, 76, 95, 77, - -1, 40, 46, -1, 40, 45, 20, -1, 31, -1, - 25, -1, -1, -1, -1, 102, 84, -1, 102, 88, - -1, 11, -1, 14, -1, 21, -1, 16, -1, 20, - 76, 116, 77, -1, 20, -1, 12, -1, 13, 103, - -1, 86, -1, 103, 70, 103, -1, 103, 71, 103, - -1, 103, 72, 103, -1, 103, 73, 103, -1, 103, - 78, 103, -1, 103, 79, 103, -1, 103, 59, 103, - -1, 103, 54, 103, -1, 103, 55, 103, -1, 103, - 60, 103, -1, 103, 61, 103, -1, 62, 103, -1, - 103, 80, 103, -1, 103, 63, 103, -1, 103, 64, - 103, -1, 103, 65, 103, -1, 70, 103, -1, 71, - 103, -1, 76, 103, 77, -1, 66, 103, 67, 103, - -1, 66, 103, 68, 103, -1, 39, 103, -1, 106, - -1, 109, -1, 27, -1, 29, 105, -1, 18, 103, - -1, 17, 103, -1, 17, -1, 19, 116, -1, 50, - 103, 45, 103, -1, 50, 103, -1, 20, -1, 105, - 81, 20, -1, 30, 33, -1, 30, 36, -1, 30, - 38, -1, 30, 107, -1, 30, 107, 108, -1, 30, - 108, -1, 28, 103, -1, 103, -1, 37, 35, 103, - -1, 35, 103, -1, 51, 52, -1, 51, 107, -1, - 51, 107, 108, -1, 51, 108, -1, -1, 34, 20, - 111, 100, 113, 84, 114, 102, -1, 48, 20, -1, - -1, 49, 20, 112, 100, 113, 84, 114, 102, -1, - -1, 20, -1, 113, 81, 20, -1, 113, 84, 81, - 20, -1, -1, 20, 100, 116, -1, -1, 103, -1, - 116, 81, 103, -1 + 86, 0, -1, 86, 87, 88, -1, 88, -1, 1, + 87, -1, 79, -1, -1, 115, -1, 108, -1, 120, + -1, 89, -1, 91, -1, 40, 107, 33, 21, -1, + 42, 21, 72, 107, -1, 42, 13, 72, 107, -1, + 42, 14, 107, 72, 107, -1, 42, 21, 44, 107, + -1, 42, 13, 44, 107, -1, 42, 14, 107, 44, + 107, -1, 107, -1, 108, -1, 90, -1, 92, -1, + 99, 80, 98, 81, 105, 104, 27, 41, -1, 100, + 72, 107, 104, 44, 107, 104, 105, 104, 27, 41, + -1, 100, 72, 107, 104, 24, 44, 107, 104, 105, + 104, 27, 41, -1, 106, 107, 104, -1, 101, 98, + 43, 87, 105, 104, 27, 32, -1, 101, 98, 43, + 87, 105, 104, 48, 105, 104, 27, 32, -1, 101, + 98, 43, 87, 105, 104, 103, 94, 104, 27, 32, + -1, 101, 98, 43, 103, 90, 104, -1, 101, 98, + 43, 103, 90, 104, 48, 103, 90, 104, -1, 101, + 98, 43, 103, 90, 104, 95, 104, 93, 104, -1, + -1, 48, 103, 90, -1, 94, 97, -1, 97, -1, + 95, 96, -1, 96, -1, 102, 98, 43, 103, 91, + 104, -1, 95, -1, 102, 98, 43, 105, 104, -1, + 107, -1, 107, 72, 107, -1, 80, 98, 81, -1, + 41, 47, -1, 41, 46, 21, -1, 32, -1, 26, + -1, -1, -1, -1, 105, 87, -1, 105, 91, -1, + 45, 21, 43, -1, 12, -1, 15, -1, 22, -1, + 17, -1, 21, 80, 121, 81, -1, 21, -1, 13, + -1, 14, 107, -1, 89, -1, 107, 74, 107, -1, + 107, 75, 107, -1, 107, 76, 107, -1, 107, 77, + 107, -1, 107, 65, 107, -1, 107, 82, 107, -1, + 107, 83, 107, -1, 107, 61, 107, -1, 107, 56, + 107, -1, 107, 57, 107, -1, 107, 62, 107, -1, + 107, 63, 107, -1, 64, 107, -1, 107, 73, 107, + -1, 107, 66, 107, -1, 107, 67, 107, -1, 107, + 68, 107, -1, 74, 107, -1, 75, 107, -1, 80, + 107, 81, -1, 69, 107, 70, 107, -1, 69, 107, + 71, 107, -1, 40, 107, -1, 111, -1, 114, -1, + 28, 41, -1, 28, -1, 30, 109, -1, 55, 110, + -1, 19, 107, -1, 18, 107, -1, 18, -1, 20, + 121, -1, 51, 107, 46, 107, -1, 51, 107, -1, + 21, -1, 109, 84, 21, -1, 21, -1, 110, 84, + 21, -1, 31, 34, -1, 31, 37, -1, 31, 39, + -1, 31, 112, -1, 31, 112, 113, -1, 31, 113, + -1, 29, 107, -1, 107, -1, 38, 36, 107, -1, + 36, 107, -1, 52, 53, -1, 52, 112, -1, 52, + 112, 113, -1, 52, 113, -1, -1, 35, 21, 116, + 103, 118, 87, 119, 105, -1, 49, 21, -1, -1, + 50, 21, 117, 103, 118, 87, 119, 105, -1, -1, + 21, -1, 118, 84, 21, -1, 118, 87, 84, 21, + -1, -1, 21, 103, 121, -1, -1, 107, -1, 121, + 84, 107, -1 }; /* YYRLINE[YYN] -- source line where rule number YYN was defined. */ static const yytype_uint16 yyrline[] = { - 0, 103, 103, 104, 105, 108, 113, 114, 115, 116, - 117, 118, 121, 127, 133, 141, 149, 155, 163, 172, - 173, 175, 176, 181, 192, 208, 220, 225, 232, 241, - 250, 260, 270, 281, 282, 285, 286, 289, 290, 293, - 301, 302, 310, 311, 312, 314, 316, 322, 328, 335, - 337, 339, 340, 341, 344, 345, 348, 351, 355, 358, - 362, 369, 375, 376, 377, 378, 379, 380, 381, 382, - 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, - 393, 394, 395, 396, 399, 400, 401, 402, 404, 405, - 408, 411, 414, 415, 416, 419, 420, 431, 432, 433, - 434, 437, 440, 445, 446, 449, 450, 453, 454, 457, - 460, 490, 490, 496, 499, 499, 505, 506, 507, 508, - 510, 514, 522, 523, 524 + 0, 105, 105, 106, 107, 110, 115, 116, 117, 118, + 119, 120, 123, 129, 135, 143, 151, 157, 165, 174, + 175, 177, 178, 183, 194, 210, 222, 230, 237, 246, + 255, 265, 275, 286, 287, 290, 291, 294, 295, 298, + 306, 307, 315, 316, 317, 319, 321, 327, 333, 340, + 342, 344, 345, 346, 349, 355, 356, 359, 362, 365, + 368, 372, 379, 385, 386, 387, 388, 389, 390, 391, + 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, + 402, 403, 404, 405, 406, 407, 410, 411, 412, 413, + 414, 416, 417, 418, 421, 424, 427, 428, 429, 432, + 433, 436, 437, 448, 449, 450, 451, 454, 457, 462, + 463, 466, 467, 470, 471, 474, 477, 507, 507, 513, + 516, 516, 521, 522, 523, 524, 526, 530, 538, 539, + 540 }; #endif @@ -639,23 +649,24 @@ static const yytype_uint16 yyrline[] = static const char *const yytname[] = { "$end", "error", "$undefined", "UNARY", "CASTREF", "VOID", "VAR", - "POINT", "RECT", "ARRAY", "SYMBOL", "INT", "THEENTITY", + "POINT", "RECT", "ARRAY", "SYMBOL", "OBJECT", "INT", "THEENTITY", "THEENTITYWITHID", "FLOAT", "BLTIN", "BLTINNOARGS", "BLTINNOARGSORONE", "BLTINONEARG", "BLTINARGLIST", "ID", "STRING", "HANDLER", "tDOWN", "tELSE", "tNLELSIF", "tEND", "tEXIT", "tFRAME", "tGLOBAL", "tGO", "tIF", "tINTO", "tLOOP", "tMACRO", "tMOVIE", "tNEXT", "tOF", "tPREVIOUS", "tPUT", "tREPEAT", "tSET", "tTHEN", "tTO", "tWHEN", "tWITH", "tWHILE", "tNLELSE", "tFACTORY", "tMETHOD", "tOPEN", "tPLAY", "tDONE", - "tPLAYACCEL", "tGE", "tLE", "tGT", "tLT", "tEQ", "tNEQ", "tAND", "tOR", - "tNOT", "tCONCAT", "tCONTAINS", "tSTARTS", "tSPRITE", "tINTERSECTS", - "tWITHIN", "'='", "'+'", "'-'", "'*'", "'/'", "'%'", "'\\n'", "'('", - "')'", "'>'", "'<'", "'&'", "','", "$accept", "program", "nl", - "programline", "asgn", "stmtoneliner", "stmt", "ifstmt", + "tPLAYACCEL", "tINSTANCE", "tGE", "tLE", "tGT", "tLT", "tEQ", "tNEQ", + "tAND", "tOR", "tNOT", "tMOD", "tCONCAT", "tCONTAINS", "tSTARTS", + "tSPRITE", "tINTERSECTS", "tWITHIN", "'='", "'&'", "'+'", "'-'", "'*'", + "'/'", "'%'", "'\\n'", "'('", "')'", "'>'", "'<'", "','", "$accept", + "program", "nl", "programline", "asgn", "stmtoneliner", "stmt", "ifstmt", "elsestmtoneliner", "elseifstmt", "elseifstmtoneliner", "elseifstmtoneliner1", "elseifstmt1", "cond", "repeatwhile", - "repeatwith", "if", "elseif", "begin", "end", "stmtlist", "expr", "func", - "globallist", "gotofunc", "gotoframe", "gotomovie", "playfunc", "defn", - "@1", "@2", "argdef", "argstore", "macro", "arglist", 0 + "repeatwith", "if", "elseif", "begin", "end", "stmtlist", "when", "expr", + "func", "globallist", "instancelist", "gotofunc", "gotoframe", + "gotomovie", "playfunc", "defn", "@1", "@2", "argdef", "argstore", + "macro", "arglist", 0 }; #endif @@ -670,28 +681,29 @@ static const yytype_uint16 yytoknum[] = 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, - 315, 316, 317, 318, 319, 320, 321, 322, 323, 61, - 43, 45, 42, 47, 37, 10, 40, 41, 62, 60, - 38, 44 + 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, + 325, 326, 61, 38, 43, 45, 42, 47, 37, 10, + 40, 41, 62, 60, 44 }; # endif /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ static const yytype_uint8 yyr1[] = { - 0, 82, 83, 83, 83, 84, 85, 85, 85, 85, - 85, 85, 86, 86, 86, 86, 86, 86, 86, 87, - 87, 88, 88, 88, 88, 88, 88, 89, 89, 89, - 89, 89, 89, 90, 90, 91, 91, 92, 92, 93, - 94, 94, 95, 95, 95, 96, 97, 98, 99, 100, - 101, 102, 102, 102, 103, 103, 103, 103, 103, 103, - 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, - 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, - 103, 103, 103, 103, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 105, 105, 106, 106, 106, - 106, 106, 106, 107, 107, 108, 108, 109, 109, 109, - 109, 111, 110, 110, 112, 110, 113, 113, 113, 113, - 114, 115, 116, 116, 116 + 0, 85, 86, 86, 86, 87, 88, 88, 88, 88, + 88, 88, 89, 89, 89, 89, 89, 89, 89, 90, + 90, 91, 91, 91, 91, 91, 91, 92, 92, 92, + 92, 92, 92, 93, 93, 94, 94, 95, 95, 96, + 97, 97, 98, 98, 98, 99, 100, 101, 102, 103, + 104, 105, 105, 105, 106, 107, 107, 107, 107, 107, + 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, + 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, + 107, 107, 107, 107, 107, 107, 108, 108, 108, 108, + 108, 108, 108, 108, 108, 108, 108, 108, 108, 109, + 109, 110, 110, 111, 111, 111, 111, 111, 111, 112, + 112, 113, 113, 114, 114, 114, 114, 116, 115, 115, + 117, 115, 118, 118, 118, 118, 119, 120, 121, 121, + 121 }; /* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ @@ -699,17 +711,18 @@ static const yytype_uint8 yyr2[] = { 0, 2, 3, 1, 2, 1, 0, 1, 1, 1, 1, 1, 4, 4, 4, 5, 4, 4, 5, 1, - 1, 1, 1, 8, 11, 12, 4, 8, 11, 11, + 1, 1, 1, 8, 11, 12, 3, 8, 11, 11, 6, 10, 10, 0, 3, 2, 1, 2, 1, 6, 1, 5, 1, 3, 3, 2, 3, 1, 1, 0, - 0, 0, 2, 2, 1, 1, 1, 1, 4, 1, - 1, 2, 1, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 2, 3, 3, 3, 3, 2, - 2, 3, 4, 4, 2, 1, 1, 1, 2, 2, - 2, 1, 2, 4, 2, 1, 3, 2, 2, 2, - 2, 3, 2, 2, 1, 3, 2, 2, 2, 3, - 2, 0, 8, 2, 0, 8, 0, 1, 3, 4, - 0, 3, 0, 1, 3 + 0, 0, 2, 2, 3, 1, 1, 1, 1, 4, + 1, 1, 2, 1, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, + 3, 2, 2, 3, 4, 4, 2, 1, 1, 2, + 1, 2, 2, 2, 2, 1, 2, 4, 2, 1, + 3, 1, 3, 2, 2, 2, 2, 3, 2, 2, + 1, 3, 2, 2, 2, 3, 2, 0, 8, 2, + 0, 8, 0, 1, 3, 4, 0, 3, 0, 1, + 3 }; /* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state @@ -717,28 +730,29 @@ static const yytype_uint8 yyr2[] = means the default is an error. */ static const yytype_uint8 yydefact[] = { - 0, 0, 54, 60, 0, 55, 57, 91, 0, 122, - 49, 56, 87, 0, 0, 47, 0, 0, 0, 0, + 0, 0, 55, 61, 0, 56, 58, 95, 0, 128, + 49, 57, 90, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 3, 62, 21, 11, 22, 0, 0, 0, 19, - 8, 85, 86, 7, 9, 5, 4, 59, 0, 62, - 61, 90, 89, 123, 92, 122, 122, 95, 88, 0, - 97, 0, 98, 0, 99, 104, 100, 102, 111, 84, - 0, 45, 0, 0, 0, 0, 113, 114, 94, 107, - 108, 110, 74, 0, 79, 80, 0, 1, 6, 0, - 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 63, 21, 11, 22, 0, 0, 0, + 0, 19, 8, 87, 88, 7, 9, 5, 4, 60, + 0, 63, 62, 94, 93, 129, 96, 128, 128, 89, + 99, 91, 0, 103, 0, 104, 0, 105, 110, 106, + 108, 117, 86, 0, 45, 0, 0, 0, 0, 119, + 120, 98, 113, 114, 116, 101, 92, 76, 0, 81, + 82, 0, 1, 6, 0, 0, 0, 0, 42, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 121, 0, 103, 106, 0, 101, 49, 0, - 46, 0, 0, 0, 0, 0, 0, 49, 0, 109, - 0, 0, 81, 2, 0, 50, 0, 0, 49, 0, - 70, 71, 69, 72, 73, 76, 77, 78, 63, 64, - 65, 66, 67, 68, 75, 124, 58, 96, 105, 116, - 12, 17, 14, 0, 0, 16, 13, 26, 116, 93, - 82, 83, 51, 0, 44, 51, 0, 43, 117, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, + 0, 109, 112, 0, 107, 49, 0, 46, 0, 0, + 0, 0, 0, 54, 49, 0, 115, 0, 0, 0, + 83, 2, 0, 50, 0, 0, 49, 0, 26, 72, + 73, 71, 74, 75, 68, 78, 79, 80, 77, 64, + 65, 66, 67, 69, 70, 130, 59, 100, 111, 122, + 12, 17, 14, 0, 0, 16, 13, 122, 97, 102, + 84, 85, 51, 0, 44, 51, 0, 43, 123, 0, 18, 15, 0, 50, 0, 0, 50, 50, 20, 0, - 120, 120, 52, 53, 0, 0, 50, 49, 30, 118, + 126, 126, 52, 53, 0, 0, 50, 49, 30, 124, 0, 51, 51, 0, 50, 51, 0, 51, 0, 48, - 49, 50, 38, 0, 119, 112, 115, 23, 51, 50, + 49, 50, 38, 0, 125, 118, 121, 23, 51, 50, 27, 50, 50, 40, 36, 0, 0, 37, 33, 0, 50, 0, 0, 35, 0, 0, 50, 49, 50, 49, 0, 0, 0, 0, 49, 31, 0, 32, 0, 0, @@ -748,283 +762,299 @@ static const yytype_uint8 yydefact[] = /* YYDEFGOTO[NTERM-NUM]. */ static const yytype_int16 yydefgoto[] = { - -1, 30, 192, 31, 49, 33, 193, 35, 238, 222, - 223, 212, 224, 92, 36, 37, 38, 213, 248, 173, - 183, 39, 188, 58, 41, 66, 67, 42, 43, 118, - 127, 179, 201, 44, 54 + -1, 31, 202, 32, 51, 34, 203, 36, 248, 232, + 233, 222, 234, 97, 37, 38, 39, 223, 258, 148, + 193, 40, 41, 198, 61, 86, 43, 69, 70, 44, + 45, 125, 134, 189, 211, 46, 56 }; /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM. */ -#define YYPACT_NINF -198 +#define YYPACT_NINF -206 static const yytype_int16 yypact[] = { - 244, -52, -198, -198, 386, -198, -198, 386, 386, 386, - 792, -198, -198, 19, 574, -198, 23, 386, 11, 7, - 27, 42, 43, 386, 614, 386, 386, 386, 386, 386, - 5, -198, 6, -198, -198, -198, -47, -8, 513, 770, - -198, -198, -198, -198, -198, -198, -198, -5, 386, -198, - 770, 770, 770, 770, -6, 386, 386, -198, 1, 386, - -198, 386, -198, 37, -198, 770, 9, -198, -198, 152, - 56, -198, -36, 386, -28, 36, -198, -198, 650, -198, - 9, -198, 841, 672, 841, 841, 721, -198, 342, 513, - 386, 513, 44, 748, 386, 386, 386, 386, 386, 386, - 386, 386, 386, 386, 386, 386, 386, 386, 386, 152, - 386, -56, -6, 63, 770, 770, 386, -198, -198, 64, - -198, 386, 386, 628, 386, 386, 386, -198, 386, -198, - 386, 386, -198, -198, 8, 770, 10, 694, -52, 386, - 770, 770, 770, 770, 770, 770, 770, 770, 819, 819, - 841, 841, 770, 770, 770, 770, -198, -198, 770, 72, - -198, 770, 770, 386, 386, 770, 770, 770, 72, 770, - 770, 770, -198, -11, -198, -198, 530, 770, -198, -57, - 770, 770, -57, 403, 50, 386, 403, -198, -198, 74, - 14, 14, -198, -198, 73, 386, 770, -10, -16, -198, - 78, -198, -198, 61, 770, -198, 75, -198, 79, -198, - -198, 79, -198, 513, -198, 403, 403, -198, -198, 403, - -198, 403, 79, 79, -198, 513, 530, -198, 58, 65, - 403, 77, 82, -198, 84, 69, -198, -198, -198, -198, - 86, 76, 88, 89, -15, -198, 530, -198, 469, 81, - -198, -198, -198, 403, -198, -198, -198, -198, -198 + 263, -61, -206, -206, 671, -206, -206, 671, 671, 671, + 912, -206, -17, 9, 593, -206, 14, 671, 8, 6, + 20, 24, 29, 671, 634, 31, 671, 671, 671, 671, + 671, 3, -206, 4, -206, -206, -206, -21, -8, 707, + 671, 889, -206, -206, -206, -206, -206, -206, -206, -20, + 671, -206, 889, 889, 889, 889, 1, 671, 671, -206, + -206, 7, 671, -206, 671, -206, 37, -206, 889, 13, + -206, -206, 723, 54, -206, -38, 671, -34, 41, -206, + -206, 774, -206, 13, -206, -206, 10, -43, 797, -43, + -43, 843, -206, 332, 707, 671, 707, 43, 866, 889, + 671, 671, 671, 671, 671, 671, 671, 671, 671, 671, + 671, 671, 671, 671, 671, 671, 723, 671, -59, 1, + 67, 889, 889, 671, -206, -206, 69, -206, 671, 671, + 751, 671, 671, -206, -206, 671, -206, 71, 671, 671, + -206, -206, 18, 889, 19, 820, -61, 671, -206, 44, + 44, 44, -43, -43, -43, 889, 44, 44, 166, 224, + 224, -43, -43, 889, 889, 889, -206, -206, 889, 81, + -206, 889, 889, 671, 671, 889, 889, 81, 889, -206, + 889, 889, -206, -7, -206, -206, 529, 889, -206, -58, + 889, 889, -58, 396, 60, 671, 396, -206, -206, 84, + 27, 27, -206, -206, 85, 671, 889, -16, -12, -206, + 87, -206, -206, 73, 889, -206, 90, -206, 97, -206, + -206, 97, -206, 707, -206, 396, 396, -206, -206, 396, + -206, 396, 97, 97, -206, 707, 529, -206, 76, 86, + 396, 101, 104, -206, 105, 93, -206, -206, -206, -206, + 111, 114, 126, 127, -18, -206, 529, -206, 465, 120, + -206, -206, -206, 396, -206, -206, -206, -206, -206 }; /* YYPGOTO[NTERM-NUM]. */ static const yytype_int16 yypgoto[] = { - -198, -198, 12, 25, 2, -172, 0, -198, -198, -198, - -83, -197, -105, -61, -198, -198, -198, -186, -9, 113, - -167, 41, 3, -198, -198, 98, -7, -198, -198, -198, - -198, -45, -67, -198, -3 + -206, -206, 11, 70, 2, -179, 0, -206, -206, -206, + -44, -205, -67, -63, -206, -206, -206, -203, -9, -13, + -141, -206, 39, 5, -206, -206, -206, 142, -11, -206, + -206, -206, -206, -4, -32, -206, 23 }; /* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If positive, shift that token. If negative, reduce the rule which number is the opposite. If zero, do what YYDEFACT says. If YYTABLE_NINF, syntax error. */ -#define YYTABLE_NINF -60 +#define YYTABLE_NINF -61 static const yytype_int16 yytable[] = { - 34, 56, 32, 40, 187, 87, -10, 121, 186, 209, - -51, -51, 184, 46, 227, 124, 206, 81, 45, 72, - 73, 156, 225, 45, 189, 110, 227, 74, 134, 89, - 136, 210, 185, 122, 215, 216, 225, 207, 219, 57, - 221, 125, 88, 68, 61, 50, 63, 75, 51, 52, - 53, 230, 111, 112, 236, 65, 70, 71, 69, 117, - -51, 90, 76, 77, 78, 65, 82, 83, 84, 85, - 86, 55, 116, 129, 254, 110, 120, 253, 126, 93, - 45, -10, 113, 157, 160, 172, 138, 174, 34, 109, - 32, 40, 178, 195, 199, 200, 53, 53, 214, 203, - 114, 217, 115, 241, 209, 237, 220, 239, 242, 159, - 243, 244, 249, 133, 123, 211, 250, 233, 168, 251, - 252, 256, 80, 182, 202, 0, 0, 0, 0, 176, - 93, 135, 137, 0, 0, 140, 141, 142, 143, 144, - 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, - 175, 155, 229, 0, 0, 0, 0, 158, 0, 0, - 0, 0, 161, 162, 235, 165, 166, 167, 0, 169, - 0, 170, 171, 0, 0, 0, 0, 0, 0, 0, - 177, 0, 0, 0, 119, 0, 0, 0, 208, 0, - 0, 190, 0, 0, 191, 0, 0, 0, 0, 0, - 0, 226, 0, 0, 180, 181, 94, 95, 0, 0, - 0, 96, 97, 98, 0, 99, 100, 101, 0, 0, - 0, 0, 102, 103, 104, 105, 196, 0, 246, 0, - 106, 107, 108, 0, 0, 0, 204, 0, 0, 0, - 0, 0, 0, 0, -6, 1, 0, 0, 255, 0, - 0, 0, 0, 0, 93, 2, 3, 4, 5, 0, - 6, 7, 8, 9, 10, 11, 93, 0, 0, 0, - 0, 12, 0, 13, 14, 15, 0, 0, 16, 0, - 0, 0, 0, 17, 18, 19, 0, 0, 20, 0, - 0, 0, 21, 22, 23, 24, 194, 0, 0, 197, - 198, 0, 0, 0, 0, 0, 25, 0, 0, 205, - 26, 0, 0, 0, 27, 28, 0, 218, 0, -6, - 29, 0, 0, 0, 228, 0, 0, 0, 0, 0, - 0, 0, 231, 0, 232, 234, 0, 0, 0, 0, - 0, 0, 0, 240, 0, 0, 0, 0, 0, 245, - 0, 247, 0, 2, 3, 4, 5, 0, 6, 7, - 8, 9, 10, 11, 0, 0, 257, 0, 258, 12, - 0, 13, 14, 15, 0, 0, 16, 0, 0, 0, - 0, 17, 18, 19, 0, 0, 20, 0, 0, 0, - 21, 22, 23, 24, 0, 0, 0, 2, 3, 4, - 5, 0, 6, 0, 25, 0, 47, 11, 26, 0, - 0, 0, 27, 28, 2, 3, 4, 5, 29, 6, - 7, 8, 9, 47, 11, 48, 0, 19, 0, 0, - 12, 0, 13, 14, 15, 0, 0, 0, 0, 0, - 0, 0, 17, 18, 19, 0, 0, 20, 25, 0, - 0, 0, 26, 23, 24, 0, 27, 28, 0, 0, - 0, 0, 29, 0, 0, 25, 0, 0, 0, 26, - 0, 0, 0, 27, 28, 0, 0, 0, 45, 29, - 2, 3, 4, 5, 0, 6, 7, 8, 9, 47, - 11, 0, 0, 0, 0, 0, 12, 0, 13, 14, - 15, 0, 0, 0, 0, 0, 0, 0, 17, 18, - 19, 0, 0, 20, 0, 0, 0, 0, 0, 23, - 24, 0, 0, 0, 2, 3, 4, 5, 0, 6, - 0, 25, 0, 47, 11, 26, 0, 0, 0, 27, - 28, 2, 3, 4, 5, 29, 6, 7, 8, 9, - 47, 11, 48, 0, 19, 0, 0, 12, 0, 13, + 35, 58, 33, 92, -10, 42, 128, 197, -51, -51, + 131, 216, 48, 84, 219, 235, 237, 194, 47, 75, + 76, 47, 166, 106, 59, 117, 199, 77, 237, 235, + 60, 142, 217, 144, 129, 71, 220, 195, 132, 114, + 115, 78, 93, 52, 196, 79, 53, 54, 55, 64, + 80, 66, 85, 68, 73, 74, 72, 246, 124, 94, + 57, -51, 81, 68, 95, 87, 88, 89, 90, 91, + 225, 226, 136, 123, 229, 127, 231, 264, 98, 99, + 118, 119, 47, -10, 133, 117, 146, 240, 167, 116, + 170, 120, 179, 35, 137, 33, 55, 55, 42, 182, + 184, 121, 188, 122, 205, 209, 103, 104, 224, 105, + 106, 210, 213, 263, 227, 130, 169, 109, 110, 111, + 112, 113, 230, 219, 247, 177, 114, 115, 251, 249, + 183, 252, 253, 98, 143, 145, 254, 186, 259, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 260, 165, 185, 261, 262, + 239, 266, 168, 141, 221, 243, 83, 171, 172, 212, + 175, 176, 245, 192, 178, 0, 0, 180, 181, 0, + 204, 0, 0, 207, 208, 0, 187, 0, 0, 0, + 0, 0, 0, 215, 0, 0, 0, 0, 218, 0, + 200, 228, 0, 201, 0, 0, 0, 0, 238, 0, + 0, 236, 190, 191, 0, 0, 241, 0, 242, 244, + 0, 0, 0, 0, 0, 0, 0, 250, 103, 104, + 0, 105, 106, 255, 206, 257, 0, 0, 256, 0, + 110, 111, 112, 113, 214, 0, 0, 0, 114, 115, + 267, 0, 268, 0, 0, 0, 0, 0, 265, 0, + 0, 0, 98, -6, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 98, 2, 3, 4, 5, 0, + 6, 7, 8, 9, 10, 11, 103, 104, 0, 105, + 106, 12, 0, 13, 14, 15, 0, 0, 16, 0, + 112, 113, 0, 17, 18, 19, 114, 115, 20, 0, + 0, 0, 21, 22, 23, 24, 0, 0, 25, 0, + 0, 0, 0, 0, 0, 0, 0, 26, 0, 0, + 0, 0, 27, 0, 0, 0, 0, 28, 29, 0, + 0, 0, -6, 30, 2, 3, 4, 5, 0, 6, + 7, 8, 9, 10, 11, 0, 0, 0, 0, 0, + 12, 0, 13, 14, 15, 0, 0, 16, 0, 0, + 0, 0, 17, 18, 19, 0, 0, 20, 0, 0, + 0, 21, 22, 23, 24, 0, 0, 25, 0, 0, + 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, + 0, 27, 0, 0, 0, 0, 28, 29, 2, 3, + 4, 5, 30, 6, 7, 8, 9, 49, 11, 0, + 0, 0, 0, 0, 12, 0, 13, 14, 15, 0, + 0, 0, 0, 0, 0, 0, 17, 18, 19, 0, + 0, 20, 0, 0, 0, 0, 0, 23, 24, 0, + 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, + 26, 0, 0, 0, 0, 27, 0, 0, 0, 0, + 28, 29, 0, 0, 0, 47, 30, 2, 3, 4, + 5, 0, 6, 7, 8, 9, 49, 11, 0, 0, + 0, 0, 0, 12, 0, 13, 14, 15, 0, 0, + 0, 0, 0, 0, 0, 17, 18, 19, 0, 0, + 20, 0, 0, 0, 0, 0, 23, 24, 0, 0, + 25, 0, 0, 0, 0, 0, 0, 0, 0, 26, + 0, 0, 0, 0, 27, 0, 0, 0, 0, 28, + 29, 2, 3, 4, 5, 30, 6, 7, 8, 9, + 49, 11, 0, 0, 0, 0, 0, 12, 0, 13, 14, 0, 0, 0, 0, 0, 0, 0, 0, 17, - 0, 19, 0, 0, 0, 25, 0, 0, 0, 26, - 23, 24, 0, 27, 28, 2, 3, 4, 5, 91, - 6, 0, 25, 0, 47, 11, 26, 0, 0, 0, - 27, 28, 59, 0, 0, 0, 29, 60, 0, 61, - 62, 63, 64, 48, 0, 19, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 2, 3, 4, 5, 0, - 6, 0, 0, 0, 47, 11, 25, 0, 0, 0, - 26, 0, 59, 0, 27, 28, 0, 0, 0, 61, - 29, 63, 0, 48, 0, 19, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 79, 0, 0, 0, - 0, 163, 0, 0, 0, 0, 25, 0, 0, 0, - 26, 0, 94, 95, 27, 28, 0, 96, 97, 98, - 29, 99, 100, 101, 0, 128, 0, 164, 102, 103, - 104, 105, 0, 0, 94, 95, 106, 107, 108, 96, - 97, 98, 0, 99, 100, 101, 0, 0, 0, 0, - 102, 103, 104, 105, 0, 0, 94, 95, 106, 107, - 108, 96, 97, 98, 0, 99, 100, 101, 0, 130, - 131, 0, 102, 103, 104, 105, 0, 0, 94, 95, - 106, 107, 108, 96, 97, 98, 0, 99, 100, 101, - 0, 0, 0, 139, 102, 103, 104, 105, 0, 0, - 0, 132, 106, 107, 108, 94, 95, 0, 0, 0, - 96, 97, 98, 0, 99, 100, 101, 0, 0, 0, - 0, 102, 103, 104, 105, 0, 0, 0, 132, 106, - 107, 108, 94, 95, 0, 0, 0, 96, 97, 98, - 0, 99, 100, 101, 0, 0, 0, 139, 102, 103, - 104, 105, 0, 0, 94, 95, 106, 107, 108, 96, - 97, 98, 0, 99, 100, 101, 0, 0, 0, 0, - 102, 103, 104, 105, 0, 0, -59, -59, 106, 107, - 108, -59, -59, -59, 0, -59, -59, -59, 0, 0, - 0, 0, 0, 0, -59, -59, 0, 0, 55, 0, - -59, -59, -59, 94, 95, 0, 0, 0, 96, 97, - 98, 0, 99, 100, 101, 0, 0, 0, 0, 0, - 0, 104, 105, 0, 0, 94, 95, 106, 107, 108, - 96, 97, 98, 0, 99, 100, 101, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, - 107, 108 + 0, 19, 0, 0, 0, 0, 0, 0, 0, 0, + 23, 24, 0, 0, 25, 0, 0, 0, 0, 0, + 0, 0, 0, 26, 0, 0, 0, 0, 27, 0, + 0, 0, 0, 28, 29, 2, 3, 4, 5, 30, + 6, 0, 0, 0, 49, 11, 0, 0, 0, 0, + 0, 0, 62, 0, 0, 0, 0, 63, 0, 64, + 65, 66, 67, 50, 0, 19, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 2, 3, 4, 5, + 0, 6, 0, 0, 0, 49, 11, 26, 0, 0, + 0, 0, 27, 62, 0, 0, 0, 28, 29, 0, + 64, 0, 66, 30, 50, 0, 19, 0, 0, 0, + 0, 0, 0, 2, 3, 4, 5, 82, 6, 0, + 0, 0, 49, 11, 0, 0, 0, 0, 26, 0, + 0, 0, 0, 27, 0, 0, 0, 0, 28, 29, + 0, 50, 0, 19, 30, 0, 0, 0, 0, 2, + 3, 4, 5, 0, 6, 0, 0, 0, 49, 11, + 0, 0, 0, 0, 0, 26, 0, 0, 0, 0, + 27, 0, 0, 0, 0, 28, 29, 50, 0, 19, + 0, 30, 0, 0, 0, 0, 126, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 26, 0, 0, 0, 0, 27, 0, 0, 100, + 101, 28, 29, 0, 102, 103, 104, 96, 105, 106, + 107, 108, 0, 0, 0, 173, 109, 110, 111, 112, + 113, 0, 0, 0, 0, 114, 115, 100, 101, 0, + 0, 0, 102, 103, 104, 0, 105, 106, 107, 108, + 135, 0, 0, 174, 109, 110, 111, 112, 113, 0, + 100, 101, 0, 114, 115, 102, 103, 104, 0, 105, + 106, 107, 108, 0, 0, 0, 0, 109, 110, 111, + 112, 113, 0, 100, 101, 0, 114, 115, 102, 103, + 104, 0, 105, 106, 107, 108, 0, 138, 139, 0, + 109, 110, 111, 112, 113, 0, 100, 101, 0, 114, + 115, 102, 103, 104, 0, 105, 106, 107, 108, 0, + 0, 0, 147, 109, 110, 111, 112, 113, 0, 100, + 101, 140, 114, 115, 102, 103, 104, 0, 105, 106, + 107, 108, 0, 0, 0, 0, 109, 110, 111, 112, + 113, 0, 100, 101, 140, 114, 115, 102, 103, 104, + 0, 105, 106, 107, 108, 0, 0, 0, 147, 109, + 110, 111, 112, 113, 0, 100, 101, 0, 114, 115, + 102, 103, 104, 0, 105, 106, 107, 108, 0, 0, + 0, 0, 109, 110, 111, 112, 113, 0, -60, -60, + 0, 114, 115, -60, -60, -60, 0, -60, -60, -60, + -60, 0, 0, 0, 0, -60, 0, 0, -60, -60, + 0, 0, 57, 0, -60, -60 }; static const yytype_int16 yycheck[] = { - 0, 10, 0, 0, 176, 0, 0, 43, 175, 25, - 25, 26, 23, 1, 211, 43, 26, 24, 75, 12, - 13, 77, 208, 75, 81, 81, 223, 20, 89, 76, - 91, 47, 43, 69, 201, 202, 222, 47, 205, 20, - 207, 69, 30, 20, 35, 4, 37, 20, 7, 8, - 9, 218, 55, 56, 226, 14, 45, 46, 17, 66, - 75, 69, 20, 20, 23, 24, 25, 26, 27, 28, - 29, 76, 35, 80, 246, 81, 20, 244, 42, 38, - 75, 75, 81, 20, 20, 77, 42, 77, 88, 48, - 88, 88, 20, 43, 20, 81, 55, 56, 20, 26, - 59, 40, 61, 26, 25, 47, 31, 42, 26, 118, - 26, 42, 26, 88, 73, 198, 40, 222, 127, 31, - 31, 40, 24, 168, 191, -1, -1, -1, -1, 138, - 89, 90, 91, -1, -1, 94, 95, 96, 97, 98, - 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, - 138, 110, 213, -1, -1, -1, -1, 116, -1, -1, - -1, -1, 121, 122, 225, 124, 125, 126, -1, 128, - -1, 130, 131, -1, -1, -1, -1, -1, -1, -1, - 139, -1, -1, -1, 32, -1, -1, -1, 197, -1, - -1, 179, -1, -1, 182, -1, -1, -1, -1, -1, - -1, 210, -1, -1, 163, 164, 54, 55, -1, -1, - -1, 59, 60, 61, -1, 63, 64, 65, -1, -1, - -1, -1, 70, 71, 72, 73, 185, -1, 237, -1, - 78, 79, 80, -1, -1, -1, 195, -1, -1, -1, - -1, -1, -1, -1, 0, 1, -1, -1, 248, -1, - -1, -1, -1, -1, 213, 11, 12, 13, 14, -1, - 16, 17, 18, 19, 20, 21, 225, -1, -1, -1, - -1, 27, -1, 29, 30, 31, -1, -1, 34, -1, - -1, -1, -1, 39, 40, 41, -1, -1, 44, -1, - -1, -1, 48, 49, 50, 51, 183, -1, -1, 186, - 187, -1, -1, -1, -1, -1, 62, -1, -1, 196, - 66, -1, -1, -1, 70, 71, -1, 204, -1, 75, - 76, -1, -1, -1, 211, -1, -1, -1, -1, -1, - -1, -1, 219, -1, 221, 222, -1, -1, -1, -1, - -1, -1, -1, 230, -1, -1, -1, -1, -1, 236, - -1, 238, -1, 11, 12, 13, 14, -1, 16, 17, - 18, 19, 20, 21, -1, -1, 253, -1, 255, 27, - -1, 29, 30, 31, -1, -1, 34, -1, -1, -1, - -1, 39, 40, 41, -1, -1, 44, -1, -1, -1, - 48, 49, 50, 51, -1, -1, -1, 11, 12, 13, - 14, -1, 16, -1, 62, -1, 20, 21, 66, -1, - -1, -1, 70, 71, 11, 12, 13, 14, 76, 16, - 17, 18, 19, 20, 21, 39, -1, 41, -1, -1, - 27, -1, 29, 30, 31, -1, -1, -1, -1, -1, - -1, -1, 39, 40, 41, -1, -1, 44, 62, -1, - -1, -1, 66, 50, 51, -1, 70, 71, -1, -1, - -1, -1, 76, -1, -1, 62, -1, -1, -1, 66, - -1, -1, -1, 70, 71, -1, -1, -1, 75, 76, - 11, 12, 13, 14, -1, 16, 17, 18, 19, 20, - 21, -1, -1, -1, -1, -1, 27, -1, 29, 30, - 31, -1, -1, -1, -1, -1, -1, -1, 39, 40, - 41, -1, -1, 44, -1, -1, -1, -1, -1, 50, - 51, -1, -1, -1, 11, 12, 13, 14, -1, 16, - -1, 62, -1, 20, 21, 66, -1, -1, -1, 70, - 71, 11, 12, 13, 14, 76, 16, 17, 18, 19, - 20, 21, 39, -1, 41, -1, -1, 27, -1, 29, - 30, -1, -1, -1, -1, -1, -1, -1, -1, 39, - -1, 41, -1, -1, -1, 62, -1, -1, -1, 66, - 50, 51, -1, 70, 71, 11, 12, 13, 14, 76, - 16, -1, 62, -1, 20, 21, 66, -1, -1, -1, - 70, 71, 28, -1, -1, -1, 76, 33, -1, 35, - 36, 37, 38, 39, -1, 41, -1, -1, -1, -1, - -1, -1, -1, -1, -1, 11, 12, 13, 14, -1, - 16, -1, -1, -1, 20, 21, 62, -1, -1, -1, - 66, -1, 28, -1, 70, 71, -1, -1, -1, 35, - 76, 37, -1, 39, -1, 41, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, 52, -1, -1, -1, - -1, 43, -1, -1, -1, -1, 62, -1, -1, -1, - 66, -1, 54, 55, 70, 71, -1, 59, 60, 61, - 76, 63, 64, 65, -1, 45, -1, 69, 70, 71, - 72, 73, -1, -1, 54, 55, 78, 79, 80, 59, - 60, 61, -1, 63, 64, 65, -1, -1, -1, -1, - 70, 71, 72, 73, -1, -1, 54, 55, 78, 79, - 80, 59, 60, 61, -1, 63, 64, 65, -1, 67, - 68, -1, 70, 71, 72, 73, -1, -1, 54, 55, - 78, 79, 80, 59, 60, 61, -1, 63, 64, 65, - -1, -1, -1, 69, 70, 71, 72, 73, -1, -1, - -1, 77, 78, 79, 80, 54, 55, -1, -1, -1, - 59, 60, 61, -1, 63, 64, 65, -1, -1, -1, - -1, 70, 71, 72, 73, -1, -1, -1, 77, 78, - 79, 80, 54, 55, -1, -1, -1, 59, 60, 61, - -1, 63, 64, 65, -1, -1, -1, 69, 70, 71, - 72, 73, -1, -1, 54, 55, 78, 79, 80, 59, - 60, 61, -1, 63, 64, 65, -1, -1, -1, -1, - 70, 71, 72, 73, -1, -1, 54, 55, 78, 79, - 80, 59, 60, 61, -1, 63, 64, 65, -1, -1, - -1, -1, -1, -1, 72, 73, -1, -1, 76, -1, - 78, 79, 80, 54, 55, -1, -1, -1, 59, 60, - 61, -1, 63, 64, 65, -1, -1, -1, -1, -1, - -1, 72, 73, -1, -1, 54, 55, 78, 79, 80, - 59, 60, 61, -1, 63, 64, 65, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, 78, - 79, 80 + 0, 10, 0, 0, 0, 0, 44, 186, 26, 27, + 44, 27, 1, 24, 26, 218, 221, 24, 79, 13, + 14, 79, 81, 66, 41, 84, 84, 21, 233, 232, + 21, 94, 48, 96, 72, 21, 48, 44, 72, 82, + 83, 21, 31, 4, 185, 21, 7, 8, 9, 36, + 21, 38, 21, 14, 46, 47, 17, 236, 69, 80, + 80, 79, 23, 24, 72, 26, 27, 28, 29, 30, + 211, 212, 83, 36, 215, 21, 217, 256, 39, 40, + 57, 58, 79, 79, 43, 84, 43, 228, 21, 50, + 21, 84, 21, 93, 84, 93, 57, 58, 93, 81, + 81, 62, 21, 64, 44, 21, 62, 63, 21, 65, + 66, 84, 27, 254, 41, 76, 125, 73, 74, 75, + 76, 77, 32, 26, 48, 134, 82, 83, 27, 43, + 143, 27, 27, 94, 95, 96, 43, 146, 27, 100, + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, + 111, 112, 113, 114, 115, 41, 117, 146, 32, 32, + 223, 41, 123, 93, 208, 232, 24, 128, 129, 201, + 131, 132, 235, 177, 135, -1, -1, 138, 139, -1, + 193, -1, -1, 196, 197, -1, 147, -1, -1, -1, + -1, -1, -1, 206, -1, -1, -1, -1, 207, -1, + 189, 214, -1, 192, -1, -1, -1, -1, 221, -1, + -1, 220, 173, 174, -1, -1, 229, -1, 231, 232, + -1, -1, -1, -1, -1, -1, -1, 240, 62, 63, + -1, 65, 66, 246, 195, 248, -1, -1, 247, -1, + 74, 75, 76, 77, 205, -1, -1, -1, 82, 83, + 263, -1, 265, -1, -1, -1, -1, -1, 258, -1, + -1, -1, 223, 0, 1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 235, 12, 13, 14, 15, -1, + 17, 18, 19, 20, 21, 22, 62, 63, -1, 65, + 66, 28, -1, 30, 31, 32, -1, -1, 35, -1, + 76, 77, -1, 40, 41, 42, 82, 83, 45, -1, + -1, -1, 49, 50, 51, 52, -1, -1, 55, -1, + -1, -1, -1, -1, -1, -1, -1, 64, -1, -1, + -1, -1, 69, -1, -1, -1, -1, 74, 75, -1, + -1, -1, 79, 80, 12, 13, 14, 15, -1, 17, + 18, 19, 20, 21, 22, -1, -1, -1, -1, -1, + 28, -1, 30, 31, 32, -1, -1, 35, -1, -1, + -1, -1, 40, 41, 42, -1, -1, 45, -1, -1, + -1, 49, 50, 51, 52, -1, -1, 55, -1, -1, + -1, -1, -1, -1, -1, -1, 64, -1, -1, -1, + -1, 69, -1, -1, -1, -1, 74, 75, 12, 13, + 14, 15, 80, 17, 18, 19, 20, 21, 22, -1, + -1, -1, -1, -1, 28, -1, 30, 31, 32, -1, + -1, -1, -1, -1, -1, -1, 40, 41, 42, -1, + -1, 45, -1, -1, -1, -1, -1, 51, 52, -1, + -1, 55, -1, -1, -1, -1, -1, -1, -1, -1, + 64, -1, -1, -1, -1, 69, -1, -1, -1, -1, + 74, 75, -1, -1, -1, 79, 80, 12, 13, 14, + 15, -1, 17, 18, 19, 20, 21, 22, -1, -1, + -1, -1, -1, 28, -1, 30, 31, 32, -1, -1, + -1, -1, -1, -1, -1, 40, 41, 42, -1, -1, + 45, -1, -1, -1, -1, -1, 51, 52, -1, -1, + 55, -1, -1, -1, -1, -1, -1, -1, -1, 64, + -1, -1, -1, -1, 69, -1, -1, -1, -1, 74, + 75, 12, 13, 14, 15, 80, 17, 18, 19, 20, + 21, 22, -1, -1, -1, -1, -1, 28, -1, 30, + 31, -1, -1, -1, -1, -1, -1, -1, -1, 40, + -1, 42, -1, -1, -1, -1, -1, -1, -1, -1, + 51, 52, -1, -1, 55, -1, -1, -1, -1, -1, + -1, -1, -1, 64, -1, -1, -1, -1, 69, -1, + -1, -1, -1, 74, 75, 12, 13, 14, 15, 80, + 17, -1, -1, -1, 21, 22, -1, -1, -1, -1, + -1, -1, 29, -1, -1, -1, -1, 34, -1, 36, + 37, 38, 39, 40, -1, 42, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 12, 13, 14, 15, + -1, 17, -1, -1, -1, 21, 22, 64, -1, -1, + -1, -1, 69, 29, -1, -1, -1, 74, 75, -1, + 36, -1, 38, 80, 40, -1, 42, -1, -1, -1, + -1, -1, -1, 12, 13, 14, 15, 53, 17, -1, + -1, -1, 21, 22, -1, -1, -1, -1, 64, -1, + -1, -1, -1, 69, -1, -1, -1, -1, 74, 75, + -1, 40, -1, 42, 80, -1, -1, -1, -1, 12, + 13, 14, 15, -1, 17, -1, -1, -1, 21, 22, + -1, -1, -1, -1, -1, 64, -1, -1, -1, -1, + 69, -1, -1, -1, -1, 74, 75, 40, -1, 42, + -1, 80, -1, -1, -1, -1, 33, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 64, -1, -1, -1, -1, 69, -1, -1, 56, + 57, 74, 75, -1, 61, 62, 63, 80, 65, 66, + 67, 68, -1, -1, -1, 44, 73, 74, 75, 76, + 77, -1, -1, -1, -1, 82, 83, 56, 57, -1, + -1, -1, 61, 62, 63, -1, 65, 66, 67, 68, + 46, -1, -1, 72, 73, 74, 75, 76, 77, -1, + 56, 57, -1, 82, 83, 61, 62, 63, -1, 65, + 66, 67, 68, -1, -1, -1, -1, 73, 74, 75, + 76, 77, -1, 56, 57, -1, 82, 83, 61, 62, + 63, -1, 65, 66, 67, 68, -1, 70, 71, -1, + 73, 74, 75, 76, 77, -1, 56, 57, -1, 82, + 83, 61, 62, 63, -1, 65, 66, 67, 68, -1, + -1, -1, 72, 73, 74, 75, 76, 77, -1, 56, + 57, 81, 82, 83, 61, 62, 63, -1, 65, 66, + 67, 68, -1, -1, -1, -1, 73, 74, 75, 76, + 77, -1, 56, 57, 81, 82, 83, 61, 62, 63, + -1, 65, 66, 67, 68, -1, -1, -1, 72, 73, + 74, 75, 76, 77, -1, 56, 57, -1, 82, 83, + 61, 62, 63, -1, 65, 66, 67, 68, -1, -1, + -1, -1, 73, 74, 75, 76, 77, -1, 56, 57, + -1, 82, 83, 61, 62, 63, -1, 65, 66, 67, + 68, -1, -1, -1, -1, 73, -1, -1, 76, 77, + -1, -1, 80, -1, 82, 83 }; /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing symbol of state STATE-NUM. */ static const yytype_uint8 yystos[] = { - 0, 1, 11, 12, 13, 14, 16, 17, 18, 19, - 20, 21, 27, 29, 30, 31, 34, 39, 40, 41, - 44, 48, 49, 50, 51, 62, 66, 70, 71, 76, - 83, 85, 86, 87, 88, 89, 96, 97, 98, 103, - 104, 106, 109, 110, 115, 75, 84, 20, 39, 86, - 103, 103, 103, 103, 116, 76, 100, 20, 105, 28, - 33, 35, 36, 37, 38, 103, 107, 108, 20, 103, - 45, 46, 12, 13, 20, 20, 20, 20, 103, 52, - 107, 108, 103, 103, 103, 103, 103, 0, 84, 76, - 69, 76, 95, 103, 54, 55, 59, 60, 61, 63, - 64, 65, 70, 71, 72, 73, 78, 79, 80, 103, - 81, 116, 116, 81, 103, 103, 35, 108, 111, 32, - 20, 43, 69, 103, 43, 69, 42, 112, 45, 108, - 67, 68, 77, 85, 95, 103, 95, 103, 42, 69, - 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, - 103, 103, 103, 103, 103, 103, 77, 20, 103, 100, - 20, 103, 103, 43, 69, 103, 103, 103, 100, 103, - 103, 103, 77, 101, 77, 84, 100, 103, 20, 113, - 103, 103, 113, 102, 23, 43, 102, 87, 104, 81, - 84, 84, 84, 88, 101, 43, 103, 101, 101, 20, - 81, 114, 114, 26, 103, 101, 26, 47, 100, 25, - 47, 92, 93, 99, 20, 102, 102, 40, 101, 102, - 31, 102, 91, 92, 94, 99, 100, 93, 101, 95, - 102, 101, 101, 94, 101, 95, 87, 47, 90, 42, - 101, 26, 26, 26, 42, 101, 100, 101, 100, 26, - 40, 31, 31, 102, 87, 88, 40, 101, 101 + 0, 1, 12, 13, 14, 15, 17, 18, 19, 20, + 21, 22, 28, 30, 31, 32, 35, 40, 41, 42, + 45, 49, 50, 51, 52, 55, 64, 69, 74, 75, + 80, 86, 88, 89, 90, 91, 92, 99, 100, 101, + 106, 107, 108, 111, 114, 115, 120, 79, 87, 21, + 40, 89, 107, 107, 107, 107, 121, 80, 103, 41, + 21, 109, 29, 34, 36, 37, 38, 39, 107, 112, + 113, 21, 107, 46, 47, 13, 14, 21, 21, 21, + 21, 107, 53, 112, 113, 21, 110, 107, 107, 107, + 107, 107, 0, 87, 80, 72, 80, 98, 107, 107, + 56, 57, 61, 62, 63, 65, 66, 67, 68, 73, + 74, 75, 76, 77, 82, 83, 107, 84, 121, 121, + 84, 107, 107, 36, 113, 116, 33, 21, 44, 72, + 107, 44, 72, 43, 117, 46, 113, 84, 70, 71, + 81, 88, 98, 107, 98, 107, 43, 72, 104, 107, + 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, + 107, 107, 107, 107, 107, 107, 81, 21, 107, 103, + 21, 107, 107, 44, 72, 107, 107, 103, 107, 21, + 107, 107, 81, 104, 81, 87, 103, 107, 21, 118, + 107, 107, 118, 105, 24, 44, 105, 90, 108, 84, + 87, 87, 87, 91, 104, 44, 107, 104, 104, 21, + 84, 119, 119, 27, 107, 104, 27, 48, 103, 26, + 48, 95, 96, 102, 21, 105, 105, 41, 104, 105, + 32, 105, 94, 95, 97, 102, 103, 96, 104, 98, + 105, 104, 104, 97, 104, 98, 90, 48, 93, 43, + 104, 27, 27, 27, 43, 104, 103, 104, 103, 27, + 41, 32, 32, 105, 90, 91, 41, 104, 104 }; #define yyerrok (yyerrstatus = 0) @@ -1839,12 +1869,12 @@ yyreduce: switch (yyn) { case 4: -#line 105 "engines/director/lingo/lingo-gr.y" +#line 107 "engines/director/lingo/lingo-gr.y" { yyerrok; ;} break; case 5: -#line 108 "engines/director/lingo/lingo-gr.y" +#line 110 "engines/director/lingo/lingo-gr.y" { g_lingo->_linenumber++; g_lingo->_colnumber = 1; @@ -1852,12 +1882,12 @@ yyreduce: break; case 10: -#line 117 "engines/director/lingo/lingo-gr.y" +#line 119 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_xpop); ;} break; case 12: -#line 121 "engines/director/lingo/lingo-gr.y" +#line 123 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_varpush); g_lingo->codeString((yyvsp[(4) - (4)].s)->c_str()); @@ -1867,7 +1897,7 @@ yyreduce: break; case 13: -#line 127 "engines/director/lingo/lingo-gr.y" +#line 129 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_varpush); g_lingo->codeString((yyvsp[(2) - (4)].s)->c_str()); @@ -1877,7 +1907,7 @@ yyreduce: break; case 14: -#line 133 "engines/director/lingo/lingo-gr.y" +#line 135 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(0); // Put dummy id g_lingo->code1(g_lingo->c_theentityassign); @@ -1889,7 +1919,7 @@ yyreduce: break; case 15: -#line 141 "engines/director/lingo/lingo-gr.y" +#line 143 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_swap); g_lingo->code1(g_lingo->c_theentityassign); @@ -1901,7 +1931,7 @@ yyreduce: break; case 16: -#line 149 "engines/director/lingo/lingo-gr.y" +#line 151 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_varpush); g_lingo->codeString((yyvsp[(2) - (4)].s)->c_str()); @@ -1911,7 +1941,7 @@ yyreduce: break; case 17: -#line 155 "engines/director/lingo/lingo-gr.y" +#line 157 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(0); // Put dummy id g_lingo->code1(g_lingo->c_theentityassign); @@ -1923,7 +1953,7 @@ yyreduce: break; case 18: -#line 163 "engines/director/lingo/lingo-gr.y" +#line 165 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_swap); g_lingo->code1(g_lingo->c_theentityassign); @@ -1935,12 +1965,12 @@ yyreduce: break; case 19: -#line 172 "engines/director/lingo/lingo-gr.y" +#line 174 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_xpop); ;} break; case 23: -#line 181 "engines/director/lingo/lingo-gr.y" +#line 183 "engines/director/lingo/lingo-gr.y" { inst body = 0, end = 0; WRITE_UINT32(&body, (yyvsp[(5) - (8)].code)); @@ -1950,7 +1980,7 @@ yyreduce: break; case 24: -#line 192 "engines/director/lingo/lingo-gr.y" +#line 194 "engines/director/lingo/lingo-gr.y" { inst init = 0, finish = 0, body = 0, end = 0, inc = 0; WRITE_UINT32(&init, (yyvsp[(3) - (11)].code)); @@ -1966,7 +1996,7 @@ yyreduce: break; case 25: -#line 208 "engines/director/lingo/lingo-gr.y" +#line 210 "engines/director/lingo/lingo-gr.y" { inst init = 0, finish = 0, body = 0, end = 0, inc = 0; WRITE_UINT32(&init, (yyvsp[(3) - (12)].code)); @@ -1982,14 +2012,17 @@ yyreduce: break; case 26: -#line 220 "engines/director/lingo/lingo-gr.y" +#line 222 "engines/director/lingo/lingo-gr.y" { - g_lingo->code1(g_lingo->c_ifcode); + inst end = 0; + WRITE_UINT32(&end, (yyvsp[(3) - (3)].code)); + g_lingo->code1(STOP); + (*g_lingo->_currentScript)[(yyvsp[(1) - (3)].code) + 1] = end; ;} break; case 27: -#line 225 "engines/director/lingo/lingo-gr.y" +#line 230 "engines/director/lingo/lingo-gr.y" { inst then = 0, end = 0; WRITE_UINT32(&then, (yyvsp[(5) - (8)].code)); @@ -2000,7 +2033,7 @@ yyreduce: break; case 28: -#line 232 "engines/director/lingo/lingo-gr.y" +#line 237 "engines/director/lingo/lingo-gr.y" { inst then = 0, else1 = 0, end = 0; WRITE_UINT32(&then, (yyvsp[(5) - (11)].code)); @@ -2013,7 +2046,7 @@ yyreduce: break; case 29: -#line 241 "engines/director/lingo/lingo-gr.y" +#line 246 "engines/director/lingo/lingo-gr.y" { inst then = 0, else1 = 0, end = 0; WRITE_UINT32(&then, (yyvsp[(5) - (11)].code)); @@ -2026,7 +2059,7 @@ yyreduce: break; case 30: -#line 250 "engines/director/lingo/lingo-gr.y" +#line 255 "engines/director/lingo/lingo-gr.y" { inst then = 0, else1 = 0, end = 0; WRITE_UINT32(&then, (yyvsp[(4) - (6)].code)); @@ -2040,7 +2073,7 @@ yyreduce: break; case 31: -#line 260 "engines/director/lingo/lingo-gr.y" +#line 265 "engines/director/lingo/lingo-gr.y" { inst then = 0, else1 = 0, end = 0; WRITE_UINT32(&then, (yyvsp[(4) - (10)].code)); @@ -2054,7 +2087,7 @@ yyreduce: break; case 32: -#line 270 "engines/director/lingo/lingo-gr.y" +#line 275 "engines/director/lingo/lingo-gr.y" { inst then = 0, else1 = 0, end = 0; WRITE_UINT32(&then, (yyvsp[(4) - (10)].code)); @@ -2068,17 +2101,17 @@ yyreduce: break; case 33: -#line 281 "engines/director/lingo/lingo-gr.y" +#line 286 "engines/director/lingo/lingo-gr.y" { (yyval.code) = 0; ;} break; case 34: -#line 282 "engines/director/lingo/lingo-gr.y" +#line 287 "engines/director/lingo/lingo-gr.y" { (yyval.code) = (yyvsp[(2) - (3)].code); ;} break; case 39: -#line 293 "engines/director/lingo/lingo-gr.y" +#line 298 "engines/director/lingo/lingo-gr.y" { inst then = 0; WRITE_UINT32(&then, (yyvsp[(4) - (6)].code)); @@ -2088,7 +2121,7 @@ yyreduce: break; case 41: -#line 302 "engines/director/lingo/lingo-gr.y" +#line 307 "engines/director/lingo/lingo-gr.y" { inst then = 0; WRITE_UINT32(&then, (yyvsp[(4) - (5)].code)); @@ -2098,22 +2131,22 @@ yyreduce: break; case 42: -#line 310 "engines/director/lingo/lingo-gr.y" +#line 315 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(STOP); ;} break; case 43: -#line 311 "engines/director/lingo/lingo-gr.y" +#line 316 "engines/director/lingo/lingo-gr.y" { g_lingo->code2(g_lingo->c_eq, STOP); ;} break; case 45: -#line 314 "engines/director/lingo/lingo-gr.y" +#line 319 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code3(g_lingo->c_repeatwhilecode, STOP, STOP); ;} break; case 46: -#line 316 "engines/director/lingo/lingo-gr.y" +#line 321 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code3(g_lingo->c_repeatwithcode, STOP, STOP); g_lingo->code3(STOP, STOP, STOP); @@ -2122,7 +2155,7 @@ yyreduce: break; case 47: -#line 322 "engines/director/lingo/lingo-gr.y" +#line 327 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code1(g_lingo->c_ifcode); g_lingo->code3(STOP, STOP, STOP); @@ -2131,7 +2164,7 @@ yyreduce: break; case 48: -#line 328 "engines/director/lingo/lingo-gr.y" +#line 333 "engines/director/lingo/lingo-gr.y" { inst skipEnd; WRITE_UINT32(&skipEnd, 1); // We have to skip end to avoid multiple executions @@ -2141,64 +2174,72 @@ yyreduce: break; case 49: -#line 335 "engines/director/lingo/lingo-gr.y" +#line 340 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->_currentScript->size(); ;} break; case 50: -#line 337 "engines/director/lingo/lingo-gr.y" +#line 342 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(STOP); (yyval.code) = g_lingo->_currentScript->size(); ;} break; case 51: -#line 339 "engines/director/lingo/lingo-gr.y" +#line 344 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->_currentScript->size(); ;} break; case 54: -#line 344 "engines/director/lingo/lingo-gr.y" - { (yyval.code) = g_lingo->codeConst((yyvsp[(1) - (1)].i)); ;} +#line 349 "engines/director/lingo/lingo-gr.y" + { + (yyval.code) = g_lingo->code1(g_lingo->c_whencode); + g_lingo->code1(STOP); + g_lingo->codeString((yyvsp[(2) - (3)].s)->c_str()); + delete (yyvsp[(2) - (3)].s); ;} break; case 55: -#line 345 "engines/director/lingo/lingo-gr.y" +#line 355 "engines/director/lingo/lingo-gr.y" + { (yyval.code) = g_lingo->codeConst((yyvsp[(1) - (1)].i)); ;} + break; + + case 56: +#line 356 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code1(g_lingo->c_fconstpush); g_lingo->codeFloat((yyvsp[(1) - (1)].f)); ;} break; - case 56: -#line 348 "engines/director/lingo/lingo-gr.y" + case 57: +#line 359 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code1(g_lingo->c_stringpush); g_lingo->codeString((yyvsp[(1) - (1)].s)->c_str()); ;} break; - case 57: -#line 351 "engines/director/lingo/lingo-gr.y" + case 58: +#line 362 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code1(g_lingo->_handlers[*(yyvsp[(1) - (1)].s)]->u.func); - g_lingo->codeConst(0); // Put dummy value delete (yyvsp[(1) - (1)].s); ;} break; - case 58: -#line 355 "engines/director/lingo/lingo-gr.y" + case 59: +#line 365 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->codeFunc((yyvsp[(1) - (4)].s), (yyvsp[(3) - (4)].narg)); delete (yyvsp[(1) - (4)].s); ;} break; - case 59: -#line 358 "engines/director/lingo/lingo-gr.y" + case 60: +#line 368 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code1(g_lingo->c_eval); g_lingo->codeString((yyvsp[(1) - (1)].s)->c_str()); delete (yyvsp[(1) - (1)].s); ;} break; - case 60: -#line 362 "engines/director/lingo/lingo-gr.y" + case 61: +#line 372 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->codeConst(0); // Put dummy id g_lingo->code1(g_lingo->c_theentitypush); @@ -2208,8 +2249,8 @@ yyreduce: g_lingo->code2(e, f); ;} break; - case 61: -#line 369 "engines/director/lingo/lingo-gr.y" + case 62: +#line 379 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code1(g_lingo->c_theentitypush); inst e = 0, f = 0; @@ -2218,237 +2259,257 @@ yyreduce: g_lingo->code2(e, f); ;} break; - case 63: -#line 376 "engines/director/lingo/lingo-gr.y" + case 64: +#line 386 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_add); ;} break; - case 64: -#line 377 "engines/director/lingo/lingo-gr.y" + case 65: +#line 387 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_sub); ;} break; - case 65: -#line 378 "engines/director/lingo/lingo-gr.y" + case 66: +#line 388 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_mul); ;} break; - case 66: -#line 379 "engines/director/lingo/lingo-gr.y" + case 67: +#line 389 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_div); ;} break; - case 67: -#line 380 "engines/director/lingo/lingo-gr.y" + case 68: +#line 390 "engines/director/lingo/lingo-gr.y" + { g_lingo->code1(g_lingo->c_mod); ;} + break; + + case 69: +#line 391 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_gt); ;} break; - case 68: -#line 381 "engines/director/lingo/lingo-gr.y" + case 70: +#line 392 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_lt); ;} break; - case 69: -#line 382 "engines/director/lingo/lingo-gr.y" + case 71: +#line 393 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_neq); ;} break; - case 70: -#line 383 "engines/director/lingo/lingo-gr.y" + case 72: +#line 394 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_ge); ;} break; - case 71: -#line 384 "engines/director/lingo/lingo-gr.y" + case 73: +#line 395 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_le); ;} break; - case 72: -#line 385 "engines/director/lingo/lingo-gr.y" + case 74: +#line 396 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_and); ;} break; - case 73: -#line 386 "engines/director/lingo/lingo-gr.y" + case 75: +#line 397 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_or); ;} break; - case 74: -#line 387 "engines/director/lingo/lingo-gr.y" + case 76: +#line 398 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_not); ;} break; - case 75: -#line 388 "engines/director/lingo/lingo-gr.y" + case 77: +#line 399 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_ampersand); ;} break; - case 76: -#line 389 "engines/director/lingo/lingo-gr.y" + case 78: +#line 400 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_concat); ;} break; - case 77: -#line 390 "engines/director/lingo/lingo-gr.y" + case 79: +#line 401 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_contains); ;} break; - case 78: -#line 391 "engines/director/lingo/lingo-gr.y" + case 80: +#line 402 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_starts); ;} break; - case 79: -#line 392 "engines/director/lingo/lingo-gr.y" + case 81: +#line 403 "engines/director/lingo/lingo-gr.y" { (yyval.code) = (yyvsp[(2) - (2)].code); ;} break; - case 80: -#line 393 "engines/director/lingo/lingo-gr.y" + case 82: +#line 404 "engines/director/lingo/lingo-gr.y" { (yyval.code) = (yyvsp[(2) - (2)].code); g_lingo->code1(g_lingo->c_negate); ;} break; - case 81: -#line 394 "engines/director/lingo/lingo-gr.y" + case 83: +#line 405 "engines/director/lingo/lingo-gr.y" { (yyval.code) = (yyvsp[(2) - (3)].code); ;} break; - case 82: -#line 395 "engines/director/lingo/lingo-gr.y" + case 84: +#line 406 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_intersects); ;} break; - case 83: -#line 396 "engines/director/lingo/lingo-gr.y" + case 85: +#line 407 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_within); ;} break; - case 84: -#line 399 "engines/director/lingo/lingo-gr.y" + case 86: +#line 410 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_printtop); ;} break; - case 87: -#line 402 "engines/director/lingo/lingo-gr.y" + case 89: +#line 413 "engines/director/lingo/lingo-gr.y" + { g_lingo->code1(g_lingo->c_exitRepeat); ;} + break; + + case 90: +#line 414 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(0); // Push fake value on stack g_lingo->code1(g_lingo->c_procret); ;} break; - case 89: -#line 405 "engines/director/lingo/lingo-gr.y" + case 93: +#line 418 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->_handlers[*(yyvsp[(1) - (2)].s)]->u.func); delete (yyvsp[(1) - (2)].s); ;} break; - case 90: -#line 408 "engines/director/lingo/lingo-gr.y" + case 94: +#line 421 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->_handlers[*(yyvsp[(1) - (2)].s)]->u.func); delete (yyvsp[(1) - (2)].s); ;} break; - case 91: -#line 411 "engines/director/lingo/lingo-gr.y" + case 95: +#line 424 "engines/director/lingo/lingo-gr.y" { g_lingo->code2(g_lingo->c_voidpush, g_lingo->_handlers[*(yyvsp[(1) - (1)].s)]->u.func); delete (yyvsp[(1) - (1)].s); ;} break; - case 92: -#line 414 "engines/director/lingo/lingo-gr.y" + case 96: +#line 427 "engines/director/lingo/lingo-gr.y" { g_lingo->codeFunc((yyvsp[(1) - (2)].s), (yyvsp[(2) - (2)].narg)); ;} break; - case 93: -#line 415 "engines/director/lingo/lingo-gr.y" + case 97: +#line 428 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_open); ;} break; - case 94: -#line 416 "engines/director/lingo/lingo-gr.y" + case 98: +#line 429 "engines/director/lingo/lingo-gr.y" { g_lingo->code2(g_lingo->c_voidpush, g_lingo->c_open); ;} break; - case 95: -#line 419 "engines/director/lingo/lingo-gr.y" + case 99: +#line 432 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_global); g_lingo->codeString((yyvsp[(1) - (1)].s)->c_str()); delete (yyvsp[(1) - (1)].s); ;} break; - case 96: -#line 420 "engines/director/lingo/lingo-gr.y" + case 100: +#line 433 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_global); g_lingo->codeString((yyvsp[(3) - (3)].s)->c_str()); delete (yyvsp[(3) - (3)].s); ;} break; - case 97: -#line 431 "engines/director/lingo/lingo-gr.y" + case 101: +#line 436 "engines/director/lingo/lingo-gr.y" + { g_lingo->code1(g_lingo->c_instance); g_lingo->codeString((yyvsp[(1) - (1)].s)->c_str()); delete (yyvsp[(1) - (1)].s); ;} + break; + + case 102: +#line 437 "engines/director/lingo/lingo-gr.y" + { g_lingo->code1(g_lingo->c_instance); g_lingo->codeString((yyvsp[(3) - (3)].s)->c_str()); delete (yyvsp[(3) - (3)].s); ;} + break; + + case 103: +#line 448 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_gotoloop); ;} break; - case 98: -#line 432 "engines/director/lingo/lingo-gr.y" + case 104: +#line 449 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_gotonext); ;} break; - case 99: -#line 433 "engines/director/lingo/lingo-gr.y" + case 105: +#line 450 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_gotoprevious); ;} break; - case 100: -#line 434 "engines/director/lingo/lingo-gr.y" + case 106: +#line 451 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(1); g_lingo->code1(g_lingo->c_goto); ;} break; - case 101: -#line 437 "engines/director/lingo/lingo-gr.y" + case 107: +#line 454 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(3); g_lingo->code1(g_lingo->c_goto); ;} break; - case 102: -#line 440 "engines/director/lingo/lingo-gr.y" + case 108: +#line 457 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(2); g_lingo->code1(g_lingo->c_goto); ;} break; - case 107: -#line 453 "engines/director/lingo/lingo-gr.y" + case 113: +#line 470 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_playdone); ;} break; - case 108: -#line 454 "engines/director/lingo/lingo-gr.y" + case 114: +#line 471 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(1); g_lingo->code1(g_lingo->c_play); ;} break; - case 109: -#line 457 "engines/director/lingo/lingo-gr.y" + case 115: +#line 474 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(3); g_lingo->code1(g_lingo->c_play); ;} break; - case 110: -#line 460 "engines/director/lingo/lingo-gr.y" + case 116: +#line 477 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(2); g_lingo->code1(g_lingo->c_play); ;} break; - case 111: -#line 490 "engines/director/lingo/lingo-gr.y" + case 117: +#line 507 "engines/director/lingo/lingo-gr.y" { g_lingo->_indef = true; g_lingo->_currentFactory.clear(); ;} break; - case 112: -#line 491 "engines/director/lingo/lingo-gr.y" + case 118: +#line 508 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(0); // Push fake value on stack g_lingo->code1(g_lingo->c_procret); @@ -2456,54 +2517,53 @@ yyreduce: g_lingo->_indef = false; ;} break; - case 113: -#line 496 "engines/director/lingo/lingo-gr.y" + case 119: +#line 513 "engines/director/lingo/lingo-gr.y" { g_lingo->codeFactory(*(yyvsp[(2) - (2)].s)); ;} break; - case 114: -#line 499 "engines/director/lingo/lingo-gr.y" + case 120: +#line 516 "engines/director/lingo/lingo-gr.y" { g_lingo->_indef = true; ;} break; - case 115: -#line 500 "engines/director/lingo/lingo-gr.y" + case 121: +#line 517 "engines/director/lingo/lingo-gr.y" { - g_lingo->codeConst(0); // Push fake value on stack g_lingo->code1(g_lingo->c_procret); - g_lingo->define(*(yyvsp[(2) - (8)].s), (yyvsp[(4) - (8)].code), (yyvsp[(5) - (8)].narg), &g_lingo->_currentFactory); + g_lingo->define(*(yyvsp[(2) - (8)].s), (yyvsp[(4) - (8)].code), (yyvsp[(5) - (8)].narg) + 1, &g_lingo->_currentFactory); g_lingo->_indef = false; ;} break; - case 116: -#line 505 "engines/director/lingo/lingo-gr.y" + case 122: +#line 521 "engines/director/lingo/lingo-gr.y" { (yyval.narg) = 0; ;} break; - case 117: -#line 506 "engines/director/lingo/lingo-gr.y" + case 123: +#line 522 "engines/director/lingo/lingo-gr.y" { g_lingo->codeArg((yyvsp[(1) - (1)].s)); (yyval.narg) = 1; ;} break; - case 118: -#line 507 "engines/director/lingo/lingo-gr.y" + case 124: +#line 523 "engines/director/lingo/lingo-gr.y" { g_lingo->codeArg((yyvsp[(3) - (3)].s)); (yyval.narg) = (yyvsp[(1) - (3)].narg) + 1; ;} break; - case 119: -#line 508 "engines/director/lingo/lingo-gr.y" + case 125: +#line 524 "engines/director/lingo/lingo-gr.y" { g_lingo->codeArg((yyvsp[(4) - (4)].s)); (yyval.narg) = (yyvsp[(1) - (4)].narg) + 1; ;} break; - case 120: -#line 510 "engines/director/lingo/lingo-gr.y" + case 126: +#line 526 "engines/director/lingo/lingo-gr.y" { g_lingo->codeArgStore(); ;} break; - case 121: -#line 514 "engines/director/lingo/lingo-gr.y" + case 127: +#line 530 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_call); g_lingo->codeString((yyvsp[(1) - (3)].s)->c_str()); @@ -2512,24 +2572,24 @@ yyreduce: g_lingo->code1(numpar); ;} break; - case 122: -#line 522 "engines/director/lingo/lingo-gr.y" + case 128: +#line 538 "engines/director/lingo/lingo-gr.y" { (yyval.narg) = 0; ;} break; - case 123: -#line 523 "engines/director/lingo/lingo-gr.y" + case 129: +#line 539 "engines/director/lingo/lingo-gr.y" { (yyval.narg) = 1; ;} break; - case 124: -#line 524 "engines/director/lingo/lingo-gr.y" + case 130: +#line 540 "engines/director/lingo/lingo-gr.y" { (yyval.narg) = (yyvsp[(1) - (3)].narg) + 1; ;} break; /* Line 1267 of yacc.c. */ -#line 2533 "engines/director/lingo/lingo-gr.cpp" +#line 2593 "engines/director/lingo/lingo-gr.cpp" default: break; } YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); @@ -2743,6 +2803,6 @@ yyreturn: } -#line 527 "engines/director/lingo/lingo-gr.y" +#line 543 "engines/director/lingo/lingo-gr.y" diff --git a/engines/director/lingo/lingo-gr.h b/engines/director/lingo/lingo-gr.h index de14709531..46097085fe 100644 --- a/engines/director/lingo/lingo-gr.h +++ b/engines/director/lingo/lingo-gr.h @@ -47,64 +47,67 @@ RECT = 263, ARRAY = 264, SYMBOL = 265, - INT = 266, - THEENTITY = 267, - THEENTITYWITHID = 268, - FLOAT = 269, - BLTIN = 270, - BLTINNOARGS = 271, - BLTINNOARGSORONE = 272, - BLTINONEARG = 273, - BLTINARGLIST = 274, - ID = 275, - STRING = 276, - HANDLER = 277, - tDOWN = 278, - tELSE = 279, - tNLELSIF = 280, - tEND = 281, - tEXIT = 282, - tFRAME = 283, - tGLOBAL = 284, - tGO = 285, - tIF = 286, - tINTO = 287, - tLOOP = 288, - tMACRO = 289, - tMOVIE = 290, - tNEXT = 291, - tOF = 292, - tPREVIOUS = 293, - tPUT = 294, - tREPEAT = 295, - tSET = 296, - tTHEN = 297, - tTO = 298, - tWHEN = 299, - tWITH = 300, - tWHILE = 301, - tNLELSE = 302, - tFACTORY = 303, - tMETHOD = 304, - tOPEN = 305, - tPLAY = 306, - tDONE = 307, - tPLAYACCEL = 308, - tGE = 309, - tLE = 310, - tGT = 311, - tLT = 312, - tEQ = 313, - tNEQ = 314, - tAND = 315, - tOR = 316, - tNOT = 317, - tCONCAT = 318, - tCONTAINS = 319, - tSTARTS = 320, - tSPRITE = 321, - tINTERSECTS = 322, - tWITHIN = 323 + OBJECT = 266, + INT = 267, + THEENTITY = 268, + THEENTITYWITHID = 269, + FLOAT = 270, + BLTIN = 271, + BLTINNOARGS = 272, + BLTINNOARGSORONE = 273, + BLTINONEARG = 274, + BLTINARGLIST = 275, + ID = 276, + STRING = 277, + HANDLER = 278, + tDOWN = 279, + tELSE = 280, + tNLELSIF = 281, + tEND = 282, + tEXIT = 283, + tFRAME = 284, + tGLOBAL = 285, + tGO = 286, + tIF = 287, + tINTO = 288, + tLOOP = 289, + tMACRO = 290, + tMOVIE = 291, + tNEXT = 292, + tOF = 293, + tPREVIOUS = 294, + tPUT = 295, + tREPEAT = 296, + tSET = 297, + tTHEN = 298, + tTO = 299, + tWHEN = 300, + tWITH = 301, + tWHILE = 302, + tNLELSE = 303, + tFACTORY = 304, + tMETHOD = 305, + tOPEN = 306, + tPLAY = 307, + tDONE = 308, + tPLAYACCEL = 309, + tINSTANCE = 310, + tGE = 311, + tLE = 312, + tGT = 313, + tLT = 314, + tEQ = 315, + tNEQ = 316, + tAND = 317, + tOR = 318, + tNOT = 319, + tMOD = 320, + tCONCAT = 321, + tCONTAINS = 322, + tSTARTS = 323, + tSPRITE = 324, + tINTERSECTS = 325, + tWITHIN = 326 }; #endif /* Tokens. */ @@ -116,64 +119,67 @@ #define RECT 263 #define ARRAY 264 #define SYMBOL 265 -#define INT 266 -#define THEENTITY 267 -#define THEENTITYWITHID 268 -#define FLOAT 269 -#define BLTIN 270 -#define BLTINNOARGS 271 -#define BLTINNOARGSORONE 272 -#define BLTINONEARG 273 -#define BLTINARGLIST 274 -#define ID 275 -#define STRING 276 -#define HANDLER 277 -#define tDOWN 278 -#define tELSE 279 -#define tNLELSIF 280 -#define tEND 281 -#define tEXIT 282 -#define tFRAME 283 -#define tGLOBAL 284 -#define tGO 285 -#define tIF 286 -#define tINTO 287 -#define tLOOP 288 -#define tMACRO 289 -#define tMOVIE 290 -#define tNEXT 291 -#define tOF 292 -#define tPREVIOUS 293 -#define tPUT 294 -#define tREPEAT 295 -#define tSET 296 -#define tTHEN 297 -#define tTO 298 -#define tWHEN 299 -#define tWITH 300 -#define tWHILE 301 -#define tNLELSE 302 -#define tFACTORY 303 -#define tMETHOD 304 -#define tOPEN 305 -#define tPLAY 306 -#define tDONE 307 -#define tPLAYACCEL 308 -#define tGE 309 -#define tLE 310 -#define tGT 311 -#define tLT 312 -#define tEQ 313 -#define tNEQ 314 -#define tAND 315 -#define tOR 316 -#define tNOT 317 -#define tCONCAT 318 -#define tCONTAINS 319 -#define tSTARTS 320 -#define tSPRITE 321 -#define tINTERSECTS 322 -#define tWITHIN 323 +#define OBJECT 266 +#define INT 267 +#define THEENTITY 268 +#define THEENTITYWITHID 269 +#define FLOAT 270 +#define BLTIN 271 +#define BLTINNOARGS 272 +#define BLTINNOARGSORONE 273 +#define BLTINONEARG 274 +#define BLTINARGLIST 275 +#define ID 276 +#define STRING 277 +#define HANDLER 278 +#define tDOWN 279 +#define tELSE 280 +#define tNLELSIF 281 +#define tEND 282 +#define tEXIT 283 +#define tFRAME 284 +#define tGLOBAL 285 +#define tGO 286 +#define tIF 287 +#define tINTO 288 +#define tLOOP 289 +#define tMACRO 290 +#define tMOVIE 291 +#define tNEXT 292 +#define tOF 293 +#define tPREVIOUS 294 +#define tPUT 295 +#define tREPEAT 296 +#define tSET 297 +#define tTHEN 298 +#define tTO 299 +#define tWHEN 300 +#define tWITH 301 +#define tWHILE 302 +#define tNLELSE 303 +#define tFACTORY 304 +#define tMETHOD 305 +#define tOPEN 306 +#define tPLAY 307 +#define tDONE 308 +#define tPLAYACCEL 309 +#define tINSTANCE 310 +#define tGE 311 +#define tLE 312 +#define tGT 313 +#define tLT 314 +#define tEQ 315 +#define tNEQ 316 +#define tAND 317 +#define tOR 318 +#define tNOT 319 +#define tMOD 320 +#define tCONCAT 321 +#define tCONTAINS 322 +#define tSTARTS 323 +#define tSPRITE 324 +#define tINTERSECTS 325 +#define tWITHIN 326 @@ -191,7 +197,7 @@ typedef union YYSTYPE Common::Array<double> *arr; } /* Line 1529 of yacc.c. */ -#line 195 "engines/director/lingo/lingo-gr.hpp" +#line 201 "engines/director/lingo/lingo-gr.hpp" YYSTYPE; # define yystype YYSTYPE /* obsolescent; will be withdrawn */ # define YYSTYPE_IS_DECLARED 1 diff --git a/engines/director/lingo/lingo-gr.y b/engines/director/lingo/lingo-gr.y index 131507880f..9077f44876 100644 --- a/engines/director/lingo/lingo-gr.y +++ b/engines/director/lingo/lingo-gr.y @@ -58,7 +58,7 @@ extern int yylex(); extern int yyparse(); using namespace Director; -void yyerror(char *s) { +void yyerror(const char *s) { g_lingo->_hadError = true; warning("%s at line %d col %d", s, g_lingo->_linenumber, g_lingo->_colnumber); } @@ -77,7 +77,7 @@ void yyerror(char *s) { } %token UNARY -%token CASTREF VOID VAR POINT RECT ARRAY SYMBOL +%token CASTREF VOID VAR POINT RECT ARRAY SYMBOL OBJECT %token<i> INT %token<e> THEENTITY THEENTITYWITHID %token<f> FLOAT @@ -85,17 +85,19 @@ void yyerror(char *s) { %token<s> ID STRING HANDLER %token tDOWN tELSE tNLELSIF tEND tEXIT tFRAME tGLOBAL tGO tIF tINTO tLOOP tMACRO %token tMOVIE tNEXT tOF tPREVIOUS tPUT tREPEAT tSET tTHEN tTO tWHEN -%token tWITH tWHILE tNLELSE tFACTORY tMETHOD tOPEN tPLAY tDONE tPLAYACCEL -%token tGE tLE tGT tLT tEQ tNEQ tAND tOR tNOT +%token tWITH tWHILE tNLELSE tFACTORY tMETHOD tOPEN tPLAY tDONE tPLAYACCEL tINSTANCE +%token tGE tLE tGT tLT tEQ tNEQ tAND tOR tNOT tMOD %token tCONCAT tCONTAINS tSTARTS %token tSPRITE tINTERSECTS tWITHIN -%type<code> asgn begin elseif elsestmtoneliner end expr if repeatwhile repeatwith stmtlist +%type<code> asgn begin elseif elsestmtoneliner end expr if when repeatwhile repeatwith stmtlist %type<narg> argdef arglist %right '=' +%left tLT tLE tGT tGE tNEQ tCONTAINS tSTARTS +%left '&' %left '+' '-' -%left '*' '/' '%' +%left '*' '/' '%' tAND tOR tMOD %right UNARY %% @@ -217,8 +219,11 @@ stmt: stmtoneliner (*g_lingo->_currentScript)[$1 + 3] = body; /* body of loop */ (*g_lingo->_currentScript)[$1 + 4] = inc; /* increment */ (*g_lingo->_currentScript)[$1 + 5] = end; } /* end, if cond fails */ - | tWHEN ID tTHEN expr { - g_lingo->code1(g_lingo->c_ifcode); + | when expr end { + inst end = 0; + WRITE_UINT32(&end, $3); + g_lingo->code1(STOP); + (*g_lingo->_currentScript)[$1 + 1] = end; } ; @@ -341,6 +346,12 @@ stmtlist: /* nothing */ { $$ = g_lingo->_currentScript->size(); } | stmtlist stmt ; +when: tWHEN ID tTHEN { + $$ = g_lingo->code1(g_lingo->c_whencode); + g_lingo->code1(STOP); + g_lingo->codeString($2->c_str()); + delete $2; } + expr: INT { $$ = g_lingo->codeConst($1); } | FLOAT { $$ = g_lingo->code1(g_lingo->c_fconstpush); @@ -350,7 +361,6 @@ expr: INT { $$ = g_lingo->codeConst($1); } g_lingo->codeString($1->c_str()); } | BLTINNOARGS { $$ = g_lingo->code1(g_lingo->_handlers[*$1]->u.func); - g_lingo->codeConst(0); // Put dummy value delete $1; } | ID '(' arglist ')' { $$ = g_lingo->codeFunc($1, $3); @@ -377,6 +387,7 @@ expr: INT { $$ = g_lingo->codeConst($1); } | expr '-' expr { g_lingo->code1(g_lingo->c_sub); } | expr '*' expr { g_lingo->code1(g_lingo->c_mul); } | expr '/' expr { g_lingo->code1(g_lingo->c_div); } + | expr tMOD expr { g_lingo->code1(g_lingo->c_mod); } | expr '>' expr { g_lingo->code1(g_lingo->c_gt); } | expr '<' expr { g_lingo->code1(g_lingo->c_lt); } | expr tNEQ expr { g_lingo->code1(g_lingo->c_neq); } @@ -399,9 +410,11 @@ expr: INT { $$ = g_lingo->codeConst($1); } func: tPUT expr { g_lingo->code1(g_lingo->c_printtop); } | gotofunc | playfunc + | tEXIT tREPEAT { g_lingo->code1(g_lingo->c_exitRepeat); } | tEXIT { g_lingo->codeConst(0); // Push fake value on stack g_lingo->code1(g_lingo->c_procret); } | tGLOBAL globallist + | tINSTANCE instancelist | BLTINONEARG expr { g_lingo->code1(g_lingo->_handlers[*$1]->u.func); delete $1; } @@ -420,6 +433,10 @@ globallist: ID { g_lingo->code1(g_lingo->c_global); g_lingo->codeString($1->c | globallist ',' ID { g_lingo->code1(g_lingo->c_global); g_lingo->codeString($3->c_str()); delete $3; } ; +instancelist: ID { g_lingo->code1(g_lingo->c_instance); g_lingo->codeString($1->c_str()); delete $1; } + | instancelist ',' ID { g_lingo->code1(g_lingo->c_instance); g_lingo->codeString($3->c_str()); delete $3; } + ; + // go {to} {frame} whichFrame {of movie whichMovie} // go {to} {frame "Open23" of} movie whichMovie // go loop @@ -498,9 +515,8 @@ defn: tMACRO ID { g_lingo->_indef = true; g_lingo->_currentFactory.clear(); } } | tMETHOD ID { g_lingo->_indef = true; } begin argdef nl argstore stmtlist { - g_lingo->codeConst(0); // Push fake value on stack g_lingo->code1(g_lingo->c_procret); - g_lingo->define(*$2, $4, $5, &g_lingo->_currentFactory); + g_lingo->define(*$2, $4, $5 + 1, &g_lingo->_currentFactory); g_lingo->_indef = false; } ; argdef: /* nothing */ { $$ = 0; } | ID { g_lingo->codeArg($1); $$ = 1; } diff --git a/engines/director/lingo/lingo-lex.cpp b/engines/director/lingo/lingo-lex.cpp index 5fbd8d8653..ebdb169a65 100644 --- a/engines/director/lingo/lingo-lex.cpp +++ b/engines/director/lingo/lingo-lex.cpp @@ -364,8 +364,8 @@ static void yy_fatal_error (yyconst char msg[] ); *yy_cp = '\0'; \ (yy_c_buf_p) = yy_cp; -#define YY_NUM_RULES 56 -#define YY_END_OF_BUFFER 57 +#define YY_NUM_RULES 59 +#define YY_END_OF_BUFFER 60 /* This struct is not used in this scanner, but its presence is necessary. */ struct yy_trans_info @@ -373,28 +373,31 @@ struct yy_trans_info flex_int32_t yy_verify; flex_int32_t yy_nxt; }; -static yyconst flex_int16_t yy_accept[191] = +static yyconst flex_int16_t yy_accept[206] = { 0, - 0, 0, 57, 55, 3, 53, 53, 55, 55, 52, - 52, 52, 51, 52, 52, 49, 49, 49, 49, 49, - 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, - 49, 49, 2, 2, 3, 53, 0, 0, 0, 0, - 0, 54, 48, 1, 50, 51, 47, 45, 46, 49, - 49, 49, 49, 49, 49, 49, 49, 49, 49, 18, - 8, 49, 49, 49, 49, 49, 49, 49, 27, 49, - 29, 49, 49, 49, 49, 49, 49, 49, 49, 39, - 49, 49, 2, 2, 0, 1, 50, 4, 49, 49, - 49, 49, 12, 49, 49, 49, 49, 0, 49, 49, - - 49, 49, 49, 49, 26, 49, 49, 49, 32, 49, - 34, 49, 49, 49, 49, 49, 49, 0, 49, 6, - 7, 11, 14, 49, 49, 49, 0, 49, 20, 21, - 49, 49, 49, 25, 28, 30, 49, 49, 49, 49, - 0, 38, 43, 49, 41, 10, 49, 49, 15, 49, - 17, 49, 22, 49, 24, 49, 49, 49, 49, 37, - 44, 49, 0, 49, 49, 16, 49, 23, 49, 33, - 40, 35, 0, 42, 0, 49, 13, 49, 49, 0, - 9, 5, 49, 31, 0, 49, 0, 19, 36, 0 + 0, 0, 60, 58, 3, 56, 56, 58, 58, 55, + 55, 55, 54, 55, 55, 52, 52, 52, 52, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, + 52, 52, 2, 2, 3, 56, 0, 0, 0, 0, + 0, 57, 51, 1, 53, 54, 50, 48, 49, 52, + 52, 52, 52, 52, 52, 52, 52, 52, 52, 18, + 8, 52, 52, 52, 52, 52, 52, 52, 29, 52, + 31, 52, 52, 52, 52, 52, 52, 52, 52, 42, + 52, 52, 2, 2, 0, 1, 53, 4, 52, 52, + 52, 52, 12, 52, 52, 52, 52, 0, 52, 52, + + 52, 52, 52, 25, 52, 52, 28, 52, 52, 52, + 34, 52, 36, 52, 52, 52, 52, 52, 52, 0, + 52, 6, 7, 11, 14, 52, 52, 52, 0, 52, + 52, 21, 22, 52, 52, 52, 27, 30, 32, 52, + 52, 52, 52, 0, 41, 46, 52, 44, 10, 52, + 52, 15, 52, 17, 52, 52, 23, 52, 26, 52, + 52, 52, 52, 40, 40, 47, 52, 0, 52, 52, + 16, 52, 52, 24, 52, 35, 43, 37, 0, 40, + 45, 0, 52, 13, 52, 52, 52, 0, 40, 9, + 5, 19, 52, 33, 0, 40, 52, 0, 0, 20, + + 39, 0, 0, 38, 0 } ; static yyconst flex_int32_t yy_ec[256] = @@ -407,12 +410,12 @@ static yyconst flex_int32_t yy_ec[256] = 11, 11, 11, 11, 11, 11, 11, 7, 1, 12, 13, 14, 1, 1, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 24, 25, 26, 27, 28, 29, - 24, 30, 31, 32, 33, 34, 35, 36, 37, 24, - 1, 1, 1, 7, 38, 1, 39, 40, 41, 42, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 24, + 1, 1, 1, 7, 39, 1, 40, 41, 42, 43, - 43, 44, 45, 46, 47, 24, 24, 48, 49, 50, - 51, 52, 24, 53, 54, 55, 56, 57, 58, 59, - 60, 24, 1, 1, 1, 1, 1, 1, 1, 1, + 44, 45, 46, 47, 48, 24, 24, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 62, 24, 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, @@ -429,130 +432,140 @@ static yyconst flex_int32_t yy_ec[256] = 1, 1, 1, 1, 1 } ; -static yyconst flex_int32_t yy_meta[61] = +static yyconst flex_int32_t yy_meta[63] = { 0, 1, 2, 3, 3, 2, 1, 1, 1, 1, 1, 4, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5 } ; -static yyconst flex_int16_t yy_base[196] = +static yyconst flex_int16_t yy_base[211] = { 0, - 0, 59, 203, 452, 63, 67, 71, 75, 167, 452, - 157, 154, 52, 68, 143, 56, 0, 56, 57, 67, - 72, 68, 68, 69, 85, 102, 102, 104, 80, 119, - 113, 120, 173, 177, 181, 452, 185, 189, 193, 102, - 99, 452, 452, 0, 80, 129, 452, 452, 452, 0, - 91, 120, 165, 118, 126, 146, 184, 187, 171, 96, - 0, 172, 177, 189, 178, 177, 178, 184, 0, 188, - 0, 202, 199, 188, 192, 192, 199, 220, 219, 0, - 226, 208, 251, 262, 217, 0, 78, 0, 219, 227, - 230, 239, 0, 228, 229, 242, 256, 273, 257, 250, - - 251, 255, 263, 256, 0, 262, 253, 258, 0, 274, - 0, 271, 267, 304, 271, 274, 273, 284, 299, 0, - 0, 0, 0, 279, 297, 308, 297, 296, 0, 0, - 301, 304, 314, 0, 0, 0, 311, 320, 305, 307, - 156, 0, 0, 322, 319, 341, 321, 320, 0, 326, - 452, 322, 0, 327, 0, 328, 329, 344, 336, 370, - 0, 343, 375, 344, 341, 0, 345, 0, 348, 0, - 0, 0, 380, 0, 363, 355, 0, 372, 360, 372, - 452, 0, 363, 0, 394, 366, 398, 0, 400, 452, - 431, 433, 438, 442, 446 - + 0, 61, 156, 521, 65, 69, 73, 77, 148, 521, + 104, 99, 54, 70, 91, 58, 0, 58, 59, 69, + 74, 70, 70, 71, 88, 105, 105, 110, 82, 118, + 117, 130, 177, 181, 185, 521, 189, 193, 197, 106, + 94, 521, 521, 0, 82, 132, 521, 521, 521, 0, + 84, 119, 169, 116, 120, 174, 186, 191, 180, 171, + 0, 177, 183, 196, 182, 201, 181, 188, 0, 204, + 0, 209, 206, 194, 201, 207, 212, 231, 228, 0, + 233, 222, 271, 280, 230, 0, 80, 0, 230, 234, + 238, 247, 0, 235, 236, 244, 264, 293, 255, 271, + + 267, 266, 278, 0, 279, 271, 0, 279, 269, 273, + 0, 290, 0, 287, 282, 309, 289, 292, 296, 300, + 307, 0, 0, 0, 0, 296, 310, 318, 310, 324, + 311, 0, 0, 317, 318, 330, 0, 0, 0, 328, + 337, 322, 323, 363, 0, 0, 334, 334, 202, 336, + 330, 0, 338, 521, 340, 340, 0, 355, 0, 347, + 348, 364, 354, 212, 387, 0, 361, 388, 367, 359, + 0, 383, 382, 0, 369, 0, 0, 0, 402, 404, + 0, 393, 383, 0, 401, 405, 384, 403, 427, 521, + 0, 0, 395, 0, 256, 429, 401, 441, 448, 0, + + 453, 404, 459, 460, 521, 500, 502, 507, 511, 515 } ; -static yyconst flex_int16_t yy_def[196] = +static yyconst flex_int16_t yy_def[211] = { 0, - 190, 1, 190, 190, 190, 190, 190, 190, 191, 190, - 190, 190, 190, 190, 190, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 190, 190, 190, 190, 190, 190, 190, 190, - 191, 190, 190, 193, 190, 190, 190, 190, 190, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 190, 190, 190, 193, 190, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 190, 192, 192, - - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 190, 192, 192, - 192, 192, 192, 192, 192, 192, 190, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 194, 192, 192, 192, 192, 190, 192, 192, 192, 192, - 190, 192, 192, 192, 192, 192, 192, 192, 192, 194, - 192, 192, 190, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 190, 192, 190, 192, 192, 192, 192, 190, - 190, 192, 192, 192, 190, 192, 195, 192, 195, 0, - 190, 190, 190, 190, 190 - + 205, 1, 205, 205, 205, 205, 205, 205, 206, 205, + 205, 205, 205, 205, 205, 207, 207, 207, 207, 207, + 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, + 207, 207, 205, 205, 205, 205, 205, 205, 205, 205, + 206, 205, 205, 208, 205, 205, 205, 205, 205, 207, + 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, + 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, + 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, + 207, 207, 205, 205, 205, 208, 205, 207, 207, 207, + 207, 207, 207, 207, 207, 207, 207, 205, 207, 207, + + 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, + 207, 207, 207, 207, 207, 207, 207, 207, 207, 205, + 207, 207, 207, 207, 207, 207, 207, 207, 205, 207, + 207, 207, 207, 207, 207, 207, 207, 207, 207, 207, + 207, 207, 207, 209, 207, 207, 207, 207, 205, 207, + 207, 207, 207, 205, 207, 207, 207, 207, 207, 207, + 207, 207, 207, 209, 209, 207, 207, 205, 207, 207, + 207, 207, 207, 207, 207, 207, 207, 207, 205, 209, + 207, 205, 207, 207, 207, 207, 207, 205, 209, 205, + 207, 207, 207, 207, 205, 209, 207, 210, 205, 207, + + 210, 205, 205, 210, 0, 205, 205, 205, 205, 205 } ; -static yyconst flex_int16_t yy_nxt[513] = +static yyconst flex_int16_t yy_nxt[584] = { 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 4, 13, 14, 10, 15, 16, 17, 18, 19, 20, 21, - 22, 17, 23, 17, 24, 25, 26, 27, 28, 29, - 30, 31, 17, 17, 32, 17, 17, 17, 16, 17, - 18, 19, 20, 21, 22, 17, 23, 24, 25, 26, - 27, 28, 29, 30, 31, 17, 17, 32, 17, 17, - 33, 45, 46, 34, 35, 36, 36, 37, 38, 39, - 39, 38, 38, 39, 39, 38, 37, 36, 36, 37, - 47, 48, 51, 52, 53, 40, 57, 61, 87, 40, - 87, 54, 59, 55, 62, 60, 63, 98, 75, 64, - - 98, 58, 56, 65, 42, 51, 52, 53, 88, 40, - 57, 61, 66, 40, 54, 59, 55, 62, 60, 63, - 67, 69, 75, 64, 58, 56, 85, 65, 72, 68, - 70, 71, 88, 73, 79, 66, 74, 76, 45, 46, - 80, 81, 82, 93, 67, 69, 89, 77, 92, 85, - 78, 72, 68, 70, 71, 49, 73, 141, 79, 74, - 141, 76, 44, 80, 43, 81, 82, 93, 94, 89, - 77, 92, 42, 78, 83, 36, 36, 84, 84, 36, - 36, 84, 35, 36, 36, 37, 37, 36, 36, 37, - 38, 90, 94, 38, 38, 39, 39, 38, 97, 91, - - 95, 96, 190, 99, 100, 101, 106, 40, 190, 102, - 103, 40, 190, 104, 90, 105, 107, 108, 190, 109, - 110, 97, 91, 111, 95, 96, 99, 100, 112, 101, - 106, 40, 102, 103, 113, 40, 104, 114, 105, 117, - 107, 108, 109, 110, 115, 120, 111, 118, 116, 190, - 119, 112, 83, 36, 36, 84, 121, 122, 113, 123, - 124, 114, 117, 84, 36, 36, 84, 125, 115, 120, - 118, 126, 116, 119, 98, 128, 132, 98, 130, 121, - 131, 122, 123, 124, 129, 133, 190, 134, 135, 136, - 125, 137, 138, 139, 145, 126, 140, 143, 144, 128, - - 132, 130, 146, 131, 127, 141, 148, 129, 141, 133, - 134, 135, 136, 147, 137, 149, 138, 139, 145, 140, - 143, 144, 150, 190, 151, 152, 146, 127, 153, 148, - 142, 154, 155, 156, 157, 190, 158, 147, 159, 149, - 161, 162, 163, 164, 168, 163, 150, 151, 152, 165, - 166, 153, 167, 142, 154, 169, 155, 156, 157, 158, - 170, 159, 171, 178, 161, 162, 172, 164, 168, 174, - 176, 173, 165, 166, 173, 167, 163, 177, 169, 163, - 179, 173, 181, 170, 173, 182, 171, 178, 183, 172, - 184, 185, 174, 176, 186, 187, 188, 175, 187, 187, - - 177, 190, 187, 179, 190, 190, 181, 180, 182, 190, - 190, 190, 183, 184, 190, 185, 190, 186, 190, 188, - 190, 175, 190, 190, 190, 190, 190, 190, 190, 190, - 180, 41, 41, 190, 41, 41, 50, 50, 86, 86, - 190, 86, 86, 160, 190, 190, 160, 189, 190, 190, - 189, 3, 190, 190, 190, 190, 190, 190, 190, 190, - 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, - 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, - 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, - 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, - - 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, - 190, 190 + 22, 17, 23, 17, 24, 25, 26, 27, 28, 17, + 29, 30, 31, 17, 17, 32, 17, 17, 17, 16, + 17, 18, 19, 20, 21, 22, 17, 23, 24, 25, + 26, 27, 28, 17, 29, 30, 31, 17, 17, 32, + 17, 17, 33, 45, 46, 34, 35, 36, 36, 37, + 38, 39, 39, 38, 38, 39, 39, 38, 37, 36, + 36, 37, 47, 48, 51, 52, 53, 40, 57, 61, + 87, 40, 87, 54, 59, 55, 62, 60, 63, 42, + + 75, 88, 64, 49, 58, 56, 65, 44, 51, 52, + 53, 43, 40, 57, 61, 66, 40, 54, 59, 55, + 62, 60, 63, 67, 69, 75, 88, 64, 58, 56, + 85, 65, 68, 70, 72, 71, 76, 93, 79, 66, + 73, 45, 46, 74, 80, 89, 77, 92, 67, 69, + 78, 81, 82, 42, 85, 205, 68, 70, 72, 71, + 205, 76, 93, 79, 73, 205, 205, 74, 80, 89, + 77, 92, 98, 205, 78, 98, 81, 82, 83, 36, + 36, 84, 84, 36, 36, 84, 35, 36, 36, 37, + 37, 36, 36, 37, 38, 90, 94, 38, 38, 39, + + 39, 38, 95, 168, 91, 96, 168, 97, 99, 100, + 101, 40, 102, 179, 103, 40, 179, 106, 104, 90, + 107, 94, 108, 109, 110, 205, 111, 95, 91, 112, + 96, 97, 99, 100, 101, 105, 40, 102, 103, 113, + 40, 106, 114, 104, 107, 115, 116, 108, 109, 110, + 111, 117, 122, 112, 119, 118, 205, 198, 205, 105, + 198, 120, 121, 113, 123, 124, 114, 125, 126, 127, + 115, 116, 83, 36, 36, 84, 117, 122, 119, 128, + 118, 84, 36, 36, 84, 120, 121, 130, 123, 131, + 124, 125, 126, 127, 98, 133, 134, 98, 132, 135, + + 205, 136, 205, 137, 128, 138, 139, 140, 141, 142, + 144, 130, 143, 144, 131, 146, 147, 148, 149, 133, + 134, 150, 132, 151, 135, 129, 136, 137, 152, 138, + 139, 140, 153, 141, 142, 145, 143, 154, 155, 146, + 147, 156, 148, 149, 157, 158, 150, 151, 159, 129, + 160, 161, 166, 152, 162, 163, 167, 153, 169, 145, + 170, 154, 171, 155, 144, 156, 172, 144, 157, 158, + 205, 173, 174, 159, 175, 160, 161, 166, 162, 163, + 176, 167, 177, 169, 170, 178, 171, 181, 179, 168, + 172, 179, 168, 183, 165, 173, 184, 174, 175, 185, + + 186, 205, 187, 179, 176, 179, 179, 177, 179, 178, + 182, 181, 190, 205, 191, 194, 180, 183, 165, 192, + 184, 193, 195, 203, 185, 186, 187, 197, 179, 188, + 199, 179, 200, 199, 189, 182, 205, 190, 191, 194, + 180, 205, 198, 205, 192, 198, 193, 195, 203, 199, + 205, 197, 199, 188, 205, 205, 200, 205, 189, 196, + 204, 204, 205, 204, 204, 205, 205, 205, 205, 205, + 205, 205, 205, 205, 205, 202, 205, 205, 205, 205, + 205, 205, 205, 196, 205, 205, 205, 205, 205, 205, + 205, 205, 205, 205, 205, 205, 205, 205, 205, 202, + + 41, 41, 205, 41, 41, 50, 50, 86, 86, 205, + 86, 86, 164, 205, 205, 164, 201, 205, 205, 201, + 3, 205, 205, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 205 } ; -static yyconst flex_int16_t yy_chk[513] = +static yyconst flex_int16_t yy_chk[584] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -560,57 +573,64 @@ static yyconst flex_int16_t yy_chk[513] = 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, 13, 13, 2, 5, 5, 5, 5, 6, 6, - 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, - 14, 14, 16, 18, 19, 6, 21, 23, 87, 7, - 45, 20, 22, 20, 23, 22, 24, 60, 29, 25, - - 60, 21, 20, 25, 41, 16, 18, 19, 51, 6, - 21, 23, 25, 7, 20, 22, 20, 23, 22, 24, - 26, 27, 29, 25, 21, 20, 40, 25, 28, 26, - 27, 27, 51, 28, 31, 25, 28, 30, 46, 46, - 31, 32, 32, 55, 26, 27, 52, 30, 54, 40, - 30, 28, 26, 27, 27, 15, 28, 141, 31, 28, - 141, 30, 12, 31, 11, 32, 32, 55, 56, 52, - 30, 54, 9, 30, 33, 33, 33, 33, 34, 34, - 34, 34, 35, 35, 35, 35, 37, 37, 37, 37, - 38, 53, 56, 38, 39, 39, 39, 39, 59, 53, - - 57, 58, 3, 62, 63, 64, 70, 38, 0, 65, - 66, 39, 0, 67, 53, 68, 72, 73, 0, 74, - 75, 59, 53, 76, 57, 58, 62, 63, 77, 64, - 70, 38, 65, 66, 78, 39, 67, 79, 68, 82, - 72, 73, 74, 75, 81, 90, 76, 85, 81, 0, - 89, 77, 83, 83, 83, 83, 91, 92, 78, 94, - 95, 79, 82, 84, 84, 84, 84, 96, 81, 90, - 85, 97, 81, 89, 98, 99, 102, 98, 100, 91, - 101, 92, 94, 95, 99, 103, 0, 104, 106, 107, - 96, 108, 110, 112, 117, 97, 113, 115, 116, 99, - - 102, 100, 118, 101, 98, 114, 124, 99, 114, 103, - 104, 106, 107, 119, 108, 125, 110, 112, 117, 113, - 115, 116, 126, 0, 127, 128, 118, 98, 131, 124, - 114, 132, 133, 137, 138, 0, 139, 119, 140, 125, - 144, 145, 146, 147, 154, 146, 126, 127, 128, 148, - 150, 131, 152, 114, 132, 156, 133, 137, 138, 139, - 157, 140, 158, 167, 144, 145, 159, 147, 154, 162, - 164, 160, 148, 150, 160, 152, 163, 165, 156, 163, - 169, 173, 175, 157, 173, 176, 158, 167, 178, 159, - 179, 180, 162, 164, 183, 185, 186, 163, 185, 187, - - 165, 189, 187, 169, 189, 0, 175, 173, 176, 0, - 0, 0, 178, 179, 0, 180, 0, 183, 0, 186, - 0, 163, 0, 0, 0, 0, 0, 0, 0, 0, - 173, 191, 191, 0, 191, 191, 192, 192, 193, 193, - 0, 193, 193, 194, 0, 0, 194, 195, 0, 0, - 195, 190, 190, 190, 190, 190, 190, 190, 190, 190, - 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, - 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, - 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, - 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, - - 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, - 190, 190 + 1, 1, 2, 13, 13, 2, 5, 5, 5, 5, + 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, + 8, 8, 14, 14, 16, 18, 19, 6, 21, 23, + 87, 7, 45, 20, 22, 20, 23, 22, 24, 41, + + 29, 51, 25, 15, 21, 20, 25, 12, 16, 18, + 19, 11, 6, 21, 23, 25, 7, 20, 22, 20, + 23, 22, 24, 26, 27, 29, 51, 25, 21, 20, + 40, 25, 26, 27, 28, 27, 30, 55, 31, 25, + 28, 46, 46, 28, 31, 52, 30, 54, 26, 27, + 30, 32, 32, 9, 40, 3, 26, 27, 28, 27, + 0, 30, 55, 31, 28, 0, 0, 28, 31, 52, + 30, 54, 60, 0, 30, 60, 32, 32, 33, 33, + 33, 33, 34, 34, 34, 34, 35, 35, 35, 35, + 37, 37, 37, 37, 38, 53, 56, 38, 39, 39, + + 39, 39, 57, 149, 53, 58, 149, 59, 62, 62, + 63, 38, 64, 164, 65, 39, 164, 67, 66, 53, + 68, 56, 70, 72, 73, 0, 74, 57, 53, 75, + 58, 59, 62, 62, 63, 66, 38, 64, 65, 76, + 39, 67, 77, 66, 68, 78, 79, 70, 72, 73, + 74, 81, 90, 75, 82, 81, 0, 195, 0, 66, + 195, 85, 89, 76, 91, 92, 77, 94, 95, 96, + 78, 79, 83, 83, 83, 83, 81, 90, 82, 97, + 81, 84, 84, 84, 84, 85, 89, 99, 91, 100, + 92, 94, 95, 96, 98, 101, 102, 98, 100, 103, + + 0, 105, 0, 106, 97, 108, 109, 110, 112, 114, + 116, 99, 115, 116, 100, 117, 118, 119, 120, 101, + 102, 121, 100, 126, 103, 98, 105, 106, 127, 108, + 109, 110, 128, 112, 114, 116, 115, 129, 130, 117, + 118, 131, 119, 120, 134, 135, 121, 126, 136, 98, + 140, 141, 147, 127, 142, 143, 148, 128, 150, 116, + 151, 129, 153, 130, 144, 131, 155, 144, 134, 135, + 0, 156, 158, 136, 160, 140, 141, 147, 142, 143, + 161, 148, 162, 150, 151, 163, 153, 167, 165, 168, + 155, 165, 168, 169, 144, 156, 170, 158, 160, 172, + + 173, 0, 175, 179, 161, 180, 179, 162, 180, 163, + 168, 167, 182, 0, 183, 187, 165, 169, 144, 185, + 170, 186, 188, 202, 172, 173, 175, 193, 189, 179, + 196, 189, 197, 196, 180, 168, 0, 182, 183, 187, + 165, 0, 198, 0, 185, 198, 186, 188, 202, 199, + 0, 193, 199, 179, 201, 0, 197, 201, 180, 189, + 203, 204, 0, 203, 204, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 199, 0, 0, 0, 0, + 0, 0, 0, 189, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 199, + + 206, 206, 0, 206, 206, 207, 207, 208, 208, 0, + 208, 208, 209, 0, 0, 209, 210, 0, 0, 210, + 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 205 } ; static yy_state_type yy_last_accepting_state; @@ -684,7 +704,7 @@ static void countnl() { g_lingo->_colnumber = strlen(p); } -#line 688 "engines/director/lingo/lingo-lex.cpp" +#line 708 "engines/director/lingo/lingo-lex.cpp" #define INITIAL 0 @@ -872,7 +892,7 @@ YY_DECL #line 69 "engines/director/lingo/lingo-lex.l" -#line 876 "engines/director/lingo/lingo-lex.cpp" +#line 896 "engines/director/lingo/lingo-lex.cpp" if ( !(yy_init) ) { @@ -926,13 +946,13 @@ yy_match: while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; - if ( yy_current_state >= 191 ) + if ( yy_current_state >= 206 ) yy_c = yy_meta[(unsigned int) yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; ++yy_cp; } - while ( yy_base[yy_current_state] != 452 ); + while ( yy_base[yy_current_state] != 521 ); yy_find_action: yy_act = yy_accept[yy_current_state]; @@ -1051,91 +1071,113 @@ YY_RULE_SETUP case 19: YY_RULE_SETUP #line 90 "engines/director/lingo/lingo-lex.l" -{ count(); return tINTERSECTS; } +{ count(); return tINSTANCE; } YY_BREAK case 20: YY_RULE_SETUP #line 91 "engines/director/lingo/lingo-lex.l" -{ count(); return tINTO; } +{ count(); return tINTERSECTS; } YY_BREAK case 21: YY_RULE_SETUP #line 92 "engines/director/lingo/lingo-lex.l" -{ count(); return tLOOP; } +{ count(); return tINTO; } YY_BREAK case 22: YY_RULE_SETUP #line 93 "engines/director/lingo/lingo-lex.l" -{ count(); return tMACRO; } +{ count(); return tLOOP; } YY_BREAK case 23: YY_RULE_SETUP #line 94 "engines/director/lingo/lingo-lex.l" -{ count(); return tMETHOD; } +{ count(); return tMACRO; } YY_BREAK case 24: YY_RULE_SETUP #line 95 "engines/director/lingo/lingo-lex.l" -{ count(); return tMOVIE; } +{ count(); return tMETHOD; } YY_BREAK case 25: YY_RULE_SETUP #line 96 "engines/director/lingo/lingo-lex.l" -{ count(); return tNEXT; } +{ count(); return tMOD; } YY_BREAK case 26: YY_RULE_SETUP #line 97 "engines/director/lingo/lingo-lex.l" -{ count(); return tNOT; } +{ count(); return tMOVIE; } YY_BREAK case 27: YY_RULE_SETUP #line 98 "engines/director/lingo/lingo-lex.l" -{ count(); return tOF; } +{ count(); return tNEXT; } YY_BREAK case 28: YY_RULE_SETUP #line 99 "engines/director/lingo/lingo-lex.l" -{ count(); return tOPEN; } +{ count(); return tNOT; } YY_BREAK case 29: YY_RULE_SETUP #line 100 "engines/director/lingo/lingo-lex.l" -{ count(); return tOR; } +{ count(); return tOF; } YY_BREAK case 30: YY_RULE_SETUP #line 101 "engines/director/lingo/lingo-lex.l" -{ count(); return tPLAY; } +{ count(); return tOPEN; } YY_BREAK case 31: YY_RULE_SETUP #line 102 "engines/director/lingo/lingo-lex.l" -{ count(); return tPREVIOUS; } +{ count(); return tOR; } YY_BREAK case 32: YY_RULE_SETUP #line 103 "engines/director/lingo/lingo-lex.l" -{ count(); return tPUT; } +{ count(); return tPLAY; } YY_BREAK case 33: YY_RULE_SETUP #line 104 "engines/director/lingo/lingo-lex.l" -{ count(); return tREPEAT; } +{ count(); return tPREVIOUS; } YY_BREAK case 34: YY_RULE_SETUP #line 105 "engines/director/lingo/lingo-lex.l" -{ count(); return tSET; } +{ count(); return tPUT; } YY_BREAK case 35: YY_RULE_SETUP #line 106 "engines/director/lingo/lingo-lex.l" -{ count(); return tSTARTS; } +{ count(); return tREPEAT; } YY_BREAK case 36: YY_RULE_SETUP #line 107 "engines/director/lingo/lingo-lex.l" +{ count(); return tSET; } + YY_BREAK +case 37: +YY_RULE_SETUP +#line 108 "engines/director/lingo/lingo-lex.l" +{ count(); return tSTARTS; } + YY_BREAK +case 38: +YY_RULE_SETUP +#line 109 "engines/director/lingo/lingo-lex.l" +{ + count(); + + yylval.e[0] = g_lingo->_theEntities["sqrt"]->entity; + yylval.e[1] = 0; // No field + + return THEENTITYWITHID; + } + YY_BREAK +case 39: +YY_RULE_SETUP +#line 117 "engines/director/lingo/lingo-lex.l" { count(); @@ -1177,9 +1219,9 @@ YY_RULE_SETUP warning("Unhandled the entity %s", ptr); } YY_BREAK -case 37: +case 40: YY_RULE_SETUP -#line 147 "engines/director/lingo/lingo-lex.l" +#line 157 "engines/director/lingo/lingo-lex.l" { count(); @@ -1200,64 +1242,64 @@ YY_RULE_SETUP warning("Unhandled the entity %s", ptr); } YY_BREAK -case 38: +case 41: YY_RULE_SETUP -#line 166 "engines/director/lingo/lingo-lex.l" +#line 176 "engines/director/lingo/lingo-lex.l" { count(); return tTHEN; } YY_BREAK -case 39: +case 42: YY_RULE_SETUP -#line 167 "engines/director/lingo/lingo-lex.l" +#line 177 "engines/director/lingo/lingo-lex.l" { count(); return tTO; } YY_BREAK -case 40: +case 43: YY_RULE_SETUP -#line 168 "engines/director/lingo/lingo-lex.l" +#line 178 "engines/director/lingo/lingo-lex.l" { count(); return tSPRITE; } YY_BREAK -case 41: +case 44: YY_RULE_SETUP -#line 169 "engines/director/lingo/lingo-lex.l" +#line 179 "engines/director/lingo/lingo-lex.l" { count(); return tWITH; } YY_BREAK -case 42: +case 45: YY_RULE_SETUP -#line 170 "engines/director/lingo/lingo-lex.l" +#line 180 "engines/director/lingo/lingo-lex.l" { count(); return tWITHIN; } YY_BREAK -case 43: +case 46: YY_RULE_SETUP -#line 171 "engines/director/lingo/lingo-lex.l" +#line 181 "engines/director/lingo/lingo-lex.l" { count(); return tWHEN; } YY_BREAK -case 44: +case 47: YY_RULE_SETUP -#line 172 "engines/director/lingo/lingo-lex.l" +#line 182 "engines/director/lingo/lingo-lex.l" { count(); return tWHILE; } YY_BREAK -case 45: +case 48: YY_RULE_SETUP -#line 174 "engines/director/lingo/lingo-lex.l" +#line 184 "engines/director/lingo/lingo-lex.l" { count(); return tNEQ; } YY_BREAK -case 46: +case 49: YY_RULE_SETUP -#line 175 "engines/director/lingo/lingo-lex.l" +#line 185 "engines/director/lingo/lingo-lex.l" { count(); return tGE; } YY_BREAK -case 47: +case 50: YY_RULE_SETUP -#line 176 "engines/director/lingo/lingo-lex.l" +#line 186 "engines/director/lingo/lingo-lex.l" { count(); return tLE; } YY_BREAK -case 48: +case 51: YY_RULE_SETUP -#line 177 "engines/director/lingo/lingo-lex.l" +#line 187 "engines/director/lingo/lingo-lex.l" { count(); return tCONCAT; } YY_BREAK -case 49: +case 52: YY_RULE_SETUP -#line 179 "engines/director/lingo/lingo-lex.l" +#line 189 "engines/director/lingo/lingo-lex.l" { count(); yylval.s = new Common::String(yytext); @@ -1285,43 +1327,43 @@ YY_RULE_SETUP return ID; } YY_BREAK -case 50: +case 53: YY_RULE_SETUP -#line 205 "engines/director/lingo/lingo-lex.l" +#line 215 "engines/director/lingo/lingo-lex.l" { count(); yylval.f = atof(yytext); return FLOAT; } YY_BREAK -case 51: +case 54: YY_RULE_SETUP -#line 206 "engines/director/lingo/lingo-lex.l" +#line 216 "engines/director/lingo/lingo-lex.l" { count(); yylval.i = strtol(yytext, NULL, 10); return INT; } YY_BREAK -case 52: +case 55: YY_RULE_SETUP -#line 207 "engines/director/lingo/lingo-lex.l" +#line 217 "engines/director/lingo/lingo-lex.l" { count(); return *yytext; } YY_BREAK -case 53: -/* rule 53 can match eol */ +case 56: +/* rule 56 can match eol */ YY_RULE_SETUP -#line 208 "engines/director/lingo/lingo-lex.l" +#line 218 "engines/director/lingo/lingo-lex.l" { return '\n'; } YY_BREAK -case 54: +case 57: YY_RULE_SETUP -#line 209 "engines/director/lingo/lingo-lex.l" +#line 219 "engines/director/lingo/lingo-lex.l" { count(); yylval.s = new Common::String(&yytext[1]); yylval.s->deleteLastChar(); return STRING; } YY_BREAK -case 55: +case 58: YY_RULE_SETUP -#line 210 "engines/director/lingo/lingo-lex.l" +#line 220 "engines/director/lingo/lingo-lex.l" YY_BREAK -case 56: +case 59: YY_RULE_SETUP -#line 212 "engines/director/lingo/lingo-lex.l" +#line 222 "engines/director/lingo/lingo-lex.l" ECHO; YY_BREAK -#line 1325 "engines/director/lingo/lingo-lex.cpp" +#line 1367 "engines/director/lingo/lingo-lex.cpp" case YY_STATE_EOF(INITIAL): yyterminate(); @@ -1614,7 +1656,7 @@ static int yy_get_next_buffer (void) while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; - if ( yy_current_state >= 191 ) + if ( yy_current_state >= 206 ) yy_c = yy_meta[(unsigned int) yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; @@ -1642,11 +1684,11 @@ static int yy_get_next_buffer (void) while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) { yy_current_state = (int) yy_def[yy_current_state]; - if ( yy_current_state >= 191 ) + if ( yy_current_state >= 206 ) yy_c = yy_meta[(unsigned int) yy_c]; } yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c]; - yy_is_jam = (yy_current_state == 190); + yy_is_jam = (yy_current_state == 205); return yy_is_jam ? 0 : yy_current_state; } @@ -2321,7 +2363,7 @@ void yyfree (void * ptr ) #define YYTABLES_NAME "yytables" -#line 212 "engines/director/lingo/lingo-lex.l" +#line 222 "engines/director/lingo/lingo-lex.l" diff --git a/engines/director/lingo/lingo-lex.l b/engines/director/lingo/lingo-lex.l index 8c89a99764..c2a5b19fb9 100644 --- a/engines/director/lingo/lingo-lex.l +++ b/engines/director/lingo/lingo-lex.l @@ -87,11 +87,13 @@ whitespace [\t ] (?i:global) { count(); return tGLOBAL; } (?i:go[\t ]+to) { count(); return tGO; } (?i:go) { count(); return tGO; } +(?i:instance) { count(); return tINSTANCE; } (?i:intersects) { count(); return tINTERSECTS; } (?i:into) { count(); return tINTO; } (?i:loop) { count(); return tLOOP; } (?i:macro) { count(); return tMACRO; } (?i:method) { count(); return tMETHOD; } +(?i:mod) { count(); return tMOD; } (?i:movie) { count(); return tMOVIE; } (?i:next) { count(); return tNEXT; } (?i:not) { count(); return tNOT; } @@ -104,6 +106,14 @@ whitespace [\t ] (?i:repeat) { count(); return tREPEAT; } (?i:set) { count(); return tSET; } (?i:starts) { count(); return tSTARTS; } +(?i:the[ \t]+sqrt[\t ]+of[\t ]+) { + count(); + + yylval.e[0] = g_lingo->_theEntities["sqrt"]->entity; + yylval.e[1] = 0; // No field + + return THEENTITYWITHID; + } (?i:the[ \t]+[[:alpha:]]+[\t ]+of[\t ]+[[:alpha:]]+) { count(); diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp index 3f4f9cc432..9751d06900 100644 --- a/engines/director/lingo/lingo-the.cpp +++ b/engines/director/lingo/lingo-the.cpp @@ -20,88 +20,123 @@ * */ -#include "engines/director/lingo/lingo.h" +#include "director/lingo/lingo.h" +#include "director/sprite.h" namespace Director { class Sprite; TheEntity entities[] = { + { kTheBeepOn, "beepOn", false }, // D2 property + { kTheButtonStyle, "buttonStyle", false }, // D2 p { kTheCast, "cast", true }, - { kTheClickOn, "clickOn", false }, - { kTheColorDepth, "colorDepth", false }, - { kTheColorQD, "colorQD", false }, - { kTheCommandDown, "commandDown", false }, - { kTheControlDown, "controlDown", false }, - { kTheDoubleClick, "doubleClick", false }, - { kTheExitLock, "exitlock", false }, + { kTheCenterStage, "centerStage", false }, // D2 p + { kTheCheckBoxAccess, "checkBoxAccess", false }, // D2 p + { kTheCheckBoxType, "checkBoxType", false }, // D2 p + { kTheClickOn, "clickOn", false }, // D2 function + { kTheColorDepth, "colorDepth", false }, // D2 p + { kTheColorQD, "colorQD", false }, // D2 f + { kTheCommandDown, "commandDown", false }, // D2 f + { kTheControlDown, "controlDown", false }, // D2 f + { kTheDoubleClick, "doubleClick", false }, // D2 f + { kTheExitLock, "exitLock", false }, // D2 p + { kTheFixStageSize, "fixStageSize", false }, // D2 p { kTheFloatPrecision, "floatPrecision", false }, - { kTheFrame, "frame", false }, + { kTheFrame, "frame", false }, // D2 f + { kTheFreeBlock, "freeBlock", false }, // D2 f + { kTheFreeBytes, "freeBytes", false }, // D2 f + { kTheFullColorPermit, "fullColorPermit", false }, // D2 p + { kTheImageDirect, "imageDirect", false }, // D2 p { kTheItemDelimiter, "itemDelimiter", false }, - { kTheKey, "key", false }, - { kTheKeyCode, "keycode", false }, - { kTheLastClick, "lastClick", false }, - { kTheLastEvent, "lastEvent", false }, + { kTheKey, "key", false }, // D2 f + { kTheKeyCode, "keyCode", false }, // D2 f + { kTheKeyDownScript, "keyDownScript", false }, // D2 p + { kTheLastClick, "lastClick", false }, // D2 f + { kTheLastEvent, "lastEvent", false }, // D2 f { kTheLastFrame, "lastFrame", false }, + { kTheLastKey, "lastKey", false }, // D2 f + { kTheLastRoll, "lastRoll", false }, // D2 f + { kTheMachineType, "machineType", false }, // D2 f + { kTheMemorySize, "memorySize", false }, // D2 f { kTheMenu, "menu", true }, { kTheMenus, "menus", false }, { kTheMenuItem, "menuitem", true }, { kTheMenuItems, "menuitems", false }, - { kTheMouseDown, "mouseDown", false }, - { kTheMouseDownScript, "mouseDownScript", false }, - { kTheMouseH, "mouseh", false }, - { kTheMouseUp, "mouseUp", false }, - { kTheMouseUpScript, "mouseUpScript", false }, - { kTheMouseV, "mousev", false }, - { kTheMovie, "movie", false }, + { kTheMouseDown, "mouseDown", false }, // D2 f + { kTheMouseDownScript, "mouseDownScript", false }, // D2 p + { kTheMouseH, "mouseH", false }, // D2 f + { kTheMouseUp, "mouseUp", false }, // D2 f + { kTheMouseUpScript, "mouseUpScript", false }, // D2 p + { kTheMouseV, "mouseV", false }, // D2 f + { kTheMovie, "movie", false }, // D2 f { kTheMultiSound, "multiSound", false }, - { kTheOptionDown, "optionDown", false }, - { kThePathName, "pathname", false }, - { kThePerFrameHook, "perframehook", false }, + { kTheOptionDown, "optionDown", false }, // D2 f + { kThePathName, "pathName", false }, // D2 f + { kThePauseState, "pauseState", false }, // D2 f + { kThePerFrameHook, "perFrameHook", false }, // D2 p { kThePreloadEventAbort,"preloadEventAbort",false }, + { kTheResult, "result", false }, // D2 f { kTheRightMouseDown, "rightMouseDown", false }, { kTheRightMouseUp, "rightMouseUp", false }, { kTheRomanLingo, "romanLingo", false }, - { kTheShiftDown, "shiftDown", false }, + { kTheSelection, "selection", false }, // D2 f + { kTheShiftDown, "shiftDown", false }, // D2 f + { kTheSoundEnabled, "soundEnabled", false }, // D2 p + { kTheSoundLevel, "soundLevel", false }, // D2 p { kTheSprite, "sprite", true }, + { kTheSqrt, "sqrt", false }, // D2 f { kTheStage, "stage", false }, - { kTheStillDown, "stillDown", false }, - { kTheTicks, "ticks", false }, - { kTheTimeoutLength, "timeoutlength", false }, - { kTheTimer, "timer", false }, + { kTheStageBottom, "stageBottom", false }, // D2 f + { kTheStageLeft, "stageLeft", false }, // D2 f + { kTheStageRight, "stageRight", false }, // D2 f + { kTheStageTop, "stageTop", false }, // D2 f + { kTheStillDown, "stillDown", false }, // D2 f + { kTheSwitchColorDepth, "switchColorDepth", false }, // D2 p + { kTheTicks, "ticks", false }, // D2 f + { kTheTimeoutKeydown, "timeoutKeydown", false }, // D2 p + { kTheTimeoutLapsed, "timeoutLapsed", false }, // D2 p + { kTheTimeoutLength, "timeoutLength", false }, // D2 p + { kTheTimeoutMouse, "timeoutMouse", false }, // D2 p + { kTheTimeoutPlay, "timeoutPlay", false }, // D2 p + { kTheTimeoutScript, "timeoutScript", false }, // D2 p + { kTheTimer, "timer", false }, // D2 p { kTheWindow, "window", false }, { kTheNOEntity, NULL, false } }; TheEntityField fields[] = { - { kTheSprite, "backColor", kTheBackColor }, + { kTheSprite, "backColor", kTheBackColor }, // D2 p { kTheSprite, "blend", kTheBlend }, - { kTheSprite, "bottom", kTheBottom }, - { kTheSprite, "castnum", kTheCastNum }, - { kTheSprite, "constraint", kTheConstraint }, - { kTheSprite, "cursor", kTheCursor }, + { kTheSprite, "bottom", kTheBottom }, // D2 p + { kTheSprite, "castNum", kTheCastNum }, // D2 p + { kTheSprite, "constraint", kTheConstraint }, // D2 p + { kTheSprite, "cursor", kTheCursor }, // D2 p { kTheSprite, "editableText", kTheEditableText }, - { kTheSprite, "foreColor", kTheForeColor }, - { kTheSprite, "height", kTheHeight }, - { kTheSprite, "ink", kTheInk }, - { kTheSprite, "left", kTheLeft }, - { kTheSprite, "lineSize", kTheLineSize }, - { kTheSprite, "loch", kTheLocH }, - { kTheSprite, "locv", kTheLocV }, + { kTheSprite, "foreColor", kTheForeColor }, // D2 p + { kTheSprite, "height", kTheHeight }, // D2 p + { kTheSprite, "immediate", kTheImmediate }, // D2 p + { kTheSprite, "ink", kTheInk }, // D2 p + { kTheSprite, "left", kTheLeft }, // D2 p + { kTheSprite, "lineSize", kTheLineSize }, // D2 p + { kTheSprite, "locH", kTheLocH }, // D2 p + { kTheSprite, "locV", kTheLocV }, // D2 p { kTheSprite, "moveable", kTheMoveable }, { kTheSprite, "movieRate", kTheMovieRate }, { kTheSprite, "movieTime", kTheMovieTime }, - { kTheSprite, "right", kTheRight }, + { kTheSprite, "pattern", kThePattern }, // D2 p + { kTheSprite, "puppet", kThePuppet }, // D2 p + { kTheSprite, "right", kTheRight }, // D2 p { kTheSprite, "scriptNum", kTheScriptNum }, { kTheSprite, "startTime", kTheStartTime }, - { kTheSprite, "stretch", kTheStrech }, + { kTheSprite, "stretch", kTheStrech }, // D2 p { kTheSprite, "stopTime", kTheStopTime }, - { kTheSprite, "top", kTheTop }, + { kTheSprite, "top", kTheTop }, // D2 p { kTheSprite, "trails", kTheTrails }, - { kTheSprite, "type", kTheType }, + { kTheSprite, "type", kTheType }, // D2 p { kTheSprite, "visible", kTheVisible }, { kTheSprite, "volume", kTheVolume }, - { kTheSprite, "width", kTheWidth }, + { kTheSprite, "width", kTheWidth }, // D2 p // Common cast fields { kTheCast, "castType", kTheCastType }, @@ -136,9 +171,11 @@ TheEntityField fields[] = { { kTheCast, "picture", kThePicture }, // TextCast fields - { kTheCast, "hilite", kTheHilite }, + { kTheCast, "hilite", kTheHilite }, // D2 p + { kTheCast, "selEnd", kTheSelEnd }, // D2 p + { kTheCast, "selStart", kTheSelStart }, // D2 p { kTheCast, "size", kTheSize }, - { kTheCast, "text", kTheText }, + { kTheCast, "text", kTheText }, // D2 p { kTheWindow, "drawRect", kTheDrawRect }, { kTheWindow, "filename", kTheFilename }, @@ -188,6 +225,10 @@ void Lingo::setTheEntity(int entity, Datum &id, int field, Datum &d) { _floatPrecisionFormat = Common::String::format("%%.%df", _floatPrecision); warning("set to %d: %s", _floatPrecision, _floatPrecisionFormat.c_str()); break; + case kTheColorDepth: + _vm->_colorDepth = d.toInt(); + warning("STUB: Set color depth to %d", _vm->_colorDepth); + break; default: warning("Unprocessed setting field %d of entity %d", field, entity); } @@ -311,6 +352,7 @@ Datum Lingo::getTheEntity(int entity, Datum &id, int field) { break; case kTheCast: d = getTheCast(id, field); + break; case kThePerFrameHook: warning("STUB: getting the perframehook"); break; @@ -318,6 +360,23 @@ Datum Lingo::getTheEntity(int entity, Datum &id, int field) { d.type = INT; d.u.i = _floatPrecision; break; + case kTheSqrt: + id.toFloat(); + d.type = FLOAT; + d.u.f = sqrt(id.u.f); + break; + case kTheKeyCode: + d.type = INT; + d.u.i = _vm->_keyCode; + break; + case kTheColorQD: + d.type = INT; + d.u.i = 1; + break; + case kTheColorDepth: + d.type = INT; + d.u.i = _vm->_colorDepth; + break; default: warning("Unprocessed getting field %d of entity %d", field, entity); d.type = VOID; @@ -464,8 +523,8 @@ Datum Lingo::getTheCast(Datum &id1, int field) { return d; } else { warning("The cast %d found", id); - return d; } + cast = _vm->_currentScore->_casts[id]; castInfo = _vm->_currentScore->_castsInfo[id]; diff --git a/engines/director/lingo/lingo-the.h b/engines/director/lingo/lingo-the.h index 5fea4ba009..f68a81d363 100644 --- a/engines/director/lingo/lingo-the.h +++ b/engines/director/lingo/lingo-the.h @@ -28,6 +28,8 @@ namespace Director { enum TheEntityType { kTheNOEntity = 0, kTheFrame = 1, + kTheFreeBlock, + kTheFreeBytes, kThePathName, kTheMenu, kTheMenuItem, @@ -44,25 +46,50 @@ enum TheEntityType { kThePerFrameHook, kTheTicks, kTheTimer, + kTheTimeoutKeydown, + kTheTimeoutLapsed, kTheTimeoutLength, + kTheTimeoutMouse, + kTheTimeoutPlay, + kTheTimeoutScript, kTheWindow, + kTheBeepOn, + kTheButtonStyle, + kTheCenterStage, + kTheCheckBoxAccess, + kTheCheckBoxType, kTheClickOn, + kTheControlDown, + kTheCommandDown, kTheDoubleClick, + kTheFixStageSize, + kTheFullColorPermit, + kTheImageDirect, + kTheKey, + kTheKeyDownScript, + kTheKeyCode, kTheLastClick, - kTheLastFrame, kTheLastEvent, + kTheLastFrame, + kTheLastKey, + kTheLastRoll, + kTheMachineType, + kTheMemorySize, kTheMouseDown, kTheMouseUp, + kTheOptionDown, + kThePauseState, kTheRightMouseUp, kTheRightMouseDown, + kTheSoundEnabled, + kTheSoundLevel, kTheStillDown, - kTheKey, - kTheKeyCode, - kTheControlDown, - kTheCommandDown, + kTheSwitchColorDepth, + kTheResult, + kTheSelection, kTheShiftDown, - kTheOptionDown, + kTheSqrt, kTheColorDepth, kTheColorQD, @@ -72,7 +99,11 @@ enum TheEntityType { kTheMultiSound, kThePreloadEventAbort, kTheRomanLingo, - kTheStage + kTheStage, + kTheStageBottom, + kTheStageLeft, + kTheStageRight, + kTheStageTop }; enum TheFieldType { @@ -98,6 +129,7 @@ enum TheFieldType { kTheFilename, kTheHeight, kTheHilite, + kTheImmediate, kTheInk, kTheLeft, kTheLineSize, @@ -110,9 +142,11 @@ enum TheFieldType { kTheNumber, kTheName, kThePalette, + kThePattern, kThePausedAtStart, kThePicture, kThePreload, + kThePuppet, kThePurgePriority, kTheRect, kTheRegPoint, @@ -123,6 +157,8 @@ enum TheFieldType { kTheScript, kTheScriptNum, kTheScriptText, + kTheSelEnd, + kTheSelStart, kTheSize, kTheStrech, kTheSound, diff --git a/engines/director/lingo/lingo.cpp b/engines/director/lingo/lingo.cpp index 7076ac2bc3..30714deec1 100644 --- a/engines/director/lingo/lingo.cpp +++ b/engines/director/lingo/lingo.cpp @@ -20,6 +20,10 @@ * */ +#include "common/archive.h" +#include "common/file.h" +#include "common/str-array.h" + #include "director/lingo/lingo.h" #include "director/lingo/lingo-gr.h" @@ -54,9 +58,9 @@ struct EventHandlerType { { kEventStart, "start" }, { kEventKeyUp, "keyUp" }, - { kEventKeyDown, "keyDown" }, - { kEventMouseUp, "mouseUp" }, - { kEventMouseDown, "mouseDown" }, + { kEventKeyDown, "keyDown" }, // D2 as when + { kEventMouseUp, "mouseUp" }, // D2 as when + { kEventMouseDown, "mouseDown" }, // D2 as when { kEventRightMouseDown, "rightMouseDown" }, { kEventRightMouseUp, "rightMouseUp" }, { kEventMouseEnter, "mouseEnter" }, @@ -64,6 +68,8 @@ struct EventHandlerType { { kEventMouseUpOutSide, "mouseUpOutSide" }, { kEventMouseWithin, "mouseWithin" }, + { kEventTimeout, "timeout" }, // D2 as when + { kEventNone, 0 }, }; @@ -84,6 +90,7 @@ Lingo::Lingo(DirectorEngine *vm) : _vm(vm) { _eventHandlerTypes[t->handler] = t->name; initBuiltIns(); + initFuncs(); initTheEntities(); _currentScript = 0; @@ -101,6 +108,10 @@ Lingo::Lingo(DirectorEngine *vm) : _vm(vm) { _floatPrecision = 4; _floatPrecisionFormat = "%.4f"; + _exitRepeat = false; + + _localvars = NULL; + warning("Lingo Inited"); } @@ -118,17 +129,17 @@ const char *Lingo::findNextDefinition(const char *s) { return NULL; if (!strncmp(res, "macro ", 6)) { - warning("See macro"); + debugC(3, kDebugLingoCompile, "See macro"); return res; } if (!strncmp(res, "factory ", 8)) { - warning("See factory"); + debugC(3, kDebugLingoCompile, "See factory"); return res; } if (!strncmp(res, "method ", 7)) { - warning("See method"); + debugC(3, kDebugLingoCompile, "See method"); return res; } @@ -140,7 +151,7 @@ const char *Lingo::findNextDefinition(const char *s) { } void Lingo::addCode(const char *code, ScriptType type, uint16 id) { - debug(2, "Add code \"%s\" for type %d with id %d", code, type, id); + debugC(2, kDebugLingoCompile, "Add code \"%s\" for type %d with id %d", code, type, id); if (_scripts[type].contains(id)) { delete _scripts[type][id]; @@ -155,6 +166,13 @@ void Lingo::addCode(const char *code, ScriptType type, uint16 id) { const char *begin, *end; + if (!strncmp(code, "menu:", 5)) { + debugC(2, kDebugLingoCompile, "Parsing menu"); + parseMenu(code); + + return; + } + // macros and factories have conflicting grammar. Thus we ease life for the parser. if ((begin = findNextDefinition(code))) { bool first = true; @@ -174,10 +192,18 @@ void Lingo::addCode(const char *code, ScriptType type, uint16 id) { else _inFactory = false; - debug(2, "Code chunk:\n#####\n%s#####", chunk.c_str()); + debugC(2, kDebugLingoCompile, "Code chunk:\n#####\n%s#####", chunk.c_str()); parse(chunk.c_str()); + if (debugChannelSet(3, kDebugLingoCompile)) { + int pc = 0; + while (pc < _currentScript->size()) { + Common::String instr = decodeInstruction(pc, &pc); + debugC(3, kDebugLingoCompile, "[%5d] %s", pc, instr.c_str()); + } + } + _currentScript->clear(); begin = end; @@ -185,7 +211,7 @@ void Lingo::addCode(const char *code, ScriptType type, uint16 id) { _hadError = true; // HACK: This is for preventing test execution - debug(2, "Code chunk:\n#####\n%s#####", begin); + debugC(2, kDebugLingoCompile, "Code chunk:\n#####\n%s#####", begin); parse(begin); } else { parse(code); @@ -195,8 +221,16 @@ void Lingo::addCode(const char *code, ScriptType type, uint16 id) { _inFactory = false; - if (_currentScript->size() && !_hadError) - Common::hexdump((byte *)&_currentScript->front(), _currentScript->size() * sizeof(inst)); + if (debugChannelSet(3, kDebugLingoCompile)) { + if (_currentScript->size() && !_hadError) + Common::hexdump((byte *)&_currentScript->front(), _currentScript->size() * sizeof(inst)); + + int pc = 0; + while (pc < _currentScript->size()) { + Common::String instr = decodeInstruction(pc, &pc); + debugC(3, kDebugLingoCompile, "[%5d] %s", pc, instr.c_str()); + } + } } void Lingo::executeScript(ScriptType type, uint16 id) { @@ -205,7 +239,7 @@ void Lingo::executeScript(ScriptType type, uint16 id) { return; } - debug(2, "Executing script type: %d, id: %d", type, id); + debugC(2, kDebugLingoExec, "Executing script type: %d, id: %d", type, id); _currentScript = _scripts[type][id]; _pc = 0; @@ -218,11 +252,36 @@ void Lingo::executeScript(ScriptType type, uint16 id) { cleanLocalVars(); } +ScriptType Lingo::event2script(LEvent ev) { + if (_vm->getVersion() < 4) { + switch (ev) { + //case kEventStartMovie: // We are precompiling it now + // return kMovieScript; + case kEventEnterFrame: + return kFrameScript; + default: + return kNoneScript; + } + } + + return kNoneScript; +} + void Lingo::processEvent(LEvent event, int entityId) { if (!_eventHandlerTypes.contains(event)) error("processEvent: Unknown event %d for entity %d", event, entityId); - debug(2, "processEvent(%s) for %d", _eventHandlerTypes[event], entityId); + ScriptType st = event2script(event); + + if (st != kNoneScript) { + executeScript(st, entityId + 1); + } else if (_handlers.contains(_eventHandlerTypes[event])) { + call(_eventHandlerTypes[event], 0); + pop(); + } else { + warning("---- Handler %s is not set", _eventHandlerTypes[event]); + debugC(8, kDebugLingoExec, "STUB: processEvent(%s) for %d", _eventHandlerTypes[event], entityId); + } } int Lingo::alignTypes(Datum &d1, Datum &d2) { @@ -282,6 +341,15 @@ Common::String *Datum::toString() { delete s; s = u.s; break; + case OBJECT: + *s = Common::String::format("#%s", u.s->c_str()); + break; + case VOID: + *s = "#void"; + break; + case VAR: + *s = Common::String::format("var: #%s", u.sym->name); + break; default: warning("Incorrect operation toString() for type: %s", type2str()); } @@ -310,6 +378,10 @@ const char *Datum::type2str(bool isk) { return isk ? "#point" : "POINT"; case SYMBOL: return isk ? "#symbol" : "SYMBOL"; + case OBJECT: + return isk ? "#object" : "OBJECT"; + case VAR: + return isk ? "#var" : "VAR"; default: snprintf(res, 20, "-- (%d) --", type); return res; @@ -358,16 +430,25 @@ Common::String *Lingo::toLowercaseMac(Common::String *s) { return res; } +void Lingo::parseMenu(const char *code) { + warning("STUB: parseMenu"); +} + void Lingo::runTests() { Common::File inFile; - Common::ArchiveMemberList fileList; - SearchMan.listMatchingMembers(fileList, "*.lingo"); + Common::ArchiveMemberList fsList; + SearchMan.listMatchingMembers(fsList, "*.lingo"); + Common::StringArray fileList; int counter = 1; - for (Common::ArchiveMemberList::iterator it = fileList.begin(); it != fileList.end(); ++it) { - Common::ArchiveMember const &m = **it; - Common::SeekableReadStream *const stream = m.createReadStream(); + for (Common::ArchiveMemberList::iterator it = fsList.begin(); it != fsList.end(); ++it) + fileList.push_back((*it)->getName()); + + Common::sort(fileList.begin(), fileList.end()); + + for (uint i = 0; i < fileList.size(); i++) { + Common::SeekableReadStream *const stream = SearchMan.createReadStreamForMember(fileList[i]); if (stream) { uint size = stream->size(); @@ -375,7 +456,7 @@ void Lingo::runTests() { stream->read(script, size); - warning("Compiling file %s of size %d, id: %d", m.getName().c_str(), size, counter); + debugC(2, kDebugLingoCompile, "Compiling file %s of size %d, id: %d", fileList[i].c_str(), size, counter); _hadError = false; addCode(script, kMovieScript, counter); @@ -383,7 +464,7 @@ void Lingo::runTests() { if (!_hadError) executeScript(kMovieScript, counter); else - warning("Skipping execution"); + debugC(2, kDebugLingoCompile, "Skipping execution"); free(script); diff --git a/engines/director/lingo/lingo.h b/engines/director/lingo/lingo.h index a42b796014..05c73f9886 100644 --- a/engines/director/lingo/lingo.h +++ b/engines/director/lingo/lingo.h @@ -23,13 +23,11 @@ #ifndef DIRECTOR_LINGO_LINGO_H #define DIRECTOR_LINGO_LINGO_H -#include "common/debug.h" -#include "common/hashmap.h" -#include "common/hash-str.h" #include "audio/audiostream.h" -#include "common/str.h" -#include "engines/director/director.h" -#include "engines/director/score.h" +#include "common/hash-str.h" + +#include "director/director.h" +#include "director/score.h" #include "director/lingo/lingo-gr.h" #include "director/lingo/lingo-the.h" @@ -50,6 +48,7 @@ enum LEvent { kEventIdle, kEventStepFrame, kEventExitFrame, + kEventTimeout, kEventActivateWindow, kEventDeactivateWindow, @@ -78,6 +77,30 @@ typedef void (*inst)(void); typedef Common::Array<inst> ScriptData; typedef Common::Array<double> FloatArray; +struct FuncDesc { + Common::String name; + const char *proto; + + FuncDesc(Common::String n, const char *p) { name = n; proto = p; } +}; + +struct Pointer_EqualTo { + bool operator()(const void *x, const void *y) const { return x == y; } +}; + +struct Pointer_Hash { + uint operator()(const void *x) const { +#ifdef SCUMM_64BITS + uint64 v = (uint64)x; + return (v >> 32) ^ (v & 0xffffffff); +#else + return (uint)x; +#endif + } +}; + +typedef Common::HashMap<void *, FuncDesc *, Pointer_Hash, Pointer_EqualTo> FuncHash; + struct Symbol { /* symbol table entry */ char *name; int type; @@ -111,6 +134,9 @@ struct Datum { /* interpreter stack type */ } u; Datum() { u.sym = NULL; type = VOID; } + Datum(int val) { u.i = val; type = INT; } + Datum(double val) { u.f = val; type = FLOAT; } + Datum(Common::String *val) { u.s = val; type = STRING; } double toFloat(); int toInt(); @@ -148,10 +174,15 @@ public: void addCode(const char *code, ScriptType type, uint16 id); void executeScript(ScriptType type, uint16 id); + void printStack(const char *s); + Common::String decodeInstruction(int pc, int *newPC = NULL); + + ScriptType event2script(LEvent ev); void processEvent(LEvent event, int entityId); void initBuiltIns(); + void initFuncs(); void initTheEntities(); Common::String *toLowercaseMac(Common::String *s); @@ -167,7 +198,7 @@ public: void popContext(); Symbol *lookupVar(const char *name, bool create = true, bool putInGlobalList = false); void cleanLocalVars(); - void define(Common::String &s, int start, int nargs, Common::String *prefix = NULL); + void define(Common::String &s, int start, int nargs, Common::String *prefix = NULL, int end = -1); void processIf(int elselabel, int endlabel); int alignTypes(Datum &d1, Datum &d2); @@ -202,6 +233,7 @@ public: static void c_sub(); static void c_mul(); static void c_div(); + static void c_mod(); static void c_negate(); static void c_and(); @@ -233,6 +265,8 @@ public: static void c_repeatwhilecode(); static void c_repeatwithcode(); static void c_ifcode(); + static void c_whencode(); + static void c_exitRepeat(); static void c_eq(); static void c_neq(); static void c_gt(); @@ -240,6 +274,9 @@ public: static void c_ge(); static void c_le(); static void c_call(); + + void call(Common::String name, int nargs); + static void c_procret(); static void c_mci(); @@ -249,6 +286,7 @@ public: static void c_gotonext(); static void c_gotoprevious(); static void c_global(); + static void c_instance(); static void c_play(); static void c_playdone(); @@ -258,41 +296,56 @@ public: void printStubWithArglist(const char *funcname, int nargs); void convertVOIDtoString(int arg, int nargs); void dropStack(int nargs); + void drop(int num); static void b_abs(int nargs); static void b_atan(int nargs); - static void b_chars(int nargs); static void b_cos(int nargs); static void b_exp(int nargs); static void b_float(int nargs); static void b_integer(int nargs); - static void b_length(int nargs); + static void b_integerp(int nargs); static void b_log(int nargs); static void b_pi(int nargs); static void b_power(int nargs); static void b_random(int nargs); static void b_sin(int nargs); static void b_sqrt(int nargs); - static void b_string(int nargs); static void b_tan(int nargs); + static void b_chars(int nargs); + static void b_charToNum(int nargs); + static void b_length(int nargs); + static void b_numToChar(int nargs); + static void b_offset(int nargs); + static void b_string(int nargs); + static void b_stringp(int nargs); + static void b_ilk(int nargs); static void b_alert(int nargs); static void b_cursor(int nargs); + static void b_objectp(int nargs); static void b_printFrom(int nargs); static void b_showGlobals(int nargs); static void b_showLocals(int nargs); + static void b_symbolp(int nargs); + static void b_value(int nargs); + static void b_constrainH(int nargs); + static void b_constrainV(int nargs); static void b_editableText(int nargs); static void b_installMenu(int nargs); - static void b_updateStage(int nargs); + static void b_label(int nargs); + static void b_marker(int nargs); static void b_moveableSprite(int nargs); static void b_puppetPalette(int nargs); static void b_puppetSound(int nargs); static void b_puppetSprite(int nargs); static void b_puppetTempo(int nargs); static void b_puppetTransition(int nargs); + static void b_rollOver(int nargs); static void b_spriteBox(int nargs); + static void b_updateStage(int nargs); static void b_zoomBox(int nargs); static void b_continue(int nargs); @@ -322,6 +375,18 @@ public: static void b_mci(int nargs); static void b_mciwait(int nargs); + static void b_backspace(int nargs); + static void b_empty(int nargs); + static void b_enter(int nargs); + static void b_false(int nargs); + static void b_quote(int nargs); + static void b_return(int nargs); + static void b_tab(int nargs); + static void b_true(int nargs); + + static void b_factory(int nargs); + void factoryCall(Common::String &name, int nargs); + void func_mci(Common::String &s); void func_mciwait(Common::String &s); void func_goto(Datum &frame, Datum &movie); @@ -361,8 +426,12 @@ public: bool _inFactory; Common::String _currentFactory; + bool _exitRepeat; + private: int parse(const char *code); + void parseMenu(const char *code); + void push(Datum d); Datum pop(void); @@ -374,6 +443,8 @@ private: SymbolHash _globalvars; SymbolHash *_localvars; + FuncHash _functions; + int _pc; StackData _stack; diff --git a/engines/director/lingo/tests/factory2.lingo b/engines/director/lingo/tests/factory2.lingo new file mode 100644 index 0000000000..a7b2317e17 --- /dev/null +++ b/engines/director/lingo/tests/factory2.lingo @@ -0,0 +1,4 @@ +global aim1 +AimGun2 + +aim1(mDispose) diff --git a/engines/director/lingo/tests/math.lingo b/engines/director/lingo/tests/math.lingo index 6f8ecc374f..f38b061b6a 100644 --- a/engines/director/lingo/tests/math.lingo +++ b/engines/director/lingo/tests/math.lingo @@ -20,3 +20,5 @@ updatestage put (1024/4096)*100 -- 0 put (1024/4096)*100.0 -- 0.0 put ((1024*1.0)/4096)*100.0 -- 25.0 + +put the sqrt of 9 diff --git a/engines/director/module.mk b/engines/director/module.mk index 2499528304..1ea361590a 100644 --- a/engines/director/module.mk +++ b/engines/director/module.mk @@ -1,13 +1,16 @@ MODULE := engines/director MODULE_OBJS = \ + archive.o \ detection.o \ - dib.o \ director.o \ + frame.o \ + images.o \ movie.o \ resource.o \ score.o \ sound.o \ + sprite.o \ lingo/lingo-gr.o \ lingo/lingo.o \ lingo/lingo-builtins.o \ diff --git a/engines/director/movie.cpp b/engines/director/movie.cpp index 3c34e2d432..fef2b57ff3 100644 --- a/engines/director/movie.cpp +++ b/engines/director/movie.cpp @@ -21,11 +21,12 @@ * */ +#include "common/system.h" #include "video/qt_decoder.h" + #include "director/movie.h" #include "director/score.h" -#include "common/debug.h" -#include "common/system.h" + namespace Director { Movie::Movie(Common::String fileName, DirectorEngine *vm) { diff --git a/engines/director/movie.h b/engines/director/movie.h index e26d10a7c7..84bc116134 100644 --- a/engines/director/movie.h +++ b/engines/director/movie.h @@ -24,9 +24,8 @@ #ifndef DIRECTOR_MOVIE_H #define DIRECTOR_MOVIE_H -#include "common/str.h" -#include "common/rect.h" #include "graphics/managed_surface.h" + #include "director/director.h" namespace Video { diff --git a/engines/director/resource.cpp b/engines/director/resource.cpp index fdb0712cb9..7bb73289dd 100644 --- a/engines/director/resource.cpp +++ b/engines/director/resource.cpp @@ -24,9 +24,6 @@ #include "common/debug.h" #include "common/macresman.h" -#include "common/substream.h" -#include "common/util.h" -#include "common/textconsole.h" namespace Director { @@ -54,12 +51,18 @@ bool Archive::openFile(const Common::String &fileName) { return false; } + _fileName = fileName; + return true; } void Archive::close() { _types.clear(); - delete _stream; _stream = 0; + + if (_stream) + delete _stream; + + _stream = 0; } bool Archive::hasResource(uint32 tag, uint16 id) const { @@ -189,6 +192,12 @@ bool MacArchive::openFile(const Common::String &fileName) { return false; } + _fileName = _resFork->getBaseFileName(); + if (_fileName.hasSuffix(".bin")) { + for (int i = 0; i < 4; i++) + _fileName.deleteLastChar(); + } + Common::MacResTagArray tagArray = _resFork->getResTagArray(); for (uint32 i = 0; i < tagArray.size(); i++) { diff --git a/engines/director/resource.h b/engines/director/resource.h index fda8b79d82..8e2ceaeaa5 100644 --- a/engines/director/resource.h +++ b/engines/director/resource.h @@ -23,12 +23,7 @@ #ifndef DIRECTOR_RESOURCE_H #define DIRECTOR_RESOURCE_H -#include "common/scummsys.h" -#include "common/endian.h" -#include "common/func.h" -#include "common/hashmap.h" #include "common/file.h" -#include "common/str.h" #include "common/substream.h" namespace Common { @@ -48,6 +43,8 @@ public: virtual bool openStream(Common::SeekableReadStream *stream, uint32 offset = 0) = 0; virtual void close(); + Common::String getFileName() const { return _fileName; } + bool isOpen() const { return _stream != 0; } bool hasResource(uint32 tag, uint16 id) const; @@ -72,6 +69,8 @@ protected: typedef Common::HashMap<uint16, Resource> ResourceMap; typedef Common::HashMap<uint32, ResourceMap> TypeMap; TypeMap _types; + + Common::String _fileName; }; class MacArchive : public Archive { diff --git a/engines/director/score.cpp b/engines/director/score.cpp index 6587270641..49ec050dc1 100644 --- a/engines/director/score.cpp +++ b/engines/director/score.cpp @@ -20,28 +20,20 @@ * */ -#include "director/score.h" -#include "common/stream.h" -#include "common/debug.h" -#include "common/file.h" -#include "common/archive.h" +#include "common/system.h" #include "common/config-manager.h" -#include "common/unzip.h" +#include "common/events.h" -#include "common/system.h" -#include "director/dib.h" +#include "engines/util.h" +#include "graphics/font.h" +#include "graphics/palette.h" + +#include "director/score.h" +#include "director/frame.h" #include "director/resource.h" -#include "director/lingo/lingo.h" #include "director/sound.h" - -#include "graphics/palette.h" -#include "common/events.h" -#include "engines/util.h" -#include "graphics/managed_surface.h" -#include "graphics/macgui/macwindowmanager.h" -#include "image/bmp.h" -#include "graphics/fontman.h" -#include "graphics/fonts/bdf.h" +#include "director/sprite.h" +#include "director/lingo/lingo.h" namespace Director { @@ -95,23 +87,35 @@ static byte defaultPalette[768] = { 204, 51, 255, 204, 102, 255, 204, 153, 255, 204, 204, 255, 204, 255, 255, 255, 0, 255, 255, 51, 255, 255, 102, 255, 255, 153, 255, 255, 204, 255, 255, 255 }; -Score::Score(DirectorEngine *vm) { +Score::Score(DirectorEngine *vm, Archive *archive) { _vm = vm; _surface = new Graphics::ManagedSurface; _trailSurface = new Graphics::ManagedSurface; - _movieArchive = _vm->getMainArchive(); + _movieArchive = archive; _lingo = _vm->getLingo(); _soundManager = _vm->getSoundManager(); _lingo->processEvent(kEventPrepareMovie, 0); _movieScriptCount = 0; _labels = NULL; + _font = NULL; + + _versionMinor = _versionMajor = 0; + _currentFrameRate = 20; + _castArrayStart = _castArrayEnd = 0; + _currentFrame = 0; + _nextFrameTime = 0; + _flags = 0; + _stopPlay = false; + _stageColor = 0; - if (_movieArchive->hasResource(MKTAG('M','C','N','M'), 0)) { - _macName = _movieArchive->getName(MKTAG('M','C','N','M'), 0).c_str(); + if (archive->hasResource(MKTAG('M','C','N','M'), 0)) { + _macName = archive->getName(MKTAG('M','C','N','M'), 0).c_str(); + } else { + _macName = archive->getFileName(); } - if (_movieArchive->hasResource(MKTAG('V','W','L','B'), 1024)) { - loadLabels(*_movieArchive->getResource(MKTAG('V','W','L','B'), 1024)); + if (archive->hasResource(MKTAG('V','W','L','B'), 1024)) { + loadLabels(*archive->getResource(MKTAG('V','W','L','B'), 1024)); } } @@ -156,7 +160,6 @@ void Score::loadArchive() { } Common::Array<uint16> vwci = _movieArchive->getResourceIDList(MKTAG('V','W','C','I')); - if (vwci.size() > 0) { Common::Array<uint16>::iterator iterator; @@ -165,13 +168,8 @@ void Score::loadArchive() { } Common::Array<uint16> stxt = _movieArchive->getResourceIDList(MKTAG('S','T','X','T')); - if (stxt.size() > 0) { - Common::Array<uint16>::iterator iterator; - - for (iterator = stxt.begin(); iterator != stxt.end(); ++iterator) { - loadScriptText(*_movieArchive->getResource(MKTAG('S','T','X','T'), *iterator)); - } + loadScriptText(*_movieArchive->getResource(MKTAG('S','T','X','T'), *stxt.begin())); } } @@ -188,12 +186,7 @@ Score::~Score() { if (_movieArchive) _movieArchive->close(); - delete _surface; - delete _trailSurface; - delete _font; - delete _movieArchive; - delete _labels; } @@ -201,7 +194,7 @@ void Score::loadPalette(Common::SeekableSubReadStreamEndian &stream) { uint16 steps = stream.size() / 6; uint16 index = (steps * 3) - 1; uint16 _paletteColorCount = steps; - byte *_palette = new byte[index]; + byte *_palette = new byte[index + 1]; for (uint8 i = 0; i < steps; i++) { _palette[index - 2] = stream.readByte(); @@ -223,6 +216,8 @@ void Score::loadFrames(Common::SeekableSubReadStreamEndian &stream) { if (_vm->getVersion() > 3) { stream.skip(16); + + warning("STUB: Score::loadFrames. Skipping initial bytes"); //Unknown, some bytes - constant (refer to contuinity). } @@ -249,7 +244,6 @@ void Score::loadFrames(Common::SeekableSubReadStreamEndian &stream) { frameSize -= channelSize + 4; } frame->readChannel(stream, channelOffset, channelSize); - } _frames.push_back(frame); @@ -279,6 +273,8 @@ void Score::readVersion(uint32 rid) { } void Score::loadCastData(Common::SeekableSubReadStreamEndian &stream) { + debugC(1, kDebugLoading, "Score::loadCastData(). start: %d, end: %d", _castArrayStart, _castArrayEnd); + for (uint16 id = _castArrayStart; id <= _castArrayEnd; id++) { byte size = stream.readByte(); if (size == 0) @@ -349,7 +345,7 @@ void Score::loadLabels(Common::SeekableSubReadStreamEndian &stream) { Common::SortedArray<Label *>::iterator j; for (j = _labels->begin(); j != _labels->end(); ++j) { - debug("Frame %d, Label %s", (*j)->number, (*j)->name.c_str()); + debugC(2, kDebugLoading, "Frame %d, Label %s", (*j)->number, (*j)->name.c_str()); } } @@ -412,10 +408,10 @@ void Score::loadScriptText(Common::SeekableSubReadStreamEndian &stream) { for (uint32 i = 0; i < strLen; i++) { byte ch = stream.readByte(); - if (ch == 0x0d) { - //in old Mac systems \r was the code for end-of-line instead. + // Convert Mac line endings + if (ch == 0x0d) ch = '\n'; - } + script += ch; } @@ -451,6 +447,8 @@ void Score::dumpScript(const char *script, ScriptType type, uint16 id) { char buf[256]; switch (type) { + case kNoneScript: + error("Incorrect dumpScript() call"); case kFrameScript: typeName = "frame"; break; @@ -497,8 +495,8 @@ void Score::loadCastInfo(Common::SeekableSubReadStreamEndian &stream, uint16 id) } void Score::gotoloop() { - //This command has the playback head contonuously return to the first marker to to the left and then loop back. - //If no marker are to the left of the playback head, the playback head continues to the right. + // This command has the playback head contonuously return to the first marker to to the left and then loop back. + // If no marker are to the left of the playback head, the playback head continues to the right. Common::SortedArray<Label *>::iterator i; for (i = _labels->begin(); i != _labels->end(); ++i) { @@ -515,25 +513,25 @@ void Score::gotonext() { for (i = _labels->begin(); i != _labels->end(); ++i) { if ((*i)->name == _currentLabel) { if (i != _labels->end()) { - //return to the first marker to to the right + // return to the first marker to to the right ++i; _currentFrame = (*i)->number; return; } else { - //if no markers are to the right of the playback head, - //the playback head goes to the first marker to the left + // if no markers are to the right of the playback head, + // the playback head goes to the first marker to the left _currentFrame = (*i)->number; return; } } } - //If there are not markers to the left, - //the playback head goes to frame 1, (Director frame array start from 1, engine from 0) + // If there are not markers to the left, + // the playback head goes to frame 1, (Director frame array start from 1, engine from 0) _currentFrame = 0; } void Score::gotoprevious() { - //One label + // One label if (_labels->begin() == _labels->end()) { _currentFrame = (*_labels->begin())->number; return; @@ -603,7 +601,7 @@ Common::Array<Common::String> Score::loadStrings(Common::SeekableSubReadStreamEn } uint16 count = stream.readUint16(); - offset += (count + 1) * 4 + 2; //positions info + uint16 count + offset += (count + 1) * 4 + 2; // positions info + uint16 count uint32 startPos = stream.readUint32() + offset; for (uint16 i = 0; i < count; i++) { @@ -653,16 +651,17 @@ void Score::loadFontMap(Common::SeekableSubReadStreamEndian &stream) { } BitmapCast::BitmapCast(Common::SeekableSubReadStreamEndian &stream) { - /*byte flags = */ stream.readByte(); - uint16 someFlaggyThing = stream.readUint16(); + flags = stream.readByte(); + someFlaggyThing = stream.readUint16(); initialRect = Score::readRect(stream); boundingRect = Score::readRect(stream); regY = stream.readUint16(); regX = stream.readUint16(); + unk1 = unk2 = 0; if (someFlaggyThing & 0x8000) { - /*uint16 unk1 =*/ stream.readUint16(); - /*uint16 unk2 =*/ stream.readUint16(); + unk1 = stream.readUint16(); + unk2 = stream.readUint16(); } modified = 0; } @@ -675,8 +674,9 @@ TextCast::TextCast(Common::SeekableSubReadStreamEndian &stream) { textType = static_cast<TextType>(stream.readByte()); textAlign = static_cast<TextAlignType>(stream.readUint16()); stream.skip(6); //palinfo - //for now, just supposition - fontId = stream.readUint32(); + + int t = stream.readUint32(); + assert(t == 0); // So far we saw only 0 here initialRect = Score::readRect(stream); textShadow = static_cast<SizeType>(stream.readByte()); @@ -687,8 +687,11 @@ TextCast::TextCast(Common::SeekableSubReadStreamEndian &stream) { textFlags.push_back(kTextFlagAutoTab); if (flags & 0x4) textFlags.push_back(kTextFlagDoNotWrap); - //again supposition - fontSize = stream.readUint16(); + + // TODO: FIXME: guesswork + fontId = stream.readByte(); + fontSize = stream.readByte(); + modified = 0; } @@ -735,11 +738,9 @@ void Score::startLoop() { _frames[_currentFrame]->prepareFrame(this); while (!_stopPlay && _currentFrame < _frames.size() - 2) { + debugC(1, kDebugImages, "Current frame: %d", _currentFrame); update(); processEvents(); - - g_system->updateScreen(); - g_system->delayMillis(10); } } @@ -750,20 +751,24 @@ void Score::update() { _surface->clear(); _surface->copyFrom(*_trailSurface); - //Enter and exit from previous frame (Director 4) - _lingo->processEvent(kEventEnterFrame, _currentFrame); - _lingo->processEvent(kEventExitFrame, _currentFrame); - //TODO Director 6 - another order + // Enter and exit from previous frame (Director 4) + _lingo->processEvent(kEventEnterFrame, _frames[_currentFrame]->_actionId); + _lingo->processEvent(kEventExitFrame, _frames[_currentFrame]->_actionId); + // TODO Director 6 - another order + // TODO Director 6 step: send beginSprite event to any sprites whose span begin in the upcoming frame + if (_vm->getVersion() >= 6) { + for (uint16 i = 0; i < CHANNEL_COUNT; i++) { + if (_frames[_currentFrame]->_sprites[i]->_enabled) { + _lingo->processEvent(kEventBeginSprite, i); + } + } + } - //TODO Director 6 step: send beginSprite event to any sprites whose span begin in the upcoming frame - //for (uint16 i = 0; i < CHANNEL_COUNT; i++) { - // if (_frames[_currentFrame]->_sprites[i]->_enabled) - // _lingo->processEvent(kEventBeginSprite, i); - //} + // TODO Director 6 step: send prepareFrame event to all sprites and the script channel in upcoming frame + if (_vm->getVersion() >= 6) + _lingo->processEvent(kEventPrepareFrame, _currentFrame); - //TODO Director 6 step: send prepareFrame event to all sprites and the script channel in upcoming frame - //_lingo->processEvent(kEventPrepareFrame, _currentFrame); _currentFrame++; Common::SortedArray<Label *>::iterator i; @@ -774,35 +779,35 @@ void Score::update() { } _frames[_currentFrame]->prepareFrame(this); - //Stage is drawn between the prepareFrame and enterFrame events (Lingo in a Nutshell) + // Stage is drawn between the prepareFrame and enterFrame events (Lingo in a Nutshell) byte tempo = _frames[_currentFrame]->_tempo; if (tempo) { if (tempo > 161) { - //Delay + // Delay _nextFrameTime = g_system->getMillis() + (256 - tempo) * 1000; return; } else if (tempo <= 60) { - //FPS + // FPS _nextFrameTime = g_system->getMillis() + (float)tempo / 60 * 1000; _currentFrameRate = tempo; } else if (tempo >= 136) { - //TODO Wait for channel tempo - 135 + // TODO Wait for channel tempo - 135 + warning("STUB: tempo >= 136"); } else if (tempo == 128) { - //TODO Wait for Click/Key + // TODO Wait for Click/Key + warning("STUB: tempo == 128"); } else if (tempo == 135) { - //Wait for sound channel 1 + // Wait for sound channel 1 while (_soundManager->isChannelActive(1)) { processEvents(); - g_system->delayMillis(10); } } else if (tempo == 134) { - //Wait for sound channel 2 + // Wait for sound channel 2 while (_soundManager->isChannelActive(2)) { processEvents(); - g_system->delayMillis(10); } } } @@ -815,803 +820,61 @@ void Score::processEvents() { Common::Event event; - while (g_system->getEventManager()->pollEvent(event)) { - if (event.type == Common::EVENT_QUIT) - _stopPlay = true; - - if (event.type == Common::EVENT_LBUTTONDOWN) { - Common::Point pos = g_system->getEventManager()->getMousePos(); - - //TODO there is dont send frame id - _lingo->processEvent(kEventMouseDown, _frames[_currentFrame]->getSpriteIDFromPos(pos)); - } - - if (event.type == Common::EVENT_LBUTTONUP) { - Common::Point pos = g_system->getEventManager()->getMousePos(); - - _lingo->processEvent(kEventMouseUp, _frames[_currentFrame]->getSpriteIDFromPos(pos)); - } - } -} - -Sprite *Score::getSpriteById(uint16 id) { - if (_frames[_currentFrame]->_sprites[id]) { - return _frames[_currentFrame]->_sprites[id]; - } else { - warning("Sprite on frame %d width id %d not found", _currentFrame, id); - return nullptr; - } -} - -Frame::Frame(DirectorEngine *vm) { - _vm = vm; - _transDuration = 0; - _transType = kTransNone; - _transArea = 0; - _transChunkSize = 0; - _tempo = 0; - - _sound1 = 0; - _sound2 = 0; - _soundType1 = 0; - _soundType2 = 0; - - _actionId = 0; - _skipFrameFlag = 0; - _blend = 0; - - _sprites.resize(CHANNEL_COUNT); - - for (uint16 i = 0; i < _sprites.size(); i++) { - Sprite *sp = new Sprite(); - _sprites[i] = sp; - } -} + int endTime = g_system->getMillis() + 200; -Frame::Frame(const Frame &frame) { - _vm = frame._vm; - _actionId = frame._actionId; - _transArea = frame._transArea; - _transDuration = frame._transDuration; - _transType = frame._transType; - _transChunkSize = frame._transChunkSize; - _tempo = frame._tempo; - _sound1 = frame._sound1; - _sound2 = frame._sound2; - _soundType1 = frame._soundType1; - _soundType2 = frame._soundType2; - _skipFrameFlag = frame._skipFrameFlag; - _blend = frame._blend; - _palette = new PaletteInfo(); - - _sprites.resize(CHANNEL_COUNT); - - for (uint16 i = 0; i < CHANNEL_COUNT; i++) { - _sprites[i] = new Sprite(*frame._sprites[i]); - } -} + while (g_system->getMillis() < endTime) { + while (g_system->getEventManager()->pollEvent(event)) { + if (event.type == Common::EVENT_QUIT) + _stopPlay = true; -Frame::~Frame() { - delete[] &_sprites; - delete[] &_drawRects; - delete _palette; -} + if (event.type == Common::EVENT_LBUTTONDOWN) { + Common::Point pos = g_system->getEventManager()->getMousePos(); -void Frame::readChannel(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size) { - if (offset >= 32) { - if (size <= 16) - readSprite(stream, offset, size); - else { - //read > 1 sprites channel - while (size > 16) { - byte spritePosition = (offset - 32) / 16; - uint16 nextStart = (spritePosition + 1) * 16 + 32; - uint16 needSize = nextStart - offset; - readSprite(stream, offset, needSize); - offset += needSize; - size -= needSize; + // TODO there is dont send frame id + _lingo->processEvent(kEventMouseDown, _frames[_currentFrame]->getSpriteIDFromPos(pos)); } - readSprite(stream, offset, size); - } - } else { - readMainChannels(stream, offset, size); - } -} -void Frame::readMainChannels(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size) { - uint16 finishPosition = offset + size; + if (event.type == Common::EVENT_LBUTTONUP) { + Common::Point pos = g_system->getEventManager()->getMousePos(); - while (offset < finishPosition) { - switch(offset) { - case kScriptIdPosition: - _actionId = stream.readByte(); - offset++; - break; - case kSoundType1Position: - _soundType1 = stream.readByte(); - offset++; - break; - case kTransFlagsPosition: { - uint8 transFlags = stream.readByte(); - if (transFlags & 0x80) - _transArea = 1; - else - _transArea = 0; - _transDuration = transFlags & 0x7f; - offset++; + _lingo->processEvent(kEventMouseUp, _frames[_currentFrame]->getSpriteIDFromPos(pos)); } - break; - case kTransChunkSizePosition: - _transChunkSize = stream.readByte(); - offset++; - break; - case kTempoPosition: - _tempo = stream.readByte(); - offset++; - break; - case kTransTypePosition: - _transType = static_cast<TransitionType>(stream.readByte()); - offset++; - break; - case kSound1Position: - _sound1 = stream.readUint16(); - offset+=2; - break; - case kSkipFrameFlagsPosition: - _skipFrameFlag = stream.readByte(); - offset++; - break; - case kBlendPosition: - _blend = stream.readByte(); - offset++; - break; - case kSound2Position: - _sound2 = stream.readUint16(); - offset += 2; - break; - case kSound2TypePosition: - _soundType2 = stream.readByte(); - offset += 1; - break; - case kPaletePosition: - if (stream.readUint16()) - readPaletteInfo(stream); - offset += 16; - default: - offset++; - stream.readByte(); - debug("Field Position %d, Finish Position %d", offset, finishPosition); - break; - } - } -} - -void Frame::readPaletteInfo(Common::SeekableSubReadStreamEndian &stream) { - _palette->firstColor = stream.readByte(); - _palette->lastColor = stream.readByte(); - _palette->flags = stream.readByte(); - _palette->speed = stream.readByte(); - _palette->frameCount = stream.readUint16(); - stream.skip(8); //unknown -} -void Frame::readSprite(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size) { - uint16 spritePosition = (offset - 32) / 16; - uint16 spriteStart = spritePosition * 16 + 32; - - uint16 fieldPosition = offset - spriteStart; - uint16 finishPosition = fieldPosition + size; - - Sprite &sprite = *_sprites[spritePosition]; - - while (fieldPosition < finishPosition) { - switch (fieldPosition) { - case kSpritePositionUnk1: - /*byte x1 = */ stream.readByte(); - fieldPosition++; - break; - case kSpritePositionEnabled: - sprite._enabled = (stream.readByte() != 0); - fieldPosition++; - break; - case kSpritePositionUnk2: - /*byte x2 = */ stream.readUint16(); - fieldPosition += 2; - break; - case kSpritePositionFlags: - sprite._flags = stream.readUint16(); - sprite._ink = static_cast<InkType>(sprite._flags & 0x3f); - - if (sprite._flags & 0x40) - sprite._trails = 1; - else - sprite._trails = 0; - - fieldPosition += 2; - break; - case kSpritePositionCastId: - sprite._castId = stream.readUint16(); - fieldPosition += 2; - break; - case kSpritePositionY: - sprite._startPoint.y = stream.readUint16(); - fieldPosition += 2; - break; - case kSpritePositionX: - sprite._startPoint.x = stream.readUint16(); - fieldPosition += 2; - break; - case kSpritePositionWidth: - sprite._width = stream.readUint16(); - fieldPosition += 2; - break; - case kSpritePositionHeight: - sprite._height = stream.readUint16(); - fieldPosition += 2; - break; - default: - //end cycle, go to next sprite channel - readSprite(stream, spriteStart + 16, finishPosition - fieldPosition); - fieldPosition = finishPosition; - break; - } - } -} - -void Frame::prepareFrame(Score *score) { - renderSprites(*score->_surface, false); - renderSprites(*score->_trailSurface, true); - - if (_transType != 0) - //TODO Handle changing area case - playTransition(score); - - if (_sound1 != 0 || _sound2 != 0) { - playSoundChannel(); - } - - g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, score->_surface->getBounds().width(), score->_surface->getBounds().height()); -} - -void Frame::playSoundChannel() { - debug(0, "Sound2 %d", _sound2); - debug(0, "Sound1 %d", _sound1); -} - -void Frame::playTransition(Score *score) { - uint16 duration = _transDuration * 250; // _transDuration in 1/4 of sec - duration = (duration == 0 ? 250 : duration); // director support transition duration = 0, but animation play like value = 1, idk. - - if (_transChunkSize == 0) - _transChunkSize = 1; //equal 1 step - - uint16 stepDuration = duration / _transChunkSize; - uint16 steps = duration / stepDuration; - - switch (_transType) { - case kTransCoverDown: - { - uint16 stepSize = score->_movieRect.height() / steps; - Common::Rect r = score->_movieRect; - - for (uint16 i = 1; i < steps; i++) { - r.setHeight(stepSize * i); - - g_system->delayMillis(stepDuration); - score->processEvents(); - - g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, r.width(), r.height()); - g_system->updateScreen(); - } - } - break; - case kTransCoverUp: - { - uint16 stepSize = score->_movieRect.height() / steps; - Common::Rect r = score->_movieRect; - - for (uint16 i = 1; i < steps; i++) { - r.setHeight(stepSize * i); - - g_system->delayMillis(stepDuration); - score->processEvents(); - - g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, score->_movieRect.height() - stepSize * i, r.width(), r.height()); - g_system->updateScreen(); - } - } - break; - case kTransCoverRight: { - uint16 stepSize = score->_movieRect.width() / steps; - Common::Rect r = score->_movieRect; - - for (uint16 i = 1; i < steps; i++) { - r.setWidth(stepSize * i); - - g_system->delayMillis(stepDuration); - score->processEvents(); - - g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, r.width(), r.height()); - g_system->updateScreen(); - } - } - break; - case kTransCoverLeft: { - uint16 stepSize = score->_movieRect.width() / steps; - Common::Rect r = score->_movieRect; - - for (uint16 i = 1; i < steps; i++) { - r.setWidth(stepSize * i); - - g_system->delayMillis(stepDuration); - score->processEvents(); - - g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, score->_movieRect.width() - stepSize * i, 0, r.width(), r.height()); - g_system->updateScreen(); - } - } - break; - case kTransCoverUpLeft: { - uint16 stepSize = score->_movieRect.width() / steps; - Common::Rect r = score->_movieRect; - - for (uint16 i = 1; i < steps; i++) { - r.setWidth(stepSize * i); - r.setHeight(stepSize * i); - - g_system->delayMillis(stepDuration); - score->processEvents(); - - g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, score->_movieRect.width() - stepSize * i, score->_movieRect.height() - stepSize * i, r.width(), r.height()); - g_system->updateScreen(); - } - } - break; - case kTransCoverUpRight: { - uint16 stepSize = score->_movieRect.width() / steps; - Common::Rect r = score->_movieRect; - - for (uint16 i = 1; i < steps; i++) { - r.setWidth(stepSize * i); - r.setHeight(stepSize * i); - - g_system->delayMillis(stepDuration); - score->processEvents(); - - g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, score->_movieRect.height() - stepSize * i, r.width(), r.height()); - g_system->updateScreen(); - } - } - break; - case kTransCoverDownLeft: { - uint16 stepSize = score->_movieRect.width() / steps; - Common::Rect r = score->_movieRect; - - for (uint16 i = 1; i < steps; i++) { - r.setWidth(stepSize * i); - r.setHeight(stepSize * i); - - g_system->delayMillis(stepDuration); - score->processEvents(); - - g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, score->_movieRect.width() - stepSize * i, 0, r.width(), r.height()); - g_system->updateScreen(); - } - } - break; - case kTransCoverDownRight: { - uint16 stepSize = score->_movieRect.width() / steps; - Common::Rect r = score->_movieRect; - - for (uint16 i = 1; i < steps; i++) { - r.setWidth(stepSize * i); - r.setHeight(stepSize * i); - - g_system->delayMillis(stepDuration); - score->processEvents(); - - g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, r.width(), r.height()); - g_system->updateScreen(); - } - } - break; - default: - warning("Unhandled transition type %d %d %d", _transType, duration, _transChunkSize); - break; - - } -} - -void Frame::renderSprites(Graphics::ManagedSurface &surface, bool renderTrail) { - for (uint16 i = 0; i < CHANNEL_COUNT; i++) { - if (_sprites[i]->_enabled) { - if ((_sprites[i]->_trails == 0 && renderTrail) || (_sprites[i]->_trails == 1 && !renderTrail)) - continue; - - Cast *cast; - if (!_vm->_currentScore->_casts.contains(_sprites[i]->_castId)) { - if (!_vm->getSharedCasts()->contains(_sprites[i]->_castId)) { - warning("Cast id %d not found", _sprites[i]->_castId); - continue; - } else { - cast = _vm->getSharedCasts()->getVal(_sprites[i]->_castId); + if (event.type == Common::EVENT_KEYDOWN) { + _vm->_keyCode = event.kbd.keycode; + switch (_vm->_keyCode) { + case Common::KEYCODE_LEFT: + _vm->_keyCode = 123; + break; + case Common::KEYCODE_RIGHT: + _vm->_keyCode = 124; + break; + case Common::KEYCODE_DOWN: + _vm->_keyCode = 125; + break; + case Common::KEYCODE_UP: + _vm->_keyCode = 126; + break; + default: + warning("Keycode: %d", _vm->_keyCode); } - } else { - cast = _vm->_currentScore->_casts[_sprites[i]->_castId]; - } - - if (cast->type == kCastText) { - renderText(surface, i); - continue; - } - - Image::ImageDecoder *img = getImageFrom(_sprites[i]->_castId); - if (!img) { - warning("Image with id %d not found", _sprites[i]->_castId); - continue; - } - - if (!img->getSurface()) { - //TODO - //BMPDecoder doesnt cover all BITD resources (not all have first two bytes 'BM') - //Some BITD's first two bytes 0x6 0x0 - warning("Can not load image %d", _sprites[i]->_castId); - continue; - } - - uint32 regX = static_cast<BitmapCast *>(_sprites[i]->_cast)->regX; - uint32 regY = static_cast<BitmapCast *>(_sprites[i]->_cast)->regY; - uint32 rectLeft = static_cast<BitmapCast *>(_sprites[i]->_cast)->initialRect.left; - uint32 rectTop = static_cast<BitmapCast *>(_sprites[i]->_cast)->initialRect.top; - - int x = _sprites[i]->_startPoint.x - regX + rectLeft; - int y = _sprites[i]->_startPoint.y - regY + rectTop; - int height = _sprites[i]->_height; - int width = _sprites[i]->_width; - - Common::Rect drawRect = Common::Rect(x, y, x + width, y + height); - _drawRects.push_back(drawRect); - - switch (_sprites[i]->_ink) { - case kInkTypeCopy: - surface.blitFrom(*img->getSurface(), Common::Point(x, y)); - break; - case kInkTypeBackgndTrans: - drawBackgndTransSprite(surface, *img->getSurface(), drawRect); - break; - case kInkTypeMatte: - drawMatteSprite(surface, *img->getSurface(), drawRect); - break; - case kInkTypeGhost: - drawGhostSprite(surface, *img->getSurface(), drawRect); - break; - case kInkTypeReverse: - drawReverseSprite(surface, *img->getSurface(), drawRect); - break; - default: - warning("Unhandled ink type %d", _sprites[i]->_ink); - surface.blitFrom(*img->getSurface(), Common::Point(x, y)); - break; + _lingo->processEvent(kEventKeyDown, 0); } } - } -} - -void Frame::renderButton(Graphics::ManagedSurface &surface, uint16 spriteId) { - renderText(surface, spriteId); - - uint16 castID = _sprites[spriteId]->_castId; - ButtonCast *button = static_cast<ButtonCast *>(_vm->_currentScore->_casts[castID]); - - uint32 rectLeft = button->initialRect.left; - uint32 rectTop = button->initialRect.top; - int x = _sprites[spriteId]->_startPoint.x + rectLeft; - int y = _sprites[spriteId]->_startPoint.y + rectTop; - int height = _sprites[spriteId]->_height; - int width = _sprites[spriteId]->_width; - - switch (button->buttonType) { - case kTypeCheckBox: - //Magic numbers: checkbox square need to move left about 5px from text and 12px side size (d4) - surface.frameRect(Common::Rect(x - 17, y, x + 12, y + 12), 0); - break; - case kTypeButton: - surface.frameRect(Common::Rect(x, y, x + width, y + height), 0); - break; - case kTypeRadio: - warning("STUB: renderButton: kTypeRadio"); - break; - } -} - -Image::ImageDecoder *Frame::getImageFrom(uint16 spriteId) { - uint16 imgId = spriteId + 1024; - Image::ImageDecoder *img = NULL; - - if (_vm->_currentScore->getArchive()->hasResource(MKTAG('D', 'I', 'B', ' '), imgId)) { - img = new DIBDecoder(); - img->loadStream(*_vm->_currentScore->getArchive()->getResource(MKTAG('D', 'I', 'B', ' '), imgId)); - return img; - } - - if (_vm->getSharedDIB() != NULL && _vm->getSharedDIB()->contains(imgId)) { - img = new DIBDecoder(); - img->loadStream(*_vm->getSharedDIB()->getVal(imgId)); - return img; - } - - if (_vm->_currentScore->getArchive()->hasResource(MKTAG('B', 'I', 'T', 'D'), imgId)) { - img = new Image::BitmapDecoder(); - img->loadStream(*_vm->_currentScore->getArchive()->getResource(MKTAG('B', 'I', 'T', 'D'), imgId)); - return img; - } - - if (_vm->getSharedBMP() != NULL && _vm->getSharedBMP()->contains(imgId)) { - img = new Image::BitmapDecoder(); - img->loadStream(*_vm->getSharedBMP()->getVal(imgId)); - return img; + g_system->updateScreen(); + g_system->delayMillis(10); } - - warning("Image %d not found", spriteId); - return img; } - -void Frame::renderText(Graphics::ManagedSurface &surface, uint16 spriteID) { - uint16 castID = _sprites[spriteID]->_castId; - - TextCast *textCast = static_cast<TextCast *>(_vm->_currentScore->_casts[castID]); - Common::SeekableSubReadStreamEndian *textStream; - - if (_vm->_currentScore->_movieArchive->hasResource(MKTAG('S','T','X','T'), castID + 1024)) { - textStream = _vm->_currentScore->_movieArchive->getResource(MKTAG('S','T','X','T'), castID + 1024); +Sprite *Score::getSpriteById(uint16 id) { + if (_frames[_currentFrame]->_sprites[id]) { + return _frames[_currentFrame]->_sprites[id]; } else { - textStream = _vm->getSharedSTXT()->getVal(spriteID + 1024); - } - /*uint32 unk1 = */ textStream->readUint32(); - uint32 strLen = textStream->readUint32(); - /*uin32 dataLen = */ textStream->readUint32(); - Common::String text; - - for (uint32 i = 0; i < strLen; i++) { - byte ch = textStream->readByte(); - if (ch == 0x0d) { - ch = '\n'; - } - text += ch; - } - - uint32 rectLeft = static_cast<TextCast *>(_sprites[spriteID]->_cast)->initialRect.left; - uint32 rectTop = static_cast<TextCast *>(_sprites[spriteID]->_cast)->initialRect.top; - - int x = _sprites[spriteID]->_startPoint.x + rectLeft; - int y = _sprites[spriteID]->_startPoint.y + rectTop; - int height = _sprites[spriteID]->_height; - int width = _sprites[spriteID]->_width; - - const char *fontName; - - if (_vm->_currentScore->_fontMap.contains(textCast->fontId)) { - fontName = _vm->_currentScore->_fontMap[textCast->fontId].c_str(); - } else if ((fontName = _vm->_wm->getFontName(textCast->fontId, textCast->fontSize)) == NULL) { - warning("Unknown font id %d, falling back to default", textCast->fontId); - fontName = _vm->_wm->getFontName(0, 12); - } - - const Graphics::Font *font = _vm->_wm->getFont(fontName, Graphics::FontManager::kBigGUIFont); - - font->drawString(&surface, text, x, y, width, 0); - - if (textCast->borderSize != kSizeNone) { - uint16 size = textCast->borderSize; - - //Indent from borders, measured in d4 - x -= 1; - y -= 4; - - height += 4; - width += 1; - - while (size) { - surface.frameRect(Common::Rect(x, y, x + height, y + width), 0); - x--; - y--; - height += 2; - width += 2; - size--; - } - } - - if (textCast->gutterSize != kSizeNone) { - x -= 1; - y -= 4; - - height += 4; - width += 1; - uint16 size = textCast->gutterSize; - - surface.frameRect(Common::Rect(x, y, x + height, y + width), 0); - - while (size) { - surface.drawLine(x + width, y, x + width, y + height, 0); - surface.drawLine(x, y + height, x + width, y + height, 0); - x++; - y++; - size--; - } - } -} - -void Frame::drawBackgndTransSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) { - uint8 skipColor = _vm->getPaletteColorCount() - 1; //FIXME is it always white (last entry in pallette) ? - - for (int ii = 0; ii < sprite.h; ii++) { - const byte *src = (const byte *)sprite.getBasePtr(0, ii); - byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii); - - for (int j = 0; j < drawRect.width(); j++) { - if (*src != skipColor) - *dst = *src; - - src++; - dst++; - } - } -} - -void Frame::drawGhostSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) { - uint8 skipColor = _vm->getPaletteColorCount() - 1; - for (int ii = 0; ii < sprite.h; ii++) { - const byte *src = (const byte *)sprite.getBasePtr(0, ii); - byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii); - - for (int j = 0; j < drawRect.width(); j++) { - if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0) && (*src != skipColor)) - *dst = (_vm->getPaletteColorCount() - 1) - *src; //Oposite color - - src++; - dst++; - } - } -} - -void Frame::drawReverseSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) { - uint8 skipColor = _vm->getPaletteColorCount() - 1; - for (int ii = 0; ii < sprite.h; ii++) { - const byte *src = (const byte *)sprite.getBasePtr(0, ii); - byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii); - - for (int j = 0; j < drawRect.width(); j++) { - if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0)) - *dst = (_vm->getPaletteColorCount() - 1) - *src; - else if (*src != skipColor) - *dst = *src; - src++; - dst++; - } - } -} - -void Frame::drawMatteSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) { - //Like background trans, but all white pixels NOT ENCLOSED by coloured pixels are transparent - Graphics::Surface tmp; - tmp.copyFrom(sprite); - - // Searching white color in the corners - int whiteColor = -1; - - for (int corner = 0; corner < 4; corner++) { - int x = (corner & 0x1) ? tmp.w - 1 : 0; - int y = (corner & 0x2) ? tmp.h - 1 : 0; - - byte color = *(byte *)tmp.getBasePtr(x, y); - - if (_vm->getPalette()[color * 3 + 0] == 0xff && - _vm->getPalette()[color * 3 + 1] == 0xff && - _vm->getPalette()[color * 3 + 2] == 0xff) { - whiteColor = color; - break; - } - } - - if (whiteColor == -1) { - warning("No white color for Matte image"); - whiteColor = *(byte *)tmp.getBasePtr(0, 0); - } - - Graphics::FloodFill ff(&tmp, whiteColor, 0, true); - - for (int yy = 0; yy < tmp.h; yy++) { - ff.addSeed(0, yy); - ff.addSeed(tmp.w - 1, yy); - } - - for (int xx = 0; xx < tmp.w; xx++) { - ff.addSeed(xx, 0); - ff.addSeed(xx, tmp.h - 1); - } - ff.fillMask(); - - for (int yy = 0; yy < tmp.h; yy++) { - const byte *src = (const byte *)tmp.getBasePtr(0, yy); - const byte *mask = (const byte *)ff.getMask()->getBasePtr(0, yy); - byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + yy); - - for (int xx = 0; xx < drawRect.width(); xx++, src++, dst++, mask++) - if (*mask == 0) - *dst = *src; - } - - tmp.free(); -} - -uint16 Frame::getSpriteIDFromPos(Common::Point pos) { - //Find first from top to bottom - for (uint16 i = _drawRects.size() - 1; i > 0; i--) { - if (_drawRects[i].contains(pos)) - return i; + warning("Sprite on frame %d width id %d not found", _currentFrame, id); + return nullptr; } - - return 0; -} - -Sprite::Sprite() { - _enabled = false; - _trails = 0; - _width = 0; - _ink = kInkTypeCopy; - _flags = 0; - _height = 0; - _castId = 0; - _constraint = 0; - _moveable = 0; - _castId = 0; - _backColor = 0; - _foreColor = 0; - _left = 0; - _right = 0; - _top = 0; - _bottom = 0; - _visible = false; - _movieRate = 0; - _movieTime = 0; - _startTime = 0; - _stopTime = 0; - _volume = 0; - _stretch = 0; - _type = kInactiveSprite; -} - -Sprite::Sprite(const Sprite &sprite) { - _enabled = sprite._enabled; - _castId = sprite._castId; - _flags = sprite._flags; - _trails = sprite._trails; - _ink = sprite._ink; - _width = sprite._width; - _height = sprite._height; - _startPoint.x = sprite._startPoint.x; - _startPoint.y = sprite._startPoint.y; - _backColor = sprite._backColor; - _foreColor = sprite._foreColor; - _left = sprite._left; - _right = sprite._right; - _top = sprite._top; - _bottom = sprite._bottom; - _visible = sprite._visible; - _movieRate = sprite._movieRate; - _movieTime = sprite._movieTime; - _stopTime = sprite._stopTime; - _volume = sprite._volume; - _stretch = sprite._stretch; - _type = sprite._type; -} - -Sprite::~Sprite() { - delete _cast; - delete &_startPoint; } } //End of namespace Director diff --git a/engines/director/score.h b/engines/director/score.h index a7ca59b475..9d929adc6a 100644 --- a/engines/director/score.h +++ b/engines/director/score.h @@ -23,23 +23,22 @@ #ifndef DIRECTOR_SCORE_H #define DIRECTOR_SCORE_H +#include "common/substream.h" #include "common/rect.h" -#include "common/stream.h" -#include "common/array.h" -#include "director/resource.h" -#include "graphics/managed_surface.h" -#include "common/str.h" -#include "image/image_decoder.h" -#include "graphics/font.h" + +namespace Graphics { + class ManagedSurface; + class Font; +} namespace Director { -class Lingo; -class DirectorSound; -class Score; +class Archive; class DirectorEngine; - -#define CHANNEL_COUNT 24 +class DirectorSound; +class Frame; +class Lingo; +class Sprite; enum CastType { kCastBitmap = 1, @@ -55,134 +54,14 @@ enum CastType { kCastScript }; -//Director v4 -enum SpriteType { - kInactiveSprite, //turns the sprite off - kBitmapSprite, - kRectangleSprite, - kRoundedRectangleSprite, - kOvalSprite, - kLineTopBottomSprite, //line from top left to bottom right - kLineBottomTopSprite, //line from bottom left to top right - kTextSprite, - kButtonSprite, - kCheckboxSprite, - kRadioButtonSprite, - kUndeterminedSprite = 16 //use castType property to examine the type of cast member associated with sprite -}; - -enum SpritePosition { - kSpritePositionUnk1 = 0, - kSpritePositionEnabled, - kSpritePositionUnk2, - kSpritePositionFlags = 4, - kSpritePositionCastId = 6, - kSpritePositionY = 8, - kSpritePositionX = 10, - kSpritePositionHeight = 12, - kSpritePositionWidth = 14 -}; - -enum MainChannelsPosition { - kScriptIdPosition = 0, - kSoundType1Position, - kTransFlagsPosition, - kTransChunkSizePosition, - kTempoPosition, - kTransTypePosition, - kSound1Position, - kSkipFrameFlagsPosition = 8, - kBlendPosition, - kSound2Position, - kSound2TypePosition = 11, - kPaletePosition = 15 -}; - -enum InkType { - kInkTypeCopy, - kInkTypeTransparent, - kInkTypeReverse, - kInkTypeGhost, - kInkTypeNotCopy, - kInkTypeNotTrans, - kInkTypeNotReverse, - kInkTypeNotGhost, - kInkTypeMatte, - kInkTypeMask, - //10-31 Not used (Lingo in a Nutshell) - kInkTypeBlend = 32, - kInkTypeAddPin, - kInkTypeAdd, - kInkTypeSubPin, - kInkTypeBackgndTrans, - kInkTypeLight, - kInkTypeSub, - kInkTypeDark -}; - enum ScriptType { kMovieScript = 0, kSpriteScript = 1, kFrameScript = 2, + kNoneScript = -1, kMaxScriptType = 2 }; -enum TransitionType { - kTransNone, - kTransWipeRight, - kTransWipeLeft, - kTransWipeDown, - kTransWipeUp, - kTransCenterOutHorizontal, - kTransEdgesInHorizontal, - kTransCenterOutVertical, - kTransEdgesInVertical, - kTransCenterOutSquare, - kTransEdgesInSquare, - kTransPushLeft, - kTransPushRight, - kTransPushDown, - kTransPushUp, - kTransRevealUp, - kTransRevealUpRight, - kTransRevealRight, - kTransRevealDown, - kTransRevealDownRight, - kTransRevealDownLeft, - kTransRevealLeft, - kTransRevealUpLeft, - kTransDissolvePixelsFast, - kTransDissolveBoxyRects, - kTransDissolveBoxySquares, - kTransDissolvePatterns, - kTransRandomRows, - kTransRandomColumns, - kTransCoverDown, - kTransCoverDownLeft, - kTransCoverDownRight, - kTransCoverLeft, - kTransCoverRight, - kTransCoverUp, - kTransCoverUpLeft, - kTransCoverUpRight, - kTransTypeVenitianBlind, - kTransTypeCheckerboard, - kTransTypeStripsBottomBuildLeft, - kTransTypeStripsBottomBuildRight, - kTransTypeStripsLeftBuildDown, - kTransTypeStripsLeftBuildUp, - kTransTypeStripsRightBuildDown, - kTransTypeStripsRightBuildUp, - kTransTypeStripsTopBuildLeft, - kTransTypeStripsTopBuildRight, - kTransZoomOpen, - kTransZoomClose, - kTransVerticalBinds, - kTransDissolveBitsTrans, - kTransDissolvePixels, - kTransDissolveBits -}; - struct Cast { CastType type; Common::Rect initialRect; @@ -196,6 +75,8 @@ struct BitmapCast : Cast { uint16 regX; uint16 regY; uint8 flags; + uint16 someFlaggyThing; + uint16 unk1, unk2; }; enum ShapeType { @@ -281,98 +162,6 @@ struct CastInfo { Common::String type; }; -struct PaletteInfo { - uint8 firstColor; - uint8 lastColor; - uint8 flags; - uint8 speed; - uint16 frameCount; -}; - -class Sprite { -public: - Sprite(); - Sprite(const Sprite &sprite); - ~Sprite(); - bool _enabled; - byte _castId; - InkType _ink; - uint16 _trails; - Cast *_cast; - uint16 _flags; - Common::Point _startPoint; - uint16 _width; - uint16 _height; - //TODO: default constraint = 0, if turned on, sprite is constrainted to the bounding rect - //As i know, constrainted != 0 only if sprite moveable - byte _constraint; - byte _moveable; - byte _backColor; - byte _foreColor; - uint16 _left; - uint16 _right; - uint16 _top; - uint16 _bottom; - byte _blend; - bool _visible; - SpriteType _type; - //Using in digital movie sprites - byte _movieRate; - uint16 _movieTime; - uint16 _startTime; - uint16 _stopTime; - byte _volume; - byte _stretch; - //Using in shape sprites - byte _lineSize; - //Using in text sprites - Common::String _editableText; -}; - -class Frame { -public: - Frame(DirectorEngine *vm); - Frame(const Frame &frame); - ~Frame(); - void readChannel(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size); - void prepareFrame(Score *score); - uint16 getSpriteIDFromPos(Common::Point pos); - -private: - void playTransition(Score *score); - void playSoundChannel(); - void renderSprites(Graphics::ManagedSurface &surface, bool renderTrail); - void renderText(Graphics::ManagedSurface &surface, uint16 spriteId); - void renderButton(Graphics::ManagedSurface &surface, uint16 spriteId); - void readPaletteInfo(Common::SeekableSubReadStreamEndian &stream); - void readSprite(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size); - void readMainChannels(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size); - Image::ImageDecoder *getImageFrom(uint16 spriteID); - void drawBackgndTransSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect); - void drawMatteSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect); - void drawGhostSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect); - void drawReverseSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect); -public: - uint8 _actionId; - uint8 _transDuration; - uint8 _transArea; //1 - Whole Stage, 0 - Changing Area - uint8 _transChunkSize; - TransitionType _transType; - PaletteInfo *_palette; - uint8 _tempo; - - uint16 _sound1; - uint8 _soundType1; - uint16 _sound2; - uint8 _soundType2; - - uint8 _skipFrameFlag; - uint8 _blend; - Common::Array<Sprite *> _sprites; - Common::Array<Common::Rect > _drawRects; - DirectorEngine *_vm; -}; - struct Label { Common::String name; uint16 number; @@ -381,7 +170,7 @@ struct Label { class Score { public: - Score(DirectorEngine *vm); + Score(DirectorEngine *vm, Archive *); ~Score(); static Common::Rect readRect(Common::SeekableSubReadStreamEndian &stream); @@ -394,14 +183,15 @@ public: void startLoop(); void processEvents(); Archive *getArchive() const { return _movieArchive; }; + void loadConfig(Common::SeekableSubReadStreamEndian &stream); void loadCastData(Common::SeekableSubReadStreamEndian &stream); void setCurrentFrame(uint16 frameId) { _currentFrame = frameId; } Common::String getMacName() const { return _macName; } Sprite *getSpriteById(uint16 id); + private: void update(); void readVersion(uint32 rid); - void loadConfig(Common::SeekableSubReadStreamEndian &stream); void loadPalette(Common::SeekableSubReadStreamEndian &stream); void loadFrames(Common::SeekableSubReadStreamEndian &stream); void loadLabels(Common::SeekableSubReadStreamEndian &stream); diff --git a/engines/director/sound.cpp b/engines/director/sound.cpp index 5f6d435392..af6e0d2e1c 100644 --- a/engines/director/sound.cpp +++ b/engines/director/sound.cpp @@ -20,12 +20,12 @@ * */ -#include "director/sound.h" #include "audio/decoders/wave.h" #include "common/file.h" #include "audio/decoders/aiff.h" #include "common/system.h" -#include "common/debug.h" + +#include "director/sound.h" namespace Director { @@ -36,6 +36,12 @@ DirectorSound::DirectorSound() { _mixer = g_system->getMixer(); } +DirectorSound::~DirectorSound() { + delete _sound1; + delete _sound2; + delete _scriptSound; +} + void DirectorSound::playWAV(Common::String filename, uint8 soundChannel) { Common::File *file = new Common::File(); diff --git a/engines/director/sound.h b/engines/director/sound.h index 87a989c596..850842be21 100644 --- a/engines/director/sound.h +++ b/engines/director/sound.h @@ -22,7 +22,6 @@ #include "audio/audiostream.h" #include "audio/mixer.h" -#include "common/str.h" #ifndef DIRECTOR_SOUND_H #define DIRECTOR_SOUND_H @@ -39,6 +38,7 @@ private: public: DirectorSound(); + ~DirectorSound(); void playWAV(Common::String filename, uint8 channelID); void playAIFF(Common::String filename, uint8 channelID); diff --git a/engines/director/sprite.cpp b/engines/director/sprite.cpp new file mode 100644 index 0000000000..77d53ae1da --- /dev/null +++ b/engines/director/sprite.cpp @@ -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 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 "director/director.h" +#include "director/score.h" +#include "director/sprite.h" + +namespace Director { + +Sprite::Sprite() { + _enabled = false; + _trails = 0; + _width = 0; + _ink = kInkTypeCopy; + _flags = 0; + _height = 0; + _castId = 0; + _constraint = 0; + _moveable = 0; + _castId = 0; + _backColor = 0; + _foreColor = 0; + _left = 0; + _right = 0; + _top = 0; + _bottom = 0; + _visible = false; + _movieRate = 0; + _movieTime = 0; + _startTime = 0; + _stopTime = 0; + _volume = 0; + _stretch = 0; + _type = kInactiveSprite; + + _cast = nullptr; + _blend = 0; + _lineSize = 1; +} + +Sprite::Sprite(const Sprite &sprite) { + _enabled = sprite._enabled; + _castId = sprite._castId; + _flags = sprite._flags; + _trails = sprite._trails; + _ink = sprite._ink; + _width = sprite._width; + _height = sprite._height; + _startPoint.x = sprite._startPoint.x; + _startPoint.y = sprite._startPoint.y; + _backColor = sprite._backColor; + _foreColor = sprite._foreColor; + _left = sprite._left; + _right = sprite._right; + _top = sprite._top; + _bottom = sprite._bottom; + _visible = sprite._visible; + _movieRate = sprite._movieRate; + _movieTime = sprite._movieTime; + _stopTime = sprite._stopTime; + _volume = sprite._volume; + _stretch = sprite._stretch; + _type = sprite._type; + + _cast = sprite._cast; + _constraint = sprite._constraint; + _moveable = sprite._moveable; + _blend = sprite._blend; + _startTime = sprite._startTime; + _lineSize = sprite._lineSize; +} + +Sprite::~Sprite() { + delete _cast; +} + +} //End of namespace Director diff --git a/engines/director/sprite.h b/engines/director/sprite.h new file mode 100644 index 0000000000..c66c66d6e4 --- /dev/null +++ b/engines/director/sprite.h @@ -0,0 +1,137 @@ +/* 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. + * + */ + +#ifndef DIRECTOR_SPRITE_H +#define DIRECTOR_SPRITE_H + +#include "common/rect.h" + +namespace Director { + +enum InkType { + kInkTypeCopy, + kInkTypeTransparent, + kInkTypeReverse, + kInkTypeGhost, + kInkTypeNotCopy, + kInkTypeNotTrans, + kInkTypeNotReverse, + kInkTypeNotGhost, + kInkTypeMatte, + kInkTypeMask, + //10-31 Not used (Lingo in a Nutshell) + kInkTypeBlend = 32, + kInkTypeAddPin, + kInkTypeAdd, + kInkTypeSubPin, + kInkTypeBackgndTrans, + kInkTypeLight, + kInkTypeSub, + kInkTypeDark +}; + +//Director v4 +enum SpriteType { + kInactiveSprite, //turns the sprite off + kBitmapSprite, + kRectangleSprite, + kRoundedRectangleSprite, + kOvalSprite, + kLineTopBottomSprite, //line from top left to bottom right + kLineBottomTopSprite, //line from bottom left to top right + kTextSprite, + kButtonSprite, + kCheckboxSprite, + kRadioButtonSprite, + kUndeterminedSprite = 16 //use castType property to examine the type of cast member associated with sprite +}; + +enum SpritePosition { + kSpritePositionUnk1 = 0, + kSpritePositionEnabled, + kSpritePositionUnk2, + kSpritePositionFlags = 4, + kSpritePositionCastId = 6, + kSpritePositionY = 8, + kSpritePositionX = 10, + kSpritePositionHeight = 12, + kSpritePositionWidth = 14 +}; + +enum MainChannelsPosition { + kScriptIdPosition = 0, + kSoundType1Position, + kTransFlagsPosition, + kTransChunkSizePosition, + kTempoPosition, + kTransTypePosition, + kSound1Position, + kSkipFrameFlagsPosition = 8, + kBlendPosition, + kSound2Position, + kSound2TypePosition = 11, + kPaletePosition = 15 +}; + +class Sprite { +public: + Sprite(); + Sprite(const Sprite &sprite); + ~Sprite(); + bool _enabled; + byte _castId; + InkType _ink; + uint16 _trails; + Cast *_cast; + uint16 _flags; + Common::Point _startPoint; + uint16 _width; + uint16 _height; + //TODO: default constraint = 0, if turned on, sprite is constrainted to the bounding rect + //As i know, constrainted != 0 only if sprite moveable + byte _constraint; + byte _moveable; + byte _backColor; + byte _foreColor; + uint16 _left; + uint16 _right; + uint16 _top; + uint16 _bottom; + byte _blend; + bool _visible; + SpriteType _type; + //Using in digital movie sprites + byte _movieRate; + uint16 _movieTime; + uint16 _startTime; + uint16 _stopTime; + byte _volume; + byte _stretch; + //Using in shape sprites + byte _lineSize; + //Using in text sprites + Common::String _editableText; +}; + +} //End of namespace Director + +#endif diff --git a/engines/drascula/detection.cpp b/engines/drascula/detection.cpp index ffec393a0a..3bc8069b76 100644 --- a/engines/drascula/detection.cpp +++ b/engines/drascula/detection.cpp @@ -339,7 +339,8 @@ bool DrasculaMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || (f == kSavesSupportCreationDate) || - (f == kSavesSupportPlayTime); + (f == kSavesSupportPlayTime) || + (f == kSimpleSavesNames); } const ExtraGuiOptions DrasculaMetaEngine::getExtraGuiOptions(const Common::String &target) const { diff --git a/engines/fullpipe/mgm.cpp b/engines/fullpipe/anihandler.cpp index c9eeebb224..126abbf247 100644 --- a/engines/fullpipe/mgm.cpp +++ b/engines/fullpipe/anihandler.cpp @@ -29,14 +29,14 @@ namespace Fullpipe { -void MGM::clear() { +void AniHandler::detachAllObjects() { _items.clear(); } -MessageQueue *MGM::genMQ(StaticANIObject *ani, int staticsIndex, int staticsId, int *resStatId, Common::Point **pointArr) { - debugC(4, kDebugPathfinding, "MGM::genMQ(*%d, %d, %d, res, point)", ani->_id, staticsIndex, staticsId); +MessageQueue *AniHandler::makeQueue(StaticANIObject *ani, int staticsIndex, int staticsId, int *resStatId, Common::Point **pointArr) { + debugC(4, kDebugPathfinding, "AniHandler::makeQueue(*%d, %d, %d, res, point)", ani->_id, staticsIndex, staticsId); - int idx = getItemIndexById(ani->_id); + int idx = getIndex(ani->_id); if (idx == -1) return 0; @@ -62,8 +62,8 @@ MessageQueue *MGM::genMQ(StaticANIObject *ani, int staticsIndex, int staticsId, int subidx = startidx + endidx * _items[idx]->statics.size(); if (!_items[idx]->subItems[subidx]->movement) { - clearMovements2(idx); - recalcOffsets(idx, startidx, endidx, 0, 1); + clearVisitsList(idx); + seekWay(idx, startidx, endidx, 0, 1); } if (!_items[idx]->subItems[subidx]->movement) @@ -131,25 +131,25 @@ MGMSubItem::MGMSubItem() { y = 0; } -void MGM::addItem(int objId) { - debugC(4, kDebugPathfinding, "MGM::addItem(%d)", objId); +void AniHandler::attachObject(int objId) { + debugC(4, kDebugPathfinding, "AniHandler::addItem(%d)", objId); - if (getItemIndexById(objId) == -1) { + if (getIndex(objId) == -1) { MGMItem *item = new MGMItem(); item->objId = objId; _items.push_back(item); } - rebuildTables(objId); + resetData(objId); } -void MGM::rebuildTables(int objId) { - int idx = getItemIndexById(objId); +void AniHandler::resetData(int objId) { + int idx = getIndex(objId); if (idx == -1) return; - debugC(3, kDebugPathfinding, "MGM::rebuildTables. (1) movements1 sz: %d movements2 sz: %d", _items[idx]->movements1.size(), _items[idx]->movements2.size()); + debugC(3, kDebugPathfinding, "AniHandler::resetData. (1) movements1 sz: %d movements2 sz: %d", _items[idx]->movements1.size(), _items[idx]->movements2.size()); _items[idx]->subItems.clear(); _items[idx]->statics.clear(); @@ -174,10 +174,10 @@ void MGM::rebuildTables(int objId) { _items[idx]->movements2.push_back(0); } - debugC(3, kDebugPathfinding, "MGM::rebuildTables. (2) movements1 sz: %d movements2 sz: %d", _items[idx]->movements1.size(), _items[idx]->movements2.size()); + debugC(3, kDebugPathfinding, "AniHandler::resetData. (2) movements1 sz: %d movements2 sz: %d", _items[idx]->movements1.size(), _items[idx]->movements2.size()); } -int MGM::getItemIndexById(int objId) { +int AniHandler::getIndex(int objId) { for (uint i = 0; i < _items.size(); i++) if (_items[i]->objId == objId) return i; @@ -185,62 +185,62 @@ int MGM::getItemIndexById(int objId) { return -1; } -MessageQueue *MGM::genMovement(MGMInfo *mgminfo) { - debugC(4, kDebugPathfinding, "MGM::genMovement(*%d)", mgminfo->ani ? mgminfo->ani->_id : -1); +MessageQueue *AniHandler::makeRunQueue(MakeQueueStruct *mkQueue) { + debugC(4, kDebugPathfinding, "AniHandler::makeRunQueue(*%d)", mkQueue->ani ? mkQueue->ani->_id : -1); - if (!mgminfo->ani) + if (!mkQueue->ani) return 0; - Movement *mov = mgminfo->ani->_movement; + Movement *mov = mkQueue->ani->_movement; - if (!mov && !mgminfo->ani->_statics) + if (!mov && !mkQueue->ani->_statics) return 0; - if (!(mgminfo->flags & 1)) { + if (!(mkQueue->flags & 1)) { if (mov) - mgminfo->staticsId1 = mov->_staticsObj2->_staticsId; + mkQueue->staticsId1 = mov->_staticsObj2->_staticsId; else - mgminfo->staticsId1 = mgminfo->ani->_statics->_staticsId; + mkQueue->staticsId1 = mkQueue->ani->_statics->_staticsId; } Common::Point point; - if (!(mgminfo->flags & 0x10) || !(mgminfo->flags & 0x20)) { - int nx = mgminfo->ani->_ox; - int ny = mgminfo->ani->_oy; + if (!(mkQueue->flags & 0x10) || !(mkQueue->flags & 0x20)) { + int nx = mkQueue->ani->_ox; + int ny = mkQueue->ani->_oy; - if (mgminfo->ani->_movement) { - mgminfo->ani->calcNextStep(&point); + if (mkQueue->ani->_movement) { + mkQueue->ani->calcNextStep(&point); nx += point.x; ny += point.y; } - if (!(mgminfo->flags & 0x10)) - mgminfo->x2 = nx; + if (!(mkQueue->flags & 0x10)) + mkQueue->x2 = nx; - if (!(mgminfo->flags & 0x20)) - mgminfo->y2 = ny; + if (!(mkQueue->flags & 0x20)) + mkQueue->y2 = ny; } - mov = mgminfo->ani->getMovementById(mgminfo->movementId); + mov = mkQueue->ani->getMovementById(mkQueue->movementId); if (!mov) return 0; - int itemIdx = getItemIndexById(mgminfo->ani->_id); - int subIdx = getStaticsIndexById(itemIdx, mgminfo->staticsId1); + int itemIdx = getIndex(mkQueue->ani->_id); + int subIdx = getStaticsIndexById(itemIdx, mkQueue->staticsId1); int st2idx = getStaticsIndexById(itemIdx, mov->_staticsObj1->_staticsId); int st1idx = getStaticsIndexById(itemIdx, mov->_staticsObj2->_staticsId); - int subOffset = getStaticsIndexById(itemIdx, mgminfo->staticsId2); + int subOffset = getStaticsIndexById(itemIdx, mkQueue->staticsId2); - debugC(3, kDebugPathfinding, "MGM::genMovement. (1) movements1 sz: %d movements2 sz: %d", _items[itemIdx]->movements1.size(), _items[itemIdx]->movements2.size()); + debugC(3, kDebugPathfinding, "AniHandler::genMovement. (1) movements1 sz: %d movements2 sz: %d", _items[itemIdx]->movements1.size(), _items[itemIdx]->movements2.size()); - clearMovements2(itemIdx); - recalcOffsets(itemIdx, subIdx, st2idx, 0, 1); - clearMovements2(itemIdx); - recalcOffsets(itemIdx, st1idx, subOffset, 0, 1); + clearVisitsList(itemIdx); + seekWay(itemIdx, subIdx, st2idx, 0, 1); + clearVisitsList(itemIdx); + seekWay(itemIdx, st1idx, subOffset, 0, 1); MGMSubItem *sub1 = _items[itemIdx]->subItems[subIdx + st2idx * _items[itemIdx]->statics.size()]; MGMSubItem *sub2 = _items[itemIdx]->subItems[st1idx + subOffset * _items[itemIdx]->statics.size()]; @@ -251,8 +251,8 @@ MessageQueue *MGM::genMovement(MGMInfo *mgminfo) { if (st1idx != subOffset && !sub2->movement) return 0; - int n1x = mgminfo->x1 - mgminfo->x2 - sub1->x - sub2->x; - int n1y = mgminfo->y1 - mgminfo->y2 - sub1->y - sub2->y; + int n1x = mkQueue->x1 - mkQueue->x2 - sub1->x - sub2->x; + int n1y = mkQueue->y1 - mkQueue->y2 - sub1->y - sub2->y; Common::Point point1; @@ -263,37 +263,37 @@ MessageQueue *MGM::genMovement(MGMInfo *mgminfo) { int mult; int len = -1; - if (mgminfo->flags & 0x40) { - mult = mgminfo->field_10; + if (mkQueue->flags & 0x40) { + mult = mkQueue->field_10; len = -1; n2x *= mult; n2y *= mult; } else { - calcLength(&point, mov, n1x, n1y, &mult, &len, 1); + getNumCycles(&point, mov, n1x, n1y, &mult, &len, 1); n2x = point.x; n2y = point.y; } - if (!(mgminfo->flags & 2)) { + if (!(mkQueue->flags & 2)) { len = -1; n2x = mult * point1.x; n1x = mult * point1.x; - mgminfo->x1 = mgminfo->x2 + mult * point1.x + sub1->x + sub2->x; + mkQueue->x1 = mkQueue->x2 + mult * point1.x + sub1->x + sub2->x; } - if (!(mgminfo->flags & 4)) { + if (!(mkQueue->flags & 4)) { n2y = mult * point1.y; n1y = mult * point1.y; len = -1; - mgminfo->y1 = mgminfo->y2 + mult * point1.y + sub1->y + sub2->y; + mkQueue->y1 = mkQueue->y2 + mult * point1.y + sub1->y + sub2->y; } int px = 0; int py = 0; if (sub1->movement) { - px = countPhases(itemIdx, subIdx, st2idx, 1); - py = countPhases(itemIdx, subIdx, st2idx, 2); + px = getFramesCount(itemIdx, subIdx, st2idx, 1); + py = getFramesCount(itemIdx, subIdx, st2idx, 2); } if (mult > 1) { @@ -307,8 +307,8 @@ MessageQueue *MGM::genMovement(MGMInfo *mgminfo) { } if (sub2->movement) { - px += countPhases(itemIdx, st1idx, subOffset, 1); - py += countPhases(itemIdx, st1idx, subOffset, 2); + px += getFramesCount(itemIdx, st1idx, subOffset, 1); + py += getFramesCount(itemIdx, st1idx, subOffset, 2); } int dx1 = n1x - n2x; @@ -348,9 +348,9 @@ MessageQueue *MGM::genMovement(MGMInfo *mgminfo) { for (int i = subIdx; i != st2idx;) { MGMSubItem *s = _items[itemIdx]->subItems[i + subOffset * _items[itemIdx]->statics.size()]; - ex2 = buildExCommand2(s->movement, mgminfo->ani->_id, x1, y1, &x2, &y2, -1); + ex2 = createCommand(s->movement, mkQueue->ani->_id, x1, y1, &x2, &y2, -1); ex2->_parId = mq->_id; - ex2->_keyCode = mgminfo->ani->_okeyCode; + ex2->_keyCode = mkQueue->ani->_okeyCode; mq->addExCommandToEnd(ex2); @@ -365,9 +365,9 @@ MessageQueue *MGM::genMovement(MGMInfo *mgminfo) { else plen = -1; - ex2 = buildExCommand2(mov, mgminfo->ani->_id, x1, y1, &x2, &y2, plen); + ex2 = createCommand(mov, mkQueue->ani->_id, x1, y1, &x2, &y2, plen); ex2->_parId = mq->_id; - ex2->_keyCode = mgminfo->ani->_okeyCode; + ex2->_keyCode = mkQueue->ani->_okeyCode; mq->addExCommandToEnd(ex2); } @@ -375,30 +375,30 @@ MessageQueue *MGM::genMovement(MGMInfo *mgminfo) { for (int j = st1idx; j != subOffset;) { MGMSubItem *s = _items[itemIdx]->subItems[j + subOffset * _items[itemIdx]->statics.size()]; - ex2 = buildExCommand2(s->movement, mgminfo->ani->_id, x1, y1, &x2, &y2, -1); + ex2 = createCommand(s->movement, mkQueue->ani->_id, x1, y1, &x2, &y2, -1); ex2->_parId = mq->_id; - ex2->_keyCode = mgminfo->ani->_okeyCode; + ex2->_keyCode = mkQueue->ani->_okeyCode; mq->addExCommandToEnd(ex2); j = s->staticsIndex; } - ExCommand *ex = new ExCommand(mgminfo->ani->_id, 5, -1, mgminfo->x1, mgminfo->y1, 0, 1, 0, 0, 0); + ExCommand *ex = new ExCommand(mkQueue->ani->_id, 5, -1, mkQueue->x1, mkQueue->y1, 0, 1, 0, 0, 0); - ex->_field_14 = mgminfo->field_1C; - ex->_keyCode = mgminfo->ani->_okeyCode; + ex->_field_14 = mkQueue->field_1C; + ex->_keyCode = mkQueue->ani->_okeyCode; ex->_field_24 = 0; ex->_excFlags |= 3; mq->addExCommandToEnd(ex); - debugC(3, kDebugPathfinding, "MGM::genMovement. (2) movements1 sz: %d movements2 sz: %d", _items[itemIdx]->movements1.size(), _items[itemIdx]->movements2.size()); + debugC(3, kDebugPathfinding, "AniHandler::genMovement. (2) movements1 sz: %d movements2 sz: %d", _items[itemIdx]->movements1.size(), _items[itemIdx]->movements2.size()); return mq; } -int MGM::countPhases(int idx, int subIdx, int endIdx, int flag) { +int AniHandler::getFramesCount(int idx, int subIdx, int endIdx, int flag) { int res = 0; if (endIdx < 0) @@ -415,10 +415,10 @@ int MGM::countPhases(int idx, int subIdx, int endIdx, int flag) { return res; } -void MGM::updateAnimStatics(StaticANIObject *ani, int staticsId) { - debugC(4, kDebugPathfinding, "MGM::updateAnimStatics(*%d, %d)", ani->_id, staticsId); +void AniHandler::putObjectToStatics(StaticANIObject *ani, int staticsId) { + debugC(4, kDebugPathfinding, "AniHandler::putObjectToStatics(*%d, %d)", ani->_id, staticsId); - if (getItemIndexById(ani->_id) == -1) + if (getIndex(ani->_id) == -1) return; if (ani->_movement) { @@ -437,7 +437,7 @@ void MGM::updateAnimStatics(StaticANIObject *ani, int staticsId) { if (ani->_statics) { Common::Point point; - getPoint(&point, ani->_id, ani->_statics->_staticsId, staticsId); + getTransitionSize(&point, ani->_id, ani->_statics->_staticsId, staticsId); ani->setOXY(ani->_ox + point.x, ani->_oy + point.y); @@ -445,10 +445,10 @@ void MGM::updateAnimStatics(StaticANIObject *ani, int staticsId) { } } -Common::Point *MGM::getPoint(Common::Point *point, int objectId, int staticsId1, int staticsId2) { - debugC(4, kDebugPathfinding, "MGM::getPoint([%d, %d], %d, %d, %d)", point->x, point->y, objectId, staticsId1, staticsId2); +Common::Point *AniHandler::getTransitionSize(Common::Point *point, int objectId, int staticsId1, int staticsId2) { + debugC(4, kDebugPathfinding, "AniHandler::getTransitionSize([%d, %d], %d, %d, %d)", point->x, point->y, objectId, staticsId1, staticsId2); - int idx = getItemIndexById(objectId); + int idx = getIndex(objectId); if (idx == -1) { point->x = -1; @@ -464,12 +464,12 @@ Common::Point *MGM::getPoint(Common::Point *point, int objectId, int staticsId1, int subidx = st1idx + st2idx * _items[idx]->statics.size(); if (!_items[idx]->subItems[subidx]->movement) { - clearMovements2(idx); - recalcOffsets(idx, st1idx, st2idx, false, true); + clearVisitsList(idx); + seekWay(idx, st1idx, st2idx, false, true); if (!_items[idx]->subItems[subidx]->movement) { - clearMovements2(idx); - recalcOffsets(idx, st1idx, st2idx, true, false); + clearVisitsList(idx); + seekWay(idx, st1idx, st2idx, true, false); } } @@ -488,7 +488,7 @@ Common::Point *MGM::getPoint(Common::Point *point, int objectId, int staticsId1, return point; } -int MGM::getStaticsIndexById(int idx, int16 id) { +int AniHandler::getStaticsIndexById(int idx, int16 id) { if (!_items[idx]->statics.size()) return -1; @@ -500,7 +500,7 @@ int MGM::getStaticsIndexById(int idx, int16 id) { return -1; } -int MGM::getStaticsIndex(int idx, Statics *st) { +int AniHandler::getStaticsIndex(int idx, Statics *st) { if (!_items[idx]->statics.size()) return -1; @@ -512,23 +512,23 @@ int MGM::getStaticsIndex(int idx, Statics *st) { return -1; } -void MGM::clearMovements2(int idx) { - debugC(2, kDebugPathfinding, "MGM::clearMovements2(%d)", idx); +void AniHandler::clearVisitsList(int idx) { + debugC(2, kDebugPathfinding, "AniHandler::clearVisitsList(%d)", idx); for (uint i = 0; i < _items[idx]->movements2.size(); i++) _items[idx]->movements2[i] = 0; - debugC(3, kDebugPathfinding, "MGM::clearMovements2. movements1 sz: %d movements2 sz: %d", _items[idx]->movements1.size(), _items[idx]->movements2.size()); + debugC(3, kDebugPathfinding, "AniHandler::clearVisitsList. movements1 sz: %d movements2 sz: %d", _items[idx]->movements1.size(), _items[idx]->movements2.size()); } -int MGM::recalcOffsets(int idx, int st1idx, int st2idx, bool flip, bool flop) { +int AniHandler::seekWay(int idx, int st1idx, int st2idx, bool flip, bool flop) { MGMItem *item = _items[idx]; int subIdx = st1idx + st2idx * item->statics.size(); - debugC(2, kDebugPathfinding, "MGM::recalcOffsets(%d, %d, %d, %d, %d)", idx, st1idx, st2idx, flip, flop); + debugC(2, kDebugPathfinding, "AniHandler::seekWay(%d, %d, %d, %d, %d)", idx, st1idx, st2idx, flip, flop); if (st1idx == st2idx) { - memset(&item->subItems[subIdx], 0, sizeof(item->subItems[subIdx])); + memset(item->subItems[subIdx], 0, sizeof(*(item->subItems[subIdx]))); return 0; } @@ -537,7 +537,7 @@ int MGM::recalcOffsets(int idx, int st1idx, int st2idx, bool flip, bool flop) { Common::Point point; - debugC(3, kDebugPathfinding, "MGM::recalcOffsets. movements1 sz: %d movements2 sz: %d", item->movements1.size(), item->movements2.size()); + debugC(3, kDebugPathfinding, "AniHandler::seekWay. movements1 sz: %d movements2 sz: %d", item->movements1.size(), item->movements2.size()); for (uint i = 0; i < item->movements1.size(); i++) { Movement *mov = item->movements1[i]; @@ -549,9 +549,9 @@ int MGM::recalcOffsets(int idx, int st1idx, int st2idx, bool flip, bool flop) { item->movements2[i] = 1; int stidx = getStaticsIndex(idx, mov->_staticsObj2); - int recalc = recalcOffsets(idx, stidx, st2idx, flip, flop); + int recalc = seekWay(idx, stidx, st2idx, flip, flop); int sz = mov->_currMovement ? mov->_currMovement->_dynamicPhases.size() : mov->_dynamicPhases.size(); - debugC(1, kDebugPathfinding, "MGM::recalcOffsets, want idx: %d, off: %d (%d + %d), sz: %d", idx, stidx + st2idx * _items[idx]->statics.size(), stidx, st2idx, item->subItems.size()); + debugC(1, kDebugPathfinding, "AniHandler::seekWay, want idx: %d, off: %d (%d + %d), sz: %d", idx, stidx + st2idx * _items[idx]->statics.size(), stidx, st2idx, item->subItems.size()); int newsz = sz + item->subItems[stidx + st2idx * _items[idx]->statics.size()]->field_C; @@ -580,7 +580,7 @@ int MGM::recalcOffsets(int idx, int st1idx, int st2idx, bool flip, bool flop) { item->movements2[i] = 1; int stidx = getStaticsIndex(idx, mov->_staticsObj1); - int recalc = recalcOffsets(idx, stidx, st2idx, flip, flop); + int recalc = seekWay(idx, stidx, st2idx, flip, flop); if (recalc < 0) continue; @@ -608,10 +608,10 @@ int MGM::recalcOffsets(int idx, int st1idx, int st2idx, bool flip, bool flop) { return -1; } -int MGM::refreshOffsets(int objectId, int idx1, int idx2) { - debugC(4, kDebugPathfinding, "MGM::refreshOffsets(%d, %d, %d)", objectId, idx1, idx2); +int AniHandler::getNumMovements(int objectId, int idx1, int idx2) { + debugC(4, kDebugPathfinding, "AniHandler::getNumMovements(%d, %d, %d)", objectId, idx1, idx2); - int idx = getItemIndexById(objectId); + int idx = getIndex(objectId); if (idx != -1) { int from = getStaticsIndexById(idx, idx1); @@ -623,15 +623,15 @@ int MGM::refreshOffsets(int objectId, int idx1, int idx2) { if (sub->movement) { idx = sub->field_8; } else { - clearMovements2(idx); - idx = recalcOffsets(idx, from, to, 0, 1); + clearVisitsList(idx); + idx = seekWay(idx, from, to, 0, 1); } } return idx; } -Common::Point *MGM::calcLength(Common::Point *pRes, Movement *mov, int x, int y, int *mult, int *len, int flag) { +Common::Point *AniHandler::getNumCycles(Common::Point *pRes, Movement *mov, int x, int y, int *mult, int *len, int flag) { Common::Point point; mov->calcSomeXY(point, 0, -1); @@ -639,7 +639,6 @@ Common::Point *MGM::calcLength(Common::Point *pRes, Movement *mov, int x, int y, int p1y = point.y; int newmult = 0; - int oldlen = *len; if (abs(p1y) > abs(p1x)) { if (mov->calcSomeXY(point, 0, -1)->y) @@ -685,13 +684,13 @@ Common::Point *MGM::calcLength(Common::Point *pRes, Movement *mov, int x, int y, int p2x = 0; int p2y = 0; - if (!oldlen) - oldlen = -1; + if (!*len) + *len = -1; - if (oldlen > 0) { + if (*len > 0) { ++*mult; - mov->calcSomeXY(point, 0, oldlen); + mov->calcSomeXY(point, 0, *len); p2x = point.x; p2y = point.y; @@ -707,8 +706,8 @@ Common::Point *MGM::calcLength(Common::Point *pRes, Movement *mov, int x, int y, return pRes; } -ExCommand2 *MGM::buildExCommand2(Movement *mov, int objId, int x1, int y1, Common::Point *x2, Common::Point *y2, int len) { - debugC(2, kDebugPathfinding, "MGM::buildExCommand2(mov, %d, %d, %d, [%d, %d], [%d, %d], %d)", objId, x1, y1, x2->x, x2->y, y2->x, y2->y, len); +ExCommand2 *AniHandler::createCommand(Movement *mov, int objId, int x1, int y1, Common::Point *x2, Common::Point *y2, int len) { + debugC(2, kDebugPathfinding, "AniHandler::createCommand(mov, %d, %d, %d, [%d, %d], [%d, %d], %d)", objId, x1, y1, x2->x, x2->y, y2->x, y2->y, len); uint cnt; diff --git a/engines/fullpipe/mgm.h b/engines/fullpipe/anihandler.h index 13195891da..ae16f91ba8 100644 --- a/engines/fullpipe/mgm.h +++ b/engines/fullpipe/anihandler.h @@ -20,8 +20,8 @@ * */ -#ifndef FULLPIPE_MGM_H -#define FULLPIPE_MGM_H +#ifndef FULLPIPE_ANIHANDLER_H +#define FULLPIPE_ANIHANDLER_H namespace Fullpipe { @@ -50,7 +50,7 @@ struct MGMItem { MGMItem(); }; -struct MGMInfo { +struct MakeQueueStruct { StaticANIObject *ani; int staticsId1; int staticsId2; @@ -63,33 +63,33 @@ struct MGMInfo { int y2; int flags; - MGMInfo() { memset(this, 0, sizeof(MGMInfo)); } + MakeQueueStruct() { memset(this, 0, sizeof(MakeQueueStruct)); } }; -class MGM : public CObject { +class AniHandler : public CObject { public: Common::Array<MGMItem *> _items; public: - void clear(); - void addItem(int objId); - void rebuildTables(int objId); - int getItemIndexById(int objId); + void detachAllObjects(); + void attachObject(int objId); + void resetData(int objId); + int getIndex(int objId); - MessageQueue *genMovement(MGMInfo *mgminfo); - void updateAnimStatics(StaticANIObject *ani, int staticsId); - Common::Point *getPoint(Common::Point *point, int aniId, int staticsId1, int staticsId2); + MessageQueue *makeRunQueue(MakeQueueStruct *mkQueue); + void putObjectToStatics(StaticANIObject *ani, int staticsId); + Common::Point *getTransitionSize(Common::Point *point, int aniId, int staticsId1, int staticsId2); int getStaticsIndexById(int idx, int16 id); int getStaticsIndex(int idx, Statics *st); - void clearMovements2(int idx); - int recalcOffsets(int idx, int st1idx, int st2idx, bool flip, bool flop); - Common::Point *calcLength(Common::Point *point, Movement *mov, int x, int y, int *mult, int *len, int flag); - ExCommand2 *buildExCommand2(Movement *mov, int objId, int x1, int y1, Common::Point *x2, Common::Point *y2, int len); - MessageQueue *genMQ(StaticANIObject *ani, int staticsIndex, int staticsId, int *resStatId, Common::Point **pointArr); - int countPhases(int idx, int subIdx, int subOffset, int flag); - int refreshOffsets(int objectId, int idx1, int idx2); + void clearVisitsList(int idx); + int seekWay(int idx, int st1idx, int st2idx, bool flip, bool flop); + Common::Point *getNumCycles(Common::Point *point, Movement *mov, int x, int y, int *mult, int *len, int flag); + ExCommand2 *createCommand(Movement *mov, int objId, int x1, int y1, Common::Point *x2, Common::Point *y2, int len); + MessageQueue *makeQueue(StaticANIObject *ani, int staticsIndex, int staticsId, int *resStatId, Common::Point **pointArr); + int getFramesCount(int idx, int subIdx, int subOffset, int flag); + int getNumMovements(int objectId, int idx1, int idx2); }; } // End of namespace Fullpipe -#endif /* FULLPIPE_MGM_H */ +#endif /* FULLPIPE_ANIHANDLER_H */ diff --git a/engines/fullpipe/behavior.cpp b/engines/fullpipe/behavior.cpp index 75b1c78143..92fb952605 100644 --- a/engines/fullpipe/behavior.cpp +++ b/engines/fullpipe/behavior.cpp @@ -49,6 +49,8 @@ void BehaviorManager::clear() { } void BehaviorManager::initBehavior(Scene *sc, GameVar *var) { + debugC(2, kDebugBehavior, "BehaviorManager::initBehavior(%d, %s)", sc->_sceneId, transCyrillic((byte *)var->_varName)); + clear(); _scene = sc; @@ -58,7 +60,10 @@ void BehaviorManager::initBehavior(Scene *sc, GameVar *var) { if (!behvar) return; + debugC(3, kDebugBehavior, "BehaviorManager::initBehavior. have Variable"); + for (GameVar *subvar = behvar->_subVars; subvar; subvar = subvar->_nextVarObj) { + debugC(3, kDebugBehavior, "BehaviorManager::initBehavior. subVar %s", transCyrillic((byte *)subvar->_varName)); if (!strcmp(subvar->_varName, "AMBIENT")) { behinfo = new BehaviorInfo; behinfo->initAmbientBehavior(subvar, sc); @@ -66,8 +71,8 @@ void BehaviorManager::initBehavior(Scene *sc, GameVar *var) { _behaviors.push_back(behinfo); } else { StaticANIObject *ani = sc->getStaticANIObject1ByName(subvar->_varName, -1); - if (ani) - for (uint i = 0; i < sc->_staticANIObjectList1.size(); i++) + if (ani) { + for (uint i = 0; i < sc->_staticANIObjectList1.size(); i++) { if (((StaticANIObject *)sc->_staticANIObjectList1[i])->_id == ani->_id) { behinfo = new BehaviorInfo; behinfo->initObjectBehavior(subvar, sc, ani); @@ -75,6 +80,8 @@ void BehaviorManager::initBehavior(Scene *sc, GameVar *var) { _behaviors.push_back(behinfo); } + } + } } } } @@ -83,7 +90,7 @@ void BehaviorManager::updateBehaviors() { if (!_isActive) return; - debugC(4, kDebugAnimation, "BehaviorManager::updateBehaviors()"); + debugC(6, kDebugBehavior, "BehaviorManager::updateBehaviors()"); for (uint i = 0; i < _behaviors.size(); i++) { BehaviorInfo *beh = _behaviors[i]; @@ -122,7 +129,7 @@ void BehaviorManager::updateBehaviors() { } void BehaviorManager::updateBehavior(BehaviorInfo *behaviorInfo, BehaviorAnim *entry) { - debugC(4, kDebugAnimation, "BehaviorManager::updateBehavior() %d", entry->_movesCount); + debugC(7, kDebugBehavior, "BehaviorManager::updateBehavior() moves: %d", entry->_movesCount); for (int i = 0; i < entry->_movesCount; i++) { BehaviorMove *bhi = entry->_behaviorMoves[i]; if (!(bhi->_flags & 1)) { @@ -144,7 +151,7 @@ void BehaviorManager::updateBehavior(BehaviorInfo *behaviorInfo, BehaviorAnim *e } void BehaviorManager::updateStaticAniBehavior(StaticANIObject *ani, int delay, BehaviorAnim *bhe) { - debugC(4, kDebugAnimation, "BehaviorManager::updateStaticAniBehavior(%s)", transCyrillic((byte *)ani->_objectName)); + debugC(6, kDebugBehavior, "BehaviorManager::updateStaticAniBehavior(%s)", transCyrillic((byte *)ani->_objectName)); MessageQueue *mq = 0; @@ -199,7 +206,7 @@ void BehaviorManager::setFlagByStaticAniObject(StaticANIObject *ani, int flag) { if (ani == beh->_ani) { if (flag) - beh->_flags &= 0xfe; + beh->_flags &= 0xfffffffe; else beh->_flags |= 1; } @@ -224,7 +231,7 @@ BehaviorMove *BehaviorManager::getBehaviorMoveByMessageQueueDataId(StaticANIObje } void BehaviorInfo::clear() { - _ani = 0; + _ani = NULL; _staticsId = 0; _counter = 0; _counterMax = 0; @@ -236,7 +243,7 @@ void BehaviorInfo::clear() { } void BehaviorInfo::initAmbientBehavior(GameVar *var, Scene *sc) { - debugC(4, kDebugAnimation, "BehaviorInfo::initAmbientBehavior(%s)", transCyrillic((byte *)var->_varName)); + debugC(4, kDebugBehavior, "BehaviorInfo::initAmbientBehavior(%s)", transCyrillic((byte *)var->_varName)); clear(); _animsCount = 1; @@ -260,7 +267,8 @@ void BehaviorInfo::initAmbientBehavior(GameVar *var, Scene *sc) { } void BehaviorInfo::initObjectBehavior(GameVar *var, Scene *sc, StaticANIObject *ani) { - debugC(4, kDebugAnimation, "BehaviorInfo::initObjectBehavior(%s)", transCyrillic((byte *)var->_varName)); + Common::String s((char *)transCyrillic((byte *)var->_varName)); + debugC(4, kDebugBehavior, "BehaviorInfo::initObjectBehavior(%s, %d, %s)", s.c_str(), sc->_sceneId, transCyrillic((byte *)ani->_objectName)); clear(); @@ -303,7 +311,7 @@ BehaviorAnim::BehaviorAnim(GameVar *var, Scene *sc, StaticANIObject *ani, int *m _staticsId = 0; _movesCount = 0; - *minDelay = 100000000; + *minDelay = 0xffffffff; int totalPercent = 0; _flags = 0; diff --git a/engines/fullpipe/fullpipe.cpp b/engines/fullpipe/fullpipe.cpp index 5f06d8ad79..54a77938c9 100644 --- a/engines/fullpipe/fullpipe.cpp +++ b/engines/fullpipe/fullpipe.cpp @@ -52,8 +52,11 @@ FullpipeEngine::FullpipeEngine(OSystem *syst, const ADGameDescription *gameDesc) DebugMan.addDebugChannel(kDebugDrawing, "drawing", "Drawing"); DebugMan.addDebugChannel(kDebugLoading, "loading", "Scene loading"); DebugMan.addDebugChannel(kDebugAnimation, "animation", "Animation"); + DebugMan.addDebugChannel(kDebugBehavior, "behavior", "Behavior"); DebugMan.addDebugChannel(kDebugMemory, "memory", "Memory management"); DebugMan.addDebugChannel(kDebugEvents, "events", "Event handling"); + DebugMan.addDebugChannel(kDebugInventory, "inventory", "Inventory"); + DebugMan.addDebugChannel(kDebugSceneLogic, "scenelogic", "Scene Logic"); // Setup mixer if (!_mixer->isReady()) { @@ -139,7 +142,7 @@ FullpipeEngine::FullpipeEngine(OSystem *syst, const ADGameDescription *gameDesc) _scene3 = 0; _movTable = 0; _floaters = 0; - _mgm = 0; + _aniHandler = 0; _globalMessageQueueList = 0; _messageHandlers = 0; @@ -216,7 +219,7 @@ void FullpipeEngine::initialize() { _sceneRect.bottom = 599; _floaters = new Floaters; - _mgm = new MGM; + _aniHandler = new AniHandler; } void FullpipeEngine::restartGame() { @@ -403,6 +406,7 @@ void FullpipeEngine::updateEvents() { _lastInputTicks = _updateTicks; ex->handle(); } + _mouseScreenPos = event.mouse; break; case Common::EVENT_LBUTTONDOWN: if (!_inputArFlag && (_updateTicks - _lastInputTicks) >= 2) { @@ -415,6 +419,7 @@ void FullpipeEngine::updateEvents() { _lastInputTicks = _updateTicks; ex->handle(); } + _mouseScreenPos = event.mouse; break; case Common::EVENT_LBUTTONUP: if (!_inputArFlag && (_updateTicks - _lastButtonUpTicks) >= 2) { @@ -423,6 +428,7 @@ void FullpipeEngine::updateEvents() { _lastButtonUpTicks = _updateTicks; ex->handle(); } + _mouseScreenPos = event.mouse; break; default: break; diff --git a/engines/fullpipe/fullpipe.h b/engines/fullpipe/fullpipe.h index 1c85536a9a..09c9559199 100644 --- a/engines/fullpipe/fullpipe.h +++ b/engines/fullpipe/fullpipe.h @@ -48,13 +48,16 @@ namespace Fullpipe { enum FullpipeGameFeatures { }; -enum AccessDebugChannels { - kDebugPathfinding = 1 << 0, - kDebugDrawing = 1 << 1, - kDebugLoading = 1 << 2, - kDebugAnimation = 1 << 3, - kDebugMemory = 1 << 4, - kDebugEvents = 1 << 5 +enum { + kDebugPathfinding = 1 << 0, + kDebugDrawing = 1 << 1, + kDebugLoading = 1 << 2, + kDebugAnimation = 1 << 3, + kDebugMemory = 1 << 4, + kDebugEvents = 1 << 5, + kDebugBehavior = 1 << 6, + kDebugInventory = 1 << 7, + kDebugSceneLogic = 1 << 8 }; class BehaviorManager; @@ -73,7 +76,7 @@ class GlobalMessageQueueList; struct MessageHandler; class MessageQueue; struct MovTable; -class MGM; +class AniHandler; class NGIArchive; class PictureObject; struct PreloadItem; @@ -209,7 +212,7 @@ public: MovTable *_movTable; Floaters *_floaters; - MGM *_mgm; + AniHandler *_aniHandler; Common::Array<Common::Point *> _arcadeKeys; diff --git a/engines/fullpipe/gameloader.cpp b/engines/fullpipe/gameloader.cpp index 1cd51036d0..270e69aa42 100644 --- a/engines/fullpipe/gameloader.cpp +++ b/engines/fullpipe/gameloader.cpp @@ -88,10 +88,10 @@ GameLoader::~GameLoader() { for (uint i = 0; i < _sc2array.size(); i++) { if (_sc2array[i]._defPicAniInfos) - delete _sc2array[i]._defPicAniInfos; + free(_sc2array[i]._defPicAniInfos); if (_sc2array[i]._picAniInfos) - delete _sc2array[i]._picAniInfos; + free(_sc2array[i]._picAniInfos); if (_sc2array[i]._motionController) delete _sc2array[i]._motionController; diff --git a/engines/fullpipe/gfx.cpp b/engines/fullpipe/gfx.cpp index dcd5c33740..eba5d442d5 100644 --- a/engines/fullpipe/gfx.cpp +++ b/engines/fullpipe/gfx.cpp @@ -290,8 +290,8 @@ bool GameObject::load(MfcArchive &file) { _id = file.readUint16LE(); _objectName = file.readPascalString(); - _ox = file.readUint32LE(); - _oy = file.readUint32LE(); + _ox = file.readSint32LE(); + _oy = file.readSint32LE(); _priority = file.readUint16LE(); if (g_fp->_gameProjectVersion >= 11) { @@ -353,7 +353,6 @@ bool GameObject::getPicAniInfo(PicAniInfo *info) { info->ox = _ox; info->oy = _oy; info->priority = _priority; - warning("Yep %d", _id); return true; } @@ -495,8 +494,8 @@ bool Picture::load(MfcArchive &file) { debugC(5, kDebugLoading, "Picture::load()"); MemoryObject::load(file); - _x = file.readUint32LE(); - _y = file.readUint32LE(); + _x = file.readSint32LE(); + _y = file.readSint32LE(); _field_44 = file.readUint16LE(); assert(g_fp->_gameProjectVersion >= 2); @@ -786,8 +785,8 @@ Bitmap::~Bitmap() { void Bitmap::load(Common::ReadStream *s) { debugC(5, kDebugLoading, "Bitmap::load()"); - _x = s->readUint32LE(); - _y = s->readUint32LE(); + _x = s->readSint32LE(); + _y = s->readSint32LE(); _width = s->readUint32LE(); _height = s->readUint32LE(); s->readUint32LE(); // pixels @@ -806,7 +805,7 @@ bool Bitmap::isPixelHitAtPos(int x, int y) { if (!_surface) return false; - return ((*((int32 *)_surface->getBasePtr(x, y)) & 0xff000000) != 0); + return ((*((int32 *)_surface->getBasePtr(x - _x, y - _y)) & 0xff) != 0); } void Bitmap::decode(int32 *palette) { @@ -1122,28 +1121,31 @@ void Bitmap::copier(uint32 *dest, byte *src, int len, int32 *palette, bool cb05_ } Bitmap *Bitmap::reverseImage(bool flip) { + Bitmap *b = new Bitmap(this); + if (flip) - _flipping = Graphics::FLIP_H; - else - _flipping = Graphics::FLIP_NONE; + b->_flipping ^= Graphics::FLIP_H; - return this; + return b; } Bitmap *Bitmap::flipVertical() { - _flipping = Graphics::FLIP_V; + Bitmap *b = new Bitmap(this); + + b->_flipping ^= Graphics::FLIP_V; - return this; + return b; } void Bitmap::drawShaded(int type, int x, int y, byte *palette, int alpha) { - warning("STUB: Bitmap::drawShaded(%d, %d, %d)", type, x, y); + if (alpha != 255) + warning("STUB: Bitmap::drawShaded(%d, %d, %d, %d)", type, x, y, alpha); putDib(x, y, (int32 *)palette, alpha); } void Bitmap::drawRotated(int x, int y, int angle, byte *palette, int alpha) { - warning("STUB: Bitmap::drawShaded(%d, %d, %d)", x, y, angle); + warning("STUB: Bitmap::drawRotated(%d, %d, %d, %d)", x, y, angle, alpha); putDib(x, y, (int32 *)palette, alpha); } diff --git a/engines/fullpipe/interaction.cpp b/engines/fullpipe/interaction.cpp index 484b1e150e..dc40750fe6 100644 --- a/engines/fullpipe/interaction.cpp +++ b/engines/fullpipe/interaction.cpp @@ -143,8 +143,8 @@ bool InteractionController::handleInteraction(StaticANIObject *subj, GameObject obj->setPicAniInfo(&aniInfo); if (abs(xpos - subj->_ox) > 1 || abs(ypos - subj->_oy) > 1) { - debugC(0, kDebugPathfinding, "Calling doWalkTo() at [%d, %d]", xpos, ypos); - mq = getSc2MctlCompoundBySceneId(g_fp->_currentScene->_sceneId)->doWalkTo(subj, xpos, ypos, 1, cinter->_staticsId2); + debugC(0, kDebugPathfinding, "Calling makeQueue() at [%d, %d]", xpos, ypos); + mq = getSc2MctlCompoundBySceneId(g_fp->_currentScene->_sceneId)->makeQueue(subj, xpos, ypos, 1, cinter->_staticsId2); if (mq) { dur = mq->calcDuration(subj); delete mq; @@ -305,7 +305,7 @@ LABEL_38: ani->changeStatics2(inter->_staticsId1); } - int xpos = inter->_yOffs + obj->_ox; + int xpos = inter->_xOffs + obj->_ox; int ypos = inter->_yOffs + obj->_oy; obj->setPicAniInfo(&aniInfo); @@ -450,8 +450,8 @@ bool Interaction::load(MfcArchive &file) { _objectId3 = file.readUint16LE(); _objectState2 = file.readUint32LE(); _objectState1 = file.readUint32LE(); - _xOffs = file.readUint32LE(); - _yOffs = file.readUint32LE(); + _xOffs = file.readSint32LE(); + _yOffs = file.readSint32LE(); _sceneId = file.readUint32LE(); _flags = file.readUint32LE(); _actionName = file.readPascalString(); diff --git a/engines/fullpipe/inventory.cpp b/engines/fullpipe/inventory.cpp index aa229d55d7..f1dafeba7d 100644 --- a/engines/fullpipe/inventory.cpp +++ b/engines/fullpipe/inventory.cpp @@ -35,7 +35,7 @@ Inventory::~Inventory() { } bool Inventory::load(MfcArchive &file) { - debugC(5, kDebugLoading, "Inventory::load()"); + debugC(5, kDebugLoading | kDebugInventory, "Inventory::load()"); _sceneId = file.readUint16LE(); int numInvs = file.readUint32LE(); @@ -119,12 +119,36 @@ void Inventory2::addItem2(StaticANIObject *obj) { } void Inventory2::removeItem(int itemId, int count) { - warning("STUB: Inventory2::removeItem(%d, %d)", itemId, count); + debugC(2, kDebugInventory, "Inventory2::removeItem(%d, %d)", itemId, count); + + while (count) { + int i; + for (i = _inventoryItems.size() - 1; i >= 0; i--) { + if (_inventoryItems[i]->itemId == itemId) { + if (_selectedId == itemId) + unselectItem(false); + + if (_inventoryItems[i]->count > count) { + _inventoryItems[i]->count -= count; + } else { + count -= _inventoryItems[i]->count; + _inventoryItems.remove_at(i); + } + + if (getCountItemsWithId(itemId) < 0) + getInventoryPoolItemFieldCById(itemId); + + break; + } + } + } } void Inventory2::removeItem2(Scene *sceneObj, int itemId, int x, int y, int priority) { int idx = getInventoryItemIndexById(itemId); + debugC(2, kDebugInventory, "removeItem2(*, %d, %d, %d, %d)", itemId, x, y, priority); + if (idx >= 0) { if (_inventoryItems[idx]->count) { removeItem(itemId, 1); @@ -187,11 +211,15 @@ int Inventory2::getItemFlags(int itemId) { } void Inventory2::rebuildItemRects() { + debugC(2, kDebugInventory, "rebuildItemRects()"); + _scene = g_fp->accessScene(_sceneId); if (!_scene) return; + _inventoryIcons.clear(); + _picture = _scene->getBigPicture(0, 0); _picture->setAlpha(50); diff --git a/engines/fullpipe/lift.cpp b/engines/fullpipe/lift.cpp index d066c89d4a..93bfbaaa24 100644 --- a/engines/fullpipe/lift.cpp +++ b/engines/fullpipe/lift.cpp @@ -392,7 +392,8 @@ void FullpipeEngine::lift_clickButton() { lift_walkAndGo(); } -void FullpipeEngine::lift_goAnimation() { if (_lastLiftButton) { +void FullpipeEngine::lift_goAnimation() { + if (_lastLiftButton) { int parentId = _currentScene->_sceneId; int buttonId = lift_getButtonIdN(_lastLiftButton->_statics->_staticsId); @@ -428,6 +429,8 @@ void FullpipeEngine::lift_goAnimation() { if (_lastLiftButton) { delete mq; _aniMan->_flags |= 1; + + return; } } } diff --git a/engines/fullpipe/messages.cpp b/engines/fullpipe/messages.cpp index 8652223ed1..9ddf0fda4e 100644 --- a/engines/fullpipe/messages.cpp +++ b/engines/fullpipe/messages.cpp @@ -61,8 +61,8 @@ bool ExCommand::load(MfcArchive &file) { _parentId = file.readUint16LE(); _messageKind = file.readUint32LE(); - _x = file.readUint32LE(); - _y = file.readUint32LE(); + _x = file.readSint32LE(); + _y = file.readSint32LE(); _field_14 = file.readUint32LE(); _sceneClickX = file.readUint32LE(); _sceneClickY = file.readUint32LE(); @@ -457,7 +457,7 @@ void MessageQueue::deleteExCommandByIndex(uint idx, bool doFree) { _exCommands.erase(it); } -void MessageQueue::transferExCommands(MessageQueue *mq) { +void MessageQueue::mergeQueue(MessageQueue *mq) { // Original belongs to AniHandler while (mq->_exCommands.size()) { _exCommands.push_back(mq->_exCommands.front()); mq->_exCommands.pop_front(); @@ -682,7 +682,7 @@ int GlobalMessageQueueList::compact() { useList[i] = 0; for (uint i = 0; i < size();) { - if (((MessageQueue *)_storage[i])->_isFinished) { + if (_storage[i]->_isFinished) { disableQueueById(_storage[i]->_id); remove_at(i); } else { diff --git a/engines/fullpipe/messages.h b/engines/fullpipe/messages.h index e6f7f05150..67fbb2a6cd 100644 --- a/engines/fullpipe/messages.h +++ b/engines/fullpipe/messages.h @@ -142,7 +142,7 @@ class MessageQueue : public CObject { ExCommand *getExCommandByIndex(uint idx); void deleteExCommandByIndex(uint idx, bool doFree); - void transferExCommands(MessageQueue *mq); + void mergeQueue(MessageQueue *mq); void replaceKeyCode(int key1, int key2); diff --git a/engines/fullpipe/modal.cpp b/engines/fullpipe/modal.cpp index 096323781f..aab4843067 100644 --- a/engines/fullpipe/modal.cpp +++ b/engines/fullpipe/modal.cpp @@ -309,7 +309,7 @@ bool ModalMap::init(int counterdiff) { _rect2.right = _rect2.left + 800; _rect2.bottom = _rect2.top + 600; - g_fp->_sceneRect =_rect2; + g_fp->_sceneRect = _rect2; _mapScene->updateScrolling2(); @@ -344,19 +344,19 @@ bool ModalMap::handleMessage(ExCommand *cmd) { case 29: _flag = 1; _mouseX = g_fp->_mouseScreenPos.x; - _mouseY = g_fp->_mouseScreenPos.x; + _mouseY = g_fp->_mouseScreenPos.y; - _field_3C = _rect2.top; _field_38 = _rect2.left; + _field_3C = _rect2.top; - break; + return false; case 30: _flag = 0; - break; + return false; case 36: - if (cmd->_keyCode != 9 && cmd->_keyCode != 27 ) + if (cmd->_keyCode != 9 && cmd->_keyCode != 27) return false; break; @@ -431,20 +431,20 @@ PictureObject *ModalMap::getScenePicture() { switch (g_fp->_currentScene->_sceneId) { case SC_1: - picId = PIC_MAP_S01; - break; + picId = PIC_MAP_S01; + break; case SC_2: - picId = PIC_MAP_S02; - break; + picId = PIC_MAP_S02; + break; case SC_3: - picId = PIC_MAP_S03; - break; + picId = PIC_MAP_S03; + break; case SC_4: - picId = PIC_MAP_S04; - break; + picId = PIC_MAP_S04; + break; case SC_5: - picId = PIC_MAP_S05; - break; + picId = PIC_MAP_S05; + break; case SC_6: picId = PIC_MAP_S06; break; @@ -489,7 +489,7 @@ PictureObject *ModalMap::getScenePicture() { picId = PIC_MAP_S20; break; case SC_21: - picId = PIC_MAP_S21; + picId = PIC_MAP_S21; break; case SC_22: picId = PIC_MAP_S22; diff --git a/engines/fullpipe/module.mk b/engines/fullpipe/module.mk index 96bd91fd39..01aba1bd82 100644 --- a/engines/fullpipe/module.mk +++ b/engines/fullpipe/module.mk @@ -1,6 +1,7 @@ MODULE := engines/fullpipe MODULE_OBJS = \ + anihandler.o \ behavior.o \ console.o \ detection.o \ @@ -15,7 +16,6 @@ MODULE_OBJS = \ lift.o \ messagehandlers.o \ messages.o \ - mgm.o \ modal.o \ motion.o \ ngiarchive.o \ diff --git a/engines/fullpipe/motion.cpp b/engines/fullpipe/motion.cpp index fbfa602e62..81d92ccac8 100644 --- a/engines/fullpipe/motion.cpp +++ b/engines/fullpipe/motion.cpp @@ -132,11 +132,11 @@ int MctlCompound::detachObject(StaticANIObject *obj) { return 1; } -void MctlCompound::initMovGraph2() { +void MctlCompound::initMctlGraph() { if (_objtype != kObjTypeMctlCompound) return; - debugC(4, kDebugPathfinding, "MctlCompound::initMovGraph2()"); + debugC(4, kDebugPathfinding, "MctlCompound::initMctlGraph()"); for (uint i = 0; i < _motionControllers.size(); i++) { if (_motionControllers[i]->_motionControllerObj->_objtype != kObjTypeMovGraph) @@ -144,7 +144,7 @@ void MctlCompound::initMovGraph2() { MovGraph *gr = (MovGraph *)_motionControllers[i]->_motionControllerObj; - MovGraph2 *newgr = new MovGraph2(); + MctlGraph *newgr = new MctlGraph(); newgr->_links = gr->_links; newgr->_nodes = gr->_nodes; @@ -208,7 +208,7 @@ MessageQueue *MctlCompound::startMove(StaticANIObject *ani, int sourceX, int sou if (!cp) return 0; - MessageQueue *mq = _motionControllers[idx]->_motionControllerObj->doWalkTo(ani, cp->_connectionX, cp->_connectionY, 1, cp->_mctlmirror); + MessageQueue *mq = _motionControllers[idx]->_motionControllerObj->makeQueue(ani, cp->_connectionX, cp->_connectionY, 1, cp->_mctlmirror); if (!mq) return 0; @@ -237,11 +237,11 @@ MessageQueue *MctlCompound::startMove(StaticANIObject *ani, int sourceX, int sou return mq; } -MessageQueue *MctlCompound::doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { +MessageQueue *MctlCompound::makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { int match1 = -1; int match2 = -1; - debugC(1, kDebugPathfinding, "MctlCompound::doWalkTo(*%d, %d, %d, %d, %d)", (subj ? subj->_id : -1), xpos, ypos, fuzzyMatch, staticsId); + debugC(1, kDebugPathfinding, "MctlCompound::makeQueue(*%d, %d, %d, %d, %d)", (subj ? subj->_id : -1), xpos, ypos, fuzzyMatch, staticsId); if (!subj) return 0; @@ -271,7 +271,7 @@ MessageQueue *MctlCompound::doWalkTo(StaticANIObject *subj, int xpos, int ypos, return 0; if (match1 == match2) - return _motionControllers[match1]->_motionControllerObj->doWalkTo(subj, xpos, ypos, fuzzyMatch, staticsId); + return _motionControllers[match1]->_motionControllerObj->makeQueue(subj, xpos, ypos, fuzzyMatch, staticsId); double dist; MctlConnectionPoint *closestP = findClosestConnectionPoint(subj->_ox, subj->_oy, match1, xpos, ypos, match2, &dist); @@ -279,7 +279,7 @@ MessageQueue *MctlCompound::doWalkTo(StaticANIObject *subj, int xpos, int ypos, if (!closestP) return 0; - MessageQueue *mq = _motionControllers[match1]->_motionControllerObj->doWalkTo(subj, closestP->_connectionX, closestP->_connectionY, 1, closestP->_mctlmirror); + MessageQueue *mq = _motionControllers[match1]->_motionControllerObj->makeQueue(subj, closestP->_connectionX, closestP->_connectionY, 1, closestP->_mctlmirror); ExCommand *ex; @@ -348,7 +348,7 @@ void MctlLadder::attachObject(StaticANIObject *obj) { MctlLadderMovement *movement = new MctlLadderMovement; if (initMovement(obj, movement)) { - _mgm.addItem(obj->_id); + _aniHandler.attachObject(obj->_id); _ladmovements.push_back(movement); } else { delete movement; @@ -372,16 +372,16 @@ bool MctlLadder::initMovement(StaticANIObject *ani, MctlLadderMovement *movement if (!v) return false; - v = v->getSubVarByName("Test_Ladder"); + GameVar *l = v->getSubVarByName("Test_Ladder"); - if (!v) + if (!l) return false; movement->staticIdsSize = 6; movement->movVars = new MctlLadderMovementVars; movement->staticIds = new int[movement->staticIdsSize]; - v = v->getSubVarByName("Up"); + v = l->getSubVarByName("Up"); if (!v) return false; @@ -393,7 +393,7 @@ bool MctlLadder::initMovement(StaticANIObject *ani, MctlLadderMovement *movement movement->staticIds[0] = ani->getMovementById(movement->movVars->varUpStart)->_staticsObj1->_staticsId; movement->staticIds[2] = ani->getMovementById(movement->movVars->varUpGo)->_staticsObj1->_staticsId; - v = v->getSubVarByName("Down"); + v = l->getSubVarByName("Down"); if (!v) return false; @@ -413,7 +413,7 @@ bool MctlLadder::initMovement(StaticANIObject *ani, MctlLadderMovement *movement void MctlLadder::detachAllObjects() { debugC(4, kDebugPathfinding, "MctlLadder::detachAllObjects()"); - _mgm.clear(); + _aniHandler.detachAllObjects(); for (uint i = 0; i < _ladmovements.size(); i++) { delete _ladmovements[i]->movVars; @@ -426,7 +426,7 @@ void MctlLadder::detachAllObjects() { MessageQueue *MctlLadder::startMove(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { debugC(4, kDebugPathfinding, "MctlLadder::startMove(*%d, %d, %d, %d, %d)", (subj ? subj->_id : -1), xpos, ypos, fuzzyMatch, staticsId); - MessageQueue *mq = doWalkTo(subj, xpos, ypos, fuzzyMatch, staticsId); + MessageQueue *mq = makeQueue(subj, xpos, ypos, fuzzyMatch, staticsId); if (mq) { if (mq->chain(subj)) @@ -436,8 +436,8 @@ MessageQueue *MctlLadder::startMove(StaticANIObject *subj, int xpos, int ypos, i return 0; } -MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int fuzzyMatch, int staticsId) { - debugC(1, kDebugPathfinding, "MctlLadder::doWalkTo(*%d, %d, %d, %d, %d)", (ani ? ani->_id : -1), xpos, ypos, fuzzyMatch, staticsId); +MessageQueue *MctlLadder::makeQueue(StaticANIObject *ani, int xpos, int ypos, int fuzzyMatch, int staticsId) { + debugC(1, kDebugPathfinding, "MctlLadder::makeQueue(*%d, %d, %d, %d, %d)", (ani ? ani->_id : -1), xpos, ypos, fuzzyMatch, staticsId); int pos = findObjectPos(ani); @@ -459,7 +459,7 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int int direction = (normy - ani->_oy) < 0 ? 0 : 1; - MGMInfo mgminfo; + MakeQueueStruct mkQueue; PicAniInfo picinfo; MessageQueue *mq; ExCommand *ex; @@ -476,7 +476,7 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int ani->_movement = 0; ani->setOXY(point.x + ox, point.y + oy); - mq = doWalkTo(ani, normx, normy, fuzzyMatch, staticsId); + mq = makeQueue(ani, normx, normy, fuzzyMatch, staticsId); ani->setPicAniInfo(&picinfo); @@ -484,38 +484,38 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int } if (ani->_statics->_staticsId == _ladmovements[pos]->staticIds[0]) { - mgminfo.ani = ani; + mkQueue.ani = ani; if (staticsId) - mgminfo.staticsId2 = staticsId; + mkQueue.staticsId2 = staticsId; else - mgminfo.staticsId2 = _ladmovements[pos]->staticIds[direction]; + mkQueue.staticsId2 = _ladmovements[pos]->staticIds[direction]; - mgminfo.x1 = normx; - mgminfo.y1 = normy; - mgminfo.field_1C = _ladder_field_14; - mgminfo.flags = 14; - mgminfo.movementId = direction ? _ladmovements[pos]->movVars->varDownGo : _ladmovements[pos]->movVars->varUpGo; + mkQueue.x1 = normx; + mkQueue.y1 = normy; + mkQueue.field_1C = _ladder_field_14; + mkQueue.flags = 14; + mkQueue.movementId = direction ? _ladmovements[pos]->movVars->varDownGo : _ladmovements[pos]->movVars->varUpGo; - return _mgm.genMovement(&mgminfo); + return _aniHandler.makeRunQueue(&mkQueue); } if (ani->_statics->_staticsId == _ladmovements[pos]->staticIds[2]) { if (!direction) { - mgminfo.ani = ani; + mkQueue.ani = ani; if (staticsId) - mgminfo.staticsId2 = staticsId; + mkQueue.staticsId2 = staticsId; else - mgminfo.staticsId2 = _ladmovements[pos]->staticIds[0]; + mkQueue.staticsId2 = _ladmovements[pos]->staticIds[0]; - mgminfo.x1 = normx; - mgminfo.y1 = normy; - mgminfo.field_1C = _ladder_field_14; - mgminfo.flags = 14; - mgminfo.movementId = _ladmovements[pos]->movVars->varUpGo; + mkQueue.x1 = normx; + mkQueue.y1 = normy; + mkQueue.field_1C = _ladder_field_14; + mkQueue.flags = 14; + mkQueue.movementId = _ladmovements[pos]->movVars->varUpGo; - return _mgm.genMovement(&mgminfo); + return _aniHandler.makeRunQueue(&mkQueue); } int ox = ani->_ox; @@ -523,23 +523,23 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int ani->getMovementById(_ladmovements[pos]->movVars->varUpStop)->calcSomeXY(point, 0, -1); - mgminfo.ani = ani; + mkQueue.ani = ani; if (staticsId) - mgminfo.staticsId2 = staticsId; + mkQueue.staticsId2 = staticsId; else - mgminfo.staticsId2 = _ladmovements[pos]->staticIds[1]; + mkQueue.staticsId2 = _ladmovements[pos]->staticIds[1]; - mgminfo.field_1C = _ladder_field_14; - mgminfo.x1 = normx; - mgminfo.y1 = normy; - mgminfo.y2 = point.y + oy; - mgminfo.x2 = point.x + ox; - mgminfo.flags = 63; - mgminfo.staticsId1 = _ladmovements[pos]->staticIds[0]; - mgminfo.movementId = _ladmovements[pos]->movVars->varDownGo; + mkQueue.field_1C = _ladder_field_14; + mkQueue.x1 = normx; + mkQueue.y1 = normy; + mkQueue.y2 = point.y + oy; + mkQueue.x2 = point.x + ox; + mkQueue.flags = 63; + mkQueue.staticsId1 = _ladmovements[pos]->staticIds[0]; + mkQueue.movementId = _ladmovements[pos]->movVars->varDownGo; - mq = _mgm.genMovement(&mgminfo); + mq = _aniHandler.makeRunQueue(&mkQueue); ex = new ExCommand(ani->_id, 1, _ladmovements[pos]->movVars->varUpStop, 0, 0, 0, 1, 0, 0, 0); ex->_keyCode = ani->_okeyCode; @@ -551,7 +551,7 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int } if (ani->_statics->_staticsId != _ladmovements[pos]->staticIds[3]) { - mq = _mgm.genMQ(ani, _ladmovements[pos]->staticIds[0], 0, 0, 0); + mq = _aniHandler.makeQueue(ani, _ladmovements[pos]->staticIds[0], 0, 0, 0); if (!mq) return 0; @@ -559,7 +559,7 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int int nx = ani->_ox; int ny = ani->_oy; - _mgm.getPoint(&point, ani->_id, ani->_statics->_staticsId, _ladmovements[pos]->staticIds[0]); + _aniHandler.getTransitionSize(&point, ani->_id, ani->_statics->_staticsId, _ladmovements[pos]->staticIds[0]); nx += point.x; ny += point.y; @@ -570,9 +570,9 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int ani->_movement = 0; ani->setOXY(nx, ny); - MessageQueue *newmq = doWalkTo(ani, normx, normy, fuzzyMatch, staticsId); + MessageQueue *newmq = makeQueue(ani, normx, normy, fuzzyMatch, staticsId); - mq->transferExCommands(newmq); + mq->mergeQueue(newmq); delete newmq; @@ -590,22 +590,22 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int nx += point.x; ny += point.y; - mgminfo.ani = ani; + mkQueue.ani = ani; if (staticsId) - mgminfo.staticsId2 = staticsId; + mkQueue.staticsId2 = staticsId; else - mgminfo.staticsId2 = _ladmovements[pos]->staticIds[0]; + mkQueue.staticsId2 = _ladmovements[pos]->staticIds[0]; - mgminfo.field_1C = _ladder_field_14; - mgminfo.x1 = normx; - mgminfo.y1 = normy; - mgminfo.y2 = ny; - mgminfo.x2 = nx; - mgminfo.flags = 63; - mgminfo.staticsId1 = _ladmovements[pos]->staticIds[1]; - mgminfo.movementId = _ladmovements[pos]->movVars->varUpGo; + mkQueue.field_1C = _ladder_field_14; + mkQueue.x1 = normx; + mkQueue.y1 = normy; + mkQueue.y2 = ny; + mkQueue.x2 = nx; + mkQueue.flags = 63; + mkQueue.staticsId1 = _ladmovements[pos]->staticIds[1]; + mkQueue.movementId = _ladmovements[pos]->movVars->varUpGo; - mq = _mgm.genMovement(&mgminfo); + mq = _aniHandler.makeRunQueue(&mkQueue); ex = new ExCommand(ani->_id, 1, _ladmovements[pos]->movVars->varDownStop, 0, 0, 0, 1, 0, 0, 0); ex->_keyCode = ani->_okeyCode; @@ -617,24 +617,24 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int } - mgminfo.ani = ani; + mkQueue.ani = ani; if (staticsId) - mgminfo.staticsId2 = staticsId; + mkQueue.staticsId2 = staticsId; else - mgminfo.staticsId2 = _ladmovements[pos]->staticIds[1]; + mkQueue.staticsId2 = _ladmovements[pos]->staticIds[1]; - mgminfo.x1 = normx; - mgminfo.y1 = normy; - mgminfo.field_1C = _ladder_field_14; - mgminfo.flags = 14; - mgminfo.movementId = _ladmovements[pos]->movVars->varDownGo; + mkQueue.x1 = normx; + mkQueue.y1 = normy; + mkQueue.field_1C = _ladder_field_14; + mkQueue.flags = 14; + mkQueue.movementId = _ladmovements[pos]->movVars->varDownGo; - return _mgm.genMovement(&mgminfo); + return _aniHandler.makeRunQueue(&mkQueue); } MessageQueue *MctlLadder::controllerWalkTo(StaticANIObject *ani, int off) { - return doWalkTo(ani, _ladderX + off * _width, _ladderY + off * _height, 1, 0); + return makeQueue(ani, _ladderX + off * _width, _ladderY + off * _height, 1, 0); } MctlConnectionPoint *MctlCompound::findClosestConnectionPoint(int ox, int oy, int destIndex, int connectionX, int connectionY, int sourceIdx, double *minDistancePtr) { @@ -705,7 +705,7 @@ MctlConnectionPoint::~MctlConnectionPoint() { delete _messageQueueObj; } -MovInfo1::MovInfo1(MovInfo1 *src) { +MctlMQ::MctlMQ(MctlMQ *src) { index = src->index; pt1 = src->pt1; pt2 = src->pt2; @@ -718,7 +718,7 @@ MovInfo1::MovInfo1(MovInfo1 *src) { flags = src->flags; } -void MovInfo1::clear() { +void MctlMQ::clear() { index = 0; pt1.x = pt1.y = 0; pt2.x = pt2.y = 0; @@ -806,8 +806,8 @@ bool MovGraph::load(MfcArchive &file) { void MovGraph::attachObject(StaticANIObject *obj) { debugC(4, kDebugPathfinding, "MovGraph::attachObject(*%d)", obj->_id); - _mgm.clear(); - _mgm.addItem(obj->_id); + _aniHandler.detachAllObjects(); + _aniHandler.attachObject(obj->_id); for (uint i = 0; i < _items.size(); i++) if (_items[i]->ani == obj) @@ -819,7 +819,7 @@ void MovGraph::attachObject(StaticANIObject *obj) { _items.push_back(item); - _mgm.addItem(obj->_id); // FIXME: Is it really needed? + _aniHandler.attachObject(obj->_id); // FIXME: Is it really needed? } int MovGraph::detachObject(StaticANIObject *obj) { @@ -1008,8 +1008,8 @@ bool MovGraph::resetPosition(StaticANIObject *ani, int flag) { Statics *st; if (ani->_statics) { - int t = _mgm.refreshOffsets(ani->_id, ani->_statics->_staticsId, movarr._link->_dwordArray2[_field_44]); - if (t > _mgm.refreshOffsets(ani->_id, ani->_statics->_staticsId, movarr._link->_dwordArray2[_field_44 + 1])) + int t = _aniHandler.getNumMovements(ani->_id, ani->_statics->_staticsId, movarr._link->_dwordArray2[_field_44]); + if (t > _aniHandler.getNumMovements(ani->_id, ani->_statics->_staticsId, movarr._link->_dwordArray2[_field_44 + 1])) st = ani->getStaticsById(movarr._link->_dwordArray2[_field_44 + 1]); else st = ani->getStaticsById(movarr._link->_dwordArray2[_field_44]); @@ -1051,8 +1051,8 @@ bool MovGraph::canDropInventory(StaticANIObject *ani, int x, int y) { return false; } -MessageQueue *MovGraph::doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { - debugC(1, kDebugPathfinding, "MovGraph::doWalkTo(*%d, %d, %d, %d, %d)", (subj ? subj->_id : -1), xpos, ypos, fuzzyMatch, staticsId); +MessageQueue *MovGraph::makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { + debugC(1, kDebugPathfinding, "MovGraph::makeQueue(*%d, %d, %d, %d, %d)", (subj ? subj->_id : -1), xpos, ypos, fuzzyMatch, staticsId); PicAniInfo picAniInfo; int ss; @@ -1230,28 +1230,28 @@ MessageQueue *MovGraph::makeWholeQueue(StaticANIObject *ani, MovArr *movarr, int } } - MGMInfo mgminfo; + MakeQueueStruct mkQueue; - memset(&mgminfo, 0, sizeof(mgminfo)); - mgminfo.ani = ani; - mgminfo.staticsId2 = id2; - mgminfo.staticsId1 = id1; - mgminfo.x1 = nx; - mgminfo.x2 = ox; - mgminfo.y2 = oy; - mgminfo.y1 = ny; - mgminfo.field_1C = nd; - mgminfo.movementId = st->link->_dwordArray1[_field_44 + st->sfield_0]; + memset(&mkQueue, 0, sizeof(mkQueue)); + mkQueue.ani = ani; + mkQueue.staticsId2 = id2; + mkQueue.staticsId1 = id1; + mkQueue.x1 = nx; + mkQueue.x2 = ox; + mkQueue.y2 = oy; + mkQueue.y1 = ny; + mkQueue.field_1C = nd; + mkQueue.movementId = st->link->_dwordArray1[_field_44 + st->sfield_0]; - mgminfo.flags = 0xe; + mkQueue.flags = 0xe; if (mq) - mgminfo.flags |= 0x31; + mkQueue.flags |= 0x31; - MessageQueue *newmq = _mgm.genMovement(&mgminfo); + MessageQueue *newmq = _aniHandler.makeRunQueue(&mkQueue); if (mq) { if (newmq) { - mq->transferExCommands(newmq); + mq->mergeQueue(newmq); delete newmq; } @@ -1332,9 +1332,9 @@ double MovGraph::putToLink(Common::Point *point, MovGraphLink *link, int fuzzyMa int n2x = link->_graphDst->_x; int n2y = link->_graphDst->_y; double dist1x = (double)(point->x - n1x); - double dist1y = (double)(point->y - n1y); + double dist1y = (double)(n1y - point->y); double dist2x = (double)(n2x - n1x); - double dist2y = (double)(n2y - n1y); + double dist2y = (double)(n1y - n2y); double dist1 = sqrt(dist1x * dist1x + dist1y * dist1y); double dist2 = (dist2y * dist1y + dist2x * dist1x) / link->_length / dist1; double distm = dist2 * dist1; @@ -1355,8 +1355,8 @@ double MovGraph::putToLink(Common::Point *point, MovGraphLink *link, int fuzzyMa return -1.0; } } else { - point->x = (int)(n1x + (dist2x * distm / link->_length)); - point->y = (int)(n1y + (dist2y * distm / link->_length)); + point->x = n1x + (int)((double)(n2x - n1x) * distm / link->_length); + point->y = n1y + (int)((double)(n2y - n1y) * distm / link->_length); } return res; @@ -1608,14 +1608,14 @@ bool MovGraph::getHitPoint(int idx, int x, int y, MovArr *arr, int a6) { int offmin = 100; for (int i = 0; i < arrSize; i++) { - int off = _mgm.refreshOffsets(_items[idx]->ani->_id, staticsId, (*movarr)[i]->_link->_dwordArray2[_field_44]); + int off = _aniHandler.getNumMovements(_items[idx]->ani->_id, staticsId, (*movarr)[i]->_link->_dwordArray2[_field_44]); if (off < offmin) { offmin = off; idxmin = i; } - off = _mgm.refreshOffsets(_items[idx]->ani->_id, staticsId, (*movarr)[i]->_link->_dwordArray2[_field_44 + 1]); + off = _aniHandler.getNumMovements(_items[idx]->ani->_id, staticsId, (*movarr)[i]->_link->_dwordArray2[_field_44 + 1]); if (off < offmin) { offmin = off; idxmin = i; @@ -1657,7 +1657,7 @@ void MovGraph::setEnds(MovStep *step1, MovStep *step2) { } } -int MovGraph2::getItemIndexByGameObjectId(int objectId) { +int MctlGraph::getObjIndex(int objectId) { for (uint i = 0; i < _items2.size(); i++) if (_items2[i]->_objectId == objectId) return i; @@ -1665,7 +1665,7 @@ int MovGraph2::getItemIndexByGameObjectId(int objectId) { return -1; } -int MovGraph2::getItemSubIndexByStaticsId(int idx, int staticsId) { +int MctlGraph::getDirByStatics(int idx, int staticsId) { for (int i = 0; i < 4; i++) if (_items2[idx]->_subItems[i]._staticsId1 == staticsId || _items2[idx]->_subItems[i]._staticsId2 == staticsId) return i; @@ -1673,7 +1673,7 @@ int MovGraph2::getItemSubIndexByStaticsId(int idx, int staticsId) { return -1; } -int MovGraph2::getItemSubIndexByMovementId(int idx, int movId) { +int MctlGraph::getDirByMovement(int idx, int movId) { for (int i = 0; i < 4; i++) if (_items2[idx]->_subItems[i]._walk[0]._movementId == movId || _items2[idx]->_subItems[i]._turn[0]._movementId == movId || _items2[idx]->_subItems[i]._turnS[0]._movementId == movId) @@ -1682,14 +1682,14 @@ int MovGraph2::getItemSubIndexByMovementId(int idx, int movId) { return -1; } -int MovGraph2::getItemSubIndexByMGM(int index, StaticANIObject *ani) { - if (findNode(ani->_ox, ani->_oy, 0) || findLink1(ani->_ox, ani->_oy, -1, 0) || findLink2(ani->_ox, ani->_oy)) { +int MctlGraph::getDirByPoint(int index, StaticANIObject *ani) { + if (getHitNode(ani->_ox, ani->_oy, 0) || getHitLink(ani->_ox, ani->_oy, -1, 0) || getNearestLink(ani->_ox, ani->_oy)) { int minidx = -1; int min = 0; for (int i = 0; i < 4; i++) { debugC(1, kDebugPathfinding, "WWW 5"); - int tmp = _mgm.refreshOffsets(ani->_id, ani->_statics->_staticsId, _items2[index]->_subItems[i]._staticsId1); + int tmp = _aniHandler.getNumMovements(ani->_id, ani->_statics->_staticsId, _items2[index]->_subItems[i]._staticsId1); if (tmp >= 0 && (minidx == -1 || tmp < min)) { minidx = i; @@ -1703,8 +1703,8 @@ int MovGraph2::getItemSubIndexByMGM(int index, StaticANIObject *ani) { return -1; } -bool MovGraph2::initDirections(StaticANIObject *obj, MovGraph2Item *item) { - debugC(4, kDebugPathfinding, "MovGraph::initDirections(%d, ...)", obj->_id); +bool MctlGraph::fillData(StaticANIObject *obj, MctlAni *item) { + debugC(4, kDebugPathfinding, "MovGraph::fillData(%d, ...)", obj->_id); item->_obj = obj; item->_objectId = obj->_id; @@ -1834,19 +1834,19 @@ bool MovGraph2::initDirections(StaticANIObject *obj, MovGraph2Item *item) { return true; } -void MovGraph2::attachObject(StaticANIObject *obj) { - debugC(4, kDebugPathfinding, "MovGraph2::attachObject(*%d)", obj->_id); +void MctlGraph::attachObject(StaticANIObject *obj) { + debugC(4, kDebugPathfinding, "MctlGraph::attachObject(*%d)", obj->_id); MovGraph::attachObject(obj); - int id = getItemIndexByGameObjectId(obj->_id); + int id = getObjIndex(obj->_id); if (id >= 0) { _items2[id]->_obj = obj; } else { - MovGraph2Item *item = new MovGraph2Item; + MctlAni *item = new MctlAni; - if (initDirections(obj, item)) { + if (fillData(obj, item)) { _items2.push_back(item); } else { delete item; @@ -1854,10 +1854,10 @@ void MovGraph2::attachObject(StaticANIObject *obj) { } } -void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphLink *> *linkList, LinkInfo *lnkSrc, LinkInfo *lnkDst) { - debugC(4, kDebugPathfinding, "MovGraph2::buildMovInfo1SubItems(...)"); +void MctlGraph::generateList(MctlMQ *movinfo, Common::Array<MovGraphLink *> *linkList, LinkInfo *lnkSrc, LinkInfo *lnkDst) { + debugC(4, kDebugPathfinding, "MctlGraph::generateList(...)"); - MovInfo1Sub *elem; + MctlMQSub *elem; Common::Point point; Common::Rect rect; @@ -1865,7 +1865,7 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL movinfo->items.clear(); - elem = new MovInfo1Sub; + elem = new MctlMQSub; elem->subIndex = subIndex; elem->x = movinfo->pt1.x; elem->y = movinfo->pt1.y; @@ -1880,9 +1880,9 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL if (linkList->size() <= 1) { if (linkList->size() == 1) - idx1 = getShortSide((*linkList)[0], movinfo->pt2.x - movinfo->pt1.x, movinfo->pt2.y - movinfo->pt1.y); + idx1 = getDirBySize((*linkList)[0], movinfo->pt2.x - movinfo->pt1.x, movinfo->pt2.y - movinfo->pt1.y); else - idx1 = getShortSide(0, movinfo->pt2.x - movinfo->pt1.x, movinfo->pt2.y - movinfo->pt1.y); + idx1 = getDirBySize(0, movinfo->pt2.x - movinfo->pt1.x, movinfo->pt2.y - movinfo->pt1.y); point.y = -1; rect.bottom = -1; @@ -1890,14 +1890,14 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL rect.top = -1; rect.left = -1; } else { - idx1 = findLink(linkList, i, &rect, &point); + idx1 = getLinkDir(linkList, i, &rect, &point); } if (idx1 != prevSubIndex) { prevSubIndex = idx1; subIndex = idx1; - elem = new MovInfo1Sub; + elem = new MctlMQSub; elem->subIndex = subIndex; elem->x = rect.left; elem->y = rect.top; @@ -1909,9 +1909,9 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL if (i != linkList->size() - 1) { while (1) { i++; - if (findLink(linkList, i, &rect, 0) != prevSubIndex) { + if (getLinkDir(linkList, i, &rect, 0) != prevSubIndex) { i--; - findLink(linkList, i, &rect, &point); + getLinkDir(linkList, i, &rect, &point); break; } @@ -1924,7 +1924,7 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL if (movinfo->items.back()->subIndex != 10) { subIndex = prevSubIndex; - elem = new MovInfo1Sub; + elem = new MctlMQSub; elem->subIndex = 10; elem->x = -1; elem->y = -1; @@ -1932,8 +1932,8 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL movinfo->items.push_back(elem); - if (i == linkList->size()) { - elem = new MovInfo1Sub; + if (i == linkList->size() - 1) { + elem = new MctlMQSub; elem->subIndex = prevSubIndex; elem->x = movinfo->pt2.x; elem->y = movinfo->pt2.y; @@ -1941,7 +1941,7 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL movinfo->items.push_back(elem); } else { - elem = new MovInfo1Sub; + elem = new MctlMQSub; elem->subIndex = prevSubIndex; elem->x = rect.right; elem->y = rect.bottom; @@ -1953,7 +1953,7 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL } if (subIndex != movinfo->item1Index) { - elem = new MovInfo1Sub; + elem = new MctlMQSub; elem->subIndex = movinfo->item1Index; elem->x = movinfo->pt2.x; elem->y = movinfo->pt2.y; @@ -1965,40 +1965,40 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL movinfo->itemsCount = movinfo->items.size(); } -MessageQueue *MovGraph2::buildMovInfo1MessageQueue(MovInfo1 *movInfo) { - debugC(4, kDebugPathfinding, "MovGraph2::buildMovInfo1MessageQueue(...)"); +MessageQueue *MctlGraph::makeWholeQueue(MctlMQ *mctlMQ) { + debugC(4, kDebugPathfinding, "MctlGraph::makeWholeQueue(...)"); - MovInfo1 movinfo(movInfo); + MctlMQ movinfo(mctlMQ); - int curX = movInfo->pt1.x; - int curY = movInfo->pt1.y; - int curDistance = movInfo->distance1; + int curX = mctlMQ->pt1.x; + int curY = mctlMQ->pt1.y; + int curDistance = mctlMQ->distance1; MessageQueue *mq = new MessageQueue(g_fp->_globalMessageQueueList->compact()); - for (int i = 0; i < movInfo->itemsCount - 1; i++) { - if (movInfo->items[i + 1]->subIndex != 10) { + for (int i = 0; i < mctlMQ->itemsCount - 1; i++) { + if (mctlMQ->items[i + 1]->subIndex != 10) { MG2I *mg2i; - if (i >= movInfo->itemsCount - 2 || movInfo->items[i + 2]->subIndex != 10) { + if (i >= mctlMQ->itemsCount - 2 || mctlMQ->items[i + 2]->subIndex != 10) { movinfo.flags = 0; - mg2i = &_items2[movInfo->index]->_subItems[movInfo->items[i]->subIndex]._turnS[movInfo->items[i + 1]->subIndex]; + mg2i = &_items2[mctlMQ->index]->_subItems[mctlMQ->items[i]->subIndex]._turnS[mctlMQ->items[i + 1]->subIndex]; } else { movinfo.flags = 2; - mg2i = &_items2[movInfo->index]->_subItems[movInfo->items[i]->subIndex]._turn[movInfo->items[i + 1]->subIndex]; + mg2i = &_items2[mctlMQ->index]->_subItems[mctlMQ->items[i]->subIndex]._turn[mctlMQ->items[i + 1]->subIndex]; } - if (i < movInfo->itemsCount - 2 - || (movInfo->items[i]->x == movInfo->items[i + 1]->x - && movInfo->items[i]->y == movInfo->items[i + 1]->y) - || movInfo->items[i]->x == -1 - || movInfo->items[i]->y == -1 - || movInfo->items[i + 1]->x == -1 - || movInfo->items[i + 1]->y == -1) { + if (i < mctlMQ->itemsCount - 2 + || (mctlMQ->items[i]->x == mctlMQ->items[i + 1]->x + && mctlMQ->items[i]->y == mctlMQ->items[i + 1]->y) + || mctlMQ->items[i]->x == -1 + || mctlMQ->items[i]->y == -1 + || mctlMQ->items[i + 1]->x == -1 + || mctlMQ->items[i + 1]->y == -1) { - ExCommand *ex = new ExCommand(_items2[movInfo->index]->_objectId, 1, mg2i->_movementId, 0, 0, 0, 1, 0, 0, 0); + ExCommand *ex = new ExCommand(_items2[mctlMQ->index]->_objectId, 1, mg2i->_movementId, 0, 0, 0, 1, 0, 0, 0); ex->_excFlags |= 2; - ex->_keyCode = _items2[movInfo->index]->_obj->_okeyCode; + ex->_keyCode = _items2[mctlMQ->index]->_obj->_okeyCode; ex->_field_24 = 1; ex->_field_14 = -1; mq->addExCommandToEnd(ex); @@ -2006,60 +2006,49 @@ MessageQueue *MovGraph2::buildMovInfo1MessageQueue(MovInfo1 *movInfo) { curX += mg2i->_mx; curY += mg2i->_my; } else { - MGMInfo mgminfo; + MakeQueueStruct mkQueue; - memset(&mgminfo, 0, sizeof(mgminfo)); + memset(&mkQueue, 0, sizeof(mkQueue)); - mgminfo.ani = _items2[movInfo->index]->_obj; - mgminfo.staticsId2 = mg2i->_mov->_staticsObj2->_staticsId; - mgminfo.x1 = movInfo->items[i + 1]->x; - mgminfo.y1 = movInfo->items[i + 1]->y; - mgminfo.field_1C = movInfo->items[i + 1]->distance; - mgminfo.staticsId1 = mg2i->_mov->_staticsObj1->_staticsId; + mkQueue.ani = _items2[mctlMQ->index]->_obj; + mkQueue.staticsId2 = mg2i->_mov->_staticsObj2->_staticsId; + mkQueue.x1 = mctlMQ->items[i + 1]->x; + mkQueue.y1 = mctlMQ->items[i + 1]->y; + mkQueue.field_1C = mctlMQ->items[i + 1]->distance; + mkQueue.staticsId1 = mg2i->_mov->_staticsObj1->_staticsId; - mgminfo.x2 = movInfo->items[i]->x; - mgminfo.y2 = movInfo->items[i]->y; - mgminfo.field_10 = 1; - mgminfo.flags = 0x7f; - mgminfo.movementId = mg2i->_movementId; + mkQueue.x2 = mctlMQ->items[i]->x; + mkQueue.y2 = mctlMQ->items[i]->y; + mkQueue.field_10 = 1; + mkQueue.flags = 0x7f; + mkQueue.movementId = mg2i->_movementId; - MessageQueue *mq2 = _mgm.genMovement(&mgminfo); - mq->transferExCommands(mq2); + MessageQueue *mq2 = _aniHandler.makeRunQueue(&mkQueue); + mq->mergeQueue(mq2); delete mq2; - curX = movInfo->items[i + 1]->x; - curY = movInfo->items[i + 1]->y; + curX = mctlMQ->items[i + 1]->x; + curY = mctlMQ->items[i + 1]->y; } } else { - movinfo.item1Index = movInfo->items[i]->subIndex; + movinfo.item1Index = mctlMQ->items[i]->subIndex; movinfo.subIndex = movinfo.item1Index; movinfo.pt1.y = curY; movinfo.pt1.x = curX; movinfo.distance1 = curDistance; - movinfo.pt2.x = movInfo->items[i + 2]->x; - movinfo.pt2.y = movInfo->items[i + 2]->y; - movinfo.distance2 = movInfo->items[i + 2]->distance; - - if (i >= movInfo->itemsCount - 4 - || movInfo->items[i + 2]->subIndex == 10 - || movInfo->items[i + 3]->subIndex == 10 - || movInfo->items[i + 2]->subIndex == movInfo->items[i + 3]->subIndex - || movInfo->items[i + 4]->subIndex != 10) { - if (i >= movInfo->itemsCount - 3 - || movInfo->items[i + 2]->subIndex == 10 - || movInfo->items[i + 3]->subIndex == 10 - || movInfo->items[i + 2]->subIndex == movInfo->items[i + 3]->subIndex) { - movinfo.flags &= 3; - } else { - MG2I *m = &_items2[movInfo->index]->_subItems[movInfo->items[i + 2]->subIndex]._turnS[movInfo->items[i + 3]->subIndex]; - movinfo.pt2.x -= m->_mx; - movinfo.pt2.y -= m->_my; - movinfo.flags &= 3; - } - } else { - MG2I *m = &_items2[movInfo->index]->_subItems[movInfo->items[i + 2]->subIndex]._turn[movInfo->items[i + 3]->subIndex]; + movinfo.pt2.x = mctlMQ->items[i + 2]->x; + movinfo.pt2.y = mctlMQ->items[i + 2]->y; + movinfo.distance2 = mctlMQ->items[i + 2]->distance; + + if (i < mctlMQ->itemsCount - 4 + && mctlMQ->items[i + 2]->subIndex != 10 + && mctlMQ->items[i + 3]->subIndex != 10 + && mctlMQ->items[i + 2]->subIndex != mctlMQ->items[i + 3]->subIndex + && mctlMQ->items[i + 4]->subIndex == 10) { + + MG2I *m = &_items2[mctlMQ->index]->_subItems[mctlMQ->items[i + 2]->subIndex]._turn[mctlMQ->items[i + 3]->subIndex]; if (movinfo.item1Index && movinfo.item1Index != 1) { movinfo.pt2.y -= m->_my; @@ -2068,17 +2057,31 @@ MessageQueue *MovGraph2::buildMovInfo1MessageQueue(MovInfo1 *movInfo) { movinfo.pt2.x -= m->_mx; movinfo.flags = (movinfo.flags & 2) | 1; } + + } else if (i < mctlMQ->itemsCount - 3 + && mctlMQ->items[i + 2]->subIndex != 10 + && mctlMQ->items[i + 3]->subIndex != 10 + && mctlMQ->items[i + 2]->subIndex != mctlMQ->items[i + 3]->subIndex) { + + MG2I *m = &_items2[mctlMQ->index]->_subItems[mctlMQ->items[i + 2]->subIndex]._turnS[mctlMQ->items[i + 3]->subIndex]; + movinfo.pt2.x -= m->_mx; + movinfo.pt2.y -= m->_my; + movinfo.flags = (movinfo.flags & 2) | (mctlMQ->flags & 1); + + } else { + movinfo.flags = (movinfo.flags & 2) | (mctlMQ->flags & 1); } + i++; // intentional - MessageQueue *mq2 = genMovement(&movinfo); + MessageQueue *mq2 = makeLineQueue(&movinfo); if (!mq2) { delete mq; return 0; } - mq->transferExCommands(mq2); + mq->mergeQueue(mq2); delete mq2; @@ -2088,20 +2091,20 @@ MessageQueue *MovGraph2::buildMovInfo1MessageQueue(MovInfo1 *movInfo) { } } - movInfo->pt2.x = movinfo.pt2.x; - movInfo->pt2.y = movinfo.pt2.y; + mctlMQ->pt2.x = movinfo.pt2.x; + mctlMQ->pt2.y = movinfo.pt2.y; return mq; } -int MovGraph2::detachObject(StaticANIObject *obj) { - warning("STUB: MovGraph2::detachObject()"); +int MctlGraph::detachObject(StaticANIObject *obj) { + warning("STUB: MctlGraph::detachObject()"); return 0; } -void MovGraph2::detachAllObjects() { - debugC(4, kDebugPathfinding, "MovGraph2::detachAllObjects()"); +void MctlGraph::detachAllObjects() { + debugC(4, kDebugPathfinding, "MctlGraph::detachAllObjects()"); for (uint i = 0; i < _items2.size(); i++) delete _items2[i]; @@ -2109,8 +2112,8 @@ void MovGraph2::detachAllObjects() { _items2.clear(); } -MessageQueue *MovGraph2::startMove(StaticANIObject *ani, int xpos, int ypos, int fuzzyMatch, int staticsId) { - debugC(4, kDebugPathfinding, "MovGraph2::startMove(*%d, %d, %d, %d, %d)", ani->_id, xpos, ypos, fuzzyMatch, staticsId); +MessageQueue *MctlGraph::startMove(StaticANIObject *ani, int xpos, int ypos, int fuzzyMatch, int staticsId) { + debugC(4, kDebugPathfinding, "MctlGraph::startMove(*%d, %d, %d, %d, %d)", ani->_id, xpos, ypos, fuzzyMatch, staticsId); if (!ani->isIdle()) return 0; @@ -2119,7 +2122,7 @@ MessageQueue *MovGraph2::startMove(StaticANIObject *ani, int xpos, int ypos, int return 0; debugC(1, kDebugPathfinding, "WWW 3"); - MessageQueue *mq = doWalkTo(ani, xpos, ypos, fuzzyMatch, staticsId); + MessageQueue *mq = makeQueue(ani, xpos, ypos, fuzzyMatch, staticsId); if (!mq) return 0; @@ -2130,7 +2133,7 @@ MessageQueue *MovGraph2::startMove(StaticANIObject *ani, int xpos, int ypos, int ani->getPicAniInfo(&picAniInfo); ani->updateStepPos(); - MessageQueue *mq1 = doWalkTo(ani, xpos, ypos, fuzzyMatch, staticsId); + MessageQueue *mq1 = makeQueue(ani, xpos, ypos, fuzzyMatch, staticsId); ani->setPicAniInfo(&picAniInfo); @@ -2153,16 +2156,16 @@ MessageQueue *MovGraph2::startMove(StaticANIObject *ani, int xpos, int ypos, int return mq; } -MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int fuzzyMatch, int staticsId) { +MessageQueue *MctlGraph::makeQueue(StaticANIObject *obj, int xpos, int ypos, int fuzzyMatch, int staticsId) { LinkInfo linkInfoDest; LinkInfo linkInfoSource; - MovInfo1 movInfo1; + MctlMQ mctlMQ1; PicAniInfo picAniInfo; Common::Point point; - debugC(1, kDebugPathfinding, "MovGraph2::doWalkTo(%d, %d, %d, %d, %d)", obj->_id, xpos, ypos, fuzzyMatch, staticsId); + debugC(1, kDebugPathfinding, "MctlGraph::makeQueue(%d, %d, %d, %d, %d)", obj->_id, xpos, ypos, fuzzyMatch, staticsId); - int idx = getItemIndexByGameObjectId(obj->_id); + int idx = getObjIndex(obj->_id); if (idx < 0) return 0; @@ -2180,15 +2183,15 @@ MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int int idxsub; if (obj->_movement) - idxsub = getItemSubIndexByMovementId(idx, obj->_movement->_id); + idxsub = getDirByMovement(idx, obj->_movement->_id); else - idxsub = getItemSubIndexByStaticsId(idx, obj->_statics->_staticsId); + idxsub = getDirByStatics(idx, obj->_statics->_staticsId); bool subMgm = false; if (idxsub == -1) { debugC(1, kDebugPathfinding, "WWW 4"); - idxsub = getItemSubIndexByMGM(idx, obj); + idxsub = getDirByPoint(idx, obj); subMgm = true; if (idxsub == -1) @@ -2226,7 +2229,7 @@ MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int MessageQueue *mq = new MessageQueue(); if (staticsId && obj->_statics->_staticsId != staticsId) { - int idxwalk = getItemSubIndexByStaticsId(idx, staticsId); + int idxwalk = getDirByStatics(idx, staticsId); if (idxwalk == -1) { obj->setPicAniInfo(&picAniInfo); @@ -2262,13 +2265,13 @@ MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int return mq; } - linkInfoSource.node = findNode(obj->_ox, obj->_oy, 0); + linkInfoSource.node = getHitNode(obj->_ox, obj->_oy, 0); if (!linkInfoSource.node) { - linkInfoSource.link = findLink1(obj->_ox, obj->_oy, idxsub, 0); + linkInfoSource.link = getHitLink(obj->_ox, obj->_oy, idxsub, 0); if (!linkInfoSource.link) { - linkInfoSource.link = findLink2(obj->_ox, obj->_oy); + linkInfoSource.link = getNearestLink(obj->_ox, obj->_oy); if (!linkInfoSource.link) { obj->setPicAniInfo(&picAniInfo); @@ -2278,10 +2281,10 @@ MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int } } - linkInfoDest.node = findNode(xpos, ypos, fuzzyMatch); + linkInfoDest.node = getHitNode(xpos, ypos, fuzzyMatch); if (!linkInfoDest.node) { - linkInfoDest.link = findLink1(xpos, ypos, idxsub, fuzzyMatch); + linkInfoDest.link = getHitLink(xpos, ypos, idxsub, fuzzyMatch); if (!linkInfoDest.link) { obj->setPicAniInfo(&picAniInfo); @@ -2291,78 +2294,78 @@ MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int } Common::Array<MovGraphLink *> tempLinkList; - double minPath = findMinPath(&linkInfoSource, &linkInfoDest, &tempLinkList); + double minPath = iterate(&linkInfoSource, &linkInfoDest, &tempLinkList); - debugC(0, kDebugPathfinding, "MovGraph2::doWalkTo(): path: %g parts: %d", minPath, tempLinkList.size()); + debugC(0, kDebugPathfinding, "MctlGraph::makeQueue(): path: %g parts: %d", minPath, tempLinkList.size()); if (minPath < 0.0 || ((linkInfoSource.node != linkInfoDest.node || !linkInfoSource.node) && !tempLinkList.size())) return 0; - movInfo1.clear(); + mctlMQ1.clear(); - movInfo1.subIndex = idxsub; - movInfo1.pt1.x = obj->_ox; - movInfo1.pt1.y = obj->_oy; + mctlMQ1.subIndex = idxsub; + mctlMQ1.pt1.x = obj->_ox; + mctlMQ1.pt1.y = obj->_oy; int dx1 = obj->_ox; int dy1 = obj->_oy; int dx2, dy2; if (linkInfoSource.node) - movInfo1.distance1 = linkInfoSource.node->_z; + mctlMQ1.distance1 = linkInfoSource.node->_z; else - movInfo1.distance1 = linkInfoSource.link->_graphSrc->_z; + mctlMQ1.distance1 = linkInfoSource.link->_graphSrc->_z; if (linkInfoDest.node) { dx2 = linkInfoDest.node->_x; dy2 = linkInfoDest.node->_y; - movInfo1.pt2.x = linkInfoDest.node->_x; - movInfo1.pt2.y = linkInfoDest.node->_y; + mctlMQ1.pt2.x = linkInfoDest.node->_x; + mctlMQ1.pt2.y = linkInfoDest.node->_y; - movInfo1.distance2 = linkInfoDest.node->_z; + mctlMQ1.distance2 = linkInfoDest.node->_z; } else { - movInfo1.pt2.x = xpos; - movInfo1.pt2.y = ypos; + mctlMQ1.pt2.x = xpos; + mctlMQ1.pt2.y = ypos; MovGraphNode *nod = linkInfoDest.link->_graphSrc; double dst1 = sqrt((double)((ypos - nod->_y) * (ypos - nod->_y) + (xpos - nod->_x) * (xpos - nod->_x))); int dst = linkInfoDest.link->_graphDst->_z - nod->_z; - movInfo1.distance2 = (int)(nod->_z + (dst1 * (double)dst / linkInfoDest.link->_length)); + mctlMQ1.distance2 = (int)(nod->_z + (dst1 * (double)dst / linkInfoDest.link->_length)); - putToLink(&movInfo1.pt2, linkInfoDest.link, 1); + putToLink(&mctlMQ1.pt2, linkInfoDest.link, 1); - dx1 = movInfo1.pt1.x; - dy1 = movInfo1.pt1.y; - dx2 = movInfo1.pt2.x; - dy2 = movInfo1.pt2.y; + dx1 = mctlMQ1.pt1.x; + dy1 = mctlMQ1.pt1.y; + dx2 = mctlMQ1.pt2.x; + dy2 = mctlMQ1.pt2.y; } if (staticsId) { - movInfo1.item1Index = getItemSubIndexByStaticsId(idx, staticsId); + mctlMQ1.item1Index = getDirByStatics(idx, staticsId); } else if (tempLinkList.size() <= 1) { if (tempLinkList.size() == 1) - movInfo1.item1Index = getShortSide(tempLinkList[0], dx2 - dx1, dy2 - dy1); + mctlMQ1.item1Index = getDirBySize(tempLinkList[0], dx2 - dx1, dy2 - dy1); else - movInfo1.item1Index = getShortSide(0, dx2 - dx1, dy2 - dy1); + mctlMQ1.item1Index = getDirBySize(0, dx2 - dx1, dy2 - dy1); } else { - movInfo1.item1Index = findLink(&tempLinkList, tempLinkList.size() - 1, 0, 0); + mctlMQ1.item1Index = getLinkDir(&tempLinkList, tempLinkList.size() - 1, 0, 0); } - movInfo1.flags = fuzzyMatch != 0; + mctlMQ1.flags = fuzzyMatch != 0; if (_items2[idx]->_subItems[idxsub]._staticsId1 != obj->_statics->_staticsId) - movInfo1.flags |= 2; + mctlMQ1.flags |= 2; - buildMovInfo1SubItems(&movInfo1, &tempLinkList, &linkInfoSource, &linkInfoDest); + generateList(&mctlMQ1, &tempLinkList, &linkInfoSource, &linkInfoDest); - MessageQueue *mq = buildMovInfo1MessageQueue(&movInfo1); + MessageQueue *mq = makeWholeQueue(&mctlMQ1); - linkInfoDest.node = findNode(movInfo1.pt2.x, movInfo1.pt2.y, fuzzyMatch); + linkInfoDest.node = getHitNode(mctlMQ1.pt2.x, mctlMQ1.pt2.y, fuzzyMatch); if (!linkInfoDest.node) - linkInfoDest.link = findLink1(movInfo1.pt2.x, movInfo1.pt2.y, movInfo1.item1Index, fuzzyMatch); + linkInfoDest.link = getHitLink(mctlMQ1.pt2.x, mctlMQ1.pt2.y, mctlMQ1.item1Index, fuzzyMatch); if (fuzzyMatch || linkInfoDest.link || linkInfoDest.node) { if (mq && mq->getCount() > 0 && picAniInfo.movementId) { @@ -2397,13 +2400,13 @@ MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int return mq; } -MovGraphNode *MovGraph2::findNode(int x, int y, int fuzzyMatch) { +MovGraphNode *MctlGraph::getHitNode(int x, int y, int strictMatch) { for (ObList::iterator i = _nodes.begin(); i != _nodes.end(); ++i) { assert(((CObject *)*i)->_objtype == kObjTypeMovGraphNode); MovGraphNode *node = (MovGraphNode *)*i; - if (fuzzyMatch) { + if (!strictMatch) { if (abs(node->_x - x) < 15 && abs(node->_y - y) < 15) return node; } else { @@ -2415,7 +2418,7 @@ MovGraphNode *MovGraph2::findNode(int x, int y, int fuzzyMatch) { return 0; } -int MovGraph2::getShortSide(MovGraphLink *lnk, int x, int y) { +int MctlGraph::getDirBySize(MovGraphLink *lnk, int x, int y) { bool cond; if (lnk) @@ -2429,8 +2432,8 @@ int MovGraph2::getShortSide(MovGraphLink *lnk, int x, int y) { return ((y > 0) + 2); } -int MovGraph2::findLink(Common::Array<MovGraphLink *> *linkList, int idx, Common::Rect *rect, Common::Point *point) { - debugC(4, kDebugPathfinding, "MovGraph2::findLink(...)"); +int MctlGraph::getLinkDir(Common::Array<MovGraphLink *> *linkList, int idx, Common::Rect *rect, Common::Point *point) { + debugC(4, kDebugPathfinding, "MctlGraph::getLinkDir(...)"); MovGraphNode *node1 = (*linkList)[idx]->_graphSrc; MovGraphNode *node2 = (*linkList)[idx]->_graphDst; @@ -2473,13 +2476,13 @@ int MovGraph2::findLink(Common::Array<MovGraphLink *> *linkList, int idx, Common } if (abs(node3->_x - node2->_x) <= abs(node3->_y - node2->_y)) - return (node3->_y < node2->_x) + 2; + return (node3->_y < node2->_y) + 2; else return node3->_x >= node2->_x; } -MessageQueue *MovGraph2::genMovement(MovInfo1 *info) { - debugC(4, kDebugPathfinding, "MovGraph2::genMovement(...)"); +MessageQueue *MctlGraph::makeLineQueue(MctlMQ *info) { + debugC(4, kDebugPathfinding, "MctlGraph::makeLineQueue(...)"); int mx1 = 0; int my1 = 0; @@ -2504,7 +2507,7 @@ MessageQueue *MovGraph2::genMovement(MovInfo1 *info) { int a2 = 0; int mgmLen; - _mgm.calcLength(&point, _items2[info->index]->_subItems[info->subIndex]._walk[1]._mov, x, y, &mgmLen, &a2, info->flags & 1); + _aniHandler.getNumCycles(&point, _items2[info->index]->_subItems[info->subIndex]._walk[1]._mov, x, y, &mgmLen, &a2, info->flags & 1); int x1 = point.x; int y1 = point.y; @@ -2619,7 +2622,7 @@ MessageQueue *MovGraph2::genMovement(MovInfo1 *info) { ex->_excFlags |= 2; mq->addExCommandToEnd(ex); - ex = _mgm.buildExCommand2( + ex = _aniHandler.createCommand( _items2[info->index]->_subItems[info->subIndex]._walk[0]._mov, _items2[info->index]->_objectId, x1, @@ -2641,7 +2644,7 @@ MessageQueue *MovGraph2::genMovement(MovInfo1 *info) { else par = -1; - ex = _mgm.buildExCommand2( + ex = _aniHandler.createCommand( _items2[info->index]->_subItems[info->subIndex]._walk[1]._mov, _items2[info->index]->_objectId, x1, @@ -2655,7 +2658,7 @@ MessageQueue *MovGraph2::genMovement(MovInfo1 *info) { } if (!(info->flags & 4)) { - ex = _mgm.buildExCommand2( + ex = _aniHandler.createCommand( _items2[info->index]->_subItems[info->subIndex]._walk[2]._mov, _items2[info->index]->_objectId, x1, @@ -2681,8 +2684,8 @@ MessageQueue *MovGraph2::genMovement(MovInfo1 *info) { return mq; } -MovGraphLink *MovGraph2::findLink1(int x, int y, int idx, int fuzzyMatch) { - debugC(4, kDebugPathfinding, "MovGraph2::findLink1(...)"); +MovGraphLink *MctlGraph::getHitLink(int x, int y, int idx, int fuzzyMatch) { + debugC(4, kDebugPathfinding, "MctlGraph::getHitLink(...)"); Common::Point point; MovGraphLink *res = 0; @@ -2717,8 +2720,8 @@ MovGraphLink *MovGraph2::findLink1(int x, int y, int idx, int fuzzyMatch) { return res; } -MovGraphLink *MovGraph2::findLink2(int x, int y) { - debugC(4, kDebugPathfinding, "MovGraph2::findLink2(...)"); +MovGraphLink *MctlGraph::getNearestLink(int x, int y) { + debugC(4, kDebugPathfinding, "MctlGraph::getNearestLink(...)"); double mindist = 1.0e20; MovGraphLink *res = 0; @@ -2761,8 +2764,8 @@ MovGraphLink *MovGraph2::findLink2(int x, int y) { return 0; } -double MovGraph2::findMinPath(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, Common::Array<MovGraphLink *> *listObj) { - debugC(4, kDebugPathfinding, "MovGraph2::findMinPath(...)"); +double MctlGraph::iterate(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, Common::Array<MovGraphLink *> *listObj) { + debugC(4, kDebugPathfinding, "MctlGraph::iterate(...)"); LinkInfo linkInfoWorkSource; @@ -2781,7 +2784,7 @@ double MovGraph2::findMinPath(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, lnk->_flags |= 0x80000000; - double newDistance = findMinPath(&linkInfoWorkSource, linkInfoDest, &tmpList); + double newDistance = iterate(&linkInfoWorkSource, linkInfoDest, &tmpList); if (newDistance >= 0.0 && (minDistance < 0.0 || newDistance + lnk->_length < minDistance)) { listObj->clear(); @@ -2799,7 +2802,7 @@ double MovGraph2::findMinPath(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, Common::Array<MovGraphLink *> tmpList; - double newDistance = findMinPath(&linkInfoWorkSource, linkInfoDest, &tmpList); + double newDistance = iterate(&linkInfoWorkSource, linkInfoDest, &tmpList); if (newDistance >= 0.0) { listObj->clear(); @@ -2815,9 +2818,11 @@ double MovGraph2::findMinPath(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, tmpList.clear(); - newDistance = findMinPath(&linkInfoWorkSource, linkInfoDest, &tmpList); + newDistance = iterate(&linkInfoWorkSource, linkInfoDest, &tmpList); if (newDistance >= 0 && (minDistance < 0.0 || newDistance < minDistance)) { + listObj->clear(); + listObj->push_back(linkInfoSource->link); listObj->push_back(tmpList); @@ -2905,7 +2910,7 @@ void MovGraphLink::recalcLength() { double dy = _graphDst->_y - _graphSrc->_y; _length = sqrt(dy * dy + dx * dx); - _angle = atan2(dx, dy); + _angle = atan2(dy, dx); } } @@ -2913,9 +2918,9 @@ bool MovGraphNode::load(MfcArchive &file) { debugC(5, kDebugLoading, "MovGraphNode::load()"); _field_14 = file.readUint32LE(); - _x = file.readUint32LE(); - _y = file.readUint32LE(); - _z = file.readUint32LE(); + _x = file.readSint32LE(); + _y = file.readSint32LE(); + _z = file.readSint32LE(); return true; } @@ -2932,12 +2937,12 @@ ReactParallel::ReactParallel() { bool ReactParallel::load(MfcArchive &file) { debugC(5, kDebugLoading, "ReactParallel::load()"); - _x1 = file.readUint32LE(); - _y1 = file.readUint32LE(); - _x2 = file.readUint32LE(); - _y2 = file.readUint32LE(); - _dx = file.readUint32LE(); - _dy = file.readUint32LE(); + _x1 = file.readSint32LE(); + _y1 = file.readSint32LE(); + _x2 = file.readSint32LE(); + _y2 = file.readSint32LE(); + _dx = file.readSint32LE(); + _dy = file.readSint32LE(); createRegion(); @@ -2950,7 +2955,7 @@ void ReactParallel::createRegion() { for (int i = 0; i < 4; i++) _points[i] = new Common::Point; - double at = atan2((double)(_x1 - _x2), (double)(_y1 - _y2)) + 1.570796; // pi/2 + double at = atan2((double)(_y1 - _y2), (double)(_x1 - _x2)) + 1.570796; // pi/2 double sn = sin(at); double cs = cos(at); @@ -2990,8 +2995,8 @@ ReactPolygonal::~ReactPolygonal() { bool ReactPolygonal::load(MfcArchive &file) { debugC(5, kDebugLoading, "ReactPolygonal::load()"); - _centerX = file.readUint32LE(); - _centerY = file.readUint32LE(); + _centerX = file.readSint32LE(); + _centerY = file.readSint32LE(); _pointCount = file.readUint32LE(); if (_pointCount > 0) { diff --git a/engines/fullpipe/motion.h b/engines/fullpipe/motion.h index 41860e32d0..93b57c7aaa 100644 --- a/engines/fullpipe/motion.h +++ b/engines/fullpipe/motion.h @@ -23,7 +23,7 @@ #ifndef FULLPIPE_MOTION_H #define FULLPIPE_MOTION_H -#include "fullpipe/mgm.h" +#include "fullpipe/anihandler.h" namespace Fullpipe { @@ -62,7 +62,7 @@ public: virtual int method40() { return 0; } virtual bool canDropInventory(StaticANIObject *ani, int x, int y) { return false; } virtual int method48() { return -1; } - virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { return 0; } + virtual MessageQueue *makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { return 0; } void enableLinks(const char *linkName, bool enable); MovGraphLink *getLinkByName(const char *name); @@ -113,9 +113,9 @@ public: virtual int detachObject(StaticANIObject *obj); virtual void detachAllObjects(); virtual MessageQueue *startMove(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); - virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); + virtual MessageQueue *makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); - void initMovGraph2(); + void initMctlGraph(); MctlConnectionPoint *findClosestConnectionPoint(int ox, int oy, int destIndex, int connectionX, int connectionY, int sourceIndex, double *minDistancePtr); void replaceNodeX(int from, int to); @@ -149,7 +149,7 @@ public: int _ladder_field_20; int _ladder_field_24; Common::Array<MctlLadderMovement *> _ladmovements; - MGM _mgm; + AniHandler _aniHandler; public: MctlLadder(); @@ -160,7 +160,7 @@ public: virtual int detachObject(StaticANIObject *obj) { return 1; } virtual void detachAllObjects(); virtual MessageQueue *startMove(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); - virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); + virtual MessageQueue *makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); MessageQueue *controllerWalkTo(StaticANIObject *ani, int off); @@ -282,7 +282,7 @@ public: int _field_44; Common::Array<MovGraphItem *> _items; MovArr *(*_callback1)(StaticANIObject *ani, Common::Array<MovItem *> *items, signed int counter); - MGM _mgm; + AniHandler _aniHandler; public: MovGraph(); @@ -299,7 +299,7 @@ public: virtual void setSelFunc(MovArr *(*_callback1)(StaticANIObject *ani, Common::Array<MovItem *> *items, signed int counter)); virtual bool resetPosition(StaticANIObject *ani, int flag); virtual bool canDropInventory(StaticANIObject *ani, int x, int y); - virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); + virtual MessageQueue *makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); virtual MessageQueue *method50(StaticANIObject *ani, MovArr *movarr, int staticsId); double putToLink(Common::Point *point, MovGraphLink *link, int fuzzyMatch); @@ -326,7 +326,7 @@ struct MG2I { int _my; }; -struct MovGraph2ItemSub { +struct MctlAniSub { int _staticsId2; int _staticsId1; MG2I _walk[3]; @@ -339,14 +339,14 @@ struct LinkInfo { MovGraphNode *node; }; -struct MovInfo1Sub { +struct MctlMQSub { int subIndex; int x; int y; int distance; }; -struct MovInfo1 { +struct MctlMQ { int index; Common::Point pt1; Common::Point pt2; @@ -354,50 +354,50 @@ struct MovInfo1 { int distance2; int subIndex; int item1Index; - Common::Array<MovInfo1Sub *> items; + Common::Array<MctlMQSub *> items; int itemsCount; int flags; - MovInfo1() { clear(); } - MovInfo1(MovInfo1 *src); + MctlMQ() { clear(); } + MctlMQ(MctlMQ *src); void clear(); }; -struct MovGraph2Item { // 744 +struct MctlAni { // 744 int _objectId; StaticANIObject *_obj; - MovGraph2ItemSub _subItems[4]; // 184 + MctlAniSub _subItems[4]; // 184 }; -class MovGraph2 : public MovGraph { +class MctlGraph : public MovGraph { public: - Common::Array<MovGraph2Item *> _items2; + Common::Array<MctlAni *> _items2; public: virtual void attachObject(StaticANIObject *obj); virtual int detachObject(StaticANIObject *obj); virtual void detachAllObjects(); virtual MessageQueue *startMove(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); - virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); + virtual MessageQueue *makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId); - int getItemIndexByGameObjectId(int objectId); - int getItemSubIndexByStaticsId(int index, int staticsId); - int getItemSubIndexByMovementId(int index, int movId); - int getItemSubIndexByMGM(int idx, StaticANIObject *ani); + int getObjIndex(int objectId); + int getDirByStatics(int index, int staticsId); + int getDirByMovement(int index, int movId); + int getDirByPoint(int idx, StaticANIObject *ani); - int getShortSide(MovGraphLink *lnk, int x, int y); - int findLink(Common::Array<MovGraphLink *> *linkList, int idx, Common::Rect *a3, Common::Point *a4); + int getDirBySize(MovGraphLink *lnk, int x, int y); + int getLinkDir(Common::Array<MovGraphLink *> *linkList, int idx, Common::Rect *a3, Common::Point *a4); - bool initDirections(StaticANIObject *obj, MovGraph2Item *item); - void buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphLink *> *linkList, LinkInfo *lnkSrc, LinkInfo *lnkDst); - MessageQueue *buildMovInfo1MessageQueue(MovInfo1 *movInfo); + bool fillData(StaticANIObject *obj, MctlAni *item); + void generateList(MctlMQ *movinfo, Common::Array<MovGraphLink *> *linkList, LinkInfo *lnkSrc, LinkInfo *lnkDst); + MessageQueue *makeWholeQueue(MctlMQ *mctlMQ); - MovGraphNode *findNode(int x, int y, int fuzzyMatch); - MovGraphLink *findLink1(int x, int y, int idx, int fuzzyMatch); - MovGraphLink *findLink2(int x, int y); - double findMinPath(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, Common::Array<MovGraphLink *> *listObj); + MovGraphNode *getHitNode(int x, int y, int strictMatch); + MovGraphLink *getHitLink(int x, int y, int idx, int fuzzyMatch); + MovGraphLink *getNearestLink(int x, int y); + double iterate(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, Common::Array<MovGraphLink *> *listObj); - MessageQueue *genMovement(MovInfo1 *movinfo); + MessageQueue *makeLineQueue(MctlMQ *movinfo); }; class MctlConnectionPoint : public CObject { diff --git a/engines/fullpipe/objects.h b/engines/fullpipe/objects.h index 1849bcb96e..f9a641d562 100644 --- a/engines/fullpipe/objects.h +++ b/engines/fullpipe/objects.h @@ -61,6 +61,8 @@ struct PicAniInfo { int32 someDynamicPhaseIndex; bool load(MfcArchive &file); + + PicAniInfo() { memset(this, 0, sizeof(PicAniInfo)); } }; union VarValue { diff --git a/engines/fullpipe/scene.cpp b/engines/fullpipe/scene.cpp index b47988d768..70f5f1aa81 100644 --- a/engines/fullpipe/scene.cpp +++ b/engines/fullpipe/scene.cpp @@ -601,7 +601,7 @@ StaticANIObject *Scene::getStaticANIObjectAtPos(int x, int y) { if ((p->_field_8 & 0x100) && (p->_flags & 4) && p->getPixelAtPos(x, y, &pixel) && - (!res || res->_priority >= p->_priority)) + (!res || res->_priority > p->_priority)) res = p; } diff --git a/engines/fullpipe/scenes.cpp b/engines/fullpipe/scenes.cpp index 32aa955a61..cb9f8c4c01 100644 --- a/engines/fullpipe/scenes.cpp +++ b/engines/fullpipe/scenes.cpp @@ -582,7 +582,7 @@ bool FullpipeEngine::sceneSwitcher(EntranceInfo *entrance) { _aniMan2 = _aniMan; MctlCompound *cmp = getSc2MctlCompoundBySceneId(entrance->_sceneId); - cmp->initMovGraph2(); + cmp->initMctlGraph(); cmp->attachObject(_aniMan); cmp->activate(); getGameLoaderInteractionController()->enableFlag24(); diff --git a/engines/fullpipe/scenes.h b/engines/fullpipe/scenes.h index 17ef5c3140..fd90b5f972 100644 --- a/engines/fullpipe/scenes.h +++ b/engines/fullpipe/scenes.h @@ -28,7 +28,7 @@ namespace Fullpipe { struct Bat; struct BehaviorMove; struct Hanger; -class MGM; +class AniHandler; class MctlLadder; struct Ring; class StaticANIObject; @@ -396,7 +396,7 @@ public: StaticANIObject *scene11_boots; StaticANIObject *scene11_dudeOnSwing; PictureObject *scene11_hint; - MGM scene11_mgm; + AniHandler scene11_aniHandler; bool scene11_arcadeIsOn; bool scene11_scrollIsEnabled; bool scene11_scrollIsMaximized; @@ -612,7 +612,7 @@ public: Common::Array<WalkingBearder *> scene29_bearders; int scene29_manX; int scene29_manY; - MGM scene29_mgm; + AniHandler scene29_aniHandler; StaticANIObject *scene30_leg; int scene30_liftFlag; diff --git a/engines/fullpipe/scenes/scene03.cpp b/engines/fullpipe/scenes/scene03.cpp index e6c9fa3bbd..9c2d5e75cc 100644 --- a/engines/fullpipe/scenes/scene03.cpp +++ b/engines/fullpipe/scenes/scene03.cpp @@ -48,6 +48,18 @@ void FullpipeEngine::setSwallowedEggsState() { } void scene03_initScene(Scene *sc) { + debugC(1, kDebugSceneLogic, "scene03_initScene()"); + +#if 0 + Inventory2 *inv = getGameLoaderInventory(); + inv->addItem(ANI_INV_EGGAPL, 1); + inv->addItem(ANI_INV_EGGDOM, 1); + inv->addItem(ANI_INV_EGGCOIN, 1); + inv->addItem(ANI_INV_EGGBOOT, 1); + inv->addItem(ANI_INV_EGGGLS, 1); + inv->rebuildItemRects(); +#endif + g_vars->scene03_eggeater = sc->getStaticANIObject1ById(ANI_EGGEATER, -1); g_vars->scene03_domino = sc->getStaticANIObject1ById(ANI_DOMINO_3, -1); @@ -60,6 +72,9 @@ void scene03_initScene(Scene *sc) { g_fp->lift_setButton(sO_Level2, ST_LBN_2N); g_fp->lift_init(sc, QU_SC3_ENTERLIFT, QU_SC3_EXITLIFT); + + debugC(2, kDebugSceneLogic, "scene03: egg1: %d egg2: %d egg3: %d", g_vars->swallowedEgg1->_value.intValue, + g_vars->swallowedEgg2->_value.intValue, g_vars->swallowedEgg3->_value.intValue); } void scene03_setEaterState() { @@ -82,18 +97,25 @@ int scene03_updateCursor() { } void sceneHandler03_eaterFat() { + debugC(2, kDebugSceneLogic, "scene03: eaterFat"); + g_vars->scene03_eggeater->_flags &= 0xFF7F; g_vars->scene03_eggeater->startAnim(MV_EGTR_FATASK, 0, -1); } void sceneHandler03_swallowEgg(int item) { + debugC(2, kDebugSceneLogic, "scene03: swallowEgg"); + if (!g_vars->swallowedEgg1->_value.intValue) { g_vars->swallowedEgg1->_value.intValue = item; + debugC(2, kDebugSceneLogic, "scene03: setting egg1: %d", g_vars->swallowedEgg1->_value.intValue); } else if (!g_vars->swallowedEgg2->_value.intValue) { g_vars->swallowedEgg2->_value.intValue = item; + debugC(2, kDebugSceneLogic, "scene03: setting egg2: %d", g_vars->swallowedEgg2->_value.intValue); } else if (!g_vars->swallowedEgg3->_value.intValue) { g_vars->swallowedEgg3->_value.intValue = item; + debugC(2, kDebugSceneLogic, "scene03: setting egg3: %d", g_vars->swallowedEgg3->_value.intValue); g_fp->setObjectState(sO_EggGulperGaveCoin, g_fp->getObjectEnumState(sO_EggGulperGaveCoin, sO_Yes)); @@ -102,6 +124,8 @@ void sceneHandler03_swallowEgg(int item) { } void sceneHandler03_giveItem(ExCommand *ex) { + debugC(2, kDebugSceneLogic, "scene03: giveItem"); + if (ex->_parentId == ANI_INV_EGGAPL || ex->_parentId == ANI_INV_EGGDOM || ex->_parentId == ANI_INV_EGGCOIN || ex->_parentId == ANI_INV_EGGBOOT || ex->_parentId == ANI_INV_EGGGLS) @@ -113,6 +137,8 @@ int sceneHandler03_swallowedEgg1State() { } void sceneHandler03_giveCoin(ExCommand *ex) { + debugC(2, kDebugSceneLogic, "scene03: giveCoin"); + MessageQueue *mq = g_fp->_globalMessageQueueList->getMessageQueueById(ex->_parId); if (mq && mq->getCount() > 0) { @@ -141,6 +167,8 @@ void sceneHandler03_goLadder() { } void sceneHandler03_pushEggStack() { + debugC(2, kDebugSceneLogic, "scene03: pushEggStack"); + g_vars->swallowedEgg1->_value.intValue = g_vars->swallowedEgg2->_value.intValue; g_vars->swallowedEgg2->_value.intValue = g_vars->swallowedEgg3->_value.intValue; g_vars->swallowedEgg3->_value.intValue = 0; @@ -153,12 +181,16 @@ void sceneHandler03_pushEggStack() { } void sceneHandler03_releaseEgg() { + debugC(2, kDebugSceneLogic, "scene03: releaseEgg"); + g_vars->scene03_eggeater->_flags &= 0xFF7F; g_vars->scene03_eggeater->show1(-1, -1, -1, 0); } void sceneHandler03_takeEgg(ExCommand *ex) { + debugC(2, kDebugSceneLogic, "scene03: taking egg"); + MessageQueue *mq = g_fp->_globalMessageQueueList->getMessageQueueById(ex->_parId); if (mq && mq->getCount() > 0) { @@ -187,6 +219,9 @@ void sceneHandler03_takeEgg(ExCommand *ex) { } int sceneHandler03(ExCommand *ex) { + if (ex->_messageKind != 17 && ex->_messageNum != 33) + debugC(3, kDebugSceneLogic, "scene03: got message: kind %d, num: %d", ex->_messageKind, ex->_messageNum); + if (ex->_messageKind != 17) { if (ex->_messageKind == 57) sceneHandler03_giveItem(ex); diff --git a/engines/fullpipe/scenes/scene04.cpp b/engines/fullpipe/scenes/scene04.cpp index 6c361d6f1a..b6239c219f 100644 --- a/engines/fullpipe/scenes/scene04.cpp +++ b/engines/fullpipe/scenes/scene04.cpp @@ -60,6 +60,10 @@ void scene04_speakerCallback(int *phase) { } } +void scene04_springCallback(int *phase) { + // do nothing +} + void scene04_initScene(Scene *sc) { g_vars->scene04_dudeOnLadder = false; g_vars->scene04_bottle = sc->getPictureObjectById(PIC_SC4_BOTTLE, 0); @@ -127,7 +131,7 @@ void scene04_initScene(Scene *sc) { StaticANIObject *spring = sc->getStaticANIObject1ById(ANI_SPRING, -1); if (spring) - spring->_callback2 = 0; + spring->_callback2 = scene04_springCallback; g_vars->scene04_bottleObjList.clear(); g_vars->scene04_bottleObjList.push_back(sc->getPictureObjectById(PIC_SC4_BOTTLE, 0)); @@ -395,21 +399,21 @@ void sceneHandler04_jumpOnLadder() { g_fp->_aniMan->_flags |= 1; - MGM mgm; - MGMInfo mgminfo; + AniHandler aniHandler; + MakeQueueStruct mkQueue; - mgm.addItem(ANI_MAN); + aniHandler.attachObject(ANI_MAN); - mgminfo.ani = g_fp->_aniMan; - mgminfo.staticsId2 = ST_MAN_ONPLANK; - mgminfo.x1 = 938; - mgminfo.y1 = 442; - mgminfo.field_1C = 10; - mgminfo.field_10 = 1; - mgminfo.flags = 78; - mgminfo.movementId = MV_MAN_JUMPONPLANK; + mkQueue.ani = g_fp->_aniMan; + mkQueue.staticsId2 = ST_MAN_ONPLANK; + mkQueue.x1 = 938; + mkQueue.y1 = 442; + mkQueue.field_1C = 10; + mkQueue.field_10 = 1; + mkQueue.flags = 78; + mkQueue.movementId = MV_MAN_JUMPONPLANK; - MessageQueue *mq = mgm.genMovement(&mgminfo); + MessageQueue *mq = aniHandler.makeRunQueue(&mkQueue); if (mq) { mq->_flags |= 1; @@ -460,21 +464,21 @@ void sceneHandler04_dropBottle() { } void sceneHandler04_gotoLadder(ExCommand *ex) { - MGM mgm; - MGMInfo mgminfo; + AniHandler aniHandler; + MakeQueueStruct mkQueue; - mgm.addItem(ANI_MAN); + aniHandler.attachObject(ANI_MAN); - mgminfo.ani = g_fp->_aniMan; - mgminfo.staticsId2 = ST_MAN_UP; - mgminfo.x1 = 1095; - mgminfo.y1 = 434; - mgminfo.field_1C = 12; - mgminfo.field_10 = 1; - mgminfo.flags = 78; - mgminfo.movementId = MV_MAN_PLANKTOLADDER; + mkQueue.ani = g_fp->_aniMan; + mkQueue.staticsId2 = ST_MAN_UP; + mkQueue.x1 = 1095; + mkQueue.y1 = 434; + mkQueue.field_1C = 12; + mkQueue.field_10 = 1; + mkQueue.flags = 78; + mkQueue.movementId = MV_MAN_PLANKTOLADDER; - MessageQueue *mq = mgm.genMovement(&mgminfo); + MessageQueue *mq = aniHandler.makeRunQueue(&mkQueue); if (mq) { mq->deleteExCommandByIndex(mq->getCount() - 1, 1); @@ -550,21 +554,21 @@ void sceneHandler04_raisePlank() { } MessageQueue *sceneHandler04_kozFly3(StaticANIObject *ani, double phase) { - MGM mgm; - MGMInfo mgminfo; + AniHandler aniHandler; + MakeQueueStruct mkQueue; - mgm.addItem(ANI_KOZAWKA); + aniHandler.attachObject(ANI_KOZAWKA); - mgminfo.ani = ani; - mgminfo.staticsId2 = ST_KZW_SIT; - mgminfo.x1 = (int)(723.0 - phase * 185.0); - mgminfo.y1 = 486; - mgminfo.field_1C = 10; - mgminfo.field_10 = 1; - mgminfo.flags = 78; - mgminfo.movementId = MV_KZW_JUMP; + mkQueue.ani = ani; + mkQueue.staticsId2 = ST_KZW_SIT; + mkQueue.x1 = (int)(723.0 - phase * 185.0); + mkQueue.y1 = 486; + mkQueue.field_1C = 10; + mkQueue.field_10 = 1; + mkQueue.flags = 78; + mkQueue.movementId = MV_KZW_JUMP; - MessageQueue *mq = mgm.genMovement(&mgminfo); + MessageQueue *mq = aniHandler.makeRunQueue(&mkQueue); if (mq) { ExCommand *ex = new ExCommand(ANI_KOZAWKA, 1, MV_KZW_STANDUP, 0, 0, 0, 1, 0, 0, 0); @@ -599,35 +603,35 @@ MessageQueue *sceneHandler04_kozFly3(StaticANIObject *ani, double phase) { } MessageQueue *sceneHandler04_kozFly5(StaticANIObject *ani, double phase) { - MGM mgm; - MGMInfo mgminfo; - - mgm.addItem(ANI_KOZAWKA); - - mgminfo.ani = ani; - mgminfo.staticsId2 = ST_KZW_JUMPOUT; - mgminfo.x1 = 525; - mgminfo.y1 = (int)(344.0 - (double)(320 - g_vars->scene04_bottle->_oy) * phase); - mgminfo.field_1C = 10; - mgminfo.field_10 = 1; - mgminfo.flags = 78; - mgminfo.movementId = MV_KZW_JUMPHIT; - - MessageQueue *mq1 = mgm.genMovement(&mgminfo); - - memset(&mgminfo, 0, sizeof(mgminfo)); - mgminfo.ani = ani; - mgminfo.staticsId1 = ST_KZW_JUMPOUT; - mgminfo.staticsId2 = ST_KZW_SIT; - mgminfo.x2 = 525; - mgminfo.y2 = (int)(344.0 - (double)(320 - g_vars->scene04_bottle->_oy) * phase); - mgminfo.y1 = 486; - mgminfo.field_1C = 10; - mgminfo.field_10 = 1; - mgminfo.flags = 117; - mgminfo.movementId = MV_KZW_JUMPOUT; - - MessageQueue *mq2 = mgm.genMovement(&mgminfo); + AniHandler aniHandler; + MakeQueueStruct mkQueue; + + aniHandler.attachObject(ANI_KOZAWKA); + + mkQueue.ani = ani; + mkQueue.staticsId2 = ST_KZW_JUMPOUT; + mkQueue.x1 = 525; + mkQueue.y1 = (int)(344.0 - (double)(320 - g_vars->scene04_bottle->_oy) * phase); + mkQueue.field_1C = 10; + mkQueue.field_10 = 1; + mkQueue.flags = 78; + mkQueue.movementId = MV_KZW_JUMPHIT; + + MessageQueue *mq1 = aniHandler.makeRunQueue(&mkQueue); + + memset(&mkQueue, 0, sizeof(mkQueue)); + mkQueue.ani = ani; + mkQueue.staticsId1 = ST_KZW_JUMPOUT; + mkQueue.staticsId2 = ST_KZW_SIT; + mkQueue.x2 = 525; + mkQueue.y2 = (int)(344.0 - (double)(320 - g_vars->scene04_bottle->_oy) * phase); + mkQueue.y1 = 486; + mkQueue.field_1C = 10; + mkQueue.field_10 = 1; + mkQueue.flags = 117; + mkQueue.movementId = MV_KZW_JUMPOUT; + + MessageQueue *mq2 = aniHandler.makeRunQueue(&mkQueue); if (mq1 && mq2) { mq1->addExCommandToEnd(mq2->getExCommandByIndex(0)->createClone()); @@ -670,21 +674,21 @@ MessageQueue *sceneHandler04_kozFly5(StaticANIObject *ani, double phase) { } MessageQueue *sceneHandler04_kozFly6(StaticANIObject *ani) { - MGM mgm; - MGMInfo mgminfo; + AniHandler aniHandler; + MakeQueueStruct mkQueue; - mgm.addItem(ANI_KOZAWKA); + aniHandler.attachObject(ANI_KOZAWKA); - mgminfo.ani = ani; - mgminfo.staticsId2 = ST_KZW_SIT; - mgminfo.x1 = 397 - 4 * g_fp->_rnd->getRandomNumber(1); - mgminfo.field_1C = ani->_priority; - mgminfo.y1 = g_vars->scene04_bottle->_oy - 4 * g_fp->_rnd->getRandomNumber(1) + 109; - mgminfo.field_10 = 1; - mgminfo.flags = 78; - mgminfo.movementId = MV_KZW_JUMPROTATE; + mkQueue.ani = ani; + mkQueue.staticsId2 = ST_KZW_SIT; + mkQueue.x1 = 397 - 4 * g_fp->_rnd->getRandomNumber(1); + mkQueue.field_1C = ani->_priority; + mkQueue.y1 = g_vars->scene04_bottle->_oy - 4 * g_fp->_rnd->getRandomNumber(1) + 109; + mkQueue.field_10 = 1; + mkQueue.flags = 78; + mkQueue.movementId = MV_KZW_JUMPROTATE; - MessageQueue *mq = mgm.genMovement(&mgminfo); + MessageQueue *mq = aniHandler.makeRunQueue(&mkQueue); if (mq) { mq->deleteExCommandByIndex(mq->getCount() - 1, 1); @@ -728,21 +732,21 @@ void sceneHandler04_kozMove(Movement *mov, int from, int to, Common::Point *poin } MessageQueue *sceneHandler04_kozFly7(StaticANIObject *ani, double phase) { - MGM mgm; - MGMInfo mgminfo; + AniHandler aniHandler; + MakeQueueStruct mkQueue; - mgm.addItem(ANI_KOZAWKA); + aniHandler.attachObject(ANI_KOZAWKA); - mgminfo.ani = ani; - mgminfo.staticsId2 = 560; - mgminfo.x1 = (int)(250.0 - phase * 100.0); - mgminfo.y1 = 455; - mgminfo.field_1C = 10; - mgminfo.field_10 = 1; - mgminfo.flags = 78; - mgminfo.movementId = MV_KZW_JUMPROTATE; + mkQueue.ani = ani; + mkQueue.staticsId2 = 560; + mkQueue.x1 = (int)(250.0 - phase * 100.0); + mkQueue.y1 = 455; + mkQueue.field_1C = 10; + mkQueue.field_10 = 1; + mkQueue.flags = 78; + mkQueue.movementId = MV_KZW_JUMPROTATE; - MessageQueue *mq = mgm.genMovement(&mgminfo); + MessageQueue *mq = aniHandler.makeRunQueue(&mkQueue); if (mq) { sceneHandler04_kozMove(ani->getMovementById(MV_KZW_JUMPROTATE), 1, 9, g_vars->scene04_jumpRotateKozyawki, phase * 0.5 + 1.5); @@ -949,7 +953,8 @@ void sceneHandler04_springWobble() { if (g_vars->scene04_bottleWeight < newdelta) g_vars->scene04_springOffset--; - if ((oldDynIndex <= g_vars->scene04_bottleWeight && newdelta > g_vars->scene04_bottleWeight) || newdelta <= g_vars->scene04_bottleWeight) { + if ((oldDynIndex <= g_vars->scene04_bottleWeight && newdelta > g_vars->scene04_bottleWeight) + || (oldDynIndex > g_vars->scene04_bottleWeight && newdelta <= g_vars->scene04_bottleWeight)) { g_vars->scene04_springDelay++; if (g_vars->scene04_springOffset && g_vars->scene04_springDelay > 1) { diff --git a/engines/fullpipe/scenes/scene11.cpp b/engines/fullpipe/scenes/scene11.cpp index 1fa5cabc15..1a8fa8ed67 100644 --- a/engines/fullpipe/scenes/scene11.cpp +++ b/engines/fullpipe/scenes/scene11.cpp @@ -96,7 +96,7 @@ void scene11_setupMusic() { void scene11_initScene(Scene *sc) { g_vars->scene11_swingie = sc->getStaticANIObject1ById(ANI_SWINGER, -1); g_vars->scene11_boots = sc->getStaticANIObject1ById(ANI_BOOTS_11, -1); - g_vars->scene11_mgm.clear(); + g_vars->scene11_aniHandler.detachAllObjects(); g_vars->scene11_dudeOnSwing = sc->getStaticANIObject1ById(ANI_MAN11, -1); g_vars->scene11_dudeOnSwing->_callback2 = scene11_dudeSwingCallback; g_vars->scene11_dudeOnSwing = sc->getStaticANIObject1ById(ANI_KACHELI, -1); @@ -251,7 +251,7 @@ void sceneHandler11_manToSwing() { g_vars->scene11_dudeOnSwing->startAnim(MV_MAN11_SWING_0, 0, -1); g_vars->scene11_dudeOnSwing->_movement->setDynamicPhaseIndex(45); - g_vars->scene11_mgm.addItem(g_fp->_aniMan->_id); + g_vars->scene11_aniHandler.attachObject(g_fp->_aniMan->_id); g_fp->_currentScene->_x = 1400 - g_fp->_sceneRect.right; @@ -385,7 +385,7 @@ void sceneHandler11_emptySwing() { } void sceneHandler11_jumpHitAndWin() { - MGMInfo mgminfo; + MakeQueueStruct mkQueue; sceneHandler11_emptySwing(); @@ -393,16 +393,16 @@ void sceneHandler11_jumpHitAndWin() { MV_MAN11_JUMPHIT, 0); g_fp->_aniMan->_priority = 10; - mgminfo.field_1C = 10; - mgminfo.ani = g_fp->_aniMan; - mgminfo.staticsId2 = ST_MAN_1PIX; - mgminfo.x1 = 1400; - mgminfo.y1 = 0; - mgminfo.field_10 = 1; - mgminfo.flags = 66; - mgminfo.movementId = MV_MAN11_JUMPHIT; + mkQueue.field_1C = 10; + mkQueue.ani = g_fp->_aniMan; + mkQueue.staticsId2 = ST_MAN_1PIX; + mkQueue.x1 = 1400; + mkQueue.y1 = 0; + mkQueue.field_10 = 1; + mkQueue.flags = 66; + mkQueue.movementId = MV_MAN11_JUMPHIT; - MessageQueue *mq = g_vars->scene11_mgm.genMovement(&mgminfo); + MessageQueue *mq = g_vars->scene11_aniHandler.makeRunQueue(&mkQueue); if (mq) { g_vars->scene11_crySound = SND_11_024; @@ -430,7 +430,7 @@ void sceneHandler11_jumpHitAndWin() { } void sceneHandler11_jumpOver(double angle) { - MGMInfo mgminfo; + MakeQueueStruct mkQueue; sceneHandler11_emptySwing(); @@ -438,16 +438,16 @@ void sceneHandler11_jumpOver(double angle) { MV_MAN11_JUMPOVER, 0); g_fp->_aniMan->_priority = 0; - mgminfo.staticsId2 = ST_MAN_1PIX; - mgminfo.ani = g_fp->_aniMan; - mgminfo.x1 = 1163; - mgminfo.y1 = 837 - (int)(angle * 153.0); - mgminfo.field_1C = 0; - mgminfo.field_10 = 1; - mgminfo.flags = 78; - mgminfo.movementId = MV_MAN11_JUMPOVER; + mkQueue.staticsId2 = ST_MAN_1PIX; + mkQueue.ani = g_fp->_aniMan; + mkQueue.x1 = 1163; + mkQueue.y1 = 837 - (int)(angle * 153.0); + mkQueue.field_1C = 0; + mkQueue.field_10 = 1; + mkQueue.flags = 78; + mkQueue.movementId = MV_MAN11_JUMPOVER; - MessageQueue *mq = g_vars->scene11_mgm.genMovement(&mgminfo); + MessageQueue *mq = g_vars->scene11_aniHandler.makeRunQueue(&mkQueue); if (mq) { g_vars->scene11_crySound = SND_11_022; @@ -463,7 +463,7 @@ void sceneHandler11_jumpOver(double angle) { } void sceneHandler11_jumpHit(double angle) { - MGMInfo mgminfo; + MakeQueueStruct mkQueue; sceneHandler11_emptySwing(); @@ -478,16 +478,16 @@ void sceneHandler11_jumpHit(double angle) { MV_MAN11_JUMPOVER, 0); g_fp->_aniMan->_priority = 0; - mgminfo.staticsId2 = ST_MAN_1PIX; - mgminfo.ani = g_fp->_aniMan; - mgminfo.x1 = 1017 - (int)(angle * -214.0); - mgminfo.y1 = 700; - mgminfo.field_1C = 0; - mgminfo.field_10 = 1; - mgminfo.flags = 78; - mgminfo.movementId = MV_MAN11_JUMPHIT; + mkQueue.staticsId2 = ST_MAN_1PIX; + mkQueue.ani = g_fp->_aniMan; + mkQueue.x1 = 1017 - (int)(angle * -214.0); + mkQueue.y1 = 700; + mkQueue.field_1C = 0; + mkQueue.field_10 = 1; + mkQueue.flags = 78; + mkQueue.movementId = MV_MAN11_JUMPHIT; - MessageQueue *mq = g_vars->scene11_mgm.genMovement(&mgminfo); + MessageQueue *mq = g_vars->scene11_aniHandler.makeRunQueue(&mkQueue); if (mq) { g_vars->scene11_crySound = SND_11_022; diff --git a/engines/fullpipe/scenes/scene22.cpp b/engines/fullpipe/scenes/scene22.cpp index f51469da69..84cd5f9a9a 100644 --- a/engines/fullpipe/scenes/scene22.cpp +++ b/engines/fullpipe/scenes/scene22.cpp @@ -239,23 +239,23 @@ void sceneHandler22_stoolLogic(ExCommand *cmd) { goto LABEL_31; } - MGM mgm; - MGMInfo mgminfo; - - mgm.addItem(ANI_MAN); - mgminfo.ani = g_fp->_aniMan; - mgminfo.staticsId2 = ST_MAN_RIGHT; - mgminfo.x1 = 934; - mgminfo.y1 = 391; - mgminfo.field_1C = 10; - mgminfo.staticsId1 = 0x4145; - mgminfo.x2 = 981; - mgminfo.y2 = 390; - mgminfo.field_10 = 1; - mgminfo.flags = 127; - mgminfo.movementId = rMV_MAN_TURN_SRL; - - mq = mgm.genMovement(&mgminfo); + AniHandler mgm; + MakeQueueStruct mkQueue; + + mgm.attachObject(ANI_MAN); + mkQueue.ani = g_fp->_aniMan; + mkQueue.staticsId2 = ST_MAN_RIGHT; + mkQueue.x1 = 934; + mkQueue.y1 = 391; + mkQueue.field_1C = 10; + mkQueue.staticsId1 = 0x4145; + mkQueue.x2 = 981; + mkQueue.y2 = 390; + mkQueue.field_10 = 1; + mkQueue.flags = 127; + mkQueue.movementId = rMV_MAN_TURN_SRL; + + mq = mgm.makeRunQueue(&mkQueue); ExCommand *ex = mq->getExCommandByIndex(0); diff --git a/engines/fullpipe/scenes/scene27.cpp b/engines/fullpipe/scenes/scene27.cpp index 9570d30913..b23f29ad4c 100644 --- a/engines/fullpipe/scenes/scene27.cpp +++ b/engines/fullpipe/scenes/scene27.cpp @@ -331,7 +331,7 @@ void sceneHandler27_aimDude() { void sceneHandler27_wipeDo() { for (uint i = 0; i < g_vars->scene27_bats.size(); i++) { if (g_vars->scene27_bats[i]->currX < 800.0) { - g_vars->scene27_bats[i]->field_10 = atan2(800.0 - g_vars->scene27_bats[i]->currX, 520.0 - g_vars->scene27_bats[i]->currY); + g_vars->scene27_bats[i]->field_10 = atan2(520.0 - g_vars->scene27_bats[i]->currY, 800.0 - g_vars->scene27_bats[i]->currX); g_vars->scene27_bats[i]->power += 1.0; } } @@ -360,7 +360,7 @@ bool sceneHandler27_batFallLogic(uint batn) { } bool sceneHandler27_batCalcDistance(int bat1, int bat2) { - double at = atan2(g_vars->scene27_bats[bat1]->currX - g_vars->scene27_bats[bat2]->currX, g_vars->scene27_bats[bat1]->currY - g_vars->scene27_bats[bat2]->currY); + double at = atan2(g_vars->scene27_bats[bat1]->currY - g_vars->scene27_bats[bat2]->currY, g_vars->scene27_bats[bat1]->currX - g_vars->scene27_bats[bat2]->currX); double dy = g_vars->scene27_bats[bat1]->currY - g_vars->scene27_bats[bat2]->currY; double dx = g_vars->scene27_bats[bat1]->currX - g_vars->scene27_bats[bat2]->currX; double ay = cos(at); @@ -375,7 +375,7 @@ void sceneHandler27_knockBats(int bat1n, int bat2n) { if (0.0 != bat1->power) { double rndF = (double)g_fp->_rnd->getRandomNumber(32767) * 0.0000009155552842799158 - 0.015 - + atan2(bat2->currX - bat1->currX, bat2->currY - bat1->currY); + + atan2(bat2->currY - bat1->currY, bat2->currX - bat1->currX); double rndCos = cos(rndF); double rndSin = sin(rndF); @@ -386,7 +386,7 @@ void sceneHandler27_knockBats(int bat1n, int bat2n) { bat1->powerSin -= pow1y * 1.1; rndF = ((double)g_fp->_rnd->getRandomNumber(32767) * 0.0000009155552842799158 - 0.015 - + atan2(bat1->currX - bat2->currX, bat1->currY - bat2->currY)); + + atan2(bat1->currY - bat2->currY, bat1->currX - bat2->currX)); double pow2x = cos(bat2->field_10 - rndF) * (double)((int)(bat1->currX - bat2->currX) >= 0 ? 1 : -1) * bat2->power; double pow2y = sin(bat2->field_10 - rndF) * (double)((int)(bat1->currY - bat2->currY) >= 0 ? 1 : -1) * bat2->power; @@ -405,7 +405,7 @@ void sceneHandler27_knockBats(int bat1n, int bat2n) { else bat1->powerSin += pow2y * 0.64; - bat1->field_10 = atan2(bat1->powerCos, bat1->powerSin); + bat1->field_10 = atan2(bat1->powerSin, bat1->powerCos); bat1->power = sqrt(bat1->powerCos * bat1->powerCos + bat1->powerSin * bat1->powerSin); bat2->powerCos += pow1x * 0.64; @@ -414,7 +414,7 @@ void sceneHandler27_knockBats(int bat1n, int bat2n) { else bat2->powerSin += pow1y * 0.64; - bat2->field_10 = atan2(bat2->powerCos, bat2->powerSin); + bat2->field_10 = atan2(bat2->powerSin, bat2->powerCos); bat2->power = sqrt(bat2->powerCos * bat2->powerCos + bat2->powerSin * bat2->powerSin); g_fp->playSound(SND_27_026, 0); diff --git a/engines/fullpipe/scenes/scene29.cpp b/engines/fullpipe/scenes/scene29.cpp index a03671a4d0..28d06964a9 100644 --- a/engines/fullpipe/scenes/scene29.cpp +++ b/engines/fullpipe/scenes/scene29.cpp @@ -487,23 +487,23 @@ bool sceneHandler29_checkGreenBallHit(StaticANIObject *ani, int maxx) { } void sceneHandler29_manHit() { - MGMInfo mgminfo; + MakeQueueStruct mkQueue; g_vars->scene29_manIsHit = true; g_fp->_aniMan->changeStatics2(ST_MAN29_RUNR); g_fp->_aniMan->setOXY(g_vars->scene29_manX, g_vars->scene29_manY); - mgminfo.ani = g_fp->_aniMan; - mgminfo.staticsId2 = ST_MAN29_SITR; - mgminfo.y1 = 463; - mgminfo.x1 = g_vars->scene29_manX <= 638 ? 351 : 0; - mgminfo.field_1C = 10; - mgminfo.field_10 = 1; - mgminfo.flags = (g_vars->scene29_manX <= 638 ? 2 : 0) | 0x44; - mgminfo.movementId = MV_MAN29_HIT; + mkQueue.ani = g_fp->_aniMan; + mkQueue.staticsId2 = ST_MAN29_SITR; + mkQueue.y1 = 463; + mkQueue.x1 = g_vars->scene29_manX <= 638 ? 351 : 0; + mkQueue.field_1C = 10; + mkQueue.field_10 = 1; + mkQueue.flags = (g_vars->scene29_manX <= 638 ? 2 : 0) | 0x44; + mkQueue.movementId = MV_MAN29_HIT; - MessageQueue *mq = g_vars->scene29_mgm.genMovement(&mgminfo); + MessageQueue *mq = g_vars->scene29_aniHandler.makeRunQueue(&mkQueue); ExCommand *ex; if (mq) { @@ -728,7 +728,7 @@ void sceneHandler29_manToL() { g_vars->scene29_arcadeIsOn = true; - g_vars->scene29_mgm.addItem(g_fp->_aniMan->_id); + g_vars->scene29_aniHandler.attachObject(g_fp->_aniMan->_id); g_fp->_updateScreenCallback = sceneHandler29_updateScreenCallback; diff --git a/engines/fullpipe/stateloader.cpp b/engines/fullpipe/stateloader.cpp index c95f6c67f3..02053aa94e 100644 --- a/engines/fullpipe/stateloader.cpp +++ b/engines/fullpipe/stateloader.cpp @@ -350,8 +350,8 @@ bool PicAniInfo::load(MfcArchive &file) { field_8 = file.readUint32LE(); sceneId = file.readUint16LE(); field_E = file.readUint16LE(); - ox = file.readUint32LE(); - oy = file.readUint32LE(); + ox = file.readSint32LE(); + oy = file.readSint32LE(); priority = file.readUint32LE(); staticsId = file.readUint16LE(); movementId = file.readUint16LE(); diff --git a/engines/fullpipe/statics.cpp b/engines/fullpipe/statics.cpp index 14245d5eaf..1e43720c32 100644 --- a/engines/fullpipe/statics.cpp +++ b/engines/fullpipe/statics.cpp @@ -69,7 +69,8 @@ Common::Point *StepArray::getCurrPoint(Common::Point *point) { point->x = 0; point->y = 0; } else { - point = _points[_currPointIndex]; + point->x = _points[_currPointIndex]->x; + point->y = _points[_currPointIndex]->y; } return point; } @@ -96,7 +97,7 @@ Common::Point *StepArray::getPoint(Common::Point *point, int index, int offset) } bool StepArray::gotoNextPoint() { - if (_currPointIndex < _maxPointIndex) { + if (_currPointIndex < _maxPointIndex - 1) { _currPointIndex++; return true; } else { @@ -107,20 +108,22 @@ bool StepArray::gotoNextPoint() { void StepArray::insertPoints(Common::Point **points, int pointsCount) { if (_currPointIndex + pointsCount >= _pointsCount) { - _points = (Common::Point **)realloc(_points, sizeof(Common::Point *) * (_currPointIndex + pointsCount)); + _points = (Common::Point **)realloc(_points, sizeof(Common::Point *) * (_pointsCount + pointsCount)); if (!_points) { error("Out of memory at StepArray::insertPoints()"); } + + for(int i = 0; i < pointsCount; i++) + _points[_pointsCount + i] = new Common::Point; + + _pointsCount += pointsCount; } _maxPointIndex = _currPointIndex + pointsCount; - for (int i = 0; i < pointsCount; i++) { - _points[_currPointIndex + i] = new Common::Point; - + for (int i = 0; i < pointsCount; i++) *_points[_currPointIndex + i] = *points[i]; - } } StaticANIObject::StaticANIObject() { @@ -156,7 +159,7 @@ StaticANIObject::~StaticANIObject() { _movements.clear(); - g_fp->_mgm->clear(); + g_fp->_aniHandler->detachAllObjects(); } StaticANIObject::StaticANIObject(StaticANIObject *src) : GameObject(src) { @@ -830,7 +833,7 @@ void StaticANIObject::update(int counterdiff) { } } - if (dyn->_initialCountdown != dyn->_countdown || dyn->_field_68 == 0) { + if (dyn->_initialCountdown == dyn->_countdown && dyn->_field_68 != 0) { newex = new ExCommand(_id, 17, dyn->_field_68, 0, 0, 0, 1, 0, 0, 0); newex->_excFlags = 2; newex->_keyCode = _okeyCode; @@ -856,15 +859,15 @@ void StaticANIObject::update(int counterdiff) { } } } - if (!_movement) - return; - - _stepArray.getCurrPoint(&point); - setOXY(point.x + _ox, point.y + _oy); - _stepArray.gotoNextPoint(); - if (_someDynamicPhaseIndex == _movement->_currDynamicPhaseIndex) - adjustSomeXY(); } + if (!_movement) + return; + + _stepArray.getCurrPoint(&point); + setOXY(point.x + _ox, point.y + _oy); + _stepArray.gotoNextPoint(); + if (_someDynamicPhaseIndex == _movement->_currDynamicPhaseIndex) + adjustSomeXY(); } else if (_flags & 0x20) { _flags ^= 0x20; _flags |= 1; @@ -951,7 +954,7 @@ Common::Point *StaticANIObject::calcNextStep(Common::Point *pRes) { } void StaticANIObject::stopAnim_maybe() { - debugC(6, kDebugAnimation, "StaticANIObject::stopAnim_maybe()"); + debugC(2, kDebugAnimation, "StaticANIObject::stopAnim_maybe()"); if (!(_flags & 1)) return; @@ -966,7 +969,10 @@ void StaticANIObject::stopAnim_maybe() { setOXY(_movement->_ox, _movement->_oy); if (_flags & 0x40) { - if (!_movement->_currMovement && !_movement->_currDynamicPhaseIndex) { + if (!_movement->_currMovement) { + if (_movement->_currDynamicPhaseIndex) + goto L11; +L8: _statics = _movement->_staticsObj1; _movement->getCurrDynamicPhaseXY(point); _ox -= point.x; @@ -984,13 +990,14 @@ void StaticANIObject::stopAnim_maybe() { _ox += point.x; _oy += point.y; } - } else { - _statics = _movement->_staticsObj2; + goto L12; } - } else { - _statics = _movement->_staticsObj2; + if (!_movement->_currDynamicPhaseIndex) + goto L8; } - +L11: + _statics = _movement->_staticsObj2; +L12: _statics->getSomeXY(point); _statics->_x = _ox - point.x; @@ -1040,9 +1047,9 @@ void StaticANIObject::adjustSomeXY() { } MessageQueue *StaticANIObject::changeStatics1(int msgNum) { - g_fp->_mgm->addItem(_id); + g_fp->_aniHandler->attachObject(_id); - MessageQueue *mq = g_fp->_mgm->genMQ(this, msgNum, 0, 0, 0); + MessageQueue *mq = g_fp->_aniHandler->makeQueue(this, msgNum, 0, 0, 0); if (!mq) return 0; @@ -1071,8 +1078,8 @@ void StaticANIObject::changeStatics2(int objId) { deleteFromGlobalMessageQueue(); if (_movement || _statics) { - g_fp->_mgm->addItem(_id); - g_fp->_mgm->updateAnimStatics(this, objId); + g_fp->_aniHandler->attachObject(_id); + g_fp->_aniHandler->putObjectToStatics(this, objId); } else { _statics = getStaticsById(objId); } @@ -1696,8 +1703,8 @@ bool Movement::load(MfcArchive &file, StaticANIObject *ani) { _staticsObj1 = ani->addReverseStatics(s); } - _mx = file.readUint32LE(); - _my = file.readUint32LE(); + _mx = file.readSint32LE(); + _my = file.readSint32LE(); staticsid = file.readUint16LE(); @@ -1708,8 +1715,8 @@ bool Movement::load(MfcArchive &file, StaticANIObject *ani) { _staticsObj2 = ani->addReverseStatics(s); } - _m2x = file.readUint32LE(); - _m2y = file.readUint32LE(); + _m2x = file.readSint32LE(); + _m2y = file.readSint32LE(); if (_staticsObj2) { _dynamicPhases.push_back(_staticsObj2); @@ -2147,11 +2154,17 @@ void Movement::gotoFirstFrame() { void Movement::gotoLastFrame() { if (_currMovement) { - while ((uint)_currDynamicPhaseIndex != _currMovement->_dynamicPhases.size() - 1) - gotoNextFrame(0, 0); + if ((uint)_currDynamicPhaseIndex != _currMovement->_dynamicPhases.size() - 1) { + do { + gotoNextFrame(0, 0); + } while ((uint)_currDynamicPhaseIndex != _currMovement->_dynamicPhases.size() - 1); + } } else { - while ((uint)_currDynamicPhaseIndex != _dynamicPhases.size() - 1) - gotoNextFrame(0, 0); + if ((uint)_currDynamicPhaseIndex != _dynamicPhases.size() - 1) { + do { + gotoNextFrame(0, 0); + } while ((uint)_currDynamicPhaseIndex != _dynamicPhases.size() - 1); + } } } @@ -2260,17 +2273,17 @@ bool DynamicPhase::load(MfcArchive &file) { _field_7C = file.readUint16LE(); _rect = new Common::Rect(); - _rect->left = file.readUint32LE(); - _rect->top = file.readUint32LE(); - _rect->right = file.readUint32LE(); - _rect->bottom = file.readUint32LE(); + _rect->left = file.readSint32LE(); + _rect->top = file.readSint32LE(); + _rect->right = file.readSint32LE(); + _rect->bottom = file.readSint32LE(); - assert (g_fp->_gameProjectVersion >= 1); + assert(g_fp->_gameProjectVersion >= 1); - _someX = file.readUint32LE(); - _someY = file.readUint32LE(); + _someX = file.readSint32LE(); + _someY = file.readSint32LE(); - assert (g_fp->_gameProjectVersion >= 12); + assert(g_fp->_gameProjectVersion >= 12); _dynFlags = file.readUint32LE(); diff --git a/engines/fullpipe/utils.cpp b/engines/fullpipe/utils.cpp index a8e00468b5..148f779d1e 100644 --- a/engines/fullpipe/utils.cpp +++ b/engines/fullpipe/utils.cpp @@ -84,7 +84,7 @@ bool DWordArray::load(MfcArchive &file) { resize(count); for (int i = 0; i < count; i++) { - int32 t = file.readUint32LE(); + int32 t = file.readSint32LE(); push_back(t); } diff --git a/engines/gnap/detection.cpp b/engines/gnap/detection.cpp index 7e4ab56d1f..d92a037232 100644 --- a/engines/gnap/detection.cpp +++ b/engines/gnap/detection.cpp @@ -89,7 +89,8 @@ bool GnapMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsDeleteSave) || (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || - (f == kSavesSupportCreationDate); + (f == kSavesSupportCreationDate) || + (f == kSimpleSavesNames); } bool Gnap::GnapEngine::hasFeature(EngineFeature f) const { diff --git a/engines/hopkins/detection.cpp b/engines/hopkins/detection.cpp index cfdbf8030c..041afecaa8 100644 --- a/engines/hopkins/detection.cpp +++ b/engines/hopkins/detection.cpp @@ -128,7 +128,8 @@ bool HopkinsMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsLoadingDuringStartup) || (f == kSupportsDeleteSave) || (f == kSavesSupportMetaInfo) || - (f == kSavesSupportThumbnail); + (f == kSavesSupportThumbnail) || + (f == kSimpleSavesNames); } bool Hopkins::HopkinsEngine::hasFeature(EngineFeature f) const { diff --git a/engines/kyra/animator_tim.cpp b/engines/kyra/animator_tim.cpp index 1d65ba7b1a..b1cfc6a6a8 100644 --- a/engines/kyra/animator_tim.cpp +++ b/engines/kyra/animator_tim.cpp @@ -202,6 +202,9 @@ void TimAnimator::playPart(int animIndex, int firstFrame, int lastFrame, int del return; Animation *anim = &_animations[animIndex]; + // WORKAROUND for some bugged scripts that will try to play invalid animations + if (!anim->wsa) + return; int step = (lastFrame >= firstFrame) ? 1 : -1; for (int i = firstFrame; i != (lastFrame + step); i += step) { diff --git a/engines/kyra/detection.cpp b/engines/kyra/detection.cpp index 989a45b420..70c7e7c93c 100644 --- a/engines/kyra/detection.cpp +++ b/engines/kyra/detection.cpp @@ -173,7 +173,8 @@ bool KyraMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsLoadingDuringStartup) || (f == kSupportsDeleteSave) || (f == kSavesSupportMetaInfo) || - (f == kSavesSupportThumbnail); + (f == kSavesSupportThumbnail) || + (f == kSimpleSavesNames); } bool Kyra::KyraEngine_v1::hasFeature(EngineFeature f) const { diff --git a/engines/kyra/kyra_v1.h b/engines/kyra/kyra_v1.h index c801829855..4de7494510 100644 --- a/engines/kyra/kyra_v1.h +++ b/engines/kyra/kyra_v1.h @@ -38,6 +38,7 @@ #include "kyra/item.h" namespace Common { +class OutSaveFile; class SeekableReadStream; class WriteStream; } // End of namespace Common @@ -423,7 +424,7 @@ protected: virtual Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail) = 0; Common::SeekableReadStream *openSaveForReading(const char *filename, SaveHeader &header, bool checkID = true); - Common::WriteStream *openSaveForWriting(const char *filename, const char *saveName, const Graphics::Surface *thumbnail) const; + Common::OutSaveFile *openSaveForWriting(const char *filename, const char *saveName, const Graphics::Surface *thumbnail) const; // TODO: Consider moving this to Screen virtual Graphics::Surface *generateSaveThumbnail() const { return 0; } diff --git a/engines/kyra/saveload.cpp b/engines/kyra/saveload.cpp index 2ae6420bd7..81ea796fe9 100644 --- a/engines/kyra/saveload.cpp +++ b/engines/kyra/saveload.cpp @@ -184,7 +184,7 @@ Common::SeekableReadStream *KyraEngine_v1::openSaveForReading(const char *filena return in; } -Common::WriteStream *KyraEngine_v1::openSaveForWriting(const char *filename, const char *saveName, const Graphics::Surface *thumbnail) const { +Common::OutSaveFile *KyraEngine_v1::openSaveForWriting(const char *filename, const char *saveName, const Graphics::Surface *thumbnail) const { if (shouldQuit()) return 0; @@ -226,7 +226,7 @@ Common::WriteStream *KyraEngine_v1::openSaveForWriting(const char *filename, con delete genThumbnail; } - return out; + return new Common::OutSaveFile(out); } const char *KyraEngine_v1::getSavegameFilename(int num) { diff --git a/engines/kyra/saveload_eob.cpp b/engines/kyra/saveload_eob.cpp index cca8f3a0a4..9329d14255 100644 --- a/engines/kyra/saveload_eob.cpp +++ b/engines/kyra/saveload_eob.cpp @@ -995,7 +995,7 @@ bool EoBCoreEngine::saveAsOriginalSaveFile(int slot) { return false; Common::FSNode nf = nd.getChild(_flags.gameID == GI_EOB1 ? "EOBDATA.SAV" : Common::String::format("EOBDATA%d.SAV", slot)); - Common::WriteStream *out = nf.createWriteStream(); + Common::OutSaveFile *out = new Common::OutSaveFile(nf.createWriteStream()); if (_flags.gameID == GI_EOB2) { static const char tempStr[20] = "SCUMMVM EXPORT "; diff --git a/engines/lab/detection.cpp b/engines/lab/detection.cpp index 30890b5acf..bf6d4563b5 100644 --- a/engines/lab/detection.cpp +++ b/engines/lab/detection.cpp @@ -151,7 +151,8 @@ bool LabMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || (f == kSavesSupportCreationDate) || - (f == kSavesSupportPlayTime); + (f == kSavesSupportPlayTime) || + (f == kSimpleSavesNames); } bool Lab::LabEngine::hasFeature(EngineFeature f) const { diff --git a/engines/mads/detection.cpp b/engines/mads/detection.cpp index 4736503a38..4fb8b82eb3 100644 --- a/engines/mads/detection.cpp +++ b/engines/mads/detection.cpp @@ -166,7 +166,8 @@ bool MADSMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsLoadingDuringStartup) || (f == kSupportsDeleteSave) || (f == kSavesSupportMetaInfo) || - (f == kSavesSupportThumbnail); + (f == kSavesSupportThumbnail) || + (f == kSimpleSavesNames); } bool MADS::MADSEngine::hasFeature(EngineFeature f) const { diff --git a/engines/metaengine.h b/engines/metaengine.h index e7bfebab71..568b66ac51 100644 --- a/engines/metaengine.h +++ b/engines/metaengine.h @@ -236,7 +236,19 @@ public: * the game till the save. * This flag may only be set when 'kSavesSupportMetaInfo' is set. */ - kSavesSupportPlayTime + kSavesSupportPlayTime, + + /** + * Feature is available if engine's saves could be detected + * with "<target>.###" pattern and "###" corresponds to slot + * number. + * + * If that's not true or engine is using some unusual way + * of detecting saves and slot numbers, this should be + * unavailable. In that case Save/Load dialog for engine's + * games is locked during cloud saves sync. + */ + kSimpleSavesNames }; /** diff --git a/engines/mohawk/console.cpp b/engines/mohawk/console.cpp index fd79e53b07..f08eee9677 100644 --- a/engines/mohawk/console.cpp +++ b/engines/mohawk/console.cpp @@ -42,6 +42,7 @@ #ifdef ENABLE_RIVEN #include "mohawk/riven.h" #include "mohawk/riven_external.h" +#include "mohawk/riven_sound.h" #endif namespace Mohawk { diff --git a/engines/mohawk/cstime.cpp b/engines/mohawk/cstime.cpp index 3b26378819..b2889be714 100644 --- a/engines/mohawk/cstime.cpp +++ b/engines/mohawk/cstime.cpp @@ -54,6 +54,7 @@ MohawkEngine_CSTime::MohawkEngine_CSTime(OSystem *syst, const MohawkGameDescript _console = 0; _gfx = 0; + _sound = 0; _cursor = 0; _interface = 0; _view = 0; @@ -66,6 +67,7 @@ MohawkEngine_CSTime::~MohawkEngine_CSTime() { delete _interface; delete _view; delete _console; + delete _sound; delete _gfx; delete _rnd; } @@ -75,6 +77,7 @@ Common::Error MohawkEngine_CSTime::run() { _console = new CSTimeConsole(this); _gfx = new CSTimeGraphics(this); + _sound = new Sound(this); _cursor = new DefaultCursorManager(this, ID_CURS); _interface = new CSTimeInterface(this); diff --git a/engines/mohawk/cstime.h b/engines/mohawk/cstime.h index bfb7daf945..393032aaa9 100644 --- a/engines/mohawk/cstime.h +++ b/engines/mohawk/cstime.h @@ -136,6 +136,7 @@ public: Common::RandomSource *_rnd; + Sound *_sound; CSTimeGraphics *_gfx; bool _needsUpdate; diff --git a/engines/mohawk/livingbooks.cpp b/engines/mohawk/livingbooks.cpp index 5af8fac901..579e3792b3 100644 --- a/engines/mohawk/livingbooks.cpp +++ b/engines/mohawk/livingbooks.cpp @@ -144,6 +144,7 @@ MohawkEngine_LivingBooks::MohawkEngine_LivingBooks(OSystem *syst, const MohawkGa _rnd = new Common::RandomSource("livingbooks"); + _sound = NULL; _page = NULL; const Common::FSNode gameDataDir(ConfMan.get("path")); @@ -158,6 +159,7 @@ MohawkEngine_LivingBooks::~MohawkEngine_LivingBooks() { destroyPage(); delete _console; + delete _sound; delete _gfx; delete _rnd; _bookInfoFile.clear(); @@ -182,6 +184,7 @@ Common::Error MohawkEngine_LivingBooks::run() { error("Could not find xRes/yRes variables"); _gfx = new LBGraphics(this, _screenWidth, _screenHeight); + _sound = new Sound(this); if (getGameType() != GType_LIVINGBOOKSV1) _cursor = new LivingBooksCursorManager_v2(); diff --git a/engines/mohawk/livingbooks.h b/engines/mohawk/livingbooks.h index 1a265a1a02..cf67c1ee8e 100644 --- a/engines/mohawk/livingbooks.h +++ b/engines/mohawk/livingbooks.h @@ -714,6 +714,7 @@ public: Common::RandomSource *_rnd; + Sound *_sound; LBGraphics *_gfx; bool _needsRedraw, _needsUpdate; diff --git a/engines/mohawk/module.mk b/engines/mohawk/module.mk index 83e541e3e4..3fc118d2b6 100644 --- a/engines/mohawk/module.mk +++ b/engines/mohawk/module.mk @@ -57,6 +57,7 @@ MODULE_OBJS += \ riven_graphics.o \ riven_saveload.o \ riven_scripts.o \ + riven_sound.o \ riven_vars.o endif diff --git a/engines/mohawk/mohawk.cpp b/engines/mohawk/mohawk.cpp index d740d9479a..b38409f9f1 100644 --- a/engines/mohawk/mohawk.cpp +++ b/engines/mohawk/mohawk.cpp @@ -40,14 +40,12 @@ MohawkEngine::MohawkEngine(OSystem *syst, const MohawkGameDescription *gamedesc) // Setup mixer syncSoundSettings(); - _sound = 0; _video = 0; _pauseDialog = 0; _cursor = 0; } MohawkEngine::~MohawkEngine() { - delete _sound; delete _video; delete _pauseDialog; delete _cursor; @@ -58,7 +56,6 @@ MohawkEngine::~MohawkEngine() { } Common::Error MohawkEngine::run() { - _sound = new Sound(this); _video = new VideoManager(this); _pauseDialog = new PauseDialog(this, "The game is paused. Press any key to continue."); @@ -66,14 +63,12 @@ Common::Error MohawkEngine::run() { } void MohawkEngine::pauseEngineIntern(bool pause) { + Engine::pauseEngineIntern(pause); + if (pause) { _video->pauseVideos(); - _sound->pauseSound(); - _sound->pauseSLST(); } else { _video->resumeVideos(); - _sound->resumeSound(); - _sound->resumeSLST(); _system->updateScreen(); } } diff --git a/engines/mohawk/mohawk.h b/engines/mohawk/mohawk.h index ac91dca971..bc0d642bce 100644 --- a/engines/mohawk/mohawk.h +++ b/engines/mohawk/mohawk.h @@ -100,7 +100,6 @@ public: bool hasFeature(EngineFeature f) const; - Sound *_sound; VideoManager *_video; CursorManager *_cursor; diff --git a/engines/mohawk/myst.cpp b/engines/mohawk/myst.cpp index a1c6d0e748..3c00c1e11b 100644 --- a/engines/mohawk/myst.cpp +++ b/engines/mohawk/myst.cpp @@ -75,6 +75,7 @@ MohawkEngine_Myst::MohawkEngine_Myst(OSystem *syst, const MohawkGameDescription _curResource = -1; _hoverResource = nullptr; + _sound = nullptr; _gfx = nullptr; _console = nullptr; _scriptParser = nullptr; @@ -88,6 +89,7 @@ MohawkEngine_Myst::~MohawkEngine_Myst() { DebugMan.clearAllDebugChannels(); delete _gfx; + delete _sound; delete _console; delete _scriptParser; delete _gameState; @@ -220,6 +222,7 @@ Common::Error MohawkEngine_Myst::run() { MohawkEngine::run(); _gfx = new MystGraphics(this); + _sound = new Sound(this); _console = new MystConsole(this); _gameState = new MystGameState(this, _saveFileMan); _optionsDialog = new MystOptionsDialog(this); diff --git a/engines/mohawk/myst.h b/engines/mohawk/myst.h index 0b249e5499..0491e853b6 100644 --- a/engines/mohawk/myst.h +++ b/engines/mohawk/myst.h @@ -200,6 +200,7 @@ public: bool _showResourceRects; + Sound *_sound; MystGraphics *_gfx; MystGameState *_gameState; MystScriptParser *_scriptParser; diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp index b05b76da30..b7c83c0ff8 100644 --- a/engines/mohawk/riven.cpp +++ b/engines/mohawk/riven.cpp @@ -34,8 +34,8 @@ #include "mohawk/riven_external.h" #include "mohawk/riven_graphics.h" #include "mohawk/riven_saveload.h" +#include "mohawk/riven_sound.h" #include "mohawk/dialogs.h" -#include "mohawk/sound.h" #include "mohawk/video.h" #include "mohawk/console.h" @@ -59,6 +59,7 @@ MohawkEngine_Riven::MohawkEngine_Riven(OSystem *syst, const MohawkGameDescriptio _curStack = kStackUnknown; _hotspots = nullptr; _gfx = nullptr; + _sound = nullptr; _externalScriptHandler = nullptr; _rnd = nullptr; _scriptMan = nullptr; @@ -92,6 +93,7 @@ MohawkEngine_Riven::MohawkEngine_Riven(OSystem *syst, const MohawkGameDescriptio } MohawkEngine_Riven::~MohawkEngine_Riven() { + delete _sound; delete _gfx; delete _console; delete _externalScriptHandler; @@ -123,6 +125,7 @@ Common::Error MohawkEngine_Riven::run() { SearchMan.add("arcriven.z", &_installerArchive, 0, false); _gfx = new RivenGraphics(this); + _sound = new RivenSoundManager(this); _console = new RivenConsole(this); _saveLoad = new RivenSaveLoad(this, _saveFileMan); _externalScriptHandler = new RivenExternal(this); @@ -199,6 +202,7 @@ Common::Error MohawkEngine_Riven::run() { void MohawkEngine_Riven::handleEvents() { // Update background running things checkTimer(); + _sound->updateSLST(); bool needsUpdate = _gfx->runScheduledWaterEffects(); needsUpdate |= _video->updateMovies(); @@ -710,6 +714,7 @@ void MohawkEngine_Riven::delayAndUpdate(uint32 ms) { uint32 startTime = _system->getMillis(); while (_system->getMillis() < startTime + ms && !shouldQuit()) { + _sound->updateSLST(); bool needsUpdate = _gfx->runScheduledWaterEffects(); needsUpdate |= _video->updateMovies(); diff --git a/engines/mohawk/riven.h b/engines/mohawk/riven.h index 3ea50bb38d..ce819ac970 100644 --- a/engines/mohawk/riven.h +++ b/engines/mohawk/riven.h @@ -41,6 +41,7 @@ class RivenExternal; class RivenConsole; class RivenSaveLoad; class RivenOptionsDialog; +class RivenSoundManager; // Riven Stack Types enum { @@ -121,6 +122,7 @@ public: MohawkEngine_Riven(OSystem *syst, const MohawkGameDescription *gamedesc); virtual ~MohawkEngine_Riven(); + RivenSoundManager *_sound; RivenGraphics *_gfx; RivenExternal *_externalScriptHandler; Common::RandomSource *_rnd; diff --git a/engines/mohawk/riven_external.cpp b/engines/mohawk/riven_external.cpp index 00075039fe..125630445e 100644 --- a/engines/mohawk/riven_external.cpp +++ b/engines/mohawk/riven_external.cpp @@ -24,7 +24,7 @@ #include "mohawk/riven.h" #include "mohawk/riven_external.h" #include "mohawk/riven_graphics.h" -#include "mohawk/sound.h" +#include "mohawk/riven_sound.h" #include "mohawk/video.h" #include "gui/message.h" @@ -2429,7 +2429,7 @@ void RivenExternal::xtexterior300_telescopedown(uint16 argc, uint16 *argv) { // Play the sound of not being able to move _vm->_cursor->setCursor(kRivenHideCursor); _vm->_system->updateScreen(); - _vm->_sound->playSoundBlocking(13); + _vm->_sound->playSound(13); } } else { // We're not at the bottom, and we can move down again @@ -2463,7 +2463,7 @@ void RivenExternal::xtexterior300_telescopeup(uint16 argc, uint16 *argv) { // Play the sound of not being able to move _vm->_cursor->setCursor(kRivenHideCursor); _vm->_system->updateScreen(); - _vm->_sound->playSoundBlocking(13); + _vm->_sound->playSound(13); return; } diff --git a/engines/mohawk/riven_graphics.cpp b/engines/mohawk/riven_graphics.cpp index db22dde22d..b583bc9710 100644 --- a/engines/mohawk/riven_graphics.cpp +++ b/engines/mohawk/riven_graphics.cpp @@ -23,6 +23,7 @@ #include "mohawk/resource.h" #include "mohawk/riven.h" #include "mohawk/riven_graphics.h" +#include "mohawk/riven_sound.h" #include "common/system.h" #include "engines/util.h" @@ -111,6 +112,7 @@ void RivenGraphics::drawPLST(uint16 x) { void RivenGraphics::updateScreen(Common::Rect updateRect) { if (_updatesEnabled) { _vm->runUpdateScreenScript(); + _vm->_sound->triggerDrawSound(); if (_dirtyScreen) { _activatedPLSTs.clear(); diff --git a/engines/mohawk/riven_scripts.cpp b/engines/mohawk/riven_scripts.cpp index caa235ec8b..3655452603 100644 --- a/engines/mohawk/riven_scripts.cpp +++ b/engines/mohawk/riven_scripts.cpp @@ -25,7 +25,7 @@ #include "mohawk/riven_external.h" #include "mohawk/riven_graphics.h" #include "mohawk/riven_scripts.h" -#include "mohawk/sound.h" +#include "mohawk/riven_sound.h" #include "mohawk/video.h" #include "common/memstream.h" @@ -309,54 +309,44 @@ void RivenScript::switchCard(uint16 op, uint16 argc, uint16 *argv) { // Command 3: play an SLST from the script void RivenScript::playScriptSLST(uint16 op, uint16 argc, uint16 *argv) { - SLSTRecord slstRecord; int offset = 0, j = 0; + uint16 soundCount = argv[offset++]; + SLSTRecord slstRecord; slstRecord.index = 0; // not set by the scripts, so we set it to 0 - slstRecord.sound_count = argv[0]; - slstRecord.sound_ids = new uint16[slstRecord.sound_count]; - - offset = slstRecord.sound_count; + slstRecord.soundIds.resize(soundCount); - for (j = 0; j < slstRecord.sound_count; j++) - slstRecord.sound_ids[j] = argv[offset++]; - slstRecord.fade_flags = argv[offset++]; + for (j = 0; j < soundCount; j++) + slstRecord.soundIds[j] = argv[offset++]; + slstRecord.fadeFlags = argv[offset++]; slstRecord.loop = argv[offset++]; - slstRecord.global_volume = argv[offset++]; + slstRecord.globalVolume = argv[offset++]; slstRecord.u0 = argv[offset++]; - slstRecord.u1 = argv[offset++]; + slstRecord.suspend = argv[offset++]; - slstRecord.volumes = new uint16[slstRecord.sound_count]; - slstRecord.balances = new int16[slstRecord.sound_count]; - slstRecord.u2 = new uint16[slstRecord.sound_count]; + slstRecord.volumes.resize(soundCount); + slstRecord.balances.resize(soundCount); + slstRecord.u2.resize(soundCount); - for (j = 0; j < slstRecord.sound_count; j++) + for (j = 0; j < soundCount; j++) slstRecord.volumes[j] = argv[offset++]; - for (j = 0; j < slstRecord.sound_count; j++) + for (j = 0; j < soundCount; j++) slstRecord.balances[j] = argv[offset++]; // negative = left, 0 = center, positive = right - for (j = 0; j < slstRecord.sound_count; j++) + for (j = 0; j < soundCount; j++) slstRecord.u2[j] = argv[offset++]; // Unknown // Play the requested sound list _vm->_sound->playSLST(slstRecord); - _vm->_activatedSLST = true; - - delete[] slstRecord.sound_ids; - delete[] slstRecord.volumes; - delete[] slstRecord.balances; - delete[] slstRecord.u2; } // Command 4: play local tWAV resource (twav_id, volume, block) void RivenScript::playSound(uint16 op, uint16 argc, uint16 *argv) { - byte volume = Sound::convertRivenVolume(argv[1]); + uint16 volume = argv[1]; + bool playOnDraw = argv[2] == 1; - if (argv[2] == 1) - _vm->_sound->playSoundBlocking(argv[0], volume); - else - _vm->_sound->playSound(argv[0], volume); + _vm->_sound->playSound(argv[0], volume, playOnDraw); } // Command 7: set variable value (variable, value) diff --git a/engines/mohawk/riven_sound.cpp b/engines/mohawk/riven_sound.cpp new file mode 100644 index 0000000000..10a23a0719 --- /dev/null +++ b/engines/mohawk/riven_sound.cpp @@ -0,0 +1,459 @@ +/* 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/debug.h" +#include "common/system.h" + +#include "audio/audiostream.h" + +#include "mohawk/riven_sound.h" +#include "mohawk/sound.h" + +namespace Mohawk { + +RivenSoundManager::RivenSoundManager(MohawkEngine *vm) : + _vm(vm), + _effect(nullptr), + _mainAmbientSoundId(-1), + _effectPlayOnDraw(false), + _nextFadeUpdate(0) { + +} + +RivenSoundManager::~RivenSoundManager() { + stopSound(); + stopAllSLST(false); +} + +Audio::RewindableAudioStream *RivenSoundManager::makeAudioStream(uint16 id) { + return makeMohawkWaveStream(_vm->getResource(ID_TWAV, id)); +} + +void RivenSoundManager::playSound(uint16 id, uint16 volume, bool playOnDraw) { + debug (0, "Playing sound %d", id); + + stopSound(); + + Audio::RewindableAudioStream *rewindStream = makeAudioStream(id); + if (!rewindStream) { + warning("Unable to play sound with id %d", id); + return; + } + + _effect = new RivenSound(_vm, rewindStream); + _effect->setVolume(volume); + + _effectPlayOnDraw = playOnDraw; + if (!playOnDraw) { + _effect->play(); + } +} + +void RivenSoundManager::playSLST(uint16 index, uint16 card) { + Common::SeekableReadStream *slstStream = _vm->getResource(ID_SLST, card); + + uint16 recordCount = slstStream->readUint16BE(); + + for (uint16 i = 0; i < recordCount; i++) { + SLSTRecord slstRecord; + slstRecord.index = slstStream->readUint16BE(); + + uint16 soundCount = slstStream->readUint16BE(); + slstRecord.soundIds.resize(soundCount); + + for (uint16 j = 0; j < soundCount; j++) + slstRecord.soundIds[j] = slstStream->readUint16BE(); + + slstRecord.fadeFlags = slstStream->readUint16BE(); + slstRecord.loop = slstStream->readUint16BE(); + slstRecord.globalVolume = slstStream->readUint16BE(); + slstRecord.u0 = slstStream->readUint16BE(); // Unknown + + if (slstRecord.u0 > 1) + warning("slstRecord.u0: %d non-boolean", slstRecord.u0); + + slstRecord.suspend = slstStream->readUint16BE(); + + if (slstRecord.suspend != 0) + warning("slstRecord.u1: %d non-zero", slstRecord.suspend); + + slstRecord.volumes.resize(soundCount); + slstRecord.balances.resize(soundCount); + slstRecord.u2.resize(soundCount); + + for (uint16 j = 0; j < soundCount; j++) + slstRecord.volumes[j] = slstStream->readUint16BE(); + + for (uint16 j = 0; j < soundCount; j++) + slstRecord.balances[j] = slstStream->readSint16BE(); // negative = left, 0 = center, positive = right + + for (uint16 j = 0; j < soundCount; j++) { + slstRecord.u2[j] = slstStream->readUint16BE(); // Unknown + + if (slstRecord.u2[j] != 255 && slstRecord.u2[j] != 256) + warning("slstRecord.u2[%d]: %d not 255 or 256", j, slstRecord.u2[j]); + } + + if (slstRecord.index == index) { + playSLST(slstRecord); + delete slstStream; + return; + } + } + + delete slstStream; + + // If we have no matching entries, we do nothing and just let + // the previous ambient sounds continue. +} + +void RivenSoundManager::playSLST(const SLSTRecord &slstRecord) { + if (slstRecord.soundIds.empty()) { + return; + } + + if (slstRecord.soundIds[0] == _mainAmbientSoundId) { + if (slstRecord.soundIds.size() > _ambientSounds.sounds.size()) { + addAmbientSounds(slstRecord); + } + setAmbientLooping(slstRecord.loop); + setTargetVolumes(slstRecord); + _ambientSounds.suspend = slstRecord.suspend; + if (slstRecord.suspend) { + freePreviousAmbientSounds(); + pauseAmbientSounds(); + applyTargetVolumes(); + } else { + playAmbientSounds(); + } + } else { + _mainAmbientSoundId = slstRecord.soundIds[0]; + freePreviousAmbientSounds(); + moveAmbientSoundsToPreviousSounds(); + addAmbientSounds(slstRecord); + setAmbientLooping(slstRecord.loop); + setTargetVolumes(slstRecord); + _ambientSounds.suspend = slstRecord.suspend; + if (slstRecord.suspend) { + freePreviousAmbientSounds(); + applyTargetVolumes(); + } else { + startFadingAmbientSounds(slstRecord.fadeFlags); + } + } +} + +void RivenSoundManager::stopAllSLST(bool fade) { + _mainAmbientSoundId = -1; + freePreviousAmbientSounds(); + moveAmbientSoundsToPreviousSounds(); + startFadingAmbientSounds(fade ? kFadeOutPreviousSounds : 0); +} + +void RivenSoundManager::stopSound() { + if (_effect) { + delete _effect; + } + _effect = nullptr; + _effectPlayOnDraw = false; +} + +void RivenSoundManager::addAmbientSounds(const SLSTRecord &record) { + if (record.soundIds.size() > _ambientSounds.sounds.size()) { + uint oldSize = _ambientSounds.sounds.size(); + + // Resize the list to the new size + _ambientSounds.sounds.resize(record.soundIds.size()); + + // Add new elements to the list + for (uint i = oldSize; i < _ambientSounds.sounds.size(); i++) { + Audio::RewindableAudioStream *stream = makeAudioStream(record.soundIds[i]); + + RivenSound *sound = new RivenSound(_vm, stream); + sound->setVolume(record.volumes[i]); + sound->setBalance(record.balances[i]); + + _ambientSounds.sounds[i].sound = sound; + _ambientSounds.sounds[i].targetVolume = record.volumes[i]; + _ambientSounds.sounds[i].targetBalance = record.balances[i]; + } + } +} + +void RivenSoundManager::setTargetVolumes(const SLSTRecord &record) { + for (uint i = 0; i < record.volumes.size(); i++) { + _ambientSounds.sounds[i].targetVolume = record.volumes[i] * record.globalVolume / 256; + _ambientSounds.sounds[i].targetBalance = record.balances[i]; + } + _ambientSounds.fading = true; +} + +void RivenSoundManager::freePreviousAmbientSounds() { + for (uint i = 0; i < _previousAmbientSounds.sounds.size(); i++) { + delete _previousAmbientSounds.sounds[i].sound; + } + _previousAmbientSounds = AmbientSoundList(); +} + +void RivenSoundManager::moveAmbientSoundsToPreviousSounds() { + _previousAmbientSounds = _ambientSounds; + _ambientSounds = AmbientSoundList(); +} + +void RivenSoundManager::applyTargetVolumes() { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + AmbientSound &ambientSound = _ambientSounds.sounds[i]; + RivenSound *sound = ambientSound.sound; + sound->setVolume(ambientSound.targetVolume); + sound->setBalance(ambientSound.targetBalance); + } + _ambientSounds.fading = false; +} + +void RivenSoundManager::startFadingAmbientSounds(uint16 flags) { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + AmbientSound &ambientSound = _ambientSounds.sounds[i]; + uint16 volume; + if (flags & kFadeInNewSounds) { + volume = 0; + } else { + volume = ambientSound.targetVolume; + } + ambientSound.sound->setVolume(volume); + } + _ambientSounds.fading = true; + playAmbientSounds(); + + if (!_previousAmbientSounds.sounds.empty()) { + if (flags) { + _previousAmbientSounds.fading = true; + } else { + freePreviousAmbientSounds(); + } + + for (uint i = 0; i < _previousAmbientSounds.sounds.size(); i++) { + AmbientSound &ambientSound = _previousAmbientSounds.sounds[i]; + if (flags & kFadeOutPreviousSounds) { + ambientSound.targetVolume = 0; + } else { + ambientSound.sound->setVolume(ambientSound.targetVolume); + } + } + } +} + +void RivenSoundManager::playAmbientSounds() { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + _ambientSounds.sounds[i].sound->play(); + } +} + +void RivenSoundManager::setAmbientLooping(bool loop) { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + _ambientSounds.sounds[i].sound->setLooping(loop); + } +} + +void RivenSoundManager::triggerDrawSound() { + if (_effectPlayOnDraw && _effect) { + _effect->play(); + } + _effectPlayOnDraw = false; +} + +void RivenSoundManager::pauseAmbientSounds() { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + _ambientSounds.sounds[i].sound->pause(); + } +} + +void RivenSoundManager::updateSLST() { + uint32 time = _vm->_system->getMillis(); + int32 delta = CLIP<int32>(time - _nextFadeUpdate, -50, 50); + if (_nextFadeUpdate == 0 || delta > 0) { + _nextFadeUpdate = time + 50 - delta; + + if (_ambientSounds.fading) { + fadeAmbientSoundList(_ambientSounds); + } + + if (_previousAmbientSounds.fading) { + fadeAmbientSoundList(_previousAmbientSounds); + } + + if (!_previousAmbientSounds.sounds.empty() && !_ambientSounds.fading && !_previousAmbientSounds.fading) { + freePreviousAmbientSounds(); + } + } +} + +void RivenSoundManager::fadeAmbientSoundList(AmbientSoundList &list) { + list.fading = false; + + for (uint i = 0; i < list.sounds.size(); i++) { + AmbientSound &ambientSound = list.sounds[i]; + list.fading |= fadeVolume(ambientSound); + list.fading |= fadeBalance(ambientSound); + } +} + +bool RivenSoundManager::fadeVolume(AmbientSound &ambientSound) { + uint16 volume = ambientSound.sound->getVolume(); + float delta = (ambientSound.targetVolume - volume) / 30.0f; + + if (ABS<float>(delta) < 0.01f) { + ambientSound.sound->setVolume(ambientSound.targetVolume); + return false; + } else { + // Make sure the increment is not zero once converted to an integer + if (delta > 0 && delta < 1) { + delta = 1; + } else if (delta < 0 && delta > -1) { + delta = -1; + } + + ambientSound.sound->setVolume(volume + delta); + return true; + } +} + +bool RivenSoundManager::fadeBalance(RivenSoundManager::AmbientSound &ambientSound) { + int16 balance = ambientSound.sound->getBalance(); + float delta = (ambientSound.targetBalance - balance) / 10.0f; + + if (ABS<float>(delta) < 0.01) { + ambientSound.sound->setBalance(ambientSound.targetBalance); + return false; + } else { + // Make sure the increment is not zero once converted to an integer + if (delta > 0 && delta < 1) { + delta = 1; + } else if (delta < 0 && delta > -1) { + delta = -1; + } + + ambientSound.sound->setBalance(balance + delta); + return true; + } +} + +RivenSound::RivenSound(MohawkEngine *vm, Audio::RewindableAudioStream *rewindStream) : + _vm(vm), + _volume(Audio::Mixer::kMaxChannelVolume), + _balance(0), + _looping(false), + _stream(rewindStream) { + +} + +bool RivenSound::isPlaying() const { + return _vm->_mixer->isSoundHandleActive(_handle); +} + +void RivenSound::pause() { + _vm->_mixer->pauseHandle(_handle, true); +} + +void RivenSound::setVolume(uint16 volume) { + _volume = volume; + if (isPlaying()) { + byte mixerVolume = convertVolume(volume); + _vm->_mixer->setChannelVolume(_handle, mixerVolume); + } +} + +void RivenSound::setBalance(int16 balance) { + _balance = balance; + if (isPlaying()) { + int8 mixerBalance = convertBalance(balance); + _vm->_mixer->setChannelBalance(_handle, mixerBalance); + } +} + +void RivenSound::setLooping(bool loop) { + if (isPlaying() && _looping != loop) { + warning("Changing loop state while a sound is playing is not implemented."); + } + _looping = loop; +} + +void RivenSound::play() { + if (isPlaying()) { + // If the sound is already playing, make sure it is not paused + _vm->_mixer->pauseHandle(_handle, false); + return; + } + + if (!_stream) { + warning("Trying to play a sound without a stream"); + return; + } + + Audio::AudioStream *playStream; + if (_looping) { + playStream = new Audio::LoopingAudioStream(_stream, 0); + } else { + playStream = _stream; + } + + int8 mixerBalance = convertBalance(_balance); + byte mixerVolume = convertVolume(_volume); + _vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, playStream, -1, mixerVolume, mixerBalance); + _stream = nullptr; +} + +byte RivenSound::convertVolume(uint16 volume) { + // The volume is a fixed point value in the Mohawk part of the original engine. + // It's not clear what happens when it is higher than one. + return (volume > 255) ? 255 : volume; +} + +int8 RivenSound::convertBalance(int16 balance) { + return (int8)(balance >> 8); +} + +RivenSound::~RivenSound() { + _vm->_mixer->stopHandle(_handle); + delete _stream; +} + +int16 RivenSound::getBalance() const { + return _balance; +} + +uint16 RivenSound::getVolume() const { + return _volume; +} + +RivenSoundManager::AmbientSound::AmbientSound() : + sound(nullptr), + targetVolume(0), + targetBalance(0) { + +} + +RivenSoundManager::AmbientSoundList::AmbientSoundList() : + fading(false), + suspend(false) { +} + +} // End of namespace Mohawk diff --git a/engines/mohawk/riven_sound.h b/engines/mohawk/riven_sound.h new file mode 100644 index 0000000000..f673d1ee3f --- /dev/null +++ b/engines/mohawk/riven_sound.h @@ -0,0 +1,197 @@ +/* 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. + * + */ + +#ifndef MOHAWK_RIVEN_SOUND_H +#define MOHAWK_RIVEN_SOUND_H + +#include "common/array.h" +#include "common/str.h" + +#include "audio/mixer.h" + +namespace Audio { +class RewindableAudioStream; +} + +namespace Mohawk { + +class MohawkEngine; +class RivenSound; + +/** + * Ambient sound list + */ +struct SLSTRecord { + uint16 index; + Common::Array<uint16> soundIds; + uint16 fadeFlags; + uint16 loop; + uint16 globalVolume; + uint16 u0; + uint16 suspend; + Common::Array<uint16> volumes; + Common::Array<int16> balances; + Common::Array<uint16> u2; +}; + +/** + * Sound manager for Riven + * + * The sound manager can play simulteaneously: + * - An effect sound + * - A list of ambient sounds + * + * The list of ambient sounds can be cross faded + * with the previously running ambient sounds. + */ +class RivenSoundManager { +public: + RivenSoundManager(MohawkEngine *vm); + ~RivenSoundManager(); + + /** + * Play an effect sound + * + * @param id Sound ID in the stack + * @param volume Playback volume, between 0 and 255 + * @param playOnDraw Start playing when the current card is drawn instead of immediatly + */ + void playSound(uint16 id, uint16 volume = 255, bool playOnDraw = false); + + /** Start playing the scheduled on-draw effect sound, if any. Called by the GraphicsManager. */ + void triggerDrawSound(); + + /** Stop playing the current effect sound, if any */ + void stopSound(); + + /** Start playing an ambient sound list */ + void playSLST(const SLSTRecord &slstRecord); + + /** Start playing an ambient sound list from a resource */ + void playSLST(uint16 index, uint16 card); + + /** Stop playing the current ambient sounds */ + void stopAllSLST(bool fade = false); + + /** Update the ambient sounds for fading. Called once per frame. */ + void updateSLST(); + +private: + struct AmbientSound { + RivenSound *sound; + uint16 targetVolume; + int16 targetBalance; + + AmbientSound(); + }; + + struct AmbientSoundList { + bool fading; + bool suspend; + Common::Array<AmbientSound> sounds; + + AmbientSoundList(); + }; + + enum FadeFlags { + kFadeOutPreviousSounds = 1, + kFadeInNewSounds = 2 + }; + + MohawkEngine *_vm; + + int16 _mainAmbientSoundId; + AmbientSoundList _ambientSounds; + AmbientSoundList _previousAmbientSounds; + uint32 _nextFadeUpdate; + + RivenSound *_effect; + bool _effectPlayOnDraw; + + Audio::RewindableAudioStream *makeAudioStream(uint16 id); + + // Ambient sound management + void addAmbientSounds(const SLSTRecord &record); + void playAmbientSounds(); + void pauseAmbientSounds(); + void moveAmbientSoundsToPreviousSounds(); + void freePreviousAmbientSounds(); + + // Ambient sound fading + void setTargetVolumes(const SLSTRecord &record); + void applyTargetVolumes(); + void startFadingAmbientSounds(uint16 flags); + void fadeAmbientSoundList(AmbientSoundList &list); + bool fadeVolume(AmbientSound &ambientSound); + bool fadeBalance(AmbientSound &ambientSound); + void setAmbientLooping(bool loop); +}; + +/** + * A sound used internally by the SoundManager + */ +class RivenSound { +public: + RivenSound(MohawkEngine *vm, Audio::RewindableAudioStream *rewindStream); + ~RivenSound(); + + /** Start playing the sound stream passed to the constructor */ + void play(); + + /** Is the sound currently playing ar paused? */ + bool isPlaying() const; + + /** Pause the playback, the play method resumes */ + void pause(); + + /** Get the current volume */ + uint16 getVolume() const; + + /** Change the playback volume */ + void setVolume(uint16 volume); + + /** Get the current balance */ + int16 getBalance() const; + + /** Change the balance */ + void setBalance(int16 balance); + + /** Set the sound to indefinitely loop. Must be called before startting the playback */ + void setLooping(bool loop); + +private: + static byte convertVolume(uint16 volume); + static int8 convertBalance(int16 balance); + + MohawkEngine *_vm; + + Audio::SoundHandle _handle; + Audio::RewindableAudioStream *_stream; + + uint16 _volume; + int16 _balance; + bool _looping; +}; + +} // End of namespace Mohawk + +#endif diff --git a/engines/mohawk/sound.cpp b/engines/mohawk/sound.cpp index 38cb0b3608..0711561068 100644 --- a/engines/mohawk/sound.cpp +++ b/engines/mohawk/sound.cpp @@ -37,6 +37,150 @@ namespace Mohawk { +Audio::RewindableAudioStream *makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList) { + uint32 tag = 0; + ADPCMStatus adpcmStatus; + DataChunk dataChunk; + uint32 dataSize = 0; + + memset(&dataChunk, 0, sizeof(DataChunk)); + + if (stream->readUint32BE() != ID_MHWK) // MHWK tag again + error ("Could not find tag 'MHWK'"); + + stream->readUint32BE(); // Skip size + + if (stream->readUint32BE() != ID_WAVE) + error ("Could not find tag 'WAVE'"); + + while (!dataChunk.audioData) { + tag = stream->readUint32BE(); + + switch (tag) { + case ID_ADPC: + debug(2, "Found Tag ADPC"); + // ADPCM Sound Only + // + // This is useful for seeking in the stream, and is actually quite brilliant + // considering some of the other things Broderbund did with the engine. + // Only Riven and CSTime are known to use ADPCM audio and only CSTime + // actually requires this for seeking. On the other hand, it may be interesting + // to look at that one Riven sample that uses the cue points. + // + // Basically, the sample frame from the cue list is looked up here and then + // sets the starting sample and step index at the point specified. Quite + // an elegant/efficient system, really. + + adpcmStatus.size = stream->readUint32BE(); + adpcmStatus.itemCount = stream->readUint16BE(); + adpcmStatus.channels = stream->readUint16BE(); + adpcmStatus.statusItems = new ADPCMStatus::StatusItem[adpcmStatus.itemCount]; + + assert(adpcmStatus.channels <= 2); + + for (uint16 i = 0; i < adpcmStatus.itemCount; i++) { + adpcmStatus.statusItems[i].sampleFrame = stream->readUint32BE(); + + for (uint16 j = 0; j < adpcmStatus.channels; j++) { + adpcmStatus.statusItems[i].channelStatus[j].last = stream->readSint16BE(); + adpcmStatus.statusItems[i].channelStatus[j].stepIndex = stream->readUint16BE(); + } + } + + // TODO: Actually use this chunk. For now, just delete the status items... + delete[] adpcmStatus.statusItems; + break; + case ID_CUE: + debug(2, "Found Tag Cue#"); + // Cues are used for animation sync. There are a couple in Myst and + // Riven but are not used there at all. + + if (!cueList) { + uint32 size = stream->readUint32BE(); + stream->skip(size); + break; + } + + cueList->size = stream->readUint32BE(); + cueList->pointCount = stream->readUint16BE(); + + if (cueList->pointCount == 0) + debug(2, "Cue# chunk found with no points!"); + else + debug(2, "Cue# chunk found with %d point(s)!", cueList->pointCount); + + cueList->points.resize(cueList->pointCount); + for (uint16 i = 0; i < cueList->pointCount; i++) { + cueList->points[i].sampleFrame = stream->readUint32BE(); + + byte nameLength = stream->readByte(); + cueList->points[i].name.clear(); + for (byte j = 0; j < nameLength; j++) + cueList->points[i].name += stream->readByte(); + + // Realign to an even boundary + if (!(nameLength & 1)) + stream->readByte(); + + debug (3, "Cue# chunk point %d (frame %d): %s", i, cueList->points[i].sampleFrame, cueList->points[i].name.c_str()); + } + break; + case ID_DATA: + debug(2, "Found Tag DATA"); + // We subtract 20 from the actual chunk size, which is the total size + // of the chunk's header + dataSize = stream->readUint32BE() - 20; + dataChunk.sampleRate = stream->readUint16BE(); + dataChunk.sampleCount = stream->readUint32BE(); + dataChunk.bitsPerSample = stream->readByte(); + dataChunk.channels = stream->readByte(); + dataChunk.encoding = stream->readUint16BE(); + dataChunk.loopCount = stream->readUint16BE(); + dataChunk.loopStart = stream->readUint32BE(); + dataChunk.loopEnd = stream->readUint32BE(); + + // NOTE: We currently ignore all of the loop parameters here. Myst uses the + // loopCount variable but the loopStart and loopEnd are always 0 and the size of + // the sample. Myst ME doesn't use the Mohawk Sound format and just standard WAVE + // files and therefore does not contain any of this metadata and we have to specify + // whether or not to loop elsewhere. + + dataChunk.audioData = stream->readStream(dataSize); + break; + default: + error ("Unknown tag found in 'tWAV' chunk -- '%s'", tag2str(tag)); + } + } + + // makeMohawkWaveStream always takes control of the original stream + delete stream; + + // The sound in Myst uses raw unsigned 8-bit data + // The sound in the CD version of Riven is encoded in Intel DVI ADPCM + // The sound in the DVD version of Riven is encoded in MPEG-2 Layer II or Intel DVI ADPCM + if (dataChunk.encoding == kCodecRaw) { + byte flags = Audio::FLAG_UNSIGNED; + + if (dataChunk.channels == 2) + flags |= Audio::FLAG_STEREO; + + return Audio::makeRawStream(dataChunk.audioData, dataChunk.sampleRate, flags); + } else if (dataChunk.encoding == kCodecADPCM) { + uint32 blockAlign = dataChunk.channels * dataChunk.bitsPerSample / 8; + return Audio::makeADPCMStream(dataChunk.audioData, DisposeAfterUse::YES, dataSize, Audio::kADPCMDVI, dataChunk.sampleRate, dataChunk.channels, blockAlign); + } else if (dataChunk.encoding == kCodecMPEG2) { +#ifdef USE_MAD + return Audio::makeMP3Stream(dataChunk.audioData, DisposeAfterUse::YES); +#else + warning ("MAD library not included - unable to play MP2 audio"); +#endif + } else { + error ("Unknown Mohawk WAVE encoding %d", dataChunk.encoding); + } + + return nullptr; +} + Sound::Sound(MohawkEngine* vm) : _vm(vm) { _midiDriver = NULL; _midiParser = NULL; @@ -47,7 +191,6 @@ Sound::Sound(MohawkEngine* vm) : _vm(vm) { Sound::~Sound() { stopSound(); - stopAllSLST(); stopBackgroundMyst(); if (_midiParser) { @@ -234,300 +377,6 @@ void Sound::stopMidi() { _midiParser->unloadMusic(); } -byte Sound::convertRivenVolume(uint16 volume) { - return (volume == 256) ? 255 : volume; -} - -void Sound::playSLST(uint16 index, uint16 card) { - Common::SeekableReadStream *slstStream = _vm->getResource(ID_SLST, card); - SLSTRecord slstRecord; - uint16 recordCount = slstStream->readUint16BE(); - - for (uint16 i = 0; i < recordCount; i++) { - slstRecord.index = slstStream->readUint16BE(); - slstRecord.sound_count = slstStream->readUint16BE(); - slstRecord.sound_ids = new uint16[slstRecord.sound_count]; - - for (uint16 j = 0; j < slstRecord.sound_count; j++) - slstRecord.sound_ids[j] = slstStream->readUint16BE(); - - slstRecord.fade_flags = slstStream->readUint16BE(); - slstRecord.loop = slstStream->readUint16BE(); - slstRecord.global_volume = slstStream->readUint16BE(); - slstRecord.u0 = slstStream->readUint16BE(); // Unknown - - if (slstRecord.u0 > 1) - warning("slstRecord.u0: %d non-boolean", slstRecord.u0); - - slstRecord.u1 = slstStream->readUint16BE(); // Unknown - - if (slstRecord.u1 != 0) - warning("slstRecord.u1: %d non-zero", slstRecord.u1); - - slstRecord.volumes = new uint16[slstRecord.sound_count]; - slstRecord.balances = new int16[slstRecord.sound_count]; - slstRecord.u2 = new uint16[slstRecord.sound_count]; - - for (uint16 j = 0; j < slstRecord.sound_count; j++) - slstRecord.volumes[j] = slstStream->readUint16BE(); - - for (uint16 j = 0; j < slstRecord.sound_count; j++) - slstRecord.balances[j] = slstStream->readSint16BE(); // negative = left, 0 = center, positive = right - - for (uint16 j = 0; j < slstRecord.sound_count; j++) { - slstRecord.u2[j] = slstStream->readUint16BE(); // Unknown - - if (slstRecord.u2[j] != 255 && slstRecord.u2[j] != 256) - warning("slstRecord.u2[%d]: %d not 255 or 256", j, slstRecord.u2[j]); - } - - if (slstRecord.index == index) { - playSLST(slstRecord); - delete[] slstRecord.sound_ids; - delete[] slstRecord.volumes; - delete[] slstRecord.balances; - delete[] slstRecord.u2; - delete slstStream; - return; - } - - delete[] slstRecord.sound_ids; - delete[] slstRecord.volumes; - delete[] slstRecord.balances; - delete[] slstRecord.u2; - } - - delete slstStream; - - // If we have no matching entries, we do nothing and just let - // the previous ambient sounds continue. -} - -void Sound::playSLST(SLSTRecord slstRecord) { - // End old sounds - for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) { - bool noLongerPlay = true; - for (uint16 j = 0; j < slstRecord.sound_count; j++) - if (_currentSLSTSounds[i].id == slstRecord.sound_ids[j]) - noLongerPlay = false; - if (noLongerPlay) - stopSLSTSound(i, (slstRecord.fade_flags & 1) != 0); - } - - // Start new sounds - for (uint16 i = 0; i < slstRecord.sound_count; i++) { - bool alreadyPlaying = false; - for (uint16 j = 0; j < _currentSLSTSounds.size(); j++) { - if (_currentSLSTSounds[j].id == slstRecord.sound_ids[i]) - alreadyPlaying = true; - } - if (!alreadyPlaying) { - playSLSTSound(slstRecord.sound_ids[i], - (slstRecord.fade_flags & (1 << 1)) != 0, - slstRecord.loop != 0, - slstRecord.volumes[i], - slstRecord.balances[i]); - } - } -} - -void Sound::stopAllSLST(bool fade) { - for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) { - // TODO: Fade out, if requested - _vm->_mixer->stopHandle(*_currentSLSTSounds[i].handle); - delete _currentSLSTSounds[i].handle; - } - - _currentSLSTSounds.clear(); -} - -static int8 convertBalance(int16 balance) { - return (int8)(balance >> 8); -} - -void Sound::playSLSTSound(uint16 id, bool fade, bool loop, uint16 volume, int16 balance) { - // WORKAROUND: Some Riven SLST entries have a volume of 0, so we just ignore them. - if (volume == 0) - return; - - SLSTSndHandle sndHandle; - sndHandle.handle = new Audio::SoundHandle(); - sndHandle.id = id; - _currentSLSTSounds.push_back(sndHandle); - - Audio::RewindableAudioStream *rewindStream = makeMohawkWaveStream(_vm->getResource(ID_TWAV, id)); - - // Loop here if necessary - Audio::AudioStream *audStream = rewindStream; - if (loop) - audStream = Audio::makeLoopingAudioStream(rewindStream, 0); - - // TODO: Handle fading, possibly just raise the volume of the channel in increments? - - _vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, sndHandle.handle, audStream, -1, convertRivenVolume(volume), convertBalance(balance)); -} - -void Sound::stopSLSTSound(uint16 index, bool fade) { - // TODO: Fade out, if requested - _vm->_mixer->stopHandle(*_currentSLSTSounds[index].handle); - delete _currentSLSTSounds[index].handle; - _currentSLSTSounds.remove_at(index); -} - -void Sound::pauseSLST() { - for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) - _vm->_mixer->pauseHandle(*_currentSLSTSounds[i].handle, true); -} - -void Sound::resumeSLST() { - for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) - _vm->_mixer->pauseHandle(*_currentSLSTSounds[i].handle, false); -} - -Audio::RewindableAudioStream *Sound::makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList) { - uint32 tag = 0; - ADPCMStatus adpcmStatus; - DataChunk dataChunk; - uint32 dataSize = 0; - - memset(&dataChunk, 0, sizeof(DataChunk)); - - if (stream->readUint32BE() != ID_MHWK) // MHWK tag again - error ("Could not find tag 'MHWK'"); - - stream->readUint32BE(); // Skip size - - if (stream->readUint32BE() != ID_WAVE) - error ("Could not find tag 'WAVE'"); - - while (!dataChunk.audioData) { - tag = stream->readUint32BE(); - - switch (tag) { - case ID_ADPC: - debug(2, "Found Tag ADPC"); - // ADPCM Sound Only - // - // This is useful for seeking in the stream, and is actually quite brilliant - // considering some of the other things Broderbund did with the engine. - // Only Riven and CSTime are known to use ADPCM audio and only CSTime - // actually requires this for seeking. On the other hand, it may be interesting - // to look at that one Riven sample that uses the cue points. - // - // Basically, the sample frame from the cue list is looked up here and then - // sets the starting sample and step index at the point specified. Quite - // an elegant/efficient system, really. - - adpcmStatus.size = stream->readUint32BE(); - adpcmStatus.itemCount = stream->readUint16BE(); - adpcmStatus.channels = stream->readUint16BE(); - adpcmStatus.statusItems = new ADPCMStatus::StatusItem[adpcmStatus.itemCount]; - - assert(adpcmStatus.channels <= 2); - - for (uint16 i = 0; i < adpcmStatus.itemCount; i++) { - adpcmStatus.statusItems[i].sampleFrame = stream->readUint32BE(); - - for (uint16 j = 0; j < adpcmStatus.channels; j++) { - adpcmStatus.statusItems[i].channelStatus[j].last = stream->readSint16BE(); - adpcmStatus.statusItems[i].channelStatus[j].stepIndex = stream->readUint16BE(); - } - } - - // TODO: Actually use this chunk. For now, just delete the status items... - delete[] adpcmStatus.statusItems; - break; - case ID_CUE: - debug(2, "Found Tag Cue#"); - // Cues are used for animation sync. There are a couple in Myst and - // Riven but are not used there at all. - - if (!cueList) { - uint32 size = stream->readUint32BE(); - stream->skip(size); - break; - } - - cueList->size = stream->readUint32BE(); - cueList->pointCount = stream->readUint16BE(); - - if (cueList->pointCount == 0) - debug(2, "Cue# chunk found with no points!"); - else - debug(2, "Cue# chunk found with %d point(s)!", cueList->pointCount); - - cueList->points.resize(cueList->pointCount); - for (uint16 i = 0; i < cueList->pointCount; i++) { - cueList->points[i].sampleFrame = stream->readUint32BE(); - - byte nameLength = stream->readByte(); - cueList->points[i].name.clear(); - for (byte j = 0; j < nameLength; j++) - cueList->points[i].name += stream->readByte(); - - // Realign to an even boundary - if (!(nameLength & 1)) - stream->readByte(); - - debug (3, "Cue# chunk point %d (frame %d): %s", i, cueList->points[i].sampleFrame, cueList->points[i].name.c_str()); - } - break; - case ID_DATA: - debug(2, "Found Tag DATA"); - // We subtract 20 from the actual chunk size, which is the total size - // of the chunk's header - dataSize = stream->readUint32BE() - 20; - dataChunk.sampleRate = stream->readUint16BE(); - dataChunk.sampleCount = stream->readUint32BE(); - dataChunk.bitsPerSample = stream->readByte(); - dataChunk.channels = stream->readByte(); - dataChunk.encoding = stream->readUint16BE(); - dataChunk.loopCount = stream->readUint16BE(); - dataChunk.loopStart = stream->readUint32BE(); - dataChunk.loopEnd = stream->readUint32BE(); - - // NOTE: We currently ignore all of the loop parameters here. Myst uses the - // loopCount variable but the loopStart and loopEnd are always 0 and the size of - // the sample. Myst ME doesn't use the Mohawk Sound format and just standard WAVE - // files and therefore does not contain any of this metadata and we have to specify - // whether or not to loop elsewhere. - - dataChunk.audioData = stream->readStream(dataSize); - break; - default: - error ("Unknown tag found in 'tWAV' chunk -- '%s'", tag2str(tag)); - } - } - - // makeMohawkWaveStream always takes control of the original stream - delete stream; - - // The sound in Myst uses raw unsigned 8-bit data - // The sound in the CD version of Riven is encoded in Intel DVI ADPCM - // The sound in the DVD version of Riven is encoded in MPEG-2 Layer II or Intel DVI ADPCM - if (dataChunk.encoding == kCodecRaw) { - byte flags = Audio::FLAG_UNSIGNED; - - if (dataChunk.channels == 2) - flags |= Audio::FLAG_STEREO; - - return Audio::makeRawStream(dataChunk.audioData, dataChunk.sampleRate, flags); - } else if (dataChunk.encoding == kCodecADPCM) { - uint32 blockAlign = dataChunk.channels * dataChunk.bitsPerSample / 8; - return Audio::makeADPCMStream(dataChunk.audioData, DisposeAfterUse::YES, dataSize, Audio::kADPCMDVI, dataChunk.sampleRate, dataChunk.channels, blockAlign); - } else if (dataChunk.encoding == kCodecMPEG2) { -#ifdef USE_MAD - return Audio::makeMP3Stream(dataChunk.audioData, DisposeAfterUse::YES); -#else - warning ("MAD library not included - unable to play MP2 audio"); -#endif - } else { - error ("Unknown Mohawk WAVE encoding %d", dataChunk.encoding); - } - - return NULL; -} - Audio::RewindableAudioStream *Sound::makeLivingBooksWaveStream_v1(Common::SeekableReadStream *stream) { uint16 header = stream->readUint16BE(); uint16 rate = 0; @@ -591,18 +440,6 @@ void Sound::stopSound(uint16 id) { } } -void Sound::pauseSound() { - for (uint32 i = 0; i < _handles.size(); i++) - if (_handles[i].type == kUsedHandle) - _vm->_mixer->pauseHandle(_handles[i].handle, true); -} - -void Sound::resumeSound() { - for (uint32 i = 0; i < _handles.size(); i++) - if (_handles[i].type == kUsedHandle) - _vm->_mixer->pauseHandle(_handles[i].handle, false); -} - bool Sound::isPlaying(uint16 id) { for (uint32 i = 0; i < _handles.size(); i++) if (_handles[i].type == kUsedHandle && _handles[i].id == id) diff --git a/engines/mohawk/sound.h b/engines/mohawk/sound.h index f09706e155..2b4b1ce091 100644 --- a/engines/mohawk/sound.h +++ b/engines/mohawk/sound.h @@ -42,20 +42,6 @@ namespace Mohawk { #define MAX_CHANNELS 2 // Can there be more than 2? -struct SLSTRecord { - uint16 index; - uint16 sound_count; - uint16 *sound_ids; - uint16 fade_flags; - uint16 loop; - uint16 global_volume; - uint16 u0; - uint16 u1; - uint16 *volumes; - int16 *balances; - uint16 *u2; -}; - enum SndHandleType { kFreeHandle, kUsedHandle @@ -68,11 +54,6 @@ struct SndHandle { uint16 id; }; -struct SLSTSndHandle { - Audio::SoundHandle *handle; - uint16 id; -}; - struct ADPCMStatus { // Holds ADPCM status data, but is irrelevant for us. uint32 size; uint16 itemCount; @@ -116,6 +97,8 @@ struct DataChunk { Common::SeekableReadStream *audioData; }; +Audio::RewindableAudioStream *makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList = nullptr); + class MohawkEngine; class Sound { @@ -130,8 +113,6 @@ public: void stopMidi(); void stopSound(); void stopSound(uint16 id); - void pauseSound(); - void resumeSound(); bool isPlaying(uint16 id); bool isPlaying(); uint getNumSamplesPlayed(uint16 id); @@ -144,21 +125,12 @@ public: void stopBackgroundMyst(); void changeBackgroundVolumeMyst(uint16 vol); - // Riven-specific sound functions - void playSLST(uint16 index, uint16 card); - void playSLST(SLSTRecord slstRecord); - void pauseSLST(); - void resumeSLST(); - void stopAllSLST(bool fade = false); - static byte convertRivenVolume(uint16 volume); - private: MohawkEngine *_vm; MidiDriver *_midiDriver; MidiParser *_midiParser; byte *_midiData; - static Audio::RewindableAudioStream *makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList = NULL); static Audio::RewindableAudioStream *makeLivingBooksWaveStream_v1(Common::SeekableReadStream *stream); void initMidi(); @@ -169,11 +141,6 @@ private: // Myst-specific SndHandle _mystBackgroundSound; - - // Riven-specific - void playSLSTSound(uint16 index, bool fade, bool loop, uint16 volume, int16 balance); - void stopSLSTSound(uint16 id, bool fade); - Common::Array<SLSTSndHandle> _currentSLSTSounds; }; } // End of namespace Mohawk diff --git a/engines/mortevielle/detection.cpp b/engines/mortevielle/detection.cpp index 6791707dd5..7bc5339179 100644 --- a/engines/mortevielle/detection.cpp +++ b/engines/mortevielle/detection.cpp @@ -90,6 +90,7 @@ bool MortevielleMetaEngine::hasFeature(MetaEngineFeature f) const { case kSavesSupportMetaInfo: case kSavesSupportThumbnail: case kSavesSupportCreationDate: + case kSimpleSavesNames: return true; default: return false; diff --git a/engines/neverhood/detection.cpp b/engines/neverhood/detection.cpp index 0f409a6435..0c0347ef13 100644 --- a/engines/neverhood/detection.cpp +++ b/engines/neverhood/detection.cpp @@ -228,7 +228,8 @@ bool NeverhoodMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || (f == kSavesSupportCreationDate) || - (f == kSavesSupportPlayTime); + (f == kSavesSupportPlayTime) || + (f == kSimpleSavesNames); } bool Neverhood::NeverhoodEngine::hasFeature(EngineFeature f) const { diff --git a/engines/prince/detection.cpp b/engines/prince/detection.cpp index 1c6f63aff3..ad759823d8 100644 --- a/engines/prince/detection.cpp +++ b/engines/prince/detection.cpp @@ -56,7 +56,8 @@ bool PrinceMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSavesSupportThumbnail) || (f == kSavesSupportCreationDate) || (f == kSupportsListSaves) || - (f == kSupportsLoadingDuringStartup); + (f == kSupportsLoadingDuringStartup) || + (f == kSimpleSavesNames); } bool Prince::PrinceEngine::hasFeature(EngineFeature f) const { diff --git a/engines/saga/detection.cpp b/engines/saga/detection.cpp index 0677e84d67..6fe4277c27 100644 --- a/engines/saga/detection.cpp +++ b/engines/saga/detection.cpp @@ -157,7 +157,8 @@ bool SagaMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || (f == kSavesSupportCreationDate) || - (f == kSavesSupportPlayTime); + (f == kSavesSupportPlayTime) || + (f == kSimpleSavesNames); } bool Saga::SagaEngine::hasFeature(EngineFeature f) const { diff --git a/engines/savestate.cpp b/engines/savestate.cpp index 186d7bc5f2..7366aa6a61 100644 --- a/engines/savestate.cpp +++ b/engines/savestate.cpp @@ -27,12 +27,12 @@ SaveStateDescriptor::SaveStateDescriptor() // FIXME: default to 0 (first slot) or to -1 (invalid slot) ? : _slot(-1), _description(), _isDeletable(true), _isWriteProtected(false), - _saveDate(), _saveTime(), _playTime(), _thumbnail() { + _isLocked(false), _saveDate(), _saveTime(), _playTime(), _thumbnail() { } SaveStateDescriptor::SaveStateDescriptor(int s, const Common::String &d) : _slot(s), _description(d), _isDeletable(true), _isWriteProtected(false), - _saveDate(), _saveTime(), _playTime(), _thumbnail() { + _isLocked(false), _saveDate(), _saveTime(), _playTime(), _thumbnail() { } void SaveStateDescriptor::setThumbnail(Graphics::Surface *t) { diff --git a/engines/savestate.h b/engines/savestate.h index 21ade602fa..3244d61fdb 100644 --- a/engines/savestate.h +++ b/engines/savestate.h @@ -90,6 +90,24 @@ public: bool getWriteProtectedFlag() const { return _isWriteProtected; } /** + * Defines whether the save state is "locked" because is being synced. + */ + void setLocked(bool state) { + _isLocked = state; + + //just in case: + if (state) { + setDeletableFlag(false); + setWriteProtectedFlag(true); + } + } + + /** + * Queries whether the save state is "locked" because is being synced. + */ + bool getLocked() const { return _isLocked; } + + /** * Return a thumbnail graphics surface representing the savestate visually. * This is usually a scaled down version of the game graphics. The size * should be either 160x100 or 160x120 pixels, depending on the aspect @@ -180,6 +198,11 @@ private: bool _isWriteProtected; /** + * Whether the save state is "locked" because is being synced. + */ + bool _isLocked; + + /** * Human readable description of the date the save state was created. */ Common::String _saveDate; diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index 3e67a1819a..b20ed3f8be 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -54,7 +54,6 @@ #include "sci/graphics/frameout.h" #include "sci/graphics/paint32.h" #include "video/coktel_decoder.h" -#include "sci/video/robot_decoder.h" #endif #include "common/file.h" @@ -266,8 +265,6 @@ void Console::postEnter() { #ifdef ENABLE_SCI32 } else if (_videoFile.hasSuffix(".vmd")) { videoDecoder = new Video::AdvancedVMDDecoder(); - } else if (_videoFile.hasSuffix(".rbt")) { - videoDecoder = new RobotDecoder(_engine->getPlatform() == Common::kPlatformMacintosh); } else if (_videoFile.hasSuffix(".duk")) { duckMode = true; videoDecoder = new Video::AVIDecoder(); @@ -489,6 +486,7 @@ bool Console::cmdGetVersion(int argc, const char **argv) { debugPrintf("Lofs type: %s\n", getSciVersionDesc(_engine->_features->detectLofsType())); debugPrintf("Move count type: %s\n", (_engine->_features->handleMoveCount()) ? "increment" : "ignore"); debugPrintf("SetCursor type: %s\n", getSciVersionDesc(_engine->_features->detectSetCursorType())); + debugPrintf("PseudoMouse ability: %s\n", _engine->_features->detectPseudoMouseAbility() == kPseudoMouseAbilityTrue ? "yes" : "no"); #ifdef ENABLE_SCI32 if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE)) debugPrintf("SCI2.1 kernel table: %s\n", (_engine->_features->detectSci21KernelType() == SCI_VERSION_2) ? "modified SCI2 (old)" : "SCI2.1 (new)"); diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp index f5797dc106..ad2b0f31a5 100644 --- a/engines/sci/detection.cpp +++ b/engines/sci/detection.cpp @@ -565,8 +565,8 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, // the file should be over 10MB, as it contains all the game speech and is usually // around 450MB+. The size check is for some floppy game versions like KQ6 floppy, which // also have a small resource.aud file - if (allFiles.contains("resource.aud") || allFiles.contains("audio001.002")) { - Common::FSNode file = allFiles.contains("resource.aud") ? allFiles["resource.aud"] : allFiles["audio001.002"]; + if (allFiles.contains("resource.aud") || allFiles.contains("resaud.001") || allFiles.contains("audio001.002")) { + Common::FSNode file = allFiles.contains("resource.aud") ? allFiles["resource.aud"] : (allFiles.contains("resaud.001") ? allFiles["resaud.001"] : allFiles["audio001.002"]); Common::SeekableReadStream *tmpStream = file.createReadStream(); if (tmpStream->size() > 10 * 1024 * 1024) { // We got a CD version, so set the CD flag accordingly diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index de342a3afc..eda6bfae64 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -22,15 +22,7 @@ namespace Sci { -#define GAMEOPTION_PREFER_DIGITAL_SFX GUIO_GAMEOPTIONS1 -#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS2 -#define GAMEOPTION_FB01_MIDI GUIO_GAMEOPTIONS3 -#define GAMEOPTION_JONES_CDAUDIO GUIO_GAMEOPTIONS4 -#define GAMEOPTION_KQ6_WINDOWS_CURSORS GUIO_GAMEOPTIONS5 -#define GAMEOPTION_SQ4_SILVER_CURSORS GUIO_GAMEOPTIONS6 -#define GAMEOPTION_EGA_UNDITHER GUIO_GAMEOPTIONS7 -#define GAMEOPTION_HIGH_RESOLUTION_GRAPHICS GUIO_GAMEOPTIONS8 -#define GAMEOPTION_ENABLE_BLACK_LINED_VIDEO GUIO_GAMEOPTIONS9 +#include "sci/sci.h" // SCI3 games have a different script format (in CSC files) and are currently unsupported #define ENABLE_SCI3_GAMES @@ -836,7 +828,10 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::ES_ESP, Common::kPlatformWindows, ADGF_CD | ADGF_UNSTABLE, GUIO_GK1_CD }, - // Gabriel Knight - English Macintosh + // Gabriel Knight - English Macintosh (Floppy!) + // This version is hi-res ONLY, so it should NOT get GAMEOPTION_HIGH_RESOLUTION_GRAPHICS + // (which is meant for enforcing hi-res graphics), but instead hi-res mode should be enabled all the time. + // Confirmed by [md5] and originally by clone2727. {"gk1", "", { {"Data1", 0, "044d3bcd7e5b5bb0393d954ade8053fe", 5814918}, {"Data2", 0, "99a0c63febf9e44e12a00f99c00eae0f", 6685352}, diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp index a993506f7a..e37a1651ef 100644 --- a/engines/sci/engine/features.cpp +++ b/engines/sci/engine/features.cpp @@ -45,6 +45,7 @@ GameFeatures::GameFeatures(SegManager *segMan, Kernel *kernel) : _segMan(segMan) if (!ConfMan.getBool("use_cdaudio")) _usesCdTrack = false; _forceDOSTracks = false; + _pseudoMouseAbility = kPseudoMouseAbilityUninitialized; } reg_t GameFeatures::getDetectionAddr(const Common::String &objName, Selector slc, int methodNum) { @@ -605,4 +606,50 @@ bool GameFeatures::useAltWinGMSound() { } } +// PseudoMouse was added during SCI1 +// PseudoMouseAbility is about a tiny difference in the keyboard driver, which sets the event type to either +// 40h (old behaviour) or 44h (the keyboard driver actually added 40h to the existing value). +// See engine/kevent.cpp, kMapKeyToDir - also script 933 + +// SCI1EGA: +// Quest for Glory 2 still used the old way. +// +// SCI1EARLY: +// King's Quest 5 0.000.062 uses the old way. +// Leisure Suit Larry 1 demo uses the new way, but no PseudoMouse class. +// Fairy Tales uses the new way. +// X-Mas 1990 uses the old way, no PseudoMouse class. +// Space Quest 4 floppy (1.1) uses the new way. +// Mixed Up Mother Goose uses the old way, no PseudoMouse class. +// +// SCI1MIDDLE: +// Leisure Suit Larry 5 demo uses the new way. +// Conquests of the Longbow demo uses the new way. +// Leisure Suit Larry 1 (2.0) uses the new way. +// Astro Chicken II uses the new way. +PseudoMouseAbilityType GameFeatures::detectPseudoMouseAbility() { + if (_pseudoMouseAbility == kPseudoMouseAbilityUninitialized) { + if (getSciVersion() < SCI_VERSION_1_EARLY) { + // SCI1 EGA or earlier -> pseudo mouse ability is always disabled + _pseudoMouseAbility = kPseudoMouseAbilityFalse; + + } else if (getSciVersion() == SCI_VERSION_1_EARLY) { + // For SCI1 early some games had it enabled, some others didn't. + // We try to find an object called "PseudoMouse". If it's found, we enable the ability otherwise we don't. + reg_t pseudoMouseAddr = _segMan->findObjectByName("PseudoMouse", 0); + + if (pseudoMouseAddr != NULL_REG) { + _pseudoMouseAbility = kPseudoMouseAbilityTrue; + } else { + _pseudoMouseAbility = kPseudoMouseAbilityFalse; + } + + } else { + // SCI1 middle or later -> pseudo mouse ability is always enabled + _pseudoMouseAbility = kPseudoMouseAbilityTrue; + } + } + return _pseudoMouseAbility; +} + } // End of namespace Sci diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h index 1c410267e6..b2d40f400f 100644 --- a/engines/sci/engine/features.h +++ b/engines/sci/engine/features.h @@ -34,6 +34,12 @@ enum MoveCountType { kIncrementMoveCount }; +enum PseudoMouseAbilityType { + kPseudoMouseAbilityUninitialized, + kPseudoMouseAbilityFalse, + kPseudoMouseAbilityTrue +}; + class GameFeatures { public: GameFeatures(SegManager *segMan, Kernel *kernel); @@ -110,6 +116,12 @@ public: */ void forceDOSTracks() { _forceDOSTracks = true; } + /** + * Autodetects, if Pseudo Mouse ability is enabled (different behavior in keyboard driver) + * @return kPseudoMouseAbilityTrue or kPseudoMouseAbilityFalse + */ + PseudoMouseAbilityType detectPseudoMouseAbility(); + private: reg_t getDetectionAddr(const Common::String &objName, Selector slc, int methodNum = -1); @@ -130,6 +142,8 @@ private: bool _usesCdTrack; bool _forceDOSTracks; + PseudoMouseAbilityType _pseudoMouseAbility; + SegManager *_segMan; Kernel *_kernel; }; diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp index 156f6f51f7..8cecd8c82c 100644 --- a/engines/sci/engine/file.cpp +++ b/engines/sci/engine/file.cpp @@ -200,7 +200,7 @@ reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool u #ifdef ENABLE_SCI32 if (mode != _K_FILE_MODE_OPEN_OR_FAIL && ( - (g_sci->getGameId() == GID_PHANTASMAGORIA && filename == "phantsg.dir") || + (g_sci->getGameId() == GID_PHANTASMAGORIA && (filename == "phantsg.dir" || filename == "chase.dat")) || (g_sci->getGameId() == GID_PQSWAT && filename == "swat.dat"))) { debugC(kDebugLevelFile, " -> file_open opening %s for rewriting", wrappedName.c_str()); diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index 5ff4f932be..45477e1153 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -421,6 +421,14 @@ reg_t kStubNull(EngineState *s, int argc, reg_t *argv); #ifdef ENABLE_SCI32 // SCI2 Kernel Functions +reg_t kSetCursor32(EngineState *s, int argc, reg_t *argv); +reg_t kSetNowSeen32(EngineState *s, int argc, reg_t *argv); +reg_t kBaseSetter32(EngineState *s, int argc, reg_t *argv); +reg_t kShakeScreen32(EngineState *s, int argc, reg_t *argv); +reg_t kPlatform32(EngineState *s, int argc, reg_t *argv); +reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv); +reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv); + reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv); reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv); reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv); @@ -441,15 +449,40 @@ reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv); reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv); reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv); +reg_t kRobot(EngineState *s, int argc, reg_t *argv); +reg_t kRobotOpen(EngineState *s, int argc, reg_t *argv); +reg_t kRobotShowFrame(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv); +reg_t kRobotPlay(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetIsFinished(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetIsPlaying(EngineState *s, int argc, reg_t *argv); +reg_t kRobotClose(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetCue(EngineState *s, int argc, reg_t *argv); +reg_t kRobotPause(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetFrameNo(EngineState *s, int argc, reg_t *argv); +reg_t kRobotSetPriority(EngineState *s, int argc, reg_t *argv); + reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDOpen(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDInit(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv); + reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv); reg_t kArray(EngineState *s, int argc, reg_t *argv); reg_t kListAt(EngineState *s, int argc, reg_t *argv); @@ -550,6 +583,7 @@ reg_t kSetScroll(EngineState *s, int argc, reg_t *argv); reg_t kPaletteSetFromResource32(EngineState *s, int argc, reg_t *argv); reg_t kPaletteFindColor32(EngineState *s, int argc, reg_t *argv); reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv); +reg_t kPaletteSetGamma(EngineState *s, int argc, reg_t *argv); reg_t kPalCycle(EngineState *s, int argc, reg_t *argv); reg_t kPalCycleSetCycle(EngineState *s, int argc, reg_t *argv); @@ -576,9 +610,9 @@ reg_t kTextWidth(EngineState *s, int argc, reg_t *argv); reg_t kSave(EngineState *s, int argc, reg_t *argv); reg_t kAutoSave(EngineState *s, int argc, reg_t *argv); reg_t kList(EngineState *s, int argc, reg_t *argv); -reg_t kRobot(EngineState *s, int argc, reg_t *argv); -reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv); reg_t kCD(EngineState *s, int argc, reg_t *argv); +reg_t kCheckCD(EngineState *s, int argc, reg_t *argv); +reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv); reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv); reg_t kAddBefore(EngineState *s, int argc, reg_t *argv); reg_t kMoveToFront(EngineState *s, int argc, reg_t *argv); diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index e0e4dcc233..6e141e7f3b 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -183,8 +183,8 @@ static const SciKernelMapSubEntry kDoSound_subops[] = { }; #ifdef ENABLE_SCI32 -// NOTE: In SSCI, some 'unused' kDoAudio subops are actually -// called indirectly by kDoSound: +// NOTE: In SSCI, some 'unused' kDoAudio subops are actually called indirectly +// by kDoSound: // // kDoSoundGetAudioCapability -> kDoAudioGetCapability // kDoSoundPlay -> kDoAudioPlay, kDoAudioStop @@ -194,23 +194,26 @@ static const SciKernelMapSubEntry kDoSound_subops[] = { // kDoSoundSetLoop -> kDoAudioSetLoop // kDoSoundUpdateCues -> kDoAudioPosition // -// In ScummVM, logic inside these kernel functions has been -// moved to methods of Audio32, and direct calls to Audio32 -// are made from kDoSound instead. +// In ScummVM, logic inside these kernel functions has been moved to methods of +// Audio32, and direct calls to Audio32 are made from kDoSound instead. // -// Some kDoAudio methods are esoteric and appear to be used -// only by one or two games: +// Some kDoAudio methods are esoteric and appear to be used only by one or two +// games: // -// kDoAudioMixing: Phantasmagoria (other games call this -// function, but only to disable the feature) -// kDoAudioHasSignal: SQ6 TalkRandCycle -// kDoAudioPan: Rama RegionSFX::pan method +// - kDoAudioMixing: Phantasmagoria (other games call this function, but only +// to disable the feature) +// - kDoAudioHasSignal: SQ6 TalkRandCycle +// - kDoAudioPan: Rama RegionSFX::pan method +// - kDoAudioCritical: Phantasmagoria, chapter 3, nursery (room 14200), during +// the "ghost lullaby" event. It is used to make the +// lullaby sound exclusive, but it really doesn't make any +// major difference. Returning 0 means "non-critical", i.e. +// normal audio behavior. // -// Finally, there is a split in SCI2.1mid audio code. -// QFG4CD & SQ6 do not have opcodes 18 and 19, but they -// exist in GK2, KQ7 2.00b, Phantasmagoria 1, PQ:SWAT, and -// Torin. (It is unknown if they exist in MUMG Deluxe or -// Shivers 1; they are not used in either of these games.) +// Finally, there is a split in SCI2.1mid audio code. QFG4CD & SQ6 do not have +// opcodes 18 and 19, but they exist in GK2, KQ7 2.00b, Phantasmagoria 1, +// PQ:SWAT, and Torin. It is unknown if they exist in MUMG Deluxe or Shivers 1; +// they are not used in either of these games. // version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kDoAudio_subops[] = { @@ -235,7 +238,7 @@ static const SciKernelMapSubEntry kDoAudio_subops[] = { { SIG_SINCE_SCI21MID, 15, MAP_CALL(DoAudioFade), "(iiii)(i)(i)", NULL }, { SIG_SINCE_SCI21MID, 16, MAP_DUMMY(DoAudioFade36), "iiiii(iii)(i)", NULL }, { SIG_SINCE_SCI21MID, 17, MAP_CALL(DoAudioHasSignal), "", NULL }, - { SIG_SINCE_SCI21MID, 18, MAP_EMPTY(DoAudioCritical), "", NULL }, + { SIG_SINCE_SCI21MID, 18, MAP_EMPTY(DoAudioCritical), "(i)", NULL }, { SIG_SINCE_SCI21MID, 19, MAP_CALL(DoAudioSetLoop), "iii(o)", NULL }, { SIG_SCI3, 20, MAP_DUMMY(DoAudioPan), "", NULL }, { SIG_SCI3, 21, MAP_DUMMY(DoAudioPanOff), "", NULL }, @@ -294,16 +297,17 @@ static const SciKernelMapSubEntry kPalette_subops[] = { { SIG_SCI16, 1, MAP_CALL(PaletteSetFromResource), "i(i)", NULL }, { SIG_SCI16, 2, MAP_CALL(PaletteSetFlag), "iii", NULL }, { SIG_SCI16, 3, MAP_CALL(PaletteUnsetFlag), "iii", kPaletteUnsetFlag_workarounds }, -#ifdef ENABLE_SCI32 - { SIG_SCI32, 1, MAP_CALL(PaletteSetFromResource32), "i(i)", NULL }, - { SIG_SCI32, 2, MAP_CALL(PaletteSetFade), "iii", NULL }, - { SIG_SCI32, 3, MAP_CALL(PaletteFindColor32), "iii", NULL }, -#endif { SIG_SCI16, 4, MAP_CALL(PaletteSetIntensity), "iii(i)", NULL }, { SIG_SCI16, 5, MAP_CALL(PaletteFindColor), "iii", NULL }, { SIG_SCI16, 6, MAP_CALL(PaletteAnimate), "i*", NULL }, { SIG_SCI16, 7, MAP_CALL(PaletteSave), "", NULL }, { SIG_SCI16, 8, MAP_CALL(PaletteRestore), "[r0]", NULL }, +#ifdef ENABLE_SCI32 + { SIG_SCI32, 1, MAP_CALL(PaletteSetFromResource32), "i(i)", NULL }, + { SIG_SCI32, 2, MAP_CALL(PaletteSetFade), "iii", NULL }, + { SIG_SCI32, 3, MAP_CALL(PaletteFindColor32), "iii", NULL }, + { SIG_SCI3, 4, MAP_CALL(PaletteSetGamma), "i", NULL }, +#endif SCI_SUBOPENTRY_TERMINATOR }; @@ -376,7 +380,7 @@ static const SciKernelMapSubEntry kText_subops[] = { // version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kBitmap_subops[] = { { SIG_SINCE_SCI21, 0, MAP_CALL(BitmapCreate), "iiii(i)(i)(i)", NULL }, - { SIG_SINCE_SCI21, 1, MAP_CALL(BitmapDestroy), "r", NULL }, + { SIG_SINCE_SCI21, 1, MAP_CALL(BitmapDestroy), "[r!]", NULL }, { SIG_SINCE_SCI21, 2, MAP_CALL(BitmapDrawLine), "riiiii(i)(i)", NULL }, { SIG_SINCE_SCI21, 3, MAP_CALL(BitmapDrawView), "riii(i)(i)(0)(i)(i)", NULL }, { SIG_SINCE_SCI21, 4, MAP_CALL(BitmapDrawText), "rriiiiiiiiiii", NULL }, @@ -396,6 +400,13 @@ static const SciKernelMapSubEntry kBitmap_subops[] = { }; // version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kCD_subops[] = { + { SIG_SINCE_SCI21MID, 0, MAP_CALL(CheckCD), "(i)", NULL }, + { SIG_SINCE_SCI21MID, 1, MAP_CALL(GetSavedCD), "", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kList_subops[] = { { SIG_SINCE_SCI21, 0, MAP_CALL(NewList), "", NULL }, { SIG_SINCE_SCI21, 1, MAP_CALL(DisposeList), "l", NULL }, @@ -406,8 +417,8 @@ static const SciKernelMapSubEntry kList_subops[] = { { SIG_SINCE_SCI21, 6, MAP_CALL(NextNode), "n", NULL }, { SIG_SINCE_SCI21, 7, MAP_CALL(PrevNode), "n", NULL }, { SIG_SINCE_SCI21, 8, MAP_CALL(NodeValue), "[n0]", NULL }, - { SIG_SINCE_SCI21, 9, MAP_CALL(AddAfter), "lnn.", NULL }, - { SIG_SINCE_SCI21, 10, MAP_CALL(AddToFront), "ln.", NULL }, + { SIG_SINCE_SCI21, 9, MAP_CALL(AddAfter), "lnn(.)", NULL }, + { SIG_SINCE_SCI21, 10, MAP_CALL(AddToFront), "ln(.)", NULL }, { SIG_SINCE_SCI21, 11, MAP_CALL(AddToEnd), "ln(.)", NULL }, { SIG_SINCE_SCI21, 12, MAP_CALL(AddBefore), "ln.", NULL }, { SIG_SINCE_SCI21, 13, MAP_CALL(MoveToFront), "ln", NULL }, @@ -423,6 +434,27 @@ static const SciKernelMapSubEntry kList_subops[] = { SCI_SUBOPENTRY_TERMINATOR }; +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kShowMovieWin_subops[] = { + { SIG_SCI2, 0, MAP_CALL(ShowMovieWinOpen), "r", NULL }, + { SIG_SCI2, 1, MAP_CALL(ShowMovieWinInit), "ii(ii)", NULL }, + { SIG_SCI2, 2, MAP_CALL(ShowMovieWinPlay), "i", NULL }, + { SIG_SCI2, 6, MAP_CALL(ShowMovieWinClose), "", NULL }, + { SIG_SINCE_SCI21, 0, MAP_CALL(ShowMovieWinOpen), "ir", NULL }, + { SIG_SINCE_SCI21, 1, MAP_CALL(ShowMovieWinInit), "iii(ii)", NULL }, + { SIG_SINCE_SCI21, 2, MAP_CALL(ShowMovieWinPlay), "i(ii)(i)(i)", NULL }, + { SIG_SINCE_SCI21, 6, MAP_CALL(ShowMovieWinClose), "i", NULL }, + // Since movies are rendered within the graphics engine in ScummVM, + // it is not necessary to copy the palette from SCI to MCI, so this + // can be a no-op + { SIG_SINCE_SCI21, 7, MAP_EMPTY(ShowMovieWinSetPalette), "i", NULL }, + { SIG_SINCE_SCI21, 8, MAP_CALL(ShowMovieWinGetDuration), "i", NULL }, + { SIG_SINCE_SCI21, 11, MAP_CALL(ShowMovieWinCue), "ii", NULL }, + { SIG_SINCE_SCI21, 14, MAP_CALL(ShowMovieWinPlayUntilEvent), "i(i)", NULL }, + { SIG_SINCE_SCI21, 15, MAP_CALL(ShowMovieWinInitDouble), "iii", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + // There are a lot of subops to PlayVMD, but only a few of them are ever // actually used by games // version, subId, function-mapping, signature, workarounds @@ -430,12 +462,30 @@ static const SciKernelMapSubEntry kPlayVMD_subops[] = { { SIG_SINCE_SCI21, 0, MAP_CALL(PlayVMDOpen), "r(i)(i)", NULL }, { SIG_SINCE_SCI21, 1, MAP_CALL(PlayVMDInit), "ii(i)(i)(ii)", NULL }, { SIG_SINCE_SCI21, 6, MAP_CALL(PlayVMDClose), "", NULL }, + { SIG_SINCE_SCI21, 10, MAP_CALL(PlayVMDGetStatus), "", NULL }, { SIG_SINCE_SCI21, 14, MAP_CALL(PlayVMDPlayUntilEvent), "i(i)(i)", NULL }, { SIG_SINCE_SCI21, 16, MAP_CALL(PlayVMDShowCursor), "i", NULL }, { SIG_SINCE_SCI21, 17, MAP_DUMMY(PlayVMDStartBlob), "", NULL }, { SIG_SINCE_SCI21, 18, MAP_DUMMY(PlayVMDStopBlobs), "", NULL }, { SIG_SINCE_SCI21, 21, MAP_CALL(PlayVMDSetBlackoutArea), "iiii", NULL }, { SIG_SINCE_SCI21, 23, MAP_CALL(PlayVMDRestrictPalette), "ii", NULL }, + { SIG_SCI3, 28, MAP_EMPTY(PlayVMDSetPreload), "i", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kRobot_subops[] = { + { SIG_SINCE_SCI21, 0, MAP_CALL(RobotOpen), "ioiii(i)", NULL }, + { SIG_SINCE_SCI21, 1, MAP_CALL(RobotShowFrame), "i(ii)", NULL }, + { SIG_SINCE_SCI21, 2, MAP_CALL(RobotGetFrameSize), "r", NULL }, + { SIG_SINCE_SCI21, 4, MAP_CALL(RobotPlay), "", NULL }, + { SIG_SINCE_SCI21, 5, MAP_CALL(RobotGetIsFinished), "", NULL }, + { SIG_SINCE_SCI21, 6, MAP_CALL(RobotGetIsPlaying), "", NULL }, + { SIG_SINCE_SCI21, 7, MAP_CALL(RobotClose), "", NULL }, + { SIG_SINCE_SCI21, 8, MAP_CALL(RobotGetCue), "o", NULL }, + { SIG_SINCE_SCI21, 10, MAP_CALL(RobotPause), "", NULL }, + { SIG_SINCE_SCI21, 11, MAP_CALL(RobotGetFrameNo), "", NULL }, + { SIG_SINCE_SCI21, 12, MAP_CALL(RobotSetPriority), "i", NULL }, SCI_SUBOPENTRY_TERMINATOR }; @@ -455,7 +505,7 @@ static const SciKernelMapSubEntry kString_subops[] = { { SIG_SCI32, 0, MAP_CALL(StringNew), "i(i)", NULL }, { SIG_SCI32, 1, MAP_CALL(StringSize), "[or]", NULL }, { SIG_SCI32, 2, MAP_CALL(StringAt), "[or]i", NULL }, - { SIG_SCI32, 3, MAP_CALL(StringPutAt), "[or]i(i*)", NULL }, + { SIG_SCI32, 3, MAP_CALL(StringPutAt), "[or]i(i*)", kStringPutAt_workarounds }, // StringFree accepts invalid references { SIG_SCI32, 4, MAP_CALL(StringFree), "[or0!]", NULL }, { SIG_SCI32, 5, MAP_CALL(StringFill), "[or]ii", NULL }, @@ -548,7 +598,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(Animate), SIG_EVERYWHERE, "(l0)(i)", NULL, NULL }, { MAP_CALL(AssertPalette), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(AvoidPath), SIG_EVERYWHERE, "ii(.*)", NULL, NULL }, - { MAP_CALL(BaseSetter), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(BaseSetter), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "BaseSetter", kBaseSetter32, SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL }, +#endif { MAP_CALL(CanBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL }, { MAP_CALL(CantBeHere), SIG_SCI16, SIGFOR_ALL, "o(l)", NULL, NULL }, #ifdef ENABLE_SCI32 @@ -617,8 +670,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(GetSaveDir), SIG_EVERYWHERE, "", NULL, NULL }, { MAP_CALL(GetSaveFiles), SIG_EVERYWHERE, "rrr", NULL, NULL }, { MAP_CALL(GetTime), SIG_EVERYWHERE, "(i)", NULL, NULL }, - { MAP_CALL(GlobalToLocal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, - { MAP_CALL(GlobalToLocal), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(GlobalToLocal), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "GlobalToLocal", kGlobalToLocal32, SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, +#endif { MAP_CALL(Graph), SIG_EVERYWHERE, NULL, kGraph_subops, NULL }, { MAP_CALL(HaveMouse), SIG_EVERYWHERE, "", NULL, NULL }, { MAP_CALL(HiliteControl), SIG_EVERYWHERE, "o", NULL, NULL }, @@ -629,8 +684,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(Joystick), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop { MAP_CALL(LastNode), SIG_EVERYWHERE, "l", NULL, NULL }, { MAP_CALL(Load), SIG_EVERYWHERE, "ii(i*)", NULL, NULL }, - { MAP_CALL(LocalToGlobal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, - { MAP_CALL(LocalToGlobal), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(LocalToGlobal), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "LocalToGlobal", kLocalToGlobal32, SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, +#endif { MAP_CALL(Lock), SIG_EVERYWHERE, "ii(i)", NULL, NULL }, { MAP_CALL(MapKeyToDir), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(Memory), SIG_EVERYWHERE, "i(.*)", NULL, kMemory_workarounds }, // subop @@ -655,11 +712,15 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(Palette), SIG_EVERYWHERE, "i(.*)", kPalette_subops, NULL }, { MAP_CALL(Parse), SIG_EVERYWHERE, "ro", NULL, NULL }, { MAP_CALL(PicNotValid), SIG_EVERYWHERE, "(i)", NULL, NULL }, - { MAP_CALL(Platform), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Platform), SIG_SCI16, SIGFOR_ALL, "(.*)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "Platform", kPlatform32, SIG_SCI32, SIGFOR_MAC, "(.*)", NULL, NULL }, + { "Platform", kPlatform32, SIG_SCI32, SIGFOR_ALL, "(i)", NULL, NULL }, +#endif { MAP_CALL(Portrait), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop { MAP_CALL(PrevNode), SIG_EVERYWHERE, "n", NULL, NULL }, { MAP_CALL(PriCoord), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, NULL }, + { MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, kRandom_workarounds }, { MAP_CALL(ReadNumber), SIG_EVERYWHERE, "r", NULL, kReadNumber_workarounds }, { MAP_CALL(RemapColors), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, NULL }, #ifdef ENABLE_SCI32 @@ -672,23 +733,32 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(Said), SIG_EVERYWHERE, "[r0]", NULL, NULL }, { MAP_CALL(SaveGame), SIG_EVERYWHERE, "[r0]i[r0](r0)", NULL, NULL }, { MAP_CALL(ScriptID), SIG_EVERYWHERE, "[io](i)", NULL, NULL }, - { MAP_CALL(SetCursor), SIG_SINCE_SCI21, SIGFOR_ALL, "i(i)([io])(i*)", NULL, NULL }, - // TODO: SCI2.1 may supply an object optionally (mother goose sci21 right on startup) - find out why { MAP_CALL(SetCursor), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(iiiiii)", NULL, NULL }, - { MAP_CALL(SetCursor), SIG_EVERYWHERE, "i(i)(i)(i)(i)", NULL, kSetCursor_workarounds }, + { MAP_CALL(SetCursor), SIG_SCI16, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, kSetCursor_workarounds }, +#ifdef ENABLE_SCI32 + { "SetCursor", kSetCursor32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)", NULL, kSetCursor_workarounds }, +#endif { MAP_CALL(SetDebug), SIG_EVERYWHERE, "(i*)", NULL, NULL }, { MAP_CALL(SetJump), SIG_EVERYWHERE, "oiii", NULL, NULL }, { MAP_CALL(SetMenu), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, { MAP_CALL(SetNowSeen), SIG_SCI16, SIGFOR_ALL, "o(i)", NULL, NULL }, #ifdef ENABLE_SCI32 - { MAP_CALL(SetNowSeen), SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL }, + { "SetNowSeen", kSetNowSeen32, SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL }, #endif { MAP_CALL(SetPort), SIG_EVERYWHERE, "i(iiiii)(i)", NULL, kSetPort_workarounds }, { MAP_CALL(SetQuitStr), SIG_EVERYWHERE, "r", NULL, NULL }, { MAP_CALL(SetSynonyms), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(SetVideoMode), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(ShakeScreen), SIG_EVERYWHERE, "(i)(i)", NULL, NULL }, - { MAP_CALL(ShowMovie), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(ShakeScreen), SIG_SCI16, SIGFOR_ALL, "(i)(i)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "ShakeScreen", kShakeScreen32, SIG_SCI32, SIGFOR_ALL, "i(i)", NULL, NULL }, +#endif + { MAP_CALL(ShowMovie), SIG_SCI16, SIGFOR_ALL, "(.*)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "ShowMovie", kShowMovie32, SIG_SCI32, SIGFOR_DOS, "ri(i)(i)", NULL, NULL }, + { "ShowMovie", kShowMovie32, SIG_SCI32, SIGFOR_MAC, "ri(i)(i)", NULL, NULL }, + { "ShowMovie", kShowMovieWin, SIG_SCI32, SIGFOR_WIN, "(.*)", kShowMovieWin_subops, NULL }, +#endif { MAP_CALL(Show), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(SinDiv), SIG_EVERYWHERE, "ii", NULL, NULL }, { MAP_CALL(Sort), SIG_EVERYWHERE, "ooo", NULL, NULL }, @@ -745,7 +815,7 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(CreateTextBitmap), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, { MAP_CALL(DeletePlane), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(DeleteScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, - { "DisposeTextBitmap", kBitmapDestroy, SIG_SCI2, SIGFOR_ALL, "r", NULL, NULL }, + { "DisposeTextBitmap", kBitmapDestroy, SIG_SCI2, SIGFOR_ALL, "[r!]", NULL, NULL }, { MAP_CALL(FrameOut), SIG_EVERYWHERE, "(i)", NULL, NULL }, { MAP_CALL(GetHighPlanePri), SIG_EVERYWHERE, "", NULL, NULL }, { MAP_CALL(InPolygon), SIG_EVERYWHERE, "iio", NULL, NULL }, @@ -815,12 +885,12 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_DUMMY(PointSize), SIG_EVERYWHERE, "(.*)", NULL, NULL }, // SCI2.1 Kernel Functions - { MAP_CALL(CD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(CD), SIG_SINCE_SCI21MID, SIGFOR_ALL, "(.*)", kCD_subops, NULL }, { MAP_CALL(IsOnMe), SIG_EVERYWHERE, "iioi", NULL, NULL }, { MAP_CALL(List), SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)", kList_subops, NULL }, { MAP_CALL(MulDiv), SIG_EVERYWHERE, "iii", NULL, NULL }, { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", kPlayVMD_subops, NULL }, - { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", kRobot_subops, NULL }, { MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL }, { MAP_CALL(Text), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kText_subops, NULL }, { MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii(i)(i)", NULL, NULL }, diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp index 534d9ce713..9250e0fc13 100644 --- a/engines/sci/engine/kevent.cpp +++ b/engines/sci/engine/kevent.cpp @@ -34,6 +34,9 @@ #include "sci/graphics/coordadjuster.h" #include "sci/graphics/cursor.h" #include "sci/graphics/maciconbar.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/frameout.h" +#endif namespace Sci { @@ -58,10 +61,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { if (g_debug_simulated_key && (mask & SCI_EVENT_KEYBOARD)) { // In case we use a simulated event we query the current mouse position mousePos = g_sci->_gfxCursor->getPosition(); -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2_1_EARLY) - g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x); -#endif + // Limit the mouse cursor position, if necessary g_sci->_gfxCursor->refreshPosition(); @@ -86,11 +86,14 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2) mousePos = curEvent.mousePosSci; - else + else { #endif mousePos = curEvent.mousePos; - // Limit the mouse cursor position, if necessary - g_sci->_gfxCursor->refreshPosition(); + // Limit the mouse cursor position, if necessary + g_sci->_gfxCursor->refreshPosition(); +#ifdef ENABLE_SCI32 + } +#endif if (g_sci->getVocabulary()) g_sci->getVocabulary()->parser_event = NULL_REG; // Invalidate parser event @@ -258,11 +261,12 @@ reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) { if (readSelectorValue(segMan, obj, SELECTOR(type)) == SCI_EVENT_KEYBOARD) { // Keyboard uint16 message = readSelectorValue(segMan, obj, SELECTOR(message)); uint16 eventType = SCI_EVENT_DIRECTION; - // Check if the game is using cursor views. These games allowed control - // of the mouse cursor via the keyboard controls (the so called - // "PseudoMouse" functionality in script 933). - if (g_sci->_features->detectSetCursorType() == SCI_VERSION_1_1) + // It seems with SCI1 Sierra started to add the SCI_EVENT_DIRECTION bit instead of setting it directly. + // It was done inside the keyboard driver and is required for the PseudoMouse functionality and class + // to work (script 933). + if (g_sci->_features->detectPseudoMouseAbility() == kPseudoMouseAbilityTrue) { eventType |= SCI_EVENT_KEYBOARD; + } for (int i = 0; i < 9; i++) { if (keyToDirMap[i].key == message) { @@ -280,14 +284,13 @@ reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) { reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; - reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32 SegManager *segMan = s->_segMan; if (obj.getSegment()) { int16 x = readSelectorValue(segMan, obj, SELECTOR(x)); int16 y = readSelectorValue(segMan, obj, SELECTOR(y)); - g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y, planeObject); + g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y); writeSelectorValue(segMan, obj, SELECTOR(x), x); writeSelectorValue(segMan, obj, SELECTOR(y), y); @@ -299,14 +302,13 @@ reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) { reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; - reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32 SegManager *segMan = s->_segMan; if (obj.getSegment()) { int16 x = readSelectorValue(segMan, obj, SELECTOR(x)); int16 y = readSelectorValue(segMan, obj, SELECTOR(y)); - g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y, planeObject); + g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y); writeSelectorValue(segMan, obj, SELECTOR(x), x); writeSelectorValue(segMan, obj, SELECTOR(y), y); @@ -321,4 +323,52 @@ reg_t kJoystick(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } +#ifdef ENABLE_SCI32 +reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv) { + const reg_t result = argv[0]; + const reg_t planeObj = argv[1]; + + bool visible = true; + Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj); + if (plane == nullptr) { + plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj); + visible = false; + } + if (plane == nullptr) { + error("kGlobalToLocal: Plane %04x:%04x not found", PRINT_REG(planeObj)); + } + + const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) - plane->_gameRect.left; + const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) - plane->_gameRect.top; + + writeSelectorValue(s->_segMan, result, SELECTOR(x), x); + writeSelectorValue(s->_segMan, result, SELECTOR(y), y); + + return make_reg(0, visible); +} + +reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv) { + const reg_t result = argv[0]; + const reg_t planeObj = argv[1]; + + bool visible = true; + Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj); + if (plane == nullptr) { + plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj); + visible = false; + } + if (plane == nullptr) { + error("kLocalToGlobal: Plane %04x:%04x not found", PRINT_REG(planeObj)); + } + + const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) + plane->_gameRect.left; + const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) + plane->_gameRect.top; + + writeSelectorValue(s->_segMan, result, SELECTOR(x), x); + writeSelectorValue(s->_segMan, result, SELECTOR(y), y); + + return make_reg(0, visible); +} +#endif + } // End of namespace Sci diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 4508a481a0..e8b9d0461d 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -40,6 +40,9 @@ #include "sci/engine/savegame.h" #include "sci/sound/audio.h" #include "sci/console.h" +#ifdef ENABLE_SCI32 +#include "sci/resource.h" +#endif namespace Sci { @@ -196,26 +199,25 @@ reg_t kValidPath(EngineState *s, int argc, reg_t *argv) { #ifdef ENABLE_SCI32 reg_t kCD(EngineState *s, int argc, reg_t *argv) { - // TODO: Stub - switch (argv[0].toUint16()) { - case 0: - if (argc == 1) { - // Check if a disc is in the drive - return TRUE_REG; - } else { - // Check if the specified disc is in the drive - // and return the current disc number. We just - // return the requested disc number. - return argv[1]; - } - case 1: - // Return the current CD number - return make_reg(0, 1); - default: - warning("CD(%d)", argv[0].toUint16()); + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kCheckCD(EngineState *s, int argc, reg_t *argv) { + const int16 cdNo = argc > 0 ? argv[0].toSint16() : 0; + + if (cdNo) { + g_sci->getResMan()->findDisc(cdNo); } - return NULL_REG; + return make_reg(0, g_sci->getResMan()->getCurrentDiscNo()); +} + +reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv) { + // TODO: This is wrong, CD number needs to be available prior to + // the save game being loaded + return make_reg(0, g_sci->getResMan()->getCurrentDiscNo()); } #endif @@ -251,6 +253,28 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { return SIGNAL_REG; } + // Torin's autosave system checks for the presence of autosave.cat + // by opening it. Since we don't use .cat files, we instead check + // for autosave.000 or autosave.001. + // + // The same logic is being followed for torinsg.cat - this shows + // the "Open..." button when continuing a game. + // + // This has the added benefit of not detecting an SSCI autosave.cat + // accompanying SSCI autosave files that we wouldn't be able to load. + if (g_sci->getGameId() == GID_TORIN && (name == "autosave.cat" || name == "torinsg.cat")) { + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + const Common::String pattern = (name == "autosave.cat") ? g_sci->wrapFilename("autosave.###") : g_sci->getSavegamePattern(); + bool exists = !saveFileMan->listSavefiles(pattern).empty(); + if (exists) { + // Dummy handle. Torin only checks if this is SIGNAL_REG, + // and calls kFileIOClose on it. + return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE); + } else { + return SIGNAL_REG; + } + } + if (name.empty()) { // Happens many times during KQ1 (e.g. when typing something) debugC(kDebugLevelFile, "Attempted to open a file with an empty filename"); @@ -608,6 +632,15 @@ reg_t kFileIORename(EngineState *s, int argc, reg_t *argv) { Common::String oldName = s->_segMan->getString(argv[0]); Common::String newName = s->_segMan->getString(argv[1]); + // We don't fully implement all cases that could occur here, and + // assume the file to be renamed is a wrapped filename. + // Known usage: In Phant1 and KQ7 while deleting savegames. + // The scripts rewrite the dir file as a temporary file, and then + // rename it to the actual dir file. + + oldName = g_sci->wrapFilename(oldName); + newName = g_sci->wrapFilename(newName); + // SCI1.1 returns 0 on success and a DOS error code on fail. SCI32 // returns -1 on fail. We just return -1 for all versions. if (g_sci->getSaveFileManager()->renameSavefile(oldName, newName)) @@ -688,7 +721,7 @@ reg_t kSave(EngineState *s, int argc, reg_t *argv) { #endif reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { - Common::String game_id; + Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : ""; int16 virtualId = argv[1].toSint16(); int16 savegameId = -1; Common::String game_description; @@ -703,6 +736,13 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } + // Torin has two sets of saves: autosave.### and torinsg.###, both with + // their own slots and .cat file. + // The autosave system uses autosave.000 and autosave.001. + // It also checks the presence of autosave.cat to determine if it should + // show the chapter selection menu on startup. (See kFileIOOpen.) + bool torinAutosave = g_sci->getGameId() == GID_TORIN && game_id == "Autosave"; + if (argv[0].isNull()) { // Direct call, from a patched Game::save if ((argv[1] != SIGNAL_REG) || (!argv[2].isNull())) @@ -722,9 +762,15 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { if (savegameId < 0) return NULL_REG; + } else if (torinAutosave) { + if (argv[2].isNull()) + error("kSaveGame: called with description being NULL"); + game_description = s->_segMan->getString(argv[2]); + savegameId = virtualId; + + debug(3, "kSaveGame(%s,%d,%s,%s) [Torin autosave]", game_id.c_str(), virtualId, game_description.c_str(), version.c_str()); } else { // Real call from script - game_id = s->_segMan->getString(argv[0]); if (argv[2].isNull()) error("kSaveGame: called with description being NULL"); game_description = s->_segMan->getString(argv[2]); @@ -798,6 +844,10 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); Common::OutSaveFile *out; + if (torinAutosave) { + filename = g_sci->wrapFilename(Common::String::format("autosave.%03d", savegameId)); + } + out = saveFileMan->openForSaving(filename); if (!out) { warning("Error opening savegame \"%s\" for writing", filename.c_str()); @@ -826,6 +876,10 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId); + + // See comment in kSaveGame + bool torinAutosave = g_sci->getGameId() == GID_TORIN && game_id == "Autosave"; + if (argv[0].isNull()) { // Direct call, either from launcher or from a patched Game::restore if (savegameId == -1) { @@ -841,7 +895,7 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { pausedMusic = true; } // don't adjust ID of the saved game, it's already correct - } else { + } else if (!torinAutosave) { if (g_sci->getGameId() == GID_JONES) { // Jones has one save slot only savegameId = 0; @@ -858,8 +912,9 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { s->r_acc = NULL_REG; // signals success Common::Array<SavegameDesc> saves; - listSavegames(saves); - if (findSavegame(saves, savegameId) == -1) { + if (!torinAutosave) + listSavegames(saves); + if (!torinAutosave && findSavegame(saves, savegameId) == -1) { s->r_acc = TRUE_REG; warning("Savegame ID %d not found", savegameId); } else { @@ -867,6 +922,10 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { Common::String filename = g_sci->getSavegameName(savegameId); Common::SeekableReadStream *in; + if (torinAutosave) { + filename = g_sci->wrapFilename(Common::String::format("autosave.%03d", savegameId)); + } + in = saveFileMan->openForLoading(filename); if (in) { // found a savegame file diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index cae5a09789..d375a27954 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -579,17 +579,8 @@ reg_t kBaseSetter(EngineState *s, int argc, reg_t *argv) { } reg_t kSetNowSeen(EngineState *s, int argc, reg_t *argv) { -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2) { - g_sci->_gfxFrameout->kernelSetNowSeen(argv[0]); - return NULL_REG; - } else { -#endif - g_sci->_gfxCompare->kernelSetNowSeen(argv[0]); - return s->r_acc; -#ifdef ENABLE_SCI32 - } -#endif + g_sci->_gfxCompare->kernelSetNowSeen(argv[0]); + return s->r_acc; } reg_t kPalette(EngineState *s, int argc, reg_t *argv) { diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp index e458109cc2..a33fcf3167 100644 --- a/engines/sci/engine/kgraphics32.cpp +++ b/engines/sci/engine/kgraphics32.cpp @@ -37,8 +37,6 @@ #include "sci/graphics/cache.h" #include "sci/graphics/compare.h" #include "sci/graphics/controls16.h" -#include "sci/graphics/coordadjuster.h" -#include "sci/graphics/cursor.h" #include "sci/graphics/palette.h" #include "sci/graphics/paint16.h" #include "sci/graphics/picture.h" @@ -48,6 +46,7 @@ #include "sci/graphics/text16.h" #include "sci/graphics/view.h" #ifdef ENABLE_SCI32 +#include "sci/graphics/cursor32.h" #include "sci/graphics/celobj32.h" #include "sci/graphics/controls32.h" #include "sci/graphics/font.h" // TODO: remove once kBitmap is moved in a separate class @@ -64,6 +63,107 @@ namespace Sci { extern void showScummVMDialog(const Common::String &message); +reg_t kBaseSetter32(EngineState *s, int argc, reg_t *argv) { + reg_t object = argv[0]; + + const GuiResourceId viewId = readSelectorValue(s->_segMan, object, SELECTOR(view)); + const int16 loopNo = readSelectorValue(s->_segMan, object, SELECTOR(loop)); + const int16 celNo = readSelectorValue(s->_segMan, object, SELECTOR(cel)); + const int16 x = readSelectorValue(s->_segMan, object, SELECTOR(x)); + const int16 y = readSelectorValue(s->_segMan, object, SELECTOR(y)); + + CelObjView celObj(viewId, loopNo, celNo); + + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + const Ratio scaleX(scriptWidth, celObj._scaledWidth); + const Ratio scaleY(scriptHeight, celObj._scaledHeight); + + int16 brLeft; + + if (celObj._mirrorX) { + brLeft = x - ((celObj._width - celObj._displace.x) * scaleX).toInt(); + } else { + brLeft = x - (celObj._displace.x * scaleX).toInt(); + } + + const int16 brRight = brLeft + (celObj._width * scaleX).toInt() - 1; + + writeSelectorValue(s->_segMan, object, SELECTOR(brLeft), brLeft); + writeSelectorValue(s->_segMan, object, SELECTOR(brRight), brRight); + writeSelectorValue(s->_segMan, object, SELECTOR(brBottom), y + 1); + writeSelectorValue(s->_segMan, object, SELECTOR(brTop), y + 1 - readSelectorValue(s->_segMan, object, SELECTOR(yStep))); + + return s->r_acc; +} + +reg_t kSetNowSeen32(EngineState *s, int argc, reg_t *argv) { + const bool found = g_sci->_gfxFrameout->kernelSetNowSeen(argv[0]); + + // NOTE: MGDX is assumed to use the older kSetNowSeen since it was + // released before SQ6, but this has not been verified since it cannot be + // disassembled at the moment (Phar Lap Windows-only release) + if (getSciVersion() <= SCI_VERSION_2_1_EARLY || + g_sci->getGameId() == GID_SQ6 || + g_sci->getGameId() == GID_MOTHERGOOSEHIRES) { + + if (!found) { + error("kSetNowSeen: Unable to find screen item %04x:%04x", PRINT_REG(argv[0])); + } + return s->r_acc; + } + + if (!found) { + warning("kSetNowSeen: Unable to find screen item %04x:%04x", PRINT_REG(argv[0])); + } + + return make_reg(0, found); +} + +reg_t kSetCursor32(EngineState *s, int argc, reg_t *argv) { + switch (argc) { + case 1: { + if (argv[0].toSint16() == -2) { + g_sci->_gfxCursor32->clearRestrictedArea(); + } else { + if (argv[0].isNull()) { + g_sci->_gfxCursor32->hide(); + } else { + g_sci->_gfxCursor32->show(); + } + } + break; + } + case 2: { + const Common::Point position(argv[0].toSint16(), argv[1].toSint16()); + g_sci->_gfxCursor32->setPosition(position); + break; + } + case 3: { + g_sci->_gfxCursor32->setView(argv[0].toUint16(), argv[1].toSint16(), argv[2].toSint16()); + break; + } + case 4: { + const Common::Rect restrictRect(argv[0].toSint16(), + argv[1].toSint16(), + argv[2].toSint16() + 1, + argv[3].toSint16() + 1); + g_sci->_gfxCursor32->setRestrictedArea(restrictRect); + break; + } + default: + error("kSetCursor: Invalid number of arguments (%d)", argc); + } + + return s->r_acc; +} + +reg_t kShakeScreen32(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxFrameout->shakeScreen(argv[0].toSint16(), (ShakeDirection)argv[1].toSint16()); + return s->r_acc; +} + reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) { const Buffer &buffer = g_sci->_gfxFrameout->getCurrentBuffer(); if (buffer.screenWidth < 640 || buffer.screenHeight < 400) @@ -266,7 +366,7 @@ reg_t kMessageBox(EngineState *s, int argc, reg_t *argv) { * effect */ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { - ShowStyleType type = (ShowStyleType)argv[0].toUint16(); + const uint16 type = argv[0].toUint16(); reg_t planeObj = argv[1]; int16 seconds = argv[2].toSint16(); // NOTE: This value seems to indicate whether the transition is an @@ -301,6 +401,10 @@ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { divisions = argc > 9 ? argv[9].toSint16() : -1; } + if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && g_sci->getGameId() != GID_KQ7 && type == 15) || type > 15) { + error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj)); + } + // TODO: Reuse later for SCI2 and SCI3 implementation and then discard // warning("kSetShowStyle: effect %d, plane: %04x:%04x (%s), sec: %d, " // "dir: %d, prio: %d, animate: %d, ref frame: %d, black screen: %d, " @@ -312,7 +416,7 @@ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { // NOTE: The order of planeObj and showStyle are reversed // because this is how SCI3 called the corresponding method // on the KernelMgr - g_sci->_gfxTransitions32->kernelSetShowStyle(argc, planeObj, type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen); + g_sci->_gfxTransitions32->kernelSetShowStyle(argc, planeObj, (ShowStyleType)type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen); return s->r_acc; } @@ -543,7 +647,16 @@ reg_t kBitmapCreate(EngineState *s, int argc, reg_t *argv) { } reg_t kBitmapDestroy(EngineState *s, int argc, reg_t *argv) { - s->_segMan->freeBitmap(argv[0]); + const reg_t &addr = argv[0]; + const SegmentObj *const segment = s->_segMan->getSegmentObj(addr.getSegment()); + + if (segment != nullptr && + segment->getType() == SEG_TYPE_BITMAP && + segment->isValidOffset(addr.getOffset())) { + + s->_segMan->freeBitmap(addr); + } + return s->r_acc; } @@ -797,6 +910,19 @@ reg_t kPaletteFindColor32(EngineState *s, int argc, reg_t *argv) { return make_reg(0, g_sci->_gfxPalette32->matchColor(r, g, b)); } +/* + * Used in SCI3. SCI3 contains 6 gamma look-up tables, with the first + * table (gamma = 0) being the default one. + */ +reg_t kPaletteSetGamma(EngineState *s, int argc, reg_t *argv) { + const uint8 gamma = argv[0].toUint16(); + assert(gamma <= 6); + + warning("TODO: kPaletteSetGamma(%d)", gamma); + + return s->r_acc; +} + reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv) { uint16 fromColor = argv[0].toUint16(); uint16 toColor = argv[1].toUint16(); diff --git a/engines/sci/engine/klists.cpp b/engines/sci/engine/klists.cpp index c0da2daaeb..e780d3cdc3 100644 --- a/engines/sci/engine/klists.cpp +++ b/engines/sci/engine/klists.cpp @@ -374,13 +374,21 @@ reg_t kFindKey(EngineState *s, int argc, reg_t *argv) { reg_t kDeleteKey(EngineState *s, int argc, reg_t *argv) { reg_t node_pos = kFindKey(s, 2, argv); - Node *n; List *list = s->_segMan->lookupList(argv[0]); if (node_pos.isNull()) return NULL_REG; // Signal failure - n = s->_segMan->lookupNode(node_pos); + Node *n = s->_segMan->lookupNode(node_pos); + +#ifdef ENABLE_SCI32 + for (int i = 1; i <= list->numRecursions; ++i) { + if (list->nextNodes[i] == node_pos) { + list->nextNodes[i] = n->succ; + } + } +#endif + if (list->first == node_pos) list->first = n->succ; if (list->last == node_pos) @@ -486,7 +494,7 @@ reg_t kListAt(EngineState *s, int argc, reg_t *argv) { List *list = s->_segMan->lookupList(argv[0]); reg_t curAddress = list->first; if (list->first.isNull()) { - error("kListAt tried to reference empty list (%04x:%04x)", PRINT_REG(argv[0])); + // Happens in Torin when examining Di's locket in chapter 3 return NULL_REG; } Node *curNode = s->_segMan->lookupNode(curAddress); @@ -544,9 +552,18 @@ reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) { ObjVarRef address; + ++list->numRecursions; + + if (list->numRecursions > ARRAYSIZE(list->nextNodes)) { + error("Too much recursion in kListEachElementDo"); + } + while (curNode) { - // We get the next node here as the current node might be gone after the invoke - reg_t nextNode = curNode->succ; + // We get the next node here as the current node might be deleted by the + // invoke. In the case that the next node is also deleted, kDeleteKey + // needs to be able to adjust the location of the next node, which is + // why it is stored on the list instead of on the stack + list->nextNodes[list->numRecursions] = curNode->succ; curObject = curNode->value; // First, check if the target selector is a variable @@ -559,11 +576,18 @@ reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) { } } else { invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2); + // Check if the call above leads to a game restore, in which case + // the segment manager will be reset, and the original list will + // be invalidated + if (s->abortScriptProcessing == kAbortLoadGame) + return s->r_acc; } - curNode = s->_segMan->lookupNode(nextNode); + curNode = s->_segMan->lookupNode(list->nextNodes[list->numRecursions]); } + --list->numRecursions; + return s->r_acc; } diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp index 1924848717..f2a3c6b0f7 100644 --- a/engines/sci/engine/kmisc.cpp +++ b/engines/sci/engine/kmisc.cpp @@ -20,6 +20,7 @@ * */ +#include "common/config-manager.h" #include "common/system.h" #include "sci/sci.h" @@ -29,6 +30,9 @@ #include "sci/engine/kernel.h" #include "sci/engine/gc.h" #include "sci/graphics/cursor.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/cursor32.h" +#endif #include "sci/graphics/maciconbar.h" #include "sci/console.h" @@ -510,9 +514,12 @@ reg_t kMacPlatform(EngineState *s, int argc, reg_t *argv) { // In SCI1, its usage is still unknown // In SCI1.1, it's NOP // In SCI32, it's used for remapping cursor ID's +#ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2_1_EARLY) // Set Mac cursor remap - g_sci->_gfxCursor->setMacCursorRemapList(argc - 1, argv + 1); - else if (getSciVersion() != SCI_VERSION_1_1) + g_sci->_gfxCursor32->setMacCursorRemapList(argc - 1, argv + 1); + else +#endif + if (getSciVersion() != SCI_VERSION_1_1) warning("Unknown SCI1 kMacPlatform(0) call"); break; case 4: // Handle icon bar code @@ -535,31 +542,28 @@ reg_t kMacPlatform(EngineState *s, int argc, reg_t *argv) { } enum kSciPlatforms { + kSciPlatformMacintosh = 0, kSciPlatformDOS = 1, kSciPlatformWindows = 2 }; -enum kPlatformOps { - kPlatformUnk0 = 0, - kPlatformCDSpeed = 1, - kPlatformUnk2 = 2, - kPlatformCDCheck = 3, - kPlatformGetPlatform = 4, - kPlatformUnk5 = 5, - kPlatformIsHiRes = 6, - kPlatformIsItWindows = 7 -}; - reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { + enum Operation { + kPlatformUnknown = 0, + kPlatformGetPlatform = 4, + kPlatformUnknown5 = 5, + kPlatformIsHiRes = 6, + kPlatformWin311OrHigher = 7 + }; + bool isWindows = g_sci->getPlatform() == Common::kPlatformWindows; - if (argc == 0 && getSciVersion() < SCI_VERSION_2) { + if (argc == 0) { // This is called in KQ5CD with no parameters, where it seems to do some // graphics driver check. This kernel function didn't have subfunctions // then. If 0 is returned, the game functions normally, otherwise all // the animations show up like a slideshow (e.g. in the intro). So we - // return 0. However, the behavior changed for kPlatform with no - // parameters in SCI32. + // return 0. return NULL_REG; } @@ -571,30 +575,23 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { uint16 operation = (argc == 0) ? 0 : argv[0].toUint16(); switch (operation) { - case kPlatformCDSpeed: - // TODO: Returns CD Speed? - warning("STUB: kPlatform(CDSpeed)"); - break; - case kPlatformUnk2: - // Always returns 2 - return make_reg(0, 2); - case kPlatformCDCheck: - // TODO: Some sort of CD check? - warning("STUB: kPlatform(CDCheck)"); - break; - case kPlatformUnk0: + case kPlatformUnknown: // For Mac versions, kPlatform(0) with other args has more functionality if (g_sci->getPlatform() == Common::kPlatformMacintosh && argc > 1) return kMacPlatform(s, argc - 1, argv + 1); // Otherwise, fall through case kPlatformGetPlatform: - return make_reg(0, (isWindows) ? kSciPlatformWindows : kSciPlatformDOS); - case kPlatformUnk5: + if (isWindows) + return make_reg(0, kSciPlatformWindows); + else if (g_sci->getPlatform() == Common::kPlatformMacintosh) + return make_reg(0, kSciPlatformMacintosh); + else + return make_reg(0, kSciPlatformDOS); + case kPlatformUnknown5: // This case needs to return the opposite of case 6 to get hires graphics return make_reg(0, !isWindows); case kPlatformIsHiRes: - return make_reg(0, isWindows); - case kPlatformIsItWindows: + case kPlatformWin311OrHigher: return make_reg(0, isWindows); default: error("Unsupported kPlatform operation %d", operation); @@ -603,6 +600,43 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } +#ifdef ENABLE_SCI32 +reg_t kPlatform32(EngineState *s, int argc, reg_t *argv) { + enum Operation { + kGetPlatform = 0, + kGetCDSpeed = 1, + kGetColorDepth = 2, + kGetCDDrive = 3 + }; + + const Operation operation = argc > 0 ? (Operation)argv[0].toSint16() : kGetPlatform; + + switch (operation) { + case kGetPlatform: + switch (g_sci->getPlatform()) { + case Common::kPlatformDOS: + return make_reg(0, kSciPlatformDOS); + case Common::kPlatformWindows: + return make_reg(0, kSciPlatformWindows); + case Common::kPlatformMacintosh: + // For Mac versions, kPlatform(0) with other args has more functionality + if (argc > 1) + return kMacPlatform(s, argc - 1, argv + 1); + else + return make_reg(0, kSciPlatformMacintosh); + default: + error("Unknown platform %d", g_sci->getPlatform()); + } + case kGetColorDepth: + return make_reg(0, /* 256 color */ 2); + case kGetCDSpeed: + case kGetCDDrive: + default: + return make_reg(0, 0); + } +} +#endif + reg_t kEmpty(EngineState *s, int argc, reg_t *argv) { // Placeholder for empty kernel functions which are still called from the // engine scripts (like the empty kSetSynonyms function in SCI1.1). This diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp index de4d4a282c..b539c84f5d 100644 --- a/engines/sci/engine/kvideo.cpp +++ b/engines/sci/engine/kvideo.cpp @@ -61,40 +61,21 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { uint16 screenWidth = g_sci->_gfxScreen->getDisplayWidth(); uint16 screenHeight = g_sci->_gfxScreen->getDisplayHeight(); - videoState.fileName.toLowercase(); - bool isVMD = videoState.fileName.hasSuffix(".vmd"); - - if (screenWidth == 640 && width <= 320 && height <= 240 && ((videoState.flags & kDoubled) || !isVMD)) { + if (screenWidth == 640 && width <= 320 && height <= 240) { width *= 2; height *= 2; pitch *= 2; scaleBuffer = new byte[width * height * bytesPerPixel]; } - uint16 x, y; - - // Sanity check... - if (videoState.x > 0 && videoState.y > 0 && isVMD) { - x = videoState.x; - y = videoState.y; - - if (x + width > screenWidth || y + height > screenHeight) { - // Happens in the Lighthouse demo - warning("VMD video won't fit on screen, centering it instead"); - x = (screenWidth - width) / 2; - y = (screenHeight - height) / 2; - } - } else { - x = (screenWidth - width) / 2; - y = (screenHeight - height) / 2; - } + uint16 x = (screenWidth - width) / 2; + uint16 y = (screenHeight - height) / 2; bool skipVideo = false; - EngineState *s = g_sci->getEngineState(); if (videoDecoder->hasDirtyPalette()) { - const byte *palette = videoDecoder->getPalette() + s->_vmdPalStart * 3; - g_system->getPaletteManager()->setPalette(palette, s->_vmdPalStart, s->_vmdPalEnd - s->_vmdPalStart); + const byte *palette = videoDecoder->getPalette(); + g_system->getPaletteManager()->setPalette(palette, 0, 255); } while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { @@ -103,7 +84,7 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { if (frame) { if (scaleBuffer) { - // TODO: Probably should do aspect ratio correction in e.g. GK1 Windows + // TODO: Probably should do aspect ratio correction in KQ6 g_sci->_gfxScreen->scale2x((const byte *)frame->getPixels(), scaleBuffer, videoDecoder->getWidth(), videoDecoder->getHeight(), bytesPerPixel); g_system->copyRectToScreen(scaleBuffer, pitch, x, y, width, height); } else { @@ -111,8 +92,8 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { } if (videoDecoder->hasDirtyPalette()) { - const byte *palette = videoDecoder->getPalette() + s->_vmdPalStart * 3; - g_system->getPaletteManager()->setPalette(palette, s->_vmdPalStart, s->_vmdPalEnd - s->_vmdPalStart); + const byte *palette = videoDecoder->getPalette(); + g_system->getPaletteManager()->setPalette(palette, 0, 255); } g_system->updateScreen(); @@ -181,16 +162,6 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { // TODO: This appears to be some sort of subop. case 0 contains the string // for the video, so we'll just play it from there for now. -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2_1_EARLY) { - // SCI2.1 always has argv[0] as 1, the rest of the arguments seem to - // follow SCI1.1/2. - if (argv[0].toUint16() != 1) - error("SCI2.1 kShowMovie argv[0] not 1"); - argv++; - argc--; - } -#endif switch (argv[0].toUint16()) { case 0: { Common::String filename = s->_segMan->getString(argv[1]); @@ -243,52 +214,176 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { } #ifdef ENABLE_SCI32 +reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv) { + Common::String fileName = s->_segMan->getString(argv[0]); + const int16 numTicks = argv[1].toSint16(); + const int16 x = argc > 3 ? argv[2].toSint16() : 0; + const int16 y = argc > 3 ? argv[3].toSint16() : 0; + + g_sci->_video32->getSEQPlayer().play(fileName, numTicks, x, y); + + return s->r_acc; +} reg_t kRobot(EngineState *s, int argc, reg_t *argv) { - int16 subop = argv[0].toUint16(); - - switch (subop) { - case 0: { // init - int id = argv[1].toUint16(); - reg_t obj = argv[2]; - int16 flag = argv[3].toSint16(); - int16 x = argv[4].toUint16(); - int16 y = argv[5].toUint16(); - warning("kRobot(init), id %d, obj %04x:%04x, flag %d, x=%d, y=%d", id, PRINT_REG(obj), flag, x, y); - g_sci->_robotDecoder->load(id); - g_sci->_robotDecoder->start(); - g_sci->_robotDecoder->setPos(x, y); - } - break; - case 1: // LSL6 hires (startup) - // TODO - return NULL_REG; // an integer is expected - case 4: { // start - we don't really have a use for this one - //int id = argv[1].toUint16(); - //warning("kRobot(start), id %d", id); - } - break; - case 7: // unknown, called e.g. by Phantasmagoria - warning("kRobot(%d)", subop); - break; - case 8: // sync - //if (true) { // debug: automatically skip all robot videos - if (g_sci->_robotDecoder->endOfVideo()) { - g_sci->_robotDecoder->close(); - // Signal the engine scripts that the video is done - writeSelector(s->_segMan, argv[1], SELECTOR(signal), SIGNAL_REG); - } else { - writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG); - } - break; - default: - warning("kRobot(%d)", subop); - break; - } + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} +reg_t kRobotOpen(EngineState *s, int argc, reg_t *argv) { + const GuiResourceId robotId = argv[0].toUint16(); + const reg_t plane = argv[1]; + const int16 priority = argv[2].toSint16(); + const int16 x = argv[3].toSint16(); + const int16 y = argv[4].toSint16(); + const int16 scale = argc > 5 ? argv[5].toSint16() : 128; + g_sci->_video32->getRobotPlayer().open(robotId, plane, priority, x, y, scale); + return make_reg(0, 0); +} +reg_t kRobotShowFrame(EngineState *s, int argc, reg_t *argv) { + const uint16 frameNo = argv[0].toUint16(); + const uint16 newX = argc > 1 ? argv[1].toUint16() : (uint16)RobotDecoder::kUnspecified; + const uint16 newY = argc > 1 ? argv[2].toUint16() : (uint16)RobotDecoder::kUnspecified; + g_sci->_video32->getRobotPlayer().showFrame(frameNo, newX, newY, RobotDecoder::kUnspecified); return s->r_acc; } +reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv) { + Common::Rect frameRect; + const uint16 numFramesTotal = g_sci->_video32->getRobotPlayer().getFrameSize(frameRect); + + reg_t *outRect = s->_segMan->derefRegPtr(argv[0], 4); + outRect[0] = make_reg(0, frameRect.left); + outRect[1] = make_reg(0, frameRect.top); + outRect[2] = make_reg(0, frameRect.right - 1); + outRect[3] = make_reg(0, frameRect.bottom - 1); + + return make_reg(0, numFramesTotal); +} + +reg_t kRobotPlay(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getRobotPlayer().resume(); + return s->r_acc; +} + +reg_t kRobotGetIsFinished(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getRobotPlayer().getStatus() == RobotDecoder::kRobotStatusEnd); +} + +reg_t kRobotGetIsPlaying(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getRobotPlayer().getStatus() == RobotDecoder::kRobotStatusPlaying); +} + +reg_t kRobotClose(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getRobotPlayer().close(); + return s->r_acc; +} + +reg_t kRobotGetCue(EngineState *s, int argc, reg_t *argv) { + writeSelectorValue(s->_segMan, argv[0], SELECTOR(signal), g_sci->_video32->getRobotPlayer().getCue()); + return s->r_acc; +} + +reg_t kRobotPause(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getRobotPlayer().pause(); + return s->r_acc; +} + +reg_t kRobotGetFrameNo(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getRobotPlayer().getFrameNo()); +} + +reg_t kRobotSetPriority(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getRobotPlayer().setPriority(argv[0].toSint16()); + return s->r_acc; +} + +reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv) { + // SCI2.1 adds a movie ID to the call, but the movie ID is broken, + // so just ignore it + if (getSciVersion() > SCI_VERSION_2) { + ++argv; + --argc; + } + + const Common::String fileName = s->_segMan->getString(argv[0]); + return make_reg(0, g_sci->_video32->getAVIPlayer().open(fileName)); +} + +reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv) { + // SCI2.1 adds a movie ID to the call, but the movie ID is broken, + // so just ignore it + if (getSciVersion() > SCI_VERSION_2) { + ++argv; + --argc; + } + + const int16 x = argv[0].toSint16(); + const int16 y = argv[1].toSint16(); + const int16 width = argc > 3 ? argv[2].toSint16() : 0; + const int16 height = argc > 3 ? argv[3].toSint16() : 0; + return make_reg(0, g_sci->_video32->getAVIPlayer().init1x(x, y, width, height)); +} + +reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv) { + if (getSciVersion() == SCI_VERSION_2) { + AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)argv[0].toUint16(); + return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags)); + } else { + // argv[0] is a broken movie ID + const int16 from = argc > 2 ? argv[1].toSint16() : 0; + const int16 to = argc > 2 ? argv[2].toSint16() : 0; + const int16 showStyle = argc > 3 ? argv[3].toSint16() : 0; + const bool cue = argc > 4 ? (bool)argv[4].toSint16() : false; + return make_reg(0, g_sci->_video32->getAVIPlayer().play(from, to, showStyle, cue)); + } +} + +reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getAVIPlayer().close()); +} + +reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getAVIPlayer().getDuration()); +} + +reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv) { + // SCI2.1 adds a movie ID to the call, but the movie ID is broken, + // so just ignore it + if (getSciVersion() > SCI_VERSION_2) { + ++argv; + --argc; + } + + const uint16 frameNo = argv[0].toUint16(); + return make_reg(0, g_sci->_video32->getAVIPlayer().cue(frameNo)); +} + +reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv) { + const int defaultFlags = + AVIPlayer::kEventFlagEnd | + AVIPlayer::kEventFlagEscapeKey; + + // argv[0] is the movie number, which is not used by this method + const AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)(argc > 1 ? argv[1].toUint16() : defaultFlags); + + return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags)); +} + +reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv) { + // argv[0] is a broken movie ID + const int16 x = argv[1].toSint16(); + const int16 y = argv[2].toSint16(); + return make_reg(0, g_sci->_video32->getAVIPlayer().init2x(x, y)); +} + reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) { if (!s) return make_reg(0, getSciVersion()); @@ -330,6 +425,10 @@ reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv) { return make_reg(0, g_sci->_video32->getVMDPlayer().close()); } +reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getVMDPlayer().getStatus()); +} + reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv) { const VMDPlayer::EventFlags flags = (VMDPlayer::EventFlags)argv[0].toUint16(); const int16 lastFrameNo = argc > 1 ? argv[1].toSint16() : -1; diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index c4d53a2dc9..be2d7660cb 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -48,6 +48,7 @@ #include "sci/sound/music.h" #ifdef ENABLE_SCI32 +#include "sci/graphics/cursor32.h" #include "sci/graphics/frameout.h" #include "sci/graphics/palette32.h" #include "sci/graphics/remap32.h" @@ -159,23 +160,6 @@ void syncWithSerializer(Common::Serializer &s, SciString &obj) { } } -void syncWithSerializer(Common::Serializer &s, SciBitmap *&obj) { - bool hasEntry; - if (s.isSaving()) { - hasEntry = obj != nullptr; - } - s.syncAsByte(hasEntry); - - if (hasEntry) { - if (s.isLoading()) { - obj = new SciBitmap; - } - - obj->saveLoadWithSerializer(s); - } else { - obj = nullptr; - } -} #endif #pragma mark - @@ -183,7 +167,7 @@ void syncWithSerializer(Common::Serializer &s, SciBitmap *&obj) { // By default, sync using syncWithSerializer, which in turn can easily be overloaded. template<typename T> struct DefaultSyncer : Common::BinaryFunction<Common::Serializer, T, void> { - void operator()(Common::Serializer &s, T &obj) const { + void operator()(Common::Serializer &s, T &obj, int) const { syncWithSerializer(s, obj); } }; @@ -191,10 +175,31 @@ struct DefaultSyncer : Common::BinaryFunction<Common::Serializer, T, void> { // Syncer for entries in a segment obj table template<typename T> struct SegmentObjTableEntrySyncer : Common::BinaryFunction<Common::Serializer, typename T::Entry &, void> { - void operator()(Common::Serializer &s, typename T::Entry &entry) const { + void operator()(Common::Serializer &s, typename T::Entry &entry, int index) const { s.syncAsSint32LE(entry.next_free); - syncWithSerializer(s, entry.data); + bool hasData; + if (s.getVersion() >= 37) { + if (s.isSaving()) { + hasData = entry.data != nullptr; + } + s.syncAsByte(hasData); + } else { + hasData = (entry.next_free == index); + } + + if (hasData) { + if (s.isLoading()) { + entry.data = new typename T::value_type; + } + syncWithSerializer(s, *entry.data); + } else if (s.isLoading()) { + if (s.getVersion() < 37) { + typename T::value_type dummy; + syncWithSerializer(s, dummy); + } + entry.data = nullptr; + } } }; @@ -222,9 +227,8 @@ struct ArraySyncer : Common::BinaryFunction<Common::Serializer, T, void> { if (s.isLoading()) arr.resize(len); - typename Common::Array<T>::iterator i; - for (i = arr.begin(); i != arr.end(); ++i) { - sync(s, *i); + for (uint i = 0; i < len; ++i) { + sync(s, arr[i], i); } } }; @@ -423,6 +427,7 @@ void EngineState::saveLoadWithSerializer(Common::Serializer &s) { if (getSciVersion() >= SCI_VERSION_2) { g_sci->_gfxPalette32->saveLoadWithSerializer(s); g_sci->_gfxRemap32->saveLoadWithSerializer(s); + g_sci->_gfxCursor32->saveLoadWithSerializer(s); } else #endif g_sci->_gfxPalette16->saveLoadWithSerializer(s); @@ -886,6 +891,35 @@ void GfxRemap32::saveLoadWithSerializer(Common::Serializer &s) { _needsUpdate = true; } } + +void GfxCursor32::saveLoadWithSerializer(Common::Serializer &s) { + if (s.getVersion() < 38) { + return; + } + + int32 hideCount; + if (s.isSaving()) { + hideCount = _hideCount; + } + s.syncAsSint32LE(hideCount); + s.syncAsSint16LE(_restrictedArea.left); + s.syncAsSint16LE(_restrictedArea.top); + s.syncAsSint16LE(_restrictedArea.right); + s.syncAsSint16LE(_restrictedArea.bottom); + s.syncAsUint16LE(_cursorInfo.resourceId); + s.syncAsUint16LE(_cursorInfo.loopNo); + s.syncAsUint16LE(_cursorInfo.celNo); + + if (s.isLoading()) { + hide(); + setView(_cursorInfo.resourceId, _cursorInfo.loopNo, _cursorInfo.celNo); + if (!hideCount) { + show(); + } else { + _hideCount = hideCount; + } + } +} #endif void GfxPorts::saveLoadWithSerializer(Common::Serializer &s) { diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h index c5c2bcef08..6616081a20 100644 --- a/engines/sci/engine/savegame.h +++ b/engines/sci/engine/savegame.h @@ -37,6 +37,8 @@ struct EngineState; * * Version - new/changed feature * ============================= + * 38 - SCI32 cursor + * 37 - Segment entry data changed to pointers * 36 - SCI32 bitmap segment * 35 - SCI32 remap * 34 - SCI32 palettes, and store play time in ticks @@ -61,7 +63,7 @@ struct EngineState; */ enum { - CURRENT_SAVEGAME_VERSION = 36, + CURRENT_SAVEGAME_VERSION = 38, MINIMUM_SAVEGAME_VERSION = 14 }; diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index e6eed0b4b7..d45c689985 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -746,12 +746,115 @@ static const uint16 gk1PatchInterrogationBug[] = { PATCH_END }; -// script, description, signature patch +// On day 10 nearly at the end of the game, Gabriel Knight dresses up and right after that +// someone will be at the door. Gabriel turns around to see what's going on. +// +// In ScummVM Gabriel turning around plays endlessly. This is caused by the loop of Gabriel +// being kept at 1, but view + cel were changed accordingly. The view used - which is view 859 - +// does not have a loop 1. kNumCels is called on that, BUT kNumCels in SSCI is broken in that +// regard. It checks for loop > count and not loop >= count and will return basically random data +// in case loop == count. +// +// In SSCI this simply worked by accident. kNumCels returned 0x53 in this case, but later script code +// fixed that up somehow, so it worked out in the end. +// +// The setup for this is done in SDJEnters::changeState(0). The cycler will never reach the goal +// because the goal will be cel -1, so it loops endlessly. +// +// We fix this by adding a setLoop(0). +// +// Applies to at least: English PC-CD, German PC-CD +// Responsible method: sDJEnters::changeState +static const uint16 gk1SignatureDay10GabrielDressUp[] = { + 0x87, 0x01, // lap param[1] + 0x65, 0x14, // aTop state + 0x36, // push + 0x3c, // dup + 0x35, 0x00, // ldi 0 + 0x1a, // eq? + 0x30, SIG_UINT16(0x006f), // bnt [next state 1] + SIG_ADDTOOFFSET(+84), + 0x39, 0x0e, // pushi 0Eh (view) + 0x78, // push1 + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x035B), // pushi 035Bh (859d) + 0x38, SIG_UINT16(0x0141), // pushi 0141h (setCel) + 0x78, // push1 + 0x76, // push0 + 0x38, SIG_UINT16(0x00E9), // pushi 00E9h (setCycle) + 0x7a, // push2 + 0x51, 0x18, // class End + 0x36, // push + 0x7c, // pushSelf + 0x81, 0x00, // lag global[0] + 0x4a, 0x14, 0x00, // send 14h + // GKEgo::view(859) + // GKEgo::setCel(0) + // GKEgo::setCycle(End, sDJEnters) + 0x32, SIG_UINT16(0x0233), // jmp [ret] + // next state + 0x3c, // dup + 0x35, 0x01, // ldi 01 + 0x1a, // eq? + 0x31, 0x07, // bnt [next state 2] + 0x35, 0x02, // ldi 02 + 0x65, 0x1a, // aTop cycles + 0x32, SIG_UINT16(0x0226), // jmp [ret] + // next state + 0x3c, // dup + 0x35, 0x02, // ldi 02 + 0x1a, // eq? + 0x31, 0x2a, // bnt [next state 3] + 0x78, // push1 + SIG_ADDTOOFFSET(+34), + // part of state 2 code, delays for 1 cycle + 0x35, 0x01, // ldi 1 + 0x65, 0x1a, // aTop cycles + SIG_END +}; + +static const uint16 gk1PatchDay10GabrielDressUp[] = { + PATCH_ADDTOOFFSET(+9), + 0x30, SIG_UINT16(0x0073), // bnt [next state 1] - offset adjusted + SIG_ADDTOOFFSET(+84 + 11), + // added by us: setting loop to 0 (5 bytes needed) + 0x38, SIG_UINT16(0x00FB), // pushi 00FBh (setLoop) + 0x78, // push1 + 0x76, // push0 + // original code, but offset changed + 0x38, SIG_UINT16(0x00E9), // pushi 00E9h (setCycle) + 0x7a, // push2 + 0x51, 0x18, // class End + 0x36, // push + 0x7c, // pushSelf + 0x81, 0x00, // lag global[0] + 0x4a, 0x1a, 0x00, // send 1Ah - adjusted + // GKEgo::view(859) + // GKEgo::setCel(0) + // GKEgo::setLoop(0) <-- new, by us + // GKEgo::setCycle(End, sDJEnters) + // end of original code + 0x3a, // toss + 0x48, // ret (saves 1 byte) + // state 1 code + 0x3c, // dup + 0x34, SIG_UINT16(0x0001), // ldi 0001 (waste 1 byte) + 0x1a, // eq? + 0x31, 2, // bnt [next state 2] + 0x33, 41, // jmp to state 2 delay code + SIG_ADDTOOFFSET(+41), + // wait 2 cycles instead of only 1 + 0x35, 0x02, // ldi 2 + PATCH_END +}; + +// script, description, signature patch static const SciScriptPatcherEntry gk1Signatures[] = { - { true, 51, "interrogation bug", 1, gk1SignatureInterrogationBug, gk1PatchInterrogationBug }, - { true, 212, "day 5 phone freeze", 1, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze }, - { true, 230, "day 6 police beignet timer issue", 1, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet }, - { true, 230, "day 6 police sleep timer issue", 1, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep }, + { true, 51, "interrogation bug", 1, gk1SignatureInterrogationBug, gk1PatchInterrogationBug }, + { true, 212, "day 5 phone freeze", 1, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze }, + { true, 230, "day 6 police beignet timer issue", 1, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet }, + { true, 230, "day 6 police sleep timer issue", 1, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep }, + { true, 808, "day 10 gabriel dress up infinite turning", 1, gk1SignatureDay10GabrielDressUp, gk1PatchDay10GabrielDressUp }, SCI_SIGNATUREENTRY_TERMINATOR }; diff --git a/engines/sci/engine/script_patches.h b/engines/sci/engine/script_patches.h index 645e0946b3..f95806a3f3 100644 --- a/engines/sci/engine/script_patches.h +++ b/engines/sci/engine/script_patches.h @@ -35,13 +35,13 @@ namespace Sci { #define SIG_BYTEMASK 0x00FF #define SIG_MAGICDWORD 0xF000 #define SIG_CODE_ADDTOOFFSET 0xE000 -#define SIG_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | _offset_ +#define SIG_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | (_offset_) #define SIG_CODE_SELECTOR16 0x9000 #define SIG_SELECTOR16(_selectorID_) SIG_CODE_SELECTOR16 | SELECTOR_##_selectorID_ #define SIG_CODE_SELECTOR8 0x8000 #define SIG_SELECTOR8(_selectorID_) SIG_CODE_SELECTOR8 | SELECTOR_##_selectorID_ #define SIG_CODE_UINT16 0x1000 -#define SIG_UINT16(_value_) SIG_CODE_UINT16 | (_value_ & 0xFF), (_value_ >> 8) +#define SIG_UINT16(_value_) SIG_CODE_UINT16 | ((_value_) & 0xFF), ((_value_) >> 8) #define SIG_CODE_BYTE 0x0000 #define PATCH_END SIG_END @@ -49,17 +49,17 @@ namespace Sci { #define PATCH_VALUEMASK SIG_VALUEMASK #define PATCH_BYTEMASK SIG_BYTEMASK #define PATCH_CODE_ADDTOOFFSET SIG_CODE_ADDTOOFFSET -#define PATCH_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | _offset_ +#define PATCH_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | (_offset_) #define PATCH_CODE_GETORIGINALBYTE 0xD000 -#define PATCH_GETORIGINALBYTE(_offset_) PATCH_CODE_GETORIGINALBYTE | _offset_ +#define PATCH_GETORIGINALBYTE(_offset_) PATCH_CODE_GETORIGINALBYTE | (_offset_) #define PATCH_CODE_GETORIGINALBYTEADJUST 0xC000 -#define PATCH_GETORIGINALBYTEADJUST(_offset_, _adjustValue_) PATCH_CODE_GETORIGINALBYTEADJUST | _offset_, (uint16)(_adjustValue_) +#define PATCH_GETORIGINALBYTEADJUST(_offset_, _adjustValue_) PATCH_CODE_GETORIGINALBYTEADJUST | (_offset_), (uint16)(_adjustValue_) #define PATCH_CODE_SELECTOR16 SIG_CODE_SELECTOR16 #define PATCH_SELECTOR16(_selectorID_) SIG_CODE_SELECTOR16 | SELECTOR_##_selectorID_ #define PATCH_CODE_SELECTOR8 SIG_CODE_SELECTOR8 #define PATCH_SELECTOR8(_selectorID_) SIG_CODE_SELECTOR8 | SELECTOR_##_selectorID_ #define PATCH_CODE_UINT16 SIG_CODE_UINT16 -#define PATCH_UINT16(_value_) SIG_CODE_UINT16 | (_value_ & 0xFF), (_value_ >> 8) +#define PATCH_UINT16(_value_) SIG_CODE_UINT16 | ((_value_) & 0xFF), ((_value_) >> 8) #define PATCH_CODE_BYTE SIG_CODE_BYTE // defines maximum scratch area for getting original bytes from unpatched script data diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp index 608452983a..5cf8d6162d 100644 --- a/engines/sci/engine/seg_manager.cpp +++ b/engines/sci/engine/seg_manager.cpp @@ -959,15 +959,11 @@ SciBitmap *SegManager::allocateBitmap(reg_t *addr, const int16 width, const int1 offset = table->allocEntry(); *addr = make_reg(_bitmapSegId, offset); - SciBitmap *bitmap = table->at(offset); + SciBitmap &bitmap = table->at(offset); - if (bitmap == nullptr) { - *addr = NULL_REG; - } - - bitmap->create(width, height, skipColor, displaceX, displaceY, scaledWidth, scaledHeight, paletteSize, remap, gc); + bitmap.create(width, height, skipColor, displaceX, displaceY, scaledWidth, scaledHeight, paletteSize, remap, gc); - return bitmap; + return &bitmap; } SciBitmap *SegManager::lookupBitmap(const reg_t addr) { @@ -979,7 +975,7 @@ SciBitmap *SegManager::lookupBitmap(const reg_t addr) { if (!bitmapTable.isValidEntry(addr.getOffset())) error("Attempt to use invalid entry %04x:%04x as bitmap", PRINT_REG(addr)); - return (bitmapTable.at(addr.getOffset())); + return &(bitmapTable.at(addr.getOffset())); } void SegManager::freeBitmap(const reg_t addr) { diff --git a/engines/sci/engine/seg_manager.h b/engines/sci/engine/seg_manager.h index acebecea97..8ed1c3a143 100644 --- a/engines/sci/engine/seg_manager.h +++ b/engines/sci/engine/seg_manager.h @@ -30,8 +30,7 @@ #include "sci/engine/vm_types.h" #include "sci/engine/segment.h" #ifdef ENABLE_SCI32 -// TODO: Baaaad? -#include "sci/graphics/celobj32.h" +#include "sci/graphics/celobj32.h" // kLowResX, kLowResY #endif namespace Sci { diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h index 7c39050f18..add5f4c57c 100644 --- a/engines/sci/engine/segment.h +++ b/engines/sci/engine/segment.h @@ -200,6 +200,21 @@ struct Node { struct List { reg_t first; reg_t last; + +#ifdef ENABLE_SCI32 + /** + * The next node for each level of recursion during iteration over this list + * by kListEachElementDo. + */ + reg_t nextNodes[10]; + + /** + * The current level of recursion of kListEachElementDo for this list. + */ + int numRecursions; + + List() : numRecursions(0) {} +#endif }; struct Hunk { @@ -212,7 +227,7 @@ template<typename T> struct SegmentObjTable : public SegmentObj { typedef T value_type; struct Entry { - T data; + T *data; int next_free; /* Only used for free entries */ }; enum { HEAPENTRY_INVALID = -1 }; @@ -228,6 +243,14 @@ public: initTable(); } + ~SegmentObjTable() { + for (uint i = 0; i < _table.size(); i++) { + if (isValidEntry(i)) { + freeEntry(i); + } + } + } + void initTable() { entries_used = 0; first_free = HEAPENTRY_INVALID; @@ -241,10 +264,13 @@ public: first_free = _table[oldff].next_free; _table[oldff].next_free = oldff; + assert(_table[oldff].data == nullptr); + _table[oldff].data = new T; return oldff; } else { uint newIdx = _table.size(); _table.push_back(Entry()); + _table.back().data = new T; _table[newIdx].next_free = newIdx; // Tag as 'valid' return newIdx; } @@ -263,6 +289,8 @@ public: ::error("Table::freeEntry: Attempt to release invalid table index %d", idx); _table[idx].next_free = first_free; + delete _table[idx].data; + _table[idx].data = nullptr; first_free = idx; entries_used--; } @@ -277,8 +305,8 @@ public: uint size() const { return _table.size(); } - T &at(uint index) { return _table[index].data; } - const T &at(uint index) const { return _table[index].data; } + T &at(uint index) { return *_table[index].data; } + const T &at(uint index) const { return *_table[index].data; } T &operator[](uint index) { return at(index); } const T &operator[](uint index) const { return at(index); } @@ -338,8 +366,8 @@ struct HunkTable : public SegmentObjTable<Hunk> { } virtual void freeEntry(int idx) { - SegmentObjTable<Hunk>::freeEntry(idx); freeEntryContents(idx); + SegmentObjTable<Hunk>::freeEntry(idx); } virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) { @@ -777,42 +805,14 @@ public: virtual void saveLoadWithSerializer(Common::Serializer &ser); }; -struct BitmapTable : public SegmentObjTable<SciBitmap *> { - BitmapTable() : SegmentObjTable<SciBitmap *>(SEG_TYPE_BITMAP) {} - - virtual ~BitmapTable() { - for (uint i = 0; i < _table.size(); i++) { - if (isValidEntry(i)) { - freeEntryContents(i); - } - } - } - - int allocEntry() { - int offset = SegmentObjTable<SciBitmap *>::allocEntry(); - at(offset) = new SciBitmap; - return offset; - } - - void freeEntryContents(const int offset) { - delete at(offset); - at(offset) = nullptr; - } - - virtual void freeEntry(const int offset) override { - SegmentObjTable<SciBitmap *>::freeEntry(offset); - freeEntryContents(offset); - } - - virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) override { - freeEntry(sub_addr.getOffset()); - } +struct BitmapTable : public SegmentObjTable<SciBitmap> { + BitmapTable() : SegmentObjTable<SciBitmap>(SEG_TYPE_BITMAP) {} SegmentRef dereference(reg_t pointer) { SegmentRef ret; ret.isRaw = true; - ret.maxSize = at(pointer.getOffset())->getRawSize(); - ret.raw = at(pointer.getOffset())->getRawData(); + ret.maxSize = at(pointer.getOffset()).getRawSize(); + ret.raw = at(pointer.getOffset()).getRawData(); return ret; } diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp index 2c85907628..a338beffc9 100644 --- a/engines/sci/engine/state.cpp +++ b/engines/sci/engine/state.cpp @@ -121,9 +121,6 @@ void EngineState::reset(bool isRestoring) { _videoState.reset(); _syncedAudioOptions = false; - - _vmdPalStart = 0; - _vmdPalEnd = 256; } void EngineState::speedThrottler(uint32 neededSleep) { diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h index dd8d76f002..baa912b60e 100644 --- a/engines/sci/engine/state.h +++ b/engines/sci/engine/state.h @@ -203,7 +203,6 @@ public: // TODO: Excise video code from the state manager VideoState _videoState; - uint16 _vmdPalStart, _vmdPalEnd; bool _syncedAudioOptions; /** diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index 3e12084ed6..548fd477bf 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -405,6 +405,21 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { error("[VM] k%s[%x]: no subfunction ID parameter given", kernelCall.name, kernelCallNr); if (argv[0].isPointer()) error("[VM] k%s[%x]: given subfunction ID is actually a pointer", kernelCall.name, kernelCallNr); + +#ifdef ENABLE_SCI32 + // The Windows version of kShowMovie has subops, but the subop number + // is put in the second parameter in SCI2.1+, even though every other + // kcall with subops puts the subop in the first parameter. To allow use + // of the normal subops system, we swap the arguments so the subop + // number is in the usual place. + if (getSciVersion() > SCI_VERSION_2 && + g_sci->getPlatform() == Common::kPlatformWindows && + strcmp(kernelCall.name, "ShowMovie") == 0) { + assert(argc > 1); + SWAP(argv[0], argv[1]); + } +#endif + const uint16 subId = argv[0].toUint16(); // Skip over subfunction-id argc--; diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index 9b3b329418..7aaea0902a 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -88,6 +88,7 @@ const SciWorkaroundEntry arithmeticWorkarounds[] = { { GID_QFG2, 200, 200, 0, "astro", "messages", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_lsi: when getting asked for your name by the astrologer - bug #5152 { GID_QFG3, 780, 999, 0, "", "export 6", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_add: trying to talk to yourself at the top of the giant tree - bug #6692 { GID_QFG4, 710,64941, 0, "RandCycle", "doit", NULL, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when the tentacle appears in the third room of the caves + { GID_TORIN, 51400,64928, 0, "Blink", "init", NULL, 0, { WORKAROUND_FAKE, 1 } }, // op_div: when Lycentia knocks Torin out after he removes her collar SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -329,7 +330,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_PEPPER, -1, 894, 0, "Package", "doVerb", NULL, 3, { WORKAROUND_FAKE, 0 } }, // using the hand on the book in the inventory - bug #5154 { GID_PEPPER, 150, 928, 0, "Narrator", "startText", NULL, 0, { WORKAROUND_FAKE, 0 } }, // happens during the non-interactive demo of Pepper { GID_PQ4, -1, 25, 0, "iconToggle", "select", NULL, 1, { WORKAROUND_FAKE, 0 } }, // when toggling the icon bar to auto-hide or not - { GID_PQSWAT, -1, 64950, 0, "View", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Using the menu in the beginning + { GID_PQSWAT, -1, 64950, 0, NULL, "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Using any menus in-game { GID_QFG1, -1, 210, 0, "Encounter", "init", sig_uninitread_qfg1_1, 0, { WORKAROUND_FAKE, 0 } }, // qfg1/hq1: going to the brigands hideout { GID_QFG1VGA, 16, 16, 0, "lassoFailed", "changeState", NULL, -1, { WORKAROUND_FAKE, 0 } }, // qfg1vga: casting the "fetch" spell in the screen with the flowers, temps 0 and 1 - bug #5309 { GID_QFG1VGA, -1, 210, 0, "Encounter", "init", sig_uninitread_qfg1vga_1, 0, { WORKAROUND_FAKE, 0 } }, // qfg1vga: going to the brigands hideout - bug #5515 @@ -379,7 +380,8 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_SQ6, -1, 64950, -1, "Feature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // called when pressing "Start game" in the main menu, when entering the Orion's Belt bar (room 300), and perhaps other places { GID_SQ6, -1, 64964, 0, "DPath", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // during the game { GID_TORIN, -1, 64017, 0, "oFlags", "clear", NULL, 0, { WORKAROUND_FAKE, 0 } }, // entering Torin's home in the French version - { GID_TORIN, 10000, 64029, 0, "oMessager", "nextMsg", NULL, 3, { WORKAROUND_FAKE, 0 } }, // start of chapter one + { GID_TORIN, 10000, 64029, 0, "oMessager", "nextMsg", NULL, 3, { WORKAROUND_FAKE, 0 } }, // start of chapter one + { GID_TORIN, 20100, 64964, 0, "DPath", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // going down the cliff at the first screen of chapter 2 (washing area) SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -519,6 +521,7 @@ const SciWorkaroundEntry kDoSoundPlay_workarounds[] = { { GID_LSL6HIRES, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument { GID_QFG4, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument { GID_PQ4, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument + { GID_GK1, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // Mac version always passes an extra null argument SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -673,6 +676,14 @@ const SciWorkaroundEntry kPalVarySetPercent_workarounds[] = { }; // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kRandom_workarounds[] = { + { GID_TORIN, 51400,64928, 0, "Blink", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when Lycentia knocks Torin out after he removes her collar + { GID_TORIN, 51400,64928, 0, "Blink", "cycleDone", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when Lycentia knocks Torin out after he removes her collar + SCI_WORKAROUNDENTRY_TERMINATOR +}; + + +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kReadNumber_workarounds[] = { { GID_CNICK_LAURABOW,100, 101, 0, "dominoes.opt", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425 { GID_HOYLE3, 100, 101, 0, "dominoes.opt", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425 @@ -688,6 +699,7 @@ const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[] = { // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kSetCursor_workarounds[] = { { GID_KQ5, -1, 768, 0, "KQCursor", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // CD: gets called with 4 additional "900d" parameters + { GID_MOTHERGOOSEHIRES,0, 0, -1, "MG", "setCursor", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // At the start of the game, an object is passed as the cel number SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -755,6 +767,11 @@ const SciWorkaroundEntry kUnLoad_workarounds[] = { }; // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kStringPutAt_workarounds[] = { + { GID_PHANTASMAGORIA,902, 64918, 0, "Str", "callKernel", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // When starting a new game from after chapter 1, the game tries to save ego's object in a string +}; + +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kScrollWindowAdd_workarounds[] = { { GID_PHANTASMAGORIA, 45, 64907, 0, "ScrollableWindow", "addString", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // ScrollWindow interface passes the last two parameters twice }; diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h index 248d37fc6c..2cccd05475 100644 --- a/engines/sci/engine/workarounds.h +++ b/engines/sci/engine/workarounds.h @@ -91,6 +91,7 @@ extern const SciWorkaroundEntry kMemory_workarounds[]; extern const SciWorkaroundEntry kMoveCursor_workarounds[]; extern const SciWorkaroundEntry kNewWindow_workarounds[]; extern const SciWorkaroundEntry kPalVarySetPercent_workarounds[]; +extern const SciWorkaroundEntry kRandom_workarounds[]; extern const SciWorkaroundEntry kReadNumber_workarounds[]; extern const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[]; extern const SciWorkaroundEntry kSetCursor_workarounds[]; @@ -99,6 +100,7 @@ extern const SciWorkaroundEntry kStrAt_workarounds[]; extern const SciWorkaroundEntry kStrCpy_workarounds[]; extern const SciWorkaroundEntry kStrLen_workarounds[]; extern const SciWorkaroundEntry kUnLoad_workarounds[]; +extern const SciWorkaroundEntry kStringPutAt_workarounds[]; extern const SciWorkaroundEntry kScrollWindowAdd_workarounds[]; extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin); diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp index 4ad2a0cfa3..b267d2ebc2 100644 --- a/engines/sci/event.cpp +++ b/engines/sci/event.cpp @@ -30,6 +30,7 @@ #include "sci/engine/state.h" #include "sci/engine/kernel.h" #ifdef ENABLE_SCI32 +#include "sci/graphics/cursor32.h" #include "sci/graphics/frameout.h" #endif #include "sci/graphics/screen.h" @@ -168,9 +169,17 @@ SciEvent EventManager::getScummVMEvent() { if (getSciVersion() >= SCI_VERSION_2) { const Buffer &screen = g_sci->_gfxFrameout->getCurrentBuffer(); + if (ev.type == Common::EVENT_MOUSEMOVE) { + // This will clamp `mousePos` according to the restricted zone, + // so any cursor or screen item associated with the mouse position + // does not bounce when it hits the edge (or ignore the edge) + g_sci->_gfxCursor32->deviceMoved(mousePos); + } + Common::Point mousePosSci = mousePos; mulru(mousePosSci, Ratio(screen.scriptWidth, screen.screenWidth), Ratio(screen.scriptHeight, screen.screenHeight)); noEvent.mousePosSci = input.mousePosSci = mousePosSci; + } else { #endif g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x); diff --git a/engines/sci/graphics/cache.cpp b/engines/sci/graphics/cache.cpp index fb1f557ad6..9c77f31a14 100644 --- a/engines/sci/graphics/cache.cpp +++ b/engines/sci/graphics/cache.cpp @@ -95,10 +95,20 @@ int16 GfxCache::kernelViewGetCelHeight(GuiResourceId viewId, int16 loopNo, int16 } int16 GfxCache::kernelViewGetLoopCount(GuiResourceId viewId) { +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + return CelObjView::getNumLoops(viewId); + } +#endif return getView(viewId)->getLoopCount(); } int16 GfxCache::kernelViewGetCelCount(GuiResourceId viewId, int16 loopNo) { +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + return CelObjView::getNumCels(viewId, loopNo); + } +#endif return getView(viewId)->getCelCount(loopNo); } diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp index 311684d595..d67a4dc03c 100644 --- a/engines/sci/graphics/celobj32.cpp +++ b/engines/sci/graphics/celobj32.cpp @@ -45,7 +45,7 @@ void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) { } } - int i = 1 - _activeIndex; + const int i = 1 - _activeIndex; _activeIndex = i; CelScalerTable &table = _scaleTables[i]; @@ -65,7 +65,7 @@ void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) { void CelScaler::buildLookupTable(int *table, const Ratio &ratio, const int size) { int value = 0; int remainder = 0; - int num = ratio.getNumerator(); + const int num = ratio.getNumerator(); for (int i = 0; i < size; ++i) { *table++ = value; remainder += ratio.getDenominator(); @@ -164,8 +164,8 @@ struct SCALER_Scale { const byte *_row; READER _reader; int16 _x; - static int16 _valuesX[1024]; - static int16 _valuesY[1024]; + static int16 _valuesX[4096]; + static int16 _valuesY[4096]; SCALER_Scale(const CelObj &celObj, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio scaleX, const Ratio scaleY) : _row(nullptr), @@ -204,7 +204,7 @@ struct SCALER_Scale { if (g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth == kLowResX) { const int16 unscaledX = (scaledPosition.x / scaleX).toInt(); if (FLIP) { - int lastIndex = celObj._width - 1; + const int lastIndex = celObj._width - 1; for (int16 x = targetRect.left; x < targetRect.right; ++x) { _valuesX[x] = lastIndex - (table->valuesX[x] - unscaledX); } @@ -220,7 +220,7 @@ struct SCALER_Scale { } } else { if (FLIP) { - int lastIndex = celObj._width - 1; + const int lastIndex = celObj._width - 1; for (int16 x = 0; x < targetRect.width(); ++x) { _valuesX[targetRect.left + x] = lastIndex - table->valuesX[x]; } @@ -249,9 +249,9 @@ struct SCALER_Scale { }; template<bool FLIP, typename READER> -int16 SCALER_Scale<FLIP, READER>::_valuesX[1024]; +int16 SCALER_Scale<FLIP, READER>::_valuesX[4096]; template<bool FLIP, typename READER> -int16 SCALER_Scale<FLIP, READER>::_valuesY[1024]; +int16 SCALER_Scale<FLIP, READER>::_valuesY[4096]; #pragma mark - #pragma mark CelObj - Resource readers @@ -261,7 +261,7 @@ private: #ifndef NDEBUG const int16 _sourceHeight; #endif - byte *_pixels; + const byte *_pixels; const int16 _sourceWidth; public: @@ -270,7 +270,7 @@ public: _sourceHeight(celObj._height), #endif _sourceWidth(celObj._width) { - byte *resource = celObj.getResPointer(); + const byte *resource = celObj.getResPointer(); _pixels = resource + READ_SCI11ENDIAN_UINT32(resource + celObj._celHeaderOffset + 24); } @@ -282,8 +282,8 @@ public: struct READER_Compressed { private: - byte *_resource; - byte _buffer[1024]; + const byte *const _resource; + byte _buffer[4096]; uint32 _controlOffset; uint32 _dataOffset; uint32 _uncompressedDataOffset; @@ -301,7 +301,7 @@ public: _maxWidth(maxWidth) { assert(maxWidth <= celObj._width); - byte *celHeader = _resource + celObj._celHeaderOffset; + const byte *const celHeader = _resource + celObj._celHeaderOffset; _dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24); _uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28); _controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32); @@ -311,14 +311,14 @@ public: assert(y >= 0 && y < _sourceHeight); if (y != _y) { // compressed data segment for row - byte *row = _resource + _dataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + y * 4); + const byte *row = _resource + _dataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + y * 4); // uncompressed data segment for row - byte *literal = _resource + _uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + _sourceHeight * 4 + y * 4); + const byte *literal = _resource + _uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + _sourceHeight * 4 + y * 4); uint8 length; for (int16 i = 0; i < _maxWidth; i += length) { - byte controlByte = *row++; + const byte controlByte = *row++; length = controlByte; // Run-length encoded @@ -581,7 +581,7 @@ void CelObj::submitPalette() const { int CelObj::_nextCacheId = 1; CelCache *CelObj::_cache = nullptr; -int CelObj::searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const { +int CelObj::searchCache(const CelInfo32 &celInfo, int *const nextInsertIndex) const { *nextInsertIndex = -1; int oldestId = _nextCacheId + 1; int oldestIndex = 0; @@ -791,6 +791,49 @@ void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Rati #pragma mark - #pragma mark CelObjView +int16 CelObjView::getNumLoops(const GuiResourceId viewId) { + const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false); + + if (!resource) { + return 0; + } + + assert(resource->size >= 3); + return resource->data[2]; +} + +int16 CelObjView::getNumCels(const GuiResourceId viewId, const int16 loopNo) { + const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false); + + if (!resource) { + return 0; + } + + const byte *const data = resource->data; + + const uint16 loopCount = data[2]; + if (loopNo >= loopCount || loopNo < 0) { + return 0; + } + + const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data); + const uint8 loopHeaderSize = data[12]; + const uint8 viewHeaderFieldSize = 2; + +#ifndef NDEBUG + const byte *const dataMax = data + resource->size; +#endif + const byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * loopNo); + assert(loopHeader + 3 <= dataMax); + + if ((int8)loopHeader[0] != -1) { + loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * (int8)loopHeader[0]); + assert(loopHeader >= data && loopHeader + 3 <= dataMax); + } + + return loopHeader[2]; +} + CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) { _info.type = kCelTypeView; _info.resourceId = viewId; @@ -801,7 +844,7 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int _transparent = true; int cacheInsertIndex; - int cacheIndex = searchCache(_info, &cacheInsertIndex); + const int cacheIndex = searchCache(_info, &cacheInsertIndex); if (cacheIndex != -1) { CelCacheEntry &entry = (*_cache)[cacheIndex]; const CelObjView *const cachedCelObj = dynamic_cast<CelObjView *>(entry.celObj); @@ -817,20 +860,19 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int // generates view resource metadata for both SCI16 and SCI32 // implementations - Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false); + const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false); // NOTE: SCI2.1/SQ6 just silently returns here. if (!resource) { - warning("View resource %d not loaded", viewId); - return; + error("View resource %d not found", viewId); } - byte *data = resource->data; + const byte *const data = resource->data; _scaledWidth = READ_SCI11ENDIAN_UINT16(data + 14); _scaledHeight = READ_SCI11ENDIAN_UINT16(data + 16); - if (_scaledWidth == 0 || _scaledHeight == 0) { + if (_scaledWidth == 0 && _scaledHeight == 0) { byte sizeFlag = data[5]; if (sizeFlag == 0) { _scaledWidth = kLowResX; @@ -844,7 +886,7 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int } } - uint16 loopCount = data[2]; + const uint16 loopCount = data[2]; if (_info.loopNo >= loopCount) { _info.loopNo = loopCount - 1; } @@ -852,14 +894,14 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int // NOTE: This is the actual check, in the actual location, // from SCI engine. if (loopNo < 0) { - error("Loop is less than 0!"); + error("Loop is less than 0"); } const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data); const uint8 loopHeaderSize = data[12]; const uint8 viewHeaderFieldSize = 2; - byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo); + const byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo); if ((int8)loopHeader[0] != -1) { if (loopHeader[1] == 1) { @@ -874,10 +916,23 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int _info.celNo = celCount - 1; } + // A celNo can be negative and still valid. At least PQ4CD uses this strange + // arrangement to load its high-resolution main menu resource. In PQ4CD, the + // low-resolution menu is at view 23, loop 9, cel 0, and the high-resolution + // menu is at view 2300, loop 0, cel 0. View 2300 is specially crafted to + // have 2 loops, with the second loop having 0 cels. When in high-resolution + // mode, the game scripts only change the view resource ID from 23 to 2300, + // leaving loop 9 and cel 0 the same. The code in CelObjView constructor + // auto-corrects loop 9 to loop 1, and then auto-corrects the cel number + // from 0 to -1, which effectively causes loop 0, cel 0 to be read. + if (_info.celNo < 0 && _info.loopNo == 0) { + error("Cel is less than 0 on loop 0"); + } + _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 8); _celHeaderOffset = READ_SCI11ENDIAN_UINT32(loopHeader + 12) + (data[13] * _info.celNo); - byte *celHeader = data + _celHeaderOffset; + const byte *const celHeader = data + _celHeaderOffset; _width = READ_SCI11ENDIAN_UINT16(celHeader); _height = READ_SCI11ENDIAN_UINT16(celHeader + 2); @@ -906,7 +961,7 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int } bool CelObjView::analyzeUncompressedForRemap() const { - byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24); + const byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24); for (int i = 0; i < _width * _height; ++i) { const byte pixel = pixels[i]; if ( @@ -923,7 +978,7 @@ bool CelObjView::analyzeUncompressedForRemap() const { bool CelObjView::analyzeForRemap() const { READER_Compressed reader(*this, _width); for (int y = 0; y < _height; y++) { - const byte *curRow = reader.getRow(y); + const byte *const curRow = reader.getRow(y); for (int x = 0; x < _width; x++) { const byte pixel = curRow[x]; if ( @@ -948,7 +1003,7 @@ CelObjView *CelObjView::duplicate() const { } byte *CelObjView::getResPointer() const { - const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false); + Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false); if (resource == nullptr) { error("Failed to load view %d from resource manager", _info.resourceId); } @@ -969,7 +1024,7 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { _remap = false; int cacheInsertIndex; - int cacheIndex = searchCache(_info, &cacheInsertIndex); + const int cacheIndex = searchCache(_info, &cacheInsertIndex); if (cacheIndex != -1) { CelCacheEntry &entry = (*_cache)[cacheIndex]; const CelObjPic *const cachedCelObj = dynamic_cast<CelObjPic *>(entry.celObj); @@ -981,15 +1036,14 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { return; } - Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false); + const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false); // NOTE: SCI2.1/SQ6 just silently returns here. if (!resource) { - warning("Pic resource %d not loaded", picId); - return; + error("Pic resource %d not found", picId); } - byte *data = resource->data; + const byte *const data = resource->data; _celCount = data[2]; @@ -1000,7 +1054,7 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { _celHeaderOffset = READ_SCI11ENDIAN_UINT16(data) + (READ_SCI11ENDIAN_UINT16(data + 4) * _info.celNo); _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 6); - byte *celHeader = data + _celHeaderOffset; + const byte *const celHeader = data + _celHeaderOffset; _width = READ_SCI11ENDIAN_UINT16(celHeader); _height = READ_SCI11ENDIAN_UINT16(celHeader + 2); @@ -1012,8 +1066,8 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { _relativePosition.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 38); _relativePosition.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 40); - uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10); - uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12); + const uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10); + const uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12); if (sizeFlag2) { _scaledWidth = sizeFlag1; @@ -1032,7 +1086,7 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { if (celHeader[10] & 128) { // NOTE: This is correct according to SCI2.1/SQ6/DOS; // the engine re-reads the byte value as a word value - uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10); + const uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10); _transparent = flags & 1 ? true : false; _remap = flags & 2 ? true : false; } else { @@ -1047,8 +1101,8 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { } bool CelObjPic::analyzeUncompressedForSkip() const { - byte *resource = getResPointer(); - byte *pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24); + const byte *const resource = getResPointer(); + const byte *const pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24); for (int i = 0; i < _width * _height; ++i) { uint8 pixel = pixels[i]; if (pixel == _transparentColor) { @@ -1060,7 +1114,7 @@ bool CelObjPic::analyzeUncompressedForSkip() const { } void CelObjPic::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) { - Ratio square; + const Ratio square; _drawMirrored = mirrorX; drawTo(target, targetRect, scaledPosition, square, square); } @@ -1088,15 +1142,21 @@ CelObjMem::CelObjMem(const reg_t bitmapObject) { _celHeaderOffset = 0; _transparent = true; - SciBitmap &bitmap = *g_sci->getEngineState()->_segMan->lookupBitmap(bitmapObject); - _width = bitmap.getWidth(); - _height = bitmap.getHeight(); - _displace = bitmap.getDisplace(); - _transparentColor = bitmap.getSkipColor(); - _scaledWidth = bitmap.getScaledWidth(); - _scaledHeight = bitmap.getScaledHeight(); - _hunkPaletteOffset = bitmap.getHunkPaletteOffset(); - _remap = bitmap.getRemap(); + SciBitmap *bitmap = g_sci->getEngineState()->_segMan->lookupBitmap(bitmapObject); + + // NOTE: SSCI did no error checking here at all. + if (!bitmap) { + error("Bitmap %04x:%04x not found", PRINT_REG(bitmapObject)); + } + + _width = bitmap->getWidth(); + _height = bitmap->getHeight(); + _displace = bitmap->getDisplace(); + _transparentColor = bitmap->getSkipColor(); + _scaledWidth = bitmap->getScaledWidth(); + _scaledHeight = bitmap->getScaledHeight(); + _hunkPaletteOffset = bitmap->getHunkPaletteOffset(); + _remap = bitmap->getRemap(); } CelObjMem *CelObjMem::duplicate() const { diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h index eb6ce3a3c9..21e86d03e0 100644 --- a/engines/sci/graphics/celobj32.h +++ b/engines/sci/graphics/celobj32.h @@ -147,7 +147,7 @@ struct CelScalerTable { * the correct column to read from the source bitmap * when drawing a scaled version of the source bitmap. */ - int valuesX[1024]; + int valuesX[4096]; /** * The ratio used to generate the x-values. @@ -159,7 +159,7 @@ struct CelScalerTable { * the correct row to read from a source bitmap when * drawing a scaled version of the source bitmap. */ - int valuesY[1024]; + int valuesY[4096]; /** * The ratio used to generate the y-values. @@ -400,7 +400,7 @@ public: * Reads the pixel at the given coordinates. This method * is valid only for CelObjView and CelObjPic. */ - virtual uint8 readPixel(uint16 x, uint16 y, bool mirrorX) const; + virtual uint8 readPixel(const uint16 x, const uint16 y, const bool mirrorX) const; /** * Submits the palette from this cel to the palette @@ -505,6 +505,9 @@ public: using CelObj::draw; + static int16 getNumLoops(const GuiResourceId viewId); + static int16 getNumCels(const GuiResourceId viewId, const int16 loopNo); + /** * Draws the cel to the target buffer using the * positioning, mirroring, and scaling information from diff --git a/engines/sci/graphics/compare.cpp b/engines/sci/graphics/compare.cpp index 130416ff60..36026a8134 100644 --- a/engines/sci/graphics/compare.cpp +++ b/engines/sci/graphics/compare.cpp @@ -37,7 +37,7 @@ namespace Sci { -GfxCompare::GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster *coordAdjuster) +GfxCompare::GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster16 *coordAdjuster) : _segMan(segMan), _cache(cache), _screen(screen), _coordAdjuster(coordAdjuster) { } diff --git a/engines/sci/graphics/compare.h b/engines/sci/graphics/compare.h index c7005980d0..dd65b90bea 100644 --- a/engines/sci/graphics/compare.h +++ b/engines/sci/graphics/compare.h @@ -34,7 +34,7 @@ class Screen; */ class GfxCompare { public: - GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster *coordAdjuster); + GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster16 *coordAdjuster); ~GfxCompare(); uint16 kernelOnControl(byte screenMask, const Common::Rect &rect); @@ -50,7 +50,7 @@ private: SegManager *_segMan; GfxCache *_cache; GfxScreen *_screen; - GfxCoordAdjuster *_coordAdjuster; + GfxCoordAdjuster16 *_coordAdjuster; uint16 isOnControl(uint16 screenMask, const Common::Rect &rect); diff --git a/engines/sci/graphics/coordadjuster.cpp b/engines/sci/graphics/coordadjuster.cpp index 93dff10382..2f22d191d0 100644 --- a/engines/sci/graphics/coordadjuster.cpp +++ b/engines/sci/graphics/coordadjuster.cpp @@ -32,9 +32,6 @@ namespace Sci { -GfxCoordAdjuster::GfxCoordAdjuster() { -} - GfxCoordAdjuster16::GfxCoordAdjuster16(GfxPorts *ports) : _ports(ports) { } @@ -83,53 +80,4 @@ Common::Rect GfxCoordAdjuster16::pictureGetDisplayArea() { return displayArea; } -#ifdef ENABLE_SCI32 -GfxCoordAdjuster32::GfxCoordAdjuster32(SegManager *segMan) - : _segMan(segMan) { - _scriptsRunningWidth = 0; - _scriptsRunningHeight = 0; -} - -GfxCoordAdjuster32::~GfxCoordAdjuster32() { -} - -void GfxCoordAdjuster32::kernelGlobalToLocal(int16 &x, int16 &y, reg_t planeObject) { - uint16 planeTop = readSelectorValue(_segMan, planeObject, SELECTOR(top)); - uint16 planeLeft = readSelectorValue(_segMan, planeObject, SELECTOR(left)); - - y -= planeTop; - x -= planeLeft; -} -void GfxCoordAdjuster32::kernelLocalToGlobal(int16 &x, int16 &y, reg_t planeObject) { - uint16 planeTop = readSelectorValue(_segMan, planeObject, SELECTOR(top)); - uint16 planeLeft = readSelectorValue(_segMan, planeObject, SELECTOR(left)); - - x += planeLeft; - y += planeTop; -} - -void GfxCoordAdjuster32::setScriptsResolution(uint16 width, uint16 height) { - _scriptsRunningWidth = width; - _scriptsRunningHeight = height; -} - -void GfxCoordAdjuster32::fromDisplayToScript(int16 &y, int16 &x) { - y = ((y * _scriptsRunningHeight) / g_sci->_gfxScreen->getHeight()); - x = ((x * _scriptsRunningWidth) / g_sci->_gfxScreen->getWidth()); -} - -void GfxCoordAdjuster32::fromScriptToDisplay(int16 &y, int16 &x) { - y = ((y * g_sci->_gfxScreen->getHeight()) / _scriptsRunningHeight); - x = ((x * g_sci->_gfxScreen->getWidth()) / _scriptsRunningWidth); -} - -void GfxCoordAdjuster32::pictureSetDisplayArea(Common::Rect displayArea) { - _pictureDisplayArea = displayArea; -} - -Common::Rect GfxCoordAdjuster32::pictureGetDisplayArea() { - return _pictureDisplayArea; -} -#endif - } // End of namespace Sci diff --git a/engines/sci/graphics/coordadjuster.h b/engines/sci/graphics/coordadjuster.h index cb0227fbe4..f7ebd3ec75 100644 --- a/engines/sci/graphics/coordadjuster.h +++ b/engines/sci/graphics/coordadjuster.h @@ -35,27 +35,7 @@ class GfxPorts; * most of the time sci32 doesn't do any coordinate adjustment at all * sci16 does a lot of port adjustment on given coordinates */ -class GfxCoordAdjuster { -public: - GfxCoordAdjuster(); - virtual ~GfxCoordAdjuster() { } - - virtual void kernelGlobalToLocal(int16 &x, int16 &y, reg_t planeObject = NULL_REG) { } - virtual void kernelLocalToGlobal(int16 &x, int16 &y, reg_t planeObject = NULL_REG) { } - - virtual Common::Rect onControl(Common::Rect rect) { return rect; } - virtual void setCursorPos(Common::Point &pos) { } - virtual void moveCursor(Common::Point &pos) { } - - virtual void setScriptsResolution(uint16 width, uint16 height) { } - virtual void fromScriptToDisplay(int16 &y, int16 &x) { } - virtual void fromDisplayToScript(int16 &y, int16 &x) { } - - virtual Common::Rect pictureGetDisplayArea() { return Common::Rect(0, 0); } -private: -}; - -class GfxCoordAdjuster16 : public GfxCoordAdjuster { +class GfxCoordAdjuster16 { public: GfxCoordAdjuster16(GfxPorts *ports); ~GfxCoordAdjuster16(); @@ -73,32 +53,6 @@ private: GfxPorts *_ports; }; -#ifdef ENABLE_SCI32 -class GfxCoordAdjuster32 : public GfxCoordAdjuster { -public: - GfxCoordAdjuster32(SegManager *segMan); - ~GfxCoordAdjuster32(); - - void kernelGlobalToLocal(int16 &x, int16 &y, reg_t planeObject = NULL_REG); - void kernelLocalToGlobal(int16 &x, int16 &y, reg_t planeObject = NULL_REG); - - void setScriptsResolution(uint16 width, uint16 height); - void fromScriptToDisplay(int16 &y, int16 &x); - void fromDisplayToScript(int16 &y, int16 &x); - - void pictureSetDisplayArea(Common::Rect displayArea); - Common::Rect pictureGetDisplayArea(); - -private: - SegManager *_segMan; - - Common::Rect _pictureDisplayArea; - - uint16 _scriptsRunningWidth; - uint16 _scriptsRunningHeight; -}; -#endif - } // End of namespace Sci #endif diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp index f5dd473959..7cf9a574ef 100644 --- a/engines/sci/graphics/cursor.cpp +++ b/engines/sci/graphics/cursor.cpp @@ -80,7 +80,7 @@ GfxCursor::~GfxCursor() { kernelClearZoomZone(); } -void GfxCursor::init(GfxCoordAdjuster *coordAdjuster, EventManager *event) { +void GfxCursor::init(GfxCoordAdjuster16 *coordAdjuster, EventManager *event) { _coordAdjuster = coordAdjuster; _event = event; } @@ -512,32 +512,18 @@ void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNu // automatically. The view resources may exist, but none of the games actually // use them. - if (_macCursorRemap.empty()) { - // QFG1/Freddy/Hoyle4 use a straight viewNum->cursor ID mapping - // KQ6 uses this mapping for its cursors - if (g_sci->getGameId() == GID_KQ6) { - if (viewNum == 990) // Inventory Cursors - viewNum = loopNum * 16 + celNum + 2000; - else if (viewNum == 998) // Regular Cursors - viewNum = celNum + 1000; - else // Unknown cursor, ignored - return; - } - if (g_sci->hasMacIconBar()) - g_sci->_gfxMacIconBar->setInventoryIcon(viewNum); - } else { - // If we do have the list, we'll be using a remap based on what the - // scripts have given us. - for (uint32 i = 0; i < _macCursorRemap.size(); i++) { - if (viewNum == _macCursorRemap[i]) { - viewNum = (i + 1) * 0x100 + loopNum * 0x10 + celNum; - break; - } - - if (i == _macCursorRemap.size()) - error("Unmatched Mac cursor %d", viewNum); - } + // QFG1/Freddy/Hoyle4 use a straight viewNum->cursor ID mapping + // KQ6 uses this mapping for its cursors + if (g_sci->getGameId() == GID_KQ6) { + if (viewNum == 990) // Inventory Cursors + viewNum = loopNum * 16 + celNum + 2000; + else if (viewNum == 998) // Regular Cursors + viewNum = celNum + 1000; + else // Unknown cursor, ignored + return; } + if (g_sci->hasMacIconBar()) + g_sci->_gfxMacIconBar->setInventoryIcon(viewNum); Resource *resource = _resMan->findResource(ResourceId(kResourceTypeCursor, viewNum), false); @@ -568,9 +554,4 @@ void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNu kernelShow(); } -void GfxCursor::setMacCursorRemapList(int cursorCount, reg_t *cursors) { - for (int i = 0; i < cursorCount; i++) - _macCursorRemap.push_back(cursors[i].toUint16()); -} - } // End of namespace Sci diff --git a/engines/sci/graphics/cursor.h b/engines/sci/graphics/cursor.h index 5125469cfe..36518ea5db 100644 --- a/engines/sci/graphics/cursor.h +++ b/engines/sci/graphics/cursor.h @@ -55,7 +55,7 @@ public: GfxCursor(ResourceManager *resMan, GfxPalette *palette, GfxScreen *screen); ~GfxCursor(); - void init(GfxCoordAdjuster *coordAdjuster, EventManager *event); + void init(GfxCoordAdjuster16 *coordAdjuster, EventManager *event); void kernelShow(); void kernelHide(); @@ -95,15 +95,13 @@ public: void kernelSetPos(Common::Point pos); void kernelMoveCursor(Common::Point pos); - void setMacCursorRemapList(int cursorCount, reg_t *cursors); - private: void purgeCache(); ResourceManager *_resMan; GfxScreen *_screen; GfxPalette *_palette; - GfxCoordAdjuster *_coordAdjuster; + GfxCoordAdjuster16 *_coordAdjuster; EventManager *_event; int _upscaledHires; @@ -136,9 +134,6 @@ private: // these instead and replace the game's gold cursors with their silver // equivalents. bool _useSilverSQ4CDCursors; - - // Mac versions of games use a remap list to remap their cursors - Common::Array<uint16> _macCursorRemap; }; } // End of namespace Sci diff --git a/engines/sci/graphics/cursor32.cpp b/engines/sci/graphics/cursor32.cpp new file mode 100644 index 0000000000..88150db6e6 --- /dev/null +++ b/engines/sci/graphics/cursor32.cpp @@ -0,0 +1,448 @@ +/* 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/rational.h" // for Rational, operator* +#include "common/system.h" // for OSystem, g_system +#include "common/memstream.h" +#include "graphics/cursorman.h" // for CursorMan +#include "graphics/maccursor.h" +#include "sci/graphics/celobj32.h" // for CelObjView, CelInfo32, Ratio +#include "sci/graphics/cursor32.h" +#include "sci/graphics/frameout.h" // for GfxFrameout + +namespace Sci { + +GfxCursor32::GfxCursor32() : + _hideCount(0), + _position(0, 0), + _writeToVMAP(false) { + CursorMan.showMouse(false); +} + +void GfxCursor32::init(const Buffer &vmap) { + _vmap = vmap; + _vmapRegion.rect = Common::Rect(_vmap.screenWidth, _vmap.screenHeight); + _vmapRegion.data = (byte *)_vmap.getPixels(); + _restrictedArea = _vmapRegion.rect; +} + +GfxCursor32::~GfxCursor32() { + CursorMan.showMouse(true); + free(_cursor.data); + free(_cursorBack.data); + free(_drawBuff1.data); + free(_drawBuff2.data); + free(_savedVmapRegion.data); +} + +void GfxCursor32::hide() { + if (_hideCount++) { + return; + } + + if (!_cursorBack.rect.isEmpty()) { + drawToHardware(_cursorBack); + } +} + +void GfxCursor32::revealCursor() { + _cursorBack.rect = _cursor.rect; + _cursorBack.rect.clip(_vmapRegion.rect); + if (_cursorBack.rect.isEmpty()) { + return; + } + + readVideo(_cursorBack); + _drawBuff1.rect = _cursor.rect; + copy(_drawBuff1, _cursorBack); + paint(_drawBuff1, _cursor); + drawToHardware(_drawBuff1); +} + +void GfxCursor32::paint(DrawRegion &target, const DrawRegion &source) { + if (source.rect.isEmpty()) { + return; + } + + Common::Rect drawRect(source.rect); + drawRect.clip(target.rect); + if (drawRect.isEmpty()) { + return; + } + + const int16 sourceXOffset = drawRect.left - source.rect.left; + const int16 sourceYOffset = drawRect.top - source.rect.top; + const int16 drawRectWidth = drawRect.width(); + const int16 drawRectHeight = drawRect.height(); + + byte *targetPixel = target.data + ((drawRect.top - target.rect.top) * target.rect.width()) + (drawRect.left - target.rect.left); + const byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset; + const uint8 skipColor = source.skipColor; + + const int16 sourceStride = source.rect.width() - drawRectWidth; + const int16 targetStride = target.rect.width() - drawRectWidth; + + for (int16 y = 0; y < drawRectHeight; ++y) { + for (int16 x = 0; x < drawRectWidth; ++x) { + if (*sourcePixel != skipColor) { + *targetPixel = *sourcePixel; + } + ++targetPixel; + ++sourcePixel; + } + sourcePixel += sourceStride; + targetPixel += targetStride; + } +} + +void GfxCursor32::drawToHardware(const DrawRegion &source) { + Common::Rect drawRect(source.rect); + drawRect.clip(_vmapRegion.rect); + const int16 sourceXOffset = drawRect.left - source.rect.left; + const int16 sourceYOffset = drawRect.top - source.rect.top; + byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset; + + g_system->copyRectToScreen(sourcePixel, source.rect.width(), drawRect.left, drawRect.top, drawRect.width(), drawRect.height()); +} + +void GfxCursor32::unhide() { + if (_hideCount == 0 || --_hideCount) { + return; + } + + _cursor.rect.moveTo(_position.x - _hotSpot.x, _position.y - _hotSpot.y); + revealCursor(); +} + +void GfxCursor32::show() { + if (_hideCount) { + _hideCount = 0; + _cursor.rect.moveTo(_position.x - _hotSpot.x, _position.y - _hotSpot.y); + revealCursor(); + } +} + +void GfxCursor32::setRestrictedArea(const Common::Rect &rect) { + _restrictedArea = rect; + + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + mulru(_restrictedArea, Ratio(screenWidth, scriptWidth), Ratio(screenHeight, scriptHeight), 0); + + if (_position.x < rect.left) { + _position.x = rect.left; + } + if (_position.x >= rect.right) { + _position.x = rect.right - 1; + } + if (_position.y < rect.top) { + _position.y = rect.top; + } + if (_position.y >= rect.bottom) { + _position.y = rect.bottom - 1; + } + + g_system->warpMouse(_position.x, _position.y); +} + +void GfxCursor32::clearRestrictedArea() { + _restrictedArea = _vmapRegion.rect; +} + +void GfxCursor32::setView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) { + hide(); + + _cursorInfo.resourceId = viewId; + _cursorInfo.loopNo = loopNo; + _cursorInfo.celNo = celNo; + + if (_macCursorRemap.empty() && viewId != -1) { + CelObjView view(viewId, loopNo, celNo); + + _hotSpot = view._displace; + _width = view._width; + _height = view._height; + + // SSCI never increased the size of cursors, but some of the cursors + // in early SCI32 games were designed for low-resolution display mode + // and so are kind of hard to pick out when running in high-resolution + // mode. + // To address this, we make some slight adjustments to cursor display + // in these early games: + // GK1: All the cursors are increased in size since they all appear to + // be designed for low-res display. + // PQ4: We only make the cursors bigger if they are above a set + // threshold size because inventory items usually have a + // high-resolution cursor representation. + bool pixelDouble = false; + if (g_sci->_gfxFrameout->_isHiRes && + (g_sci->getGameId() == GID_GK1 || + (g_sci->getGameId() == GID_PQ4 && _width <= 22 && _height <= 22))) { + + _width *= 2; + _height *= 2; + _hotSpot.x *= 2; + _hotSpot.y *= 2; + pixelDouble = true; + } + + _cursor.data = (byte *)realloc(_cursor.data, _width * _height); + _cursor.rect = Common::Rect(_width, _height); + memset(_cursor.data, 255, _width * _height); + _cursor.skipColor = 255; + + Buffer target(_width, _height, _cursor.data); + if (pixelDouble) { + view.draw(target, _cursor.rect, Common::Point(0, 0), false, 2, 2); + } else { + view.draw(target, _cursor.rect, Common::Point(0, 0), false); + } + } else if (!_macCursorRemap.empty() && viewId != -1) { + // Mac cursor handling + GuiResourceId viewNum = viewId; + + // Remap cursor view based on what the scripts have given us. + for (uint32 i = 0; i < _macCursorRemap.size(); i++) { + if (viewNum == _macCursorRemap[i]) { + viewNum = (i + 1) * 0x100 + loopNo * 0x10 + celNo; + break; + } + + if (i == _macCursorRemap.size()) + error("Unmatched Mac cursor %d", viewNum); + } + + _cursorInfo.resourceId = viewNum; + + Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeCursor, viewNum), false); + + if (!resource) { + // The cursor resources often don't exist, this is normal behavior + debug(0, "Mac cursor %d not found", viewNum); + return; + } + Common::MemoryReadStream resStream(resource->data, resource->size); + Graphics::MacCursor *macCursor = new Graphics::MacCursor(); + + if (!macCursor->readFromStream(resStream)) { + warning("Failed to load Mac cursor %d", viewNum); + delete macCursor; + return; + } + + _hotSpot = Common::Point(macCursor->getHotspotX(), macCursor->getHotspotY()); + _width = macCursor->getWidth(); + _height = macCursor->getHeight(); + + _cursor.data = (byte *)realloc(_cursor.data, _width * _height); + memcpy(_cursor.data, macCursor->getSurface(), _width * _height); + _cursor.rect = Common::Rect(_width, _height); + _cursor.skipColor = macCursor->getKeyColor(); + + // The cursor will be drawn on next refresh + delete macCursor; + } else { + _hotSpot = Common::Point(0, 0); + _width = _height = 1; + _cursor.data = (byte *)realloc(_cursor.data, _width * _height); + _cursor.rect = Common::Rect(_width, _height); + *_cursor.data = _cursor.skipColor; + _cursorBack.rect = _cursor.rect; + _cursorBack.rect.clip(_vmapRegion.rect); + if (!_cursorBack.rect.isEmpty()) { + readVideo(_cursorBack); + } + } + + _cursorBack.data = (byte *)realloc(_cursorBack.data, _width * _height); + _drawBuff1.data = (byte *)realloc(_drawBuff1.data, _width * _height); + _drawBuff2.data = (byte *)realloc(_drawBuff2.data, _width * _height * 4); + _savedVmapRegion.data = (byte *)realloc(_savedVmapRegion.data, _width * _height); + + unhide(); +} + +void GfxCursor32::readVideo(DrawRegion &target) { + if (g_sci->_gfxFrameout->_frameNowVisible) { + copy(target, _vmapRegion); + } else { + // NOTE: SSCI would read the background for the cursor directly out of + // video memory here, but as far as can be determined, this does not + // seem to actually be necessary for proper cursor rendering + } +} + +void GfxCursor32::copy(DrawRegion &target, const DrawRegion &source) { + if (source.rect.isEmpty()) { + return; + } + + Common::Rect drawRect(source.rect); + drawRect.clip(target.rect); + if (drawRect.isEmpty()) { + return; + } + + const int16 sourceXOffset = drawRect.left - source.rect.left; + const int16 sourceYOffset = drawRect.top - source.rect.top; + const int16 drawWidth = drawRect.width(); + const int16 drawHeight = drawRect.height(); + + byte *targetPixel = target.data + ((drawRect.top - target.rect.top) * target.rect.width()) + (drawRect.left - target.rect.left); + const byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset; + + const int16 sourceStride = source.rect.width(); + const int16 targetStride = target.rect.width(); + + for (int y = 0; y < drawHeight; ++y) { + memcpy(targetPixel, sourcePixel, drawWidth); + targetPixel += targetStride; + sourcePixel += sourceStride; + } +} + +void GfxCursor32::setPosition(const Common::Point &position) { + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + + _position.x = (position.x * Ratio(screenWidth, scriptWidth)).toInt(); + _position.y = (position.y * Ratio(screenHeight, scriptHeight)).toInt(); + + g_system->warpMouse(_position.x, _position.y); +} + +void GfxCursor32::gonnaPaint(Common::Rect paintRect) { + if (!_hideCount && !_writeToVMAP && !_cursorBack.rect.isEmpty()) { + paintRect.left &= ~3; + paintRect.right |= 3; + if (_cursorBack.rect.intersects(paintRect)) { + _writeToVMAP = true; + } + } +} + +void GfxCursor32::paintStarting() { + if (_writeToVMAP) { + _savedVmapRegion.rect = _cursor.rect; + copy(_savedVmapRegion, _vmapRegion); + paint(_vmapRegion, _cursor); + } +} + +void GfxCursor32::donePainting() { + if (_writeToVMAP) { + copy(_vmapRegion, _savedVmapRegion); + _savedVmapRegion.rect = Common::Rect(); + _writeToVMAP = false; + } + + if (!_hideCount && !_cursorBack.rect.isEmpty()) { + copy(_cursorBack, _vmapRegion); + } +} + +void GfxCursor32::deviceMoved(Common::Point &position) { + if (position.x < _restrictedArea.left) { + position.x = _restrictedArea.left; + } + if (position.x >= _restrictedArea.right) { + position.x = _restrictedArea.right - 1; + } + if (position.y < _restrictedArea.top) { + position.y = _restrictedArea.top; + } + if (position.y >= _restrictedArea.bottom) { + position.y = _restrictedArea.bottom - 1; + } + + _position = position; + + g_system->warpMouse(position.x, position.y); + move(); +} + +void GfxCursor32::move() { + if (_hideCount) { + return; + } + + // Cursor moved onto the screen after being offscreen + _cursor.rect.moveTo(_position.x - _hotSpot.x, _position.y - _hotSpot.y); + if (_cursorBack.rect.isEmpty()) { + revealCursor(); + return; + } + + // Cursor moved offscreen + if (!_cursor.rect.intersects(_vmapRegion.rect)) { + drawToHardware(_cursorBack); + return; + } + + if (!_cursor.rect.intersects(_cursorBack.rect)) { + // Cursor moved to a completely different part of the screen + _drawBuff1.rect = _cursor.rect; + _drawBuff1.rect.clip(_vmapRegion.rect); + readVideo(_drawBuff1); + + _drawBuff2.rect = _drawBuff1.rect; + copy(_drawBuff2, _drawBuff1); + + paint(_drawBuff1, _cursor); + drawToHardware(_drawBuff1); + + drawToHardware(_cursorBack); + + _cursorBack.rect = _cursor.rect; + _cursorBack.rect.clip(_vmapRegion.rect); + copy(_cursorBack, _drawBuff2); + } else { + // Cursor moved, but still overlaps the previous cursor location + Common::Rect mergedRect(_cursorBack.rect); + mergedRect.extend(_cursor.rect); + mergedRect.clip(_vmapRegion.rect); + + _drawBuff2.rect = mergedRect; + readVideo(_drawBuff2); + + copy(_drawBuff2, _cursorBack); + + _cursorBack.rect = _cursor.rect; + _cursorBack.rect.clip(_vmapRegion.rect); + copy(_cursorBack, _drawBuff2); + + paint(_drawBuff2, _cursor); + drawToHardware(_drawBuff2); + } +} + +void GfxCursor32::setMacCursorRemapList(int cursorCount, reg_t *cursors) { + for (int i = 0; i < cursorCount; i++) + _macCursorRemap.push_back(cursors[i].toUint16()); +} + +} // End of namespace Sci diff --git a/engines/sci/graphics/cursor32.h b/engines/sci/graphics/cursor32.h new file mode 100644 index 0000000000..88a75beb7f --- /dev/null +++ b/engines/sci/graphics/cursor32.h @@ -0,0 +1,255 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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. + * + */ + +#ifndef SCI_GRAPHICS_CURSOR32_H +#define SCI_GRAPHICS_CURSOR32_H + +#include "common/rect.h" // for Point, Rect +#include "common/scummsys.h" // for int16, byte, uint8 +#include "common/serializer.h" // for Serializable, Serializer (ptr only) +#include "sci/graphics/celobj32.h" // for CelInfo32 +#include "sci/graphics/helpers.h" // for GuiResourceId + +namespace Sci { + +class GfxCursor32 : Common::Serializable { +public: + GfxCursor32(); + ~GfxCursor32(); + + /** + * Initialises the cursor system with the given + * buffer to use as the output buffer for + * rendering the cursor. + */ + void init(const Buffer &vmap); + + /** + * Called when the hardware mouse moves. + */ + void deviceMoved(Common::Point &position); + + /** + * Called by GfxFrameout once for each show + * rectangle that is going to be drawn to + * hardware. + */ + void gonnaPaint(Common::Rect paintRect); + + /** + * Called by GfxFrameout when the rendering to + * hardware begins. + */ + void paintStarting(); + + /** + * Called by GfxFrameout when the output buffer + * has finished rendering to hardware. + */ + void donePainting(); + + /** + * Hides the cursor. Each call to `hide` will + * increment a hide counter, which must be + * returned to 0 before the cursor will be + * shown again. + */ + void hide(); + + /** + * Shows the cursor, if the hide counter is + * returned to 0. + */ + void unhide(); + + /** + * Shows the cursor regardless of the state of + * the hide counter. + */ + void show(); + + /** + * Sets the view used to render the cursor. + */ + void setView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo); + + /** + * Explicitly sets the position of the cursor, + * in game script coordinates. + */ + void setPosition(const Common::Point &position); + + /** + * Sets the region that the mouse is allowed + * to move within. + */ + void setRestrictedArea(const Common::Rect &rect); + + /** + * Removes restrictions on mouse movement. + */ + void clearRestrictedArea(); + + void setMacCursorRemapList(int cursorCount, reg_t *cursors); + + virtual void saveLoadWithSerializer(Common::Serializer &ser); + +private: + struct DrawRegion { + Common::Rect rect; + byte *data; + uint8 skipColor; + + DrawRegion() : rect(), data(nullptr) {} + }; + + /** + * Information about the current cursor. + * Used to restore cursor when loading a + * savegame. + */ + CelInfo32 _cursorInfo; + + /** + * Content behind the cursor? TODO + */ + DrawRegion _cursorBack; + + /** + * Scratch buffer. + */ + DrawRegion _drawBuff1; + + /** + * Scratch buffer 2. + */ + DrawRegion _drawBuff2; + + /** + * A draw region representing the current + * output buffer. + */ + DrawRegion _vmapRegion; + + /** + * The content behind the cursor in the + * output buffer. + */ + DrawRegion _savedVmapRegion; + + /** + * The cursor bitmap. + */ + DrawRegion _cursor; + + /** + * The width and height of the cursor, + * in screen coordinates. + */ + int16 _width, _height; + + /** + * The output buffer where the cursor is + * rendered. + */ + Buffer _vmap; + + /** + * The number of times the cursor has been + * hidden. + */ + int _hideCount; + + /** + * The rendered position of the cursor, in + * screen coordinates. + */ + Common::Point _position; + + /** + * The position of the cursor hot spot, relative + * to the cursor origin, in screen pixels. + */ + Common::Point _hotSpot; + + /** + * The area within which the cursor is allowed + * to move, in screen pixels. + */ + Common::Rect _restrictedArea; + + /** + * Indicates whether or not the cursor needs to + * be repainted on the output buffer due to a + * change of graphics in the area underneath the + * cursor. + */ + bool _writeToVMAP; + + // Mac versions of games use a remap list to remap their cursors + Common::Array<uint16> _macCursorRemap; + + /** + * Reads data from the output buffer or hardware + * to the given draw region. + */ + void readVideo(DrawRegion &target); + + /** + * Reads data from the output buffer to the + * given draw region. + */ + void readVideoFromVmap(DrawRegion &target); + + /** + * Copies pixel data from the given source to + * the given target. + */ + void copy(DrawRegion &target, const DrawRegion &source); + + /** + * Draws from the given source onto the given + * target, skipping pixels in the source that + * match the `skipColor` property. + */ + void paint(DrawRegion &target, const DrawRegion &source); + + /** + * Draws the cursor to the position it was + * drawn to prior to moving offscreen or being + * hidden by a call to `hide`. + */ + void revealCursor(); + + /** + * Draws the given source to the output buffer. + */ + void drawToHardware(const DrawRegion &source); + + /** + * Renders the cursor at its new location. + */ + void move(); +}; + +} // End of namespace Sci +#endif diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index 1decfa03a9..4e0aa22669 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -29,6 +29,7 @@ #include "common/system.h" #include "common/textconsole.h" #include "engines/engine.h" +#include "engines/util.h" #include "graphics/palette.h" #include "graphics/surface.h" @@ -39,50 +40,52 @@ #include "sci/engine/selector.h" #include "sci/engine/vm.h" #include "sci/graphics/cache.h" -#include "sci/graphics/coordadjuster.h" #include "sci/graphics/compare.h" +#include "sci/graphics/cursor32.h" #include "sci/graphics/font.h" -#include "sci/graphics/screen.h" +#include "sci/graphics/frameout.h" #include "sci/graphics/paint32.h" #include "sci/graphics/palette32.h" #include "sci/graphics/plane32.h" #include "sci/graphics/remap32.h" +#include "sci/graphics/screen.h" #include "sci/graphics/screen_item32.h" #include "sci/graphics/text32.h" #include "sci/graphics/frameout.h" -#include "sci/video/robot_decoder.h" #include "sci/graphics/transitions32.h" +#include "sci/graphics/video32.h" namespace Sci { -GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette, GfxTransitions32 *transitions) : - _isHiRes(false), +GfxFrameout::GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor) : + _isHiRes(ConfMan.getBool("enable_high_resolution_graphics")), _palette(palette), - _resMan(resMan), - _screen(screen), + _cursor(cursor), _segMan(segMan), _transitions(transitions), _benchmarkingFinished(false), _throttleFrameOut(true), _throttleState(0), - // TODO: Stop using _gfxScreen - _currentBuffer(screen->getDisplayWidth(), screen->getDisplayHeight(), nullptr), _remapOccurred(false), _frameNowVisible(false), - _screenRect(screen->getDisplayWidth(), screen->getDisplayHeight()), _overdrawThreshold(0), _palMorphIsOn(false) { - _currentBuffer.setPixels(calloc(1, screen->getDisplayWidth() * screen->getDisplayHeight())); + // QFG4 is the only SCI32 game that doesn't have a high-resolution version + if (g_sci->getGameId() == GID_QFG4) { + _isHiRes = false; + } - // TODO: Make hires detection work uniformly across all SCI engine - // versions (this flag is normally passed by SCI::MakeGraphicsMgr - // to the GraphicsMgr constructor depending upon video configuration, - // so should be handled upstream based on game configuration instead - // of here) - if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _resMan->detectHires()) { - _isHiRes = true; + if (g_sci->getGameId() == GID_PHANTASMAGORIA) { + _currentBuffer = Buffer(630, 450, nullptr); + } else if (_isHiRes) { + _currentBuffer = Buffer(640, 480, nullptr); + } else { + _currentBuffer = Buffer(320, 200, nullptr); } + _currentBuffer.setPixels(calloc(1, _currentBuffer.screenWidth * _currentBuffer.screenHeight)); + _screenRect = Common::Rect(_currentBuffer.screenWidth, _currentBuffer.screenHeight); + initGraphics(_currentBuffer.screenWidth, _currentBuffer.screenHeight, _isHiRes); switch (g_sci->getGameId()) { case GID_HOYLE5: @@ -100,20 +103,6 @@ GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAd // default script width for other games is 320x200 break; } - - // TODO: Nothing in the renderer really uses this. Currently, - // the cursor renderer does, and kLocalToGlobal/kGlobalToLocal - // do, but in the real engine (1) the cursor is handled in - // frameOut, and (2) functions do a very simple lookup of the - // plane and arithmetic with the plane's gameRect. In - // principle, CoordAdjuster could be reused for - // convertGameRectToPlaneRect, but it is not super clear yet - // what the benefit would be to do that. - _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster; - - // TODO: Script resolution is hard-coded per game; - // also this must be set or else the engine will crash - _coordAdjuster->setScriptsResolution(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight); } GfxFrameout::~GfxFrameout() { @@ -497,10 +486,12 @@ void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pi #pragma mark Rendering void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseRect) { -// TODO: Robot -// if (_robot != nullptr) { -// _robot.doRobot(); -// } + RobotDecoder &robotPlayer = g_sci->_video32->getRobotPlayer(); + const bool robotIsActive = robotPlayer.getStatus() != RobotDecoder::kRobotStatusUninitialized; + + if (robotIsActive) { + robotPlayer.doRobot(); + } // NOTE: The original engine allocated these as static arrays of 100 // pointers to ScreenItemList / RectList @@ -538,10 +529,9 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseR drawScreenItemList(screenItemLists[i]); } -// TODO: Robot -// if (_robot != nullptr) { -// _robot->frameAlmostVisible(); -// } + if (robotIsActive) { + robotPlayer.frameAlmostVisible(); + } _palette->updateHardware(!shouldShowBits); @@ -551,10 +541,9 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseR _frameNowVisible = true; -// TODO: Robot -// if (_robot != nullptr) { -// robot->frameNowVisible(); -// } + if (robotIsActive) { + robotPlayer.frameNowVisible(); + } } void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, PlaneShowStyle *showStyle) { @@ -563,7 +552,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, PlaneShowStyle *show int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16(); - Common::Rect rect(_screen->getDisplayWidth(), _screen->getDisplayHeight()); + Common::Rect rect(_currentBuffer.screenWidth, _currentBuffer.screenHeight); _showList.add(rect); showBits(); @@ -1118,6 +1107,10 @@ void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showLi } void GfxFrameout::showBits() { + if (!_showList.size()) { + return; + } + for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) { Common::Rect rounded(**rect); // NOTE: SCI engine used BR-inclusive rects so used slightly @@ -1125,13 +1118,10 @@ void GfxFrameout::showBits() { // was always even. rounded.left &= ~1; rounded.right = (rounded.right + 1) & ~1; - - // TODO: - // _cursor->GonnaPaint(rounded); + _cursor->gonnaPaint(rounded); } - // TODO: - // _cursor->PaintStarting(); + _cursor->paintStarting(); for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) { Common::Rect rounded(**rect); @@ -1153,8 +1143,7 @@ void GfxFrameout::showBits() { g_system->copyRectToScreen(sourceBuffer, _currentBuffer.screenWidth, rounded.left, rounded.top, rounded.width(), rounded.height()); } - // TODO: - // _cursor->DonePainting(); + _cursor->donePainting(); _showList.clear(); } @@ -1270,6 +1259,30 @@ void GfxFrameout::showRect(const Common::Rect &rect) { } } +void GfxFrameout::shakeScreen(int16 numShakes, const ShakeDirection direction) { + if (direction & kShakeHorizontal) { + // Used by QFG4 room 750 + warning("TODO: Horizontal shake not implemented"); + return; + } + + while (numShakes--) { + if (direction & kShakeVertical) { + g_system->setShakePos(_isHiRes ? 8 : 4); + } + + g_system->updateScreen(); + g_sci->getEngineState()->wait(3); + + if (direction & kShakeVertical) { + g_system->setShakePos(0); + } + + g_system->updateScreen(); + g_sci->getEngineState()->wait(3); + } +} + #pragma mark - #pragma mark Mouse cursor @@ -1328,7 +1341,7 @@ bool GfxFrameout::isOnMe(const ScreenItem &screenItem, const Plane &plane, const return true; } -void GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const { +bool GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const { const reg_t planeObject = readSelector(_segMan, screenItemObject, SELECTOR(plane)); Plane *plane = _planes.findByObject(planeObject); @@ -1338,7 +1351,7 @@ void GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const { ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject); if (screenItem == nullptr) { - error("kSetNowSeen: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(screenItemObject), PRINT_REG(planeObject)); + return false; } Common::Rect result = screenItem->getNowSeenRect(*plane); @@ -1346,6 +1359,7 @@ void GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const { writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsTop), result.top); writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsRight), result.right - 1); writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsBottom), result.bottom - 1); + return true; } void GfxFrameout::remapMarkRedraw() { diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h index 42f1d80101..e4caffd9e5 100644 --- a/engines/sci/graphics/frameout.h +++ b/engines/sci/graphics/frameout.h @@ -30,8 +30,7 @@ namespace Sci { typedef Common::Array<DrawList> ScreenItemListList; typedef Common::Array<RectList> EraseListList; -class GfxCoordAdjuster32; -class GfxScreen; +class GfxCursor32; class GfxTransitions32; struct PlaneShowStyle; @@ -41,17 +40,16 @@ struct PlaneShowStyle; */ class GfxFrameout { private: - bool _isHiRes; - GfxCoordAdjuster32 *_coordAdjuster; + GfxCursor32 *_cursor; GfxPalette32 *_palette; - ResourceManager *_resMan; - GfxScreen *_screen; SegManager *_segMan; public: - GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette, GfxTransitions32 *transitions); + GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor); ~GfxFrameout(); + bool _isHiRes; + void clear(); void syncWithScripts(bool addElements); // this is what Game::restore does, only needed when our ScummVM dialogs are patched in void run(); @@ -112,7 +110,7 @@ public: void kernelAddScreenItem(const reg_t object); void kernelUpdateScreenItem(const reg_t object); void kernelDeleteScreenItem(const reg_t object); - void kernelSetNowSeen(const reg_t screenItemObject) const; + bool kernelSetNowSeen(const reg_t screenItemObject) const; #pragma mark - #pragma mark Planes @@ -197,13 +195,6 @@ private: bool _remapOccurred; /** - * Whether or not the data in the current buffer is what - * is visible to the user. During rendering updates, - * this flag is set to false. - */ - bool _frameNowVisible; - - /** * TODO: Document * TODO: Depending upon if the engine ever modifies this * rect, it may be stupid to store it separately instead @@ -310,6 +301,13 @@ private: public: /** + * Whether or not the data in the current buffer is what + * is visible to the user. During rendering updates, + * this flag is set to false. + */ + bool _frameNowVisible; + + /** * Whether palMorphFrameOut should be used instead of * frameOut for rendering. Used by kMorphOn to * explicitly enable palMorphFrameOut for one frame. @@ -365,6 +363,11 @@ public: */ void showRect(const Common::Rect &rect); + /** + * Shakes the screen. + */ + void shakeScreen(const int16 numShakes, const ShakeDirection direction); + #pragma mark - #pragma mark Mouse cursor private: diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h index 3fcc83c5e2..1da3749c90 100644 --- a/engines/sci/graphics/helpers.h +++ b/engines/sci/graphics/helpers.h @@ -40,8 +40,10 @@ namespace Sci { #define MAX_CACHED_FONTS 20 #define MAX_CACHED_VIEWS 50 -#define SCI_SHAKE_DIRECTION_VERTICAL 1 -#define SCI_SHAKE_DIRECTION_HORIZONTAL 2 +enum ShakeDirection { + kShakeVertical = 1, + kShakeHorizontal = 2 +}; typedef int GuiResourceId; // is a resource-number and -1 means no parameter given diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp index 6004e9ce7a..91817d4060 100644 --- a/engines/sci/graphics/paint16.cpp +++ b/engines/sci/graphics/paint16.cpp @@ -41,7 +41,7 @@ namespace Sci { -GfxPaint16::GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio) +GfxPaint16::GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster16 *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio) : _resMan(resMan), _segMan(segMan), _cache(cache), _ports(ports), _coordAdjuster(coordAdjuster), _screen(screen), _palette(palette), _transitions(transitions), _audio(audio), _EGAdrawingVisualize(false) { diff --git a/engines/sci/graphics/paint16.h b/engines/sci/graphics/paint16.h index 317388b2df..6fc9cbbdfc 100644 --- a/engines/sci/graphics/paint16.h +++ b/engines/sci/graphics/paint16.h @@ -36,7 +36,7 @@ class GfxView; */ class GfxPaint16 { public: - GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio); + GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster16 *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio); ~GfxPaint16(); void init(GfxAnimate *animate, GfxText16 *text16); @@ -91,7 +91,7 @@ private: GfxAnimate *_animate; GfxCache *_cache; GfxPorts *_ports; - GfxCoordAdjuster *_coordAdjuster; + GfxCoordAdjuster16 *_coordAdjuster; GfxScreen *_screen; GfxPalette *_palette; GfxText16 *_text16; diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp index 2a98c237b0..c7098bc3e4 100644 --- a/engines/sci/graphics/palette32.cpp +++ b/engines/sci/graphics/palette32.cpp @@ -282,10 +282,16 @@ void GfxPalette32::updateHardware(const bool updateScreen) { bpal[i * 3 + 2] = _currentPalette.colors[i].b; } - // The last color must always be white - bpal[255 * 3 ] = 255; - bpal[255 * 3 + 1] = 255; - bpal[255 * 3 + 2] = 255; + if (g_sci->getPlatform() != Common::kPlatformMacintosh) { + // The last color must always be white + bpal[255 * 3 ] = 255; + bpal[255 * 3 + 1] = 255; + bpal[255 * 3 + 2] = 255; + } else { + bpal[255 * 3 ] = 0; + bpal[255 * 3 + 1] = 0; + bpal[255 * 3 + 2] = 0; + } g_system->getPaletteManager()->setPalette(bpal, 0, 256); if (updateScreen) { diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp index 2eab391afd..0025b24476 100644 --- a/engines/sci/graphics/picture.cpp +++ b/engines/sci/graphics/picture.cpp @@ -35,7 +35,7 @@ namespace Sci { //#define DEBUG_PICTURE_DRAW -GfxPicture::GfxPicture(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize) +GfxPicture::GfxPicture(ResourceManager *resMan, GfxCoordAdjuster16 *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize) : _resMan(resMan), _coordAdjuster(coordAdjuster), _ports(ports), _screen(screen), _palette(palette), _resourceId(resourceId), _EGAdrawingVisualize(EGAdrawingVisualize) { assert(resourceId != -1); initData(resourceId); diff --git a/engines/sci/graphics/picture.h b/engines/sci/graphics/picture.h index 942fa0f107..1be1ae3004 100644 --- a/engines/sci/graphics/picture.h +++ b/engines/sci/graphics/picture.h @@ -38,7 +38,7 @@ enum { class GfxPorts; class GfxScreen; class GfxPalette; -class GfxCoordAdjuster; +class GfxCoordAdjuster16; class ResourceManager; class Resource; @@ -48,7 +48,7 @@ class Resource; */ class GfxPicture { public: - GfxPicture(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize = false); + GfxPicture(ResourceManager *resMan, GfxCoordAdjuster16 *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize = false); ~GfxPicture(); GuiResourceId getResourceId(); @@ -84,7 +84,7 @@ private: void vectorPatternTexturedCircle(Common::Rect box, byte size, byte color, byte prio, byte control, byte texture); ResourceManager *_resMan; - GfxCoordAdjuster *_coordAdjuster; + GfxCoordAdjuster16 *_coordAdjuster; GfxPorts *_ports; GfxScreen *_screen; GfxPalette *_palette; diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp index c977a93817..601ab9f09f 100644 --- a/engines/sci/graphics/screen.cpp +++ b/engines/sci/graphics/screen.cpp @@ -53,12 +53,6 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { if ((g_sci->getPlatform() == Common::kPlatformWindows) || (g_sci->forceHiresGraphics())) { if (g_sci->getGameId() == GID_KQ6) _upscaledHires = GFX_SCREEN_UPSCALED_640x440; -#ifdef ENABLE_SCI32 - if (g_sci->getGameId() == GID_GK1) - _upscaledHires = GFX_SCREEN_UPSCALED_640x480; - if (g_sci->getGameId() == GID_PQ4) - _upscaledHires = GFX_SCREEN_UPSCALED_640x480; -#endif } // Japanese versions of games use hi-res font on upscaled version of the game. @@ -90,28 +84,11 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { } } -#ifdef ENABLE_SCI32 - // GK1 Mac uses a 640x480 resolution too - if (g_sci->getPlatform() == Common::kPlatformMacintosh) { - if (g_sci->getGameId() == GID_GK1) - _upscaledHires = GFX_SCREEN_UPSCALED_640x480; - } -#endif - if (_resMan->detectHires()) { _scriptWidth = 640; _scriptHeight = 480; } -#ifdef ENABLE_SCI32 - // Phantasmagoria 1 effectively outputs 630x450 - // Coordinate translation has to use this resolution as well - if (g_sci->getGameId() == GID_PHANTASMAGORIA) { - _width = 630; - _height = 450; - } -#endif - // if not yet set, set those to script-width/height if (!_width) _width = _scriptWidth; @@ -632,13 +609,13 @@ void GfxScreen::setVerticalShakePos(uint16 shakePos) { void GfxScreen::kernelShakeScreen(uint16 shakeCount, uint16 directions) { while (shakeCount--) { - if (directions & SCI_SHAKE_DIRECTION_VERTICAL) + if (directions & kShakeVertical) setVerticalShakePos(10); // TODO: horizontal shakes g_system->updateScreen(); g_sci->getEngineState()->wait(3); - if (directions & SCI_SHAKE_DIRECTION_VERTICAL) + if (directions & kShakeVertical) setVerticalShakePos(0); g_system->updateScreen(); diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp index 7383dc222e..f4ed269265 100644 --- a/engines/sci/graphics/screen_item32.cpp +++ b/engines/sci/graphics/screen_item32.cpp @@ -178,7 +178,9 @@ void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const boo const uint8 loopCount = view->data[2]; const uint8 loopSize = view->data[12]; - if (_celInfo.loopNo >= loopCount) { + // loopNo is set to be an unsigned integer in SSCI, so if it's a + // negative value, it'll be fixed accordingly + if ((uint16)_celInfo.loopNo >= loopCount) { const int maxLoopNo = loopCount - 1; _celInfo.loopNo = maxLoopNo; writeSelectorValue(segMan, object, SELECTOR(loop), maxLoopNo); @@ -189,8 +191,11 @@ void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const boo if (seekEntry != -1) { loopData = view->data + headerSize + (seekEntry * loopSize); } + + // celNo is set to be an unsigned integer in SSCI, so if it's a + // negative value, it'll be fixed accordingly const uint8 celCount = loopData[2]; - if (_celInfo.celNo >= celCount) { + if ((uint16)_celInfo.celNo >= celCount) { const int maxCelNo = celCount - 1; _celInfo.celNo = maxCelNo; writeSelectorValue(segMan, object, SELECTOR(cel), maxCelNo); diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h index 3d9d5ef3d7..4221c0ea52 100644 --- a/engines/sci/graphics/screen_item32.h +++ b/engines/sci/graphics/screen_item32.h @@ -31,6 +31,7 @@ namespace Sci { enum ScaleSignals32 { kScaleSignalNone = 0, + // TODO: rename to 'manual' kScaleSignalDoScaling32 = 1, // enables scaling when drawing that cel (involves scaleX and scaleY) kScaleSignalUseVanishingPoint = 2, // TODO: Is this actually a thing? I have not seen it and diff --git a/engines/sci/graphics/text16.cpp b/engines/sci/graphics/text16.cpp index b0f2c52791..cb6e614657 100644 --- a/engines/sci/graphics/text16.cpp +++ b/engines/sci/graphics/text16.cpp @@ -633,7 +633,7 @@ reg_t GfxText16::allocAndFillReferenceRectArray() { if (rectCount) { reg_t rectArray; byte *rectArrayPtr = g_sci->getEngineState()->_segMan->allocDynmem(4 * 2 * (rectCount + 1), "text code reference rects", &rectArray); - GfxCoordAdjuster *coordAdjuster = g_sci->_gfxCoordAdjuster; + GfxCoordAdjuster16 *coordAdjuster = g_sci->_gfxCoordAdjuster; for (uint curRect = 0; curRect < rectCount; curRect++) { coordAdjuster->kernelLocalToGlobal(_codeRefRects[curRect].left, _codeRefRects[curRect].top); coordAdjuster->kernelLocalToGlobal(_codeRefRects[curRect].right, _codeRefRects[curRect].bottom); diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp index f81d50946b..11572581ff 100644 --- a/engines/sci/graphics/text32.cpp +++ b/engines/sci/graphics/text32.cpp @@ -211,12 +211,12 @@ void GfxText32::drawChar(const char charIndex) { SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap); byte *pixels = bitmap.getPixels(); - _font->drawToBuffer(charIndex, _drawPosition.y, _drawPosition.x, _foreColor, _dimmed, pixels, _width, _height); - _drawPosition.x += _font->getCharWidth(charIndex); + _font->drawToBuffer((unsigned char)charIndex, _drawPosition.y, _drawPosition.x, _foreColor, _dimmed, pixels, _width, _height); + _drawPosition.x += _font->getCharWidth((unsigned char)charIndex); } uint16 GfxText32::getCharWidth(const char charIndex, const bool doScaling) const { - uint16 width = _font->getCharWidth(charIndex); + uint16 width = _font->getCharWidth((unsigned char)charIndex); if (doScaling) { width = scaleUpWidth(width); } diff --git a/engines/sci/graphics/transitions32.cpp b/engines/sci/graphics/transitions32.cpp index bceb0fa84d..37f608da85 100644 --- a/engines/sci/graphics/transitions32.cpp +++ b/engines/sci/graphics/transitions32.cpp @@ -203,10 +203,6 @@ void GfxTransitions32::kernelSetShowStyle(const uint16 argc, const reg_t planeOb color = 0; } - if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && g_sci->getGameId() != GID_KQ7 && type == 15) || type > 15) { - error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj)); - } - Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj); if (plane == nullptr) { error("Plane %04x:%04x is not present in active planes list", PRINT_REG(planeObj)); diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp index dc2641c92a..8b1d4ef32b 100644 --- a/engines/sci/graphics/video32.cpp +++ b/engines/sci/graphics/video32.cpp @@ -20,20 +20,484 @@ * */ -#include "audio/mixer.h" -#include "common/config-manager.h" -#include "sci/console.h" -#include "sci/event.h" -#include "sci/graphics/cursor.h" -#include "sci/graphics/frameout.h" -#include "sci/graphics/palette32.h" -#include "sci/graphics/text32.h" +#include "audio/mixer.h" // for Audio::Mixer::kSFXSoundType +#include "common/config-manager.h" // for ConfMan +#include "common/textconsole.h" // for warning, error +#include "common/util.h" // for ARRAYSIZE +#include "common/system.h" // for g_system +#include "engine.h" // for Engine, g_engine +#include "engines/util.h" // for initGraphics +#include "sci/console.h" // for Console +#include "sci/engine/state.h" // for EngineState +#include "sci/engine/vm_types.h" // for reg_t +#include "sci/event.h" // for SciEvent, EventManager, SCI_... +#include "sci/graphics/celobj32.h" // for CelInfo32, ::kLowResX, ::kLo... +#include "sci/graphics/cursor32.h" // for GfxCursor32 +#include "sci/graphics/frameout.h" // for GfxFrameout +#include "sci/graphics/helpers.h" // for Color, Palette +#include "sci/graphics/palette32.h" // for GfxPalette32 +#include "sci/graphics/plane32.h" // for Plane, PlanePictureCodes::kP... +#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem, Scale... +#include "sci/sci.h" // for SciEngine, g_sci, getSciVersion #include "sci/graphics/video32.h" -#include "sci/sci.h" -#include "video/coktel_decoder.h" +#include "sci/video/seq_decoder.h" // for SEQDecoder +#include "video/avi_decoder.h" // for AVIDecoder +#include "video/coktel_decoder.h" // for AdvancedVMDDecoder +namespace Graphics { struct Surface; } namespace Sci { +#pragma mark SEQPlayer + +SEQPlayer::SEQPlayer(SegManager *segMan) : + _segMan(segMan), + _decoder(nullptr), + _plane(nullptr), + _screenItem(nullptr) {} + +void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y) { + delete _decoder; + _decoder = new SEQDecoder(numTicks); + _decoder->loadFile(fileName); + + // NOTE: In the original engine, video was output directly to the hardware, + // bypassing the game's rendering engine. Instead of doing this, we use a + // mechanism that is very similar to that used by the VMD player, which + // allows the SEQ to be drawn into a bitmap ScreenItem and displayed using + // the normal graphics system. + _segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, kLowResX, kLowResY, 0, false, false); + + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = _bitmap; + + _plane = new Plane(Common::Rect(kLowResX, kLowResY), kPlanePicColored); + g_sci->_gfxFrameout->addPlane(*_plane); + + // Normally we would use the x, y coordinates passed into the play function + // to position the screen item, but because the video frame bitmap is + // drawn in low-resolution coordinates, it gets automatically scaled up by + // the engine (pixel doubling with aspect ratio correction). As a result, + // the animation does not need the extra offsets from the game in order to + // be correctly positioned in the middle of the window, so we ignore them. + _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(0, 0), ScaleInfo()); + g_sci->_gfxFrameout->addScreenItem(*_screenItem); + g_sci->_gfxFrameout->frameOut(true); + _decoder->start(); + + while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) { + renderFrame(); + g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); + g_sci->getEngineState()->_throttleTrigger = true; + } + + _segMan->freeBitmap(_screenItem->_celInfo.bitmap); + g_sci->_gfxFrameout->deletePlane(*_plane); + g_sci->_gfxFrameout->frameOut(true); + _screenItem = nullptr; + _plane = nullptr; +} + +void SEQPlayer::renderFrame() const { + const Graphics::Surface *surface = _decoder->decodeNextFrame(); + + SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap); + bitmap.getBuffer().copyRectToSurface(*surface, 0, 0, Common::Rect(surface->w, surface->h)); + + const bool dirtyPalette = _decoder->hasDirtyPalette(); + if (dirtyPalette) { + Palette palette; + const byte *rawPalette = _decoder->getPalette(); + for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) { + palette.colors[i].r = *rawPalette++; + palette.colors[i].g = *rawPalette++; + palette.colors[i].b = *rawPalette++; + palette.colors[i].used = true; + } + + g_sci->_gfxPalette32->submit(palette); + } + + g_sci->_gfxFrameout->updateScreenItem(*_screenItem); + g_sci->getSciDebugger()->onFrame(); + g_sci->_gfxFrameout->frameOut(true); +} + +#pragma mark - +#pragma mark AVIPlayer + +AVIPlayer::AVIPlayer(SegManager *segMan, EventManager *eventMan) : + _segMan(segMan), + _eventMan(eventMan), + _decoder(new Video::AVIDecoder(Audio::Mixer::kSFXSoundType)), + _scaleBuffer(nullptr), + _plane(nullptr), + _screenItem(nullptr), + _status(kAVINotOpen) {} + +AVIPlayer::~AVIPlayer() { + close(); + delete _decoder; +} + +AVIPlayer::IOStatus AVIPlayer::open(const Common::String &fileName) { + if (_status != kAVINotOpen) { + close(); + } + + if (!_decoder->loadFile(fileName)) { + return kIOFileNotFound; + } + + _status = kAVIOpen; + return kIOSuccess; +} + +AVIPlayer::IOStatus AVIPlayer::init1x(const int16 x, const int16 y, int16 width, int16 height) { + if (_status == kAVINotOpen) { + return kIOFileNotFound; + } + + _pixelDouble = false; + + if (!width || !height) { + width = _decoder->getWidth(); + height = _decoder->getHeight(); + } else if (getSciVersion() == SCI_VERSION_2_1_EARLY && g_sci->getGameId() == GID_KQ7) { + // KQ7 1.51 provides an explicit width and height when it wants scaling, + // though the width and height it provides are not scaled + _pixelDouble = true; + width *= 2; + height *= 2; + } + + // QFG4CD gives non-multiple-of-2 values for width and height, + // which would normally be OK except the source video is a pixel bigger + // in each dimension + width = (width + 1) & ~1; + height = (height + 1) & ~1; + + _drawRect.left = x; + _drawRect.top = y; + _drawRect.right = x + width; + _drawRect.bottom = y + height; + + // SCI2.1mid uses init2x to draw a pixel-doubled AVI, but SCI2 has only the + // one play routine which automatically pixel-doubles in hi-res mode + if (getSciVersion() == SCI_VERSION_2) { + // This is somewhat of a hack; credits.avi from GK1 is not + // rendered correctly in SSCI because it is a 640x480 video, but the + // game script gives the wrong dimensions. Since this is the only + // high-resolution AVI ever used, just set the draw rectangle to draw + // the entire screen + if (_decoder->getWidth() > 320) { + _drawRect.left = 0; + _drawRect.top = 0; + _drawRect.right = 320; + _drawRect.bottom = 200; + } + + // In hi-res mode, video will be pixel doubled, so the origin (which + // corresponds to the correct position without pixel doubling) needs to + // be corrected + if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() <= 320) { + _drawRect.left /= 2; + _drawRect.top /= 2; + } + } + + init(); + + return kIOSuccess; +} + +AVIPlayer::IOStatus AVIPlayer::init2x(const int16 x, const int16 y) { + if (_status == kAVINotOpen) { + return kIOFileNotFound; + } + + _drawRect.left = x; + _drawRect.top = y; + _drawRect.right = x + _decoder->getWidth() * 2; + _drawRect.bottom = y + _decoder->getHeight() * 2; + + _pixelDouble = true; + init(); + + return kIOSuccess; +} + +void AVIPlayer::init() { + int16 xRes; + int16 yRes; + + bool useScreenDimensions = false; + if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() > 320) { + useScreenDimensions = true; + } + + // KQ7 1.51 gives video position in screen coordinates, not game + // coordinates, because in SSCI they are passed to Video for Windows, which + // renders as an overlay on the game video. Because we put the video into a + // ScreenItem instead of rendering directly to the hardware surface, the + // coordinates need to be converted to game script coordinates + if (g_sci->getGameId() == GID_KQ7 && getSciVersion() == SCI_VERSION_2_1_EARLY) { + useScreenDimensions = !_pixelDouble; + // This y-translation is arbitrary, based on what roughly centers the + // videos in the game window + _drawRect.translate(-_drawRect.left / 2, -_drawRect.top * 2 / 3); + } + + if (useScreenDimensions) { + xRes = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + yRes = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + } else { + xRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + yRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + } + + _plane = new Plane(_drawRect); + g_sci->_gfxFrameout->addPlane(*_plane); + + if (_decoder->getPixelFormat().bytesPerPixel == 1) { + _segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, xRes, yRes, 0, false, false); + + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = _bitmap; + + _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(_drawRect.left, _drawRect.top), ScaleInfo()); + g_sci->_gfxFrameout->addScreenItem(*_screenItem); + g_sci->_gfxFrameout->frameOut(true); + } else { + // Attempting to draw a palettized cursor into a 24bpp surface will + // cause memory corruption, so hide the cursor in this mode (SCI did not + // have a 24bpp mode but just directed VFW to display videos instead) + g_sci->_gfxCursor32->hide(); + + const Buffer ¤tBuffer = g_sci->_gfxFrameout->getCurrentBuffer(); + const Graphics::PixelFormat format = _decoder->getPixelFormat(); + initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, g_sci->_gfxFrameout->_isHiRes, &format); + + if (_pixelDouble) { + const int16 width = _drawRect.width(); + const int16 height = _drawRect.height(); + _scaleBuffer = calloc(1, width * height * format.bytesPerPixel); + } + } +} + +AVIPlayer::IOStatus AVIPlayer::play(const int16 from, const int16 to, const int16, const bool async) { + if (_status == kAVINotOpen) { + return kIOFileNotFound; + } + + if (from >= 0 && to > 0 && from <= to) { + _decoder->seekToFrame(from); + _decoder->setEndFrame(to); + } + + if (!async) { + renderVideo(); + } else if (getSciVersion() == SCI_VERSION_2_1_EARLY) { + playUntilEvent((EventFlags)(kEventFlagEnd | kEventFlagEscapeKey)); + } else { + _status = kAVIPlaying; + } + + return kIOSuccess; +} + +void AVIPlayer::renderVideo() const { + _decoder->start(); + while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) { + g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); + g_sci->getEngineState()->_throttleTrigger = true; + if (_decoder->needsUpdate()) { + renderFrame(); + } + } +} + +AVIPlayer::IOStatus AVIPlayer::close() { + if (_status == kAVINotOpen) { + return kIOSuccess; + } + + free(_scaleBuffer); + _scaleBuffer = nullptr; + + if (_decoder->getPixelFormat().bytesPerPixel != 1) { + const bool isHiRes = g_sci->_gfxFrameout->_isHiRes; + const Buffer ¤tBuffer = g_sci->_gfxFrameout->getCurrentBuffer(); + const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8(); + initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, isHiRes, &format); + g_sci->_gfxCursor32->unhide(); + } + + _decoder->close(); + _status = kAVINotOpen; + g_sci->_gfxFrameout->deletePlane(*_plane); + _plane = nullptr; + _screenItem = nullptr; + return kIOSuccess; +} + +AVIPlayer::IOStatus AVIPlayer::cue(const uint16 frameNo) { + if (!_decoder->seekToFrame(frameNo)) { + return kIOSeekFailed; + } + + _status = kAVIPaused; + return kIOSuccess; +} + +uint16 AVIPlayer::getDuration() const { + if (_status == kAVINotOpen) { + return 0; + } + + return _decoder->getFrameCount(); +} + +void AVIPlayer::renderFrame() const { + const Graphics::Surface *surface = _decoder->decodeNextFrame(); + + if (surface->format.bytesPerPixel == 1) { + SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap); + if (surface->w > bitmap.getWidth() || surface->h > bitmap.getHeight()) { + warning("Attempted to draw a video frame larger than the destination bitmap"); + return; + } + + // KQ7 1.51 encodes videos with palette entry 0 as white, which makes + // the area around the video turn white too, since it is coded to use + // palette entry 0. This happens to work in the original game because + // the video is rendered by VfW, not in the engine itself. To fix this, + // we just modify the incoming pixel data from the video so if a pixel + // is using entry 0, we change it to use entry 255, which is guaranteed + // to always be white + if (getSciVersion() == SCI_VERSION_2_1_EARLY && g_sci->getGameId() == GID_KQ7) { + uint8 *target = bitmap.getPixels(); + const uint8 *source = (const uint8 *)surface->getPixels(); + const uint8 *end = (const uint8 *)surface->getPixels() + surface->w * surface->h; + + while (source != end) { + uint8 value = *source++; + *target++ = value == 0 ? 255 : value; + } + } else { + bitmap.getBuffer().copyRectToSurface(*surface, 0, 0, Common::Rect(surface->w, surface->h)); + } + + const bool dirtyPalette = _decoder->hasDirtyPalette(); + if (dirtyPalette) { + Palette palette; + const byte *rawPalette = _decoder->getPalette(); + for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) { + palette.colors[i].r = *rawPalette++; + palette.colors[i].g = *rawPalette++; + palette.colors[i].b = *rawPalette++; + palette.colors[i].used = true; + } + + // Prevent KQ7 1.51 from setting entry 0 to white + palette.colors[0].used = false; + + g_sci->_gfxPalette32->submit(palette); + } + + g_sci->_gfxFrameout->updateScreenItem(*_screenItem); + g_sci->getSciDebugger()->onFrame(); + g_sci->_gfxFrameout->frameOut(true); + } else { + assert(surface->format.bytesPerPixel == 4); + + Common::Rect drawRect(_drawRect); + + if (_pixelDouble) { + const uint32 *source = (const uint32 *)surface->getPixels(); + uint32 *target = (uint32 *)_scaleBuffer; + // target pitch here is in uint32s, not bytes + const uint16 pitch = surface->pitch / 2; + for (int y = 0; y < surface->h; ++y) { + for (int x = 0; x < surface->w; ++x) { + const uint32 value = *source++; + + target[0] = value; + target[1] = value; + target[pitch] = value; + target[pitch + 1] = value; + target += 2; + } + target += pitch; + } + + g_system->copyRectToScreen(_scaleBuffer, surface->pitch * 2, _drawRect.left, _drawRect.top, _drawRect.width(), _drawRect.height()); + } else { + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + mulinc(drawRect, Ratio(screenWidth, scriptWidth), Ratio(screenHeight, scriptHeight)); + + g_system->copyRectToScreen(surface->getPixels(), surface->pitch, drawRect.left, drawRect.top, surface->w, surface->h); + } + } +} + +AVIPlayer::EventFlags AVIPlayer::playUntilEvent(EventFlags flags) { + _decoder->start(); + + EventFlags stopFlag = kEventFlagNone; + while (!g_engine->shouldQuit()) { + if (_decoder->endOfVideo()) { + stopFlag = kEventFlagEnd; + break; + } + + g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); + g_sci->getEngineState()->_throttleTrigger = true; + if (_decoder->needsUpdate()) { + renderFrame(); + } + + SciEvent event = _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK); + if ((flags & kEventFlagMouseDown) && event.type == SCI_EVENT_MOUSE_PRESS) { + stopFlag = kEventFlagMouseDown; + break; + } + + event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK); + if ((flags & kEventFlagEscapeKey) && event.type == SCI_EVENT_KEYBOARD) { + bool stop = false; + while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)), + event.type != SCI_EVENT_NONE) { + if (event.character == SCI_KEY_ESC) { + stop = true; + break; + } + } + + if (stop) { + stopFlag = kEventFlagEscapeKey; + break; + } + } + + // TODO: Hot rectangles + if ((flags & kEventFlagHotRectangle) /* && event.type == SCI_EVENT_HOT_RECTANGLE */) { + warning("Hot rectangles not implemented in VMD player"); + stopFlag = kEventFlagHotRectangle; + break; + } + } + + return stopFlag; +} + +#pragma mark - #pragma mark VMDPlayer VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) : @@ -117,7 +581,7 @@ VMDPlayer::IOStatus VMDPlayer::close() { if (!_planeIsOwned && _screenItem != nullptr) { g_sci->_gfxFrameout->deleteScreenItem(*_screenItem); - g_sci->getEngineState()->_segMan->freeBitmap(_screenItem->_celInfo.bitmap); + _segMan->freeBitmap(_screenItem->_celInfo.bitmap); _screenItem = nullptr; } else if (_plane != nullptr) { g_sci->_gfxFrameout->deletePlane(*_plane); @@ -140,7 +604,7 @@ VMDPlayer::IOStatus VMDPlayer::close() { } if (!_showCursor) { - g_sci->_gfxCursor->kernelShow(); + g_sci->_gfxCursor32->unhide(); } _lastYieldedFrameNo = 0; @@ -149,6 +613,22 @@ VMDPlayer::IOStatus VMDPlayer::close() { return kIOSuccess; } +VMDPlayer::VMDStatus VMDPlayer::getStatus() const { + if (!_isOpen) { + return kVMDNotOpen; + } + if (_decoder->isPaused()) { + return kVMDPaused; + } + if (_decoder->isPlaying()) { + return kVMDPlaying; + } + if (_decoder->endOfVideo()) { + return kVMDFinished; + } + return kVMDOpen; +} + VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval) { assert(lastFrameNo >= -1); @@ -201,7 +681,7 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { _isInitialized = true; if (!_showCursor) { - g_sci->_gfxCursor->kernelHide(); + g_sci->_gfxCursor32->hide(); } Common::Rect vmdRect(_x, diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h index 7033f7c647..75b8fb2d21 100644 --- a/engines/sci/graphics/video32.h +++ b/engines/sci/graphics/video32.h @@ -23,16 +23,216 @@ #ifndef SCI_GRAPHICS_VIDEO32_H #define SCI_GRAPHICS_VIDEO32_H -namespace Video { class AdvancedVMDDecoder; } +#include "common/rect.h" // for Rect +#include "common/scummsys.h" // for int16, uint8, uint16, int32 +#include "common/str.h" // for String +#include "sci/engine/vm_types.h" // for reg_t +#include "sci/video/robot_decoder.h" // for RobotDecoder + +namespace Video { +class AdvancedVMDDecoder; +class AVIDecoder; +} namespace Sci { +class EventManager; class Plane; class ScreenItem; class SegManager; +class SEQDecoder; +struct Palette; +#pragma mark SEQPlayer + +/** + * SEQPlayer is used to play SEQ animations. + * Used by DOS versions of GK1 and QFG4CD. + */ +class SEQPlayer { +public: + SEQPlayer(SegManager *segMan); + + /** + * Plays a SEQ animation with the given + * file name, with each frame being displayed + * for `numTicks` ticks. + */ + void play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y); + +private: + SegManager *_segMan; + SEQDecoder *_decoder; + + /** + * The plane where the SEQ will be drawn. + */ + Plane *_plane; + + /** + * The screen item representing the SEQ surface. + */ + ScreenItem *_screenItem; + + /** + * The bitmap used to render video output. + */ + reg_t _bitmap; + + /** + * Renders a single frame of video. + */ + void renderFrame() const; +}; + +#pragma mark - +#pragma mark AVIPlayer + +/** + * AVIPlayer is used to play AVI videos. Used by + * Windows versions of GK1CD, KQ7, and QFG4CD. + */ +class AVIPlayer { +public: + enum IOStatus { + kIOSuccess = 0, + kIOFileNotFound = 2, + kIOSeekFailed = 12 + }; + + enum AVIStatus { + kAVINotOpen = 0, + kAVIOpen = 1, + kAVIPlaying = 2, + kAVIPaused = 3 + }; + + enum EventFlags { + kEventFlagNone = 0, + kEventFlagEnd = 1, + kEventFlagEscapeKey = 2, + kEventFlagMouseDown = 4, + kEventFlagHotRectangle = 8 + }; + + AVIPlayer(SegManager *segMan, EventManager *eventMan); + ~AVIPlayer(); + + /** + * Opens a stream to an AVI resource. + */ + IOStatus open(const Common::String &fileName); + + /** + * Initializes the AVI rendering parameters for the + * current AVI. This must be called after `open`. + */ + IOStatus init1x(const int16 x, const int16 y, const int16 width, const int16 height); + + /** + * Initializes the AVI rendering parameters for the + * current AVI, in pixel-doubling mode. This must + * be called after `open`. + */ + IOStatus init2x(const int16 x, const int16 y); + + /** + * Begins playback of the current AVI. + */ + IOStatus play(const int16 from, const int16 to, const int16 showStyle, const bool cue); + + /** + * Stops playback and closes the currently open AVI stream. + */ + IOStatus close(); + + /** + * Seeks the currently open AVI stream to the given frame. + */ + IOStatus cue(const uint16 frameNo); + + /** + * Returns the duration of the current video. + */ + uint16 getDuration() const; + + /** + * Plays the AVI until an event occurs (e.g. user + * presses escape, clicks, etc.). + */ + EventFlags playUntilEvent(const EventFlags flags); + +private: + typedef Common::HashMap<uint16, AVIStatus> StatusMap; + + SegManager *_segMan; + EventManager *_eventMan; + Video::AVIDecoder *_decoder; + + /** + * Playback status of the player. + */ + AVIStatus _status; + + /** + * The plane where the AVI will be drawn. + */ + Plane *_plane; + + /** + * The screen item representing the AVI surface, + * in 8bpp mode. In 24bpp mode, video is drawn + * directly to the screen. + */ + ScreenItem *_screenItem; + + /** + * The bitmap used to render video output in + * 8bpp mode. + */ + reg_t _bitmap; + + /** + * The rectangle where the video will be drawn, + * in game script coordinates. + */ + Common::Rect _drawRect; + + /** + * The scale buffer for pixel-doubled videos + * drawn in 24bpp mode. + */ + void *_scaleBuffer; + + /** + * In SCI2.1, whether or not the video should + * be pixel doubled for playback. + */ + bool _pixelDouble; + + /** + * Performs common initialisation for both + * scaled and unscaled videos. + */ + void init(); + + /** + * Renders video without event input until the + * video is complete. + */ + void renderVideo() const; + + /** + * Renders a single frame of video. + */ + void renderFrame() const; +}; + +#pragma mark - #pragma mark VMDPlayer /** * VMDPlayer is used to play VMD videos. + * Used by Phant1, GK2, PQ:SWAT, Shivers, SQ6, + * Torin, and Lighthouse. */ class VMDPlayer { public: @@ -68,6 +268,15 @@ public: kEventFlagReverse = 0x80 }; + enum VMDStatus { + kVMDNotOpen = 0, + kVMDOpen = 1, + kVMDPlaying = 2, + kVMDPaused = 3, + kVMDStopped = 4, + kVMDFinished = 5 + }; + VMDPlayer(SegManager *segMan, EventManager *eventMan); ~VMDPlayer(); @@ -95,6 +304,11 @@ public: */ IOStatus close(); + /** + * Gets the playback status of the VMD player. + */ + VMDStatus getStatus() const; + // NOTE: Was WaitForEvent in SSCI EventFlags kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval); @@ -297,15 +511,28 @@ private: bool _showCursor; }; +/** + * Video32 provides facilities for playing back + * video in SCI engine. + */ class Video32 { public: Video32(SegManager *segMan, EventManager *eventMan) : - _VMDPlayer(segMan, eventMan) {} + _SEQPlayer(segMan), + _AVIPlayer(segMan, eventMan), + _VMDPlayer(segMan, eventMan), + _robotPlayer(segMan) {} + SEQPlayer &getSEQPlayer() { return _SEQPlayer; } + AVIPlayer &getAVIPlayer() { return _AVIPlayer; } VMDPlayer &getVMDPlayer() { return _VMDPlayer; } + RobotDecoder &getRobotPlayer() { return _robotPlayer; } private: + SEQPlayer _SEQPlayer; + AVIPlayer _AVIPlayer; VMDPlayer _VMDPlayer; + RobotDecoder _robotPlayer; }; } // End of namespace Sci diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp index 1939e66179..0c09fcbb30 100644 --- a/engines/sci/graphics/view.cpp +++ b/engines/sci/graphics/view.cpp @@ -351,18 +351,6 @@ void GfxView::initData(GuiResourceId resourceId) { celData += celSize; } } -#ifdef ENABLE_SCI32 - // adjust width/height returned to scripts - if (_sci2ScaleRes != SCI_VIEW_NATIVERES_NONE) { - for (loopNo = 0; loopNo < _loopCount; loopNo++) - for (celNo = 0; celNo < _loop[loopNo].celCount; celNo++) - _screen->adjustBackUpscaledCoordinates(_loop[loopNo].cel[celNo].scriptWidth, _loop[loopNo].cel[celNo].scriptHeight, _sci2ScaleRes); - } else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE)) { - for (loopNo = 0; loopNo < _loopCount; loopNo++) - for (celNo = 0; celNo < _loop[loopNo].celCount; celNo++) - _coordAdjuster->fromDisplayToScript(_loop[loopNo].cel[celNo].scriptHeight, _loop[loopNo].cel[celNo].scriptWidth); - } -#endif break; default: diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h index 96b48c0477..5e422468b5 100644 --- a/engines/sci/graphics/view.h +++ b/engines/sci/graphics/view.h @@ -92,7 +92,7 @@ private: void unditherBitmap(byte *bitmap, int16 width, int16 height, byte clearKey); ResourceManager *_resMan; - GfxCoordAdjuster *_coordAdjuster; + GfxCoordAdjuster16 *_coordAdjuster; GfxScreen *_screen; GfxPalette *_palette; diff --git a/engines/sci/module.mk b/engines/sci/module.mk index 18d97ea57e..eb2c6a148b 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -93,6 +93,7 @@ MODULE_OBJS += \ graphics/text32.o \ graphics/transitions32.o \ graphics/video32.o \ + graphics/cursor32.o \ sound/audio32.o \ sound/decoders/sol.o \ video/robot_decoder.o diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp index 48278e35a7..2e69932e49 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -573,6 +573,9 @@ Resource *ResourceManager::testResource(ResourceId id) { } int ResourceManager::addAppropriateSources() { +#ifdef ENABLE_SCI32 + _multiDiscAudio = false; +#endif if (Common::File::exists("resource.map")) { // SCI0-SCI2 file naming scheme ResourceSource *map = addExternalMap("resource.map"); @@ -615,6 +618,10 @@ int ResourceManager::addAppropriateSources() { if (mapFiles.empty() || files.empty() || mapFiles.size() != files.size()) return 0; + if (Common::File::exists("resaud.001")) { + _multiDiscAudio = true; + } + for (Common::ArchiveMemberList::const_iterator mapIterator = mapFiles.begin(); mapIterator != mapFiles.end(); ++mapIterator) { Common::String mapName = (*mapIterator)->getName(); int mapNumber = atoi(strrchr(mapName.c_str(), '.') + 1); @@ -659,6 +666,7 @@ int ResourceManager::addAppropriateSourcesForDetection(const Common::FSList &fsl #ifdef ENABLE_SCI32 ResourceSource *sci21PatchMap = 0; const Common::FSNode *sci21PatchRes = 0; + _multiDiscAudio = false; #endif // First, find resource.map @@ -859,6 +867,13 @@ void ResourceManager::addResourcesFromChunk(uint16 id) { scanNewSources(); } +void ResourceManager::findDisc(const int16 discNo) { + // Since all resources are expected to be copied from the original discs + // into a single game directory, this call just records the number of the CD + // that the game has requested + _currentDiscNo = discNo; +} + #endif void ResourceManager::freeResourceSources() { @@ -878,7 +893,9 @@ void ResourceManager::init() { _LRU.clear(); _resMap.clear(); _audioMapSCI1 = NULL; - +#ifdef ENABLE_SCI32 + _currentDiscNo = 1; +#endif // FIXME: put this in an Init() function, so that we can error out if detection fails completely _mapVersion = detectMapVersion(); @@ -1477,6 +1494,12 @@ void ResourceManager::readResourcePatchesBase36() { for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { name = (*x)->getName(); + // The S/T prefixes often conflict with non-patch files and generate + // spurious warnings about invalid patches + if (name.hasSuffix(".DLL") || name.hasSuffix(".EXE") || name.hasSuffix(".TXT")) { + continue; + } + ResourceId resource36 = convertPatchNameBase36((ResourceType)i, name); /* @@ -1738,11 +1761,42 @@ int ResourceManager::readResourceMapSCI1(ResourceSource *map) { // if we use the first entries in the resource file, half of the // game will be English and umlauts will also be missing :P if (resource->_source->getSourceType() == kSourceVolume) { + // Maps are read during the scanning process (below), so + // need to be treated as unallocated in order for the new + // data from this volume to be picked up and used + if (resId.getType() == kResourceTypeMap) { + resource->_status = kResStatusNoMalloc; + } resource->_source = source; resource->_fileOffset = fileOffset; resource->size = 0; } } + +#ifdef ENABLE_SCI32 + // Different CDs may have different audio maps on each disc. The + // ResourceManager does not know how to deal with this; it expects + // each resource ID to be unique across an entire game. To work + // around this problem, all audio maps from this disc must be + // processed immediately, since they will be replaced by the audio + // map from the next disc on the next call to readResourceMapSCI1 + if (_multiDiscAudio && resId.getType() == kResourceTypeMap) { + IntMapResourceSource *audioMap = static_cast<IntMapResourceSource *>(addSource(new IntMapResourceSource("MAP", mapVolumeNr, resId.getNumber()))); + Common::String volumeName; + if (resId.getNumber() == 65535) { + volumeName = Common::String::format("RESSFX.%03d", mapVolumeNr); + } else { + volumeName = Common::String::format("RESAUD.%03d", mapVolumeNr); + } + + ResourceSource *audioVolume = addSource(new AudioVolumeResourceSource(this, volumeName, audioMap, mapVolumeNr)); + if (!audioMap->_scanned) { + audioVolume->_scanned = true; + audioMap->_scanned = true; + audioMap->scanSource(this); + } + } +#endif } } diff --git a/engines/sci/resource.h b/engines/sci/resource.h index f70bf48bd4..70db5909b7 100644 --- a/engines/sci/resource.h +++ b/engines/sci/resource.h @@ -296,6 +296,7 @@ protected: typedef Common::HashMap<ResourceId, Resource *, ResourceIdHash> ResourceMap; +class IntMapResourceSource; class ResourceManager { // FIXME: These 'friend' declarations are meant to be a temporary hack to // ease transition to the ResourceSource class system. @@ -397,6 +398,30 @@ public: * resource manager. */ void addResourcesFromChunk(uint16 id); + + /** + * Updates the currently active disc number. + */ + void findDisc(const int16 discNo); + + /** + * Gets the currently active disc number. + */ + int16 getCurrentDiscNo() const { return _currentDiscNo; } + +private: + /** + * The currently active disc number. + */ + int16 _currentDiscNo; + + /** + * If true, the game has multiple audio volumes that contain different + * audio files for each disc. + */ + bool _multiDiscAudio; + +public: #endif bool detectHires(); @@ -520,7 +545,7 @@ protected: * @param map The map * @return 0 on success, an SCI_ERROR_* code otherwise */ - int readAudioMapSCI11(ResourceSource *map); + int readAudioMapSCI11(IntMapResourceSource *map); /** * Reads SCI1 audio map files. diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp index 5ab443a16d..cbc4a02739 100644 --- a/engines/sci/resource_audio.cpp +++ b/engines/sci/resource_audio.cpp @@ -277,7 +277,7 @@ void ResourceManager::removeAudioResource(ResourceId resId) { // w syncSize (iff seq has bit 7 set) // w syncAscSize (iff seq has bit 6 set) -int ResourceManager::readAudioMapSCI11(ResourceSource *map) { +int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) { #ifndef ENABLE_SCI32 // SCI32 support is not built in. Check if this is a SCI32 game // and if it is abort here. @@ -286,17 +286,19 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { #endif uint32 offset = 0; - Resource *mapRes = findResource(ResourceId(kResourceTypeMap, map->_volumeNumber), false); + Resource *mapRes = findResource(ResourceId(kResourceTypeMap, map->_mapNumber), false); if (!mapRes) { - warning("Failed to open %i.MAP", map->_volumeNumber); + warning("Failed to open %i.MAP", map->_mapNumber); return SCI_ERROR_RESMAP_NOT_FOUND; } - ResourceSource *src = findVolume(map, 0); + ResourceSource *src = findVolume(map, map->_volumeNumber); - if (!src) + if (!src) { + warning("Failed to find volume for %i.MAP", map->_mapNumber); return SCI_ERROR_NO_RESOURCE_FILES_FOUND; + } byte *ptr = mapRes->data; @@ -309,7 +311,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { break; } - if (map->_volumeNumber == 65535) { + if (map->_mapNumber == 65535) { while (ptr < mapRes->data + mapRes->size) { uint16 n = READ_LE_UINT16(ptr); ptr += 2; @@ -327,7 +329,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { addResource(ResourceId(kResourceTypeAudio, n), src, offset); } - } else if (map->_volumeNumber == 0 && entrySize == 10 && ptr[3] == 0) { + } else if (map->_mapNumber == 0 && entrySize == 10 && ptr[3] == 0) { // QFG3 demo format // ptr[3] would be 'seq' in the normal format and cannot possibly be 0 while (ptr < mapRes->data + mapRes->size) { @@ -344,7 +346,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { addResource(ResourceId(kResourceTypeAudio, n), src, offset, size); } - } else if (map->_volumeNumber == 0 && entrySize == 8 && READ_LE_UINT16(ptr + 2) == 0xffff) { + } else if (map->_mapNumber == 0 && entrySize == 8 && READ_LE_UINT16(ptr + 2) == 0xffff) { // LB2 Floppy/Mother Goose SCI1.1 format Common::SeekableReadStream *stream = getVolumeFile(src); @@ -400,7 +402,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { // FIXME: The sync36 resource seems to be two bytes too big in KQ6CD // (bytes taken from the RAVE resource right after it) if (syncSize > 0) - addResource(ResourceId(kResourceTypeSync36, map->_volumeNumber, n & 0xffffff3f), src, offset, syncSize); + addResource(ResourceId(kResourceTypeSync36, map->_mapNumber, n & 0xffffff3f), src, offset, syncSize); } if (n & 0x40) { @@ -410,12 +412,12 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { ptr += 2; if (kq6HiresSyncSize > 0) { - addResource(ResourceId(kResourceTypeRave, map->_volumeNumber, n & 0xffffff3f), src, offset + syncSize, kq6HiresSyncSize); + addResource(ResourceId(kResourceTypeRave, map->_mapNumber, n & 0xffffff3f), src, offset + syncSize, kq6HiresSyncSize); syncSize += kq6HiresSyncSize; } } - addResource(ResourceId(kResourceTypeAudio36, map->_volumeNumber, n & 0xffffff3f), src, offset + syncSize); + addResource(ResourceId(kResourceTypeAudio36, map->_mapNumber, n & 0xffffff3f), src, offset + syncSize); } } @@ -937,13 +939,21 @@ void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource * } bool ResourceManager::addAudioSources() { +#ifdef ENABLE_SCI32 + // Multi-disc audio is added during addAppropriateSources for those titles + // that require it + if (_multiDiscAudio) { + return true; + } +#endif + Common::List<ResourceId> resources = listResources(kResourceTypeMap); Common::List<ResourceId>::iterator itr; for (itr = resources.begin(); itr != resources.end(); ++itr) { - ResourceSource *src = addSource(new IntMapResourceSource("MAP", itr->getNumber())); + ResourceSource *src = addSource(new IntMapResourceSource("MAP", 0, itr->getNumber())); - if ((itr->getNumber() == 65535) && Common::File::exists("RESOURCE.SFX")) + if (itr->getNumber() == 65535 && Common::File::exists("RESOURCE.SFX")) addSource(new AudioVolumeResourceSource(this, "RESOURCE.SFX", src, 0)); else if (Common::File::exists("RESOURCE.AUD")) addSource(new AudioVolumeResourceSource(this, "RESOURCE.AUD", src, 0)); @@ -991,7 +1001,7 @@ void ResourceManager::changeAudioDirectory(Common::String path) { if ((it->getNumber() == 65535)) continue; - ResourceSource *src = addSource(new IntMapResourceSource(mapName, it->getNumber())); + ResourceSource *src = addSource(new IntMapResourceSource(mapName, 0, it->getNumber())); addSource(new AudioVolumeResourceSource(this, audioResourceName, src, 0)); } diff --git a/engines/sci/resource_intern.h b/engines/sci/resource_intern.h index 461d684005..fe4b0a97f4 100644 --- a/engines/sci/resource_intern.h +++ b/engines/sci/resource_intern.h @@ -134,8 +134,9 @@ public: class IntMapResourceSource : public ResourceSource { public: - IntMapResourceSource(const Common::String &name, int volNum) - : ResourceSource(kSourceIntMap, name, volNum) { + uint16 _mapNumber; + IntMapResourceSource(const Common::String &name, int volNum, int mapNum) + : ResourceSource(kSourceIntMap, name, volNum), _mapNumber(mapNum) { } virtual void scanSource(ResourceManager *resMan); diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 4c178b6ed7..86c0cffe15 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -65,6 +65,7 @@ #ifdef ENABLE_SCI32 #include "sci/graphics/controls32.h" +#include "sci/graphics/cursor32.h" #include "sci/graphics/frameout.h" #include "sci/graphics/palette32.h" #include "sci/graphics/remap32.h" @@ -72,17 +73,12 @@ #include "sci/graphics/transitions32.h" #include "sci/graphics/video32.h" #include "sci/sound/audio32.h" -// TODO: Move this to video32 -#include "sci/video/robot_decoder.h" #endif namespace Sci { SciEngine *g_sci = 0; - -class GfxDriver; - SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gameId) : Engine(syst), _gameDescription(desc), _gameId(gameId), _rng("sci") { @@ -96,6 +92,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam #ifdef ENABLE_SCI32 _audio32 = nullptr; _video32 = nullptr; + _gfxCursor32 = nullptr; #endif _features = 0; _resMan = 0; @@ -130,6 +127,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam DebugMan.addDebugChannel(kDebugLevelScripts, "Scripts", "Notifies when scripts are unloaded"); DebugMan.addDebugChannel(kDebugLevelScriptPatcher, "ScriptPatcher", "Notifies when scripts are patched"); DebugMan.addDebugChannel(kDebugLevelWorkarounds, "Workarounds", "Notifies when workarounds are triggered"); + DebugMan.addDebugChannel(kDebugLevelVideo, "Video", "Video (SEQ, VMD, RBT) debugging"); DebugMan.addDebugChannel(kDebugLevelGC, "GC", "Garbage Collector debugging"); DebugMan.addDebugChannel(kDebugLevelResMan, "ResMan", "Resource manager debugging"); DebugMan.addDebugChannel(kDebugLevelOnStartup, "OnStartup", "Enter debugger at start of game"); @@ -171,11 +169,11 @@ SciEngine::~SciEngine() { delete _gfxControls32; delete _gfxPaint32; delete _gfxText32; - delete _robotDecoder; // GfxFrameout and GfxPalette32 must be deleted after Video32 since // destruction of screen items in the Video32 destructor relies on these // components delete _video32; + delete _gfxCursor32; delete _gfxPalette32; delete _gfxTransitions32; delete _gfxFrameout; @@ -244,35 +242,31 @@ Common::Error SciEngine::run() { _scriptPatcher = new ScriptPatcher(); SegManager *segMan = new SegManager(_resMan, _scriptPatcher); - // Read user option for hires graphics + // Read user option for forcing hires graphics // Only show/selectable for: // - King's Quest 6 CD // - King's Quest 6 CD demo // - Gabriel Knight 1 CD // - Police Quest 4 CD // TODO: Check, if Gabriel Knight 1 floppy supports high resolution - // TODO: Check, if Gabriel Knight 1 on Mac supports high resolution - switch (getPlatform()) { - case Common::kPlatformDOS: - case Common::kPlatformWindows: - // Only DOS+Windows - switch (_gameId) { - case GID_KQ6: - case GID_GK1: - case GID_PQ4: - if (isCD()) - _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics"); - break; - default: - break; - } - default: - break; - }; + // + // Gabriel Knight 1 on Mac is hi-res only, so it should NOT get this option. + // Confirmed by [md5] and originally by clone2727. + if (Common::checkGameGUIOption(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, ConfMan.get("guioptions"))) { + // GAMEOPTION_HIGH_RESOLUTION_GRAPHICS is available for the currently detected game, + // so read the user option now. + // We need to do this, because the option's default is "true", but we don't want "true" + // for any game that does not have this option. + _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics"); + } - // Initialize the game screen - _gfxScreen = new GfxScreen(_resMan); - _gfxScreen->enableUndithering(ConfMan.getBool("disable_dithering")); + if (getSciVersion() < SCI_VERSION_2) { + // Initialize the game screen + _gfxScreen = new GfxScreen(_resMan); + _gfxScreen->enableUndithering(ConfMan.getBool("disable_dithering")); + } else { + _gfxScreen = nullptr; + } _kernel = new Kernel(_resMan, segMan); _kernel->init(); @@ -709,12 +703,12 @@ void SciEngine::initGraphics() { #ifdef ENABLE_SCI32 _gfxControls32 = 0; _gfxText32 = 0; - _robotDecoder = 0; _gfxFrameout = 0; _gfxPaint32 = 0; _gfxPalette32 = 0; _gfxRemap32 = 0; _gfxTransitions32 = 0; + _gfxCursor32 = 0; #endif if (hasMacIconBar()) @@ -734,24 +728,23 @@ void SciEngine::initGraphics() { #endif _gfxCache = new GfxCache(_resMan, _gfxScreen, _gfxPalette16); - _gfxCursor = new GfxCursor(_resMan, _gfxPalette16, _gfxScreen); #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2) { // SCI32 graphic objects creation - _gfxCoordAdjuster = new GfxCoordAdjuster32(_gamestate->_segMan); - _gfxCursor->init(_gfxCoordAdjuster, _eventMan); - _gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxCoordAdjuster); + _gfxCursor32 = new GfxCursor32(); + _gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, nullptr, _gfxCoordAdjuster); _gfxPaint32 = new GfxPaint32(_gamestate->_segMan); - _robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh); _gfxTransitions32 = new GfxTransitions32(_gamestate->_segMan); - _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxScreen, _gfxPalette32, _gfxTransitions32); + _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _gfxPalette32, _gfxTransitions32, _gfxCursor32); + _gfxCursor32->init(_gfxFrameout->getCurrentBuffer()); _gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache); _gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32); _gfxFrameout->run(); } else { #endif // SCI0-SCI1.1 graphic objects creation + _gfxCursor = new GfxCursor(_resMan, _gfxPalette16, _gfxScreen); _gfxPorts = new GfxPorts(_gamestate->_segMan, _gfxScreen); _gfxCoordAdjuster = new GfxCoordAdjuster16(_gfxPorts); _gfxCursor->init(_gfxCoordAdjuster, _eventMan); @@ -930,14 +923,19 @@ Common::String SciEngine::getFilePrefix() const { } Common::String SciEngine::wrapFilename(const Common::String &name) const { - return getFilePrefix() + "-" + name; + Common::String prefix = getFilePrefix() + "-"; + if (name.hasPrefix(prefix.c_str())) + return name; + else + return prefix + name; } Common::String SciEngine::unwrapFilename(const Common::String &name) const { Common::String prefix = getFilePrefix() + "-"; if (name.hasPrefix(prefix.c_str())) return Common::String(name.c_str() + prefix.size()); - return name; + else + return name; } const char *SciEngine::getGameObjectName() { @@ -1042,17 +1040,19 @@ void SciEngine::syncIngameAudioOptions() { case GID_SQ6: // SCI2.1 case GID_TORIN: // SCI2.1 case GID_QFG4: // SCI2.1 + case GID_PQ4: // SCI2 + case GID_PHANTASMAGORIA: // SCI2.1 + case GID_MOTHERGOOSEHIRES: // SCI2.1 useGlobal90 = true; break; case GID_LSL6: // SCI2.1 // TODO: Uses gameFlags array break; + // Shivers does not use global 90 + // Police Quest: SWAT does not use global 90 + // // TODO: Unknown at the moment: - // Shivers - seems not to use global 90 - // Police Quest: SWAT - unable to check - // Police Quest 4 - unable to check - // Mixed Up Mother Goose - unable to check - // Phantasmagoria - seems to use global 90, unable to check for subtitles atm + // LSL7, Lighthouse, RAMA, Phantasmagoria 2 default: return; } @@ -1086,6 +1086,9 @@ void SciEngine::syncIngameAudioOptions() { case GID_SQ6: // SCI2.1, SQ6 seems to always use subtitles anyway case GID_TORIN: // SCI2.1 case GID_QFG4: // SCI2.1 + case GID_PQ4: // SCI2 + // Phantasmagoria does not support simultaneous speech + subtitles + // Mixed Up Mother Goose Deluxe does not support simultaneous speech + subtitles #endif // ENABLE_SCI32 _gamestate->variables[VAR_GLOBAL][90] = make_reg(0, 3); // speech + subtitles break; diff --git a/engines/sci/sci.h b/engines/sci/sci.h index a42095259b..b336eb8cce 100644 --- a/engines/sci/sci.h +++ b/engines/sci/sci.h @@ -45,6 +45,18 @@ struct ADGameDescription; */ namespace Sci { +// GUI-options, primarily used by detection_tables.h +#define GAMEOPTION_PREFER_DIGITAL_SFX GUIO_GAMEOPTIONS1 +#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS2 +#define GAMEOPTION_FB01_MIDI GUIO_GAMEOPTIONS3 +#define GAMEOPTION_JONES_CDAUDIO GUIO_GAMEOPTIONS4 +#define GAMEOPTION_KQ6_WINDOWS_CURSORS GUIO_GAMEOPTIONS5 +#define GAMEOPTION_SQ4_SILVER_CURSORS GUIO_GAMEOPTIONS6 +#define GAMEOPTION_EGA_UNDITHER GUIO_GAMEOPTIONS7 +// HIGH_RESOLUTION_GRAPHICS availability is checked for in SciEngine::run() +#define GAMEOPTION_HIGH_RESOLUTION_GRAPHICS GUIO_GAMEOPTIONS8 +#define GAMEOPTION_ENABLE_BLACK_LINED_VIDEO GUIO_GAMEOPTIONS9 + struct EngineState; class Vocabulary; class ResourceManager; @@ -63,7 +75,7 @@ class GfxCache; class GfxCompare; class GfxControls16; class GfxControls32; -class GfxCoordAdjuster; +class GfxCoordAdjuster16; class GfxCursor; class GfxMacIconBar; class GfxMenu; @@ -80,12 +92,11 @@ class GfxText32; class GfxTransitions; #ifdef ENABLE_SCI32 -// TODO: Move RobotDecoder to Video32 -class RobotDecoder; class GfxFrameout; class Audio32; class Video32; class GfxTransitions32; +class GfxCursor32; #endif // our engine debug levels @@ -113,7 +124,8 @@ enum kDebugLevels { kDebugLevelOnStartup = 1 << 20, kDebugLevelDebugMode = 1 << 21, kDebugLevelScriptPatcher = 1 << 22, - kDebugLevelWorkarounds = 1 << 23 + kDebugLevelWorkarounds = 1 << 23, + kDebugLevelVideo = 1 << 24 }; enum SciGameId { @@ -357,7 +369,7 @@ public: GfxCompare *_gfxCompare; GfxControls16 *_gfxControls16; // Controls for 16-bit gfx GfxControls32 *_gfxControls32; // Controls for 32-bit gfx - GfxCoordAdjuster *_gfxCoordAdjuster; + GfxCoordAdjuster16 *_gfxCoordAdjuster; GfxCursor *_gfxCursor; GfxMenu *_gfxMenu; // Menu for 16-bit gfx GfxPalette *_gfxPalette16; @@ -376,9 +388,9 @@ public: #ifdef ENABLE_SCI32 Audio32 *_audio32; Video32 *_video32; - RobotDecoder *_robotDecoder; GfxFrameout *_gfxFrameout; // kFrameout and the like for 32-bit gfx GfxTransitions32 *_gfxTransitions32; + GfxCursor32 *_gfxCursor32; #endif AudioPlayer *_audio; diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp index 288b7c00f5..4af474b918 100644 --- a/engines/sci/sound/audio32.cpp +++ b/engines/sci/sound/audio32.cpp @@ -164,7 +164,7 @@ Audio32::~Audio32() { #pragma mark - #pragma mark AudioStream implementation -int Audio32::writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop) { +int Audio32::writeAudioInternal(Audio::AudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop) { int samplesToRead = numSamples; // The parent rate converter will request N * 2 @@ -182,7 +182,8 @@ int Audio32::writeAudioInternal(Audio::RewindableAudioStream *const sourceStream do { if (loop && sourceStream->endOfStream()) { - sourceStream->rewind(); + Audio::RewindableAudioStream *rewindableStream = dynamic_cast<Audio::RewindableAudioStream *>(sourceStream); + rewindableStream->rewind(); } const int loopSamplesWritten = converter->flow(*sourceStream, targetBuffer, samplesToRead, leftVolume, rightVolume); @@ -305,7 +306,14 @@ int Audio32::readBuffer(Audio::st_sample_t *buffer, const int numSamples) { } if (channel.robot) { - // TODO: Robot audio into output buffer + if (channel.stream->endOfStream()) { + stop(channelIndex--); + } else { + const int channelSamplesWritten = writeAudioInternal(channel.stream, channel.converter, buffer, numSamples, kMaxVolume, kMaxVolume, channel.loop); + if (channelSamplesWritten > maxSamplesWritten) { + maxSamplesWritten = channelSamplesWritten; + } + } continue; } @@ -443,9 +451,9 @@ void Audio32::freeUnusedChannels() { Common::StackLock lock(_mutex); for (int channelIndex = 0; channelIndex < _numActiveChannels; ++channelIndex) { const AudioChannel &channel = getChannel(channelIndex); - if (channel.stream->endOfStream()) { + if (!channel.robot && channel.stream->endOfStream()) { if (channel.loop) { - channel.stream->rewind(); + dynamic_cast<Audio::SeekableAudioStream *>(channel.stream)->rewind(); } else { stop(channelIndex--); } @@ -466,21 +474,29 @@ void Audio32::freeChannel(const int16 channelIndex) { Common::StackLock lock(_mutex); AudioChannel &channel = getChannel(channelIndex); - // We cannot unlock resources from the audio thread - // because ResourceManager is not thread-safe; instead, - // we just record that the resource needs unlocking and - // unlock it whenever we are on the main thread again - if (_inAudioThread) { - _resourcesToUnlock.push_back(channel.resource); + // Robots have no corresponding resource to free + if (channel.robot) { + delete channel.stream; + channel.stream = nullptr; + channel.robot = false; } else { - _resMan->unlockResource(channel.resource); + // We cannot unlock resources from the audio thread + // because ResourceManager is not thread-safe; instead, + // we just record that the resource needs unlocking and + // unlock it whenever we are on the main thread again + if (_inAudioThread) { + _resourcesToUnlock.push_back(channel.resource); + } else { + _resMan->unlockResource(channel.resource); + } + + channel.resource = nullptr; + delete channel.stream; + channel.stream = nullptr; + delete channel.resourceStream; + channel.resourceStream = nullptr; } - channel.resource = nullptr; - delete channel.stream; - channel.stream = nullptr; - delete channel.resourceStream; - channel.resourceStream = nullptr; delete channel.converter; channel.converter = nullptr; @@ -527,6 +543,111 @@ void Audio32::setNumOutputChannels(int16 numChannels) { } #pragma mark - +#pragma mark Robot + +int16 Audio32::findRobotChannel() const { + Common::StackLock lock(_mutex); + for (int16 i = 0; i < _numActiveChannels; ++i) { + if (_channels[i].robot) { + return i; + } + } + + return kNoExistingChannel; +} + +bool Audio32::playRobotAudio(const RobotAudioStream::RobotAudioPacket &packet) { + // Stop immediately + if (packet.dataSize == 0) { + warning("Stopping robot stream by zero-length packet"); + return stopRobotAudio(); + } + + // Flush and then stop + if (packet.dataSize == -1) { + warning("Stopping robot stream by negative-length packet"); + return finishRobotAudio(); + } + + Common::StackLock lock(_mutex); + int16 channelIndex = findRobotChannel(); + + bool isNewChannel = false; + if (channelIndex == kNoExistingChannel) { + if (_numActiveChannels == _channels.size()) { + return false; + } + + channelIndex = _numActiveChannels++; + isNewChannel = true; + } + + AudioChannel &channel = getChannel(channelIndex); + + if (isNewChannel) { + channel.id = ResourceId(); + channel.resource = nullptr; + channel.loop = false; + channel.robot = true; + channel.fadeStartTick = 0; + channel.pausedAtTick = 0; + channel.soundNode = NULL_REG; + channel.volume = kMaxVolume; + // TODO: SCI3 introduces stereo audio + channel.pan = -1; + channel.converter = Audio::makeRateConverter(RobotAudioStream::kRobotSampleRate, getRate(), false); + // The RobotAudioStream buffer size is + // ((bytesPerSample * channels * sampleRate * 2000ms) / 1000ms) & ~3 + // where bytesPerSample = 2, channels = 1, and sampleRate = 22050 + channel.stream = new RobotAudioStream(88200); + _robotAudioPaused = false; + + if (_numActiveChannels == 1) { + _startedAtTick = g_sci->getTickCount(); + } + } + + return static_cast<RobotAudioStream *>(channel.stream)->addPacket(packet); +} + +bool Audio32::queryRobotAudio(RobotAudioStream::StreamState &status) const { + Common::StackLock lock(_mutex); + + const int16 channelIndex = findRobotChannel(); + if (channelIndex == kNoExistingChannel) { + status.bytesPlaying = 0; + return false; + } + + status = static_cast<RobotAudioStream *>(getChannel(channelIndex).stream)->getStatus(); + return true; +} + +bool Audio32::finishRobotAudio() { + Common::StackLock lock(_mutex); + + const int16 channelIndex = findRobotChannel(); + if (channelIndex == kNoExistingChannel) { + return false; + } + + static_cast<RobotAudioStream *>(getChannel(channelIndex).stream)->finish(); + return true; +} + +bool Audio32::stopRobotAudio() { + Common::StackLock lock(_mutex); + + const int16 channelIndex = findRobotChannel(); + if (channelIndex == kNoExistingChannel) { + return false; + } + + stop(channelIndex); + return true; +} + +#pragma mark - #pragma mark Playback uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor) { @@ -536,14 +657,15 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool if (channelIndex != kNoExistingChannel) { AudioChannel &channel = getChannel(channelIndex); + Audio::SeekableAudioStream *stream = dynamic_cast<Audio::SeekableAudioStream *>(channel.stream); if (channel.pausedAtTick) { resume(channelIndex); - return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000); + return MIN(65534, 1 + stream->getLength().msecs() * 60 / 1000); } warning("Tried to resume channel %s that was not paused", channel.id.toString().c_str()); - return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000); + return MIN(65534, 1 + stream->getLength().msecs() * 60 / 1000); } if (_numActiveChannels == _channels.size()) { @@ -642,7 +764,7 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool // use audio streams, and allocate and fill the monitoring buffer // when reading audio data from the stream. - channel.duration = /* round up */ 1 + (channel.stream->getLength().msecs() * 60 / 1000); + channel.duration = /* round up */ 1 + (dynamic_cast<Audio::SeekableAudioStream *>(channel.stream)->getLength().msecs() * 60 / 1000); const uint32 now = g_sci->getTickCount(); channel.pausedAtTick = autoPlay ? 0 : now; @@ -687,8 +809,6 @@ bool Audio32::resume(const int16 channelIndex) { if (channel.robot) { channel.startedAtTick += now - channel.pausedAtTick; channel.pausedAtTick = 0; - // TODO: Robot - // StartRobot(); return true; } } diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h index ac3176cc5a..a9905ab6bf 100644 --- a/engines/sci/sound/audio32.h +++ b/engines/sci/sound/audio32.h @@ -30,8 +30,10 @@ #include "common/scummsys.h" // for int16, uint8, uint32, uint16 #include "engines/sci/resource.h" // for ResourceId #include "sci/engine/vm_types.h" // for reg_t, NULL_REG +#include "sci/video/robot_decoder.h" // for RobotAudioStream namespace Sci { +#pragma mark AudioChannel /** * An audio channel used by the software SCI mixer. @@ -53,14 +55,11 @@ struct AudioChannel { Common::SeekableReadStream *resourceStream; /** - * The audio stream loaded into this channel. - * `SeekableAudioStream` is used here instead of - * `RewindableAudioStream` because - * `RewindableAudioStream` does not include the - * `getLength` function, which is needed to tell the - * game engine the duration of audio streams. + * The audio stream loaded into this channel. Can cast + * to `SeekableAudioStream` for normal channels and + * `RobotAudioStream` for robot channels. */ - Audio::SeekableAudioStream *stream; + Audio::AudioStream *stream; /** * The converter used to transform and merge the input @@ -188,7 +187,7 @@ private: * Mixes audio from the given source stream into the * target buffer using the given rate converter. */ - int writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop); + int writeAudioInternal(Audio::AudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop); #pragma mark - #pragma mark Channel management @@ -395,9 +394,18 @@ private: #pragma mark - #pragma mark Robot public: + bool playRobotAudio(const RobotAudioStream::RobotAudioPacket &packet); + bool queryRobotAudio(RobotAudioStream::StreamState &outStatus) const; + bool finishRobotAudio(); + bool stopRobotAudio(); private: /** + * Finds a channel that is configured for robot playback. + */ + int16 findRobotChannel() const; + + /** * When true, channels marked as robot audio will not be * played. */ diff --git a/engines/sci/sound/decoders/sol.cpp b/engines/sci/sound/decoders/sol.cpp index e445403120..ee1ba35406 100644 --- a/engines/sci/sound/decoders/sol.cpp +++ b/engines/sci/sound/decoders/sol.cpp @@ -21,6 +21,7 @@ */ #include "audio/audiostream.h" +#include "audio/rate.h" #include "audio/decoders/raw.h" #include "common/substream.h" #include "common/util.h" @@ -52,7 +53,7 @@ static const byte tableDPCM8[8] = { 0, 1, 2, 3, 6, 10, 15, 21 }; * Decompresses 16-bit DPCM compressed audio. Each byte read * outputs one sample into the decompression buffer. */ -static void deDPCM16(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, int16 &sample) { +static void deDPCM16(int16 *out, Common::ReadStream &audioStream, const uint32 numBytes, int16 &sample) { for (uint32 i = 0; i < numBytes; ++i) { const uint8 delta = audioStream.readByte(); if (delta & 0x80) { @@ -65,6 +66,19 @@ static void deDPCM16(int16 *out, Common::ReadStream &audioStream, uint32 numByte } } +void deDPCM16(int16 *out, const byte *in, const uint32 numBytes, int16 &sample) { + for (uint32 i = 0; i < numBytes; ++i) { + const uint8 delta = *in++; + if (delta & 0x80) { + sample -= tableDPCM16[delta & 0x7f]; + } else { + sample += tableDPCM16[delta]; + } + sample = CLIP<int16>(sample, -32768, 32767); + *out++ = TO_LE_16(sample); + } +} + /** * Decompresses one half of an 8-bit DPCM compressed audio * byte. @@ -178,7 +192,7 @@ int SOLStream<STEREO, S16BIT>::getRate() const { template <bool STEREO, bool S16BIT> bool SOLStream<STEREO, S16BIT>::endOfData() const { - return _stream->eos() || _stream->pos() >= _dataOffset + _rawDataSize; + return _stream->eos() || _stream->pos() >= _rawDataSize; } template <bool STEREO, bool S16BIT> @@ -269,5 +283,4 @@ Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *headerStre return Audio::makeRawStream(dataStream, sampleRate, rawFlags, disposeAfterUse); } - } diff --git a/engines/sci/video/robot_decoder.cpp b/engines/sci/video/robot_decoder.cpp index a2795d21f9..1757088ea4 100644 --- a/engines/sci/video/robot_decoder.cpp +++ b/engines/sci/video/robot_decoder.cpp @@ -20,391 +20,1597 @@ * */ -#include "common/archive.h" -#include "common/stream.h" -#include "common/substream.h" -#include "common/system.h" -#include "common/textconsole.h" -#include "common/util.h" - -#include "graphics/surface.h" -#include "audio/audiostream.h" -#include "audio/decoders/raw.h" - -#include "sci/resource.h" -#include "sci/util.h" -#include "sci/sound/audio.h" #include "sci/video/robot_decoder.h" +#include "common/archive.h" // for SearchMan +#include "common/debug.h" // for debugC +#include "common/endian.h" // for MKTAG +#include "common/memstream.h" // for MemoryReadStream +#include "common/platform.h" // for Platform::kPlatformMacintosh +#include "common/rational.h" // for operator*, Rational +#include "common/str.h" // for String +#include "common/stream.h" // for SeekableReadStream +#include "common/substream.h" // for SeekableSubReadStreamEndian +#include "common/textconsole.h" // for error, warning +#include "common/types.h" // for Flag::NO, Flag::YES +#include "sci/engine/seg_manager.h" // for SegManager +#include "sci/graphics/celobj32.h" // for Ratio, ::kLowResX, ::kLowResY +#include "sci/graphics/text32.h" // for BitmapResource +#include "sci/sound/audio32.h" // for Audio32 +#include "sci/sci.h" // for kDebugLevels::kDebugLevelVideo +#include "sci/util.h" // for READ_SCI11ENDIAN_UINT16, READ_SC... namespace Sci { -// TODO: -// - Positioning -// - Proper handling of frame scaling - scaled frames look squashed -// (probably because both dimensions should be scaled) -// - Transparency support -// - Timing - the arbitrary 100ms delay between each frame is not quite right -// - Proper handling of sound chunks in some cases, so that the frame size -// table can be ignored (it's only used to determine the correct sound chunk -// size at the moment, cause it can be wrong in some cases) -// - Fix audio "hiccups" - probably data that shouldn't be in the audio frames - - -// Some non technical information on robot files, from an interview with -// Greg Tomko-Pavia of Sierra On-Line -// Taken from http://anthonylarme.tripod.com/phantas/phintgtp.html -// -// (...) What we needed was a way of playing video, but have it blend into -// normal room art instead of occupying its own rectangular area. Room art -// consists of a background pic overlaid with various animating cels -// (traditional lingo: sprites). The cels each have a priority that determines -// who is on top and who is behind in the drawing order. Cels are read from -// *.v56 files (another proprietary format). A Robot is video frames with -// transparent background including priority and x,y information. Thus, it is -// like a cel, except it comes from an RBT - not a v56. Because it blends into -// our graphics engine, it looks just like a part of the room. A RBT can move -// around the screen and go behind other objects. (...) - -enum RobotPalTypes { - kRobotPalVariable = 0, - kRobotPalConstant = 1 -}; - -RobotDecoder::RobotDecoder(bool isBigEndian) { - _fileStream = 0; - _pos = Common::Point(0, 0); - _isBigEndian = isBigEndian; - _frameTotalSize = 0; +#pragma mark RobotAudioStream + +extern void deDPCM16(int16 *out, const byte *in, const uint32 numBytes, int16 &sample); + +RobotAudioStream::RobotAudioStream(const int32 bufferSize) : + _loopBuffer((byte *)malloc(bufferSize)), + _loopBufferSize(bufferSize), + _decompressionBuffer(nullptr), + _decompressionBufferSize(0), + _decompressionBufferPosition(-1), + _waiting(true), + _finished(false), + _firstPacketPosition(-1) {} + +RobotAudioStream::~RobotAudioStream() { + free(_loopBuffer); + free(_decompressionBuffer); } -RobotDecoder::~RobotDecoder() { - close(); +static void interpolateChannel(int16 *buffer, int32 numSamples, const int8 bufferIndex) { + if (numSamples <= 0) { + return; + } + + if (bufferIndex) { + int16 lastSample = *buffer; + int sample = lastSample; + int16 *target = buffer + 1; + const int16 *source = buffer + 2; + --numSamples; + + while (numSamples--) { + sample = *source + lastSample; + lastSample = *source; + sample /= 2; + *target = sample; + source += 2; + target += 2; + } + + *target = sample; + } else { + int16 *target = buffer; + const int16 *source = buffer + 1; + int16 lastSample = *source; + + while (numSamples--) { + int sample = *source + lastSample; + lastSample = *source; + sample /= 2; + *target = sample; + source += 2; + target += 2; + } + } } -bool RobotDecoder::loadStream(Common::SeekableReadStream *stream) { - close(); +static void copyEveryOtherSample(int16 *out, const int16 *in, int numSamples) { + while (numSamples--) { + *out = *in++; + out += 2; + } +} + +bool RobotAudioStream::addPacket(const RobotAudioPacket &packet) { + Common::StackLock lock(_mutex); + + if (_finished) { + warning("Packet %d sent to finished robot audio stream", packet.position); + return false; + } + + // `packet.position` is the decompressed (doubled) position of the packet, + // so values of `position` will always be divisible either by 2 (even) or by + // 4 (odd). + const int8 bufferIndex = packet.position % 4 ? 1 : 0; + + // Packet 0 is the first primer, packet 2 is the second primer, + // packet 4+ are regular audio data + if (packet.position <= 2 && _firstPacketPosition == -1) { + _readHead = 0; + _readHeadAbs = 0; + _maxWriteAbs = _loopBufferSize; + _writeHeadAbs = 2; + _jointMin[0] = 0; + _jointMin[1] = 2; + _waiting = true; + _finished = false; + _firstPacketPosition = packet.position; + fillRobotBuffer(packet, bufferIndex); + return true; + } - _fileStream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), _isBigEndian, DisposeAfterUse::YES); + const int32 packetEndByte = packet.position + (packet.dataSize * sizeof(int16) * kEOSExpansion); - readHeaderChunk(); + // Already read all the way past this packet (or already wrote valid samples + // to this channel all the way past this packet), so discard it + if (packetEndByte <= MAX(_readHeadAbs, _jointMin[bufferIndex])) { + debugC(kDebugLevelVideo, "Rejecting packet %d, read past %d / %d", packet.position, _readHeadAbs, _jointMin[bufferIndex]); + return true; + } - // There are several versions of robot files, ranging from 3 to 6. - // v3: no known examples - // v4: PQ:SWAT demo - // v5: SCI2.1 and SCI3 games - // v6: SCI3 games - if (_header.version < 4 || _header.version > 6) - error("Unknown robot version: %d", _header.version); + // The loop buffer is full, so tell the caller to send the packet again + // later + if (_maxWriteAbs <= _jointMin[bufferIndex]) { + debugC(kDebugLevelVideo, "Rejecting packet %d, full buffer", packet.position); + return false; + } - RobotVideoTrack *videoTrack = new RobotVideoTrack(_header.frameCount); - addTrack(videoTrack); + fillRobotBuffer(packet, bufferIndex); - if (_header.hasSound) - addTrack(new RobotAudioTrack()); + // This packet is the second primer, so allow playback to begin + if (_firstPacketPosition != -1 && _firstPacketPosition != packet.position) { + debugC(kDebugLevelVideo, "Done waiting. Robot audio begins"); + _waiting = false; + _firstPacketPosition = -1; + } - videoTrack->readPaletteChunk(_fileStream, _header.paletteDataSize); - readFrameSizesChunk(); - videoTrack->calculateVideoDimensions(_fileStream, _frameTotalSize); + // Only part of the packet could be read into the loop buffer before it was + // full, so tell the caller to send the packet again later + if (packetEndByte > _maxWriteAbs) { + debugC(kDebugLevelVideo, "Partial read of packet %d (%d / %d)", packet.position, packetEndByte - _maxWriteAbs, packetEndByte - packet.position); + return false; + } + + // The entire packet was successfully read into the loop buffer return true; } -bool RobotDecoder::load(GuiResourceId id) { - // TODO: RAMA's robot 1003 cannot be played (shown at the menu screen) - - // its drawn at odd coordinates. SV can't play it either (along with some - // others), so it must be some new functionality added in RAMA's robot - // videos. Skip it for now. - if (g_sci->getGameId() == GID_RAMA && id == 1003) - return false; +void RobotAudioStream::fillRobotBuffer(const RobotAudioPacket &packet, const int8 bufferIndex) { + int32 sourceByte = 0; - // Robots for the options in the RAMA menu - if (g_sci->getGameId() == GID_RAMA && (id >= 1004 && id <= 1009)) - return false; + const int32 decompressedSize = packet.dataSize * sizeof(int16); + if (_decompressionBufferPosition != packet.position) { + if (decompressedSize != _decompressionBufferSize) { + _decompressionBuffer = (byte *)realloc(_decompressionBuffer, decompressedSize); + _decompressionBufferSize = decompressedSize; + } - // TODO: The robot video in the Lighthouse demo gets stuck - if (g_sci->getGameId() == GID_LIGHTHOUSE && id == 16) - return false; + int16 carry = 0; + deDPCM16((int16 *)_decompressionBuffer, packet.data, packet.dataSize, carry); + _decompressionBufferPosition = packet.position; + } + + int32 numBytes = decompressedSize; + int32 packetPosition = packet.position; + int32 endByte = packet.position + decompressedSize * kEOSExpansion; + int32 startByte = MAX(_readHeadAbs + bufferIndex * 2, _jointMin[bufferIndex]); + int32 maxWriteByte = _maxWriteAbs + bufferIndex * 2; + if (packetPosition < startByte) { + sourceByte = (startByte - packetPosition) / kEOSExpansion; + numBytes -= sourceByte; + packetPosition = startByte; + } + if (packetPosition > maxWriteByte) { + numBytes += (packetPosition - maxWriteByte) / kEOSExpansion; + packetPosition = maxWriteByte; + } + if (endByte > maxWriteByte) { + numBytes -= (endByte - maxWriteByte) / kEOSExpansion; + endByte = maxWriteByte; + } - Common::String fileName = Common::String::format("%d.rbt", id); + const int32 maxJointMin = MAX(_jointMin[0], _jointMin[1]); + if (endByte > maxJointMin) { + _writeHeadAbs += endByte - maxJointMin; + } + + if (packetPosition > _jointMin[bufferIndex]) { + int32 packetEndByte = packetPosition % _loopBufferSize; + int32 targetBytePosition; + int32 numBytesToEnd; + if ((packetPosition & ~3) > (_jointMin[1 - bufferIndex] & ~3)) { + targetBytePosition = _jointMin[1 - bufferIndex] % _loopBufferSize; + if (targetBytePosition >= packetEndByte) { + numBytesToEnd = _loopBufferSize - targetBytePosition; + memset(_loopBuffer + targetBytePosition, 0, numBytesToEnd); + targetBytePosition = (1 - bufferIndex) ? 2 : 0; + } + numBytesToEnd = packetEndByte - targetBytePosition; + if (numBytesToEnd > 0) { + memset(_loopBuffer + targetBytePosition, 0, numBytesToEnd); + } + } + targetBytePosition = _jointMin[bufferIndex] % _loopBufferSize; + if (targetBytePosition >= packetEndByte) { + numBytesToEnd = _loopBufferSize - targetBytePosition; + interpolateChannel((int16 *)(_loopBuffer + targetBytePosition), numBytesToEnd / sizeof(int16) / kEOSExpansion, 0); + targetBytePosition = bufferIndex ? 2 : 0; + } + numBytesToEnd = packetEndByte - targetBytePosition; + if (numBytesToEnd > 0) { + interpolateChannel((int16 *)(_loopBuffer + targetBytePosition), numBytesToEnd / sizeof(int16) / kEOSExpansion, 0); + } + } + + if (numBytes > 0) { + int32 targetBytePosition = packetPosition % _loopBufferSize; + int32 packetEndByte = endByte % _loopBufferSize; + int32 numBytesToEnd = 0; + if (targetBytePosition >= packetEndByte) { + numBytesToEnd = (_loopBufferSize - (targetBytePosition & ~3)) / kEOSExpansion; + copyEveryOtherSample((int16 *)(_loopBuffer + targetBytePosition), (int16 *)(_decompressionBuffer + sourceByte), numBytesToEnd / kEOSExpansion); + targetBytePosition = bufferIndex ? 2 : 0; + } + copyEveryOtherSample((int16 *)(_loopBuffer + targetBytePosition), (int16 *)(_decompressionBuffer + sourceByte + numBytesToEnd), (packetEndByte - targetBytePosition) / sizeof(int16) / kEOSExpansion); + } + _jointMin[bufferIndex] = endByte; +} + +void RobotAudioStream::interpolateMissingSamples(int32 numSamples) { + int32 numBytes = numSamples * sizeof(int16) * kEOSExpansion; + int32 targetPosition = _readHead; + + if (_readHeadAbs > _jointMin[1]) { + if (_readHeadAbs > _jointMin[0]) { + if (targetPosition + numBytes >= _loopBufferSize) { + const int32 numBytesToEdge = (_loopBufferSize - targetPosition); + memset(_loopBuffer + targetPosition, 0, numBytesToEdge); + numBytes -= numBytesToEdge; + targetPosition = 0; + } + memset(_loopBuffer + targetPosition, 0, numBytes); + _jointMin[0] += numBytes; + _jointMin[1] += numBytes; + } else { + if (targetPosition + numBytes >= _loopBufferSize) { + const int32 numSamplesToEdge = (_loopBufferSize - targetPosition) / sizeof(int16) / kEOSExpansion; + interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamplesToEdge, 1); + numSamples -= numSamplesToEdge; + targetPosition = 0; + } + interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamples, 1); + _jointMin[1] += numBytes; + } + } else if (_readHeadAbs > _jointMin[0]) { + if (targetPosition + numBytes >= _loopBufferSize) { + const int32 numSamplesToEdge = (_loopBufferSize - targetPosition) / sizeof(int16) / kEOSExpansion; + interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamplesToEdge, 0); + numSamples -= numSamplesToEdge; + targetPosition = 2; + } + interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamples, 0); + _jointMin[0] += numBytes; + } +} + +void RobotAudioStream::finish() { + Common::StackLock lock(_mutex); + _finished = true; +} + +RobotAudioStream::StreamState RobotAudioStream::getStatus() const { + Common::StackLock lock(_mutex); + StreamState status; + status.bytesPlaying = _readHeadAbs; + status.rate = getRate(); + status.bits = 8 * sizeof(int16); + return status; +} + +int RobotAudioStream::readBuffer(Audio::st_sample_t *outBuffer, int numSamples) { + Common::StackLock lock(_mutex); + + if (_waiting) { + return 0; + } + + assert(!((_writeHeadAbs - _readHeadAbs) & 1)); + const int maxNumSamples = (_writeHeadAbs - _readHeadAbs) / sizeof(Audio::st_sample_t); + numSamples = MIN(numSamples, maxNumSamples); + + if (!numSamples) { + return 0; + } + + interpolateMissingSamples(numSamples); + + Audio::st_sample_t *inBuffer = (Audio::st_sample_t *)(_loopBuffer + _readHead); + + assert(!((_loopBufferSize - _readHead) & 1)); + const int numSamplesToEnd = (_loopBufferSize - _readHead) / sizeof(Audio::st_sample_t); + + int numSamplesToRead = MIN(numSamples, numSamplesToEnd); + Common::copy(inBuffer, inBuffer + numSamplesToRead, outBuffer); + + if (numSamplesToRead < numSamples) { + inBuffer = (Audio::st_sample_t *)_loopBuffer; + outBuffer += numSamplesToRead; + numSamplesToRead = numSamples - numSamplesToRead; + Common::copy(inBuffer, inBuffer + numSamplesToRead, outBuffer); + } + + const int32 numBytes = numSamples * sizeof(Audio::st_sample_t); + + _readHead += numBytes; + if (_readHead > _loopBufferSize) { + _readHead -= _loopBufferSize; + } + _readHeadAbs += numBytes; + _maxWriteAbs += numBytes; + assert(!(_readHead & 1)); + assert(!(_readHeadAbs & 1)); + + return numSamples; +} + +#pragma mark - +#pragma mark RobotDecoder + +RobotDecoder::RobotDecoder(SegManager *segMan) : + _delayTime(this), + _segMan(segMan), + _status(kRobotStatusUninitialized), + _audioBuffer(nullptr), + _rawPalette((uint8 *)malloc(kRawPaletteSize)) {} + +RobotDecoder::~RobotDecoder() { + close(); + free(_rawPalette); + free(_audioBuffer); +} + +#pragma mark - +#pragma mark RobotDecoder - Initialization + +void RobotDecoder::initStream(const GuiResourceId robotId) { + const Common::String fileName = Common::String::format("%d.rbt", robotId); Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName); + _fileOffset = 0; - if (!stream) { - warning("Unable to open robot file %s", fileName.c_str()); - return false; + if (stream == nullptr) { + error("Unable to open robot file %s", fileName.c_str()); + } + + const uint16 id = stream->readUint16LE(); + if (id != 0x16) { + error("Invalid robot file %s", fileName.c_str()); + } + + // TODO: Mac version not tested, so this could be totally wrong + _stream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), g_sci->getPlatform() == Common::kPlatformMacintosh, DisposeAfterUse::YES); + _stream->seek(2, SEEK_SET); + if (_stream->readUint32BE() != MKTAG('S', 'O', 'L', 0)) { + error("Resource %s is not Robot type!", fileName.c_str()); + } +} + +void RobotDecoder::initPlayback() { + _startFrameNo = 0; + _startTime = -1; + _startingFrameNo = -1; + _cueForceShowFrame = -1; + _previousFrameNo = -1; + _currentFrameNo = 0; + _status = kRobotStatusPaused; +} + +void RobotDecoder::initAudio() { + _syncFrame = true; + + _audioRecordInterval = RobotAudioStream::kRobotSampleRate / _frameRate; + + // TODO: Might actually be for all games newer than Lighthouse; check to + // see which games have this condition. + if (g_sci->getGameId() != GID_LIGHTHOUSE && !(_audioRecordInterval & 1)) { + ++_audioRecordInterval; + } + + _expectedAudioBlockSize = _audioBlockSize - kAudioBlockHeaderSize; + _audioBuffer = (byte *)realloc(_audioBuffer, kRobotZeroCompressSize + _expectedAudioBlockSize); + + if (_primerReservedSize != 0) { + const int32 primerHeaderPosition = _stream->pos(); + _totalPrimerSize = _stream->readSint32(); + const int16 compressionType = _stream->readSint16(); + _evenPrimerSize = _stream->readSint32(); + _oddPrimerSize = _stream->readSint32(); + _primerPosition = _stream->pos(); + + if (compressionType) { + error("Unknown audio header compression type %d", compressionType); + } + + if (_evenPrimerSize + _oddPrimerSize != _primerReservedSize) { + _stream->seek(primerHeaderPosition + _primerReservedSize, SEEK_SET); + } + } else if (_primerZeroCompressFlag) { + _evenPrimerSize = 19922; + _oddPrimerSize = 21024; + } + + _firstAudioRecordPosition = _evenPrimerSize * 2; + + const int usedEachFrame = (RobotAudioStream::kRobotSampleRate / 2) / _frameRate; + _maxSkippablePackets = MAX(0, _audioBlockSize / usedEachFrame - 1); +} + +void RobotDecoder::initVideo(const int16 x, const int16 y, const int16 scale, const reg_t plane, const bool hasPalette, const uint16 paletteSize) { + _position = Common::Point(x, y); + + if (scale != 128) { + _scaleInfo.x = scale; + _scaleInfo.y = scale; + _scaleInfo.signal = kScaleSignalDoScaling32; + } + + _plane = g_sci->_gfxFrameout->getPlanes().findByObject(plane); + if (_plane == nullptr) { + error("Invalid plane %04x:%04x passed to RobotDecoder::open", PRINT_REG(plane)); + } + + _minFrameRate = _frameRate - kMaxFrameRateDrift; + _maxFrameRate = _frameRate + kMaxFrameRateDrift; + + if (_xResolution == 0 || _yResolution == 0) { + // TODO: Default values were taken from RESOURCE.CFG hires property + // if it exists, so need to check games' configuration files for those + _xResolution = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + _yResolution = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; } - return loadStream(stream); + if (hasPalette) { + _stream->read(_rawPalette, paletteSize); + } else { + _stream->seek(paletteSize, SEEK_CUR); + } + + _screenItemList.reserve(kScreenItemListSize); + _maxCelArea.reserve(kFixedCelListSize); + + // Fixed cel buffers are for version 5 and newer + _fixedCels.reserve(MIN(_maxCelsPerFrame, (int16)kFixedCelListSize)); + _celDecompressionBuffer.reserve(_maxCelArea[0] + SciBitmap::getBitmapHeaderSize() + kRawPaletteSize); + _celDecompressionArea = _maxCelArea[0]; +} + +void RobotDecoder::initRecordAndCuePositions() { + PositionList recordSizes; + _videoSizes.reserve(_numFramesTotal); + _recordPositions.reserve(_numFramesTotal); + recordSizes.reserve(_numFramesTotal); + + switch(_version) { + case 5: // 16-bit sizes and positions + for (int i = 0; i < _numFramesTotal; ++i) { + _videoSizes.push_back(_stream->readUint16()); + } + for (int i = 0; i < _numFramesTotal; ++i) { + recordSizes.push_back(_stream->readUint16()); + } + break; + case 6: // 32-bit sizes and positions + for (int i = 0; i < _numFramesTotal; ++i) { + _videoSizes.push_back(_stream->readSint32()); + } + for (int i = 0; i < _numFramesTotal; ++i) { + recordSizes.push_back(_stream->readSint32()); + } + break; + default: + error("Unknown Robot version %d", _version); + } + + for (int i = 0; i < kCueListSize; ++i) { + _cueTimes[i] = _stream->readSint32(); + } + + for (int i = 0; i < kCueListSize; ++i) { + _cueValues[i] = _stream->readUint16(); + } + + Common::copy(_cueTimes, _cueTimes + kCueListSize, _masterCueTimes); + + int bytesRemaining = (_stream->pos() - _fileOffset) % kRobotFrameSize; + if (bytesRemaining != 0) { + _stream->seek(kRobotFrameSize - bytesRemaining, SEEK_CUR); + } + + int position = _stream->pos(); + _recordPositions.push_back(position); + for (int i = 0; i < _numFramesTotal - 1; ++i) { + position += recordSizes[i]; + _recordPositions.push_back(position); + } +} + +#pragma mark - +#pragma mark RobotDecoder - Playback + +void RobotDecoder::open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale) { + if (_status != kRobotStatusUninitialized) { + close(); + } + + initStream(robotId); + + _version = _stream->readUint16(); + + // TODO: Version 4 for PQ:SWAT demo? + if (_version < 5 || _version > 6) { + error("Unsupported version %d of Robot resource", _version); + } + + debugC(kDebugLevelVideo, "Opening version %d robot %d", _version, robotId); + + initPlayback(); + + _audioBlockSize = _stream->readUint16(); + _primerZeroCompressFlag = _stream->readSint16(); + _stream->seek(2, SEEK_CUR); // unused + _numFramesTotal = _stream->readUint16(); + const uint16 paletteSize = _stream->readUint16(); + _primerReservedSize = _stream->readUint16(); + _xResolution = _stream->readSint16(); + _yResolution = _stream->readSint16(); + const bool hasPalette = (bool)_stream->readByte(); + _hasAudio = (bool)_stream->readByte(); + _stream->seek(2, SEEK_CUR); // unused + _frameRate = _normalFrameRate = _stream->readSint16(); + _isHiRes = (bool)_stream->readSint16(); + _maxSkippablePackets = _stream->readSint16(); + _maxCelsPerFrame = _stream->readSint16(); + + // used for memory preallocation of fixed cels + _maxCelArea.push_back(_stream->readSint32()); + _maxCelArea.push_back(_stream->readSint32()); + _maxCelArea.push_back(_stream->readSint32()); + _maxCelArea.push_back(_stream->readSint32()); + _stream->seek(8, SEEK_CUR); // reserved + + if (_hasAudio) { + initAudio(); + } else { + _stream->seek(_primerReservedSize, SEEK_CUR); + } + + _priority = priority; + initVideo(x, y, scale, plane, hasPalette, paletteSize); + initRecordAndCuePositions(); } void RobotDecoder::close() { - VideoDecoder::close(); + if (_status == kRobotStatusUninitialized) { + return; + } + + debugC(kDebugLevelVideo, "Closing robot"); - delete _fileStream; - _fileStream = 0; + _status = kRobotStatusUninitialized; + _videoSizes.clear(); + _recordPositions.clear(); + _celDecompressionBuffer.clear(); + _doVersion5Scratch.clear(); + delete _stream; + _stream = nullptr; + + for (CelHandleList::size_type i = 0; i < _celHandles.size(); ++i) { + if (_celHandles[i].status == CelHandleInfo::kFrameLifetime) { + _segMan->freeBitmap(_celHandles[i].bitmapId); + } + } + _celHandles.clear(); + + for (FixedCelsList::size_type i = 0; i < _fixedCels.size(); ++i) { + _segMan->freeBitmap(_fixedCels[i]); + } + _fixedCels.clear(); + + if (g_sci->_gfxFrameout->getPlanes().findByObject(_plane->_object) != nullptr) { + for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { + if (_screenItemList[i] != nullptr) { + g_sci->_gfxFrameout->deleteScreenItem(*_screenItemList[i]); + } + } + } + _screenItemList.clear(); - delete[] _frameTotalSize; - _frameTotalSize = 0; + if (_hasAudio) { + _audioList.reset(); + } } -void RobotDecoder::readNextPacket() { - // Get our track - RobotVideoTrack *videoTrack = (RobotVideoTrack *)getTrack(0); - videoTrack->increaseCurFrame(); - Graphics::Surface *surface = videoTrack->getSurface(); +void RobotDecoder::pause() { + if (_status != kRobotStatusPlaying) { + return; + } + + if (_hasAudio) { + _audioList.stopAudioNow(); + } + + _status = kRobotStatusPaused; + _frameRate = _normalFrameRate; +} - if (videoTrack->endOfTrack()) +void RobotDecoder::resume() { + if (_status != kRobotStatusPaused) { return; + } + + _startingFrameNo = _currentFrameNo; + _status = kRobotStatusPlaying; + if (_hasAudio) { + primeAudio(_currentFrameNo * 60 / _frameRate); + _syncFrame = true; + } + + setRobotTime(_currentFrameNo); + for (int i = 0; i < kCueListSize; ++i) { + if (_masterCueTimes[i] != -1 && _masterCueTimes[i] < _currentFrameNo) { + _cueTimes[i] = -1; + } else { + _cueTimes[i] = _masterCueTimes[i]; + } + } +} - // Read frame image header (24 bytes) - _fileStream->skip(3); - byte frameScale = _fileStream->readByte(); - uint16 frameWidth = _fileStream->readUint16(); - uint16 frameHeight = _fileStream->readUint16(); - _fileStream->skip(4); // unknown, almost always 0 - uint16 frameX = _fileStream->readUint16(); - uint16 frameY = _fileStream->readUint16(); - - // TODO: In v4 robot files, frameX and frameY have a different meaning. - // Set them both to 0 for v4 for now, so that robots in PQ:SWAT show up - // correctly. - if (_header.version == 4) - frameX = frameY = 0; - - uint16 compressedSize = _fileStream->readUint16(); - uint16 frameFragments = _fileStream->readUint16(); - _fileStream->skip(4); // unknown - uint32 decompressedSize = frameWidth * frameHeight * frameScale / 100; - - // FIXME: A frame's height + position can go off limits... why? With the - // following, we cut the contents to fit the frame - uint16 scaledHeight = CLIP<uint16>(decompressedSize / frameWidth, 0, surface->h - frameY); - - // FIXME: Same goes for the frame's width + position. In this case, we - // modify the position to fit the contents on screen. - if (frameWidth + frameX > surface->w) - frameX = surface->w - frameWidth; - - assert(frameWidth + frameX <= surface->w && scaledHeight + frameY <= surface->h); - - DecompressorLZS lzs; - byte *decompressedFrame = new byte[decompressedSize]; - byte *outPtr = decompressedFrame; - - if (_header.version == 4) { - // v4 has just the one fragment, it seems, and ignores the fragment count - Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedSize); - lzs.unpack(&fragmentStream, outPtr, compressedSize, decompressedSize); +void RobotDecoder::showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority) { + debugC(kDebugLevelVideo, "Show frame %d (%d %d %d)", frameNo, newX, newY, newPriority); + + if (newX != kUnspecified) { + _position.x = newX; + } + + if (newY != kUnspecified) { + _position.y = newY; + } + + if (newPriority != kUnspecified) { + _priority = newPriority; + } + + _currentFrameNo = frameNo; + pause(); + + if (frameNo != _previousFrameNo) { + seekToFrame(frameNo); + doVersion5(false); } else { - for (uint16 i = 0; i < frameFragments; ++i) { - uint32 compressedFragmentSize = _fileStream->readUint32(); - uint32 decompressedFragmentSize = _fileStream->readUint32(); - uint16 compressionType = _fileStream->readUint16(); - - if (compressionType == 0) { - Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedFragmentSize); - lzs.unpack(&fragmentStream, outPtr, compressedFragmentSize, decompressedFragmentSize); - } else if (compressionType == 2) { // untested - _fileStream->read(outPtr, compressedFragmentSize); + for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { + if (_isHiRes) { + SciBitmap &bitmap = *_segMan->lookupBitmap(_celHandles[i].bitmapId); + + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + + if (scriptWidth == kLowResX && scriptHeight == kLowResY) { + const Ratio lowResToScreenX(screenWidth, kLowResX); + const Ratio lowResToScreenY(screenHeight, kLowResY); + const Ratio screenToLowResX(kLowResX, screenWidth); + const Ratio screenToLowResY(kLowResY, screenHeight); + + const int16 scaledX = _originalScreenItemX[i] + (_position.x * lowResToScreenX).toInt(); + const int16 scaledY1 = _originalScreenItemY[i] + (_position.y * lowResToScreenY).toInt(); + const int16 scaledY2 = scaledY1 + bitmap.getHeight() - 1; + + const int16 lowResX = (scaledX * screenToLowResX).toInt(); + const int16 lowResY = (scaledY2 * screenToLowResY).toInt(); + + bitmap.setDisplace(Common::Point( + (scaledX - (lowResX * lowResToScreenX).toInt()) * -1, + (lowResY * lowResToScreenY).toInt() - scaledY1 + )); + + _screenItemX[i] = lowResX; + _screenItemY[i] = lowResY; + } else { + const int16 scaledX = _originalScreenItemX[i] + _position.x; + const int16 scaledY = _originalScreenItemY[i] + _position.y + bitmap.getHeight() - 1; + bitmap.setDisplace(Common::Point(0, bitmap.getHeight() - 1)); + _screenItemX[i] = scaledX; + _screenItemY[i] = scaledY; + } + } else { + _screenItemX[i] = _originalScreenItemX[i] + _position.x; + _screenItemY[i] = _originalScreenItemY[i] + _position.y; + } + + if (_screenItemList[i] == nullptr) { + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = _celHandles[i].bitmapId; + ScreenItem *screenItem = new ScreenItem(_plane->_object, celInfo); + _screenItemList[i] = screenItem; + screenItem->_position = Common::Point(_screenItemX[i], _screenItemY[i]); + if (_priority == -1) { + screenItem->_fixedPriority = false; + } else { + screenItem->_priority = _priority; + screenItem->_fixedPriority = true; + } + g_sci->_gfxFrameout->addScreenItem(*screenItem); } else { - error("Unknown frame compression found: %d", compressionType); + ScreenItem *screenItem = _screenItemList[i]; + screenItem->_celInfo.bitmap = _celHandles[i].bitmapId; + screenItem->_position = Common::Point(_screenItemX[i], _screenItemY[i]); + if (_priority == -1) { + screenItem->_fixedPriority = false; + } else { + screenItem->_priority = _priority; + screenItem->_fixedPriority = true; + } + g_sci->_gfxFrameout->updateScreenItem(*screenItem); + } + } + } + + _previousFrameNo = frameNo; +} + +int16 RobotDecoder::getCue() const { + if (_status == kRobotStatusUninitialized || + _status == kRobotStatusPaused || + _syncFrame) { + return 0; + } + + if (_status == kRobotStatusEnd) { + return -1; + } + + const uint16 estimatedNextFrameNo = MIN(calculateNextFrameNo(_delayTime.predictedTicks()), _numFramesTotal); + + for (int i = 0; i < kCueListSize; ++i) { + if (_cueTimes[i] != -1 && _cueTimes[i] <= estimatedNextFrameNo) { + if (_cueTimes[i] >= _previousFrameNo) { + _cueForceShowFrame = _cueTimes[i] + 1; } - outPtr += decompressedFragmentSize; + _cueTimes[i] = -1; + return _cueValues[i]; } } - // Copy over the decompressed frame - byte *inFrame = decompressedFrame; - byte *outFrame = (byte *)surface->getPixels(); + return 0; +} - // Black out the surface - memset(outFrame, 0, surface->w * surface->h); +int16 RobotDecoder::getFrameNo() const { + if (_status == kRobotStatusUninitialized) { + return 0; + } - // Move to the correct y coordinate - outFrame += surface->w * frameY; + return _currentFrameNo; +} + +RobotDecoder::RobotStatus RobotDecoder::getStatus() const { + return _status; +} - for (uint16 y = 0; y < scaledHeight; y++) { - memcpy(outFrame + frameX, inFrame, frameWidth); - inFrame += frameWidth; - outFrame += surface->w; +bool RobotDecoder::seekToFrame(const int frameNo) { + return _stream->seek(_recordPositions[frameNo], SEEK_SET); +} + +void RobotDecoder::setRobotTime(const int frameNo) { + _startTime = getTickCount(); + _startFrameNo = frameNo; +} + +#pragma mark - +#pragma mark RobotDecoder - Timing + +RobotDecoder::DelayTime::DelayTime(RobotDecoder *decoder) : + _decoder(decoder) { + for (int i = 0; i < kDelayListSize; ++i) { + _timestamps[i] = i; + _delays[i] = 0; } - delete[] decompressedFrame; + _oldestTimestamp = 0; + _newestTimestamp = kDelayListSize - 1; + _startTime = 0; +} - uint32 audioChunkSize = _frameTotalSize[videoTrack->getCurFrame()] - (24 + compressedSize); +void RobotDecoder::DelayTime::startTiming() { + _startTime = _decoder->getTickCount(); +} -// TODO: The audio chunk size below is usually correct, but there are some -// exceptions (e.g. robot 4902 in Phantasmagoria, towards its end) -#if 0 - // Read frame audio header (14 bytes) - _fileStream->skip(2); // buffer position - _fileStream->skip(2); // unknown (usually 1) - _fileStream->skip(2); /*uint16 audioChunkSize = _fileStream->readUint16() + 8;*/ - _fileStream->skip(2); -#endif +void RobotDecoder::DelayTime::endTiming() { + const int timeDelta = _decoder->getTickCount() - _startTime; + for (uint i = 0; i < kDelayListSize; ++i) { + if (_timestamps[i] == _oldestTimestamp) { + _timestamps[i] = ++_newestTimestamp; + _delays[i] = timeDelta; + break; + } + } + ++_newestTimestamp; + _startTime = 0; + sortList(); +} - // Queue the next audio frame - // FIXME: For some reason, there are audio hiccups/gaps - if (_header.hasSound) { - RobotAudioTrack *audioTrack = (RobotAudioTrack *)getTrack(1); - _fileStream->skip(8); // header - audioChunkSize -= 8; - audioTrack->queueBuffer(g_sci->_audio->getDecodedRobotAudioFrame(_fileStream, audioChunkSize), audioChunkSize * 2); +bool RobotDecoder::DelayTime::timingInProgress() const { + return _startTime != 0; +} + +int RobotDecoder::DelayTime::predictedTicks() const { + return _delays[kDelayListSize / 2]; +} + +void RobotDecoder::DelayTime::sortList() { + for (uint i = 0; i < kDelayListSize - 1; ++i) { + int smallestDelay = _delays[i]; + uint smallestIndex = i; + + for (uint j = i + 1; j < kDelayListSize - 1; ++j) { + if (_delays[j] < smallestDelay) { + smallestDelay = _delays[j]; + smallestIndex = j; + } + } + + if (smallestIndex != i) { + SWAP(_delays[i], _delays[smallestIndex]); + SWAP(_timestamps[i], _timestamps[smallestIndex]); + } + } +} + +uint16 RobotDecoder::calculateNextFrameNo(const uint32 extraTicks) const { + return ticksToFrames(getTickCount() + extraTicks - _startTime) + _startFrameNo; +} + +uint32 RobotDecoder::ticksToFrames(const uint32 ticks) const { + return (ticks * _frameRate) / 60; +} + +uint32 RobotDecoder::getTickCount() const { + return g_sci->getTickCount(); +} + +#pragma mark - +#pragma mark RobotDecoder - Audio + +RobotDecoder::AudioList::AudioList() : + _blocks(), + _blocksSize(0), + _oldestBlockIndex(0), + _newestBlockIndex(0), + _startOffset(0), + _status(kRobotAudioReady) {} + +void RobotDecoder::AudioList::startAudioNow() { + submitDriverMax(); + g_sci->_audio32->resume(kRobotChannel); + _status = kRobotAudioPlaying; +} + +void RobotDecoder::AudioList::stopAudio() { + g_sci->_audio32->finishRobotAudio(); + freeAudioBlocks(); + _status = kRobotAudioStopping; +} + +void RobotDecoder::AudioList::stopAudioNow() { + if (_status == kRobotAudioPlaying || _status == kRobotAudioStopping || _status == kRobotAudioPaused) { + g_sci->_audio32->stopRobotAudio(); + _status = kRobotAudioStopped; + } + + freeAudioBlocks(); +} + +void RobotDecoder::AudioList::submitDriverMax() { + while (_blocksSize != 0) { + if (!_blocks[_oldestBlockIndex]->submit(_startOffset)) { + return; + } + + delete _blocks[_oldestBlockIndex]; + _blocks[_oldestBlockIndex] = nullptr; + ++_oldestBlockIndex; + if (_oldestBlockIndex == kAudioListSize) { + _oldestBlockIndex = 0; + } + + --_blocksSize; + } +} + +void RobotDecoder::AudioList::addBlock(const int position, const int size, const byte *data) { + assert(data != nullptr); + assert(size >= 0); + assert(position >= -1); + + if (_blocksSize == kAudioListSize) { + delete _blocks[_oldestBlockIndex]; + _blocks[_oldestBlockIndex] = nullptr; + ++_oldestBlockIndex; + if (_oldestBlockIndex == kAudioListSize) { + _oldestBlockIndex = 0; + } + --_blocksSize; + } + + if (_blocksSize == 0) { + _oldestBlockIndex = _newestBlockIndex = 0; } else { - _fileStream->skip(audioChunkSize); - } -} - -void RobotDecoder::readHeaderChunk() { - // Header (60 bytes) - _fileStream->skip(6); - _header.version = _fileStream->readUint16(); - _header.audioChunkSize = _fileStream->readUint16(); - _header.audioSilenceSize = _fileStream->readUint16(); - _fileStream->skip(2); - _header.frameCount = _fileStream->readUint16(); - _header.paletteDataSize = _fileStream->readUint16(); - _header.unkChunkDataSize = _fileStream->readUint16(); - _fileStream->skip(5); - _header.hasSound = _fileStream->readByte(); - _fileStream->skip(34); - - // Some videos (e.g. robot 1305 in Phantasmagoria and - // robot 184 in Lighthouse) have an unknown chunk before - // the palette chunk (probably used for sound preloading). - // Skip it here. - if (_header.unkChunkDataSize) - _fileStream->skip(_header.unkChunkDataSize); -} - -void RobotDecoder::readFrameSizesChunk() { - // The robot video file contains 2 tables, with one entry for each frame: - // - A table containing the size of the image in each video frame - // - A table containing the total size of each video frame. - // In v5 robots, the tables contain 16-bit integers, whereas in v6 robots, - // they contain 32-bit integers. - - _frameTotalSize = new uint32[_header.frameCount]; - - // TODO: The table reading code can probably be removed once the - // audio chunk size is figured out (check the TODO inside processNextFrame()) -#if 0 - // We don't need any of the two tables to play the video, so we ignore - // both of them. - uint16 wordSize = _header.version == 6 ? 4 : 2; - _fileStream->skip(_header.frameCount * wordSize * 2); -#else - switch (_header.version) { - case 4: - case 5: // sizes are 16-bit integers - // Skip table with frame image sizes, as we don't need it - _fileStream->skip(_header.frameCount * 2); - for (int i = 0; i < _header.frameCount; ++i) - _frameTotalSize[i] = _fileStream->readUint16(); - break; - case 6: // sizes are 32-bit integers - // Skip table with frame image sizes, as we don't need it - _fileStream->skip(_header.frameCount * 4); - for (int i = 0; i < _header.frameCount; ++i) - _frameTotalSize[i] = _fileStream->readUint32(); - break; - default: - error("Can't yet handle index table for robot version %d", _header.version); + ++_newestBlockIndex; + if (_newestBlockIndex == kAudioListSize) { + _newestBlockIndex = 0; + } } -#endif - // 2 more unknown tables - _fileStream->skip(1024 + 512); + _blocks[_newestBlockIndex] = new AudioBlock(position, size, data); + ++_blocksSize; +} - // Pad to nearest 2 kilobytes - uint32 curPos = _fileStream->pos(); - if (curPos & 0x7ff) - _fileStream->seek((curPos & ~0x7ff) + 2048); +void RobotDecoder::AudioList::reset() { + stopAudioNow(); + _startOffset = 0; + _status = kRobotAudioReady; } -RobotDecoder::RobotVideoTrack::RobotVideoTrack(int frameCount) : _frameCount(frameCount) { - _surface = new Graphics::Surface(); - _curFrame = -1; - _dirtyPalette = false; +void RobotDecoder::AudioList::prepareForPrimer() { + g_sci->_audio32->pause(kRobotChannel); + _status = kRobotAudioPaused; } -RobotDecoder::RobotVideoTrack::~RobotVideoTrack() { - _surface->free(); - delete _surface; +void RobotDecoder::AudioList::setAudioOffset(const int offset) { + _startOffset = offset; } -uint16 RobotDecoder::RobotVideoTrack::getWidth() const { - return _surface->w; +RobotDecoder::AudioList::AudioBlock::AudioBlock(const int position, const int size, const byte* const data) : + _position(position), + _size(size) { + _data = (byte *)malloc(size); + memcpy(_data, data, size); } -uint16 RobotDecoder::RobotVideoTrack::getHeight() const { - return _surface->h; +RobotDecoder::AudioList::AudioBlock::~AudioBlock() { + free(_data); } -Graphics::PixelFormat RobotDecoder::RobotVideoTrack::getPixelFormat() const { - return _surface->format; +bool RobotDecoder::AudioList::AudioBlock::submit(const int startOffset) { + assert(_data != nullptr); + RobotAudioStream::RobotAudioPacket packet(_data, _size, (_position - startOffset) * 2); + return g_sci->_audio32->playRobotAudio(packet); } -void RobotDecoder::RobotVideoTrack::readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize) { - byte *paletteData = new byte[chunkSize]; - stream->read(paletteData, chunkSize); +void RobotDecoder::AudioList::freeAudioBlocks() { + while (_blocksSize != 0) { + delete _blocks[_oldestBlockIndex]; + _blocks[_oldestBlockIndex] = nullptr; + ++_oldestBlockIndex; + if (_oldestBlockIndex == kAudioListSize) { + _oldestBlockIndex = 0; + } + + --_blocksSize; + } +} - // SCI1.1 palette - byte palFormat = paletteData[32]; - uint16 palColorStart = paletteData[25]; - uint16 palColorCount = READ_SCI11ENDIAN_UINT16(paletteData + 29); +bool RobotDecoder::primeAudio(const uint32 startTick) { + bool success = true; + _audioList.reset(); + + if (startTick == 0) { + _audioList.prepareForPrimer(); + byte *evenPrimerBuff = new byte[_evenPrimerSize]; + byte *oddPrimerBuff = new byte[_oddPrimerSize]; + + success = readPrimerData(evenPrimerBuff, oddPrimerBuff); + if (success) { + if (_evenPrimerSize != 0) { + _audioList.addBlock(0, _evenPrimerSize, evenPrimerBuff); + } + if (_oddPrimerSize != 0) { + _audioList.addBlock(1, _oddPrimerSize, oddPrimerBuff); + } + } + + delete[] evenPrimerBuff; + delete[] oddPrimerBuff; + } else { + assert(_evenPrimerSize * 2 >= _audioRecordInterval || _oddPrimerSize * 2 >= _audioRecordInterval); + + int audioStartFrame = 0; + int videoStartFrame = startTick * _frameRate / 60; + assert(videoStartFrame < _numFramesTotal); + + int audioStartPosition = (startTick * RobotAudioStream::kRobotSampleRate) / 60; + if (audioStartPosition & 1) { + audioStartPosition--; + } + _audioList.setAudioOffset(audioStartPosition); + _audioList.prepareForPrimer(); + + if (audioStartPosition < _evenPrimerSize * 2 || + audioStartPosition + 1 < _oddPrimerSize * 2) { + + byte *evenPrimerBuffer = new byte[_evenPrimerSize]; + byte *oddPrimerBuffer = new byte[_oddPrimerSize]; + success = readPrimerData(evenPrimerBuffer, oddPrimerBuffer); + if (success) { + int halfAudioStartPosition = audioStartPosition / 2; + if (audioStartPosition < _evenPrimerSize * 2) { + _audioList.addBlock(audioStartPosition, _evenPrimerSize - halfAudioStartPosition, &evenPrimerBuffer[halfAudioStartPosition]); + } + + if (audioStartPosition + 1 < _oddPrimerSize * 2) { + _audioList.addBlock(audioStartPosition + 1, _oddPrimerSize - halfAudioStartPosition, &oddPrimerBuffer[halfAudioStartPosition]); + } + } + + delete[] evenPrimerBuffer; + delete[] oddPrimerBuffer; + } - int palOffset = 37; - memset(_palette, 0, 256 * 3); + if (audioStartPosition >= _firstAudioRecordPosition) { + int audioRecordSize = _expectedAudioBlockSize; + assert(audioRecordSize > 0); + assert(_audioRecordInterval > 0); + assert(_firstAudioRecordPosition >= 0); - for (uint16 colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) { - if (palFormat == kRobotPalVariable) - palOffset++; - _palette[colorNo * 3 + 0] = paletteData[palOffset++]; - _palette[colorNo * 3 + 1] = paletteData[palOffset++]; - _palette[colorNo * 3 + 2] = paletteData[palOffset++]; + audioStartFrame = (audioStartPosition - _firstAudioRecordPosition) / _audioRecordInterval; + assert(audioStartFrame < videoStartFrame); + + if (audioStartFrame > 0) { + int lastAudioFrame = audioStartFrame - 1; + int oddRemainder = lastAudioFrame & 1; + int audioRecordStart = (lastAudioFrame * _audioRecordInterval) + oddRemainder + _firstAudioRecordPosition; + int audioRecordEnd = (audioRecordStart + ((audioRecordSize - 1) * 2)) + oddRemainder + _firstAudioRecordPosition; + + if (audioStartPosition >= audioRecordStart && audioStartPosition <= audioRecordEnd) { + --audioStartFrame; + } + } + + assert(!(audioStartPosition & 1)); + if (audioStartFrame & 1) { + ++audioStartPosition; + } + + if (!readPartialAudioRecordAndSubmit(audioStartFrame, audioStartPosition)) { + return false; + } + + ++audioStartFrame; + assert(audioStartFrame < videoStartFrame); + + int oddRemainder = audioStartFrame & 1; + int audioRecordStart = (audioStartFrame * _audioRecordInterval) + oddRemainder + _firstAudioRecordPosition; + int audioRecordEnd = (audioRecordStart + ((audioRecordSize - 1) * 2)) + oddRemainder + _firstAudioRecordPosition; + + if (audioStartPosition >= audioRecordStart && audioStartPosition <= audioRecordEnd) { + if (!readPartialAudioRecordAndSubmit(audioStartFrame, audioStartPosition + 1)) { + return false; + } + + ++audioStartFrame; + } + } + + int audioPosition, audioSize; + for (int i = audioStartFrame; i < videoStartFrame; i++) { + if (!readAudioDataFromRecord(i, _audioBuffer, audioPosition, audioSize)) { + break; + } + + _audioList.addBlock(audioPosition, audioSize, _audioBuffer); + } } - _dirtyPalette = true; - delete[] paletteData; + return success; } -void RobotDecoder::RobotVideoTrack::calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes) { - // This is an O(n) operation, as each frame has a different size. - // We need to know the actual frame size to have a constant video size. - uint32 pos = stream->pos(); +bool RobotDecoder::readPrimerData(byte *outEvenBuffer, byte *outOddBuffer) { + if (_primerReservedSize != 0) { + if (_totalPrimerSize != 0) { + _stream->seek(_primerPosition, SEEK_SET); + if (_evenPrimerSize > 0) { + _stream->read(outEvenBuffer, _evenPrimerSize); + } + + if (_oddPrimerSize > 0) { + _stream->read(outOddBuffer, _oddPrimerSize); + } + } + } else if (_primerZeroCompressFlag) { + memset(outEvenBuffer, 0, _evenPrimerSize); + memset(outOddBuffer, 0, _oddPrimerSize); + } else { + error("ReadPrimerData - Flags corrupt"); + } + + return !_stream->err(); +} + +bool RobotDecoder::readAudioDataFromRecord(const int frameNo, byte *outBuffer, int &outAudioPosition, int &outAudioSize) { + _stream->seek(_recordPositions[frameNo] + _videoSizes[frameNo], SEEK_SET); + _audioList.submitDriverMax(); + + // Compressed absolute position of the audio block in the audio stream + const int position = _stream->readSint32(); - uint16 width = 0, height = 0; + // Size of the block of audio, excluding the audio block header + int size = _stream->readSint32(); - for (int curFrame = 0; curFrame < _frameCount; curFrame++) { - stream->skip(4); - uint16 frameWidth = stream->readUint16(); - uint16 frameHeight = stream->readUint16(); - if (frameWidth > width) - width = frameWidth; - if (frameHeight > height) - height = frameHeight; - stream->skip(frameSizes[curFrame] - 8); + assert(size <= _expectedAudioBlockSize); + + if (position == 0) { + return false; } - stream->seek(pos); + if (size != _expectedAudioBlockSize) { + memset(outBuffer, 0, kRobotZeroCompressSize); + _stream->read(outBuffer + kRobotZeroCompressSize, size); + size += kRobotZeroCompressSize; + } else { + _stream->read(outBuffer, size); + } - _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + outAudioPosition = position; + outAudioSize = size; + return !_stream->err(); } -RobotDecoder::RobotAudioTrack::RobotAudioTrack() { - _audioStream = Audio::makeQueuingAudioStream(11025, false); +bool RobotDecoder::readPartialAudioRecordAndSubmit(const int startFrame, const int startPosition) { + int audioPosition, audioSize; + bool success = readAudioDataFromRecord(startFrame, _audioBuffer, audioPosition, audioSize); + if (success) { + const int relativeStartOffset = (startPosition - audioPosition) / 2; + _audioList.addBlock(startPosition, audioSize - relativeStartOffset, _audioBuffer + relativeStartOffset); + } + + return success; } -RobotDecoder::RobotAudioTrack::~RobotAudioTrack() { - delete _audioStream; +#pragma mark - +#pragma mark RobotDecoder - Rendering + +uint16 RobotDecoder::getFrameSize(Common::Rect &outRect) const { + outRect.clip(0, 0); + for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { + ScreenItem &screenItem = *_screenItemList[i]; + outRect.extend(screenItem.getNowSeenRect(*_plane)); + } + + return _numFramesTotal; } -void RobotDecoder::RobotAudioTrack::queueBuffer(byte *buffer, int size) { - _audioStream->queueBuffer(buffer, size, DisposeAfterUse::YES, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN); +void RobotDecoder::doRobot() { + if (_status != kRobotStatusPlaying) { + return; + } + + if (!_syncFrame) { + if (_cueForceShowFrame != -1) { + _currentFrameNo = _cueForceShowFrame; + _cueForceShowFrame = -1; + } else { + const int nextFrameNo = calculateNextFrameNo(_delayTime.predictedTicks()); + if (nextFrameNo < _currentFrameNo) { + return; + } + _currentFrameNo = nextFrameNo; + } + } + + if (_currentFrameNo >= _numFramesTotal) { + const int finalFrameNo = _numFramesTotal - 1; + if (_previousFrameNo == finalFrameNo) { + _status = kRobotStatusEnd; + if (_hasAudio) { + _audioList.stopAudio(); + _frameRate = _normalFrameRate; + _hasAudio = false; + } + return; + } else { + _currentFrameNo = finalFrameNo; + } + } + + if (_currentFrameNo == _previousFrameNo) { + _audioList.submitDriverMax(); + return; + } + + if (_hasAudio) { + for (int candidateFrameNo = _previousFrameNo + _maxSkippablePackets + 1; candidateFrameNo < _currentFrameNo; candidateFrameNo += _maxSkippablePackets + 1) { + + _audioList.submitDriverMax(); + + int audioPosition, audioSize; + if (readAudioDataFromRecord(candidateFrameNo, _audioBuffer, audioPosition, audioSize)) { + _audioList.addBlock(audioPosition, audioSize, _audioBuffer); + } + } + _audioList.submitDriverMax(); + } + + _delayTime.startTiming(); + seekToFrame(_currentFrameNo); + doVersion5(); + if (_hasAudio) { + _audioList.submitDriverMax(); + } +} + +void RobotDecoder::frameAlmostVisible() { + if (_status == kRobotStatusPlaying && !_syncFrame) { + if (_previousFrameNo != _currentFrameNo) { + while (calculateNextFrameNo() < _currentFrameNo) { + _audioList.submitDriverMax(); + } + } + } +} + +void RobotDecoder::frameNowVisible() { + if (_status != kRobotStatusPlaying) { + return; + } + + if (_syncFrame) { + _syncFrame = false; + if (_hasAudio) { + _audioList.startAudioNow(); + _checkAudioSyncTime = _startTime + kAudioSyncCheckInterval; + } + + setRobotTime(_currentFrameNo); + } + + if (_delayTime.timingInProgress()) { + _delayTime.endTiming(); + } + + if (_hasAudio) { + _audioList.submitDriverMax(); + } + + if (_previousFrameNo != _currentFrameNo) { + _previousFrameNo = _currentFrameNo; + } + + if (!_syncFrame && _hasAudio && getTickCount() >= _checkAudioSyncTime) { + RobotAudioStream::StreamState status; + const bool success = g_sci->_audio32->queryRobotAudio(status); + if (!success) { + return; + } + + const int bytesPerFrame = status.rate / _normalFrameRate * (status.bits == 16 ? 2 : 1); + // check again in 1/3rd second + _checkAudioSyncTime = getTickCount() + 60 / 3; + + const int currentVideoFrameNo = calculateNextFrameNo() - _startingFrameNo; + const int currentAudioFrameNo = status.bytesPlaying / bytesPerFrame; + debugC(kDebugLevelVideo, "Video frame %d %s audio frame %d", currentVideoFrameNo, currentVideoFrameNo == currentAudioFrameNo ? "=" : currentVideoFrameNo < currentAudioFrameNo ? "<" : ">", currentAudioFrameNo); + if (currentVideoFrameNo < _numFramesTotal && + currentAudioFrameNo < _numFramesTotal) { + + bool shouldResetRobotTime = false; + + if (currentAudioFrameNo < currentVideoFrameNo - 1 && _frameRate != _minFrameRate) { + debugC(kDebugLevelVideo, "[v] Reducing frame rate"); + _frameRate = _minFrameRate; + shouldResetRobotTime = true; + } else if (currentAudioFrameNo > currentVideoFrameNo + 1 && _frameRate != _maxFrameRate) { + debugC(kDebugLevelVideo, "[^] Increasing frame rate"); + _frameRate = _maxFrameRate; + shouldResetRobotTime = true; + } else if (_frameRate != _normalFrameRate) { + debugC(kDebugLevelVideo, "[=] Setting to normal frame rate"); + _frameRate = _normalFrameRate; + shouldResetRobotTime = true; + } + + if (shouldResetRobotTime) { + if (currentAudioFrameNo < _currentFrameNo) { + setRobotTime(_currentFrameNo); + } else { + setRobotTime(currentAudioFrameNo); + } + } + } + } +} + +void RobotDecoder::expandCel(byte* target, const byte* source, const int16 celWidth, const int16 celHeight) const { + assert(source != nullptr && target != nullptr); + + const int sourceHeight = (celHeight * _verticalScaleFactor) / 100; + assert(sourceHeight > 0); + + const int16 numerator = celHeight; + const int16 denominator = sourceHeight; + int remainder = 0; + for (int16 y = sourceHeight - 1; y >= 0; --y) { + remainder += numerator; + int16 linesToDraw = remainder / denominator; + remainder %= denominator; + + while (linesToDraw--) { + memcpy(target, source, celWidth); + target += celWidth; + } + + source += celWidth; + } +} + +void RobotDecoder::setPriority(const int16 newPriority) { + _priority = newPriority; +} + +void RobotDecoder::doVersion5(const bool shouldSubmitAudio) { + const RobotScreenItemList::size_type oldScreenItemCount = _screenItemList.size(); + const int videoSize = _videoSizes[_currentFrameNo]; + _doVersion5Scratch.resize(videoSize); + + byte *videoFrameData = _doVersion5Scratch.begin(); + + if (!_stream->read(videoFrameData, videoSize)) { + error("RobotDecoder::doVersion5: Read error"); + } + + const RobotScreenItemList::size_type screenItemCount = READ_SCI11ENDIAN_UINT16(videoFrameData); + + if (screenItemCount > kScreenItemListSize) { + return; + } + + if (_hasAudio && + (getSciVersion() < SCI_VERSION_3 || shouldSubmitAudio)) { + int audioPosition, audioSize; + if (readAudioDataFromRecord(_currentFrameNo, _audioBuffer, audioPosition, audioSize)) { + _audioList.addBlock(audioPosition, audioSize, _audioBuffer); + } + } + + if (screenItemCount > oldScreenItemCount) { + _screenItemList.resize(screenItemCount); + _screenItemX.resize(screenItemCount); + _screenItemY.resize(screenItemCount); + _originalScreenItemX.resize(screenItemCount); + _originalScreenItemY.resize(screenItemCount); + } + + createCels5(videoFrameData + 2, screenItemCount, true); + for (RobotScreenItemList::size_type i = 0; i < screenItemCount; ++i) { + Common::Point position(_screenItemX[i], _screenItemY[i]); + +// TODO: Version 6 robot? +// int scaleXRemainder; + if (_scaleInfo.signal == kScaleSignalDoScaling32) { + position.x = (position.x * _scaleInfo.x) / 128; +// TODO: Version 6 robot? +// scaleXRemainder = (position.x * _scaleInfo.x) % 128; + position.y = (position.y * _scaleInfo.y) / 128; + } + + if (_screenItemList[i] == nullptr) { + CelInfo32 celInfo; + celInfo.bitmap = _celHandles[i].bitmapId; + ScreenItem *screenItem = new ScreenItem(_plane->_object, celInfo, position, _scaleInfo); + _screenItemList[i] = screenItem; + // TODO: Version 6 robot? + // screenItem->_field_30 = scaleXRemainder; + + if (_priority == -1) { + screenItem->_fixedPriority = false; + } else { + screenItem->_fixedPriority = true; + screenItem->_priority = _priority; + } + g_sci->_gfxFrameout->addScreenItem(*screenItem); + } else { + ScreenItem *screenItem = _screenItemList[i]; + screenItem->_celInfo.bitmap = _celHandles[i].bitmapId; + screenItem->_position = position; + // TODO: Version 6 robot? + // screenItem->_field_30 = scaleXRemainder; + + if (_priority == -1) { + screenItem->_fixedPriority = false; + } else { + screenItem->_fixedPriority = true; + screenItem->_priority = _priority; + } + g_sci->_gfxFrameout->updateScreenItem(*screenItem); + } + } + + for (RobotScreenItemList::size_type i = screenItemCount; i < oldScreenItemCount; ++i) { + if (_screenItemList[i] != nullptr) { + g_sci->_gfxFrameout->deleteScreenItem(*_screenItemList[i]); + _screenItemList[i] = nullptr; + } + } +} + +void RobotDecoder::createCels5(const byte *rawVideoData, const int16 numCels, const bool usePalette) { + preallocateCelMemory(rawVideoData, numCels); + for (int16 i = 0; i < numCels; ++i) { + rawVideoData += createCel5(rawVideoData, i, usePalette); + } +} + +uint32 RobotDecoder::createCel5(const byte *rawVideoData, const int16 screenItemIndex, const bool usePalette) { + _verticalScaleFactor = rawVideoData[1]; + const int16 celWidth = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 2); + const int16 celHeight = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 4); + const Common::Point celPosition((int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 10), + (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 12)); + const uint16 dataSize = READ_SCI11ENDIAN_UINT16(rawVideoData + 14); + const int16 numDataChunks = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 16); + + rawVideoData += kCelHeaderSize; + + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + + Common::Point displace; + if (scriptWidth == kLowResX && scriptHeight == kLowResY) { + const Ratio lowResToScreenX(screenWidth, kLowResX); + const Ratio lowResToScreenY(screenHeight, kLowResY); + const Ratio screenToLowResX(kLowResX, screenWidth); + const Ratio screenToLowResY(kLowResY, screenHeight); + + const int16 scaledX = celPosition.x + (_position.x * lowResToScreenX).toInt(); + const int16 scaledY1 = celPosition.y + (_position.y * lowResToScreenY).toInt(); + const int16 scaledY2 = scaledY1 + celHeight - 1; + + const int16 lowResX = (scaledX * screenToLowResX).toInt(); + const int16 lowResY = (scaledY2 * screenToLowResY).toInt(); + + displace.x = (scaledX - (lowResX * lowResToScreenX).toInt()) * -1; + displace.y = (lowResY * lowResToScreenY).toInt() - scaledY1; + _screenItemX[screenItemIndex] = lowResX; + _screenItemY[screenItemIndex] = lowResY; + + debugC(kDebugLevelVideo, "Low resolution position c: %d %d l: %d/%d %d/%d d: %d %d s: %d/%d %d/%d x: %d y: %d", celPosition.x, celPosition.y, lowResX, scriptWidth, lowResY, scriptHeight, displace.x, displace.y, scaledX, screenWidth, scaledY2, screenHeight, scaledX - displace.x, scaledY2 - displace.y); + } else { + const int16 highResX = celPosition.x + _position.x; + const int16 highResY = celPosition.y + _position.y + celHeight - 1; + + displace.x = 0; + displace.y = celHeight - 1; + _screenItemX[screenItemIndex] = highResX; + _screenItemY[screenItemIndex] = highResY; + + debugC(kDebugLevelVideo, "High resolution position c: %d %d s: %d %d d: %d %d", celPosition.x, celPosition.y, highResX, highResY, displace.x, displace.y); + } + + _originalScreenItemX[screenItemIndex] = celPosition.x; + _originalScreenItemY[screenItemIndex] = celPosition.y; + + assert(_celHandles[screenItemIndex].area >= celWidth * celHeight); + + SciBitmap &bitmap = *_segMan->lookupBitmap(_celHandles[screenItemIndex].bitmapId); + assert(bitmap.getWidth() == celWidth && bitmap.getHeight() == celHeight); + assert(bitmap.getScaledWidth() == _xResolution && bitmap.getScaledHeight() == _yResolution); + assert(bitmap.getHunkPaletteOffset() == (uint32)bitmap.getWidth() * bitmap.getHeight() + SciBitmap::getBitmapHeaderSize()); + bitmap.setDisplace(displace); + + byte *targetBuffer = nullptr; + if (_verticalScaleFactor == 100) { + // direct copy to bitmap + targetBuffer = bitmap.getPixels(); + } else { + // go through squashed cel decompressor + _celDecompressionBuffer.resize(_celDecompressionArea >= celWidth * (celHeight * _verticalScaleFactor / 100)); + targetBuffer = _celDecompressionBuffer.begin(); + } + + for (int i = 0; i < numDataChunks; ++i) { + uint compressedSize = READ_SCI11ENDIAN_UINT32(rawVideoData); + uint decompressedSize = READ_SCI11ENDIAN_UINT32(rawVideoData + 4); + uint16 compressionType = READ_SCI11ENDIAN_UINT16(rawVideoData + 8); + rawVideoData += 10; + + switch (compressionType) { + case kCompressionLZS: { + Common::MemoryReadStream videoDataStream(rawVideoData, compressedSize, DisposeAfterUse::NO); + _decompressor.unpack(&videoDataStream, targetBuffer, compressedSize, decompressedSize); + break; + } + case kCompressionNone: + Common::copy(rawVideoData, rawVideoData + decompressedSize, targetBuffer); + break; + default: + error("Unknown compression type %d!", compressionType); + } + + rawVideoData += compressedSize; + targetBuffer += decompressedSize; + } + + if (_verticalScaleFactor != 100) { + expandCel(bitmap.getPixels(), _celDecompressionBuffer.begin(), celWidth, celHeight); + } + + if (usePalette) { + Common::copy(_rawPalette, _rawPalette + kRawPaletteSize, bitmap.getHunkPalette()); + } + + return kCelHeaderSize + dataSize; } -Audio::AudioStream *RobotDecoder::RobotAudioTrack::getAudioStream() const { - return _audioStream; +void RobotDecoder::preallocateCelMemory(const byte *rawVideoData, const int16 numCels) { + for (CelHandleList::size_type i = 0; i < _celHandles.size(); ++i) { + CelHandleInfo &celHandle = _celHandles[i]; + + if (celHandle.status == CelHandleInfo::kFrameLifetime) { + _segMan->freeBitmap(celHandle.bitmapId); + celHandle.bitmapId = NULL_REG; + celHandle.status = CelHandleInfo::kNoCel; + celHandle.area = 0; + } + } + _celHandles.resize(numCels); + + const int numFixedCels = MIN(numCels, (int16)kFixedCelListSize); + for (int i = 0; i < numFixedCels; ++i) { + CelHandleInfo &celHandle = _celHandles[i]; + + // NOTE: There was a check to see if the cel handle was not allocated + // here, for some reason, which would mean that nothing was ever + // allocated from fixed cels, because the _celHandles array just got + // deleted and recreated... + if (celHandle.bitmapId == NULL_REG) { + break; + } + + celHandle.bitmapId = _fixedCels[i]; + celHandle.status = CelHandleInfo::kRobotLifetime; + celHandle.area = _maxCelArea[i]; + } + + uint maxFrameArea = 0; + for (int i = 0; i < numCels; ++i) { + const int16 celWidth = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 2); + const int16 celHeight = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 4); + const uint16 dataSize = READ_SCI11ENDIAN_UINT16(rawVideoData + 14); + const uint area = celWidth * celHeight; + + if (area > maxFrameArea) { + maxFrameArea = area; + } + + CelHandleInfo &celHandle = _celHandles[i]; + if (celHandle.status == CelHandleInfo::kRobotLifetime) { + if (_maxCelArea[i] < area) { + _segMan->freeBitmap(celHandle.bitmapId); + _segMan->allocateBitmap(&celHandle.bitmapId, celWidth, celHeight, 255, 0, 0, _xResolution, _yResolution, kRawPaletteSize, false, false); + celHandle.area = area; + celHandle.status = CelHandleInfo::kFrameLifetime; + } + } else if (celHandle.status == CelHandleInfo::kNoCel) { + _segMan->allocateBitmap(&celHandle.bitmapId, celWidth, celHeight, 255, 0, 0, _xResolution, _yResolution, kRawPaletteSize, false, false); + celHandle.area = area; + celHandle.status = CelHandleInfo::kFrameLifetime; + } else { + error("Cel Handle has bad status"); + } + + rawVideoData += kCelHeaderSize + dataSize; + } + + if (maxFrameArea > _celDecompressionBuffer.size()) { + _celDecompressionBuffer.resize(maxFrameArea); + } } } // End of namespace Sci diff --git a/engines/sci/video/robot_decoder.h b/engines/sci/video/robot_decoder.h index 4faea5008a..9d8c720968 100644 --- a/engines/sci/video/robot_decoder.h +++ b/engines/sci/video/robot_decoder.h @@ -20,109 +20,1412 @@ * */ -#ifndef SCI_VIDEO_ROBOT_DECODER_H -#define SCI_VIDEO_ROBOT_DECODER_H +#ifndef SCI_SOUND_DECODERS_ROBOT_H +#define SCI_SOUND_DECODERS_ROBOT_H -#include "common/rational.h" -#include "common/rect.h" -#include "video/video_decoder.h" +#include "audio/audiostream.h" // for AudioStream +#include "audio/rate.h" // for st_sample_t +#include "common/array.h" // for Array +#include "common/mutex.h" // for StackLock, Mutex +#include "common/rect.h" // for Point, Rect (ptr only) +#include "common/scummsys.h" // for int16, int32, byte, uint16 +#include "sci/engine/vm_types.h" // for NULL_REG, reg_t +#include "sci/graphics/helpers.h" // for GuiResourceId +#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem (ptr o... -namespace Audio { -class QueuingAudioStream; -} +namespace Common { class SeekableSubReadStreamEndian; } +namespace Sci { +class Plane; +class SegManager; -namespace Common { -class SeekableSubReadStreamEndian; -} +// There were 3 different Robot video versions, used in the following games: +// - v4: PQ:SWAT demo +// - v5: KQ7 DOS, Phantasmagoria, PQ:SWAT, Lighthouse +// - v6: RAMA +// +// Notes on Robot v5/v6 format: +// +// Robot is a packetized streaming AV format that encodes multiple bitmaps + +// positioning data, plus synchronised audio, for rendering in the SCI graphics +// system. +// +// Unlike traditional AV formats, Robot videos almost always require playback +// within the game engine because certain information (like the resolution of +// the Robot coordinates and the background for the video) is dependent on data +// that does not exist within the Robot file itself. +// +// The Robot container consists of a file header, an optional primer audio +// section, an optional colour palette, a frame seek index, a set of cuepoints, +// and variable-sized packets of compressed video+audio data. +// +// Integers in Robot files are coded using native endianness (LSB for x86 +// versions, MSB for 68k/PPC versions). +// +// Robot video coding is a relatively simple variable-length compression with no +// interframe compression. Each cel in a frame is constructed from multiple +// contiguous data blocks, each of which can be independently compressed with +// LZS or left uncompressed. An entire cel can also be line decimated, where +// lines are deleted from the source bitmap at compression time and are +// reconstructed by decompression using line doubling. Each cel also includes +// coordinates where it should be placed within the video frame, relative to the +// top-left corner of the frame. +// +// Audio coding is fixed-length, and all audio blocks except for the primer +// audio are the same size. Audio is encoded with Sierra SOL DPCM16 compression, +// and is split into two channels ('even' and 'odd'), each at a 11025Hz sample +// rate. The original signal is restored by interleaving samples from the two +// channels together. Channel packets are 'even' if they have an ''absolute +// position of audio'' that is evenly divisible by 2; otherwise, they are 'odd'. +// Because the channels use DPCM compression, there is an 8-byte runway at the +// start of every audio block that is never written to the output stream, which +// is used to move the signal to the correct location by the 9th sample. +// +// File header (v5/v6): +// +// byte | description +// 0 | signature 0x16 +// 1 | unused +// 2-5 | signature 'SOL\0' +// 6-7 | version (4, 5, and 6 are the only known versions) +// 8-9 | size of audio blocks +// 10-11 | primer is compressed flag +// 12-13 | unused +// 14-15 | total number of video frames +// 16-17 | embedded palette size, in bytes +// 18-19 | primer reserved size +// 20-21 | coordinate X-resolution (if 0, uses game coordinates) +// 22-23 | coordinate Y-resolution (if 0, uses game coordinates) +// 24 | if non-zero, Robot includes a palette +// 25 | if non-zero, Robot includes audio +// 26-27 | unused +// 28-29 | the frame rate, in frames per second +// 30-31 | coordinate conversion flag; if true, screen item coordinates +// | from the robot should be used as-is with NO conversion when +// | explicitly displaying a specific frame +// 32-33 | the maximum number of packets that can be skipped without causing +// | audio drop-out +// 34-35 | the maximum possible number of cels that will be displayed in any +// | frame of the robot +// 36-39 | the maximum possible size, in bytes, of the first fixed cel +// 40-43 | the maximum possible size, in bytes, of the second fixed cel +// 44-47 | the maximum possible size, in bytes, of the third fixed cel +// 48-51 | the maximum possible size, in bytes, of the fourth fixed cel +// 52-59 | unused +// +// If the ''file includes audio'' flag is false, seek ''primer reserved size'' +// bytes from the end of the file header to get past a padding zone. +// +// If the ''file includes audio'' flag is true, and the ''primer reserved size'' +// is not zero, the data immediately after the file header consists of an audio +// primer header plus compressed audio data: +// +// Audio primer header: +// +// byte | description +// 0-3 | the size, in bytes, of the entire primer audio section +// 4-5 | the compression format of the primer audio (must be zero) +// 6-9 | the size, in bytes, of the "even" primer +// 10-13 | the size, in bytes, of the "odd" primer +// +// If the combined sizes of the even and odd primers do not match the ''primer +// reserved size'', the next header block can be found ''primer reserved size'' +// bytes from the *start* of the audio primer header. +// +// Otherwise, if the Robot has audio, and the ''primer reserved size'' is zero, +// and the ''primer is compressed flag'' is set, the "even" primer size is +// 19922, the "odd" primer size is 21024, and the "even" and "odd" buffers +// should be zero-filled. +// +// Any other combination of these flags is an error. +// +// If the Robot has a palette, the next ''palette size'' bytes should be read +// as a SCI HunkPalette. Otherwise, seek ''palette size'' bytes from the current +// position to get to the frame index. +// +// The next section of the Robot is the video frame size index. In version 5 +// robots, read ''total number of frames'' 16-bit integers to get the size of +// the compressed video for each frame. For version 6 robots, use 32-bit +// integers. +// +// The next section of the Robot is the packet size index (combined compressed +// size of video + audio for each frame). In version 5 Robots, read ''total +// number of frames'' 16-bit integers. In version 6 robots, use 32-bit integers. +// +// The next section of the Robot is the cue times index. Read 256 32-bit +// integers, which represent the number of ticks from the start of playback that +// the given cue point falls on. +// +// The next section of the Robot is the cue values index. Read 256 16-bit +// integers, which represent the actual cue values that will be passed back to +// the game engine when a cue is requested. +// +// Finally, to get to the first frame packet, seek from the current position to +// the start of the next 2048-byte-aligned sector. +// +// Frame packet: +// +// byte | description +// 0..n | video data (size is in the ''video frame size index'') +// n+1.. | optional audio data (size is ''size of audio blocks'') +// +// Video data: +// +// byte | description +// 0-2 | number of cels in the frame (max 10) +// 3..n | cels +// +// Cel: +// +// 0-17 | cel header +// 18..n | data chunks +// +// Cel header: +// +// byte | description +// 0 | unused +// 1 | vertical scale factor, in percent decimation (100 = no decimation, +// | 50 = 50% of lines were removed) +// 2-3 | cel width +// 4-5 | cel height +// 6-9 | unused +// 10-11 | cel x-position, in Robot coordinates +// 12-13 | cel y-position, in Robot coordinates +// 14-15 | cel total data chunk size, in bytes +// 16-17 | number of data chunks +// +// Cel data chunk: +// +// 0-9 | cel data chunk header +// 10..n | cel data +// +// Cel data chunk header: +// +// byte | description +// 0-3 | compressed size +// 4-7 | decompressed size +// 8-9 | compression type (0 = LZS, 2 = uncompressed) +// +// Random frame seeking can be done by calculating the address of the frame +// packet by adding up the ''packet size index'' entries up to the current +// frame. This will normally disable audio playback, as audio data in a packet +// does not correspond to the video in the same packet. +// +// Audio data is placed immediately after the end of the video data in a packet, +// and consists of an audio header plus compressed audio data: +// +// Audio data: +// +// byte | description +// 0-7 | audio data header +// 8-15 | DPCM runway +// 16..n | compressed audio data +// +// Audio data header: +// +// byte | description +// 0-3 | absolute position of audio in the audio stream +// 4-7 | the size of the audio block, excluding the header +// +// When a block of audio is processed, first check to ensure that the +// decompressed audio block's `position * 2 + length * 4` runs past the end of +// the last packet of the same evenness/oddness. Discard the audio block +// entirely if data has already been written past the end of this block for this +// channel, or if the read head has already read past the end of this audio +// block. +// +// If the block is not discarded, apply DPCM decompression to the entire block, +// starting from beginning of the DPCM runway, using an initial sample value of +// 0. Then, copy every sample from the decompressed source outside of the DPCM +// runway into every *other* sample of the final audio buffer (1 -> 2, 2 -> 4, +// 3 -> 6, etc.). +// +// Finally, for any skipped samples where the opposing (even/odd) channel did +// not yet write, interpolate the skipped areas by adding together the +// neighbouring samples from this audio block and dividing by two. (This allows +// the audio quality to degrade to 11kHz in case it takes too long to decode all +// the frames in the stream). Interpolated samples must not be written on top of +// true data from the opposing channel. Audio from later packets must also not +// be written on top of data in the same channel that was already written by an +// earlier packet, in particular because the first 8 bytes of the next packet +// are garbage data used to move the waveform to the correct position (due to +// the use of DPCM compression). -namespace Sci { +#pragma mark - +#pragma mark RobotAudioStream + +/** + * A Robot audio stream is a simple loop buffer + * that accepts audio blocks from the Robot engine. + */ +class RobotAudioStream : public Audio::AudioStream { +public: + enum { + /** + * The sample rate used for all robot audio. + */ + kRobotSampleRate = 22050, + + /** + * Multiplier for the size of a packet that + * is being expanded by writing to every other + * byte of the target buffer. + */ + kEOSExpansion = 2 + }; + + /** + * Playback state information. Used for framerate + * calculation. + */ + struct StreamState { + /** + * The current position of the read head of + * the audio stream. + */ + int bytesPlaying; + + /** + * The sample rate of the audio stream. + * Always 22050. + */ + uint16 rate; + + /** + * The bit depth of the audio stream. + * Always 16. + */ + uint8 bits; + }; + + /** + * A single packet of compressed audio from a + * Robot data stream. + */ + struct RobotAudioPacket { + /** + * Raw DPCM-compressed audio data. + */ + byte *data; + + /** + * The size of the compressed audio data, + * in bytes. + */ + int dataSize; + + /** + * The uncompressed, file-relative position + * of this audio packet. + */ + int position; + + RobotAudioPacket(byte *data_, const int dataSize_, const int position_) : + data(data_), dataSize(dataSize_), position(position_) {} + }; + + RobotAudioStream(const int32 bufferSize); + virtual ~RobotAudioStream(); + + /** + * Adds a new audio packet to the stream. + * @returns `true` if the audio packet was fully + * consumed, otherwise `false`. + */ + bool addPacket(const RobotAudioPacket &packet); + + /** + * Prevents any additional audio packets from + * being added to the audio stream. + */ + void finish(); + + /** + * Returns the current status of the audio + * stream. + */ + StreamState getStatus() const; + +private: + Common::Mutex _mutex; + + /** + * Loop buffer for playback. Contains decompressed + * 16-bit PCM samples. + */ + byte *_loopBuffer; + + /** + * The size of the loop buffer, in bytes. + */ + int32 _loopBufferSize; + + /** + * The position of the read head within the loop + * buffer, in bytes. + */ + int32 _readHead; + + /** + * The lowest file position that can be buffered, + * in uncompressed bytes. + */ + int32 _readHeadAbs; + + /** + * The highest file position that can be buffered, + * in uncompressed bytes. + */ + int32 _maxWriteAbs; + + /** + * The highest file position, in uncompressed bytes, + * that has been written to the stream. + * Different from `_maxWriteAbs`, which is the highest + * uncompressed position which *can* be written right + * now. + */ + int32 _writeHeadAbs; + + /** + * The highest file position, in uncompressed bytes, + * that has been written to the even & odd sides of + * the stream. + * + * Index 0 corresponds to the 'even' side; index + * 1 correspond to the 'odd' side. + */ + int32 _jointMin[2]; + + /** + * When `true`, the stream is waiting for all primer + * blocks to be received before allowing playback to + * begin. + */ + bool _waiting; + + /** + * When `true`, the stream will accept no more audio + * blocks. + */ + bool _finished; + + /** + * The uncompressed position of the first packet of + * robot data. Used to decide whether all primer + * blocks have been received and the stream should + * be started. + */ + int32 _firstPacketPosition; + + /** + * Decompression buffer, used to temporarily store + * an uncompressed block of audio data. + */ + byte *_decompressionBuffer; + + /** + * The size of the decompression buffer, in bytes. + */ + int32 _decompressionBufferSize; + + /** + * The position of the packet currently in the + * decompression buffer. Used to avoid + * re-decompressing audio data that has already + * been decompressed during a partial packet read. + */ + int32 _decompressionBufferPosition; + + /** + * Calculates the absolute ranges for new fills + * into the loop buffer. + */ + void fillRobotBuffer(const RobotAudioPacket &packet, const int8 bufferIndex); + + /** + * Interpolates `numSamples` samples from the read + * head, if no true samples were written for one + * (or both) of the joint channels. + */ + void interpolateMissingSamples(const int32 numSamples); + +#pragma mark - +#pragma mark RobotAudioStream - AudioStream implementation +public: + int readBuffer(Audio::st_sample_t *outBuffer, int numSamples) override; + virtual bool isStereo() const override { return false; }; + virtual int getRate() const override { return 22050; }; + virtual bool endOfData() const override { + Common::StackLock lock(_mutex); + return _readHeadAbs >= _writeHeadAbs; + }; + virtual bool endOfStream() const override { + Common::StackLock lock(_mutex); + return _finished && endOfData(); + } +}; + +#pragma mark - +#pragma mark RobotDecoder + +/** + * RobotDecoder implements the logic required + * for Robot animations. + * + * @note A paused or finished RobotDecoder was + * classified as serializable in SCI3, but the + * save/load code would attempt to use uninitialised + * values, so it seems that robots were not ever + * actually able to be saved. + */ +class RobotDecoder { +public: + RobotDecoder(SegManager *segMan); + ~RobotDecoder(); + +private: + SegManager *_segMan; + +#pragma mark Constants +public: + /** + * The playback status of the robot. + */ + enum RobotStatus { + kRobotStatusUninitialized = 0, + kRobotStatusPlaying = 1, + kRobotStatusEnd = 2, + kRobotStatusPaused = 3 + }; + + enum { + // Special high value used to represent + // parameters that should be left unchanged + // when calling `showFrame` + kUnspecified = 50000 + }; + +private: + enum { + /** + * Maximum number of on-screen screen items. + */ + kScreenItemListSize = 10, + + /** + * Maximum number of queued audio blocks. + */ + kAudioListSize = 10, + + /** + * Maximum number of samples used for frame timing. + */ + kDelayListSize = 10, + + /** + * Maximum number of cues. + */ + kCueListSize = 256, + + /** + * Maximum number of 'fixed' cels that never + * change for the duration of a robot. + */ + kFixedCelListSize = 4, + + /** + * The size of a hunk palette in the Robot stream. + */ + kRawPaletteSize = 1200, + + /** + * The size of a frame of Robot data. This + * value was used to align the first block of + * data after the main Robot header to the next + * CD sector. + */ + kRobotFrameSize = 2048, + + /** + * The size of a block of zero-compressed + * audio. Used to fill audio when the size of + * an audio packet does not match the expected + * packet size. + */ + kRobotZeroCompressSize = 2048, -class RobotDecoder : public Video::VideoDecoder { + /** + * The size of the audio block header, in bytes. + * The audio block header consists of the + * compressed size of the audio in the record, + * plus the position of the audio in the + * compressed data stream. + */ + kAudioBlockHeaderSize = 8, + + /** + * The size of a Robot cel header, in bytes. + */ + kCelHeaderSize = 22, + + /** + * The maximum amount that the frame rate is + * allowed to drift from the nominal frame rate + * in order to correct for AV drift or slow + * playback. + */ + kMaxFrameRateDrift = 1 + }; + + /** + * The version number for the currently loaded + * robot. + * + * There are several known versions of robot: + * + * v2: before Nov 1994; no known examples + * v3: before Nov 1994; no known examples + * v4: Jan 1995; PQ:SWAT demo + * v5: Mar 1995; SCI2.1 and SCI3 games + * v6: SCI3 games + */ + uint16 _version; + +#pragma mark - +#pragma mark Initialisation +private: + /** + * Sets up the read stream for the robot. + */ + void initStream(const GuiResourceId robotId); + + /** + * Sets up the initial values for playback control. + */ + void initPlayback(); + + /** + * Sets up the initial values for audio decoding. + */ + void initAudio(); + + /** + * Sets up the initial values for video rendering. + */ + void initVideo(const int16 x, const int16 y, const int16 scale, const reg_t plane, const bool hasPalette, const uint16 paletteSize); + + /** + * Sets up the robot's data record and cue positions. + */ + void initRecordAndCuePositions(); + +#pragma mark - +#pragma mark Playback public: - RobotDecoder(bool isBigEndian); - virtual ~RobotDecoder(); + /** + * Opens a robot file for playback. + * Newly opened robots are paused by default. + */ + void open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale); - bool loadStream(Common::SeekableReadStream *stream); - bool load(GuiResourceId id); + /** + * Closes the currently open robot file. + */ void close(); - void setPos(uint16 x, uint16 y) { _pos = Common::Point(x, y); } - Common::Point getPos() const { return _pos; } + /** + * Pauses the robot. Once paused, the audio for a robot + * is disabled until the end of playback. + */ + void pause(); + + /** + * Resumes a paused robot. + */ + void resume(); + + /** + * Moves robot to the specified frame and pauses playback. + * + * @note Called DisplayFrame in SSCI. + */ + void showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority); + + /** + * Retrieves the value associated with the + * current cue point. + */ + int16 getCue() const; + + /** + * Gets the currently displayed frame. + */ + int16 getFrameNo() const; + + /** + * Gets the playback status of the player. + */ + RobotStatus getStatus() const; + +private: + /** + * The read stream containing raw robot data. + */ + Common::SeekableSubReadStreamEndian *_stream; + + /** + * The current status of the player. + */ + RobotStatus _status; + + typedef Common::Array<int> PositionList; + + /** + * A map of frame numbers to byte offsets within `_stream`. + */ + PositionList _recordPositions; + + /** + * The offset of the Robot file within a + * resource bundle. + */ + int32 _fileOffset; + + /** + * A list of cue times that is updated to + * prevent earlier cue values from being + * given to the game more than once. + */ + mutable int32 _cueTimes[kCueListSize]; + + /** + * The original list of cue times from the + * raw Robot data. + */ + int32 _masterCueTimes[kCueListSize]; + + /** + * The list of values to provide to a game + * when a cue value is requested. + */ + int32 _cueValues[kCueListSize]; + + /** + * The current playback frame rate. + */ + int16 _frameRate; + + /** + * The nominal playback frame rate. + */ + int16 _normalFrameRate; + + /** + * The minimal playback frame rate. Used to + * correct for AV sync drift when the video + * is more than one frame ahead of the audio. + */ + int16 _minFrameRate; + + /** + * The maximum playback frame rate. Used to + * correct for AV sync drift when the video + * is more than one frame behind the audio. + */ + int16 _maxFrameRate; + + /** + * The maximum number of record blocks that + * can be skipped without causing audio to + * drop out. + */ + int16 _maxSkippablePackets; + + /** + * The currently displayed frame number. + */ + int _currentFrameNo; + + /** + * The last displayed frame number. + */ + int _previousFrameNo; + + /** + * The time, in ticks, when the robot was + * last started or resumed. + */ + int32 _startTime; + + /** + * The first frame displayed when the + * robot was resumed. + */ + int32 _startFrameNo; + + /** + * The last frame displayed when the robot + * was resumed. + */ + int32 _startingFrameNo; + + /** + * Seeks the raw data stream to the record for + * the given frame number. + */ + bool seekToFrame(const int frameNo); -protected: - void readNextPacket(); + /** + * Sets the start time and frame of the robot + * when the robot is started or resumed. + */ + void setRobotTime(const int frameNo); +#pragma mark - +#pragma mark Timing private: - class RobotVideoTrack : public FixedRateVideoTrack { + /** + * This class tracks the amount of time it takes for + * a frame of robot animation to be rendered. This + * information is used by the player to speculatively + * skip rendering of future frames to keep the + * animation in sync with the robot audio. + */ + class DelayTime { public: - RobotVideoTrack(int frameCount); - ~RobotVideoTrack(); - - uint16 getWidth() const; - uint16 getHeight() const; - Graphics::PixelFormat getPixelFormat() const; - int getCurFrame() const { return _curFrame; } - int getFrameCount() const { return _frameCount; } - const Graphics::Surface *decodeNextFrame() { return _surface; } - const byte *getPalette() const { _dirtyPalette = false; return _palette; } - bool hasDirtyPalette() const { return _dirtyPalette; } - - void readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize); - void calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes); - Graphics::Surface *getSurface() { return _surface; } - void increaseCurFrame() { _curFrame++; } - - protected: - Common::Rational getFrameRate() const { return Common::Rational(60, 10); } + DelayTime(RobotDecoder *decoder); + + /** + * Starts performance timing. + */ + void startTiming(); + + /** + * Ends performance timing. + */ + void endTiming(); + + /** + * Returns whether or not timing is currently in + * progress. + */ + bool timingInProgress() const; + + /** + * Returns the median time, in ticks, of the + * currently stored timing samples. + */ + int predictedTicks() const; private: - int _frameCount; - int _curFrame; - byte _palette[256 * 3]; - mutable bool _dirtyPalette; - Graphics::Surface *_surface; + RobotDecoder *_decoder; + + /** + * The start time, in ticks, of the current timing + * loop. If no loop is in progress, the value is 0. + * + * @note This is slightly different than SSCI where + * the not-timing value was -1. + */ + uint32 _startTime; + + /** + * A sorted list containing the timing data for + * the last `kDelayListSize` frames, in ticks. + */ + int _delays[kDelayListSize]; + + /** + * A list of monotonically increasing identifiers + * used to identify and replace the oldest sample + * in the `_delays` array when finishing the + * next timing operation. + */ + uint _timestamps[kDelayListSize]; + + /** + * The identifier of the oldest timing. + */ + uint _oldestTimestamp; + + /** + * The identifier of the newest timing. + */ + uint _newestTimestamp; + + /** + * Sorts the list of timings. + */ + void sortList(); }; - class RobotAudioTrack : public AudioTrack { + /** + * Calculates the next frame number that needs + * to be rendered, using the timing data + * collected by DelayTime. + */ + uint16 calculateNextFrameNo(const uint32 extraTicks = 0) const; + + /** + * Calculates and returns the number of frames + * that should be rendered in `ticks` time, + * according to the current target frame rate + * of the robot. + */ + uint32 ticksToFrames(const uint32 ticks) const; + + /** + * Gets the current game time, in ticks. + */ + uint32 getTickCount() const; + + /** + * The performance timer for the robot. + */ + DelayTime _delayTime; + +#pragma mark - +#pragma mark Audio +private: + enum { + /** + * The number of ticks that should elapse + * between each AV sync check. + */ + kAudioSyncCheckInterval = 5 * 60 /* 5 seconds */ + }; + + /** + * The status of the audio track of a Robot + * animation. + */ + enum RobotAudioStatus { + kRobotAudioReady = 1, + kRobotAudioStopped = 2, + kRobotAudioPlaying = 3, + kRobotAudioPaused = 4, + kRobotAudioStopping = 5 + }; + +#pragma mark - +#pragma mark Audio - AudioList +private: + /** + * This class manages packetized audio playback + * for robots. + */ + class AudioList { public: - RobotAudioTrack(); - ~RobotAudioTrack(); + AudioList(); + + /** + * Starts playback of robot audio. + */ + void startAudioNow(); + + /** + * Stops playback of robot audio, allowing + * any queued audio to finish playing back. + */ + void stopAudio(); + + /** + * Stops playback of robot audio immediately. + */ + void stopAudioNow(); + + /** + * Submits as many blocks of audio as possible + * to the audio engine. + */ + void submitDriverMax(); + + /** + * Adds a new AudioBlock to the queue. + * + * @param position The absolute position of the + * audio for the block, in compressed bytes. + * @param size The size of the buffer. + * @param buffer A pointer to compressed audio + * data that will be copied into the new + * AudioBlock. + */ + void addBlock(const int position, const int size, const byte *buffer); - Audio::Mixer::SoundType getSoundType() const { return Audio::Mixer::kMusicSoundType; } + /** + * Immediately stops any active playback and + * purges all audio data in the audio list. + */ + void reset(); - void queueBuffer(byte *buffer, int size); + /** + * Pauses the robot audio channel in + * preparation for the first block of audio + * data to be read. + */ + void prepareForPrimer(); - protected: - Audio::AudioStream *getAudioStream() const; + /** + * Sets the audio offset which is used to + * offset the position of audio packets + * sent to the audio stream. + */ + void setAudioOffset(const int offset); + +#pragma mark - +#pragma mark Audio - AudioList - AudioBlock private: - Audio::QueuingAudioStream *_audioStream; + /** + * AudioBlock represents a block of audio + * from the Robot's audio track. + */ + class AudioBlock { + public: + AudioBlock(const int position, const int size, const byte *const data); + ~AudioBlock(); + + /** + * Submits the block of audio to the + * audio manager. + * @returns true if the block was fully + * read, or false if the block was not + * read or only partially read. + */ + bool submit(const int startOffset); + + private: + /** + * The absolute position, in compressed + * bytes, of this audio block's audio + * data in the audio stream. + */ + int _position; + + /** + * The compressed size, in bytes, of + * this audio block's audio data. + */ + int _size; + + /** + * A buffer containing raw + * SOL-compressed audio data. + */ + byte *_data; + }; + + /** + * The list of compressed audio blocks + * submitted for playback. + */ + AudioBlock *_blocks[kAudioListSize]; + + /** + * The number of blocks in `_blocks` that are + * ready to be submitted. + */ + uint8 _blocksSize; + + /** + * The index of the oldest submitted audio block. + */ + uint8 _oldestBlockIndex; + + /** + * The index of the newest submitted audio block. + */ + uint8 _newestBlockIndex; + + /** + * The offset used when sending packets to the + * audio stream. + */ + int _startOffset; + + /** + * The status of robot audio playback. + */ + RobotAudioStatus _status; + + /** + * Frees all audio blocks in the `_blocks` list. + */ + void freeAudioBlocks(); }; - struct RobotHeader { - // 6 bytes, identifier bytes - uint16 version; - uint16 audioChunkSize; - uint16 audioSilenceSize; - // 2 bytes, unknown - uint16 frameCount; - uint16 paletteDataSize; - uint16 unkChunkDataSize; - // 5 bytes, unknown - byte hasSound; - // 34 bytes, unknown - } _header; - - void readHeaderChunk(); - void readFrameSizesChunk(); - - Common::Point _pos; - bool _isBigEndian; - uint32 *_frameTotalSize; - - Common::SeekableSubReadStreamEndian *_fileStream; -}; + /** + * Whether or not this robot animation has + * an audio track. + */ + bool _hasAudio; + + /** + * The audio list for the current robot. + */ + AudioList _audioList; + + /** + * The size, in bytes, of a block of audio data, + * excluding the audio block header. + */ + uint16 _audioBlockSize; + + /** + * The expected size of a block of audio data, + * in bytes, excluding the audio block header. + */ + int16 _expectedAudioBlockSize; + + /** + * The number of compressed audio bytes that are + * needed per frame to fill the audio buffer + * without causing audio to drop out. + */ + int16 _audioRecordInterval; + + /** + * If true, primer audio buffers should be filled + * with silence instead of trying to read buffers + * from the Robot data. + */ + uint16 _primerZeroCompressFlag; + + /** + * The size, in bytes, of the primer audio in the + * Robot, including any extra alignment padding. + */ + uint16 _primerReservedSize; + + /** + * The combined size, in bytes, of the even and odd + * primer channels. + */ + int32 _totalPrimerSize; + + /** + * The absolute offset of the primer audio data in + * the robot data stream. + */ + int32 _primerPosition; + + /** + * The size, in bytes, of the even primer. + */ + int32 _evenPrimerSize; + + /** + * The size, in bytes, of the odd primer. + */ + int32 _oddPrimerSize; + + /** + * The absolute position in the audio stream of + * the first audio packet. + */ + int32 _firstAudioRecordPosition; -} // End of namespace Sci + /** + * A temporary buffer used to hold one frame of + * raw (DPCM-compressed) audio when reading audio + * records from the robot stream. + */ + byte *_audioBuffer; + /** + * The next tick count when AV sync should be + * checked and framerate adjustments made, if + * necessary. + */ + uint32 _checkAudioSyncTime; + + /** + * Primes the audio buffer with the first frame + * of audio data. + * + * @note `primeAudio` was `InitAudio` in SSCI + */ + bool primeAudio(const uint32 startTick); + + /** + * Reads primer data from the robot data stream + * and puts it into the given buffers. + */ + bool readPrimerData(byte *outEvenBuffer, byte *outOddBuffer); + + /** + * Reads audio data for the given frame number + * into the given buffer. + * + * @param outAudioPosition The position of the + * audio, in compressed bytes, in the data stream. + * @param outAudioSize The size of the audio data, + * in compressed bytes. + */ + bool readAudioDataFromRecord(const int frameNo, byte *outBuffer, int &outAudioPosition, int &outAudioSize); + + /** + * Submits part of the audio packet of the given + * frame to the audio list, starting `startPosition` + * bytes into the audio. + */ + bool readPartialAudioRecordAndSubmit(const int startFrame, const int startPosition); + +#pragma mark - +#pragma mark Rendering +public: + /** + * Puts the current dimensions of the robot, in game script + * coordinates, into the given rect, and returns the total + * number of frames in the robot animation. + */ + uint16 getFrameSize(Common::Rect &outRect) const; + + /** + * Pumps the robot player for the next frame of video. + * This is the main rendering function. + */ + void doRobot(); + + /** + * Submits any outstanding audio blocks that should + * be added to the queue before the robot frame + * becomes visible. + */ + void frameAlmostVisible(); + + /** + * Evaluates frame drift and makes modifications to + * the player in order to ensure that future frames + * will arrive on time. + */ + void frameNowVisible(); + + /** + * Scales a vertically compressed cel to its original + * uncompressed dimensions. + */ + void expandCel(byte *target, const byte* source, const int16 celWidth, const int16 celHeight) const; + + /** + * Sets the visual priority of the robot. + * @see Plane::_priority + */ + void setPriority(const int16 newPriority); + +private: + enum CompressionType { + kCompressionLZS = 0, + kCompressionNone = 2 + }; + + /** + * Describes the state of a Robot video cel. + */ + struct CelHandleInfo { + /** + * The persistence level of Robot cels. + */ + enum CelHandleLifetime { + kNoCel = 0, + kFrameLifetime = 1, + kRobotLifetime = 2 + }; + + /** + * A reg_t pointer to an in-memory + * bitmap containing the cel. + */ + reg_t bitmapId; + + /** + * The lifetime of the cel, either just + * for this frame or for the entire + * duration of the robot playback. + */ + CelHandleLifetime status; + + /** + * The size, in pixels, of the decompressed + * cel. + */ + int area; + + CelHandleInfo() : bitmapId(NULL_REG), status(kNoCel), area(0) {} + }; + + typedef Common::Array<ScreenItem *> RobotScreenItemList; + typedef Common::Array<CelHandleInfo> CelHandleList; + typedef Common::Array<int> VideoSizeList; + typedef Common::Array<uint> MaxCelAreaList; + typedef Common::Array<reg_t> FixedCelsList; + typedef Common::Array<Common::Point> CelPositionsList; + typedef Common::Array<byte> ScratchMemory; + + /** + * Renders a version 5/6 robot frame. + */ + void doVersion5(const bool shouldSubmitAudio = true); + + /** + * Creates screen items for a version 5/6 robot. + */ + void createCels5(const byte *rawVideoData, const int16 numCels, const bool usePalette); + + /** + * Creates a single screen item for a cel in a + * version 5/6 robot. + * + * Returns the size, in bytes, of the raw cel data. + */ + uint32 createCel5(const byte *rawVideoData, const int16 screenItemIndex, const bool usePalette); + + /** + * Preallocates memory for the next `numCels` cels + * in the robot data stream. + */ + void preallocateCelMemory(const byte *rawVideoData, const int16 numCels); + + /** + * The decompressor for LZS-compressed cels. + */ + DecompressorLZS _decompressor; + + /** + * The origin of the robot animation, in screen + * coordinates. + */ + Common::Point _position; + + /** + * Global scaling applied to the robot. + */ + ScaleInfo _scaleInfo; + + /** + * The native resolution of the robot. + */ + int16 _xResolution, _yResolution; + + /** + * Whether or not the coordinates read from robot + * data are high resolution. + */ + bool _isHiRes; + + /** + * The maximum number of cels that will be rendered + * on any given frame in this robot. Used for + * preallocation of cel memory. + */ + int16 _maxCelsPerFrame; + + /** + * The maximum areas, in pixels, for each of + * the fixed cels in the robot. Used for + * preallocation of cel memory. + */ + MaxCelAreaList _maxCelArea; + + /** + * The hunk palette to use when rendering the + * current frame, if the `usePalette` flag was set + * in the robot header. + */ + uint8 *_rawPalette; + + /** + * A list of the raw video data sizes, in bytes, + * for each frame of the robot. + */ + VideoSizeList _videoSizes; + + /** + * A list of cels that will be present for the + * entire duration of the robot animation. + */ + FixedCelsList _fixedCels; + + /** + * A list of handles for each cel in the current + * frame. + */ + CelHandleList _celHandles; + + /** + * Scratch memory used to temporarily store + * decompressed cel data for vertically squashed + * cels. + */ + ScratchMemory _celDecompressionBuffer; + + /** + * The size, in bytes, of the squashed cel + * decompression buffer. + */ + int _celDecompressionArea; + + /** + * If true, the robot just started playing and + * is awaiting output for the first frame. + */ + bool _syncFrame; + + /** + * Scratch memory used to store the compressed robot + * video data for the current frame. + */ + ScratchMemory _doVersion5Scratch; + + /** + * When set to a non-negative value, forces the next + * call to doRobot to render the given frame number + * instead of whatever frame would have normally been + * rendered. + */ + mutable int _cueForceShowFrame; + + /** + * The plane where the robot animation will be drawn. + */ + Plane *_plane; + + /** + * A list of pointers to ScreenItems used by the robot. + */ + RobotScreenItemList _screenItemList; + + /** + * The positions of the various screen items in this + * robot, in screen coordinates. + */ + Common::Array<int16> _screenItemX, _screenItemY; + + /** + * The raw position values from the cel header for + * each screen item currently on-screen. + */ + Common::Array<int16> _originalScreenItemX, _originalScreenItemY; + + /** + * The duration of the current robot, in frames. + */ + uint16 _numFramesTotal; + + /** + * The screen priority of the video. + * @see ScreenItem::_priority + */ + int16 _priority; + + /** + * The amount of visual vertical compression applied + * to the current cel. A value of 100 means no + * compression; a value above 100 indicates how much + * the cel needs to be scaled along the y-axis to + * return to its original dimensions. + */ + uint8 _verticalScaleFactor; +}; +} // end of namespace Sci #endif diff --git a/engines/scumm/detection.cpp b/engines/scumm/detection.cpp index 4c9d1221aa..e6740df482 100644 --- a/engines/scumm/detection.cpp +++ b/engines/scumm/detection.cpp @@ -974,7 +974,8 @@ bool ScummMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || (f == kSavesSupportCreationDate) || - (f == kSavesSupportPlayTime); + (f == kSavesSupportPlayTime) || + (f == kSimpleSavesNames); } bool ScummEngine::hasFeature(EngineFeature f) const { diff --git a/engines/scumm/he/intern_he.h b/engines/scumm/he/intern_he.h index c6abac3ecc..8a562ea7b5 100644 --- a/engines/scumm/he/intern_he.h +++ b/engines/scumm/he/intern_he.h @@ -56,9 +56,11 @@ public: Common::Rect _actorClipOverride; // HE specific int _heTimers[16]; + uint32 _pauseStartTime; int getHETimer(int timer); void setHETimer(int timer); + void pauseHETimers(bool pause); public: ScummEngine_v60he(OSystem *syst, const DetectorResult &dr); @@ -94,6 +96,7 @@ protected: Common::WriteStream *openSaveFileForAppending(const byte *fileName); void deleteSaveFile(const byte *fileName); void renameSaveFile(const byte *from, const byte *to); + void pauseEngineIntern(bool pause); Common::SeekableReadStream *openSaveFileForReading(int slot, bool compat, Common::String &fileName); Common::WriteStream *openSaveFileForWriting(int slot, bool compat, Common::String &fileName); @@ -288,6 +291,8 @@ public: virtual byte *getStringAddress(ResId idx); virtual int setupStringArray(int size); + virtual int setupStringArrayFromString(char *cStr); + virtual void getStringFromArray(int arrayNumber, char *buffer, int maxLength); protected: virtual void setupOpcodes(); diff --git a/engines/scumm/he/logic/moonbase_logic.cpp b/engines/scumm/he/logic/moonbase_logic.cpp index 1b596fc54c..c504ad4fe8 100644 --- a/engines/scumm/he/logic/moonbase_logic.cpp +++ b/engines/scumm/he/logic/moonbase_logic.cpp @@ -24,6 +24,10 @@ #include "scumm/he/logic_he.h" #include "scumm/he/moonbase/moonbase.h" #include "scumm/he/moonbase/ai_main.h" +#ifdef USE_SDL_NET +#include "scumm/he/moonbase/net_main.h" +#include "scumm/he/moonbase/net_defines.h" +#endif namespace Scumm { @@ -54,6 +58,45 @@ private: void op_ai_set_type(int op, int numArgs, int32 *args); void op_ai_clean_up(int op, int numArgs, int32 *args); +#ifdef USE_SDL_NET + void op_net_remote_start_script(int op, int numArgs, int32 *args); + void op_net_remote_send_array(int op, int numArgs, int32 *args); + int op_net_remote_start_function(int op, int numArgs, int32 *args); + int op_net_do_init_all(int op, int numArgs, int32 *args); + int op_net_do_init_provider(int op, int numArgs, int32 *args); + int op_net_do_init_session(int op, int numArgs, int32 *args); + int op_net_do_init_user(int op, int numArgs, int32 *args); + int op_net_query_providers(int op, int numArgs, int32 *args); + int op_net_get_provider_name(int op, int numArgs, int32 *args); + int op_net_set_provider(int op, int numArgs, int32 *args); + int op_net_close_provider(int op, int numArgs, int32 *args); + int op_net_start_query_sessions(int op, int numArgs, int32 *args); + int op_net_update_query_sessions(int op, int numArgs, int32 *args); + int op_net_stop_query_sessions(int op, int numArgs, int32 *args); + int op_net_query_sessions(int op, int numArgs, int32 *args); + int op_net_get_session_name(int op, int numArgs, int32 *args); + int op_net_get_session_player_count(int op, int numArgs, int32 *args); + int op_net_destroy_player(int op, int numArgs, int32 *args); + int op_net_get_player_long_name(int op, int numArgs, int32 *args); + int op_net_get_player_short_name(int op, int numArgs, int32 *args); + int op_net_create_session(int op, int numArgs, int32 *args); + int op_net_join_session(int op, int numArgs, int32 *args); + int op_net_end_session(int op, int numArgs, int32 *args); + int op_net_disable_session_player_join(int op, int numArgs, int32 *args); + int op_net_enable_session_player_join(int op, int numArgs, int32 *args); + int op_net_set_ai_player_count(int op, int numArgs, int32 *args); + int op_net_add_user(int op, int numArgs, int32 *args); + int op_net_remove_user(int op, int numArgs, int32 *args); + int op_net_who_sent_this(int op, int numArgs, int32 *args); + int op_net_who_am_i(int op, int numArgs, int32 *args); + int op_net_set_provider_by_name(int op, int numArgs, int32 *args); + void op_net_set_fake_latency(int op, int numArgs, int32 *args); + int op_net_get_host_name(int op, int numArgs, int32 *args); + int op_net_get_ip_from_name(int op, int numArgs, int32 *args); + int op_net_host_tcpip_game(int op, int numArgs, int32 *args); + int op_net_join_tcpip_game(int op, int numArgs, int32 *args); +#endif + private: ScummEngine_v100he *_vm1; }; @@ -107,6 +150,7 @@ int LogicHEmoonbase::versionID() { #define OP_NET_HOST_TCPIP_GAME 1517 #define OP_NET_JOIN_TCPIP_GAME 1518 #define OP_NET_SET_FAKE_LAG 1555 +#define OP_NET_SET_FAKE_LATENCY 1555 /* SET_FAKE_LAG is a valid alias for backwards compatibility */ #define OP_NET_GET_HOST_NAME 1556 #define OP_NET_GET_IP_FROM_NAME 1557 #define OP_NET_GET_SESSION_PLAYER_COUNT 1558 @@ -157,6 +201,86 @@ int32 LogicHEmoonbase::dispatch(int op, int numArgs, int32 *args) { op_ai_clean_up(op, numArgs, args); break; +#ifdef USE_SDL_NET + case OP_NET_REMOTE_START_SCRIPT: + op_net_remote_start_script(op, numArgs, args); + break; + case OP_NET_REMOTE_SEND_ARRAY: + op_net_remote_send_array(op, numArgs, args); + break; + case OP_NET_REMOTE_START_FUNCTION: + return op_net_remote_start_function(op, numArgs, args); + case OP_NET_DO_INIT_ALL: + return op_net_do_init_all(op, numArgs, args); + case OP_NET_DO_INIT_PROVIDER: + return op_net_do_init_provider(op, numArgs, args); + case OP_NET_DO_INIT_SESSION: + return op_net_do_init_session(op, numArgs, args); + case OP_NET_DO_INIT_USER: + return op_net_do_init_user(op, numArgs, args); + case OP_NET_QUERY_PROVIDERS: + return op_net_query_providers(op, numArgs, args); + case OP_NET_GET_PROVIDER_NAME: + return op_net_get_provider_name(op, numArgs, args); + case OP_NET_SET_PROVIDER: + return op_net_set_provider(op, numArgs, args); + case OP_NET_CLOSE_PROVIDER: + return op_net_close_provider(op, numArgs, args); + case OP_NET_START_QUERY_SESSIONS: + return op_net_start_query_sessions(op, numArgs, args); + case OP_NET_UPDATE_QUERY_SESSIONS: + return op_net_update_query_sessions(op, numArgs, args); + case OP_NET_STOP_QUERY_SESSIONS: + return op_net_stop_query_sessions(op, numArgs, args); + case OP_NET_QUERY_SESSIONS: + return op_net_query_sessions(op, numArgs, args); + case OP_NET_GET_SESSION_NAME: + return op_net_get_session_name(op, numArgs, args); + case OP_NET_GET_SESSION_PLAYER_COUNT: + return op_net_get_session_player_count(op, numArgs, args); + case OP_NET_DESTROY_PLAYER: + return op_net_destroy_player(op, numArgs, args); +#if 1 // 12/2/99 BPT + case OP_NET_GET_PLAYER_LONG_NAME: + return op_net_get_player_long_name(op, numArgs, args); + case OP_NET_GET_PLAYER_SHORT_NAME: + return op_net_get_player_short_name(op, numArgs, args); +#endif + case OP_NET_CREATE_SESSION: + return op_net_create_session(op, numArgs, args); + case OP_NET_JOIN_SESSION: + return op_net_join_session(op, numArgs, args); + case OP_NET_END_SESSION: + return op_net_end_session(op, numArgs, args); + case OP_NET_DISABLE_SESSION_PLAYER_JOIN: + return op_net_disable_session_player_join(op, numArgs, args); + case OP_NET_ENABLE_SESSION_PLAYER_JOIN: + return op_net_enable_session_player_join(op, numArgs, args); + case OP_NET_SET_AI_PLAYER_COUNT: + return op_net_set_ai_player_count(op, numArgs, args); + case OP_NET_ADD_USER: + return op_net_add_user(op, numArgs, args); + case OP_NET_REMOVE_USER: + return op_net_remove_user(op, numArgs, args); + case OP_NET_WHO_SENT_THIS: + return op_net_who_sent_this(op, numArgs, args); + case OP_NET_WHO_AM_I: + return op_net_who_am_i(op, numArgs, args); + case OP_NET_SET_PROVIDER_BY_NAME: + return op_net_set_provider_by_name(op, numArgs, args); + case OP_NET_SET_FAKE_LATENCY: // SET_FAKE_LAG is a valid alias for backwards compatibility + op_net_set_fake_latency(op, numArgs, args); + break; + case OP_NET_GET_HOST_NAME: + return op_net_get_host_name(op, numArgs, args); + case OP_NET_GET_IP_FROM_NAME: + return op_net_get_ip_from_name(op, numArgs, args); + case OP_NET_HOST_TCPIP_GAME: + return op_net_host_tcpip_game(op, numArgs, args); + case OP_NET_JOIN_TCPIP_GAME: + return op_net_join_tcpip_game(op, numArgs, args); +#endif + default: LogicHE::dispatch(op, numArgs, args); } @@ -248,6 +372,193 @@ void LogicHEmoonbase::op_ai_clean_up(int op, int numArgs, int32 *args) { _vm1->_moonbase->_ai->cleanUpAI(); } +#ifdef USE_SDL_NET +void LogicHEmoonbase::op_net_remote_start_script(int op, int numArgs, int32 *args) { + _vm1->_moonbase->_net->remoteStartScript(args[0], args[1], args[2], numArgs - 3, &args[3]); +} + +void LogicHEmoonbase::op_net_remote_send_array(int op, int numArgs, int32 *args) { + _vm1->_moonbase->_net->remoteSendArray(args[0], args[1], args[2], args[3]); +} + +int LogicHEmoonbase::op_net_remote_start_function(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->remoteStartScriptFunction(args[0], args[1], args[2], args[3], numArgs - 4, &args[4]); +} + +int LogicHEmoonbase::op_net_do_init_all(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->initAll(); +} + +int LogicHEmoonbase::op_net_do_init_provider(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->initProvider(); +} + +int LogicHEmoonbase::op_net_do_init_session(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->initSession(); +} + +int LogicHEmoonbase::op_net_do_init_user(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->initUser(); +} + +int LogicHEmoonbase::op_net_query_providers(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->queryProviders(); +} + +int LogicHEmoonbase::op_net_get_provider_name(int op, int numArgs, int32 *args) { + char name[MAX_PROVIDER_NAME]; + _vm1->_moonbase->_net->getProviderName(args[0] - 1, name, sizeof(name)); + return _vm1->setupStringArrayFromString(name); +} + +int LogicHEmoonbase::op_net_set_provider(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->setProvider(args[0] - 1); +} + +int LogicHEmoonbase::op_net_close_provider(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->closeProvider(); +} + +int LogicHEmoonbase::op_net_start_query_sessions(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->startQuerySessions(); +} + +int LogicHEmoonbase::op_net_update_query_sessions(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->updateQuerySessions(); +} + +int LogicHEmoonbase::op_net_stop_query_sessions(int op, int numArgs, int32 *args) { + _vm1->_moonbase->_net->stopQuerySessions(); + return 1; +} + +int LogicHEmoonbase::op_net_query_sessions(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->querySessions(); +} + +int LogicHEmoonbase::op_net_get_session_name(int op, int numArgs, int32 *args) { + char name[MAX_PROVIDER_NAME]; + _vm1->_moonbase->_net->getSessionName(args[0] - 1, name, sizeof(name)); + return _vm1->setupStringArrayFromString(name); +} + +int LogicHEmoonbase::op_net_get_session_player_count(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->getSessionPlayerCount(args[0] - 1); +} + +int LogicHEmoonbase::op_net_destroy_player(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->destroyPlayer(args[0]); +} + +int LogicHEmoonbase::op_net_get_player_long_name(int op, int numArgs, int32 *args) { + return _vm1->setupStringArrayFromString("long name"); // TODO: gdefMultiPlay.playername1 +} + +int LogicHEmoonbase::op_net_get_player_short_name(int op, int numArgs, int32 *args) { + return _vm1->setupStringArrayFromString("short"); // TODO: gdefMultiPlay.playername2 +} + +int LogicHEmoonbase::op_net_create_session(int op, int numArgs, int32 *args) { + char name[MAX_SESSION_NAME]; + _vm1->getStringFromArray(args[0], name, sizeof(name)); + return _vm1->_moonbase->_net->createSession(name); +} + +int LogicHEmoonbase::op_net_join_session(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->joinSession(args[0] - 1); +} + +int LogicHEmoonbase::op_net_end_session(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->endSession(); +} + +int LogicHEmoonbase::op_net_disable_session_player_join(int op, int numArgs, int32 *args) { + _vm1->_moonbase->_net->disableSessionJoining(); + return 1; +} + +int LogicHEmoonbase::op_net_enable_session_player_join(int op, int numArgs, int32 *args) { + _vm1->_moonbase->_net->enableSessionJoining(); + return 1; +} + +int LogicHEmoonbase::op_net_set_ai_player_count(int op, int numArgs, int32 *args) { + _vm1->_moonbase->_net->setBotsCount(args[0]); + return 1; +} + +int LogicHEmoonbase::op_net_add_user(int op, int numArgs, int32 *args) { + char userName[MAX_PLAYER_NAME]; + _vm1->getStringFromArray(args[0], userName, sizeof(userName)); + return _vm1->_moonbase->_net->addUser(userName, userName); +} + +int LogicHEmoonbase::op_net_remove_user(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->removeUser(); +} + +int LogicHEmoonbase::op_net_who_sent_this(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->whoSentThis(); +} + +int LogicHEmoonbase::op_net_who_am_i(int op, int numArgs, int32 *args) { + return _vm1->_moonbase->_net->whoAmI(); +} + +int LogicHEmoonbase::op_net_set_provider_by_name(int op, int numArgs, int32 *args) { + // Parameter 1 is the provider name and + // Parameter 2 is the (optional) tcp/ip address + return _vm1->_moonbase->_net->setProviderByName(args[0], args[1]); +} + +void LogicHEmoonbase::op_net_set_fake_latency(int op, int numArgs, int32 *args) { + _vm1->_moonbase->_net->setFakeLatency(args[0]); +} + +int LogicHEmoonbase::op_net_get_host_name(int op, int numArgs, int32 *args) { + char name[MAX_HOSTNAME_SIZE]; + + if (_vm1->_moonbase->_net->getHostName(name, MAX_HOSTNAME_SIZE)) { + return _vm1->setupStringArrayFromString(name); + } + + return 0; +} + +int LogicHEmoonbase::op_net_get_ip_from_name(int op, int numArgs, int32 *args) { + char name[MAX_HOSTNAME_SIZE]; + _vm1->getStringFromArray(args[0], name, sizeof(name)); + + char ip[MAX_IP_SIZE]; + + if (_vm1->_moonbase->_net->getIPfromName(ip, MAX_IP_SIZE, name)) { + return _vm1->setupStringArrayFromString(ip); + } + + return 0; +} + +int LogicHEmoonbase::op_net_host_tcpip_game(int op, int numArgs, int32 *args) { + char sessionName[MAX_SESSION_NAME]; + char userName[MAX_PLAYER_NAME]; + + _vm1->getStringFromArray(args[0], sessionName, sizeof(sessionName)); + _vm1->getStringFromArray(args[1], userName, sizeof(userName)); + + return _vm1->_moonbase->_net->hostGame(sessionName, userName); +} + +int LogicHEmoonbase::op_net_join_tcpip_game(int op, int numArgs, int32 *args) { + char ip[MAX_IP_SIZE]; + char userName[MAX_PLAYER_NAME]; + + _vm1->getStringFromArray(args[0], ip, sizeof(ip)); + _vm1->getStringFromArray(args[1], userName, sizeof(userName)); + + return _vm1->_moonbase->_net->joinGame(ip, userName); +} +#endif + LogicHE *makeLogicHEmoonbase(ScummEngine_v100he *vm) { return new LogicHEmoonbase(vm); } diff --git a/engines/scumm/he/moonbase/moonbase.cpp b/engines/scumm/he/moonbase/moonbase.cpp index 15ababd321..941f32db23 100644 --- a/engines/scumm/he/moonbase/moonbase.cpp +++ b/engines/scumm/he/moonbase/moonbase.cpp @@ -23,6 +23,9 @@ #include "scumm/he/intern_he.h" #include "scumm/he/moonbase/moonbase.h" #include "scumm/he/moonbase/ai_main.h" +#ifdef USE_SDL_NET +#include "scumm/he/moonbase/net_main.h" +#endif namespace Scumm { @@ -30,10 +33,16 @@ Moonbase::Moonbase(ScummEngine_v100he *vm) : _vm(vm) { initFOW(); _ai = new AI(_vm); +#ifdef USE_SDL_NET + _net = new Net(_vm); +#endif } Moonbase::~Moonbase() { delete _ai; +#ifdef USE_SDL_NET + delete _net; +#endif } int Moonbase::readFromArray(int array, int y, int x) { diff --git a/engines/scumm/he/moonbase/moonbase.h b/engines/scumm/he/moonbase/moonbase.h index 71c03cb007..f3399ef192 100644 --- a/engines/scumm/he/moonbase/moonbase.h +++ b/engines/scumm/he/moonbase/moonbase.h @@ -30,6 +30,7 @@ namespace Scumm { class AI; +class Net; class Moonbase { public: @@ -71,6 +72,9 @@ public: uint32 _fowSentinelConditionBits; AI *_ai; +#ifdef USE_SDL_NET + Net *_net; +#endif private: ScummEngine_v100he *_vm; diff --git a/engines/scumm/he/moonbase/net_defines.h b/engines/scumm/he/moonbase/net_defines.h new file mode 100644 index 0000000000..130ca1db94 --- /dev/null +++ b/engines/scumm/he/moonbase/net_defines.h @@ -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 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. + * + */ + +#ifndef SCUMM_HE_MOONBASE_NET_DEFINES_H +#define SCUMM_HE_MOONBASE_NET_DEFINES_H + +namespace Scumm { + +// pnetwin.h + +#define PN_PRIORITY_HIGH 0x00000001 + +#define PN_SENDTYPE_INDIVIDUAL 1 +#define PN_SENDTYPE_GROUP 2 +#define PN_SENDTYPE_HOST 3 +#define PN_SENDTYPE_ALL 4 + +#define MAX_GAME_NAME 128 /* Used for the multiplayer networking code */ +#define MAX_PLAYER_NAME 128 /* Used for the multiplayer networking code */ +#define MAX_PROVIDER_NAME 128 +#define MAX_SESSION_NAME 128 + +#define MAX_GAMES_POLLED 16 +#define MAX_PROVIDERS 16 + +#define PACKETTYPE_REMOTESTARTSCRIPT 1 +#define PACKETTYPE_REMOTESTARTSCRIPTRETURN 2 +#define PACKETTYPE_REMOTESTARTSCRIPTRESULT 3 +#define PACKETTYPE_REMOTESENDSCUMMARRAY 4 + +const int SESSION_ERROR = 0; +const int USER_CREATED_SESSION = 1; +const int USER_JOINED_SESSION = 2; + +const int TCPIP_PROVIDER = -1; +const int NO_PROVIDER = -2; + +const int MAX_PACKET_SIZE = 4096; // bytes +const int MAX_HOSTNAME_SIZE = 256; +const int MAX_IP_SIZE = 32; +const char LOCAL_HOST[] = "127.0.0.1"; //localhost + +#define NULL_IP ""; //no IP address (causes enumsessions to search local subnet) + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/he/moonbase/net_main.cpp b/engines/scumm/he/moonbase/net_main.cpp new file mode 100644 index 0000000000..cdc2eef333 --- /dev/null +++ b/engines/scumm/he/moonbase/net_main.cpp @@ -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 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 "scumm/he/intern_he.h" +#include "scumm/he/moonbase/moonbase.h" +#include "scumm/he/moonbase/net_main.h" + +namespace Scumm { + +Net::Net(ScummEngine_v100he *vm) : _latencyTime(1), _fakeLatency(false), _vm(vm) { + //some defaults for fields +} + +int Net::hostGame(char *sessionName, char *userName) { + warning("STUB: op_net_host_tcpip_game(\"%s\", \"%s\")", sessionName, userName); // PN_HostTCPIPGame + return 0; +} + +int Net::joinGame(char *IP, char *userName) { + warning("STUB: Net::joinGame(\"%s\", \"%s\")", IP, userName); // PN_JoinTCPIPGame + return 0; +} + +int Net::addUser(char *shortName, char *longName) { + warning("STUB: Net::addUser(\"%s\", \"%s\")", shortName, longName); // PN_AddUser + return 0; +} + +int Net::removeUser() { + warning("STUB: Net::removeUser()"); // PN_RemoveUser + return 0; +} + +int Net::whoSentThis() { + warning("STUB: Net::whoSentThis()"); // PN_WhoSentThis + return 0; +} + +int Net::whoAmI() { + warning("STUB: Net::whoAmI()"); // PN_WhoAmI + return 0; +} + +int Net::createSession(char *name) { + warning("STUB: Net::createSession(\"%s\")", name); // PN_CreateSession + return 0; +} + +int Net::joinSession(int sessionIndex) { + warning("STUB: Net::joinSession(%d)", sessionIndex); // PN_JoinSession + return 0; +} + +int Net::endSession() { + warning("STUB: Net::endSession()"); // PN_EndSession + return 0; +} + +void Net::disableSessionJoining() { + warning("STUB: Net::disableSessionJoining()"); // PN_DisableSessionPlayerJoin +} + +void Net::enableSessionJoining() { + warning("STUB: Net::enableSessionJoining()"); // PN_EnableSessionPlayerJoin +} + +void Net::setBotsCount(int botsCount) { + warning("STUB: Net::setBotsCount(%d)", botsCount); // PN_SetAIPlayerCountKludge +} + +int32 Net::setProviderByName(int32 parameter1, int32 parameter2) { + warning("STUB: Net::setProviderByName(%d, %d)", parameter1, parameter2); // PN_SetProviderByName + return 0; +} + +void Net::setFakeLatency(int time) { + _latencyTime = time; + debug("NETWORK: Setting Fake Latency to %d ms \n", _latencyTime); // TODO: is it OK to use debug instead of SPUTM_xprintf? + _fakeLatency = true; +} + +bool Net::destroyPlayer(int32 playerDPID) { + // bool PNETWIN_destroyplayer(DPID idPlayer) + warning("STUB: Net::destroyPlayer(%d)", playerDPID); + return false; +} + +int32 Net::startQuerySessions() { + warning("STUB: Net::startQuerySessions()"); // StartQuerySessions + return 0; +} + +int32 Net::updateQuerySessions() { + warning("STUB: Net::updateQuerySessions()"); // UpdateQuerySessions + return 0; +} + +void Net::stopQuerySessions() { + warning("STUB: Net::stopQuerySessions()"); // StopQuerySessions +} + +int Net::querySessions() { + warning("STUB: Net::querySessions()"); // PN_QuerySessions + return 0; +} + +int Net::queryProviders() { + warning("STUB: Net::queryProviders()"); // PN_QueryProviders + return 0; +} + +int Net::setProvider(int providerIndex) { + warning("STUB: Net::setProvider(%d)", providerIndex); // PN_SetProvider + return 0; +} + +int Net::closeProvider() { + warning("STUB: Net::closeProvider()"); // PN_CloseProvider + return 0; +} + +bool Net::initAll() { + warning("STUB: Net::initAll()"); // PN_DoInitAll + return false; +} + +bool Net::initProvider() { + warning("STUB: Net::initProvider()"); // PN_DoInitProvider + return false; +} + +bool Net::initSession() { + warning("STUB: Net::initSession()"); // PN_DoInitSession + return false; +} + +bool Net::initUser() { + warning("STUB: Net::initUser()"); // PN_DoInitUser + return false; +} + +void Net::remoteStartScript(int typeOfSend, int sendTypeParam, int priority, int argsCount, int32 *args) { + warning("STUB: Net::remoteStartScript(%d, %d, %d, %d, ...)", typeOfSend, sendTypeParam, priority, argsCount); // PN_RemoteStartScriptCommand +} + +void Net::remoteSendArray(int typeOfSend, int sendTypeParam, int priority, int arrayIndex) { + warning("STUB: Net::remoteSendArray(%d, %d, %d, %d)", typeOfSend, sendTypeParam, priority, arrayIndex); // PN_RemoteSendArrayCommand +} + +int Net::remoteStartScriptFunction(int typeOfSend, int sendTypeParam, int priority, int defaultReturnValue, int argsCount, int32 *args) { + warning("STUB: Net::remoteStartScriptFunction(%d, %d, %d, %d, %d, ...)", typeOfSend, sendTypeParam, priority, defaultReturnValue, argsCount); // PN_RemoteStartScriptFunction + return 0; +} + +bool Net::getHostName(char *hostname, int length) { + warning("STUB: Net::getHostName(\"%s\", %d)", hostname, length); // PN_GetHostName + return false; +} + +bool Net::getIPfromName(char *ip, int ipLength, char *nameBuffer) { + warning("STUB: Net::getIPfromName(\"%s\", %d, \"%s\")", ip, ipLength, nameBuffer); // PN_GetIPfromName + return false; +} + +void Net::getSessionName(int sessionNumber, char *buffer, int length) { + warning("STUB: Net::getSessionName(%d, \"%s\", %d)", sessionNumber, buffer, length); // PN_GetSessionName +} + +int Net::getSessionPlayerCount(int sessionNumber) { + warning("STUB: Net::getSessionPlayerCount(%d)", sessionNumber); // case GET_SESSION_PLAYER_COUNT_KLUDGE: + //assert(sessionNumber >= 0 && sessionNumber < NUMELEMENTS(gdefMultiPlay.gamedescptr)); + //return gdefMultiPlay.gamedescptr[sessionNumber].currentplayers; + return 0; +} + +void Net::getProviderName(int providerIndex, char *buffer, int length) { + warning("STUB: Net::getProviderName(%d, \"%s\", %d)", providerIndex, buffer, length); // PN_GetProviderName +} + +} // End of namespace Scumm diff --git a/engines/scumm/he/moonbase/net_main.h b/engines/scumm/he/moonbase/net_main.h new file mode 100644 index 0000000000..8350904fcd --- /dev/null +++ b/engines/scumm/he/moonbase/net_main.h @@ -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 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. + * + */ + +#ifndef SCUMM_HE_MOONBASE_NET_MAIN_H +#define SCUMM_HE_MOONBASE_NET_MAIN_H + +namespace Scumm { + +class ScummEngine_v100he; + +//this is a dummy based on ai_main.h Scumm::AI + +class Net { +public: + Net(ScummEngine_v100he *vm); + + int hostGame(char *sessionName, char *userName); + int joinGame(char *IP, char *userName); + int addUser(char *shortName, char *longName); + int removeUser(); + int whoSentThis(); + int whoAmI(); + int createSession(char *name); + int joinSession(int sessionIndex); + int endSession(); + void disableSessionJoining(); + void enableSessionJoining(); + void setBotsCount(int botsCount); + int32 setProviderByName(int32 parameter1, int32 parameter2); + void setFakeLatency(int time); + bool destroyPlayer(int32 playerDPID); + int32 startQuerySessions(); + int32 updateQuerySessions(); + void stopQuerySessions(); + int querySessions(); + int queryProviders(); + int setProvider(int providerIndex); + int closeProvider(); + bool initAll(); + bool initProvider(); + bool initSession(); + bool initUser(); + void remoteStartScript(int typeOfSend, int sendTypeParam, int priority, int argsCount, int32 *args); + void remoteSendArray(int typeOfSend, int sendTypeParam, int priority, int arrayIndex); + int remoteStartScriptFunction(int typeOfSend, int sendTypeParam, int priority, int defaultReturnValue, int argsCount, int32 *args); + +private: + +public: + //getters + bool getHostName(char *hostname, int length); + bool getIPfromName(char *ip, int ipLength, char *nameBuffer); + void getSessionName(int sessionNumber, char *buffer, int length); + int getSessionPlayerCount(int sessionNumber); + void getProviderName(int providerIndex, char *buffer, int length); + +private: + //mostly getters + +public: + //fields + int _latencyTime; // ms + bool _fakeLatency; + + ScummEngine_v100he *_vm; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/he/script_v72he.cpp b/engines/scumm/he/script_v72he.cpp index d32eb766cb..c764de7da1 100644 --- a/engines/scumm/he/script_v72he.cpp +++ b/engines/scumm/he/script_v72he.cpp @@ -230,6 +230,23 @@ int ScummEngine_v72he::setupStringArray(int size) { return readVar(0); } +int ScummEngine_v72he::setupStringArrayFromString(char *cStr) { + // this is PUI_ScummStringArrayFromCString() found in PUSERMAC.cpp + // I can see how its done up there in setupStringArray() + // yet I'd note that 'SCUMMVAR_user_reserved' var was used instead of 0 + // and strlen(), not strlen() + 1 was used + // plus, this function actually copies the string, not just 'sets up' the array + + writeVar(0, 0); + + int len = strlen(cStr) + 1; + byte *ptr = defineArray(0, kStringArray, 0, 0, 0, len); + if (ptr != nullptr) + Common::strlcpy((char*)ptr, cStr, len); + + return readVar(0); +} + void ScummEngine_v72he::readArrayFromIndexFile() { int num; int a, b, c; @@ -1484,6 +1501,26 @@ void ScummEngine_v72he::writeFileFromArray(int slot, int32 resID) { } } +void ScummEngine_v72he::getStringFromArray(int arrayNumber, char *buffer, int maxLength) { + // I'm not really sure it belongs here and not some other version + // this is ARRAY_GetStringFromArray() from ARRAYS.cpp of SPUTM + + // this function makes a C-string out of <arrayNumber> contents + + VAR(0) = arrayNumber; // it was 0 in original code, but I've seen ScummVM Moonbase code which uses VAR_U32_ARRAY_UNK + + int i, ch; + for (i = 0; i < maxLength; ++i) { + if (!(ch = readArray(0, 0, i))) { + break; + } + + buffer[i] = ch; + } + + buffer[i] = 0; +} + void ScummEngine_v72he::o72_writeFile() { int32 resID = pop(); int slot = pop(); diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index 85ff1aa4cd..fee61ec29f 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -150,6 +150,11 @@ MODULE_OBJS += \ he/moonbase/distortion.o \ he/moonbase/moonbase.o \ he/moonbase/moonbase_fow.o + +ifdef USE_SDL_NET +MODULE_OBJS += \ + he/moonbase/net_main.o +endif endif # This module can be built as a plugin diff --git a/engines/scumm/object.cpp b/engines/scumm/object.cpp index da94a34baf..cbc24a8b7e 100644 --- a/engines/scumm/object.cpp +++ b/engines/scumm/object.cpp @@ -110,6 +110,16 @@ void ScummEngine::setOwnerOf(int obj, int owner) { // This causes it to try to remove object 0 from the inventory. if (_game.id == GID_PASS && obj == 0 && vm.slot[_currentScript].number == 94) return; + + // WORKAROUND for bug #6802: assert() was triggered in freddi2. + // Bug is in room 39. Problem is script 10, in the localvar2==78 case; + // this only sets the obj id if var198 is non-zero, but in the asserting + // case, it is obj 0. That means two setOwnerOf calls are made with obj 0. + // The correct setOwnerOf calls are made afterwards, so just ignoring this + // seems to work just fine. + if (_game.id == GID_HEGAME && obj == 0 && _currentRoom == 39 && vm.slot[_currentScript].number == 10) + return; + assert(obj > 0); if (owner == 0) { diff --git a/engines/scumm/script_v6.cpp b/engines/scumm/script_v6.cpp index 6c81f17f2f..62c62c0b4a 100644 --- a/engines/scumm/script_v6.cpp +++ b/engines/scumm/script_v6.cpp @@ -707,6 +707,17 @@ void ScummEngine_v6::o6_ifNot() { void ScummEngine_v6::o6_jump() { int offset = fetchScriptWordSigned(); + // WORKAROUND bug #6097: Pressing escape at the lake side entrance of + // the cave while Putt Putt is not on solid ground and still talking + // will cause the raft to disappear. This is a script bug in the + // original game and affects several versions. + if (_game.id == GID_PUTTZOO) { + if (_game.heversion == 73 && vm.slot[_currentScript].number == 206 && offset == 176 && !isScriptRunning(202)) + _scummVars[244] = 35; + if (_game.features & GF_HE_985 && vm.slot[_currentScript].number == 2054 && offset == 178 && !isScriptRunning(2050)) + _scummVars[202] = 35; + } + // WORKAROUND bug #2826144: Talking to the guard at the bigfoot party, after // he's let you inside, will cause the game to hang, if you end the conversation. // This is a script bug, due to a missing jump in one segment of the script. diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h index 68e4887b00..81372d0586 100644 --- a/engines/scumm/scumm-md5.h +++ b/engines/scumm/scumm-md5.h @@ -1,5 +1,5 @@ /* - This file was generated by the md5table tool on Sat Apr 30 14:24:41 2016 + This file was generated by the md5table tool on Mon Aug 8 18:46:17 2016 DO NOT EDIT MANUALLY! */ @@ -188,6 +188,7 @@ static const MD5Table md5table[] = { { "3b301b7892f883ce42ab4be6a274fea6", "samnmax", "Floppy", "Floppy", -1, Common::EN_ANY, Common::kPlatformDOS }, { "3b832f4a90740bf22e9b8ed42ca0128c", "freddi4", "HE 99", "", -1, Common::EN_GRB, Common::kPlatformUnknown }, { "3c4c471342bd95505a42334367d8f127", "puttmoon", "HE 70", "", 12161, Common::RU_RUS, Common::kPlatformWindows }, + { "3c90d2a39cafa60b8ebce70a34a59a41", "airport", "", "Demo", 51152, Common::NL_NLD, Common::kPlatformWindows }, { "3cce1913a3bc586b51a75c3892ff18dd", "indy3", "VGA", "VGA", -1, Common::RU_RUS, Common::kPlatformDOS }, { "3cf4b6ff78f735b671d8ccc2bc110b15", "maniac", "V2", "V2", -1, Common::ES_ESP, Common::kPlatformAmiga }, { "3d219e7546039543307b55a91282bf18", "funpack", "", "", -1, Common::EN_ANY, Common::kPlatformDOS }, diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 72c6909f8c..107228453e 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -2589,6 +2589,30 @@ void ScummEngine_v60he::setHETimer(int timer) { _heTimers[timer] = _system->getMillis(); } +void ScummEngine_v60he::pauseHETimers(bool pause) { + // The HE timers rely on system time which of course doesn't pause when + // the engine does. By adding the elapsed time we compensate for this. + // Fixes bug #6352 + if (pause) { + // Pauses can be layered, we only need the start of the first + if (!_pauseStartTime) + _pauseStartTime = _system->getMillis(); + } else { + int elapsedTime = _system->getMillis() - _pauseStartTime; + for (int i = 0; i < ARRAYSIZE(_heTimers); i++) { + if (_heTimers[i] != 0) + _heTimers[i] += elapsedTime; + } + _pauseStartTime = 0; + } +} + +void ScummEngine_v60he::pauseEngineIntern(bool pause) { + pauseHETimers(pause); + + ScummEngine::pauseEngineIntern(pause); +} + void ScummEngine::pauseGame() { pauseDialog(); } diff --git a/engines/sherlock/detection.cpp b/engines/sherlock/detection.cpp index 5a94b3485f..c6e632f999 100644 --- a/engines/sherlock/detection.cpp +++ b/engines/sherlock/detection.cpp @@ -199,7 +199,8 @@ bool SherlockMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsLoadingDuringStartup) || (f == kSupportsDeleteSave) || (f == kSavesSupportMetaInfo) || - (f == kSavesSupportThumbnail); + (f == kSavesSupportThumbnail) || + (f == kSimpleSavesNames); } bool Sherlock::SherlockEngine::hasFeature(EngineFeature f) const { diff --git a/engines/sky/detection.cpp b/engines/sky/detection.cpp index 4b91f50a61..d86689e5d7 100644 --- a/engines/sky/detection.cpp +++ b/engines/sky/detection.cpp @@ -78,7 +78,7 @@ public: virtual bool hasFeature(MetaEngineFeature f) const; virtual GameList getSupportedGames() const; virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const; - virtual GameDescriptor findGame(const char *gameid) const; + virtual GameDescriptor findGame(const char *gameid) const; virtual GameList detectGames(const Common::FSList &fslist) const; virtual Common::Error createInstance(OSystem *syst, Engine **engine) const; diff --git a/engines/sword2/sword2.cpp b/engines/sword2/sword2.cpp index 44371bf6cf..4f3caa437e 100644 --- a/engines/sword2/sword2.cpp +++ b/engines/sword2/sword2.cpp @@ -107,7 +107,8 @@ bool Sword2MetaEngine::hasFeature(MetaEngineFeature f) const { return (f == kSupportsListSaves) || (f == kSupportsLoadingDuringStartup) || - (f == kSupportsDeleteSave); + (f == kSupportsDeleteSave) || + (f == kSimpleSavesNames); } bool Sword2::Sword2Engine::hasFeature(EngineFeature f) const { diff --git a/engines/testbed/cloud.cpp b/engines/testbed/cloud.cpp new file mode 100644 index 0000000000..a2d62733be --- /dev/null +++ b/engines/testbed/cloud.cpp @@ -0,0 +1,565 @@ +/* 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/config-manager.h" +#include "common/stream.h" +#include "common/util.h" +#include "testbed/fs.h" +#include "testbed/cloud.h" +#include <backends/cloud/cloudmanager.h> + +namespace Testbed { + +CloudTestSuite::CloudTestSuite() { + // Cloud tests depend on CloudMan. + // If there is no Storage connected to it, disable this test suite. + if (CloudMan.getCurrentStorage() == nullptr) { + logPrintf("WARNING! : No Storage connected to CloudMan found. Skipping Cloud tests\n"); + Testsuite::enable(false); + } + + addTest("UserInfo", &CloudTests::testInfo, true); + addTest("ListDirectory", &CloudTests::testDirectoryListing, true); + addTest("CreateDirectory", &CloudTests::testDirectoryCreating, true); + addTest("FileUpload", &CloudTests::testUploading, true); + addTest("FileDownload", &CloudTests::testDownloading, true); + addTest("FolderDownload", &CloudTests::testFolderDownloading, true); + addTest("SyncSaves", &CloudTests::testSavesSync, true); +} + +/* +void CloudTestSuite::enable(bool flag) { + Testsuite::enable(ConfParams.isGameDataFound() ? flag : false); +} +*/ + +///// TESTS GO HERE ///// + +bool CloudTests::waitForCallback() { + const int TIMEOUT = 30; + + Common::Point pt; + pt.x = 10; pt.y = 10; + Testsuite::writeOnScreen("Waiting for callback...", pt); + + int left = TIMEOUT; + while (--left) { + if (ConfParams.isCloudTestCallbackCalled()) return true; + if (ConfParams.isCloudTestErrorCallbackCalled()) return true; + g_system->delayMillis(1000); + } + return false; +} + +bool CloudTests::waitForCallbackMore() { + while (!waitForCallback()) { + Common::String info = "It takes more time than expected. Do you want to skip the test or wait more?"; + if (Testsuite::handleInteractiveInput(info, "Wait", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : info()\n"); + return false; + } + } + return true; +} + +const char *CloudTests::getRemoteTestPath() { + if (CloudMan.getStorageIndex() == Cloud::kStorageDropboxId) + return "/testbed"; + return "testbed"; +} + +void CloudTests::infoCallback(Cloud::Storage::StorageInfoResponse response) { + ConfParams.setCloudTestCallbackCalled(true); + Testsuite::logPrintf("Info! User's ID: %s\n", response.value.uid().c_str()); + Testsuite::logPrintf("Info! User's email: %s\n", response.value.email().c_str()); + Testsuite::logPrintf("Info! User's name: %s\n", response.value.name().c_str()); + Testsuite::logPrintf("Info! User's quota: %lu bytes used / %lu bytes available\n", response.value.used(), response.value.available()); +} + +void CloudTests::directoryListedCallback(Cloud::Storage::FileArrayResponse response) { + ConfParams.setCloudTestCallbackCalled(true); + if (response.value.size() == 0) { + Testsuite::logPrintf("Warning! Directory is empty!\n"); + return; + } + + Common::String directory, file; + uint32 directories = 0, files = 0; + for (uint32 i = 0; i < response.value.size(); ++i) { + if (response.value[i].isDirectory()) { + if (++directories == 1) directory = response.value[i].path(); + } else { + if (++files == 1) file = response.value[i].name(); + } + } + + if (directories == 0) { + Testsuite::logPrintf("Info! %u files listed, first one is '%s'\n", files, file.c_str()); + } else if (files == 0) { + Testsuite::logPrintf("Info! %u directories listed, first one is '%s'\n", directories, directory.c_str()); + } else { + Testsuite::logPrintf("Info! %u directories and %u files listed\n", directories, files); + Testsuite::logPrintf("Info! First directory is '%s' and first file is '%s'\n", directory.c_str(), file.c_str()); + } +} + +void CloudTests::directoryCreatedCallback(Cloud::Storage::BoolResponse response) { + ConfParams.setCloudTestCallbackCalled(true); + if (response.value) { + Testsuite::logPrintf("Info! Directory created!\n"); + } else { + Testsuite::logPrintf("Info! Such directory already exists!\n"); + } +} + +void CloudTests::fileUploadedCallback(Cloud::Storage::UploadResponse response) { + ConfParams.setCloudTestCallbackCalled(true); + Testsuite::logPrintf("Info! Uploaded file into '%s'\n", response.value.path().c_str()); + Testsuite::logPrintf("Info! It's id = '%s' and size = '%u'\n", response.value.id().c_str(), response.value.size()); +} + +void CloudTests::fileDownloadedCallback(Cloud::Storage::BoolResponse response) { + ConfParams.setCloudTestCallbackCalled(true); + if (response.value) { + Testsuite::logPrintf("Info! File downloaded!\n"); + } else { + Testsuite::logPrintf("Info! Failed to download the file!\n"); + } +} + +void CloudTests::directoryDownloadedCallback(Cloud::Storage::FileArrayResponse response) { + ConfParams.setCloudTestCallbackCalled(true); + if (response.value.size() == 0) { + Testsuite::logPrintf("Info! Directory is downloaded successfully!\n"); + } else { + Testsuite::logPrintf("Warning! %u files were not downloaded during folder downloading!\n", response.value.size()); + } +} + +void CloudTests::savesSyncedCallback(Cloud::Storage::BoolResponse response) { + ConfParams.setCloudTestCallbackCalled(true); + if (response.value) { + Testsuite::logPrintf("Info! Saves are synced successfully!\n"); + } else { + Testsuite::logPrintf("Warning! Saves were not synced!\n"); + } +} + +void CloudTests::errorCallback(Networking::ErrorResponse response) { + ConfParams.setCloudTestErrorCallbackCalled(true); + Testsuite::logPrintf("Info! Error Callback was called\n"); + Testsuite::logPrintf("Info! code = %ld, message = %s\n", response.httpResponseCode, response.response.c_str()); +} + +/** This test calls Storage::info(). */ + +TestExitStatus CloudTests::testInfo() { + ConfParams.setCloudTestCallbackCalled(false); + ConfParams.setCloudTestErrorCallbackCalled(false); + + if (CloudMan.getCurrentStorage() == nullptr) { + Testsuite::logPrintf("Couldn't find connected Storage\n"); + return kTestFailed; + } + + Common::String info = Common::String::format( + "Welcome to the Cloud test suite!\n" + "We're going to use the %s cloud storage which is connected right now.\n\n" + "Testing Cloud Storage API info() method.\n" + "In this test we'll try to list user infomation.", + CloudMan.getCurrentStorage()->name().c_str() + ); + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : info()\n"); + return kTestSkipped; + } + + if (CloudMan.info( + new Common::GlobalFunctionCallback<Cloud::Storage::StorageInfoResponse>(&infoCallback), + new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback) + ) == nullptr) { + Testsuite::logPrintf("Warning! No Request is returned!\n"); + } + + if (!waitForCallbackMore()) return kTestSkipped; + Testsuite::clearScreen(); + + if (ConfParams.isCloudTestErrorCallbackCalled()) { + Testsuite::logPrintf("Error callback was called\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("Info was displayed\n"); + return kTestPassed; +} + +TestExitStatus CloudTests::testDirectoryListing() { + ConfParams.setCloudTestCallbackCalled(false); + ConfParams.setCloudTestErrorCallbackCalled(false); + + if (CloudMan.getCurrentStorage() == nullptr) { + Testsuite::logPrintf("Couldn't find connected Storage\n"); + return kTestFailed; + } + + Common::String info = "Testing Cloud Storage API listDirectory() method.\n" + "In this test we'll try to list root directory."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : listDirectory()\n"); + return kTestSkipped; + } + + if (CloudMan.listDirectory( + "", + new Common::GlobalFunctionCallback<Cloud::Storage::FileArrayResponse>(&directoryListedCallback), + new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback) + ) == nullptr) { + Testsuite::logPrintf("Warning! No Request is returned!\n"); + } + + if (!waitForCallbackMore()) return kTestSkipped; + Testsuite::clearScreen(); + + if (ConfParams.isCloudTestErrorCallbackCalled()) { + Testsuite::logPrintf("Error callback was called\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("Directory was listed\n"); + return kTestPassed; +} + +TestExitStatus CloudTests::testDirectoryCreating() { + ConfParams.setCloudTestCallbackCalled(false); + ConfParams.setCloudTestErrorCallbackCalled(false); + + if (CloudMan.getCurrentStorage() == nullptr) { + Testsuite::logPrintf("Couldn't find connected Storage\n"); + return kTestFailed; + } + + Common::String info = "Testing Cloud Storage API createDirectory() method.\n" + "In this test we'll try to create a 'testbed' directory."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : createDirectory()\n"); + return kTestSkipped; + } + + Common::String info2 = "We'd list the root directory, create the directory and the list it again.\n" + "If all goes smoothly, you'd notice that there are more directories now."; + Testsuite::displayMessage(info2); + + // list root directory + if (CloudMan.listDirectory( + "", + new Common::GlobalFunctionCallback<Cloud::Storage::FileArrayResponse>(&directoryListedCallback), + new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback) + ) == nullptr) { + Testsuite::logPrintf("Warning! No Request is returned!\n"); + } + + if (!waitForCallbackMore()) return kTestSkipped; + Testsuite::clearScreen(); + + if (ConfParams.isCloudTestErrorCallbackCalled()) { + Testsuite::logPrintf("Error callback was called\n"); + return kTestFailed; + } + + ConfParams.setCloudTestCallbackCalled(false); + + // create 'testbed' + if (CloudMan.getCurrentStorage()->createDirectory( + getRemoteTestPath(), + new Common::GlobalFunctionCallback<Cloud::Storage::BoolResponse>(&directoryCreatedCallback), + new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback) + ) == nullptr) { + Testsuite::logPrintf("Warning! No Request is returned!\n"); + } + + if (!waitForCallbackMore()) return kTestSkipped; + Testsuite::clearScreen(); + + if (ConfParams.isCloudTestErrorCallbackCalled()) { + Testsuite::logPrintf("Error callback was called\n"); + return kTestFailed; + } + + ConfParams.setCloudTestCallbackCalled(false); + + // list it again + if (CloudMan.listDirectory( + "", + new Common::GlobalFunctionCallback<Cloud::Storage::FileArrayResponse>(&directoryListedCallback), + new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback) + ) == nullptr) { + Testsuite::logPrintf("Warning! No Request is returned!\n"); + } + + if (!waitForCallbackMore()) return kTestSkipped; + Testsuite::clearScreen(); + + if (ConfParams.isCloudTestErrorCallbackCalled()) { + Testsuite::logPrintf("Error callback was called\n"); + return kTestFailed; + } + + if (Testsuite::handleInteractiveInput("Was the CloudMan able to create a 'testbed' directory?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! Directory was not created!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("Directory was created\n"); + return kTestPassed; +} + +TestExitStatus CloudTests::testUploading() { + ConfParams.setCloudTestCallbackCalled(false); + ConfParams.setCloudTestErrorCallbackCalled(false); + + if (CloudMan.getCurrentStorage() == nullptr) { + Testsuite::logPrintf("Couldn't find connected Storage\n"); + return kTestFailed; + } + + Common::String info = "Testing Cloud Storage API upload() method.\n" + "In this test we'll try to upload a 'test1/file.txt' file."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : upload()\n"); + return kTestSkipped; + } + + if (!ConfParams.isGameDataFound()) { + Testsuite::logPrintf("Info! Couldn't find the game data, so skipping test : upload()\n"); + return kTestSkipped; + } + + const Common::String &path = ConfMan.get("path"); + Common::FSDirectory gameRoot(path); + Common::FSDirectory *directory = gameRoot.getSubDirectory("test1"); + Common::FSNode node = directory->getFSNode().getChild("file.txt"); + delete directory; + + if (CloudMan.getCurrentStorage()->uploadStreamSupported()) { + if (CloudMan.getCurrentStorage()->upload( + Common::String(getRemoteTestPath()) + "/testfile.txt", + node.createReadStream(), + new Common::GlobalFunctionCallback<Cloud::Storage::UploadResponse>(&fileUploadedCallback), + new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback) + ) == nullptr) { + Testsuite::logPrintf("Warning! No Request is returned!\n"); + } + } else { + Common::String filepath = node.getPath(); + if (CloudMan.getCurrentStorage()->upload( + Common::String(getRemoteTestPath()) + "/testfile.txt", + filepath.c_str(), + new Common::GlobalFunctionCallback<Cloud::Storage::UploadResponse>(&fileUploadedCallback), + new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback) + ) == nullptr) { + Testsuite::logPrintf("Warning! No Request is returned!\n"); + } + } + + if (!waitForCallbackMore()) return kTestSkipped; + Testsuite::clearScreen(); + + if (ConfParams.isCloudTestErrorCallbackCalled()) { + Testsuite::logPrintf("Error callback was called\n"); + return kTestFailed; + } + + Common::String info2 = "upload() is finished. Do you want to list '/testbed' directory?"; + + if (!Testsuite::handleInteractiveInput(info2, "Yes", "No", kOptionRight)) { + ConfParams.setCloudTestCallbackCalled(false); + + if (CloudMan.listDirectory( + getRemoteTestPath(), + new Common::GlobalFunctionCallback<Cloud::Storage::FileArrayResponse>(&directoryListedCallback), + new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback) + ) == nullptr) { + Testsuite::logPrintf("Warning! No Request is returned!\n"); + } + + if (!waitForCallbackMore()) return kTestSkipped; + Testsuite::clearScreen(); + + if (ConfParams.isCloudTestErrorCallbackCalled()) { + Testsuite::logPrintf("Error callback was called\n"); + return kTestFailed; + } + } + + if (Testsuite::handleInteractiveInput("Was the CloudMan able to upload into 'testbed/testfile.txt' file?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! File was not uploaded!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("File was uploaded\n"); + return kTestPassed; +} + +TestExitStatus CloudTests::testDownloading() { + ConfParams.setCloudTestCallbackCalled(false); + ConfParams.setCloudTestErrorCallbackCalled(false); + + if (CloudMan.getCurrentStorage() == nullptr) { + Testsuite::logPrintf("Couldn't find connected Storage\n"); + return kTestFailed; + } + + Common::String info = "Testing Cloud Storage API download() method.\n" + "In this test we'll try to download that 'testbed/testfile.txt' file."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : download()\n"); + return kTestSkipped; + } + + const Common::String &path = ConfMan.get("path"); + Common::FSDirectory gameRoot(path); + Common::FSNode node = gameRoot.getFSNode().getChild("downloaded_file.txt"); + Common::String filepath = node.getPath(); + if (CloudMan.getCurrentStorage()->download( + Common::String(getRemoteTestPath()) + "/testfile.txt", + filepath.c_str(), + new Common::GlobalFunctionCallback<Cloud::Storage::BoolResponse>(&fileDownloadedCallback), + new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback) + ) == nullptr) { + Testsuite::logPrintf("Warning! No Request is returned!\n"); + } + + if (!waitForCallbackMore()) return kTestSkipped; + Testsuite::clearScreen(); + + if (ConfParams.isCloudTestErrorCallbackCalled()) { + Testsuite::logPrintf("Error callback was called\n"); + return kTestFailed; + } + + if (Testsuite::handleInteractiveInput("Was the CloudMan able to download into 'testbed/downloaded_file.txt' file?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! File was not downloaded!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("File was downloaded\n"); + return kTestPassed; +} + +TestExitStatus CloudTests::testFolderDownloading() { + ConfParams.setCloudTestCallbackCalled(false); + ConfParams.setCloudTestErrorCallbackCalled(false); + + if (CloudMan.getCurrentStorage() == nullptr) { + Testsuite::logPrintf("Couldn't find connected Storage\n"); + return kTestFailed; + } + + Common::String info = "Testing Cloud Storage API downloadFolder() method.\n" + "In this test we'll try to download remote 'testbed/' directory."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : downloadFolder()\n"); + return kTestSkipped; + } + + const Common::String &path = ConfMan.get("path"); + Common::FSDirectory gameRoot(path); + Common::FSNode node = gameRoot.getFSNode().getChild("downloaded_directory"); + Common::String filepath = node.getPath(); + if (CloudMan.downloadFolder( + getRemoteTestPath(), + filepath.c_str(), + new Common::GlobalFunctionCallback<Cloud::Storage::FileArrayResponse>(&directoryDownloadedCallback), + new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback) + ) == nullptr) { + Testsuite::logPrintf("Warning! No Request is returned!\n"); + } + + if (!waitForCallbackMore()) return kTestSkipped; + Testsuite::clearScreen(); + + if (ConfParams.isCloudTestErrorCallbackCalled()) { + Testsuite::logPrintf("Error callback was called\n"); + return kTestFailed; + } + + if (Testsuite::handleInteractiveInput("Was the CloudMan able to download into 'testbed/downloaded_directory'?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! Directory was not downloaded!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("Directory was downloaded\n"); + return kTestPassed; +} + +TestExitStatus CloudTests::testSavesSync() { + ConfParams.setCloudTestCallbackCalled(false); + ConfParams.setCloudTestErrorCallbackCalled(false); + + if (CloudMan.getCurrentStorage() == nullptr) { + Testsuite::logPrintf("Couldn't find connected Storage\n"); + return kTestFailed; + } + + Common::String info = "Testing Cloud Storage API syncSaves() method.\n" + "In this test we'll try to sync your saves."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : syncSaves()\n"); + return kTestSkipped; + } + + const Common::String &path = ConfMan.get("path"); + Common::FSDirectory gameRoot(path); + Common::FSNode node = gameRoot.getFSNode().getChild("downloaded_directory"); + Common::String filepath = node.getPath(); + if (CloudMan.syncSaves( + new Common::GlobalFunctionCallback<Cloud::Storage::BoolResponse>(&savesSyncedCallback), + new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback) + ) == nullptr) { + Testsuite::logPrintf("Warning! No Request is returned!\n"); + } + + if (!waitForCallbackMore()) return kTestSkipped; + Testsuite::clearScreen(); + + if (ConfParams.isCloudTestErrorCallbackCalled()) { + Testsuite::logPrintf("Error callback was called\n"); + return kTestFailed; + } + + if (Testsuite::handleInteractiveInput("Was the CloudMan able to sync saves?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! Saves were not synced!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("Saves were synced successfully\n"); + return kTestPassed; +} + +} // End of namespace Testbed diff --git a/engines/testbed/cloud.h b/engines/testbed/cloud.h new file mode 100644 index 0000000000..ed27d7da82 --- /dev/null +++ b/engines/testbed/cloud.h @@ -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 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. + * + */ + +#ifndef TESTBED_CLOUD_H +#define TESTBED_CLOUD_H + +#include "testbed/testsuite.h" +#include <backends/cloud/storage.h> + +// This file can be used as template for header files of other newer testsuites. + +namespace Testbed { + +namespace CloudTests { + +// Helper functions for Cloud tests + +bool waitForCallback(); +bool waitForCallbackMore(); +const char *getRemoteTestPath(); +void infoCallback(Cloud::Storage::StorageInfoResponse response); +void directoryListedCallback(Cloud::Storage::FileArrayResponse response); +void directoryCreatedCallback(Cloud::Storage::BoolResponse response); +void fileUploadedCallback(Cloud::Storage::UploadResponse response); +void fileDownloadedCallback(Cloud::Storage::BoolResponse response); +void directoryDownloadedCallback(Cloud::Storage::FileArrayResponse response); +void savesSyncedCallback(Cloud::Storage::BoolResponse response); +void errorCallback(Networking::ErrorResponse response); + +TestExitStatus testInfo(); +TestExitStatus testDirectoryListing(); +TestExitStatus testDirectoryCreating(); +TestExitStatus testUploading(); +TestExitStatus testDownloading(); +TestExitStatus testFolderDownloading(); +TestExitStatus testSavesSync(); + +} // End of namespace CloudTests + +class CloudTestSuite : public Testsuite { +public: + /** + * The constructor for the CloudTestSuite + * For every test to be executed one must: + * 1) Create a function that would invoke the test + * 2) Add that test to list by executing addTest() + * + * @see addTest() + */ + CloudTestSuite(); + ~CloudTestSuite() {} + const char *getName() const { + return "Cloud"; + } + + const char *getDescription() const { + return "CloudMan, Storage API tests"; + } + +}; + +} // End of namespace Testbed + +#endif // TESTBED_TEMPLATE_H diff --git a/engines/testbed/config-params.h b/engines/testbed/config-params.h index 89aae199b6..57fd099bf4 100644 --- a/engines/testbed/config-params.h +++ b/engines/testbed/config-params.h @@ -54,6 +54,10 @@ private: */ bool _isInteractive; bool _isGameDataFound; +#ifdef USE_LIBCURL + bool _isCloudTestCallbackCalled; + bool _isCloudTestErrorCallbackCalled; +#endif bool _rerunTests; TestbedConfigManager *_testbedConfMan; @@ -68,6 +72,14 @@ public: bool isGameDataFound() { return _isGameDataFound; } void setGameDataFound(bool status) { _isGameDataFound = status; } +#ifdef USE_LIBCURL + bool isCloudTestCallbackCalled() const { return _isCloudTestCallbackCalled; } + void setCloudTestCallbackCalled(bool status) { _isCloudTestCallbackCalled = status; } + + bool isCloudTestErrorCallbackCalled() const { return _isCloudTestErrorCallbackCalled; } + void setCloudTestErrorCallbackCalled(bool status) { _isCloudTestErrorCallbackCalled = status; } +#endif + TestbedConfigManager *getTestbedConfigManager() { return _testbedConfMan; } void setTestbedConfigManager(TestbedConfigManager* confMan) { _testbedConfMan = confMan; } diff --git a/engines/testbed/misc.cpp b/engines/testbed/misc.cpp index 5847a8d2e4..20651e76e6 100644 --- a/engines/testbed/misc.cpp +++ b/engines/testbed/misc.cpp @@ -22,6 +22,7 @@ #include "testbed/misc.h" #include "common/timer.h" +#include <backends/networking/browser/openurl.h> namespace Testbed { @@ -160,10 +161,34 @@ TestExitStatus MiscTests::testMutexes() { return kTestFailed; } +TestExitStatus MiscTests::testOpenUrl() { + Common::String info = "Testing openUrl() method.\n" + "In this test we'll try to open scummvm.org in your default browser."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : openUrl()\n"); + return kTestSkipped; + } + + if (!Networking::Browser::openUrl("http://scummvm.org/")) { + Testsuite::logPrintf("Info! openUrl() says it couldn't open the url (probably not supported on this platform)\n"); + return kTestFailed; + } + + if (Testsuite::handleInteractiveInput("Was ScummVM able to open 'http://scummvm.org/' in your default browser?", "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! openUrl() is not working!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("openUrl() is OK\n"); + return kTestPassed; +} + MiscTestSuite::MiscTestSuite() { addTest("Datetime", &MiscTests::testDateTime, false); addTest("Timers", &MiscTests::testTimers, false); addTest("Mutexes", &MiscTests::testMutexes, false); + addTest("openUrl", &MiscTests::testOpenUrl, true); } } // End of namespace Testbed diff --git a/engines/testbed/misc.h b/engines/testbed/misc.h index 23e303d676..adfc322d9f 100644 --- a/engines/testbed/misc.h +++ b/engines/testbed/misc.h @@ -49,6 +49,7 @@ void criticalSection(void *arg); TestExitStatus testDateTime(); TestExitStatus testTimers(); TestExitStatus testMutexes(); +TestExitStatus testOpenUrl(); // add more here } // End of namespace MiscTests @@ -69,7 +70,7 @@ public: return "Misc"; } const char *getDescription() const { - return "Miscellaneous: Timers/Mutexes/Datetime"; + return "Miscellaneous: Timers/Mutexes/Datetime/openUrl"; } }; diff --git a/engines/testbed/module.mk b/engines/testbed/module.mk index ce78a48bc5..99e6157cde 100644 --- a/engines/testbed/module.mk +++ b/engines/testbed/module.mk @@ -14,6 +14,16 @@ MODULE_OBJS := \ testbed.o \ testsuite.o +ifdef USE_LIBCURL +MODULE_OBJS += \ + cloud.o +endif + +ifdef USE_SDL_NET +MODULE_OBJS += \ + webserver.o +endif + MODULE_DIRS += \ engines/testbed diff --git a/engines/testbed/testbed.cpp b/engines/testbed/testbed.cpp index 885429cafd..5943d47c58 100644 --- a/engines/testbed/testbed.cpp +++ b/engines/testbed/testbed.cpp @@ -39,6 +39,12 @@ #include "testbed/savegame.h" #include "testbed/sound.h" #include "testbed/testbed.h" +#ifdef USE_LIBCURL +#include "testbed/cloud.h" +#endif +#ifdef USE_SDL_NET +#include "testbed/webserver.h" +#endif namespace Testbed { @@ -134,6 +140,16 @@ TestbedEngine::TestbedEngine(OSystem *syst) // Midi ts = new MidiTestSuite(); _testsuiteList.push_back(ts); +#ifdef USE_LIBCURL + // Cloud + ts = new CloudTestSuite(); + _testsuiteList.push_back(ts); +#endif +#ifdef USE_SDL_NET + // Webserver + ts = new WebserverTestSuite(); + _testsuiteList.push_back(ts); +#endif } TestbedEngine::~TestbedEngine() { diff --git a/engines/testbed/webserver.cpp b/engines/testbed/webserver.cpp new file mode 100644 index 0000000000..feb2911704 --- /dev/null +++ b/engines/testbed/webserver.cpp @@ -0,0 +1,237 @@ +/* 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 "testbed/webserver.h" +#include "backends/networking/sdl_net/localwebserver.h" +#include "common/config-manager.h" +#include <backends/networking/browser/openurl.h> + +namespace Testbed { + +WebserverTestSuite::WebserverTestSuite() { + addTest("ResolveIP", &WebserverTests::testIP, true); + addTest("IndexPage", &WebserverTests::testIndexPage, true); + addTest("FilesPage", &WebserverTests::testFilesPageInvalidParameterValue, true); + addTest("CreateDirectory", &WebserverTests::testFilesPageCreateDirectory, true); + addTest("UploadFile", &WebserverTests::testFilesPageUploadFile, true); + addTest("UploadDirectory", &WebserverTests::testFilesPageUploadDirectory, true); + addTest("DownloadFile", &WebserverTests::testFilesPageDownloadFile, true); +} + +///// TESTS GO HERE ///// + +/** This test calls Storage::info(). */ + +bool WebserverTests::startServer() { + Common::Point pt; + pt.x = 10; pt.y = 10; + Testsuite::writeOnScreen("Starting webserver...", pt); + LocalServer.start(); + g_system->delayMillis(500); + Testsuite::clearScreen(); + return LocalServer.isRunning(); +} + +TestExitStatus WebserverTests::testIP() { + if (!startServer()) { + Testsuite::logPrintf("Error! Can't start local webserver!\n"); + return kTestFailed; + } + + Common::String info = "Welcome to the Webserver test suite!\n" + "You would be visiting different server's pages and saying whether they work like they should.\n\n" + "Testing Webserver's IP resolving.\n" + "In this test we'll try to resolve server's IP."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : IP resolving\n"); + return kTestSkipped; + } + + if (Testsuite::handleInteractiveInput( + Common::String::format("Is this your machine's IP?\n%s", LocalServer.getAddress().c_str()), + "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! IP was not resolved!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("IP was resolved\n"); + return kTestPassed; +} + +TestExitStatus WebserverTests::testIndexPage() { + if (!startServer()) { + Testsuite::logPrintf("Error! Can't start local webserver!\n"); + return kTestFailed; + } + + Common::String info = "Testing Webserver's index page.\n" + "In this test we'll try to open server's index page."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : index page\n"); + return kTestSkipped; + } + + Networking::Browser::openUrl(LocalServer.getAddress()); + if (Testsuite::handleInteractiveInput( + Common::String::format("The %s page opens well?", LocalServer.getAddress().c_str()), + "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! Couldn't open server's index page!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("Server's index page is OK\n"); + return kTestPassed; +} + +TestExitStatus WebserverTests::testFilesPageInvalidParameterValue() { + if (!startServer()) { + Testsuite::logPrintf("Error! Can't start local webserver!\n"); + return kTestFailed; + } + + Common::String info = "Testing Webserver's files page.\n" + "In this test we'll try to pass invalid parameter to files page."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : files page (invalid parameter)\n"); + return kTestSkipped; + } + + Networking::Browser::openUrl(LocalServer.getAddress()+"files?path=error"); + if (Testsuite::handleInteractiveInput( + Common::String::format("The %sfiles?path=error page displays error message?", LocalServer.getAddress().c_str()), + "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! No error message on invalid parameter in '/files'!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("Server's files page detects invalid paramters fine\n"); + return kTestPassed; +} + +TestExitStatus WebserverTests::testFilesPageCreateDirectory() { + if (!startServer()) { + Testsuite::logPrintf("Error! Can't start local webserver!\n"); + return kTestFailed; + } + + Common::String info = "Testing Webserver's files page Create Directory feature.\n" + "In this test you'll try to create directory."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : files page create directory\n"); + return kTestSkipped; + } + + Networking::Browser::openUrl(LocalServer.getAddress() + "files?path=/root/"); + if (Testsuite::handleInteractiveInput( + Common::String::format("You could go to %sfiles page, navigate to some directory with write access and create a directory there?", LocalServer.getAddress().c_str()), + "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! Create Directory is not working!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("Create Directory is OK\n"); + return kTestPassed; +} + +TestExitStatus WebserverTests::testFilesPageUploadFile() { + if (!startServer()) { + Testsuite::logPrintf("Error! Can't start local webserver!\n"); + return kTestFailed; + } + + Common::String info = "Testing Webserver's files page Upload Files feature.\n" + "In this test you'll try to upload a file."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : files page file upload\n"); + return kTestSkipped; + } + + Networking::Browser::openUrl(LocalServer.getAddress() + "files?path=/root/"); + if (Testsuite::handleInteractiveInput( + Common::String::format("You're able to upload a file in some directory with write access through %sfiles page?", LocalServer.getAddress().c_str()), + "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! Upload Files is not working!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("Upload Files is OK\n"); + return kTestPassed; +} + +TestExitStatus WebserverTests::testFilesPageUploadDirectory() { + if (!startServer()) { + Testsuite::logPrintf("Error! Can't start local webserver!\n"); + return kTestFailed; + } + + Common::String info = "Testing Webserver's files page Upload Directory feature.\n" + "In this test you'll try to upload a directory (works in Chrome only)."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : files page directory upload\n"); + return kTestSkipped; + } + + Networking::Browser::openUrl(LocalServer.getAddress() + "files?path=/root/"); + if (Testsuite::handleInteractiveInput( + Common::String::format("You're able to upload a directory into some directory with write access through %sfiles page using Chrome?", LocalServer.getAddress().c_str()), + "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! Upload Directory is not working!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("Upload Directory is OK\n"); + return kTestPassed; +} + +TestExitStatus WebserverTests::testFilesPageDownloadFile() { + if (!startServer()) { + Testsuite::logPrintf("Error! Can't start local webserver!\n"); + return kTestFailed; + } + + Common::String info = "Testing Webserver's files downloading feature.\n" + "In this test you'll try to download a file."; + + if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) { + Testsuite::logPrintf("Info! Skipping test : files page download\n"); + return kTestSkipped; + } + + Networking::Browser::openUrl(LocalServer.getAddress() + "files?path=/root/"); + if (Testsuite::handleInteractiveInput( + Common::String::format("You're able to download a file through %sfiles page?", LocalServer.getAddress().c_str()), + "Yes", "No", kOptionRight)) { + Testsuite::logDetailedPrintf("Error! Files downloading is not working!\n"); + return kTestFailed; + } + + Testsuite::logDetailedPrintf("Files downloading is OK\n"); + return kTestPassed; +} + +} // End of namespace Testbed diff --git a/engines/testbed/webserver.h b/engines/testbed/webserver.h new file mode 100644 index 0000000000..3f3083469e --- /dev/null +++ b/engines/testbed/webserver.h @@ -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 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. + * + */ + +#ifndef TESTBED_WEBSERVER_H +#define TESTBED_WEBSERVER_H + +#include "testbed/testsuite.h" + +namespace Testbed { + +namespace WebserverTests { + +// Helper functions for Webserver tests + +bool startServer(); +TestExitStatus testIP(); +TestExitStatus testIndexPage(); +TestExitStatus testFilesPageInvalidParameterValue(); +TestExitStatus testFilesPageCreateDirectory(); +TestExitStatus testFilesPageUploadFile(); +TestExitStatus testFilesPageUploadDirectory(); +TestExitStatus testFilesPageDownloadFile(); + +} // End of namespace WebserverTests + +class WebserverTestSuite : public Testsuite { +public: + /** + * The constructor for the WebserverTestSuite + * For every test to be executed one must: + * 1) Create a function that would invoke the test + * 2) Add that test to list by executing addTest() + * + * @see addTest() + */ + WebserverTestSuite(); + ~WebserverTestSuite() {} + const char *getName() const { + return "Webserver"; + } + + const char *getDescription() const { + return "Webserver tests"; + } + +}; + +} // End of namespace Testbed + +#endif // TESTBED_TEMPLATE_H diff --git a/engines/tinsel/detection.cpp b/engines/tinsel/detection.cpp index c44f1f4ef3..d6bcfe5ea0 100644 --- a/engines/tinsel/detection.cpp +++ b/engines/tinsel/detection.cpp @@ -109,7 +109,8 @@ bool TinselMetaEngine::hasFeature(MetaEngineFeature f) const { return (f == kSupportsListSaves) || (f == kSupportsLoadingDuringStartup) || - (f == kSupportsDeleteSave); + (f == kSupportsDeleteSave) || + (f == kSimpleSavesNames); } bool Tinsel::TinselEngine::hasFeature(EngineFeature f) const { diff --git a/engines/titanic/carry/arm.cpp b/engines/titanic/carry/arm.cpp index 880c93d309..a67937ebdf 100644 --- a/engines/titanic/carry/arm.cpp +++ b/engines/titanic/carry/arm.cpp @@ -163,7 +163,7 @@ bool CArm::MaitreDHappyMsg(CMaitreDHappyMsg *msg) { if (!_field158) playSound("z#47.wav", 100, 0, 0); if (_string6 == "Key" || _string6 == "AuditoryCentre") { - CGameObject *child = static_cast<CGameObject *>(getFirstChild()); + CGameObject *child = dynamic_cast<CGameObject *>(getFirstChild()); if (child) { child->setVisible(true); petAddToInventory(); @@ -184,7 +184,7 @@ bool CArm::MaitreDHappyMsg(CMaitreDHappyMsg *msg) { bool CArm::PETGainedObjectMsg(CPETGainedObjectMsg *msg) { if (_field158) { if (_string6 == "Key" || _string6 == "AuditoryCentre") { - CCarry *child = static_cast<CCarry *>(getFirstChild()); + CCarry *child = dynamic_cast<CCarry *>(getFirstChild()); if (child) { _visibleFrame = _field170; loadFrame(_visibleFrame); diff --git a/engines/titanic/carry/auditory_centre.cpp b/engines/titanic/carry/auditory_centre.cpp index d88989a801..0bda975a36 100644 --- a/engines/titanic/carry/auditory_centre.cpp +++ b/engines/titanic/carry/auditory_centre.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CAuditoryCentre, CBrain) + ON_MESSAGE(PuzzleSolvedMsg) +END_MESSAGE_MAP() + void CAuditoryCentre::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CBrain::save(file, indent); @@ -34,4 +38,10 @@ void CAuditoryCentre::load(SimpleFile *file) { CBrain::load(file); } +bool CAuditoryCentre::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) { + _fieldE0 = 1; + setVisible(true); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/auditory_centre.h b/engines/titanic/carry/auditory_centre.h index 743f8f2498..6f24e86208 100644 --- a/engines/titanic/carry/auditory_centre.h +++ b/engines/titanic/carry/auditory_centre.h @@ -28,6 +28,8 @@ namespace Titanic { class CAuditoryCentre : public CBrain { + DECLARE_MESSAGE_MAP; + bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/carry/bowl_ear.cpp b/engines/titanic/carry/bowl_ear.cpp index bb5172e580..852a77899a 100644 --- a/engines/titanic/carry/bowl_ear.cpp +++ b/engines/titanic/carry/bowl_ear.cpp @@ -24,6 +24,13 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CBowlEar, CEar) + ON_MESSAGE(PETGainedObjectMsg) + ON_MESSAGE(ReplaceBowlAndNutsMsg) + ON_MESSAGE(NutPuzzleMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + void CBowlEar::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CEar::save(file, indent); @@ -34,4 +41,28 @@ void CBowlEar::load(SimpleFile *file) { CEar::load(file); } +bool CBowlEar::PETGainedObjectMsg(CPETGainedObjectMsg *msg) { + CBowlStateChangeMsg changeMsg(3); + changeMsg.execute("ParrotNutBowlActor"); + + return CEar::PETGainedObjectMsg(msg); +} + +bool CBowlEar::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) { + setVisible(false); + return true; +} + +bool CBowlEar::NutPuzzleMsg(CNutPuzzleMsg *msg) { + if (msg->_value == "BowlUnlocked") + _fieldE0 = 1; + + return true; +} + +bool CBowlEar::MouseDragStartMsg(CMouseDragStartMsg *msg) { + setVisible(true); + return CEar::MouseDragStartMsg(msg); +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/bowl_ear.h b/engines/titanic/carry/bowl_ear.h index 4f2fbea478..d78092f6d7 100644 --- a/engines/titanic/carry/bowl_ear.h +++ b/engines/titanic/carry/bowl_ear.h @@ -28,6 +28,11 @@ namespace Titanic { class CBowlEar : public CEar { + DECLARE_MESSAGE_MAP; + bool PETGainedObjectMsg(CPETGainedObjectMsg *msg); + bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg); + bool NutPuzzleMsg(CNutPuzzleMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/carry/brain.cpp b/engines/titanic/carry/brain.cpp index 8df0de9961..102d8d9049 100644 --- a/engines/titanic/carry/brain.cpp +++ b/engines/titanic/carry/brain.cpp @@ -55,7 +55,7 @@ void CBrain::load(SimpleFile *file) { } bool CBrain::UseWithOtherMsg(CUseWithOtherMsg *msg) { - CBrainSlot *slot = static_cast<CBrainSlot *>(msg->_other); + CBrainSlot *slot = dynamic_cast<CBrainSlot *>(msg->_other); if (slot) { if (slot->getName() == "CentralCore") { setVisible(false); diff --git a/engines/titanic/carry/bridge_piece.cpp b/engines/titanic/carry/bridge_piece.cpp index fc845feff0..a2cb23add6 100644 --- a/engines/titanic/carry/bridge_piece.cpp +++ b/engines/titanic/carry/bridge_piece.cpp @@ -52,17 +52,17 @@ void CBridgePiece::load(SimpleFile *file) { } bool CBridgePiece::UseWithOtherMsg(CUseWithOtherMsg *msg) { - CShipSetting *shipSetting = static_cast<CShipSetting *>(msg->_other); + CShipSetting *shipSetting = dynamic_cast<CShipSetting *>(msg->_other); if (!shipSetting) { return CCarry::UseWithOtherMsg(msg); - } else if (shipSetting->_string4 == "NULL") { + } else if (shipSetting->_itemName != "NULL") { petAddToInventory(); return true; } else { setVisible(false); playSound("z#54.wav", 100, 0, 0); setPosition(shipSetting->_pos1); - shipSetting->_string4 = getName(); + shipSetting->_itemName = getName(); petMoveToHiddenRoom(); CAddHeadPieceMsg headpieceMsg(shipSetting->getName() == _string6 ? diff --git a/engines/titanic/carry/carry.cpp b/engines/titanic/carry/carry.cpp index 75b3b6f35b..03798e8713 100644 --- a/engines/titanic/carry/carry.cpp +++ b/engines/titanic/carry/carry.cpp @@ -127,7 +127,7 @@ bool CCarry::MouseDragEndMsg(CMouseDragEndMsg *msg) { return true; } - CCharacter *npc = static_cast<CCharacter *>(msg->_dropTarget); + CCharacter *npc = dynamic_cast<CCharacter *>(msg->_dropTarget); if (npc) { CUseWithCharMsg charMsg(npc); charMsg.execute(this, nullptr, 0); @@ -157,7 +157,7 @@ bool CCarry::MouseDragEndMsg(CMouseDragEndMsg *msg) { } bool CCarry::UseWithCharMsg(CUseWithCharMsg *msg) { - CSuccUBus *succubus = static_cast<CSuccUBus *>(msg->_character); + CSuccUBus *succubus = dynamic_cast<CSuccUBus *>(msg->_character); if (succubus) { CSubAcceptCCarryMsg carryMsg; carryMsg._item = this; diff --git a/engines/titanic/carry/carry.h b/engines/titanic/carry/carry.h index 72f4024904..06e446a1b5 100644 --- a/engines/titanic/carry/carry.h +++ b/engines/titanic/carry/carry.h @@ -44,11 +44,7 @@ class CCarry : public CGameObject { bool EnterViewMsg(CEnterViewMsg *msg); bool PassOnDragStartMsg(CPassOnDragStartMsg *msg); protected: - CString _string1; - Point _origPos; - CString _fullViewName; int _fieldDC; - int _fieldE0; CString _string3; CString _string4; Point _tempPos; @@ -62,6 +58,11 @@ protected: bool _enterFrameSet; int _visibleFrame; public: + CString _string1; + int _fieldE0; + Point _origPos; + CString _fullViewName; +public: CLASSDEF; CCarry(); diff --git a/engines/titanic/carry/carry_parrot.cpp b/engines/titanic/carry/carry_parrot.cpp index cf96204122..b0461ded26 100644 --- a/engines/titanic/carry/carry_parrot.cpp +++ b/engines/titanic/carry/carry_parrot.cpp @@ -111,7 +111,7 @@ bool CCarryParrot::MouseDragEndMsg(CMouseDragEndMsg *msg) { if (compareViewNameTo("ParrotLobby.Node 1.N")) { if (msg->_mousePos.x >= 75 && msg->_mousePos.x <= 565 && - !CParrot::_v2 && !CCage::_v2) { + !CParrot::_v2 && !CCage::_open) { setVisible(false); _fieldE0 = 0; CTreeItem *perchedParrot = findUnder(getRoot(), "PerchedParrot"); @@ -133,7 +133,7 @@ bool CCarryParrot::MouseDragEndMsg(CMouseDragEndMsg *msg) { actMsg.execute("ParrotCage"); } } else { - CCharacter *character = static_cast<CCharacter *>(msg->_dropTarget); + CCharacter *character = dynamic_cast<CCharacter *>(msg->_dropTarget); if (character) { CUseWithCharMsg charMsg(character); charMsg.execute(this, nullptr, 0); @@ -167,7 +167,7 @@ bool CCarryParrot::PassOnDragStartMsg(CPassOnDragStartMsg *msg) { return CCarry::PassOnDragStartMsg(msg); } - CTrueTalkNPC *npc = static_cast<CTrueTalkNPC *>(getRoot()->findByName(_string6)); + CTrueTalkNPC *npc = dynamic_cast<CTrueTalkNPC *>(getRoot()->findByName(_string6)); if (npc) startTalking(npc, 0x446BF); @@ -181,7 +181,7 @@ bool CCarryParrot::PassOnDragStartMsg(CPassOnDragStartMsg *msg) { bool CCarryParrot::PreEnterViewMsg(CPreEnterViewMsg *msg) { loadSurface(); - CCarryParrot *parrot = static_cast<CCarryParrot *>(getRoot()->findByName("CarryParrot")); + CCarryParrot *parrot = dynamic_cast<CCarryParrot *>(getRoot()->findByName("CarryParrot")); if (parrot) parrot->_fieldE0 = 0; @@ -189,7 +189,7 @@ bool CCarryParrot::PreEnterViewMsg(CPreEnterViewMsg *msg) { } bool CCarryParrot::UseWithCharMsg(CUseWithCharMsg *msg) { - CSuccUBus *succubus = static_cast<CSuccUBus *>(msg->_character); + CSuccUBus *succubus = dynamic_cast<CSuccUBus *>(msg->_character); if (succubus) CParrot::_v4 = 3; @@ -198,7 +198,7 @@ bool CCarryParrot::UseWithCharMsg(CUseWithCharMsg *msg) { bool CCarryParrot::ActMsg(CActMsg *msg) { if (msg->_action == "FreeParrot" && (CParrot::_v4 == 4 || CParrot::_v4 == 1)) { - CTrueTalkNPC *npc = static_cast<CTrueTalkNPC *>(getRoot()->findByName(_string6)); + CTrueTalkNPC *npc = dynamic_cast<CTrueTalkNPC *>(getRoot()->findByName(_string6)); if (npc) startTalking(npc, 0x446BF); @@ -212,7 +212,7 @@ bool CCarryParrot::ActMsg(CActMsg *msg) { playSound("z#475.wav", 100, 0, 0); if (!_field140) { - CCarry *feathers = static_cast<CCarry *>(getRoot()->findByName("Feathers")); + CCarry *feathers = dynamic_cast<CCarry *>(getRoot()->findByName("Feathers")); if (feathers) { feathers->setVisible(true); feathers->petAddToInventory(); diff --git a/engines/titanic/carry/central_core.cpp b/engines/titanic/carry/central_core.cpp index a50c95abbc..e210b34cbe 100644 --- a/engines/titanic/carry/central_core.cpp +++ b/engines/titanic/carry/central_core.cpp @@ -21,9 +21,16 @@ */ #include "titanic/carry/central_core.h" +#include "titanic/npcs/parrot.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CCentralCore, CBrain) + ON_MESSAGE(UseWithOtherMsg) + ON_MESSAGE(DropZoneLostObjectMsg) + ON_MESSAGE(DropZoneGotObjectMsg) +END_MESSAGE_MAP() + void CCentralCore::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CBrain::save(file, indent); @@ -34,4 +41,53 @@ void CCentralCore::load(SimpleFile *file) { CBrain::load(file); } +bool CCentralCore::UseWithOtherMsg(CUseWithOtherMsg *msg) { + CString name = msg->_other->getName(); + if (name == "HammerDispensorButton") { + CPuzzleSolvedMsg solvedMsg; + solvedMsg.execute("BigHammer"); + } else if (name == "SpeechCentre") { + CShowTextMsg textMsg("This does not reach."); + textMsg.execute("PET"); + } + + return CBrain::UseWithOtherMsg(msg); +} + +bool CCentralCore::DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg) { + CString name = msg->_object->getName(); + if (name == "PerchCoreHolder") { + CParrot::_v2 = 1; + if (isEquals("CentralCore")) + CParrot::_v5 = 0; + + CActMsg actMsg("LosePerch"); + actMsg.execute("ParrotLobbyController"); + } else if (name == "PerchHolder") { + CActMsg actMsg("LoseStick"); + actMsg.execute("ParrotLobbyController"); + } + + return true; +} + +bool CCentralCore::DropZoneGotObjectMsg(CDropZoneGotObjectMsg *msg) { + CString name = msg->_object->getName(); + if (name == "PerchCoreHolder") { + if (isEquals("CentralCore")) { + CParrot::_v5 = 1; + CActMsg actMsg("CoreReplaced"); + actMsg.execute("ParrotCage"); + } + + CActMsg actMsg("GainPerch"); + actMsg.execute("ParrotLobbyController"); + } else if (name == "PerchHolder") { + CActMsg actMsg("GainStick"); + actMsg.execute("ParrotLobbyController"); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/central_core.h b/engines/titanic/carry/central_core.h index 9d7bef2c13..cc5d9c2f95 100644 --- a/engines/titanic/carry/central_core.h +++ b/engines/titanic/carry/central_core.h @@ -28,6 +28,10 @@ namespace Titanic { class CCentralCore : public CBrain { + DECLARE_MESSAGE_MAP; + bool UseWithOtherMsg(CUseWithOtherMsg *msg); + bool DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg); + bool DropZoneGotObjectMsg(CDropZoneGotObjectMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/carry/chicken.cpp b/engines/titanic/carry/chicken.cpp index 65404dc65d..0e8f6b3653 100644 --- a/engines/titanic/carry/chicken.cpp +++ b/engines/titanic/carry/chicken.cpp @@ -80,7 +80,7 @@ bool CChicken::UseWithOtherMsg(CUseWithOtherMsg *msg) { petAddToInventory(); } else { - CSauceDispensor *dispensor = static_cast<CSauceDispensor *>(msg->_other); + CSauceDispensor *dispensor = dynamic_cast<CSauceDispensor *>(msg->_other); if (!dispensor || _string6 == "None") { return CCarry::UseWithOtherMsg(msg); } else { @@ -94,7 +94,7 @@ bool CChicken::UseWithOtherMsg(CUseWithOtherMsg *msg) { } bool CChicken::UseWithCharMsg(CUseWithCharMsg *msg) { - CSuccUBus *succubus = static_cast<CSuccUBus *>(msg->_character); + CSuccUBus *succubus = dynamic_cast<CSuccUBus *>(msg->_character); if (succubus) { setPosition(Point(330, 300)); CSubAcceptCCarryMsg acceptMsg; diff --git a/engines/titanic/carry/chicken.h b/engines/titanic/carry/chicken.h index 65fe30fd81..e64ae458a4 100644 --- a/engines/titanic/carry/chicken.h +++ b/engines/titanic/carry/chicken.h @@ -41,7 +41,7 @@ class CChicken : public CCarry { bool MouseDragEndMsg(CMouseDragEndMsg *msg); bool PETObjectStateMsg(CPETObjectStateMsg *msg); bool PETLostObjectMsg(CPETLostObjectMsg *msg); -private: +public: static int _v1; public: int _field12C; diff --git a/engines/titanic/carry/crushed_tv.cpp b/engines/titanic/carry/crushed_tv.cpp index a265b611a9..486537d28e 100644 --- a/engines/titanic/carry/crushed_tv.cpp +++ b/engines/titanic/carry/crushed_tv.cpp @@ -76,5 +76,4 @@ bool CCrushedTV::MouseDragStartMsg(CMouseDragStartMsg *msg) { return CCarry::MouseDragStartMsg(msg); } - } // End of namespace Titanic diff --git a/engines/titanic/carry/ear.cpp b/engines/titanic/carry/ear.cpp index 8d85e247f7..a2234bc6dc 100644 --- a/engines/titanic/carry/ear.cpp +++ b/engines/titanic/carry/ear.cpp @@ -21,9 +21,15 @@ */ #include "titanic/carry/ear.h" +#include "titanic/game/head_slot.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CEar, CHeadPiece) + ON_MESSAGE(ActMsg) + ON_MESSAGE(UseWithOtherMsg) +END_MESSAGE_MAP() + CEar::CEar() : CHeadPiece() { } @@ -37,4 +43,25 @@ void CEar::load(SimpleFile *file) { CHeadPiece::load(file); } +bool CEar::ActMsg(CActMsg *msg) { + if (msg->_action == "MusicSolved") + _fieldE0 = true; + return true; +} + +bool CEar::UseWithOtherMsg(CUseWithOtherMsg *msg) { + CHeadSlot *slot = dynamic_cast<CHeadSlot *>(msg->_other); + if (slot) { + setVisible(false); + petMoveToHiddenRoom(); + setPosition(Point(0, 0)); + + CAddHeadPieceMsg addMsg(getName()); + if (addMsg._value != "NULL") + addMsg.execute(addMsg._value == "Ear1" ? "Ear1Slot" : "Ear2Slot"); + } + + return CCarry::UseWithOtherMsg(msg); +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/ear.h b/engines/titanic/carry/ear.h index edef873d35..a357f46bbf 100644 --- a/engines/titanic/carry/ear.h +++ b/engines/titanic/carry/ear.h @@ -28,6 +28,9 @@ namespace Titanic { class CEar : public CHeadPiece { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool UseWithOtherMsg(CUseWithOtherMsg *msg); public: CLASSDEF; CEar(); diff --git a/engines/titanic/carry/eye.cpp b/engines/titanic/carry/eye.cpp index 5de1789e54..400df2fdc8 100644 --- a/engines/titanic/carry/eye.cpp +++ b/engines/titanic/carry/eye.cpp @@ -21,22 +21,119 @@ */ #include "titanic/carry/eye.h" +#include "titanic/game/head_slot.h" +#include "titanic/pet_control/pet_control.h" +#include "titanic/game/transport/lift.h" +#include "titanic/game/television.h" namespace Titanic { -CEye::CEye() : CHeadPiece(), _eyeNum(0) { +BEGIN_MESSAGE_MAP(CEye, CHeadPiece) + ON_MESSAGE(UseWithOtherMsg) + ON_MESSAGE(UseWithCharMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(PETGainedObjectMsg) + ON_MESSAGE(PassOnDragStartMsg) +END_MESSAGE_MAP() + +CEye::CEye() : CHeadPiece(), _eyeFlag(false) { } void CEye::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_eyeNum, indent); + file->writeNumberLine(_eyeFlag, indent); CHeadPiece::save(file, indent); } void CEye::load(SimpleFile *file) { file->readNumber(); - _eyeNum = file->readNumber(); + _eyeFlag = file->readNumber(); CHeadPiece::load(file); } + +bool CEye::UseWithOtherMsg(CUseWithOtherMsg *msg) { + CHeadSlot *slot = dynamic_cast<CHeadSlot *>(msg->_other); + if (slot) { + petMoveToHiddenRoom(); + _flag = true; + CAddHeadPieceMsg headMsg(getName()); + + if (headMsg._value != "NULL") + headMsg.execute(isEquals("Eye1") ? "Eye1Slot" : "Eye2Slot"); + } else if (msg->_other->isEquals("LiftbotWithoutHead")) { + CPetControl *pet = getPetControl(); + if (!CLift::_v1 && pet->getRoomsElevatorNum() == 4) { + _eyeFlag = true; + setPosition(_origPos); + setVisible(false); + CActMsg actMsg1(getName()); + actMsg1.execute("GetLiftEye"); + + CActMsg actMsg2("AddWrongHead"); + actMsg2.execute("FaultyLiftbot"); + } + } else { + return CCarry::UseWithOtherMsg(msg); + } + + return true; +} + +bool CEye::UseWithCharMsg(CUseWithCharMsg *msg) { + CLift *lift = dynamic_cast<CLift *>(msg->_character); + if (lift && lift->getName() == "Well") { + CPetControl *pet = getPetControl(); + if (!CLift::_v1 && pet->getRoomsElevatorNum() == 4) { + _eyeFlag = true; + setPosition(_origPos); + setVisible(false); + + CActMsg actMsg1(getName()); + actMsg1.execute("GetLiftEye"); + CActMsg actMsg2("AddWrongHead"); + actMsg2.execute(msg->_character); + } + + return true; + } else { + return CHeadPiece::UseWithCharMsg(msg); + } +} + +bool CEye::ActMsg(CActMsg *msg) { + if (msg->_action == "BellbotGetLight") { + setVisible(true); + petAddToInventory(); + playSound("z#47.wav"); + + CActMsg actMsg("Eye Removed"); + actMsg.execute("1stClassState"); + } else { + _eyeFlag = false; + + CActMsg actMsg("LoseHead"); + actMsg.execute("FaultyLiftbot"); + } + + return true; +} + +bool CEye::PETGainedObjectMsg(CPETGainedObjectMsg *msg) { + if (isEquals("Eye1")) + CTelevision::_v5 = 0; + + return CHeadPiece::PETGainedObjectMsg(msg); +} + +bool CEye::PassOnDragStartMsg(CPassOnDragStartMsg *msg) { + setVisible(true); + if (_eyeFlag) + CTelevision::_v6 = 0; + else if (isEquals("Eye1")) + CTelevision::_v5 = 0; + + return CHeadPiece::PassOnDragStartMsg(msg); +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/eye.h b/engines/titanic/carry/eye.h index 066a85609b..886bd39b84 100644 --- a/engines/titanic/carry/eye.h +++ b/engines/titanic/carry/eye.h @@ -28,8 +28,14 @@ namespace Titanic { class CEye : public CHeadPiece { + DECLARE_MESSAGE_MAP; + bool UseWithOtherMsg(CUseWithOtherMsg *msg); + bool UseWithCharMsg(CUseWithCharMsg *msg); + bool ActMsg(CActMsg *msg); + bool PETGainedObjectMsg(CPETGainedObjectMsg *msg); + bool PassOnDragStartMsg(CPassOnDragStartMsg *msg); private: - int _eyeNum; + bool _eyeFlag; public: CLASSDEF; CEye(); diff --git a/engines/titanic/carry/fruit.cpp b/engines/titanic/carry/fruit.cpp index 832dccf45a..68f3af7229 100644 --- a/engines/titanic/carry/fruit.cpp +++ b/engines/titanic/carry/fruit.cpp @@ -21,9 +21,17 @@ */ #include "titanic/carry/fruit.h" +#include "titanic/npcs/character.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CFruit, CCarry) + ON_MESSAGE(UseWithCharMsg) + ON_MESSAGE(LemonFallsFromTreeMsg) + ON_MESSAGE(UseWithOtherMsg) + ON_MESSAGE(FrameMsg) +END_MESSAGE_MAP() + CFruit::CFruit() : CCarry(), _field12C(0), _field130(0), _field134(0), _field138(0) { } @@ -48,4 +56,43 @@ void CFruit::load(SimpleFile *file) { CCarry::load(file); } +bool CFruit::UseWithCharMsg(CUseWithCharMsg *msg) { + if (msg->_character->isEquals("Barbot") && msg->_character->_visible) { + CActMsg actMsg("Fruit"); + actMsg.execute(msg->_character); + _fieldE0 = 0; + setVisible(false); + return true; + } else { + return CCarry::UseWithCharMsg(msg); + } +} + +bool CFruit::LemonFallsFromTreeMsg(CLemonFallsFromTreeMsg *msg) { + setVisible(true); + dragMove(msg->_pt); + _field130 = 1; + return true; +} + +bool CFruit::UseWithOtherMsg(CUseWithOtherMsg *msg) { + petAddToInventory(); + return true; +} + +bool CFruit::FrameMsg(CFrameMsg *msg) { + if (_field130) { + if (_bounds.top > 240) { + _field130 = 0; + _field134 = 1; + } + + makeDirty(); + _bounds.top += 3; + makeDirty(); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/fruit.h b/engines/titanic/carry/fruit.h index 93fe920740..bcbd314de8 100644 --- a/engines/titanic/carry/fruit.h +++ b/engines/titanic/carry/fruit.h @@ -28,6 +28,11 @@ namespace Titanic { class CFruit : public CCarry { + DECLARE_MESSAGE_MAP; + bool UseWithCharMsg(CUseWithCharMsg *msg); + bool LemonFallsFromTreeMsg(CLemonFallsFromTreeMsg *msg); + bool UseWithOtherMsg(CUseWithOtherMsg *msg); + bool FrameMsg(CFrameMsg *msg); private: int _field12C; int _field130; diff --git a/engines/titanic/carry/glass.cpp b/engines/titanic/carry/glass.cpp index 051457af03..03050dc60d 100644 --- a/engines/titanic/carry/glass.cpp +++ b/engines/titanic/carry/glass.cpp @@ -21,9 +21,21 @@ */ #include "titanic/carry/glass.h" +#include "titanic/carry/chicken.h" +#include "titanic/game/sauce_dispensor.h" +#include "titanic/npcs/character.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CGlass, CCarry) + ON_MESSAGE(UseWithOtherMsg) + ON_MESSAGE(UseWithCharMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MouseDragEndMsg) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) +END_MESSAGE_MAP() + CGlass::CGlass() : CCarry(), _string6("None") { } @@ -39,4 +51,108 @@ void CGlass::load(SimpleFile *file) { CCarry::load(file); } +bool CGlass::UseWithOtherMsg(CUseWithOtherMsg *msg) { + CSauceDispensor *dispensor = dynamic_cast<CSauceDispensor *>(msg->_other); + CChicken *chicken = dynamic_cast<CChicken *>(msg->_other); + + if (dispensor && _string6 != "None") { + CUse useMsg(this); + useMsg.execute(dispensor); + } else if (msg->_other->isEquals("Chicken") && _string6 != "None") { + if (chicken->_string6 != "None") { + if (!chicken->_field12C) { + CActMsg actMsg(_string6); + actMsg.execute("Chicken"); + } + + _string6 = "None"; + loadFrame(0); + _visibleFrame = 0; + } + + petAddToInventory(); + } else if (msg->_other->isEquals("Napkin") && _string6 != "None") { + petAddToInventory(); + _string6 = "None"; + loadFrame(0); + _visibleFrame = 0; + } else { + petAddToInventory(); + } + + return true; +} + +bool CGlass::UseWithCharMsg(CUseWithCharMsg *msg) { + if (msg->_character->isEquals("Barbot") && msg->_character->_visible) { + CActMsg actMsg(_string6); + setVisible(false); + + if (_string6 != "Bird") + setPosition(_origPos); + + actMsg.execute(msg->_character); + } else { + petAddToInventory(); + } + + return true; +} + +bool CGlass::ActMsg(CActMsg *msg) { + if (msg->_action == "GoToPET") { + setVisible(true); + petAddToInventory(); + } else if (msg->_action == "Mustard") { + _string6 = "Mustard"; + loadFrame(1); + _visibleFrame = 1; + } else if (msg->_action == "Tomato") { + _string6 = "Tomato"; + loadFrame(2); + _visibleFrame = 2; + } else if (msg->_action == "Bird") { + _string6 = "Bird"; + loadFrame(3); + _visibleFrame = 3; + } else if (msg->_action == "InTitilator") { + _string6 = "None"; + loadFrame(0); + _visibleFrame = 0; + } + + return true; +} + +bool CGlass::MouseDragEndMsg(CMouseDragEndMsg *msg) { + showMouse(); + if (msg->_dropTarget) { + error("TODO: See what drop target is"); + CCharacter *npc = dynamic_cast<CCharacter *>(msg->_dropTarget); + if (npc) { + CUseWithCharMsg useMsg(npc); + useMsg.execute(this); + } else { + CUseWithOtherMsg otherMsg(npc); + otherMsg.execute(this); + } + } else if (compareViewNameTo(_fullViewName) && msg->_mousePos.y < 360) { + setPosition(_origPos); + } else { + petAddToInventory(); + } + + return true; +} + +bool CGlass::TurnOn(CTurnOn *msg) { + setVisible(true); + return true; +} + +bool CGlass::TurnOff(CTurnOff *msg) { + setVisible(false); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/glass.h b/engines/titanic/carry/glass.h index 9f4056b1be..608d45cb66 100644 --- a/engines/titanic/carry/glass.h +++ b/engines/titanic/carry/glass.h @@ -28,7 +28,14 @@ namespace Titanic { class CGlass : public CCarry { -private: + DECLARE_MESSAGE_MAP; + bool UseWithOtherMsg(CUseWithOtherMsg *msg); + bool UseWithCharMsg(CUseWithCharMsg *msg); + bool ActMsg(CActMsg *msg); + bool MouseDragEndMsg(CMouseDragEndMsg *msg); + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); +public: CString _string6; public: CLASSDEF; diff --git a/engines/titanic/carry/hammer.cpp b/engines/titanic/carry/hammer.cpp index d3b912184c..88c766d564 100644 --- a/engines/titanic/carry/hammer.cpp +++ b/engines/titanic/carry/hammer.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CHammer, CCarry) + ON_MESSAGE(PuzzleSolvedMsg) + ON_MESSAGE(UseWithOtherMsg) +END_MESSAGE_MAP() + CHammer::CHammer() : CCarry() { } @@ -37,4 +42,22 @@ void CHammer::load(SimpleFile *file) { CCarry::load(file); } +bool CHammer::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) { + _fieldE0 = 1; + return true; +} + +bool CHammer::UseWithOtherMsg(CUseWithOtherMsg *msg) { + CString name = msg->_other->getName(); + if (name == "LongStickDispenser") { + CPuzzleSolvedMsg solvedMsg; + solvedMsg.execute("LongStickDispenser"); + } else if (name == "Bomb") { + CActMsg actMsg("Hit"); + actMsg.execute("Bomb"); + } + + return CCarry::UseWithOtherMsg(msg); +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/hammer.h b/engines/titanic/carry/hammer.h index a455d71434..8cc86f3db1 100644 --- a/engines/titanic/carry/hammer.h +++ b/engines/titanic/carry/hammer.h @@ -28,6 +28,9 @@ namespace Titanic { class CHammer : public CCarry { + DECLARE_MESSAGE_MAP; + bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg); + bool UseWithOtherMsg(CUseWithOtherMsg *msg); public: CLASSDEF; CHammer(); diff --git a/engines/titanic/carry/head_piece.cpp b/engines/titanic/carry/head_piece.cpp index ae709644a0..34850488a7 100644 --- a/engines/titanic/carry/head_piece.cpp +++ b/engines/titanic/carry/head_piece.cpp @@ -24,13 +24,19 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CHeadPiece, CCarry) + ON_MESSAGE(SenseWorkingMsg) + ON_MESSAGE(PETGainedObjectMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + CHeadPiece::CHeadPiece() : CCarry(), _string6("Not Working"), - _field12C(0), _field13C(0) { + _flag(0), _field13C(false) { } void CHeadPiece::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_field12C, indent); + file->writeNumberLine(_flag, indent); file->writeQuotedLine(_string6, indent); file->writeNumberLine(_field13C, indent); @@ -39,11 +45,49 @@ void CHeadPiece::save(SimpleFile *file, int indent) { void CHeadPiece::load(SimpleFile *file) { file->readNumber(); - _field12C = file->readNumber(); + _flag = file->readNumber(); _string6 = file->readString(); _field13C = file->readNumber(); CCarry::load(file); } +bool CHeadPiece::SenseWorkingMsg(CSenseWorkingMsg *msg) { + _string6 = msg->_value; + return true; +} + +bool CHeadPiece::PETGainedObjectMsg(CPETGainedObjectMsg *msg) { + _visibleFrame = 1; + if (!_field13C) { + stateInc38(); + _field13C = true; + } + + return true; +} + +bool CHeadPiece::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (!checkPoint(msg->_mousePos, false, true)) { + return false; + } else if (!_fieldE0) { + return true; + } + + if (_flag) { + setVisible(true); + moveToView(); + setPosition(Point(msg->_mousePos.x - _bounds.width() / 2, + msg->_mousePos.y - _bounds.height() / 2)); + + CTakeHeadPieceMsg takeMsg(getName()); + if (takeMsg._value != "NULL") + takeMsg.execute("TitaniaControl"); + + _flag = false; + } + + return CCarry::MouseDragStartMsg(msg); +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/head_piece.h b/engines/titanic/carry/head_piece.h index 05ac772853..367f781f0e 100644 --- a/engines/titanic/carry/head_piece.h +++ b/engines/titanic/carry/head_piece.h @@ -24,14 +24,19 @@ #define TITANIC_HEAD_PIECE_H #include "titanic/carry/carry.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { class CHeadPiece : public CCarry { -private: - int _field12C; + DECLARE_MESSAGE_MAP; + bool SenseWorkingMsg(CSenseWorkingMsg *msg); + bool PETGainedObjectMsg(CPETGainedObjectMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); +protected: + bool _flag; CString _string6; - int _field13C; + bool _field13C; public: CLASSDEF; CHeadPiece(); diff --git a/engines/titanic/carry/hose.cpp b/engines/titanic/carry/hose.cpp index 747d58c339..e90119138a 100644 --- a/engines/titanic/carry/hose.cpp +++ b/engines/titanic/carry/hose.cpp @@ -21,9 +21,18 @@ */ #include "titanic/carry/hose.h" +#include "titanic/npcs/succubus.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CHose, CCarry) + ON_MESSAGE(DropZoneGotObjectMsg) + ON_MESSAGE(PumpingMsg) + ON_MESSAGE(UseWithCharMsg) + ON_MESSAGE(HoseConnectedMsg) + ON_MESSAGE(DropZoneLostObjectMsg) +END_MESSAGE_MAP() + CHoseStatics *CHose::_statics; void CHose::init() { @@ -40,18 +49,72 @@ CHose::CHose() : CCarry(), void CHose::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_statics->_v1, indent); - file->writeQuotedLine(_statics->_v2, indent); + file->writeNumberLine(_statics->_actionVal, indent); + file->writeQuotedLine(_statics->_actionTarget, indent); file->writeQuotedLine(_string6, indent); CCarry::save(file, indent); } void CHose::load(SimpleFile *file) { file->readNumber(); - _statics->_v1 = file->readNumber(); - _statics->_v2 = file->readString(); + _statics->_actionVal = file->readNumber(); + _statics->_actionTarget = file->readString(); _string6 = file->readString(); CCarry::load(file); } +bool CHose::DropZoneGotObjectMsg(CDropZoneGotObjectMsg *msg) { + _statics->_actionTarget = msg->_object->getName(); + CPumpingMsg pumpingMsg; + pumpingMsg._value = _statics->_actionVal; + pumpingMsg.execute(_statics->_actionTarget); + CHoseConnectedMsg connectedMsg; + connectedMsg._value = 1; + connectedMsg.execute(this); + + return true; +} + +bool CHose::PumpingMsg(CPumpingMsg *msg) { + _statics->_actionVal = msg->_value; + if (!_statics->_actionTarget.empty()) { + CPumpingMsg pumpingMsg; + pumpingMsg._value = _statics->_actionVal; + pumpingMsg.execute(_statics->_actionTarget); + } + + return true; +} + +bool CHose::UseWithCharMsg(CUseWithCharMsg *msg) { + CSuccUBus *succubus = dynamic_cast<CSuccUBus *>(msg->_character); + if (!_statics->_actionVal && succubus) { + CHoseConnectedMsg connectedMsg(1, this); + if (connectedMsg.execute(succubus)) + return true; + } + + return CCarry::UseWithCharMsg(msg); +} + +bool CHose::HoseConnectedMsg(CHoseConnectedMsg *msg) { + if (msg->_value) { + CHose *hose = dynamic_cast<CHose *>(findChildInstanceOf(CHose::_type)); + if (hose) { + setVisible(true); + petAddToInventory(); + } + } + + return true; +} + +bool CHose::DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg) { + CPumpingMsg pumpingMsg; + pumpingMsg._value = 0; + pumpingMsg.execute(msg->_object); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/hose.h b/engines/titanic/carry/hose.h index ebd45860e8..3c8c1549c1 100644 --- a/engines/titanic/carry/hose.h +++ b/engines/titanic/carry/hose.h @@ -28,16 +28,23 @@ namespace Titanic { struct CHoseStatics { - int _v1; - CString _v2; + int _actionVal; + CString _actionTarget; + CHoseStatics() : _actionVal(0) {} }; class CHose : public CCarry { + DECLARE_MESSAGE_MAP; + bool DropZoneGotObjectMsg(CDropZoneGotObjectMsg *msg); + bool PumpingMsg(CPumpingMsg *msg); + bool UseWithCharMsg(CUseWithCharMsg *msg); + bool HoseConnectedMsg(CHoseConnectedMsg *msg); + bool DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg); protected: - static CHoseStatics *_statics; - CString _string6; public: + static CHoseStatics *_statics; +public: CLASSDEF; CHose(); static void init(); diff --git a/engines/titanic/carry/key.cpp b/engines/titanic/carry/key.cpp index 6e947464f1..187ff1b6c3 100644 --- a/engines/titanic/carry/key.cpp +++ b/engines/titanic/carry/key.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CKey, CCarry) + ON_MESSAGE(PuzzleSolvedMsg) + ON_MESSAGE(UseWithOtherMsg) +END_MESSAGE_MAP() + CKey::CKey() : CCarry() { } @@ -37,4 +42,19 @@ void CKey::load(SimpleFile *file) { CCarry::load(file); } +bool CKey::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) { + _fieldE0 = 1; + setVisible(true); + return true; +} + +bool CKey::UseWithOtherMsg(CUseWithOtherMsg *msg) { + if (msg->_other->getName() == "1stClassPhono") { + CActMsg actMsg("Unlock"); + actMsg.execute(msg->_other); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/key.h b/engines/titanic/carry/key.h index 8f1600f2b3..9d3957937c 100644 --- a/engines/titanic/carry/key.h +++ b/engines/titanic/carry/key.h @@ -28,6 +28,9 @@ namespace Titanic { class CKey : public CCarry { + DECLARE_MESSAGE_MAP; + bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg); + bool UseWithOtherMsg(CUseWithOtherMsg *msg); public: CLASSDEF; CKey(); diff --git a/engines/titanic/carry/liftbot_head.cpp b/engines/titanic/carry/liftbot_head.cpp index bcab8e8574..5f516fcf8c 100644 --- a/engines/titanic/carry/liftbot_head.cpp +++ b/engines/titanic/carry/liftbot_head.cpp @@ -21,22 +21,83 @@ */ #include "titanic/carry/liftbot_head.h" +#include "titanic/game/transport/lift.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { -CLiftbotHead::CLiftbotHead() : CCarry(), _field12C(0) { +BEGIN_MESSAGE_MAP(CLiftbotHead, CCarry) + ON_MESSAGE(UseWithOtherMsg) + ON_MESSAGE(UseWithCharMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + +CLiftbotHead::CLiftbotHead() : CCarry(), _flag(false) { } void CLiftbotHead::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_field12C, indent); + file->writeNumberLine(_flag, indent); CCarry::save(file, indent); } void CLiftbotHead::load(SimpleFile *file) { file->readNumber(); - _field12C = file->readNumber(); + _flag = file->readNumber(); CCarry::load(file); } +bool CLiftbotHead::UseWithOtherMsg(CUseWithOtherMsg *msg) { + if (msg->_other->getName() == "LiftbotWithoutHead") { + CPetControl *pet = getPetControl(); + if (CLift::_v1 == 1 && pet->getRoomsElevatorNum() == 4) { + _flag = true; + CActMsg actMsg("AddRightHead"); + actMsg.execute("FaultyLiftbot"); + setVisible(false); + } + + return true; + } else { + return CCarry::UseWithOtherMsg(msg); + } +} + +bool CLiftbotHead::UseWithCharMsg(CUseWithCharMsg *msg) { + CLift *lift = dynamic_cast<CLift *>(msg->_character); + if (lift) { + CPetControl *pet = getPetControl(); + if (lift->isEquals("Well") && !CLift::_v1 && pet->getRoomsElevatorNum() == 4) { + _flag = true; + CActMsg actMsg("AddRightHead"); + actMsg.execute(lift); + setVisible(false); + + return true; + } + } + + return CCarry::UseWithCharMsg(msg); +} + +bool CLiftbotHead::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (!checkStartDragging(msg)) { + return false; + } else if (compareViewNameTo("BottomOfWell.Node 8.N")) { + changeView("BottomOfWell.Node 13.N"); + moveToView(); + + CActMsg actMsg("LiftbotHeadTaken"); + actMsg.execute("BOWLiftbotHeadMonitor"); + + return CCarry::MouseDragStartMsg(msg); + } else if (_flag) { + _flag = false; + CActMsg actMsg("LoseHead"); + actMsg.execute("FaultyLiftbot"); + } + + return CCarry::MouseDragStartMsg(msg); +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/liftbot_head.h b/engines/titanic/carry/liftbot_head.h index 2fcd6a71f9..44cc51c993 100644 --- a/engines/titanic/carry/liftbot_head.h +++ b/engines/titanic/carry/liftbot_head.h @@ -28,8 +28,12 @@ namespace Titanic { class CLiftbotHead : public CCarry { + DECLARE_MESSAGE_MAP; + bool UseWithOtherMsg(CUseWithOtherMsg *msg); + bool UseWithCharMsg(CUseWithCharMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); private: - int _field12C; + bool _flag; public: CLASSDEF; CLiftbotHead(); diff --git a/engines/titanic/carry/long_stick.cpp b/engines/titanic/carry/long_stick.cpp index ab1e42b81f..557b75ab87 100644 --- a/engines/titanic/carry/long_stick.cpp +++ b/engines/titanic/carry/long_stick.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CLongStick, CCarry) + ON_MESSAGE(UseWithOtherMsg) + ON_MESSAGE(PuzzleSolvedMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + CLongStick::CLongStick() : CCarry() { } @@ -37,4 +43,30 @@ void CLongStick::load(SimpleFile *file) { CCarry::load(file); } +bool CLongStick::UseWithOtherMsg(CUseWithOtherMsg *msg) { + if (msg->_other->isEquals("SpeechCentre")) { + CPuzzleSolvedMsg puzzleMsg; + puzzleMsg.execute(msg->_other); + } else if (msg->_other->isEquals("LongStickDispensor")) { + petDisplayMessage(1, "You already have one."); + } else if (msg->_other->isEquals("Bomb")) { + CActMsg actMsg("Hit"); + actMsg.execute("Bomb"); + } else { + return CCarry::UseWithOtherMsg(msg); + } + + return true; +} + +bool CLongStick::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) { + _fieldE0 = 1; + return true; +} + +bool CLongStick::LeaveViewMsg(CLeaveViewMsg *msg) { + setVisible(false); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/long_stick.h b/engines/titanic/carry/long_stick.h index 2ff5b7228e..329ca838f9 100644 --- a/engines/titanic/carry/long_stick.h +++ b/engines/titanic/carry/long_stick.h @@ -28,6 +28,10 @@ namespace Titanic { class CLongStick : public CCarry { + DECLARE_MESSAGE_MAP; + bool UseWithOtherMsg(CUseWithOtherMsg *msg); + bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); public: CLASSDEF; CLongStick(); diff --git a/engines/titanic/carry/magazine.cpp b/engines/titanic/carry/magazine.cpp index cdf92fc707..e68c63f8f9 100644 --- a/engines/titanic/carry/magazine.cpp +++ b/engines/titanic/carry/magazine.cpp @@ -52,7 +52,7 @@ void CMagazine::load(SimpleFile *file) { } bool CMagazine::UseWithCharMsg(CUseWithCharMsg *msg) { - CDeskbot *deskbot = static_cast<CDeskbot *>(msg->_character); + CDeskbot *deskbot = dynamic_cast<CDeskbot *>(msg->_character); if (deskbot) { if (deskbot->_deskbotActive) { setVisible(false); diff --git a/engines/titanic/carry/maitred_left_arm.cpp b/engines/titanic/carry/maitred_left_arm.cpp index b31c2a6f6d..0962f232bd 100644 --- a/engines/titanic/carry/maitred_left_arm.cpp +++ b/engines/titanic/carry/maitred_left_arm.cpp @@ -21,19 +21,48 @@ */ #include "titanic/carry/maitred_left_arm.h" +#include "titanic/npcs/true_talk_npc.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CMaitreDLeftArm, CArm) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + void CMaitreDLeftArm::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_field174, indent); + file->writeNumberLine(_flag, indent); CArm::save(file, indent); } void CMaitreDLeftArm::load(SimpleFile *file) { file->readNumber(); - _field174 = file->readNumber(); + _flag = file->readNumber(); CArm::load(file); } +bool CMaitreDLeftArm::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!_flag) { + CTrueTalkNPC *maitreD = dynamic_cast<CTrueTalkNPC *>(findRoomObject("MaitreD")); + startTalking(maitreD, 126); + startTalking(maitreD, 127); + } + + return true; +} + +bool CMaitreDLeftArm::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (checkPoint(msg->_mousePos) && !_flag) { + CVisibleMsg visibleMsg; + visibleMsg.execute("MD left arm background image"); + _flag = true; + + CArmPickedUpFromTableMsg takenMsg; + takenMsg.execute("Restaurant Table Pan Handler", nullptr, MSGFLAG_SCAN); + } + + return CArm::MouseDragStartMsg(msg); +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/maitred_left_arm.h b/engines/titanic/carry/maitred_left_arm.h index 8f5090b073..0e1732df46 100644 --- a/engines/titanic/carry/maitred_left_arm.h +++ b/engines/titanic/carry/maitred_left_arm.h @@ -28,11 +28,14 @@ namespace Titanic { class CMaitreDLeftArm : public CArm { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); private: - int _field174; + bool _flag; public: CLASSDEF; - CMaitreDLeftArm() : CArm(), _field174(0) {} + CMaitreDLeftArm() : CArm(), _flag(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/carry/maitred_right_arm.cpp b/engines/titanic/carry/maitred_right_arm.cpp index 7030e83c9d..5cec6be9bd 100644 --- a/engines/titanic/carry/maitred_right_arm.cpp +++ b/engines/titanic/carry/maitred_right_arm.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CMaitreDRightArm, CArm) + ON_MESSAGE(DropZoneLostObjectMsg) +END_MESSAGE_MAP() + void CMaitreDRightArm::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CArm::save(file, indent); @@ -34,4 +38,12 @@ void CMaitreDRightArm::load(SimpleFile *file) { CArm::load(file); } +bool CMaitreDRightArm::DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg) { + CActMsg actMsg("LoseArm"); + actMsg.execute("MaitreDBody"); + actMsg.execute("MaitreD Arm Holder"); + _fieldE0 = 1; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/maitred_right_arm.h b/engines/titanic/carry/maitred_right_arm.h index ce07ed7af4..4a53d45f69 100644 --- a/engines/titanic/carry/maitred_right_arm.h +++ b/engines/titanic/carry/maitred_right_arm.h @@ -28,6 +28,8 @@ namespace Titanic { class CMaitreDRightArm : public CArm { + DECLARE_MESSAGE_MAP; + bool DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/carry/mouth.cpp b/engines/titanic/carry/mouth.cpp index 8c3791fa9c..e48929a391 100644 --- a/engines/titanic/carry/mouth.cpp +++ b/engines/titanic/carry/mouth.cpp @@ -21,9 +21,16 @@ */ #include "titanic/carry/mouth.h" +#include "titanic/game/head_slot.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CMouth, CHeadPiece) + ON_MESSAGE(UseWithOtherMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(PETGainedObjectMsg) +END_MESSAGE_MAP() + CMouth::CMouth() : CHeadPiece() { } @@ -37,4 +44,37 @@ void CMouth::load(SimpleFile *file) { CHeadPiece::load(file); } +bool CMouth::UseWithOtherMsg(CUseWithOtherMsg *msg) { + CHeadSlot *slot = dynamic_cast<CHeadSlot *>(msg->_other); + if (!slot) + return CHeadPiece::UseWithOtherMsg(msg); + + _flag = true; + setVisible(false); + setPosition(Point(0, 0)); + petMoveToHiddenRoom(); + + CAddHeadPieceMsg addMsg(getName()); + if (addMsg._value != "NULL") + addMsg.execute("MouthSlot"); + + return true; +} + +bool CMouth::MovieEndMsg(CMovieEndMsg *msg) { + return true; +} + +bool CMouth::PETGainedObjectMsg(CPETGainedObjectMsg *msg) { + _visibleFrame = 2; + loadFrame(2); + setVisible(true); + if (!_field13C) { + stateInc38(); + _field13C = true; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/mouth.h b/engines/titanic/carry/mouth.h index e394330494..f5f0f53b45 100644 --- a/engines/titanic/carry/mouth.h +++ b/engines/titanic/carry/mouth.h @@ -28,6 +28,10 @@ namespace Titanic { class CMouth : public CHeadPiece { + DECLARE_MESSAGE_MAP; + bool UseWithOtherMsg(CUseWithOtherMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool PETGainedObjectMsg(CPETGainedObjectMsg *msg); public: CLASSDEF; CMouth(); diff --git a/engines/titanic/carry/napkin.cpp b/engines/titanic/carry/napkin.cpp index ace5a389a0..d0ee9acc1a 100644 --- a/engines/titanic/carry/napkin.cpp +++ b/engines/titanic/carry/napkin.cpp @@ -43,7 +43,7 @@ void CNapkin::load(SimpleFile *file) { } bool CNapkin::UseWithOtherMsg(CUseWithOtherMsg *msg) { - CChicken *chicken = static_cast<CChicken *>(msg->_other); + CChicken *chicken = dynamic_cast<CChicken *>(msg->_other); if (chicken) { if (chicken->_string6 == "None" || chicken->_field12C) { CActMsg actMsg("Clean"); @@ -57,5 +57,4 @@ bool CNapkin::UseWithOtherMsg(CUseWithOtherMsg *msg) { return CCarry::UseWithOtherMsg(msg); } - } // End of namespace Titanic diff --git a/engines/titanic/carry/nose.cpp b/engines/titanic/carry/nose.cpp index 4f3afe24ac..a08d02a88c 100644 --- a/engines/titanic/carry/nose.cpp +++ b/engines/titanic/carry/nose.cpp @@ -21,9 +21,15 @@ */ #include "titanic/carry/nose.h" +#include "titanic/game/head_slot.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CNose, CHeadPiece) + ON_MESSAGE(ChangeSeasonMsg) + ON_MESSAGE(UseWithOtherMsg) +END_MESSAGE_MAP() + CNose::CNose() : CHeadPiece() { } @@ -37,4 +43,23 @@ void CNose::load(SimpleFile *file) { CHeadPiece::load(file); } +bool CNose::ChangeSeasonMsg(CChangeSeasonMsg *msg) { + // WORKAROUND: Redundant code in original skipped + return true; +} + +bool CNose::UseWithOtherMsg(CUseWithOtherMsg *msg) { + CHeadSlot *slot = dynamic_cast<CHeadSlot *>(msg->_other); + if (!slot) + return CCarry::UseWithOtherMsg(msg); + + petMoveToHiddenRoom(); + _flag = false; + CAddHeadPieceMsg addMsg(getName()); + if (addMsg._value != "NULL") + addMsg.execute("NoseSlot"); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/nose.h b/engines/titanic/carry/nose.h index b688da231a..6e5be30df2 100644 --- a/engines/titanic/carry/nose.h +++ b/engines/titanic/carry/nose.h @@ -28,6 +28,9 @@ namespace Titanic { class CNose : public CHeadPiece { + DECLARE_MESSAGE_MAP; + bool ChangeSeasonMsg(CChangeSeasonMsg *msg); + bool UseWithOtherMsg(CUseWithOtherMsg *msg); public: CLASSDEF; CNose(); diff --git a/engines/titanic/carry/perch.cpp b/engines/titanic/carry/perch.cpp index 281b3fce53..4f0e76bdb0 100644 --- a/engines/titanic/carry/perch.cpp +++ b/engines/titanic/carry/perch.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CPerch, CCentralCore) + ON_MESSAGE(UseWithOtherMsg) +END_MESSAGE_MAP() + void CPerch::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CCentralCore::save(file, indent); @@ -34,4 +38,13 @@ void CPerch::load(SimpleFile *file) { CCentralCore::load(file); } +bool CPerch::UseWithOtherMsg(CUseWithOtherMsg *msg) { + if (msg->_other->isEquals("SpeechCentre")) { + CShowTextMsg textMsg("This does not reach."); + textMsg.execute("PET"); + } + + return CCentralCore::UseWithOtherMsg(msg); +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/perch.h b/engines/titanic/carry/perch.h index d23868d909..8941c8ea4d 100644 --- a/engines/titanic/carry/perch.h +++ b/engines/titanic/carry/perch.h @@ -28,6 +28,8 @@ namespace Titanic { class CPerch : public CCentralCore { + DECLARE_MESSAGE_MAP; + bool UseWithOtherMsg(CUseWithOtherMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/carry/phonograph_cylinder.cpp b/engines/titanic/carry/phonograph_cylinder.cpp index 0684c56611..3dedbc4ac9 100644 --- a/engines/titanic/carry/phonograph_cylinder.cpp +++ b/engines/titanic/carry/phonograph_cylinder.cpp @@ -22,6 +22,7 @@ #include "titanic/carry/phonograph_cylinder.h" #include "titanic/game/phonograph.h" +#include "titanic/sound/music_room.h" namespace Titanic { @@ -101,7 +102,7 @@ void CPhonographCylinder::load(SimpleFile *file) { } bool CPhonographCylinder::UseWithOtherMsg(CUseWithOtherMsg *msg) { - CPhonograph *phonograph = static_cast<CPhonograph *>(msg->_other); + CPhonograph *phonograph = dynamic_cast<CPhonograph *>(msg->_other); if (phonograph) { CSetVarMsg varMsg("m_RecordStatus", 1); return true; @@ -162,10 +163,33 @@ bool CPhonographCylinder::RecordOntoCylinderMsg(CRecordOntoCylinderMsg *msg) { } bool CPhonographCylinder::SetMusicControlsMsg(CSetMusicControlsMsg *msg) { - if (_itemName.left(7) == "STMusic") { - //todo - warning("TODO"); - } + if (!_itemName.hasPrefix("STMusic")) + return true; + + CMusicRoom *musicRoom = getMusicRoom(); + musicRoom->setMuteControl(BELLS, _bellsMuteControl); + musicRoom->setPitchControl(BELLS, _bellsPitchControl); + musicRoom->setSpeedControl(BELLS, _bellsSpeedControl); + musicRoom->setInversionControl(BELLS, _bellsInversionControl); + musicRoom->setDirectionControl(BELLS, _bellsDirectionControl); + + musicRoom->setMuteControl(SNAKE, _snakeMuteControl); + musicRoom->setPitchControl(SNAKE, _snakePitchControl); + musicRoom->setSpeedControl(SNAKE, _snakeSpeedControl); + musicRoom->setInversionControl(SNAKE, _snakeInversionControl); + musicRoom->setDirectionControl(SNAKE, _snakeDirectionControl); + + musicRoom->setMuteControl(PIANO, _pianoMuteControl); + musicRoom->setPitchControl(PIANO, _pianoPitchControl); + musicRoom->setSpeedControl(PIANO, _pianoSpeedControl); + musicRoom->setInversionControl(PIANO, _pianoInversionControl); + musicRoom->setDirectionControl(PIANO, _pianoDirectionControl); + + musicRoom->setMuteControl(BASS, _bassMuteControl); + musicRoom->setPitchControl(BASS, _bassPitchControl); + musicRoom->setSpeedControl(BASS, _bassSpeedControl); + musicRoom->setInversionControl(BASS, _bassInversionControl); + musicRoom->setDirectionControl(BASS, _bassDirectionControl); return true; } diff --git a/engines/titanic/carry/phonograph_ear.cpp b/engines/titanic/carry/phonograph_ear.cpp index ceb71babd2..95297a77a1 100644 --- a/engines/titanic/carry/phonograph_ear.cpp +++ b/engines/titanic/carry/phonograph_ear.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CPhonographEar, CEar) + ON_MESSAGE(CorrectMusicPlayedMsg) + ON_MESSAGE(PETGainedObjectMsg) + ON_MESSAGE(TimerMsg) +END_MESSAGE_MAP() + void CPhonographEar::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_field140, indent); @@ -36,4 +42,24 @@ void CPhonographEar::load(SimpleFile *file) { CEar::load(file); } +bool CPhonographEar::CorrectMusicPlayedMsg(CCorrectMusicPlayedMsg *msg) { + _fieldE0 = true; + return true; +} + +bool CPhonographEar::PETGainedObjectMsg(CPETGainedObjectMsg *msg) { + if (_field140) { + _field140 = false; + addTimer(1000); + } + + return CEar::PETGainedObjectMsg(msg); +} + +bool CPhonographEar::TimerMsg(CTimerMsg *msg) { + CVisibleMsg visibleMsg; + visibleMsg.execute("Replacement Phonograph Ear"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/phonograph_ear.h b/engines/titanic/carry/phonograph_ear.h index 582db9f7ef..b5db015f90 100644 --- a/engines/titanic/carry/phonograph_ear.h +++ b/engines/titanic/carry/phonograph_ear.h @@ -28,11 +28,15 @@ namespace Titanic { class CPhonographEar : public CEar { + DECLARE_MESSAGE_MAP; + bool CorrectMusicPlayedMsg(CCorrectMusicPlayedMsg *msg); + bool PETGainedObjectMsg(CPETGainedObjectMsg *msg); + bool TimerMsg(CTimerMsg *msg); private: - int _field140; + bool _field140; public: CLASSDEF; - CPhonographEar() : CEar(), _field140(1) {} + CPhonographEar() : CEar(), _field140(true) {} /** * Save the data for the class to file diff --git a/engines/titanic/carry/photograph.cpp b/engines/titanic/carry/photograph.cpp index 7f32a0623d..039efd0252 100644 --- a/engines/titanic/carry/photograph.cpp +++ b/engines/titanic/carry/photograph.cpp @@ -21,6 +21,7 @@ */ #include "titanic/carry/photograph.h" +#include "titanic/core/dont_save_file_item.h" #include "titanic/core/room_item.h" namespace Titanic { @@ -59,8 +60,12 @@ bool CPhotograph::MouseDragEndMsg(CMouseDragEndMsg *msg) { _v1 = 0; CGameObject *target = msg->_dropTarget; - if (target && target->getName() != "NavigationComputer") { - warning("TODO: CPhotograph::MouseDragEndMsg"); + if (target && target->isEquals("NavigationComputer")) { + moveUnder(getDontSave()); + makeDirty(); + playSound("a#46.wav"); + starFn1(14); + showMouse(); return true; } else { return CCarry::MouseDragEndMsg(msg); @@ -78,7 +83,7 @@ bool CPhotograph::MouseDragStartMsg(CMouseDragStartMsg *msg) { } bool CPhotograph::PETGainedObjectMsg(CPETGainedObjectMsg *msg) { - if (getRoom()->getName() == "Home") { + if (getRoom()->isEquals("Home")) { CActMsg actMsg("PlayerPutsPhotoInPET"); actMsg.execute("Doorbot"); } diff --git a/engines/titanic/carry/plug_in.cpp b/engines/titanic/carry/plug_in.cpp index c82a4cc422..883458c9b1 100644 --- a/engines/titanic/carry/plug_in.cpp +++ b/engines/titanic/carry/plug_in.cpp @@ -47,19 +47,13 @@ bool CPlugIn::UseWithOtherMsg(CUseWithOtherMsg *msg) { if (otherName == "PET") { return CCarry::UseWithOtherMsg(msg); - } else if (otherName == "DatasideTransporter") { - CString name = getName(); - if (name == "DatasideTransporter") { - // TODO - if (name != "SendYourself") { - // TODO - } - } else { - // TODO - } - } else { + } else if (isEquals("DatasideTransporter")) { CShowTextMsg textMsg("This item is incorrectly calibrated."); textMsg.execute("PET"); + } else if (isEquals("DatasideTransporter")) { + error("TODO: Set msg->_other->fieldC4 = 2"); + } else if (isEquals("SendYourself")) { + error("TODO: Set msg->_other->fieldC8 = 1"); } return true; diff --git a/engines/titanic/carry/speech_centre.cpp b/engines/titanic/carry/speech_centre.cpp index b8076aee76..29ced484a5 100644 --- a/engines/titanic/carry/speech_centre.cpp +++ b/engines/titanic/carry/speech_centre.cpp @@ -24,10 +24,17 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CSpeechCentre, CBrain) + ON_MESSAGE(PuzzleSolvedMsg) + ON_MESSAGE(ChangeSeasonMsg) + ON_MESSAGE(SpeechFallsFromTreeMsg) + ON_MESSAGE(FrameMsg) +END_MESSAGE_MAP() + void CSpeechCentre::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_field13C, indent); - file->writeQuotedLine(_string1, indent); + file->writeQuotedLine(_season, indent); file->writeNumberLine(_field14C, indent); CBrain::save(file, indent); @@ -36,10 +43,41 @@ void CSpeechCentre::save(SimpleFile *file, int indent) { void CSpeechCentre::load(SimpleFile *file) { file->readNumber(); _field13C = file->readNumber(); - _string1 = file->readString(); + _season = file->readString(); _field14C = file->readNumber(); CBrain::load(file); } +bool CSpeechCentre::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) { + if (_field13C == 1 && _season == "Autumn") + _fieldE0 = true; + return true; +} + +bool CSpeechCentre::ChangeSeasonMsg(CChangeSeasonMsg *msg) { + _season = msg->_season; + return true; +} + +bool CSpeechCentre::SpeechFallsFromTreeMsg(CSpeechFallsFromTreeMsg *msg) { + setVisible(true); + dragMove(msg->_pos); + _field14C = true; + return true; +} + +bool CSpeechCentre::FrameMsg(CFrameMsg *msg) { + if (_field14C) { + if (_bounds.top > 200) + _field14C = false; + + makeDirty(); + _bounds.top += 3; + makeDirty(); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/speech_centre.h b/engines/titanic/carry/speech_centre.h index 50f47e9c8a..806e22247b 100644 --- a/engines/titanic/carry/speech_centre.h +++ b/engines/titanic/carry/speech_centre.h @@ -28,13 +28,18 @@ namespace Titanic { class CSpeechCentre : public CBrain { + DECLARE_MESSAGE_MAP; + bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg); + bool ChangeSeasonMsg(CChangeSeasonMsg *msg); + bool SpeechFallsFromTreeMsg(CSpeechFallsFromTreeMsg *msg); + bool FrameMsg(CFrameMsg *msg); private: int _field13C; - CString _string1; + CString _season; int _field14C; public: CLASSDEF; - CSpeechCentre() : CBrain(), _string1("Summer"), + CSpeechCentre() : CBrain(), _season("Summer"), _field13C(1), _field14C(0) {} /** diff --git a/engines/titanic/carry/vision_centre.cpp b/engines/titanic/carry/vision_centre.cpp index 8c8bab15f8..fd30089fc5 100644 --- a/engines/titanic/carry/vision_centre.cpp +++ b/engines/titanic/carry/vision_centre.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CVisionCentre, CBrain) + ON_MESSAGE(PuzzleSolvedMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + void CVisionCentre::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CBrain::save(file, indent); @@ -34,4 +40,27 @@ void CVisionCentre::load(SimpleFile *file) { CBrain::load(file); } +bool CVisionCentre::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) { + _fieldE0 = true; + return true; +} + +bool CVisionCentre::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_fieldE0) { + return CBrain::MouseButtonDownMsg(msg); + } else { + petDisplayMessage(1, "It would be nice if you could take that but you can't."); + return true; + } +} + +bool CVisionCentre::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (_fieldE0) { + return CBrain::MouseDragStartMsg(msg); + } else { + petDisplayMessage(1, "It would be nice if you could take that but you can't."); + return true; + } +} + } // End of namespace Titanic diff --git a/engines/titanic/carry/vision_centre.h b/engines/titanic/carry/vision_centre.h index 6cf8e2c653..14055a5f5f 100644 --- a/engines/titanic/carry/vision_centre.h +++ b/engines/titanic/carry/vision_centre.h @@ -28,6 +28,10 @@ namespace Titanic { class CVisionCentre : public CBrain { + DECLARE_MESSAGE_MAP; + bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/core/background.cpp b/engines/titanic/core/background.cpp index f180df8867..733dfc1cf3 100644 --- a/engines/titanic/core/background.cpp +++ b/engines/titanic/core/background.cpp @@ -30,13 +30,13 @@ BEGIN_MESSAGE_MAP(CBackground, CGameObject) ON_MESSAGE(VisibleMsg) END_MESSAGE_MAP() -CBackground::CBackground() : CGameObject(), _fieldBC(0), _fieldC0(0), _fieldDC(0) { +CBackground::CBackground() : CGameObject(), _startFrame(0), _endFrame(0), _fieldDC(0) { } void CBackground::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); + file->writeNumberLine(_startFrame, indent); + file->writeNumberLine(_endFrame, indent); file->writeQuotedLine(_string1, indent); file->writeQuotedLine(_string2, indent); file->writeNumberLine(_fieldDC, indent); @@ -46,8 +46,8 @@ void CBackground::save(SimpleFile *file, int indent) { void CBackground::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); + _startFrame = file->readNumber(); + _endFrame = file->readNumber(); _string1 = file->readString(); _string2 = file->readString(); _fieldDC = file->readNumber(); @@ -58,9 +58,9 @@ void CBackground::load(SimpleFile *file) { bool CBackground::StatusChangeMsg(CStatusChangeMsg *msg) { setVisible(true); if (_fieldDC) { - playMovie(_fieldBC, _fieldC0, 16); + playMovie(_startFrame, _endFrame, 16); } else { - playMovie(_fieldBC, _fieldC0, 0); + playMovie(_startFrame, _endFrame, 0); } return true; } diff --git a/engines/titanic/core/background.h b/engines/titanic/core/background.h index 6a2fd21454..b7f160db28 100644 --- a/engines/titanic/core/background.h +++ b/engines/titanic/core/background.h @@ -34,8 +34,8 @@ class CBackground : public CGameObject { bool SetFrameMsg(CSetFrameMsg *msg); bool VisibleMsg(CVisibleMsg *msg); protected: - int _fieldBC; - int _fieldC0; + int _startFrame; + int _endFrame; CString _string1; CString _string2; int _fieldDC; diff --git a/engines/titanic/core/click_responder.cpp b/engines/titanic/core/click_responder.cpp index f9694557df..9a0e0de7ab 100644 --- a/engines/titanic/core/click_responder.cpp +++ b/engines/titanic/core/click_responder.cpp @@ -24,20 +24,33 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CClickResponder, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CClickResponder::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string1, indent); - file->writeQuotedLine(_string2, indent); + file->writeQuotedLine(_message, indent); + file->writeQuotedLine(_soundName, indent); CGameObject::save(file, indent); } void CClickResponder::load(SimpleFile *file) { file->readNumber(); - _string1 = file->readString(); - _string2 = file->readString(); + _message = file->readString(); + _soundName = file->readString(); CGameObject::load(file); } +bool CClickResponder::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!_soundName.empty()) + playSound(_soundName); + if (!_message.empty()) + petDisplayMessage(_message); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/core/click_responder.h b/engines/titanic/core/click_responder.h index 78381b9948..40f22d7906 100644 --- a/engines/titanic/core/click_responder.h +++ b/engines/titanic/core/click_responder.h @@ -28,8 +28,10 @@ namespace Titanic { class CClickResponder : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); protected: - CString _string1, _string2; + CString _message, _soundName; public: CLASSDEF; diff --git a/engines/titanic/core/drop_target.cpp b/engines/titanic/core/drop_target.cpp index 05ea6445c3..13a31c3f8a 100644 --- a/engines/titanic/core/drop_target.cpp +++ b/engines/titanic/core/drop_target.cpp @@ -21,30 +21,40 @@ */ #include "titanic/core/drop_target.h" +#include "titanic/carry/carry.h" namespace Titanic { -CDropTarget::CDropTarget() : CGameObject(), _fieldC4(0), - _fieldD4(0), _fieldE4(0), _fieldF4(0), _fieldF8(0), - _fieldFC(0), _field10C(1), _field110(8), _field114(20) { +BEGIN_MESSAGE_MAP(CDropTarget, CGameObject) + ON_MESSAGE(DropObjectMsg) + ON_MESSAGE(MouseDragStartMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(VisibleMsg) + ON_MESSAGE(DropZoneLostObjectMsg) +END_MESSAGE_MAP() + +CDropTarget::CDropTarget() : CGameObject(), _itemFrame(0), + _itemMatchSize(0), _showItem(false), _fieldF4(0), _dropFrame(0), + _dragFrame(0), _dragCursorId(CURSOR_ARROW), _dropCursorId(CURSOR_HAND), + _clipFlags(20) { } void CDropTarget::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writePoint(_pos1, indent); - file->writeNumberLine(_fieldC4, indent); - file->writeQuotedLine(_string1, indent); - file->writeNumberLine(_fieldD4, indent); - file->writeQuotedLine(_string2, indent); - file->writeNumberLine(_fieldE4, indent); - file->writeQuotedLine(_string3, indent); + file->writeNumberLine(_itemFrame, indent); + file->writeQuotedLine(_itemMatchName, indent); + file->writeNumberLine(_itemMatchSize, indent); + file->writeQuotedLine(_soundName, indent); + file->writeNumberLine(_showItem, indent); + file->writeQuotedLine(_itemName, indent); file->writeNumberLine(_fieldF4, indent); - file->writeNumberLine(_fieldF8, indent); - file->writeNumberLine(_fieldFC, indent); - file->writeQuotedLine(_string4, indent); - file->writeNumberLine(_field10C, indent); - file->writeNumberLine(_field110, indent); - file->writeNumberLine(_field114, indent); + file->writeNumberLine(_dropFrame, indent); + file->writeNumberLine(_dragFrame, indent); + file->writeQuotedLine(_clipName, indent); + file->writeNumberLine(_dragCursorId, indent); + file->writeNumberLine(_dropCursorId, indent); + file->writeNumberLine(_clipFlags, indent); CGameObject::save(file, indent); } @@ -52,21 +62,129 @@ void CDropTarget::save(SimpleFile *file, int indent) { void CDropTarget::load(SimpleFile *file) { file->readNumber(); _pos1 = file->readPoint(); - _fieldC4 = file->readNumber(); - _string1 = file->readString(); - _fieldD4 = file->readNumber(); - _string2 = file->readString(); - _fieldE4 = file->readNumber(); - _string3 = file->readString(); + _itemFrame = file->readNumber(); + _itemMatchName = file->readString(); + _itemMatchSize = file->readNumber(); + _soundName = file->readString(); + _showItem = file->readNumber(); + _itemName = file->readString(); _fieldF4 = file->readNumber(); - _fieldF8 = file->readNumber(); - _fieldFC = file->readNumber(); - _string4 = file->readString(); - _field10C = file->readNumber(); - _field110 = file->readNumber(); - _field114 = file->readNumber(); + _dropFrame = file->readNumber(); + _dragFrame = file->readNumber(); + _clipName = file->readString(); + _dragCursorId = (CursorId)file->readNumber(); + _dropCursorId = (CursorId)file->readNumber(); + _clipFlags = file->readNumber(); CGameObject::load(file); } +bool CDropTarget::DropObjectMsg(CDropObjectMsg *msg) { + if (!_itemName.empty()) { + if (msg->_item->getName() != _itemName) { + if (findByName(_itemName, true)) + return false; + } + } + + if (!msg->_item->isEquals(_itemMatchName, _itemMatchSize)) + return false; + + msg->_item->detach(); + msg->_item->addUnder(this); + msg->_item->setPosition(Point(_bounds.left, _bounds.top)); + + msg->_item->loadFrame(_itemFrame); + if (_showItem) + msg->_item->setVisible(false); + + CDropZoneGotObjectMsg gotMsg(this); + gotMsg.execute(msg->_item); + playSound(_soundName); + + if (_clipName.empty()) { + loadFrame(_dropFrame); + } else { + playClip(_clipName, _clipFlags); + } + + _cursorId = _dropCursorId; + return true; +} + +bool CDropTarget::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (!checkStartDragging(msg)) + return false; + //msg->_dragItem = msg->_dragItem; + + CGameObject *obj = dynamic_cast<CGameObject *>(findByName(_itemName)); + if (_itemName.empty() || _fieldF4 || !obj) + return false; + + CDropZoneLostObjectMsg lostMsg; + lostMsg._object = this; + lostMsg.execute(obj); + + loadFrame(_dragFrame); + _cursorId = _dragCursorId; + + if (obj->_visible) { + msg->execute(obj); + } else { + msg->_dragItem = obj; + CPassOnDragStartMsg passMsg(msg->_mousePos, 1); + passMsg.execute(obj); + setVisible(true); + } + + return true; +} + +bool CDropTarget::EnterViewMsg(CEnterViewMsg *msg) { + if (!_itemName.empty()) { + CGameObject *obj = dynamic_cast<CGameObject *>(findByName(_itemName)); + if (!obj) { + loadFrame(_dragFrame); + _cursorId = _dragCursorId; + } else if (_clipName.empty()) { + loadFrame(_dropFrame); + } else { + playClip(_clipName, _clipFlags); + } + + _cursorId = _dropCursorId; + } + + return true; +} + +bool CDropTarget::VisibleMsg(CVisibleMsg *msg) { + setVisible(msg->_visible); + _fieldF4 = !msg->_visible; + return true; +} + +bool CDropTarget::DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg) { + if (!_itemName.empty()) { + CGameObject *obj = dynamic_cast<CGameObject *>(findByName(_itemName)); + if (obj) { + if (msg->_object) { + obj->detach(); + obj->addUnder(msg->_object); + } else if (dynamic_cast<CCarry *>(obj)) { + obj->petAddToInventory(); + } + + setVisible(true); + CDropZoneLostObjectMsg lostMsg(this); + lostMsg.execute(obj); + } + + loadFrame(_dragFrame); + _cursorId = _dragCursorId; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/core/drop_target.h b/engines/titanic/core/drop_target.h index 4bd0ae448c..e07b640c9f 100644 --- a/engines/titanic/core/drop_target.h +++ b/engines/titanic/core/drop_target.h @@ -28,21 +28,27 @@ namespace Titanic { class CDropTarget : public CGameObject { -private: + DECLARE_MESSAGE_MAP; + bool DropObjectMsg(CDropObjectMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool VisibleMsg(CVisibleMsg *msg); + bool DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg); +protected: Point _pos1; - int _fieldC4; - CString _string1; - int _fieldD4; - CString _string2; - int _fieldE4; - CString _string3; + int _itemFrame; + CString _itemMatchName; + int _itemMatchSize; + CString _soundName; + bool _showItem; + CString _itemName; int _fieldF4; - int _fieldF8; - int _fieldFC; - CString _string4; - int _field10C; - int _field110; - int _field114; + int _dropFrame; + int _dragFrame; + CString _clipName; + CursorId _dragCursorId; + CursorId _dropCursorId; + uint _clipFlags; public: CLASSDEF; CDropTarget(); diff --git a/engines/titanic/core/game_object.cpp b/engines/titanic/core/game_object.cpp index 67b7920f04..7848734792 100644 --- a/engines/titanic/core/game_object.cpp +++ b/engines/titanic/core/game_object.cpp @@ -42,7 +42,7 @@ int CGameObject::_soundHandles[4]; void CGameObject::init() { _credits = nullptr; - _soundHandles[0] = _soundHandles[1] = 0; + _soundHandles[0] = _soundHandles[1] = -1; _soundHandles[2] = _soundHandles[3] = -1; } @@ -438,7 +438,8 @@ bool CGameObject::isSoundActive(int handle) const { return false; } -void CGameObject::playGlobalSound(const CString &resName, int mode, bool initialMute, bool repeated, int handleIndex) { +void CGameObject::playGlobalSound(const CString &resName, int mode, bool initialMute, bool repeated, + int handleIndex, Audio::Mixer::SoundType soundType) { if (handleIndex < 0 || handleIndex > 3) return; CGameManager *gameManager = getGameManager(); @@ -463,19 +464,20 @@ void CGameObject::playGlobalSound(const CString &resName, int mode, bool initial CProximity prox; prox._channelVolume = volume; prox._repeated = repeated; + prox._soundType = soundType; switch (handleIndex) { case 0: - prox._channel = 6; + prox._channelMode = 6; break; case 1: - prox._channel = 7; + prox._channelMode = 7; break; case 2: - prox._channel = 8; + prox._channelMode = 8; break; case 3: - prox._channel = 9; + prox._channelMode = 9; break; default: break; @@ -519,7 +521,6 @@ void CGameObject::stopGlobalSound(bool transition, int handleIndex) { sound.stopSound(_soundHandles[handleIndex]); _soundHandles[handleIndex] = -1; } - warning("CGameObject::soundFn4"); } void CGameObject::setGlobalSoundVolume(int mode, uint seconds, int handleIndex) { @@ -657,10 +658,10 @@ void CGameObject::playClip(uint startFrame, uint endFrame) { gameManager->playClip(clip, room, room); } -void CGameObject::playRandomClip(const char **names, uint flags) { +void CGameObject::playRandomClip(const char *const *names, uint flags) { // Count size of array int count = 0; - for (const char **p = names; *p; ++p) + for (const char *const *p = names; *p; ++p) ++count; // Play clip @@ -668,6 +669,20 @@ void CGameObject::playRandomClip(const char **names, uint flags) { playClip(name, flags); } +void CGameObject::playCutscene(uint startFrame, uint endFrame) { + if (!_surface) { + if (!_resource.empty()) + loadResource(_resource); + _resource.clear(); + } + + if (_surface && _surface->loadIfReady() && _surface->_movie) { + disableMouse(); + _surface->_movie->playCutscene(_bounds, startFrame, endFrame); + enableMouse(); + } +} + void CGameObject::savePosition() { _savedPos = _bounds; } @@ -726,7 +741,7 @@ int CGameObject::playSound(const CString &name, uint volume, int val3, bool repe } int CGameObject::playSound(const CString &name, CProximity &prox) { - if (prox._field28 == 2) { + if (prox._positioningMode == POSMODE_VECTOR) { // If the proximity doesn't have a position defined, default it to // the position of the view to which the game object belongs if (prox._posX == 0.0 && prox._posY == 0.0 && prox._posZ == 0.0) @@ -768,30 +783,34 @@ void CGameObject::stopSound(int handle, uint seconds) { } int CGameObject::addTimer(int endVal, uint firstDuration, uint repeatDuration) { - CTimeEventInfo *timer = new CTimeEventInfo(g_vm->_events->getTicksCount(), - repeatDuration != 0, firstDuration, repeatDuration, this, endVal, CString()); + CTimeEventInfo *timer = new CTimeEventInfo(getTicksCount(), repeatDuration != 0, + firstDuration, repeatDuration, this, endVal, CString()); getGameManager()->addTimer(timer); return timer->_id; } int CGameObject::addTimer(uint firstDuration, uint repeatDuration) { - CTimeEventInfo *timer = new CTimeEventInfo(g_vm->_events->getTicksCount(), - repeatDuration != 0, firstDuration, repeatDuration, this, 0, CString()); + CTimeEventInfo *timer = new CTimeEventInfo(getTicksCount(), repeatDuration != 0, + firstDuration, repeatDuration, this, 0, CString()); getGameManager()->addTimer(timer); return timer->_id; } +void CGameObject::stopTimer(int id) { + getGameManager()->stopTimer(id); +} + int CGameObject::startAnimTimer(const CString &action, uint firstDuration, uint repeatDuration) { - CTimeEventInfo *timer = new CTimeEventInfo(g_vm->_events->getTicksCount(), - repeatDuration > 0, firstDuration, repeatDuration, this, 0, action); + CTimeEventInfo *timer = new CTimeEventInfo(getTicksCount(), repeatDuration > 0, + firstDuration, repeatDuration, this, 0, action); getGameManager()->addTimer(timer); return timer->_id; } -void CGameObject::stopTimer(int id) { +void CGameObject::stopAnimTimer(int id) { getGameManager()->stopTimer(id); } @@ -852,15 +871,15 @@ CViewItem *CGameObject::parseView(const CString &viewString) { return nullptr; // Find the designated node within the room - CNodeItem *node = static_cast<CNodeItem *>(room->findChildInstanceOf(CNodeItem::_type)); + CNodeItem *node = dynamic_cast<CNodeItem *>(room->findChildInstanceOf(CNodeItem::_type)); while (node && node->getName() != nodeName) - node = static_cast<CNodeItem *>(room->findNextInstanceOf(CNodeItem::_type, node)); + node = dynamic_cast<CNodeItem *>(room->findNextInstanceOf(CNodeItem::_type, node)); if (!node) return nullptr; - CViewItem *view = static_cast<CViewItem *>(node->findChildInstanceOf(CViewItem::_type)); + CViewItem *view = dynamic_cast<CViewItem *>(node->findChildInstanceOf(CViewItem::_type)); while (view && view->getName() != viewName) - view = static_cast<CViewItem *>(node->findNextInstanceOf(CViewItem::_type, view)); + view = dynamic_cast<CViewItem *>(node->findNextInstanceOf(CViewItem::_type, view)); if (!view) return nullptr; @@ -879,7 +898,12 @@ CString CGameObject::getViewFullName() const { } void CGameObject::sleep(uint milli) { - g_vm->_events->sleep(milli); + // Use an empty event target so that standard scene drawing won't happen + Events &events = *g_vm->_events; + CEventTarget nullTarget; + events.addTarget(&nullTarget); + events.sleep(milli); + events.removeTarget(); } Point CGameObject::getMousePos() const { @@ -945,12 +969,12 @@ CGameObject *CGameObject::getNextMail(CGameObject *prior) { } CGameObject *CGameObject::findRoomObject(const CString &name) const { - return static_cast<CGameObject *>(findRoom()->findByName(name)); + return dynamic_cast<CGameObject *>(findRoom()->findByName(name)); } CGameObject *CGameObject::findInRoom(const CString &name) { CRoomItem *room = getRoom(); - return room ? static_cast<CGameObject *>(room->findByName(name)) : nullptr; + return room ? dynamic_cast<CGameObject *>(room->findByName(name)) : nullptr; } Found CGameObject::find(const CString &name, CGameObject **item, int findAreas) { @@ -977,7 +1001,7 @@ Found CGameObject::find(const CString &name, CGameObject **item, int findAreas) } if (findAreas & FIND_GLOBAL) { - go = static_cast<CGameObject *>(getRoot()->findByName(name)); + go = dynamic_cast<CGameObject *>(getRoot()->findByName(name)); if (go) { *item = go; return FOUND_GLOBAL; @@ -998,21 +1022,21 @@ Found CGameObject::find(const CString &name, CGameObject **item, int findAreas) void CGameObject::moveToView() { CViewItem *view = getGameManager()->getView(); detach(); - view->addUnder(this); + addUnder(view); } void CGameObject::moveToView(const CString &name) { CViewItem *view = parseView(name); detach(); - view->addUnder(this); + addUnder(view); } -void CGameObject::stateInc14() { - getGameManager()->_gameState.inc14(); +void CGameObject::stateChangeSeason() { + getGameManager()->_gameState.changeSeason(); } -int CGameObject::stateGet14() const { - return getGameManager()->_gameState._field14; +Season CGameObject::stateGetSeason() const { + return getGameManager()->_gameState._seasonNum; } void CGameObject::stateSet24() { @@ -1048,7 +1072,7 @@ void CGameObject::setMovieFrameRate(double rate) { _surface->setMovieFrameRate(rate); } -void CGameObject::setTextBorder(const CString &str, int border, int borderRight) { +void CGameObject::setText(const CString &str, int border, int borderRight) { if (!_text) _text = new CPetText(); _textBorder = border; @@ -1142,8 +1166,8 @@ void CGameObject::mouseUnlockE4() { CScreenManager::_screenManagerPtr->_mouseCursor->unlockE4(); } -void CGameObject::mouseSaveState(int v1, int v2, int v3) { - CScreenManager::_screenManagerPtr->_mouseCursor->saveState(v1, v2, v3); +void CGameObject::mouseSetPosition(const Point &pt, double rate) { + CScreenManager::_screenManagerPtr->_mouseCursor->setPosition(pt, rate); } void CGameObject::lockInputHandler() { @@ -1172,6 +1196,10 @@ void CGameObject::loadSurface() { _surface->loadIfReady(); } +bool CGameObject::changeView(const CString &viewName) { + return changeView(viewName, ""); +} + bool CGameObject::changeView(const CString &viewName, const CString &clipName) { CViewItem *newView = parseView(viewName); CGameManager *gameManager = getGameManager(); @@ -1202,9 +1230,9 @@ void CGameObject::dragMove(const Point &pt) { setPosition(Point(pt.x - _bounds.width() / 2, pt.y - _bounds.height() / 2)); } -bool CGameObject::isObjectDragging() const { +CGameObject *CGameObject::getDraggingObject() const { CTreeItem *item = getGameManager()->_dragItem; - return item ? static_cast<CGameObject *>(item) != nullptr : false; + return dynamic_cast<CGameObject *>(item); } Point CGameObject::getControid() const { @@ -1232,7 +1260,7 @@ CDontSaveFileItem *CGameObject::getDontSave() const { } CPetControl *CGameObject::getPetControl() const { - return static_cast<CPetControl *>(getDontSaveChild(CPetControl::_type)); + return dynamic_cast<CPetControl *>(getDontSaveChild(CPetControl::_type)); } CMailMan *CGameObject::getMailMan() const { @@ -1271,7 +1299,7 @@ CRoomItem *CGameObject::locateRoom(const CString &name) const { CGameObject *CGameObject::getHiddenObject(const CString &name) const { CRoomItem *room = getHiddenRoom(); - return room ? static_cast<CGameObject *>(findUnder(room, name)) : nullptr; + return room ? dynamic_cast<CGameObject *>(findUnder(room, name)) : nullptr; } CTreeItem *CGameObject::findUnder(CTreeItem *parent, const CString &name) const { @@ -1363,10 +1391,14 @@ int CGameObject::getClipDuration(const CString &name, int frameRate) const { return clip ? (clip->_endFrame - clip->_startFrame) * 1000 / frameRate : 0; } -uint32 CGameObject::getTickCount() { +uint32 CGameObject::getTicksCount() { return g_vm->_events->getTicksCount(); } +Common::SeekableReadStream *CGameObject::getResource(const CString &name) { + return g_vm->_filesManager->getResource(name); +} + bool CGameObject::compareRoomFlags(int mode, uint flags1, uint flags2) { switch (mode) { case 1: @@ -1424,7 +1456,7 @@ void CGameObject::resetMail() { mailMan->resetValue(); } -int CGameObject::getNewRandomNumber(int max, int *oldVal) { +int CGameObject::getRandomNumber(int max, int *oldVal) { if (oldVal) { int startingVal = *oldVal; while (*oldVal == startingVal && max > 0) @@ -1479,7 +1511,7 @@ CTreeItem *CGameObject::petContainerRemove(CGameObject *obj) { if (!obj->compareRoomNameTo("CarryParcel")) return obj; - CGameObject *item = static_cast<CGameObject *>(pet->getLastChild()); + CGameObject *item = dynamic_cast<CGameObject *>(pet->getLastChild()); if (item) item->detach(); @@ -1574,11 +1606,11 @@ void CGameObject::petUnlockInput() { /*------------------------------------------------------------------------*/ CStarControl *CGameObject::getStarControl() const { - CStarControl *starControl = static_cast<CStarControl *>(getDontSaveChild(CStarControl::_type)); + CStarControl *starControl = dynamic_cast<CStarControl *>(getDontSaveChild(CStarControl::_type)); if (!starControl) { CViewItem *view = getGameManager()->getView(); if (view) - starControl = static_cast<CStarControl *>(view->findChildInstanceOf(CStarControl::_type)); + starControl = dynamic_cast<CStarControl *>(view->findChildInstanceOf(CStarControl::_type)); } return starControl; @@ -1590,16 +1622,15 @@ void CGameObject::starFn1(int v) { starControl->fn1(v); } -void CGameObject::starFn2() { +bool CGameObject::starFn2() { CStarControl *starControl = getStarControl(); - if (starControl) - starControl->fn4(); + return starControl ? starControl->fn4() : false; } /*------------------------------------------------------------------------*/ void CGameObject::startTalking(const CString &npcName, uint id, CViewItem *view) { - CTrueTalkNPC *npc = static_cast<CTrueTalkNPC *>(getRoot()->findByName(npcName)); + CTrueTalkNPC *npc = dynamic_cast<CTrueTalkNPC *>(getRoot()->findByName(npcName)); startTalking(npc, id, view); } diff --git a/engines/titanic/core/game_object.h b/engines/titanic/core/game_object.h index cad05dea00..d72fd94ac4 100644 --- a/engines/titanic/core/game_object.h +++ b/engines/titanic/core/game_object.h @@ -23,6 +23,8 @@ #ifndef TITANIC_GAME_OBJECT_H #define TITANIC_GAME_OBJECT_H +#include "audio/mixer.h" +#include "common/stream.h" #include "titanic/support/mouse_cursor.h" #include "titanic/support/credit_text.h" #include "titanic/support/movie_range_info.h" @@ -32,6 +34,7 @@ #include "titanic/core/named_item.h" #include "titanic/pet_control/pet_section.h" #include "titanic/pet_control/pet_text.h" +#include "titanic/game_state.h" namespace Titanic { @@ -52,7 +55,6 @@ class CGameObject : public CNamedItem { friend class OSMovie; DECLARE_MESSAGE_MAP; private: - static CCreditText *_credits; static int _soundHandles[4]; private: /** @@ -61,16 +63,6 @@ private: void loadResource(const CString &name); /** - * Loads a movie - */ - void loadMovie(const CString &name, bool pendingFlag = true); - - /** - * Loads an image - */ - void loadImage(const CString &name, bool pendingFlag = true); - - /** * Process and remove any registered movie range info */ void processMoveRangeInfo(); @@ -81,7 +73,8 @@ private: */ bool clipRect(const Rect &rect1, Rect &rect2) const; protected: - Rect _bounds; + static CCreditText *_credits; +protected: double _field34; double _field38; double _field3C; @@ -128,6 +121,16 @@ protected: */ CViewItem * parseView(const CString &viewString); + /** + * Loads a movie + */ + void loadMovie(const CString &name, bool pendingFlag = true); + + /** + * Loads an image + */ + void loadImage(const CString &name, bool pendingFlag = true); + void inc54(); void dec54(); @@ -164,7 +167,10 @@ protected: void mouseLockE4(); void mouseUnlockE4(); - void mouseSaveState(int v1, int v2, int v3); + /** + * Sets the mouse to a new position + */ + void mouseSetPosition(const Point &pt, double rate); /** * Lock the input handler @@ -229,8 +235,10 @@ protected: * @param initialMute If set, sound transitions in from mute over 2 seconds * @param repeated Flag for repeating sounds * @param handleIndex Slot 0 to 3 in the shared sound handle list to store the sound's handle + * @param soundType Specifies whether the sound is a sound effect or music */ - void playGlobalSound(const CString &resName, int mode, bool initialMute, bool repeated, int handleIndex); + void playGlobalSound(const CString &resName, int mode, bool initialMute, bool repeated, + int handleIndex, Audio::Mixer::SoundType soundType = Audio::Mixer::kMusicSoundType); /** * Stops a sound saved in the global sound handle list @@ -260,14 +268,19 @@ protected: int addTimer(uint firstDuration, uint repeatDuration = 0); /** + * Stops a timer + */ + void stopTimer(int id); + + /** * Start an animation timer */ int startAnimTimer(const CString &action, uint firstDuration, uint repeatDuration = 0); /** - * Stops a timer + * Stop an animation timer */ - void stopTimer(int id); + void stopAnimTimer(int id); /** * Causes the game to sleep for the specified time @@ -336,14 +349,14 @@ protected: bool changeView(const CString &viewName, const CString &clipName); /** - * Get the centre of the game object's bounds + * Change the view */ - Point getControid() const; + bool changeView(const CString &viewName); /** * Play an arbitrary clip */ - void playClip(const CString &name, uint flags); + void playClip(const CString &name, uint flags = 0); /** * Play a clip @@ -351,9 +364,14 @@ protected: void playClip(uint startFrame, uint endFrame); /** + * Play a cutscene + */ + void playCutscene(uint startFrame, uint endFrame); + + /** * Play a clip randomly from a passed list of names */ - void playRandomClip(const char **names, uint flags); + void playRandomClip(const char *const *names, uint flags = 0); /** * Return the current view/node/room as a single string @@ -437,17 +455,12 @@ protected: /** * Returns the current system tick count */ - uint32 getTickCount(); + uint32 getTicksCount(); /** - * Adds an object to the mail list + * Gets a resource from the DAT file */ - void addMail(int mailId); - - /** - * Sets the mail identifier for an object - */ - void setMailId(int mailId); + Common::SeekableReadStream *getResource(const CString &name); /** * Returns true if a mail with a specified Id exists @@ -460,11 +473,6 @@ protected: CGameObject *findMail(int id) const; /** - * Remove an object from the mail list - */ - void removeMail(int id, int v); - - /** * Resets the Mail Man value */ void resetMail(); @@ -490,9 +498,9 @@ protected: void setMovieFrameRate(double rate); /** - * Set up the text borders for the object + * Set up the text and borders for the object */ - void setTextBorder(const CString &str, int border = 0, int borderRight = 0); + void setText(const CString &str, int border = 0, int borderRight = 0); /** * Sets whether the text will use borders @@ -537,8 +545,9 @@ protected: /** * Gets a new random number */ - int getNewRandomNumber(int max, int *oldVal = nullptr); + int getRandomNumber(int max, int *oldVal = nullptr); public: + Rect _bounds; bool _isMail; int _id; uint _roomFlags; @@ -622,6 +631,11 @@ public: void setPosition(const Point &newPos); /** + * Get the centre of the game object's bounds + */ + Point getControid() const; + + /** * Change the object's status */ void playMovie(uint flags); @@ -692,6 +706,11 @@ public: int getPriorClass() const; /** + * Sets the mail identifier for an object + */ + void setMailId(int mailId); + + /** * Returns true if there's an attached surface which has a frame * ready for display */ @@ -718,9 +737,9 @@ public: void dragMove(const Point &pt); /** - * Returns true if an item being dragged is a game object + * Returns the currently dragging item (if any) if it's a game object */ - bool isObjectDragging() const; + CGameObject *getDraggingObject() const; bool compareRoomFlags(int mode, uint flags1, uint flags2); @@ -752,6 +771,16 @@ public: CString getRoomNodeName() const; /** + * Adds an object to the mail list + */ + void addMail(int mailId); + + /** + * Remove an object from the mail list + */ + void removeMail(int id, int v); + + /** * Return the full Id of the current view in a * room.node.view tuplet form */ @@ -874,7 +903,7 @@ public: CStarControl *getStarControl() const; void starFn1(int v); - void starFn2(); + bool starFn2(); /*--- CTrueTalkManager Methods ---*/ @@ -919,8 +948,17 @@ public: /*--- CGameState Methods ---*/ void setState1C(bool flag); - void stateInc14(); - int stateGet14() const; + + /** + * Change to the next season + */ + void stateChangeSeason(); + + /** + * Returns the currently active season + */ + Season stateGetSeason() const; + void stateSet24(); int stateGet24() const; void stateInc38(); diff --git a/engines/titanic/core/game_object_desc_item.cpp b/engines/titanic/core/game_object_desc_item.cpp index 409334c9d7..a6ee7e3cf5 100644 --- a/engines/titanic/core/game_object_desc_item.cpp +++ b/engines/titanic/core/game_object_desc_item.cpp @@ -30,7 +30,7 @@ CGameObjectDescItem::CGameObjectDescItem(): CTreeItem() { void CGameObjectDescItem::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); _clipList.save(file, indent); - file->writeQuotedLine(_string1, indent); + file->writeQuotedLine(_name, indent); file->writeQuotedLine(_string2, indent); _list1.save(file, indent); _list2.save(file, indent); @@ -45,7 +45,7 @@ void CGameObjectDescItem::load(SimpleFile *file) { if (val) _clipList.load(file); - _string1 = file->readString(); + _name = file->readString(); _string2 = file->readString(); _list1.load(file); _list1.load(file); diff --git a/engines/titanic/core/game_object_desc_item.h b/engines/titanic/core/game_object_desc_item.h index 7bfecaf5a2..4f485e0d55 100644 --- a/engines/titanic/core/game_object_desc_item.h +++ b/engines/titanic/core/game_object_desc_item.h @@ -31,7 +31,7 @@ namespace Titanic { class CGameObjectDescItem : public CTreeItem { protected: - CString _string1; + CString _name; CString _string2; List<ListItem> _list1; List<ListItem> _list2; @@ -49,6 +49,11 @@ public: * Load the data for the class from file */ virtual void load(SimpleFile *file); + + /** + * Gets the name of the item, if any + */ + virtual const CString getName() const { return _name; } }; } // End of namespace Titanic diff --git a/engines/titanic/core/mail_man.cpp b/engines/titanic/core/mail_man.cpp index afe13bebad..11e17fc4e5 100644 --- a/engines/titanic/core/mail_man.cpp +++ b/engines/titanic/core/mail_man.cpp @@ -37,14 +37,14 @@ void CMailMan::load(SimpleFile *file) { } CGameObject *CMailMan::getFirstObject() const { - return static_cast<CGameObject *>(getFirstChild()); + return dynamic_cast<CGameObject *>(getFirstChild()); } CGameObject *CMailMan::getNextObject(CGameObject *prior) const { if (!prior || prior->getParent() != this) return nullptr; - return static_cast<CGameObject *>(prior->getNextSibling()); + return dynamic_cast<CGameObject *>(prior->getNextSibling()); } void CMailMan::addMail(CGameObject *obj, int id) { diff --git a/engines/titanic/core/multi_drop_target.cpp b/engines/titanic/core/multi_drop_target.cpp index f2998199b1..b95696577f 100644 --- a/engines/titanic/core/multi_drop_target.cpp +++ b/engines/titanic/core/multi_drop_target.cpp @@ -21,9 +21,14 @@ */ #include "titanic/core/multi_drop_target.h" +#include "titanic/support/string_parser.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CMultiDropTarget, CDropTarget) + ON_MESSAGE(DropObjectMsg) +END_MESSAGE_MAP() + void CMultiDropTarget::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeQuotedLine(_string5, indent); @@ -40,4 +45,20 @@ void CMultiDropTarget::load(SimpleFile *file) { CDropTarget::load(file); } +bool CMultiDropTarget::DropObjectMsg(CDropObjectMsg *msg) { + CStringParser parser1(_string5); + CStringParser parser2(_string6); + CString seperatorChars = ","; + + while (parser2.parse(_itemMatchName, seperatorChars)) { + _dropFrame = parser1.readInt(); + CDropTarget::DropObjectMsg(msg); + + parser1.skipSeperators(seperatorChars); + parser2.skipSeperators(seperatorChars); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/core/multi_drop_target.h b/engines/titanic/core/multi_drop_target.h index c004b9bece..ab552f96e1 100644 --- a/engines/titanic/core/multi_drop_target.h +++ b/engines/titanic/core/multi_drop_target.h @@ -28,6 +28,8 @@ namespace Titanic { class CMultiDropTarget : public CDropTarget { + DECLARE_MESSAGE_MAP; + bool DropObjectMsg(CDropObjectMsg *msg); public: CString _string5; CString _string6; diff --git a/engines/titanic/core/named_item.cpp b/engines/titanic/core/named_item.cpp index 6eafbf8c8b..9c4c28d04d 100644 --- a/engines/titanic/core/named_item.cpp +++ b/engines/titanic/core/named_item.cpp @@ -51,11 +51,11 @@ void CNamedItem::load(SimpleFile *file) { CTreeItem::load(file); } -int CNamedItem::compareTo(const CString &name, int maxLen) const { +bool CNamedItem::isEquals(const CString &name, int maxLen) const { if (maxLen) { - return getName().left(maxLen).compareToIgnoreCase(name); + return getName().left(maxLen).compareToIgnoreCase(name) == 0; } else { - return getName().compareToIgnoreCase(name); + return getName().compareToIgnoreCase(name) == 0; } } diff --git a/engines/titanic/core/named_item.h b/engines/titanic/core/named_item.h index 809cda1156..9ee3d490ae 100644 --- a/engines/titanic/core/named_item.h +++ b/engines/titanic/core/named_item.h @@ -59,9 +59,9 @@ public: virtual const CString getName() const { return _name; } /** - * Compares the name of the item to a passed name + * Returns true if the item's name matches a passed name */ - virtual int compareTo(const CString &name, int maxLen) const; + virtual bool isEquals(const CString &name, int maxLen = 0) const; /** * Find a parent node for the item diff --git a/engines/titanic/core/project_item.cpp b/engines/titanic/core/project_item.cpp index 76293233b0..df48bad501 100644 --- a/engines/titanic/core/project_item.cpp +++ b/engines/titanic/core/project_item.cpp @@ -85,7 +85,7 @@ void CProjectItem::buildFilesList() { CTreeItem *treeItem = getFirstChild(); while (treeItem) { if (treeItem->isFileItem()) { - CString name = static_cast<CFileItem *>(treeItem)->getFilename(); + CString name = dynamic_cast<CFileItem *>(treeItem)->getFilename(); _files.add()->_name = name; } diff --git a/engines/titanic/core/room_item.cpp b/engines/titanic/core/room_item.cpp index 541a8e1a9e..49b3232ba1 100644 --- a/engines/titanic/core/room_item.cpp +++ b/engines/titanic/core/room_item.cpp @@ -36,8 +36,8 @@ void CRoomItem::save(SimpleFile *file, int indent) { _exitMovieKey.save(file, indent); file->writeQuotedLine("Room dimensions x 1000", indent); - file->writeNumberLine(_roomDimensionX * 1000.0, indent + 1); - file->writeNumberLine(_roomDimensionY * 1000.0, indent + 1); + file->writeNumberLine((int)(_roomDimensionX * 1000.0), indent + 1); + file->writeNumberLine((int)(_roomDimensionY * 1000.0), indent + 1); file->writeQuotedLine("Transition Movie", indent); _transitionMovieKey.save(file, indent); diff --git a/engines/titanic/core/saveable_object.cpp b/engines/titanic/core/saveable_object.cpp index 62cee47045..db3249c107 100644 --- a/engines/titanic/core/saveable_object.cpp +++ b/engines/titanic/core/saveable_object.cpp @@ -92,7 +92,7 @@ #include "titanic/game/bar_menu.h" #include "titanic/game/bar_menu_button.h" #include "titanic/game/belbot_get_light.h" -#include "titanic/game/bilge_succubus.h" +#include "titanic/npcs/bilge_succubus.h" #include "titanic/game/bomb.h" #include "titanic/game/bottom_of_well_monitor.h" #include "titanic/game/bowl_unlocker.h" @@ -103,7 +103,6 @@ #include "titanic/game/broken_pellerator.h" #include "titanic/game/broken_pellerator_froz.h" #include "titanic/game/cage.h" -#include "titanic/game/call_pellerator.h" #include "titanic/game/captains_wheel.h" #include "titanic/game/cdrom.h" #include "titanic/game/cdrom_computer.h" @@ -166,6 +165,8 @@ #include "titanic/game/music_room_stop_phonograph_button.h" #include "titanic/game/music_system_lock.h" #include "titanic/game/nav_helmet.h" +#include "titanic/game/nav_helmet_on.h" +#include "titanic/game/nav_helmet_off.h" #include "titanic/game/navigation_computer.h" #include "titanic/game/no_nut_bowl.h" #include "titanic/game/nose_holder.h" @@ -228,7 +229,7 @@ #include "titanic/game/parrot/parrot_nut_bowl_actor.h" #include "titanic/game/parrot/parrot_nut_eater.h" #include "titanic/game/parrot/parrot_perch_holder.h" -#include "titanic/game/parrot/parrot_succubus.h" +#include "titanic/npcs/parrot_succubus.h" #include "titanic/game/parrot/parrot_trigger.h" #include "titanic/game/parrot/player_meets_parrot.h" #include "titanic/game/pet/pet.h" @@ -251,7 +252,7 @@ #include "titanic/game/pickup/pick_up_vis_centre.h" #include "titanic/game/placeholder/bar_shelf_vis_centre.h" #include "titanic/game/placeholder/lemon_on_bar.h" -#include "titanic/game/placeholder/place_holder_item.h" +#include "titanic/game/placeholder/place_holder.h" #include "titanic/game/placeholder/tv_on_bar.h" #include "titanic/game/sgt/armchair.h" #include "titanic/game/sgt/basin.h" @@ -285,7 +286,6 @@ #include "titanic/gfx/chev_right_off.h" #include "titanic/gfx/chev_right_on.h" #include "titanic/gfx/chev_send_rec_switch.h" -#include "titanic/gfx/chev_switch.h" #include "titanic/gfx/edit_control.h" #include "titanic/gfx/elevator_button.h" #include "titanic/gfx/get_from_succ.h" @@ -333,6 +333,7 @@ #include "titanic/messages/pet_messages.h" #include "titanic/messages/service_elevator_door.h" +#include "titanic/moves/call_pellerator.h" #include "titanic/moves/enter_bomb_room.h" #include "titanic/moves/enter_bridge.h" #include "titanic/moves/enter_exit_first_class_state.h" @@ -575,6 +576,8 @@ DEFFN(CMusicRoomPhonograph); DEFFN(CMusicRoomStopPhonographButton); DEFFN(CMusicSystemLock); DEFFN(CNavHelmet); +DEFFN(CNavHelmetOn); +DEFFN(CNavHelmetOff); DEFFN(CNavigationComputer); DEFFN(CNoNutBowl); DEFFN(CNoseHolder); @@ -665,7 +668,7 @@ DEFFN(CPickUpSpeechCentre); DEFFN(CPickUpVisCentre); DEFFN(CBarShelfVisCentre); DEFFN(CLemonOnBar); -DEFFN(CPlaceHolderItem); +DEFFN(CPlaceHolder); DEFFN(CTVOnBar); DEFFN(CArmchair); DEFFN(CBasin); @@ -701,7 +704,6 @@ DEFFN(CChevLeftOn); DEFFN(CChevRightOff); DEFFN(CChevRightOn); DEFFN(CChevSendRecSwitch); -DEFFN(CChevSwitch); DEFFN(CEditControl); DEFFN(CElevatorButton); DEFFN(CGetFromSucc); @@ -757,7 +759,7 @@ DEFFN(CAutoSoundEvent); DEFFN(CBilgeAutoSoundEvent); DEFFN(CBilgeDispensorEvent); DEFFN(CBodyInBilgeRoomMsg); -DEFFN(CBowlStateChange); +DEFFN(CBowlStateChangeMsg); DEFFN(CCarryObjectArrivedMsg); DEFFN(CChangeMusicMsg); DEFFN(CChangeSeasonMsg); @@ -1162,6 +1164,8 @@ void CSaveableObject::initClassList() { ADDFN(CMusicRoomStopPhonographButton, CEjectPhonographButton); ADDFN(CMusicSystemLock, CDropTarget); ADDFN(CNavHelmet, CGameObject); + ADDFN(CNavHelmetOn, CGameObject); + ADDFN(CNavHelmetOff, CGameObject); ADDFN(CNavigationComputer, CGameObject); ADDFN(CNoNutBowl, CBackground); ADDFN(CNoseHolder, CDropTarget); @@ -1245,10 +1249,10 @@ void CSaveableObject::initClassList() { ADDFN(CPickUpLemon, CPickUp); ADDFN(CPickUpSpeechCentre, CPickUp); ADDFN(CPickUpVisCentre, CPickUp); - ADDFN(CBarShelfVisCentre, CPlaceHolderItem); - ADDFN(CLemonOnBar, CPlaceHolderItem); - ADDFN(CPlaceHolderItem, CGameObject); - ADDFN(CTVOnBar, CPlaceHolderItem); + ADDFN(CBarShelfVisCentre, CPlaceHolder); + ADDFN(CLemonOnBar, CPlaceHolder); + ADDFN(CPlaceHolder, CGameObject); + ADDFN(CTVOnBar, CPlaceHolder); ADDFN(CArmchair, CSGTStateRoom); ADDFN(CBasin, CSGTStateRoom); ADDFN(CBedfoot, CSGTStateRoom); @@ -1283,7 +1287,6 @@ void CSaveableObject::initClassList() { ADDFN(CChevRightOff, CToggleSwitch); ADDFN(CChevRightOn, CToggleSwitch); ADDFN(CChevSendRecSwitch, CToggleSwitch); - ADDFN(CChevSwitch, CToggleSwitch); ADDFN(CEditControl, CGameObject); ADDFN(CElevatorButton, CSTButton); ADDFN(CGetFromSucc, CToggleSwitch); @@ -1344,7 +1347,7 @@ void CSaveableObject::initClassList() { ADDFN(CBilgeAutoSoundEvent, CAutoSoundEvent); ADDFN(CBilgeDispensorEvent, CAutoSoundEvent); ADDFN(CBodyInBilgeRoomMsg, CMessage); - ADDFN(CBowlStateChange, CMessage); + ADDFN(CBowlStateChangeMsg, CMessage); ADDFN(CCarryObjectArrivedMsg, CMessage); ADDFN(CChangeMusicMsg, CMessage); ADDFN(CChangeSeasonMsg, CMessage); diff --git a/engines/titanic/core/tree_item.cpp b/engines/titanic/core/tree_item.cpp index 6adbbe39fa..86c34cab8b 100644 --- a/engines/titanic/core/tree_item.cpp +++ b/engines/titanic/core/tree_item.cpp @@ -34,7 +34,7 @@ #include "titanic/core/room_item.h" #include "titanic/pet_control/pet_control.h" #include "titanic/game_manager.h" -#include "titanic/game/placeholder/place_holder_item.h" +#include "titanic/game/placeholder/place_holder.h" namespace Titanic { @@ -96,7 +96,7 @@ bool CTreeItem::isLinkItem() const { } bool CTreeItem::isPlaceHolderItem() const { - return isInstanceOf(CPlaceHolderItem::_type); + return isInstanceOf(CPlaceHolder::_type); } bool CTreeItem::isNamedItem() const { @@ -252,19 +252,32 @@ void CTreeItem::detach() { _priorSibling = _nextSibling = _parent = nullptr; } -CNamedItem *CTreeItem::findByName(const CString &name, int maxLen) { +void CTreeItem::attach(CTreeItem *item) { + _nextSibling = item; + _priorSibling = item->_priorSibling; + _parent = item->_parent; + + if (item->_priorSibling) + item->_priorSibling->_nextSibling = this; + + item->_priorSibling = this; + if (item->_parent && !item->_parent->_firstChild) + item->_parent->_firstChild = this; +} + +CNamedItem *CTreeItem::findByName(const CString &name, bool subMatch) { CString nameLower = name; nameLower.toLowercase(); for (CTreeItem *treeItem = this; treeItem; treeItem = treeItem->scan(this)) { - CString nodeName = treeItem->getName(); - nodeName.toLowercase(); + CString itemName = treeItem->getName(); + itemName.toLowercase(); - if (maxLen) { - if (nodeName.left(maxLen).compareTo(nameLower)) + if (subMatch) { + if (itemName.left(name.size()).compareTo(nameLower)) return dynamic_cast<CNamedItem *>(treeItem); } else { - if (!nodeName.compareTo(nameLower)) + if (!itemName.compareTo(nameLower)) return dynamic_cast<CNamedItem *>(treeItem); } } diff --git a/engines/titanic/core/tree_item.h b/engines/titanic/core/tree_item.h index db4ba30a44..e92f5cda49 100644 --- a/engines/titanic/core/tree_item.h +++ b/engines/titanic/core/tree_item.h @@ -125,6 +125,11 @@ public: virtual const CString getName() const { return CString(); } /** + * Returns true if the item's name matches a passed name + */ + virtual bool isEquals(const CString &name, int maxLen = 0) const { return false; } + + /** * Compares the name of the item to a passed name */ virtual int compareTo(const CString &name, int maxLen = 0) const { return false; } @@ -242,9 +247,17 @@ public: void detach(); /** + * Attaches a tree item to a new node + */ + void attach(CTreeItem *item); + + /** * Finds a tree item by name + * @param name Name to find + * @param subMatch If false, does an exact name match. + * If false, matches any item that starts with the given name */ - CNamedItem *findByName(const CString &name, int maxLen = 0); + CNamedItem *findByName(const CString &name, bool subMatch = false); }; } // End of namespace Titanic diff --git a/engines/titanic/core/turn_on_play_sound.cpp b/engines/titanic/core/turn_on_play_sound.cpp index 2f9dba24a6..ab50b33134 100644 --- a/engines/titanic/core/turn_on_play_sound.cpp +++ b/engines/titanic/core/turn_on_play_sound.cpp @@ -24,26 +24,37 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CTurnOnPlaySound, CTurnOnObject) + ON_MESSAGE(MouseButtonUpMsg) +END_MESSAGE_MAP() + CTurnOnPlaySound::CTurnOnPlaySound() : CTurnOnObject(), - _string3("NULL"), _fieldF8(80), _fieldFC(0) { + _soundName("NULL"), _soundVolume(80), _soundVal3(0) { } void CTurnOnPlaySound::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string3, indent); - file->writeNumberLine(_fieldF8, indent); - file->writeNumberLine(_fieldFC, indent); + file->writeQuotedLine(_soundName, indent); + file->writeNumberLine(_soundVolume, indent); + file->writeNumberLine(_soundVal3, indent); CTurnOnObject::save(file, indent); } void CTurnOnPlaySound::load(SimpleFile *file) { file->readNumber(); - _string3 = file->readString(); - _fieldF8 = file->readNumber(); - _fieldFC = file->readNumber(); + _soundName = file->readString(); + _soundVolume = file->readNumber(); + _soundVal3 = file->readNumber(); CTurnOnObject::load(file); } +bool CTurnOnPlaySound::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + if (_soundName != "NULL") + playSound(_soundName, _soundVolume, _soundVal3); + + return CTurnOnObject::MouseButtonUpMsg(msg); +} + } // End of namespace Titanic diff --git a/engines/titanic/core/turn_on_play_sound.h b/engines/titanic/core/turn_on_play_sound.h index 1164135071..29a25a5665 100644 --- a/engines/titanic/core/turn_on_play_sound.h +++ b/engines/titanic/core/turn_on_play_sound.h @@ -28,10 +28,12 @@ namespace Titanic { class CTurnOnPlaySound : public CTurnOnObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); private: - CString _string3; - int _fieldF8; - int _fieldFC; + CString _soundName; + int _soundVolume; + int _soundVal3; public: CLASSDEF; CTurnOnPlaySound(); diff --git a/engines/titanic/core/turn_on_turn_off.cpp b/engines/titanic/core/turn_on_turn_off.cpp index d43ddf7038..6498c226a0 100644 --- a/engines/titanic/core/turn_on_turn_off.cpp +++ b/engines/titanic/core/turn_on_turn_off.cpp @@ -24,16 +24,21 @@ namespace Titanic { -CTurnOnTurnOff::CTurnOnTurnOff() : CBackground(), _fieldE0(0), - _fieldE4(0), _fieldE8(0), _fieldEC(0), _fieldF0(0) { +BEGIN_MESSAGE_MAP(CTurnOnTurnOff, CBackground) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) +END_MESSAGE_MAP() + +CTurnOnTurnOff::CTurnOnTurnOff() : CBackground(), _startFrameOn(0), + _endFrameOn(0), _startFrameOff(0), _endFrameOff(0), _fieldF0(false) { } void CTurnOnTurnOff::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); - file->writeNumberLine(_fieldE4, indent); - file->writeNumberLine(_fieldE8, indent); - file->writeNumberLine(_fieldEC, indent); + file->writeNumberLine(_startFrameOn, indent); + file->writeNumberLine(_endFrameOn, indent); + file->writeNumberLine(_startFrameOff, indent); + file->writeNumberLine(_endFrameOff, indent); file->writeNumberLine(_fieldF0, indent); CBackground::save(file, indent); @@ -41,13 +46,37 @@ void CTurnOnTurnOff::save(SimpleFile *file, int indent) { void CTurnOnTurnOff::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); - _fieldE4 = file->readNumber(); - _fieldE8 = file->readNumber(); - _fieldEC = file->readNumber(); + _startFrameOn = file->readNumber(); + _endFrameOn = file->readNumber(); + _startFrameOff = file->readNumber(); + _endFrameOff = file->readNumber(); _fieldF0 = file->readNumber(); CBackground::load(file); } +bool CTurnOnTurnOff::TurnOn(CTurnOn *msg) { + if (!_fieldF0) { + if (_fieldDC) + playMovie(_startFrameOn, _endFrameOn, MOVIE_GAMESTATE); + else + playMovie(_startFrameOn, _endFrameOn, MOVIE_NOTIFY_OBJECT); + _fieldF0 = true; + } + + return true; +} + +bool CTurnOnTurnOff::TurnOff(CTurnOff *msg) { + if (!_fieldF0) { + if (_fieldDC) + playMovie(_startFrameOff, _endFrameOff, MOVIE_GAMESTATE); + else + playMovie(_startFrameOff, _endFrameOff, MOVIE_NOTIFY_OBJECT); + _fieldF0 = true; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/core/turn_on_turn_off.h b/engines/titanic/core/turn_on_turn_off.h index adca6876ff..c09f0e0d7d 100644 --- a/engines/titanic/core/turn_on_turn_off.h +++ b/engines/titanic/core/turn_on_turn_off.h @@ -28,12 +28,15 @@ namespace Titanic { class CTurnOnTurnOff : public CBackground { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); private: - int _fieldE0; - int _fieldE4; - int _fieldE8; - int _fieldEC; - int _fieldF0; + int _startFrameOn; + int _endFrameOn; + int _startFrameOff; + int _endFrameOff; + bool _fieldF0; public: CLASSDEF; CTurnOnTurnOff(); diff --git a/engines/titanic/core/view_item.cpp b/engines/titanic/core/view_item.cpp index 03e2753839..af23fca027 100644 --- a/engines/titanic/core/view_item.cpp +++ b/engines/titanic/core/view_item.cpp @@ -168,8 +168,8 @@ void CViewItem::enterView(CViewItem *newView) { CLinkItem *CViewItem::findLink(CViewItem *newView) { for (CTreeItem *treeItem = getFirstChild(); treeItem; - treeItem = scan(treeItem)) { - CLinkItem *link = static_cast<CLinkItem *>(treeItem); + treeItem = treeItem->scan(this)) { + CLinkItem *link = dynamic_cast<CLinkItem *>(treeItem); if (link && link->connectsTo(newView)) return link; } diff --git a/engines/titanic/debugger.cpp b/engines/titanic/debugger.cpp index 37fc546851..a9da83f724 100644 --- a/engines/titanic/debugger.cpp +++ b/engines/titanic/debugger.cpp @@ -231,7 +231,7 @@ bool Debugger::cmdItem(int argc, const char **argv) { } // Get the item - CCarry *item = static_cast<CCarry *>( + CCarry *item = dynamic_cast<CCarry *>( g_vm->_window->_project->findByName(argv[1])); assert(item); diff --git a/engines/titanic/events.cpp b/engines/titanic/events.cpp index 8a7cd550e8..318ddf5726 100644 --- a/engines/titanic/events.cpp +++ b/engines/titanic/events.cpp @@ -84,6 +84,11 @@ void Events::pollEvents() { void Events::pollEventsAndWait() { pollEvents(); g_system->delayMillis(10); + + // Regularly update the sound mixer + CGameManager *gameManager = g_vm->_window->_gameManager; + if (gameManager) + gameManager->_sound.updateMixer(); } bool Events::checkForNextFrameCounter() { @@ -114,12 +119,9 @@ uint32 Events::getTicksCount() const { void Events::sleep(uint time) { uint32 delayEnd = g_system->getMillis() + time; - CSound &sound = g_vm->_window->_gameManager->_sound; - while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd) { + while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd) pollEventsAndWait(); - sound.updateMixer(); - } } bool Events::waitForPress(uint expiry) { diff --git a/engines/titanic/game/announce.cpp b/engines/titanic/game/announce.cpp index df6689d262..74c126476f 100644 --- a/engines/titanic/game/announce.cpp +++ b/engines/titanic/game/announce.cpp @@ -24,27 +24,109 @@ namespace Titanic { -CAnnounce::CAnnounce() : _fieldBC(0), _fieldC0(0), _fieldC4(1), _fieldC8(0) { +BEGIN_MESSAGE_MAP(CAnnounce, CGameObject) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(LeaveRoomMsg) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + +CAnnounce::CAnnounce() : _nameIndex(0), _soundHandle(0), _leaveFlag(1), _enabled(false) { } void CAnnounce::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); - file->writeNumberLine(_fieldC4, indent); - file->writeNumberLine(_fieldC8, indent); + file->writeNumberLine(_nameIndex, indent); + file->writeNumberLine(_soundHandle, indent); + file->writeNumberLine(_leaveFlag, indent); + file->writeNumberLine(_enabled, indent); CGameObject::save(file, indent); } void CAnnounce::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); - _fieldC4 = file->readNumber(); - _fieldC8 = file->readNumber(); + _nameIndex = file->readNumber(); + _soundHandle = file->readNumber(); + _leaveFlag = file->readNumber(); + _enabled = file->readNumber(); CGameObject::load(file); } +bool CAnnounce::TimerMsg(CTimerMsg *msg) { + if (!_enabled) + return false; + + if (msg->_timerCtr == 1) { + CString numStr = "0"; + CString waveNames1[20] = { + "z#181.wav", "z#211.wav", "z#203.wav", "z#202.wav", "z#201.wav", + "z#200.wav", "z#199.wav", "z#198.wav", "z#197.wav", "z#196.wav", + "z#210.wav", "z#209.wav", "z#208.wav", "z#207.wav", "z#206.wav", + "z#205.wav", "z#204.wav", "z#145.wav", "", "" + }; + CString waveNames2[37] = { + "z#154.wav", "z#153.wav", "z#152.wav", "z#151.wav", "z#150.wav", + "z#149.wav", "z#148.wav", "z#169.wav", "z#171.wav", "z#178.wav", + "z#176.wav", "z#177.wav", "z#165.wav", "z#170.wav", "z#180.wav", + "z#156.wav", "z#172.wav", "z#173.wav", "z#160.wav", "z#158.wav", + "z#161.wav", "z#179.wav", "z#163.wav", "z#164.wav", "z#162.wav", + "z#159.wav", "z#175.wav", "z#166.wav", "z#174.wav", "z#157.wav", + "", "", "", "", "", "", "" + }; + + int randVal = _nameIndex ? getRandomNumber(2) : 0; + switch (randVal) { + case 0: + case 1: + _soundHandle = playSound("z#189.wav"); + if (_nameIndex < 20) { + queueSound(waveNames1[_nameIndex], _soundHandle); + ++_nameIndex; + } else { + queueSound(waveNames1[1 + getRandomNumber(17)], _soundHandle); + } + break; + + case 2: + _soundHandle = playSound("z#189.wav"); + queueSound(waveNames2[1 + getRandomNumber(35)], _soundHandle); + break; + + default: + break; + } + + addTimer(1, 300000 + getRandomNumber(30000), 0); + if (getRandomNumber(3) == 0) + addTimer(2, 4000, 0); + + } else if (msg->_timerCtr == 2) { + CParrotSpeakMsg speakMsg; + speakMsg._target = "Announcements"; + speakMsg.execute("PerchedParrot"); + } + + return true; +} + +bool CAnnounce::LeaveRoomMsg(CLeaveRoomMsg *msg) { + if (_leaveFlag) { + addTimer(1, 1000, 0); + _leaveFlag = 0; + _enabled = true; + } + + return true; +} + +bool CAnnounce::ActMsg(CActMsg *msg) { + if (msg->_action == "Enable") + _enabled = true; + else if (msg->_action == "Disable") + _enabled = false; + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/announce.h b/engines/titanic/game/announce.h index f960241c36..9bf060daae 100644 --- a/engines/titanic/game/announce.h +++ b/engines/titanic/game/announce.h @@ -28,11 +28,15 @@ namespace Titanic { class CAnnounce : public CGameObject { + DECLARE_MESSAGE_MAP; + bool TimerMsg(CTimerMsg *msg); + bool LeaveRoomMsg(CLeaveRoomMsg *msg); + bool ActMsg(CActMsg *msg); private: - int _fieldBC; - int _fieldC0; - int _fieldC4; - int _fieldC8; + int _nameIndex; + int _soundHandle; + bool _leaveFlag; + bool _enabled; public: CLASSDEF; CAnnounce(); diff --git a/engines/titanic/game/annoy_barbot.cpp b/engines/titanic/game/annoy_barbot.cpp index d69d9fff3c..8b22f9c13a 100644 --- a/engines/titanic/game/annoy_barbot.cpp +++ b/engines/titanic/game/annoy_barbot.cpp @@ -26,6 +26,10 @@ namespace Titanic { int CAnnoyBarbot::_v1; +BEGIN_MESSAGE_MAP(CAnnoyBarbot, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CAnnoyBarbot::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_v1, indent); @@ -38,4 +42,13 @@ void CAnnoyBarbot::load(SimpleFile *file) { CGameObject::load(file); } +bool CAnnoyBarbot::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if ((++_v1 % 3) == 1) { + CActMsg actMsg("GoRingBell"); + actMsg.execute("Barbot"); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/annoy_barbot.h b/engines/titanic/game/annoy_barbot.h index 955a82bdf8..0ccfe43794 100644 --- a/engines/titanic/game/annoy_barbot.h +++ b/engines/titanic/game/annoy_barbot.h @@ -28,6 +28,8 @@ namespace Titanic { class CAnnoyBarbot : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); private: static int _v1; public: diff --git a/engines/titanic/game/arboretum_gate.cpp b/engines/titanic/game/arboretum_gate.cpp index 9caa87c48d..1435e3e204 100644 --- a/engines/titanic/game/arboretum_gate.cpp +++ b/engines/titanic/game/arboretum_gate.cpp @@ -25,135 +25,306 @@ namespace Titanic { BEGIN_MESSAGE_MAP(CArboretumGate, CBackground) + ON_MESSAGE(ChangeSeasonMsg) ON_MESSAGE(ActMsg) + ON_MESSAGE(MovieEndMsg) ON_MESSAGE(LeaveViewMsg) ON_MESSAGE(TurnOff) ON_MESSAGE(MouseButtonDownMsg) ON_MESSAGE(EnterViewMsg) ON_MESSAGE(TurnOn) - ON_MESSAGE(MovieEndMsg) END_MESSAGE_MAP() int CArboretumGate::_v1; -int CArboretumGate::_v2; +int CArboretumGate::_initialFrame; int CArboretumGate::_v3; CArboretumGate::CArboretumGate() : CBackground() { - _string1 = "NULL"; - _string2 = "NULL"; - _fieldE0 = 0; + _viewName1 = "NULL"; + _viewName2 = "NULL"; + _seasonNum = 0; _fieldF0 = 0; - _fieldF4 = 244; - _fieldF8 = 304; - _fieldFC = 122; - _field100 = 182; - _field104 = 183; - _field108 = 243; - _field10C = 665; - _field110 = 724; - _field114 = 61; - _field118 = 121; - _field11C = 0; - _field120 = 60; - _field124 = 485; - _field128 = 544; - _field12C = 425; - _field130 = 484; - _field134 = 545; - _field138 = 604; - _field13C = 605; - _field140 = 664; - _field144 = 305; - _field148 = 364; - _field14C = 365; - _field150 = 424; + _startFrameSpringOff = 244; + _endFrameSpringOff = 304; + _startFrameSummerOff = 122; + _endFrameSummerOff = 182; + _startFrameAutumnOff1 = 183; + _endFrameAutumnOff1 = 243; + _startFrameAutumnOff2 = 665; + _endFrameAutumnOff2 = 724; + _startFrameWinterOff1 = 61; + _endFrameWinterOff1 = 121; + _startFrameWinterOff2 = 0; + _endFrameWinterOff2 = 60; + _startFrameSpringOn = 485; + _endFrameSpringOn = 544; + _startFrameSummerOn = 425; + _endFrameSummerOn = 484; + _startFrameAutumnOn1 = 545; + _endFrameAutumnOn1 = 604; + _startFrameAutumnOn2 = 605; + _endFrameAutumnOn2 = 664; + _startFrameWinterOn1 = 305; + _endFrameWinterOn1 = 364; + _startFrameWinterOn2 = 365; + _endFrameWinterOn2 = 424; } void CArboretumGate::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); + file->writeNumberLine(_seasonNum, indent); file->writeNumberLine(_v1, indent); - file->writeNumberLine(_v2, indent); + file->writeNumberLine(_initialFrame, indent); file->writeNumberLine(_v3, indent); - file->writeQuotedLine(_string1, indent); + file->writeQuotedLine(_viewName1, indent); file->writeNumberLine(_fieldF0, indent); - file->writeNumberLine(_fieldF4, indent); - file->writeNumberLine(_fieldF8, indent); - file->writeNumberLine(_fieldFC, indent); - file->writeNumberLine(_field100, indent); - file->writeNumberLine(_field104, indent); - file->writeNumberLine(_field108, indent); - file->writeNumberLine(_field10C, indent); - file->writeNumberLine(_field110, indent); - file->writeNumberLine(_field114, indent); - file->writeNumberLine(_field118, indent); - file->writeNumberLine(_field11C, indent); - file->writeNumberLine(_field120, indent); - file->writeNumberLine(_field124, indent); - file->writeNumberLine(_field128, indent); - file->writeNumberLine(_field12C, indent); - file->writeNumberLine(_field130, indent); - file->writeNumberLine(_field134, indent); - file->writeNumberLine(_field138, indent); - file->writeNumberLine(_field13C, indent); - file->writeNumberLine(_field140, indent); - file->writeNumberLine(_field144, indent); - file->writeNumberLine(_field148, indent); - file->writeNumberLine(_field14C, indent); - file->writeNumberLine(_field150, indent); - file->writeQuotedLine(_string2, indent); + file->writeNumberLine(_startFrameSpringOff, indent); + file->writeNumberLine(_endFrameSpringOff, indent); + file->writeNumberLine(_startFrameSummerOff, indent); + file->writeNumberLine(_endFrameSummerOff, indent); + file->writeNumberLine(_startFrameAutumnOff1, indent); + file->writeNumberLine(_endFrameAutumnOff1, indent); + file->writeNumberLine(_startFrameAutumnOff2, indent); + file->writeNumberLine(_endFrameAutumnOff2, indent); + file->writeNumberLine(_startFrameWinterOff1, indent); + file->writeNumberLine(_endFrameWinterOff1, indent); + file->writeNumberLine(_startFrameWinterOff2, indent); + file->writeNumberLine(_endFrameWinterOff2, indent); + file->writeNumberLine(_startFrameSpringOn, indent); + file->writeNumberLine(_endFrameSpringOn, indent); + file->writeNumberLine(_startFrameSummerOn, indent); + file->writeNumberLine(_endFrameSummerOn, indent); + file->writeNumberLine(_startFrameAutumnOn1, indent); + file->writeNumberLine(_endFrameAutumnOn1, indent); + file->writeNumberLine(_startFrameAutumnOn2, indent); + file->writeNumberLine(_endFrameAutumnOn2, indent); + file->writeNumberLine(_startFrameWinterOn1, indent); + file->writeNumberLine(_endFrameWinterOn1, indent); + file->writeNumberLine(_startFrameWinterOn2, indent); + file->writeNumberLine(_endFrameWinterOn2, indent); + file->writeQuotedLine(_viewName2, indent); CBackground::save(file, indent); } void CArboretumGate::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); + _seasonNum = file->readNumber(); _v1 = file->readNumber(); - _v2 = file->readNumber(); + _initialFrame = file->readNumber(); _v3 = file->readNumber(); - _string1 = file->readString(); + _viewName1 = file->readString(); _fieldF0 = file->readNumber(); - _fieldF4 = file->readNumber(); - _fieldF8 = file->readNumber(); - _fieldFC = file->readNumber(); - _field100 = file->readNumber(); - _field104 = file->readNumber(); - _field108 = file->readNumber(); - _field10C = file->readNumber(); - _field110 = file->readNumber(); - _field114 = file->readNumber(); - _field118 = file->readNumber(); - _field11C = file->readNumber(); - _field120 = file->readNumber(); - _field124 = file->readNumber(); - _field128 = file->readNumber(); - _field12C = file->readNumber(); - _field130 = file->readNumber(); - _field134 = file->readNumber(); - _field138 = file->readNumber(); - _field13C = file->readNumber(); - _field140 = file->readNumber(); - _field144 = file->readNumber(); - _field148 = file->readNumber(); - _field14C = file->readNumber(); - _field150 = file->readNumber(); - _string2 = file->readString(); + _startFrameSpringOff = file->readNumber(); + _endFrameSpringOff = file->readNumber(); + _startFrameSummerOff = file->readNumber(); + _endFrameSummerOff = file->readNumber(); + _startFrameAutumnOff1 = file->readNumber(); + _endFrameAutumnOff1 = file->readNumber(); + _startFrameAutumnOff2 = file->readNumber(); + _endFrameAutumnOff2 = file->readNumber(); + _startFrameWinterOff1 = file->readNumber(); + _endFrameWinterOff1 = file->readNumber(); + _startFrameWinterOff2 = file->readNumber(); + _endFrameWinterOff2 = file->readNumber(); + _startFrameSpringOn = file->readNumber(); + _endFrameSpringOn = file->readNumber(); + _startFrameSummerOn = file->readNumber(); + _endFrameSummerOn = file->readNumber(); + _startFrameAutumnOn1 = file->readNumber(); + _endFrameAutumnOn1 = file->readNumber(); + _startFrameAutumnOn2 = file->readNumber(); + _endFrameAutumnOn2 = file->readNumber(); + _startFrameWinterOn1 = file->readNumber(); + _endFrameWinterOn1 = file->readNumber(); + _startFrameWinterOn2 = file->readNumber(); + _endFrameWinterOn2 = file->readNumber(); + _viewName2 = file->readString(); CBackground::load(file); } -bool CArboretumGate::ActMsg(CActMsg *msg) { return false; } -bool CArboretumGate::LeaveViewMsg(CLeaveViewMsg *msg) { return false; } -bool CArboretumGate::TurnOff(CTurnOff *msg) { return false; } -bool CArboretumGate::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { return false; } +bool CArboretumGate::ChangeSeasonMsg(CChangeSeasonMsg *msg) { + _seasonNum = (_seasonNum + 1) % 4; + return true; +} -bool CArboretumGate::EnterViewMsg(CEnterViewMsg *msg) { - warning("CArboretumGate::handleEvent"); +bool CArboretumGate::ActMsg(CActMsg *msg) { + if (msg->_action == "PlayerGetsSpeechCentre") { + _v1 = 1; + CVisibleMsg visibleMsg(true); + visibleMsg.execute("SpCtrOverlay"); + } else if (msg->_action == "ExitLFrozen") { + if (_v3) { + _viewName2 = "FrozenArboretum.Node 2.W"; + CTurnOn onMsg; + onMsg.execute(this); + } else { + changeView("FrozenArboretum.Node 2.W"); + } + } else if (msg->_action == "ExitRFrozen") { + if (_v3) { + _viewName2 = "FrozenArboretum.Node 2.E"; + CTurnOn onMsg; + onMsg.execute(this); + } else { + changeView("FrozenArboretum.Node 2.E"); + } + } else if (msg->_action == "ExitLNormal") { + if (_v3) { + _viewName2 = "Arboretum.Node 2.W"; + CTurnOn onMsg; + onMsg.execute(this); + } else { + changeView("Arboretum.Node 2.W"); + } + } else if (msg->_action == "ExitRNormal") { + if (_v3) { + _viewName2 = "Arboretum.Node 2.E"; + CTurnOn onMsg; + onMsg.execute(this); + } + else { + changeView("Arboretum.Node 2.E"); + } + } + + return true; +} + +bool CArboretumGate::MovieEndMsg(CMovieEndMsg *msg) { + setVisible(!_v3); + + if (_viewName1 != "NULL") { + changeView(_viewName1); + } else if (_viewName2 != "NULL") { + changeView(_viewName2); + _viewName2 = "NULL"; + } + + return true; +} + +bool CArboretumGate::LeaveViewMsg(CLeaveViewMsg *msg) { return false; } -bool CArboretumGate::TurnOn(CTurnOn *msg) { return false; } -bool CArboretumGate::MovieEndMsg(CMovieEndMsg *msg) { return false; } +bool CArboretumGate::TurnOff(CTurnOff *msg) { + if (!_v3) { + switch (_seasonNum) { + case SEASON_SUMMER: + playMovie(_startFrameSummerOff, _endFrameSummerOff, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + break; + + case SEASON_AUTUMN: + if (_v1) { + playMovie(_startFrameAutumnOff2, _endFrameAutumnOff2, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + } else { + playMovie(_startFrameAutumnOff1, _endFrameAutumnOff1, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + } + break; + + case SEASON_WINTER: + if (_v1) { + playMovie(_startFrameWinterOff2, _endFrameWinterOff2, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + } else { + playMovie(_startFrameWinterOff1, _endFrameWinterOff1, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + } + break; + + case SEASON_SPRING: + playMovie(_startFrameSpringOff, _endFrameSpringOff, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + break; + + default: + break; + } + + _v3 = 1; + CArboretumGateMsg gateMsg; + gateMsg.execute("Arboretum", nullptr, MSGFLAG_SCAN); + } + + return true; +} + +bool CArboretumGate::TurnOn(CTurnOn *msg) { + if (_v3) { + CArboretumGateMsg gateMsg(0); + gateMsg.execute("Arboretum"); + setVisible(true); + + switch (_seasonNum) { + case SEASON_SUMMER: + playMovie(_startFrameSummerOn, _endFrameSummerOn, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + break; + + case SEASON_AUTUMN: + if (_v1) { + playMovie(_startFrameAutumnOn2, _endFrameAutumnOn2, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + } else { + playMovie(_startFrameAutumnOn1, _endFrameAutumnOn1, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + } + break; + + case SEASON_WINTER: + if (_v1) { + playMovie(_startFrameWinterOn2, _endFrameWinterOn2, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + } else { + playMovie(_startFrameWinterOn1, _endFrameWinterOn1, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + } + break; + + case SEASON_SPRING: + playMovie(_startFrameSpringOn, _endFrameSpringOn, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + break; + + default: + break; + } + + _v3 = 0; + } + + return true; +} + +bool CArboretumGate::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!_v3) { + CTurnOff offMsg; + offMsg.execute(this); + } + + return true; +} + +bool CArboretumGate::EnterViewMsg(CEnterViewMsg *msg) { + if (!_v3) { + switch (_seasonNum) { + case SEASON_SUMMER: + _initialFrame = _startFrameSummerOff; + break; + + case SEASON_AUTUMN: + _initialFrame = _v1 ? _startFrameAutumnOff2 : _startFrameAutumnOff1; + break; + + case SEASON_WINTER: + _initialFrame = _v1 ? _startFrameWinterOff1 : _startFrameWinterOff2; + break; + + case SEASON_SPRING: + _initialFrame = _startFrameSpringOff; + break; + + default: + break; + } + + loadFrame(_initialFrame); + } + + return true; +} } // End of namespace Titanic diff --git a/engines/titanic/game/arboretum_gate.h b/engines/titanic/game/arboretum_gate.h index 927b2190c7..b1c06cf773 100644 --- a/engines/titanic/game/arboretum_gate.h +++ b/engines/titanic/game/arboretum_gate.h @@ -31,48 +31,47 @@ namespace Titanic { class CArboretumGate : public CBackground { DECLARE_MESSAGE_MAP; + bool ChangeSeasonMsg(CChangeSeasonMsg *msg); bool ActMsg(CActMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); bool LeaveViewMsg(CLeaveViewMsg *msg); bool TurnOff(CTurnOff *msg); bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); bool EnterViewMsg(CEnterViewMsg *msg); bool TurnOn(CTurnOn *msg); - bool MovieEndMsg(CMovieEndMsg *msg); private: static int _v1; - static int _v2; + static int _initialFrame; static int _v3; private: - int _fieldE0; - CString _string1; - int _fieldE8; - int _fieldEC; + int _seasonNum; + CString _viewName1; int _fieldF0; - int _fieldF4; - int _fieldF8; - int _fieldFC; - int _field100; - int _field104; - int _field108; - int _field10C; - int _field110; - int _field114; - int _field118; - int _field11C; - int _field120; - int _field124; - int _field128; - int _field12C; - int _field130; - int _field134; - int _field138; - int _field13C; - int _field140; - int _field144; - int _field148; - int _field14C; - int _field150; - CString _string2; + int _startFrameSpringOff; + int _endFrameSpringOff; + int _startFrameSummerOff; + int _endFrameSummerOff; + int _startFrameAutumnOff2; + int _endFrameAutumnOff2; + int _startFrameAutumnOff1; + int _endFrameAutumnOff1; + int _startFrameWinterOff2; + int _endFrameWinterOff2; + int _startFrameWinterOff1; + int _endFrameWinterOff1; + int _startFrameSpringOn; + int _endFrameSpringOn; + int _startFrameSummerOn; + int _endFrameSummerOn; + int _startFrameAutumnOn1; + int _endFrameAutumnOn1; + int _startFrameAutumnOn2; + int _endFrameAutumnOn2; + int _startFrameWinterOn1; + int _endFrameWinterOn1; + int _startFrameWinterOn2; + int _endFrameWinterOn2; + CString _viewName2; public: CLASSDEF; CArboretumGate(); diff --git a/engines/titanic/game/auto_animate.cpp b/engines/titanic/game/auto_animate.cpp index 172b8c44df..16e6e56747 100644 --- a/engines/titanic/game/auto_animate.cpp +++ b/engines/titanic/game/auto_animate.cpp @@ -24,24 +24,44 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CAutoAnimate, CBackground) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(InitializeAnimMsg) +END_MESSAGE_MAP() + void CAutoAnimate::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); + file->writeNumberLine(_enabled, indent); file->writeNumberLine(_fieldE4, indent); - file->writeNumberLine(_fieldE8, indent); + file->writeNumberLine(_repeat, indent); CBackground::save(file, indent); } void CAutoAnimate::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); + _enabled = file->readNumber(); _fieldE4 = file->readNumber(); - _fieldE8 = file->readNumber(); + _repeat = file->readNumber(); CBackground::load(file); } bool CAutoAnimate::EnterViewMsg(CEnterViewMsg *msg) { - warning("CAutoAnimate::handleEvent"); + if (_enabled) { + uint flags = _repeat ? MOVIE_REPEAT : 0; + if (_startFrame != _endFrame) + playMovie(_startFrame, _endFrame, flags); + else + playMovie(flags); + + if (!_fieldE4) + _enabled = false; + } + + return true; +} + +bool CAutoAnimate::InitializeAnimMsg(CInitializeAnimMsg *msg) { + _enabled = true; return true; } diff --git a/engines/titanic/game/auto_animate.h b/engines/titanic/game/auto_animate.h index 7bca808bfb..735aba922e 100644 --- a/engines/titanic/game/auto_animate.h +++ b/engines/titanic/game/auto_animate.h @@ -29,14 +29,16 @@ namespace Titanic { class CAutoAnimate : public CBackground { + DECLARE_MESSAGE_MAP; bool EnterViewMsg(CEnterViewMsg *msg); + bool InitializeAnimMsg(CInitializeAnimMsg *msg); private: - int _fieldE0; + bool _enabled; int _fieldE4; - int _fieldE8; + bool _repeat; public: CLASSDEF; - CAutoAnimate() : CBackground(), _fieldE0(1), _fieldE4(1), _fieldE8(0) {} + CAutoAnimate() : CBackground(), _enabled(true), _fieldE4(1), _repeat(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/bar_bell.cpp b/engines/titanic/game/bar_bell.cpp index b33ee1c26c..5f17dffda1 100644 --- a/engines/titanic/game/bar_bell.cpp +++ b/engines/titanic/game/bar_bell.cpp @@ -24,15 +24,22 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CBarBell, CGameObject) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + CBarBell::CBarBell() : CGameObject(), _fieldBC(0), - _fieldC0(65), _fieldC4(0), _fieldC8(0), _fieldCC(0) { + _volume(65), _soundVal3(0), _fieldC8(0), _fieldCC(0) { } void CBarBell::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); - file->writeNumberLine(_fieldC4, indent); + file->writeNumberLine(_volume, indent); + file->writeNumberLine(_soundVal3, indent); file->writeNumberLine(_fieldC8, indent); file->writeNumberLine(_fieldCC, indent); @@ -42,8 +49,8 @@ void CBarBell::save(SimpleFile *file, int indent) { void CBarBell::load(SimpleFile *file) { file->readNumber(); _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); - _fieldC4 = file->readNumber(); + _volume = file->readNumber(); + _soundVal3 = file->readNumber(); _fieldC8 = file->readNumber(); _fieldCC = file->readNumber(); @@ -55,4 +62,70 @@ bool CBarBell::EnterRoomMsg(CEnterRoomMsg *msg) { return true; } +bool CBarBell::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if ((_fieldC8 % 3) == 2) { + switch (_fieldBC) { + case 0: + case 1: + case 5: + playSound("c#54.wav", _volume, _soundVal3); + break; + + case 2: + playSound("c#52.wav", _volume, _soundVal3); + break; + + case 3: + playSound("c#53.wav", _volume, _soundVal3); + break; + + case 4: + playSound("c#55.wav", _volume, _soundVal3); + break; + + default: + playSound("c#51.wav", _volume, _soundVal3); + break; + } + } else if (_fieldBC >= 5) { + if (_fieldBC == 6) { + CActMsg actMsg("BellRing3"); + actMsg.execute("Barbot"); + } + + playSound("c#51.wav", _volume, _soundVal3); + } else { + if (_fieldBC == 3) { + CActMsg actMsg("BellRing1"); + actMsg.execute("Barbot"); + } else if (_fieldBC == 4) { + CActMsg actMsg("BellRing2"); + actMsg.execute("Barbot"); + } + + playSound("c#54.wav", _volume, _soundVal3); + } + + return true; +} + +bool CBarBell::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + if (!_fieldBC) { + CTurnOn onMsg; + onMsg.execute("Barbot"); + } + + ++_fieldBC; + return true; +} + +bool CBarBell::ActMsg(CActMsg *msg) { + if (msg->_action == "ResetCount") { + _fieldBC = 0; + ++_fieldC8; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/bar_bell.h b/engines/titanic/game/bar_bell.h index 5d1d2c54e0..b50fe505ba 100644 --- a/engines/titanic/game/bar_bell.h +++ b/engines/titanic/game/bar_bell.h @@ -29,11 +29,15 @@ namespace Titanic { class CBarBell : public CGameObject { + DECLARE_MESSAGE_MAP; bool EnterRoomMsg(CEnterRoomMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool ActMsg(CActMsg *msg); public: int _fieldBC; - int _fieldC0; - int _fieldC4; + int _volume; + int _soundVal3; int _fieldC8; int _fieldCC; public: diff --git a/engines/titanic/game/bar_menu.cpp b/engines/titanic/game/bar_menu.cpp index b24c429c9b..3812a8dab6 100644 --- a/engines/titanic/game/bar_menu.cpp +++ b/engines/titanic/game/bar_menu.cpp @@ -24,25 +24,81 @@ namespace Titanic { -CBarMenu::CBarMenu() : CGameObject(), _fieldBC(0), _fieldC0(0), _fieldC4(6) { +BEGIN_MESSAGE_MAP(CBarMenu, CGameObject) + ON_MESSAGE(PETActivateMsg) + ON_MESSAGE(PETDownMsg) + ON_MESSAGE(PETUpMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + +CBarMenu::CBarMenu() : CGameObject(), _barFrameNumber(0), _visibleFlag(false), _numFrames(6) { } void CBarMenu::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); - file->writeNumberLine(_fieldC4, indent); + file->writeNumberLine(_barFrameNumber, indent); + file->writeNumberLine(_visibleFlag, indent); + file->writeNumberLine(_numFrames, indent); CGameObject::save(file, indent); } void CBarMenu::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); - _fieldC4 = file->readNumber(); + _barFrameNumber = file->readNumber(); + _visibleFlag = file->readNumber(); + _numFrames = file->readNumber(); CGameObject::load(file); } +bool CBarMenu::PETActivateMsg(CPETActivateMsg *msg) { + if (msg->_name == "Television") { + _visibleFlag = !_visibleFlag; + setVisible(_visibleFlag); + loadFrame(_barFrameNumber); + } + + return true; +} + +bool CBarMenu::PETDownMsg(CPETDownMsg *msg) { + if (_visibleFlag) { + if (--_barFrameNumber < 0) + _barFrameNumber = _numFrames - 1; + + loadFrame(_barFrameNumber); + } + + return true; +} + +bool CBarMenu::PETUpMsg(CPETUpMsg *msg) { + if (_visibleFlag) { + _barFrameNumber = (_barFrameNumber + 1) % _numFrames; + loadFrame(_barFrameNumber); + } + + return true; +} + +bool CBarMenu::EnterViewMsg(CEnterViewMsg *msg) { + petSetArea(PET_REMOTE); + petHighlightGlyph(2); + petSetRemoteTarget(); + setVisible(_visibleFlag); + loadFrame(_barFrameNumber); + + return true; +} + +bool CBarMenu::LeaveViewMsg(CLeaveViewMsg *msg) { + petClear(); + _visibleFlag = false; + setVisible(false); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/bar_menu.h b/engines/titanic/game/bar_menu.h index 84c0219084..f16f7b035d 100644 --- a/engines/titanic/game/bar_menu.h +++ b/engines/titanic/game/bar_menu.h @@ -24,14 +24,21 @@ #define TITANIC_BAR_MENU_H #include "titanic/core/game_object.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { class CBarMenu : public CGameObject { + DECLARE_MESSAGE_MAP; + bool PETActivateMsg(CPETActivateMsg *msg); + bool PETDownMsg(CPETDownMsg *msg); + bool PETUpMsg(CPETUpMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); public: - int _fieldBC; - int _fieldC0; - int _fieldC4; + int _barFrameNumber; + bool _visibleFlag; + int _numFrames; public: CLASSDEF; CBarMenu(); diff --git a/engines/titanic/game/bar_menu_button.cpp b/engines/titanic/game/bar_menu_button.cpp index f57d72c64a..874584db30 100644 --- a/engines/titanic/game/bar_menu_button.cpp +++ b/engines/titanic/game/bar_menu_button.cpp @@ -21,9 +21,15 @@ */ #include "titanic/game/bar_menu_button.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CBarMenuButton, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseButtonUpMsg) +END_MESSAGE_MAP() + void CBarMenuButton::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value, indent); @@ -36,4 +42,20 @@ void CBarMenuButton::load(SimpleFile *file) { CGameObject::load(file); } +bool CBarMenuButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + return true; +} + +bool CBarMenuButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + if (_value) { + CPETUpMsg upMsg("", -1); + upMsg.execute("BarTelevision"); + } else { + CPETDownMsg downMsg("", -1); + downMsg.execute("BarTelevision"); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/bar_menu_button.h b/engines/titanic/game/bar_menu_button.h index c666df413b..300435c209 100644 --- a/engines/titanic/game/bar_menu_button.h +++ b/engines/titanic/game/bar_menu_button.h @@ -28,6 +28,9 @@ namespace Titanic { class CBarMenuButton : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); public: int _value; public: diff --git a/engines/titanic/game/belbot_get_light.cpp b/engines/titanic/game/belbot_get_light.cpp index 3e678a8a0c..2cc4c3ae19 100644 --- a/engines/titanic/game/belbot_get_light.cpp +++ b/engines/titanic/game/belbot_get_light.cpp @@ -24,6 +24,13 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CBelbotGetLight, CGameObject) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(MovieFrameMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + void CBelbotGetLight::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeQuotedLine(_value, indent); @@ -36,4 +43,36 @@ void CBelbotGetLight::load(SimpleFile *file) { CGameObject::load(file); } +bool CBelbotGetLight::ActMsg(CActMsg *msg) { + if (msg->_action == "BellbotGetLight") { + _value = getFullViewName(); + lockMouse(); + changeView("1stClassState.Node 11.N", ""); + } + + return true; +} + +bool CBelbotGetLight::MovieEndMsg(CMovieEndMsg *msg) { + sleep(1000); + changeView(_value, ""); + unlockMouse(); + return true; +} + +bool CBelbotGetLight::MovieFrameMsg(CMovieFrameMsg *msg) { + if (getMovieFrame() == 37) { + CActMsg actMsg("BellbotGetLight"); + actMsg.execute("Eye1"); + } + + return true; +} + +bool CBelbotGetLight::EnterViewMsg(CEnterViewMsg *msg) { + playMovie(MOVIE_NOTIFY_OBJECT); + movieEvent(37); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/belbot_get_light.h b/engines/titanic/game/belbot_get_light.h index a3aa0f737e..1707ad4793 100644 --- a/engines/titanic/game/belbot_get_light.h +++ b/engines/titanic/game/belbot_get_light.h @@ -28,6 +28,11 @@ namespace Titanic { class CBelbotGetLight : public CGameObject { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool MovieFrameMsg(CMovieFrameMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); private: CString _value; public: diff --git a/engines/titanic/game/bomb.cpp b/engines/titanic/game/bomb.cpp index 9a08f26ece..f3f1129e22 100644 --- a/engines/titanic/game/bomb.cpp +++ b/engines/titanic/game/bomb.cpp @@ -21,21 +21,67 @@ */ #include "titanic/game/bomb.h" -#include "titanic/titanic.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CBomb, CBackground) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(TrueTalkGetStateValueMsg) + ON_MESSAGE(SetFrameMsg) +END_MESSAGE_MAP() + +static const char *const WAVE_NAMES1[] = { + "z#353.wav", "z#339.wav", "z#325.wav", "z#311.wav", "z#297.wav", + "z#283.wav", "z#269.wav", "z#255.wav", "z#241.wav" +}; + +static const char *const WAVE_NAMES2[] = { + "", "z#352.wav", "z#338.wav", "z#324.wav", "z#310.wav", "z#296.wav", + "z#281.wav", "z#268.wav", "z#254.wav", "z#240.wav", "", "z#351.wav", + "z#337.wav", "z#323.wav", "z#309.wav", "z#295.wav", "z#282.wav", + "z#267.wav", "z#253.wav", "z#239.wav" +}; + +static const char *const WAVE_NAMES3[100] = { + "bombcountdown_c0.wav", "z#355.wav", "z#341.wav", "z#327.wav", "z#313.wav", + "z#299.wav", "z#285.wav", "z#271.wav", "z#257.wav", "z#243.wav", + "z#354.wav", "z#350.wav", "z#349.wav", "z#348.wav", "z#347.wav", + "z#346.wav", "z#345.wav", "z#344.wav", "z#343.wav", "z#342.wav", + "z#340.wav", "z#336.wav", "z#335.wav", "z#334.wav", "z#333.wav", + "z#332.wav", "z#331.wav", "z#330.wav", "z#329.wav", "z#328.wav", + "z#326.wav", "z#322.wav", "z#321.wav", "z#320.wav", "z#319.wav", + "z#318.wav", "z#317.wav", "z#316.wav", "z#315.wav", "z#314.wav", + "z#312.wav", "z#308.wav", "z#307.wav", "z#306.wav", "z#305.wav", + "z#304.wav", "z#303.wav", "z#302.wav", "z#301.wav", "z#300.wav", + "z#298.wav", "z#294.wav", "z#293.wav", "z#292.wav", "z#291.wav", + "z#290.wav", "z#289.wav", "z#288.wav", "z#287.wav", "z#286.wav", + "z#284.wav", "z#280.wav", "z#279.wav", "z#278.wav", "z#277.wav", + "z#276.wav", "z#275.wav", "z#274.wav", "z#273.wav", "z#272.wav", + "z#270.wav", "z#266.wav", "z#265.wav", "z#264.wav", "z#263.wav", + "z#262.wav", "z#261.wav", "z#260.wav", "z#259.wav", "z#258.wav", + "z#256.wav", "z#252.wav", "z#251.wav", "z#250.wav", "z#249.wav", + "z#248.wav", "z#247.wav", "z#246.wav", "z#245.wav", "z#244.wav", + "z#242.wav", "z#238.wav", "z#237.wav", "z#236.wav", "z#235.wav", + "z#234.wav", "z#233.wav", "z#232.wav", "z#231.wav", "z#230.wav", +}; + CBomb::CBomb() : CBackground() { _fieldE0 = 0; _fieldE4 = 0; _fieldE8 = 17; _fieldEC = 9; _fieldF0 = 0; - _fieldF4 = 999; - _fieldF8 = 0; + _countdown = 999; + _soundHandle = 0; _fieldFC = 0; _startingTicks = 0; - _field104 = 60; + _volume = 60; } void CBomb::save(SimpleFile *file, int indent) { @@ -45,11 +91,11 @@ void CBomb::save(SimpleFile *file, int indent) { file->writeNumberLine(_fieldE8, indent); file->writeNumberLine(_fieldEC, indent); file->writeNumberLine(_fieldF0, indent); - file->writeNumberLine(_fieldF4, indent); - file->writeNumberLine(_fieldF8, indent); + file->writeNumberLine(_countdown, indent); + file->writeNumberLine(_soundHandle, indent); file->writeNumberLine(_fieldFC, indent); file->writeNumberLine(_startingTicks, indent); - file->writeNumberLine(_field104, indent); + file->writeNumberLine(_volume, indent); CBackground::save(file, indent); } @@ -61,20 +107,256 @@ void CBomb::load(SimpleFile *file) { _fieldE8 = file->readNumber(); _fieldEC = file->readNumber(); _fieldF0 = file->readNumber(); - _fieldF4 = file->readNumber(); - _fieldF8 = file->readNumber(); + _countdown = file->readNumber(); + _soundHandle = file->readNumber(); _fieldFC = file->readNumber(); _startingTicks = file->readNumber(); - _field104 = file->readNumber(); + _volume = file->readNumber(); CBackground::load(file); } +bool CBomb::StatusChangeMsg(CStatusChangeMsg *msg) { + _fieldE4 += msg->_newStatus; + + if (_fieldE4 == 23) { + startAnimTimer("Disarmed", 2000); + lockMouse(); + } + + _fieldF0 %= 1000; + if (!(_fieldF0 % 20) && _countdown < 995) { + int val = getRandomNumber(5) + 25; + if (_fieldF0 < 20 || _fieldF0 > 80) + val = 28; + + CString name; + switch (val - 25) { + case 0: + name = "z#372.wav"; + break; + case 1: + name = "z#371.wav"; + break; + case 2: + name = "z#370.wav"; + break; + case 3: + name = "z#369.wav"; + break; + case 4: + name = "z#368.wav"; + break; + default: + name = "z#366.wav"; + break; + } + + _soundHandle = queueSound(name, _soundHandle, _volume); + } + + return true; +} + +bool CBomb::EnterViewMsg(CEnterViewMsg *msg) { + _fieldE4 = 2; + return true; +} + +bool CBomb::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + playSound("z#62.wav"); + + if (_fieldE0) { + stopSound(_soundHandle); + if (_fieldE4 < 23) { + _fieldE8 = MIN(_fieldE8 + 1, 23); + + CString name; + switch (_fieldE8) { + case 18: + name = "z#380.wav"; + break; + case 19: + name = "z#379.wav"; + break; + case 20: + name = "z#377.wav"; + break; + case 21: + name = "z#376.wav"; + break; + case 22: + name = "z#375.wav"; + break; + default: + name = "z#374.wav"; + break; + } + + _soundHandle = queueSound(name, _soundHandle, _volume); + _countdown = 999; + } + } else { + _soundHandle = playSound("z#389.wav", _volume); + _fieldE0 = true; + CActMsg actMsg("Arm Bomb"); + actMsg.execute("EndExplodeShip"); + } + + return true; +} + bool CBomb::EnterRoomMsg(CEnterRoomMsg *msg) { - _fieldE8 = 12; + _fieldE8 = 17; _fieldEC = 9; _fieldF0 = 0; - _startingTicks = g_vm->_events->getTicksCount(); + _startingTicks = getTicksCount(); + return true; +} + +bool CBomb::ActMsg(CActMsg *msg) { + if (msg->_action == "Hit") { + playSound("z#63.wav"); + stopSound(_soundHandle); + + if (_fieldEC < 17) + ++_fieldEC; + + CString name; + switch (_fieldEC) { + case 10: + name = "z#388.wav"; + break; + case 11: + name = "z#387.wav"; + break; + case 12: + name = "z#386.wav"; + break; + case 13: + name = "z#385.wav"; + break; + case 14: + name = "z#384.wav"; + break; + case 15: + name = "z#383.wav"; + break; + case 16: + name = "z#382.wav"; + break; + default: + name = "z#381.wav"; + break; + } + + _soundHandle = queueSound(name, _soundHandle, _volume); + _countdown = 999; + } + + return true; +} + +bool CBomb::TurnOn(CTurnOn *msg) { + if (!_fieldE0) { + _soundHandle = playSound("z#389.wav", _volume); + _fieldE0 = true; + + CActMsg actMsg("Arm Bomb"); + actMsg.execute("EndExplodeShip"); + addTimer(0); + } + + changeView("Titania.Node 8.W", ""); + CActMsg actMsg("Titania.Node 8.N"); + actMsg.execute("BombNav"); + actMsg.execute("EnterBombRoom"); + + return true; +} + +bool CBomb::TimerMsg(CTimerMsg *msg) { + if (msg->_action == "Disarmed") { + stopSound(_soundHandle); + playSound("z#364.wav", _volume); + + CActMsg actMsg1("Disarm Bomb"); + actMsg1.execute("EndExplodeShip"); + _fieldE0 = false; + CActMsg actMsg2("Titania.Node 5.N"); + actMsg2.execute("BombNav"); + actMsg2.execute("EnterBombNav"); + + changeView("Titania.Node 8.W", ""); + changeView("Titania.Node 13.N", ""); + unlockMouse(); + } + + if (compareRoomNameTo("Titania")) { + if (msg->_actionVal == 1 && getRandomNumber(9) == 0) { + if (!_fieldE0) + return true; + + CParrotSpeakMsg speakMsg("Bomb", "BombCountdown"); + speakMsg.execute("PerchedParrot"); + } + + if (_fieldE0) { + if (isSoundActive(_soundHandle)) { + if (msg->_actionVal == 0) { + addTimer(1, 1000, 0); + } else { + _soundHandle = 0; + int section = _countdown / 100; + int index = _countdown % 100; + + if (_countdown >= 100) { + CString name1 = index ? WAVE_NAMES2[section] : + WAVE_NAMES1[section]; + playSound(name1, _volume); + } + + CString name2 = WAVE_NAMES3[index]; + if (_countdown == 10) { + name2 = "z#229.wav"; + _countdown = 998; + } + + if (_soundHandle > 0) { + _soundHandle = queueSound(name2, _soundHandle, _volume); + } else { + _soundHandle = playSound(name2, _volume); + } + + --_countdown; + addTimer(0, 1000, 0); + } + } else { + addTimer(0, 100, 0); + } + } + } else { + if (_fieldE0) { + --_countdown; + addTimer(6000); + + if (_countdown < 11) + _countdown = getRandomNumber(900) + 50; + } + } + + return true; +} + +bool CBomb::TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg) { + if (msg->_stateNum == 10) + msg->_stateVal = _fieldE0; + + return true; +} + +bool CBomb::SetFrameMsg(CSetFrameMsg *msg) { + _volume = msg->_frameNumber; return true; } diff --git a/engines/titanic/game/bomb.h b/engines/titanic/game/bomb.h index ab4df16db0..f78c42cff0 100644 --- a/engines/titanic/game/bomb.h +++ b/engines/titanic/game/bomb.h @@ -29,18 +29,27 @@ namespace Titanic { class CBomb : public CBackground { + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); + bool ActMsg(CActMsg *msg); + bool TurnOn(CTurnOn *msg); + bool TimerMsg(CTimerMsg *msg); + bool TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg); + bool SetFrameMsg(CSetFrameMsg *msg); + DECLARE_MESSAGE_MAP; private: int _fieldE0; int _fieldE4; int _fieldE8; int _fieldEC; int _fieldF0; - int _fieldF4; - int _fieldF8; + int _countdown; + int _soundHandle; int _fieldFC; int _startingTicks; - int _field104; + int _volume; public: CLASSDEF; CBomb(); diff --git a/engines/titanic/game/bottom_of_well_monitor.cpp b/engines/titanic/game/bottom_of_well_monitor.cpp index beb2a80ce9..38211040d8 100644 --- a/engines/titanic/game/bottom_of_well_monitor.cpp +++ b/engines/titanic/game/bottom_of_well_monitor.cpp @@ -24,6 +24,13 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CBottomOfWellMonitor, CGameObject) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + int CBottomOfWellMonitor::_v1; int CBottomOfWellMonitor::_v2; @@ -31,7 +38,7 @@ void CBottomOfWellMonitor::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_v1, indent); file->writeNumberLine(_v2, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_flag, indent); CGameObject::save(file, indent); } @@ -39,8 +46,69 @@ void CBottomOfWellMonitor::load(SimpleFile *file) { file->readNumber(); _v1 = file->readNumber(); _v2 = file->readNumber(); - _value = file->readNumber(); + _flag = file->readNumber(); CGameObject::load(file); } +bool CBottomOfWellMonitor::ActMsg(CActMsg *msg) { + if (msg->_action == "TelevisionTaken") { + _v1 = 0; + _cursorId = CURSOR_ARROW; + CVisibleMsg visibleMsg; + visibleMsg.execute("CrushedTV2NE"); + visibleMsg.execute("CrushedTV4SW"); + _cursorId = CURSOR_ARROW; + } else if (msg->_action == "LiftbotHeadTaken") { + _v2 = 0; + _cursorId = CURSOR_ARROW; + CVisibleMsg visibleMsg; + visibleMsg.execute("LiftbotHead2NE"); + visibleMsg.execute("LiftbotHead4SW"); + _cursorId = CURSOR_ARROW; + } else if (msg->_action == "LiftbotHeadTaken") { + _v2 = 1; + CVisibleMsg visibleMsg; + visibleMsg.execute("CrushedTV2NE"); + visibleMsg.execute("CrushedTV4SW"); + _cursorId = CURSOR_MOVE_DOWN1; + } + + return true; +} + +bool CBottomOfWellMonitor::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (isEquals("BOWTelevisionMonitor")) { + if (_v1) + changeView("BottomOfWell.Node 7.N", ""); + } else { + if (_v2) + changeView("BottomOfWell.Node 8.N", ""); + } + + return true; +} + +bool CBottomOfWellMonitor::EnterViewMsg(CEnterViewMsg *msg) { + if (_flag) { + if (isEquals("BOWTelevisionMonitor")) { + if (_v1) { + changeView("BottomOfWell.Node 7.N", ""); + _flag = false; + } + } else { + if (_v2) { + changeView("BottomOfWell.Node 8.N", ""); + _flag = false; + } + } + } + + return true; +} + +bool CBottomOfWellMonitor::LeaveViewMsg(CLeaveViewMsg *msg) { + _flag = true; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/bottom_of_well_monitor.h b/engines/titanic/game/bottom_of_well_monitor.h index 65424aad70..be9ae2c093 100644 --- a/engines/titanic/game/bottom_of_well_monitor.h +++ b/engines/titanic/game/bottom_of_well_monitor.h @@ -28,12 +28,17 @@ namespace Titanic { class CBottomOfWellMonitor : public CGameObject { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); public: static int _v1, _v2; - int _value; + bool _flag; public: CLASSDEF; - CBottomOfWellMonitor() : _value(1) {} + CBottomOfWellMonitor() : _flag(true) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/bowl_unlocker.cpp b/engines/titanic/game/bowl_unlocker.cpp index c3c501dbd6..c4adac34f2 100644 --- a/engines/titanic/game/bowl_unlocker.cpp +++ b/engines/titanic/game/bowl_unlocker.cpp @@ -21,19 +21,58 @@ */ #include "titanic/game/bowl_unlocker.h" +#include "titanic/core/room_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CBowlUnlocker, CGameObject) + ON_MESSAGE(NutPuzzleMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + void CBowlUnlocker::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_bowlUnlocked, indent); CGameObject::save(file, indent); } void CBowlUnlocker::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _bowlUnlocked = file->readNumber(); CGameObject::load(file); } +bool CBowlUnlocker::NutPuzzleMsg(CNutPuzzleMsg *msg) { + if (msg->_value == "UnlockBowl") { + setVisible(true); + playMovie(MOVIE_NOTIFY_OBJECT); + } + + return true; +} + +bool CBowlUnlocker::MovieEndMsg(CMovieEndMsg *msg) { + setVisible(false); + _bowlUnlocked = true; + + CNutPuzzleMsg puzzleMsg("BowlUnlocked"); + puzzleMsg.execute(getRoom(), nullptr, MSGFLAG_SCAN); + + playSound("z#47.wav"); + return true; +} + +bool CBowlUnlocker::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_bowlUnlocked) + msg->execute("Ear1"); + return true; +} + +bool CBowlUnlocker::LeaveViewMsg(CLeaveViewMsg *msg) { + _bowlUnlocked = false; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/bowl_unlocker.h b/engines/titanic/game/bowl_unlocker.h index 2559ac2c52..b940661904 100644 --- a/engines/titanic/game/bowl_unlocker.h +++ b/engines/titanic/game/bowl_unlocker.h @@ -28,11 +28,16 @@ namespace Titanic { class CBowlUnlocker : public CGameObject { + DECLARE_MESSAGE_MAP; + bool NutPuzzleMsg(CNutPuzzleMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); public: - int _value; + bool _bowlUnlocked; public: CLASSDEF; - CBowlUnlocker() : CGameObject(), _value(0) {} + CBowlUnlocker() : CGameObject(), _bowlUnlocked(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/brain_slot.cpp b/engines/titanic/game/brain_slot.cpp index f1963142ac..1518d9b0b3 100644 --- a/engines/titanic/game/brain_slot.cpp +++ b/engines/titanic/game/brain_slot.cpp @@ -21,18 +21,27 @@ */ #include "titanic/game/brain_slot.h" +#include "titanic/core/project_item.h" namespace Titanic { -int CBrainSlot::_v1; -int CBrainSlot::_v2; +BEGIN_MESSAGE_MAP(CBrainSlot, CGameObject) + ON_MESSAGE(SetFrameMsg) + ON_MESSAGE(AddHeadPieceMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + +int CBrainSlot::_added; +bool CBrainSlot::_woken; void CBrainSlot::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value1, indent); - file->writeQuotedLine(_value2, indent); - file->writeNumberLine(_v1, indent); - file->writeNumberLine(_v2, indent); + file->writeQuotedLine(_target, indent); + file->writeNumberLine(_added, indent); + file->writeNumberLine(_woken, indent); CGameObject::save(file, indent); } @@ -40,11 +49,101 @@ void CBrainSlot::save(SimpleFile *file, int indent) { void CBrainSlot::load(SimpleFile *file) { file->readNumber(); _value1 = file->readNumber(); - _value2 = file->readString(); - _v1 = file->readNumber(); - _v2 = file->readNumber(); + _target = file->readString(); + _added = file->readNumber(); + _woken = file->readNumber(); CGameObject::load(file); } +bool CBrainSlot::SetFrameMsg(CSetFrameMsg *msg) { + loadFrame(msg->_frameNumber); + _value1 = 1; + return true; +} + +bool CBrainSlot::AddHeadPieceMsg(CAddHeadPieceMsg *msg) { + _added = 1; + _cursorId = CURSOR_HAND; + CAddHeadPieceMsg addMsg("NULL"); + + if (isEquals("AuditoryCentreSlot")) { + if (msg->_value == "AuditoryCentre") + addMsg._value = "AuditoryCentre"; + } else if (isEquals("SpeechCentreSlot")) { + if (msg->_value == "SpeechCentre") + addMsg._value = "SpeechCentre"; + } else if (isEquals("OlfactoryCentreSlot")) { + if (msg->_value == "OlfactoryCentre") + addMsg._value = "OlfactoryCentre"; + } else if (isEquals("VisionCentreSlot")) { + if (msg->_value == "VisionCentre") + addMsg._value = "VisionCentre"; + } else if (isEquals("CentralCoreSlot")) { + if (msg->_value == "CentralCore") + addMsg._value = "CentralCore"; + } + + if (addMsg._value != "NULL") + addMsg.execute("TitaniaControl"); + + if (addMsg._value == "OlfactoryCentre") + loadFrame(2); + else if (addMsg._value == "AuditoryCentre") + loadFrame(1); + else if (addMsg._value == "SpeechCentre") + loadFrame(3); + else if (addMsg._value == "VisionCentre") + loadFrame(4); + else if (addMsg._value == "CentralCore") { + CActMsg actMsg("Insert Central Core"); + actMsg.execute("CentralCoreSlot"); + } + + _target = msg->_value; + _value1 = 1; + return true; +} + +bool CBrainSlot::EnterViewMsg(CEnterViewMsg *msg) { + if (getName() == "CentralCoreSlot") + loadFrame(21); + if (_woken) + _cursorId = CURSOR_ARROW; + + return true; +} + +bool CBrainSlot::ActMsg(CActMsg *msg) { + if (msg->_action == "Insert Central Core") + playMovie(0, 21, 0); + else if (msg->_action == "Woken") + _woken = true; + + return true; +} + +bool CBrainSlot::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (!_value1 || _woken || !checkPoint(msg->_mousePos, false, true)) + return false; + + _cursorId = CURSOR_ARROW; + CVisibleMsg visibleMsg(true); + visibleMsg.execute(_target); + CTakeHeadPieceMsg takeMsg(_target); + takeMsg.execute("TitaniaControl"); + + loadFrame(isEquals("CentralCoreSlot") ? 21 : 0); + _value1 = 0; + + CPassOnDragStartMsg passMsg; + passMsg._mousePos = msg->_mousePos; + passMsg.execute(_target); + + msg->_dragItem = getRoot()->findByName(_target); + _added = 0; + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/brain_slot.h b/engines/titanic/game/brain_slot.h index 94b6d7f227..4d500cc59a 100644 --- a/engines/titanic/game/brain_slot.h +++ b/engines/titanic/game/brain_slot.h @@ -28,11 +28,18 @@ namespace Titanic { class CBrainSlot : public CGameObject { + DECLARE_MESSAGE_MAP; + bool SetFrameMsg(CSetFrameMsg *msg); + bool AddHeadPieceMsg(CAddHeadPieceMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool ActMsg(CActMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); public: - static int _v1, _v2; + static int _added; + static bool _woken; public: int _value1; - CString _value2; + CString _target; public: CLASSDEF; CBrainSlot() : CGameObject(), _value1(0) {} diff --git a/engines/titanic/game/bridge_door.cpp b/engines/titanic/game/bridge_door.cpp index 57cdbd23ad..bfa30fd650 100644 --- a/engines/titanic/game/bridge_door.cpp +++ b/engines/titanic/game/bridge_door.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CBridgeDoor, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CBridgeDoor::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -34,4 +40,23 @@ void CBridgeDoor::load(SimpleFile *file) { CGameObject::load(file); } +bool CBridgeDoor::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + setVisible(true); + playMovie(0, 6, 0); + changeView("Titania.Node 12.N"); + + return true; +} + +bool CBridgeDoor::StatusChangeMsg(CStatusChangeMsg *msg) { + setVisible(true); + playMovie(7, 0, MOVIE_NOTIFY_OBJECT); + return true; +} + +bool CBridgeDoor::MovieEndMsg(CMovieEndMsg *msg) { + setVisible(false); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/bridge_door.h b/engines/titanic/game/bridge_door.h index c1872a29be..010a8b8bc0 100644 --- a/engines/titanic/game/bridge_door.h +++ b/engines/titanic/game/bridge_door.h @@ -28,6 +28,10 @@ namespace Titanic { class CBridgeDoor : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/bridge_view.cpp b/engines/titanic/game/bridge_view.cpp index 9854969494..466480a64c 100644 --- a/engines/titanic/game/bridge_view.cpp +++ b/engines/titanic/game/bridge_view.cpp @@ -24,16 +24,92 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CBridgeView, CBackground) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CBridgeView::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); + file->writeNumberLine(_mode, indent); CBackground::save(file, indent); } void CBridgeView::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); + _mode = file->readNumber(); CBackground::load(file); } +bool CBridgeView::ActMsg(CActMsg *msg) { + CTurnOn onMsg; + CSetVolumeMsg volumeMsg; + volumeMsg._secondsTransition = 1; + + if (msg->_action == "End") { + _mode = 4; + petLockInput(); + petHide(); + setVisible(true); + playMovie(MOVIE_NOTIFY_OBJECT); + } else if (msg->_action == "Go") { + _mode = 1; + setVisible(true); + volumeMsg._volume = 100; + volumeMsg.execute("EngineSounds"); + onMsg.execute("EngineSounds"); + playMovie(MOVIE_NOTIFY_OBJECT); + } else { + volumeMsg._volume = 50; + volumeMsg.execute("EngineSounds"); + onMsg.execute("EngineSounds"); + + if (msg->_action == "Cruise") { + _mode = 2; + setVisible(true); + playMovie(MOVIE_NOTIFY_OBJECT); + } else if (msg->_action == "GoENd") { + _mode = 3; + setVisible(true); + CChangeMusicMsg musicMsg; + musicMsg._flags = 1; + musicMsg.execute("BridgeAutoMusicPlayer"); + playSound("a#42.wav"); + playMovie(MOVIE_NOTIFY_OBJECT); + } + } + + return true; +} + +bool CBridgeView::MovieEndMsg(CMovieEndMsg *msg) { + CTurnOff offMsg; + offMsg.execute("EngineSounds"); + + switch (_mode) { + case 0: + case 1: + setVisible(false); + dec54(); + break; + + case 2: { + setVisible(false); + CActMsg actMsg("End"); + actMsg.execute("HomeSequence"); + break; + } + + case 3: + setVisible(false); + changeView("TheEnd.Node 3.N"); + break; + + default: + break; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/bridge_view.h b/engines/titanic/game/bridge_view.h index d7c7c35aa9..45cfa3f4c8 100644 --- a/engines/titanic/game/bridge_view.h +++ b/engines/titanic/game/bridge_view.h @@ -28,11 +28,14 @@ namespace Titanic { class CBridgeView : public CBackground { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: - int _fieldE0; + int _mode; public: CLASSDEF; - CBridgeView() : CBackground(), _fieldE0(0) {} + CBridgeView() : CBackground(), _mode(0) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/broken_pell_base.cpp b/engines/titanic/game/broken_pell_base.cpp index 59e2b9bca1..02c2d873ac 100644 --- a/engines/titanic/game/broken_pell_base.cpp +++ b/engines/titanic/game/broken_pell_base.cpp @@ -26,7 +26,7 @@ namespace Titanic { EMPTY_MESSAGE_MAP(CBrokenPellBase, CBackground); -int CBrokenPellBase::_v1; +bool CBrokenPellBase::_v1; int CBrokenPellBase::_v2; void CBrokenPellBase::save(SimpleFile *file, int indent) { diff --git a/engines/titanic/game/broken_pell_base.h b/engines/titanic/game/broken_pell_base.h index f63cd0112b..4ca7eddd20 100644 --- a/engines/titanic/game/broken_pell_base.h +++ b/engines/titanic/game/broken_pell_base.h @@ -29,8 +29,8 @@ namespace Titanic { class CBrokenPellBase : public CBackground { DECLARE_MESSAGE_MAP; -private: - static int _v1; +protected: + static bool _v1; static int _v2; int _fieldE0; diff --git a/engines/titanic/game/broken_pellerator.cpp b/engines/titanic/game/broken_pellerator.cpp index d3b204b1e5..8fb7244b7e 100644 --- a/engines/titanic/game/broken_pellerator.cpp +++ b/engines/titanic/game/broken_pellerator.cpp @@ -21,9 +21,17 @@ */ #include "titanic/game/broken_pellerator.h" +#include "titanic/core/view_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CBrokenPellerator, CBrokenPellBase) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CBrokenPellerator::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeQuotedLine(_string2, indent); @@ -44,4 +52,103 @@ void CBrokenPellerator::load(SimpleFile *file) { CBrokenPellBase::load(file); } +bool CBrokenPellerator::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_v1) { + changeView(_v2 ? _string5 : _string4); + } else { + if (_v2) { + playMovie(28, 43, 0); + } else { + playMovie(0, 14, MOVIE_NOTIFY_OBJECT); + } + + _v1 = true; + } + + return true; +} + +bool CBrokenPellerator::LeaveViewMsg(CLeaveViewMsg *msg) { + CString name = msg->_newView->getNodeViewName(); + if (name == "Node 3.S" || name == "Node 3.N") { + _v1 = false; + loadFrame(0); + } + + return true; +} + +bool CBrokenPellerator::ActMsg(CActMsg *msg) { + if (msg->_action == "PlayerGetsHose") { + _v2 = 1; + loadFrame(43); + + CStatusChangeMsg statusMsg; + statusMsg.execute("PickupHose"); + } else { + _fieldE0 = 0; + bool closeFlag = msg->_action == "Close"; + if (msg->_action == "CloseLeft") { + closeFlag = true; + _fieldE0 = 1; + } + if (msg->_action == "CloseRight") { + closeFlag = true; + _fieldE0 = 2; + } + + if (closeFlag) { + if (_v1) { + _v1 = false; + if (_v2) + playMovie(43, 57, MOVIE_NOTIFY_OBJECT); + else + playMovie(14, 28, MOVIE_NOTIFY_OBJECT); + } else { + switch (_fieldE0) { + case 1: + changeView(_string2); + break; + case 2: + changeView(_string3); + break; + default: + break; + } + + _fieldE0 = 0; + } + } + } + + return true; +} + +bool CBrokenPellerator::MovieEndMsg(CMovieEndMsg *msg) { + if (msg->_endFrame == 14) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 1; + statusMsg.execute("PickUpHose"); + } + + if (msg->_endFrame == 28) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 0; + statusMsg.execute("PickUpHose"); + } + + switch (_fieldE0) { + case 1: + changeView(_string2); + break; + case 2: + changeView(_string3); + break; + default: + break; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/broken_pellerator.h b/engines/titanic/game/broken_pellerator.h index 6fbde91053..3b8c3ba587 100644 --- a/engines/titanic/game/broken_pellerator.h +++ b/engines/titanic/game/broken_pellerator.h @@ -28,6 +28,11 @@ namespace Titanic { class CBrokenPellerator : public CBrokenPellBase { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool ActMsg(CActMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); private: CString _string2; CString _string3; diff --git a/engines/titanic/game/broken_pellerator_froz.cpp b/engines/titanic/game/broken_pellerator_froz.cpp index 4b21ea93d0..690ab76820 100644 --- a/engines/titanic/game/broken_pellerator_froz.cpp +++ b/engines/titanic/game/broken_pellerator_froz.cpp @@ -21,9 +21,17 @@ */ #include "titanic/game/broken_pellerator_froz.h" +#include "titanic/core/view_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CBrokenPelleratorFroz, CBrokenPellBase) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CBrokenPelleratorFroz::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeQuotedLine(_string2, indent); @@ -44,4 +52,99 @@ void CBrokenPelleratorFroz::load(SimpleFile *file) { CBrokenPellBase::load(file); } +bool CBrokenPelleratorFroz::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_v1) { + changeView(_v2 ? _string5 : _string4); + } else { + _v1 = true; + if (_v2) { + playMovie(0, 13, 0); + } else { + playMovie(43, 55, MOVIE_NOTIFY_OBJECT); + } + } + + return true; +} + +bool CBrokenPelleratorFroz::LeaveViewMsg(CLeaveViewMsg *msg) { + CString name = msg->_newView->getNodeViewName(); + + if (name == "Node 3.S" || name == "Node 3.E") { + _v1 = false; + loadFrame(0); + } + + return true; +} + +bool CBrokenPelleratorFroz::ActMsg(CActMsg *msg) { + if (msg->_action == "PlayerGetsHose") { + _v2 = 1; + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 0; + statusMsg.execute("FPickUpHose"); + } else { + _fieldE0 = 0; + bool closeFlag = msg->_action == "Close"; + if (msg->_action == "CloseLeft") { + closeFlag = true; + _fieldE0 = 1; + } + if (msg->_action == "CloseRight") { + closeFlag = true; + _fieldE0 = 2; + } + + if (closeFlag) { + if (_v1) { + _v1 = false; + if (_v2) + playMovie(29, 42, MOVIE_NOTIFY_OBJECT); + else + playMovie(72, 84, MOVIE_NOTIFY_OBJECT); + } else { + switch (_fieldE0) { + case 1: + changeView(_string2); + break; + case 2: + changeView(_string3); + break; + default: + break; + } + + _fieldE0 = 0; + } + } + } + + return true; +} + +bool CBrokenPelleratorFroz::MovieEndMsg(CMovieEndMsg *msg) { + if (msg->_endFrame == 55) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 1; + statusMsg.execute("FPickUpHose"); + } + + if (msg->_endFrame == 84) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 0; + statusMsg.execute("FPickUpHose"); + } + + if (_fieldE0 == 1) { + changeView(_string2); + _fieldE0 = 0; + } else if (_fieldE0 == 2) { + changeView(_string3); + _fieldE0 = 0; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/broken_pellerator_froz.h b/engines/titanic/game/broken_pellerator_froz.h index 1df6d2d0b2..ccdae6ffa8 100644 --- a/engines/titanic/game/broken_pellerator_froz.h +++ b/engines/titanic/game/broken_pellerator_froz.h @@ -28,6 +28,11 @@ namespace Titanic { class CBrokenPelleratorFroz : public CBrokenPellBase { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool ActMsg(CActMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); private: CString _string2; CString _string3; diff --git a/engines/titanic/game/cage.cpp b/engines/titanic/game/cage.cpp index 7fbc052278..bbac384cea 100644 --- a/engines/titanic/game/cage.cpp +++ b/engines/titanic/game/cage.cpp @@ -21,16 +21,25 @@ */ #include "titanic/game/cage.h" +#include "titanic/npcs/parrot.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CCage, CBackground) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(PreEnterViewMsg) + ON_MESSAGE(MouseMoveMsg) +END_MESSAGE_MAP() + int CCage::_v1; -int CCage::_v2; +bool CCage::_open; void CCage::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_v1, indent); - file->writeNumberLine(_v2, indent); + file->writeNumberLine(_open, indent); CBackground::save(file, indent); } @@ -38,9 +47,64 @@ void CCage::save(SimpleFile *file, int indent) { void CCage::load(SimpleFile *file) { file->readNumber(); _v1 = file->readNumber(); - _v2 = file->readNumber(); + _open = file->readNumber(); CBackground::load(file); } +bool CCage::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (CParrot::_v4 && !CParrot::_v5) { + CActMsg actMsg(_open ? "Open" : "Shut"); + actMsg.execute(this); + } + + return true; +} + +bool CCage::ActMsg(CActMsg *msg) { + if (msg->_action == "Shut") { + if (!_open) { + playClip("Shut", MOVIE_STOP_PREVIOUS | MOVIE_NOTIFY_OBJECT); + disableMouse(); + } + } else if (msg->_action == "Open") { + if (_open) { + playClip("Open", MOVIE_STOP_PREVIOUS | MOVIE_NOTIFY_OBJECT); + disableMouse(); + } + } else if (msg->_action == "CoreReplaced") { + CActMsg actMsg("Shut"); + actMsg.execute(this); + } else if (msg->_action == "OpenNow") { + loadFrame(0); + _open = false; + } + + return true; +} + +bool CCage::MovieEndMsg(CMovieEndMsg *msg) { + enableMouse(); + _open = clipExistsByEnd("Shut", msg->_endFrame); + + CStatusChangeMsg statusMsg; + statusMsg._newStatus = _open ? 1 : (CParrot::_v4 == 0 ? 1 : 0); + statusMsg.execute("PerchCoreHolder"); + + return true; +} + +bool CCage::PreEnterViewMsg(CPreEnterViewMsg *msg) { + loadSurface(); + _open = CParrot::_v4 != 0; + loadFrame(_open ? 8 : 0); + + return true; +} + +bool CCage::MouseMoveMsg(CMouseMoveMsg *msg) { + _cursorId = CParrot::_v4 && !CParrot::_v5 ? CURSOR_ACTIVATE : CURSOR_ARROW; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/cage.h b/engines/titanic/game/cage.h index bbce978489..48b1b46ab7 100644 --- a/engines/titanic/game/cage.h +++ b/engines/titanic/game/cage.h @@ -28,9 +28,15 @@ namespace Titanic { class CCage : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool ActMsg(CActMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool PreEnterViewMsg(CPreEnterViewMsg *msg); + bool MouseMoveMsg(CMouseMoveMsg *msg); public: static int _v1; - static int _v2; + static bool _open; public: CLASSDEF; diff --git a/engines/titanic/game/captains_wheel.cpp b/engines/titanic/game/captains_wheel.cpp index c84c9194ce..79908b561d 100644 --- a/engines/titanic/game/captains_wheel.cpp +++ b/engines/titanic/game/captains_wheel.cpp @@ -24,6 +24,15 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CCaptainsWheel, CBackground) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(TurnOff) + ON_MESSAGE(TurnOn) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + CCaptainsWheel::CCaptainsWheel() : CBackground(), _fieldE0(0), _fieldE4(0), _fieldE8(0), _fieldEC(0), _fieldF0(0), _fieldF4(0) { @@ -53,4 +62,148 @@ void CCaptainsWheel::load(SimpleFile *file) { CBackground::load(file); } +bool CCaptainsWheel::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_fieldE0) { + _fieldE0 = false; + CTurnOff offMsg; + offMsg.execute(this); + playMovie(162, 168, 0); + } else { + playMovie(0, 8, MOVIE_NOTIFY_OBJECT); + } + + return true; +} + +bool CCaptainsWheel::LeaveViewMsg(CLeaveViewMsg *msg) { + if (_fieldE0) { + _fieldE0 = false; + CTurnOff offMsg; + offMsg.execute(this); + playMovie(162, 168, MOVIE_GAMESTATE); + } + + return true; +} + +bool CCaptainsWheel::ActMsg(CActMsg *msg) { + if (msg->_action == "Spin") { + if (_fieldE0) { + CTurnOn onMsg; + onMsg.execute("RatchetySound"); + playMovie(8, 142, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + } else if (msg->_action == "Honk") { + if (_fieldE0) { + playMovie(150, 160, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + } else if (msg->_action == "Go") { + if (!_fieldE0) { + inc54(); + _fieldE0 = false; + _fieldE4 = 1; + + CTurnOff offMsg; + offMsg.execute(this); + playMovie(162, 168, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + } else if (msg->_action == "Cruise") { + if (_fieldE0) { + inc54(); + _fieldE0 = false; + _fieldE4 = 2; + + CTurnOff offMsg; + offMsg.execute(this); + playMovie(162, 168, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + } else if (msg->_action == "SetDestin") { + playSound("a#44.wav"); + CSetVolumeMsg volumeMsg; + volumeMsg._volume = 25; + volumeMsg.execute("EngineSounds"); + CTurnOn onMsg; + onMsg.execute("EngineSounds"); + _fieldF0 = 1; + } else if (msg->_action == "ClearDestin") { + _fieldF0 = 0; + } + + return true; +} + +bool CCaptainsWheel::TurnOff(CTurnOff *msg) { + CSignalObject signalMsg; + signalMsg._numValue = 0; + + static const char *const NAMES[8] = { + "WheelSpin", "SeagullHorn", "WheelStopButt", "StopHotSpot", + "WheelCruiseButt", "CruiseHotSpot", "WheelGoButt","GoHotSpot" + }; + for (int idx = 0; idx < 8; ++idx) + signalMsg.execute(NAMES[idx]); + + return true; +} + +bool CCaptainsWheel::TurnOn(CTurnOn *msg) { + CSignalObject signalMsg; + signalMsg._numValue = 1; + signalMsg.execute("WheelSpin"); + signalMsg.execute("SeagullHorn"); + + if (_fieldE0) { + signalMsg.execute("WheelStopButt"); + signalMsg.execute("StopHotSpot"); + } + + if (_fieldEC) { + signalMsg.execute("WheelCruiseButt"); + signalMsg.execute("CruiseHotSpot"); + } + + if (_fieldF0) { + signalMsg.execute("WheelGoButt"); + signalMsg.execute("GoHotSpot"); + } + + return true; +} + +bool CCaptainsWheel::MovieEndMsg(CMovieEndMsg *msg) { + if (msg->_endFrame == 8) { + _fieldE0 = true; + CTurnOn onMsg; + onMsg.execute(this); + } + + if (msg->_endFrame == 142) { + CTurnOff offMsg; + offMsg.execute("RatchetySound"); + } + + if (msg->_endFrame == 168) { + switch (_fieldE4) { + case 1: { + CActMsg actMsg(starFn2() ? "GoEnd" : "Go"); + actMsg.execute("GoSequence"); + break; + } + + case 2: { + CActMsg actMsg("Cruise"); + actMsg.execute("CruiseSequence"); + break; + } + + default: + break; + } + + _fieldE4 = 0; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/captains_wheel.h b/engines/titanic/game/captains_wheel.h index 549dcbe685..3aca45c21f 100644 --- a/engines/titanic/game/captains_wheel.h +++ b/engines/titanic/game/captains_wheel.h @@ -28,6 +28,13 @@ namespace Titanic { class CCaptainsWheel : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool ActMsg(CActMsg *msg); + bool TurnOff(CTurnOff *msg); + bool TurnOn(CTurnOn *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: int _fieldE0; int _fieldE4; diff --git a/engines/titanic/game/cell_point_button.cpp b/engines/titanic/game/cell_point_button.cpp index 18ece09cb0..207dd73543 100644 --- a/engines/titanic/game/cell_point_button.cpp +++ b/engines/titanic/game/cell_point_button.cpp @@ -24,12 +24,17 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CCellPointButton, CBackground) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + CCellPointButton::CCellPointButton() : CBackground() { _fieldE0 = 0; _fieldE4 = 0; _fieldE8 = 0; _fieldEC = 0; - _fieldF0 = 0; + _regionNum = 0; _fieldF4 = 0; _fieldF8 = 0; _fieldFC = 0; @@ -44,7 +49,7 @@ void CCellPointButton::save(SimpleFile *file, int indent) { file->writeNumberLine(_fieldE4, indent); file->writeNumberLine(_fieldE8, indent); file->writeNumberLine(_fieldEC, indent); - file->writeNumberLine(_fieldF0, indent); + file->writeNumberLine(_regionNum, indent); file->writeNumberLine(_fieldF4, indent); file->writeNumberLine(_fieldF8, indent); file->writeNumberLine(_fieldFC, indent); @@ -52,7 +57,7 @@ void CCellPointButton::save(SimpleFile *file, int indent) { file->writeNumberLine(_field104, indent); file->writeNumberLine(_field108, indent); file->writeQuotedLine(_string3, indent); - file->writeNumberLine(_field118, indent); + file->writeNumberLine(_dialNum, indent); CBackground::save(file, indent); } @@ -63,7 +68,7 @@ void CCellPointButton::load(SimpleFile *file) { _fieldE4 = file->readNumber(); _fieldE8 = file->readNumber(); _fieldEC = file->readNumber(); - _fieldF0 = file->readNumber(); + _regionNum = file->readNumber(); _fieldF4 = file->readNumber(); _fieldF8 = file->readNumber(); _fieldFC = file->readNumber(); @@ -71,9 +76,28 @@ void CCellPointButton::load(SimpleFile *file) { _field104 = file->readNumber(); _field108 = file->readNumber(); _string3 = file->readString(); - _field118 = file->readNumber(); + _dialNum = file->readNumber(); CBackground::load(file); } +bool CCellPointButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (getRandomNumber(2) == 0) { + CParrotSpeakMsg speakMsg("Cellpoints", _string3); + speakMsg.execute("PerchedParrot"); + } + + playMovie(0); + _regionNum = _regionNum ? 0 : 1; + playSound("z#425.wav"); + talkSetDialRegion(_string3, _dialNum, _regionNum); + + return true; +} + +bool CCellPointButton::EnterViewMsg(CEnterViewMsg *msg) { + _regionNum = talkGetDialRegion(_string3, _dialNum); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/cell_point_button.h b/engines/titanic/game/cell_point_button.h index 6f1fdc3809..33f58cbb83 100644 --- a/engines/titanic/game/cell_point_button.h +++ b/engines/titanic/game/cell_point_button.h @@ -28,12 +28,15 @@ namespace Titanic { class CCellPointButton : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); public: int _fieldE0; int _fieldE4; int _fieldE8; int _fieldEC; - int _fieldF0; + int _regionNum; int _fieldF4; int _fieldF8; int _fieldFC; @@ -41,7 +44,7 @@ public: int _field104; int _field108; CString _string3; - int _field118; + int _dialNum; public: CLASSDEF; CCellPointButton(); diff --git a/engines/titanic/game/chev_code.cpp b/engines/titanic/game/chev_code.cpp index ebc20578b7..0acdf575f4 100644 --- a/engines/titanic/game/chev_code.cpp +++ b/engines/titanic/game/chev_code.cpp @@ -24,16 +24,262 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CChevCode, CGameObject) + ON_MESSAGE(SetChevLiftBits) + ON_MESSAGE(SetChevClassBits) + ON_MESSAGE(SetChevFloorBits) + ON_MESSAGE(SetChevRoomBits) + ON_MESSAGE(GetChevLiftNum) + ON_MESSAGE(GetChevClassNum) + ON_MESSAGE(GetChevFloorNum) + ON_MESSAGE(GetChevRoomNum) + ON_MESSAGE(CheckChevCode) + ON_MESSAGE(GetChevCodeFromRoomNameMsg) +END_MESSAGE_MAP() + void CChevCode::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_chevCode, indent); CGameObject::save(file, indent); } void CChevCode::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _chevCode = file->readNumber(); CGameObject::load(file); } +bool CChevCode::SetChevLiftBits(CSetChevLiftBits *msg) { + _chevCode &= ~0xC0000; + if (msg->_liftNum > 0 && msg->_liftNum < 5) + _chevCode = ((msg->_liftNum - 1) << 18) | _chevCode; + + return true; +} + +bool CChevCode::SetChevClassBits(CSetChevClassBits *msg) { + _chevCode &= ~0x30000; + if (msg->_classNum > 0 && msg->_classNum < 4) + _chevCode = (msg->_classNum << 16) | msg->_classNum; + + return true; +} + +bool CChevCode::SetChevFloorBits(CSetChevFloorBits *msg) { + int section = (msg->_floorNum + 4) / 10; + int index = (msg->_floorNum + 4) % 10; + _chevCode &= ~0xFF00; + + int val = 0; + switch (section) { + case 0: + val = 144; + break; + case 1: + val = 208; + break; + case 2: + val = 224; + break; + case 3: + val = 240; + break; + default: + break; + } + + _chevCode |= ((index + val) << 8); + return true; +} + +bool CChevCode::SetChevRoomBits(CSetChevRoomBits *msg) { + _chevCode &= ~0xff; + if (msg->_roomNum > 0 && msg->_roomNum < 128) + _chevCode |= msg->_roomNum * 2; + + return true; +} + +bool CChevCode::GetChevLiftNum(CGetChevLiftNum *msg) { + msg->_liftNum = ((_chevCode >> 18) & 3) + 1; + return true; +} + +bool CChevCode::GetChevClassNum(CGetChevClassNum *msg) { + msg->_classNum = (_chevCode >> 16) & 3; + return true; +} + +bool CChevCode::GetChevFloorNum(CGetChevFloorNum *msg) { + int val1 = (_chevCode >> 8) & 0xF; + int val2 = ((_chevCode >> 12) & 0xF) - 9; + + switch (val2) { + case 0: + val2 = 0; + break; + case 4: + val2 = 1; + break; + case 5: + val2 = 2; + break; + case 6: + val2 = 3; + break; + default: + val2 = 4; + break; + } + + msg->_floorNum = (val1 >= 10) ? 0 : val1 * 10; + return true; +} + +bool CChevCode::GetChevRoomNum(CGetChevRoomNum *msg) { + msg->_roomNum = (_chevCode >> 1) & 0x7F; + return true; +} + +bool CChevCode::CheckChevCode(CCheckChevCode *msg) { + CGetChevClassNum getClassMsg; + CGetChevLiftNum getLiftMsg; + CGetChevFloorNum getFloorMsg; + CGetChevRoomNum getRoomMsg; + CString roomName; + int classNum = 0; + uint bits = 0; + + if (_chevCode & 1) { + switch (_chevCode) { + case 0x1D0D9: + roomName = "ParrLobby"; + classNum = 4; + break; + case 0x196D9: + roomName = "FCRestrnt"; + classNum = 4; + break; + case 0x39FCB: + roomName = "Bridge"; + classNum = 4; + break; + case 0x2F86D: + roomName = "CrtrsCham"; + classNum = 4; + break; + case 0x465FB: + roomName = "SculpCham"; + classNum = 4; + break; + case 0x3D94B: + roomName = "BilgeRoom"; + classNum = 4; + break; + case 0x59FAD: + roomName = "BoWell"; + classNum = 4; + break; + case 0x4D6AF: + roomName = "Arboretum"; + classNum = 4; + break; + case 0x8A397: + roomName = "TitRoom"; + classNum = 4; + break; + case 0x79C45: + roomName = "PromDeck"; + classNum = 4; + break; + case 0xB3D97: + roomName = "Bar"; + classNum = 4; + break; + case 0xCC971: + roomName = "EmbLobby"; + classNum = 4; + break; + case 0xF34DB: + roomName = "MusicRoom"; + classNum = 4; + break; + default: + roomName = "BadRoom"; + classNum = 5; + break; + } + + bits = classNum == 5 ? 0x3D94B : _chevCode; + } else { + getFloorMsg.execute(this); + getRoomMsg.execute(this); + getClassMsg.execute(this); + getLiftMsg.execute(this); + if (getFloorMsg._floorNum > 37 || getRoomMsg._roomNum > 18) + classNum = 5; + + if (classNum == 5) { + bits = 0x3D94B; + } else { + switch (getClassMsg._classNum) { + case 1: + if (getFloorMsg._floorNum >= 2 && getFloorMsg._floorNum <= 18 + && getRoomMsg._roomNum >= 1 && getRoomMsg._roomNum <= 3 + && getLiftMsg._liftNum >= 1 && getLiftMsg._liftNum <= 4) + classNum = 1; + else + classNum = 5; + break; + + case 2: + if (getFloorMsg._floorNum >= 19 && getFloorMsg._floorNum <= 26 + && getRoomMsg._roomNum >= 1 && getRoomMsg._roomNum <= 5 + && getLiftMsg._liftNum >= 1 && getLiftMsg._liftNum <= 4) + classNum = 2; + else + classNum = 5; + break; + + case 3: + if (getFloorMsg._floorNum >= 27 && getFloorMsg._floorNum <= 37 + && getRoomMsg._roomNum >= 1 && getRoomMsg._roomNum <= 18 + && (getLiftMsg._liftNum & 1) == 1 + && getLiftMsg._liftNum >= 1 && getLiftMsg._liftNum <= 4) + classNum = 3; + else + classNum = 5; + break; + } + } + } + + msg->_classNum = classNum; + msg->_chevCode = bits; + + // WORKAROUND: Skipped code from original that was for debugging purposes only + return true; +} + +bool CChevCode::GetChevCodeFromRoomNameMsg(CGetChevCodeFromRoomNameMsg *msg) { + static const char *const ROOM_NAMES[13] = { + "ParrotLobby", "sculptureChamber", "Bar", "EmbLobby", "MusicRoom", + "Titania", "BottomOfWell", "Arboretum", "PromenadeDeck", + "FCRestrnt", "CrtrsCham", "BilgeRoom", "Bridge" + }; + static const uint CHEV_CODES[13] = { + 0x1D0D9, 0x465FB, 0xB3D97, 0xCC971, 0xF34DB, 0x8A397, 0x59FAD, + 0x4D6AF, 0x79C45, 0x196D9, 0x2F86D, 0x3D94B, 0x39FCB + }; + + for (int idx = 0; idx < 13; ++idx) { + if (msg->_roomName == ROOM_NAMES[idx]) { + msg->_chevCode = CHEV_CODES[idx]; + break; + } + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/chev_code.h b/engines/titanic/game/chev_code.h index c4552d00a2..4a71b13f9e 100644 --- a/engines/titanic/game/chev_code.h +++ b/engines/titanic/game/chev_code.h @@ -28,11 +28,22 @@ namespace Titanic { class CChevCode : public CGameObject { + DECLARE_MESSAGE_MAP; + bool SetChevLiftBits(CSetChevLiftBits *msg); + bool SetChevClassBits(CSetChevClassBits *msg); + bool SetChevFloorBits(CSetChevFloorBits *msg); + bool SetChevRoomBits(CSetChevRoomBits *msg); + bool GetChevLiftNum(CGetChevLiftNum *msg); + bool GetChevClassNum(CGetChevClassNum *msg); + bool GetChevFloorNum(CGetChevFloorNum *msg); + bool GetChevRoomNum(CGetChevRoomNum *msg); + bool CheckChevCode(CCheckChevCode *msg); + bool GetChevCodeFromRoomNameMsg(CGetChevCodeFromRoomNameMsg *msg); public: - int _value; + int _chevCode; public: CLASSDEF; - CChevCode() : CGameObject(), _value(0) {} + CChevCode() : CGameObject(), _chevCode(0) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/chev_panel.cpp b/engines/titanic/game/chev_panel.cpp index 245968e356..ed730c9d61 100644 --- a/engines/titanic/game/chev_panel.cpp +++ b/engines/titanic/game/chev_panel.cpp @@ -21,25 +21,101 @@ */ #include "titanic/game/chev_panel.h" +#include "titanic/game/chev_code.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CChevPanel, CGameObject) + ON_MESSAGE(MouseDragStartMsg) + ON_MESSAGE(MouseDragMoveMsg) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(SetChevPanelBitMsg) + ON_MESSAGE(MouseDragEndMsg) + ON_MESSAGE(ClearChevPanelBits) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(SetChevPanelButtonsMsg) +END_MESSAGE_MAP() + void CChevPanel::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); - file->writeNumberLine(_fieldC4, indent); + file->writeNumberLine(_startPos.x, indent); + file->writeNumberLine(_startPos.y, indent); + file->writeNumberLine(_chevCode, indent); CGameObject::save(file, indent); } void CChevPanel::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); - _fieldC4 = file->readNumber(); + _startPos.x = file->readNumber(); + _startPos.y = file->readNumber(); + _chevCode = file->readNumber(); CGameObject::load(file); } +bool CChevPanel::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (checkStartDragging(msg)) { + _startPos = Point(msg->_mousePos.x - _bounds.left, + msg->_mousePos.y - _bounds.top); + CChildDragStartMsg dragMsg(_startPos); + dragMsg.execute(this, nullptr, MSGFLAG_SCAN); + } + + return true; +} + +bool CChevPanel::MouseDragMoveMsg(CMouseDragMoveMsg *msg) { + CChildDragMoveMsg dragMsg(_startPos); + dragMsg.execute(this, nullptr, MSGFLAG_SCAN); + + setPosition(msg->_mousePos - _startPos); + return true; +} + +bool CChevPanel::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + CChevCode chevCode; + chevCode._chevCode = _chevCode; + CCheckChevCode checkCode; + checkCode.execute(this); + CClearChevPanelBits panelBits; + panelBits.execute(this, nullptr, MSGFLAG_SCAN); + CSetChevPanelButtonsMsg setMsg; + setMsg._chevCode = checkCode._chevCode; + setMsg.execute(this); + + return true; +} + +bool CChevPanel::SetChevPanelBitMsg(CSetChevPanelBitMsg *msg) { + _chevCode = (_chevCode & ~(1 << msg->_value1)) | (msg->_value2 << msg->_value1); + return true; +} + +bool CChevPanel::MouseDragEndMsg(CMouseDragEndMsg *msg) { + setPosition(msg->_mousePos - _startPos); + return true; +} + +bool CChevPanel::ClearChevPanelBits(CClearChevPanelBits *msg) { + CSetChevPanelButtonsMsg setMsg; + setMsg._chevCode = 0; + setMsg.execute(this); + + return true; +} + +bool CChevPanel::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + return true; +} + +bool CChevPanel::SetChevPanelButtonsMsg(CSetChevPanelButtonsMsg *msg) { + _chevCode = msg->_chevCode; + CSetChevButtonImageMsg setMsg; + setMsg._value2 = 1; + setMsg.execute(this, nullptr, MSGFLAG_SCAN); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/chev_panel.h b/engines/titanic/game/chev_panel.h index 99b5501ac2..bcfb920221 100644 --- a/engines/titanic/game/chev_panel.h +++ b/engines/titanic/game/chev_panel.h @@ -28,13 +28,21 @@ namespace Titanic { class CChevPanel : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseDragStartMsg(CMouseDragStartMsg *msg); + bool MouseDragMoveMsg(CMouseDragMoveMsg *msg); + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool SetChevPanelBitMsg(CSetChevPanelBitMsg *msg); + bool MouseDragEndMsg(CMouseDragEndMsg *msg); + bool ClearChevPanelBits(CClearChevPanelBits *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool SetChevPanelButtonsMsg(CSetChevPanelButtonsMsg *msg); public: - int _fieldBC; - int _fieldC0; - int _fieldC4; + Point _startPos; + int _chevCode; public: CLASSDEF; - CChevPanel() : _fieldBC(0), _fieldC0(0), _fieldC4(0) {} + CChevPanel() : _chevCode(0) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/chicken_cooler.cpp b/engines/titanic/game/chicken_cooler.cpp index 29232e10bf..d10405de38 100644 --- a/engines/titanic/game/chicken_cooler.cpp +++ b/engines/titanic/game/chicken_cooler.cpp @@ -21,9 +21,15 @@ */ #include "titanic/game/chicken_cooler.h" +#include "titanic/carry/chicken.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CChickenCooler, CGameObject) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + void CChickenCooler::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldBC, indent); @@ -41,7 +47,32 @@ void CChickenCooler::load(SimpleFile *file) { } bool CChickenCooler::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CChickenCoolor::handlEvent"); + if (_fieldC0) { + CGameObject *obj = getMailManFirstObject(); + if (obj) { + // WORKAROUND: Redundant loop for chicken in originalhere + } else { + getNextMail(nullptr); + if (CChicken::_v1 > _fieldBC) + CChicken::_v1 = _fieldBC; + } + } + + return true; +} + +bool CChickenCooler::EnterViewMsg(CEnterViewMsg *msg) { + if (!_fieldC0) { + for (CGameObject *obj = getMailManFirstObject(); obj; + obj = getNextMail(obj)) { + if (obj->isEquals("Chicken")) + return true; + } + + if (CChicken::_v1 > _fieldBC) + CChicken::_v1 = _fieldBC; + } + return true; } diff --git a/engines/titanic/game/chicken_cooler.h b/engines/titanic/game/chicken_cooler.h index 724727b905..54dba90686 100644 --- a/engines/titanic/game/chicken_cooler.h +++ b/engines/titanic/game/chicken_cooler.h @@ -29,7 +29,9 @@ namespace Titanic { class CChickenCooler : public CGameObject { + DECLARE_MESSAGE_MAP; bool EnterRoomMsg(CEnterRoomMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); private: int _fieldBC; int _fieldC0; diff --git a/engines/titanic/game/chicken_dispensor.cpp b/engines/titanic/game/chicken_dispensor.cpp index a9bf576765..7fb8fefcda 100644 --- a/engines/titanic/game/chicken_dispensor.cpp +++ b/engines/titanic/game/chicken_dispensor.cpp @@ -21,9 +21,21 @@ */ #include "titanic/game/chicken_dispensor.h" +#include "titanic/core/project_item.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CChickenDispensor, CBackground) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MouseDragStartMsg) + ON_MESSAGE(TurnOff) +END_MESSAGE_MAP() + CChickenDispensor::CChickenDispensor() : CBackground(), _fieldE0(0), _fieldE4(0), _fieldE8(0) { } @@ -45,4 +57,133 @@ void CChickenDispensor::load(SimpleFile *file) { CBackground::load(file); } +bool CChickenDispensor::StatusChangeMsg(CStatusChangeMsg *msg) { + msg->execute("SGTRestLeverAnimation"); + int v1 = _fieldE8 ? 0 : _fieldE4; + CPetControl *pet = getPetControl(); + CGameObject *obj; + + for (obj = pet->getFirstObject(); obj; obj = pet->getNextObject(obj)) { + if (obj->isEquals("Chicken")) { + petDisplayMessage(1, "Chickens are allocated on a one-per-customer basis."); + return true; + } + } + + for (obj = getMailManFirstObject(); obj; obj = getNextMail(obj)) { + if (obj->isEquals("Chicken")) { + petDisplayMessage(1, "Chickens are allocated on a one-per-customer basis."); + return true; + } + } + + if (v1 == 1 || v1 == 2) + _fieldE8 = 1; + + switch (v1) { + case 0: + petDisplayMessage(1, "Only one piece of chicken per passenger. Thank you."); + break; + case 1: + setVisible(true); + if (_fieldE0) { + playMovie(0, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("z#400.wav"); + _fieldE4 = 0; + } else { + playMovie(12, 16, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _fieldE8 = 1; + _fieldE4 = 0; + } + break; + + case 2: + setVisible(true); + if (_fieldE0) { + playMovie(0, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("z#400.wav"); + } else { + playMovie(12, 16, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _fieldE8 = 1; + } + break; + + default: + break; + } + + return true; +} + +bool CChickenDispensor::MovieEndMsg(CMovieEndMsg *msg) { + if (getMovieFrame() == 16) { + playSound("b#50.wav", 50); + CActMsg actMsg("Dispense Chicken"); + actMsg.execute("Chicken"); + } else if (_fieldE8) { + _cursorId = CURSOR_ARROW; + loadFrame(0); + setVisible(false); + if (_fieldE4 == 2) + _fieldE8 = 0; + } else { + loadFrame(0); + setVisible(false); + changeView("SgtLobby.Node 1.N"); + } + + return true; +} + +bool CChickenDispensor::ActMsg(CActMsg *msg) { + if (msg->_action == "EnableObject") + _fieldE0 = 0; + else if (msg->_action == "DisableObject") + _fieldE0 = 1; + else if (msg->_action == "IncreaseQuantity") + _fieldE4 = 2; + else if (msg->_action == "DecreaseQuantity") + _fieldE4 = 1; + + return true; +} + +bool CChickenDispensor::LeaveViewMsg(CLeaveViewMsg *msg) { + return true; +} + +bool CChickenDispensor::EnterViewMsg(CEnterViewMsg *msg) { + playSound("b#51.wav"); + _fieldE8 = 0; + _cursorId = CURSOR_ARROW; + return true; +} + +bool CChickenDispensor::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (getMovieFrame() == 16) { + setVisible(false); + loadFrame(0); + _cursorId = CURSOR_ARROW; + _fieldE8 = 1; + + CVisibleMsg visibleMsg; + visibleMsg.execute("Chicken"); + CPassOnDragStartMsg passMsg(msg->_mousePos, 1); + passMsg.execute("Chicken"); + + msg->_dragItem = getRoot()->findByName("Chicken"); + } + + return true; +} + +bool CChickenDispensor::TurnOff(CTurnOff *msg) { + if (getMovieFrame() == 16) + setVisible(false); + playMovie(16, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _fieldE8 = 0; + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/chicken_dispensor.h b/engines/titanic/game/chicken_dispensor.h index d86b850871..5e3ba47ee8 100644 --- a/engines/titanic/game/chicken_dispensor.h +++ b/engines/titanic/game/chicken_dispensor.h @@ -28,6 +28,14 @@ namespace Titanic { class CChickenDispensor : public CBackground { + DECLARE_MESSAGE_MAP; + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool ActMsg(CActMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); + bool TurnOff(CTurnOff *msg); public: int _fieldE0; int _fieldE4; diff --git a/engines/titanic/game/close_broken_pel.cpp b/engines/titanic/game/close_broken_pel.cpp index d27441ac96..c234590849 100644 --- a/engines/titanic/game/close_broken_pel.cpp +++ b/engines/titanic/game/close_broken_pel.cpp @@ -24,16 +24,26 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CCloseBrokenPel, CBackground) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CCloseBrokenPel::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string3, indent); + file->writeQuotedLine(_target, indent); CBackground::save(file, indent); } void CCloseBrokenPel::load(SimpleFile *file) { file->readNumber(); - _string3 = file->readString(); + _target = file->readString(); CBackground::load(file); } +bool CCloseBrokenPel::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CActMsg actMsg("Close"); + actMsg.execute(_target); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/close_broken_pel.h b/engines/titanic/game/close_broken_pel.h index aacda6c002..4bd66255df 100644 --- a/engines/titanic/game/close_broken_pel.h +++ b/engines/titanic/game/close_broken_pel.h @@ -28,8 +28,10 @@ namespace Titanic { class CCloseBrokenPel : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: - CString _string3; + CString _target; public: CLASSDEF; diff --git a/engines/titanic/game/code_wheel.cpp b/engines/titanic/game/code_wheel.cpp index d8ce48e390..94ee25435a 100644 --- a/engines/titanic/game/code_wheel.cpp +++ b/engines/titanic/game/code_wheel.cpp @@ -24,13 +24,20 @@ namespace Titanic { -CodeWheel::CodeWheel() : CBomb(), _field108(0), _field10C(4), _field110(0) { +BEGIN_MESSAGE_MAP(CodeWheel, CBomb) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + +CodeWheel::CodeWheel() : CBomb(), _field108(0), _state(4), _field110(0) { } void CodeWheel::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_field108, indent); - file->writeNumberLine(_field10C, indent); + file->writeNumberLine(_state, indent); file->writeNumberLine(_field110, indent); CBomb::save(file, indent); @@ -39,10 +46,63 @@ void CodeWheel::save(SimpleFile *file, int indent) { void CodeWheel::load(SimpleFile *file) { file->readNumber(); _field108 = file->readNumber(); - _field10C = file->readNumber(); + _state = file->readNumber(); _field110 = file->readNumber(); CBomb::load(file); } +bool CodeWheel::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + static const int START_FRAMES[15] = { + 0, 5, 10, 15, 19, 24, 28, 33, 38, 42, 47, 52, 57, 61, 66 + }; + static const int END_FRAMES[15] = { + 5, 10, 15, 19, 24, 28, 33, 38, 42, 47, 52, 57, 61, 66, 70 + }; + + int yp = _bounds.top + _bounds.height() / 2; + if (msg->_mousePos.y > yp) { + if (_state == _field108) + _field110 = true; + + _state = (_state + 1) % 15; + playMovie(START_FRAMES[_state], END_FRAMES[_state], + MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + } else { + if (_state == _field108) + _field110 = true; + + playMovie(START_FRAMES[14 - _state] + 68, END_FRAMES[14 - _state] + 68, + MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + + _state = (_state <= 0) ? 14 : _state - 1; + } + + playSound("z#59.wav"); + return true; +} + +bool CodeWheel::EnterViewMsg(CEnterViewMsg *msg) { + loadFrame(24); + _state = 4; + return true; +} + +bool CodeWheel::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + return true; +} + +bool CodeWheel::MovieEndMsg(CMovieEndMsg *msg) { + sleep(200); + CStatusChangeMsg changeMsg; + changeMsg._newStatus = 0; + if (_field110) + changeMsg._newStatus = -1; + if (_field108 == _state) + changeMsg._newStatus = 1; + changeMsg.execute("Bomb"); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/code_wheel.h b/engines/titanic/game/code_wheel.h index 63af97c6fb..e38a45b631 100644 --- a/engines/titanic/game/code_wheel.h +++ b/engines/titanic/game/code_wheel.h @@ -28,9 +28,14 @@ namespace Titanic { class CodeWheel : public CBomb { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); private: int _field108; - int _field10C; + int _state; int _field110; public: CLASSDEF; diff --git a/engines/titanic/game/cookie.cpp b/engines/titanic/game/cookie.cpp index 915bb93b4a..96edca4058 100644 --- a/engines/titanic/game/cookie.cpp +++ b/engines/titanic/game/cookie.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CCookie, CGameObject) + ON_MESSAGE(LeaveNodeMsg) + ON_MESSAGE(FreshenCookieMsg) +END_MESSAGE_MAP() + void CCookie::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value1, indent); @@ -40,4 +45,16 @@ void CCookie::load(SimpleFile *file) { CGameObject::load(file); } +bool CCookie::LeaveNodeMsg(CLeaveNodeMsg *msg) { + if (_value2) + _value1 = 1; + return true; +} + +bool CCookie::FreshenCookieMsg(CFreshenCookieMsg *msg) { + _value1 = msg->_value2; + _value2 = msg->_value1; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/cookie.h b/engines/titanic/game/cookie.h index 7ae04f1144..2018deeb3e 100644 --- a/engines/titanic/game/cookie.h +++ b/engines/titanic/game/cookie.h @@ -28,6 +28,9 @@ namespace Titanic { class CCookie : public CGameObject { + DECLARE_MESSAGE_MAP; + bool LeaveNodeMsg(CLeaveNodeMsg *msg); + bool FreshenCookieMsg(CFreshenCookieMsg *msg); public: int _value1; int _value2; diff --git a/engines/titanic/game/credits.cpp b/engines/titanic/game/credits.cpp index 7078d41a17..d9149f6dd2 100644 --- a/engines/titanic/game/credits.cpp +++ b/engines/titanic/game/credits.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CCredits, CGameObject) + ON_MESSAGE(SignalObject) + ON_MESSAGE(TimerMsg) +END_MESSAGE_MAP() + CCredits::CCredits() : CGameObject(), _fieldBC(-1), _fieldC0(1) { } @@ -43,4 +48,34 @@ void CCredits::load(SimpleFile *file) { CGameObject::load(file); } +bool CCredits::SignalObject(CSignalObject *msg) { + petHide(); + disableMouse(); + addTimer(50); + return true; +} + +bool CCredits::TimerMsg(CTimerMsg *msg) { + stopGlobalSound(true, -1); + setVisible(true); + loadSound("a#16.wav"); + loadSound("a#24.wav"); + + playCutscene(0, 18); + playGlobalSound("a#16.wav", -1, false, false, 0); + playCutscene(19, 642); + playSound("a#24.wav"); + playCutscene(643, 750); + + COpeningCreditsMsg creditsMsg; + creditsMsg.execute("Service Elevator Entity"); + changeView("EmbLobby.Node 6.S"); + + setVisible(false); + petShow(); + enableMouse(); + stopGlobalSound(true, -1); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/credits.h b/engines/titanic/game/credits.h index fa9794b6de..23fd25584d 100644 --- a/engines/titanic/game/credits.h +++ b/engines/titanic/game/credits.h @@ -28,6 +28,9 @@ namespace Titanic { class CCredits : public CGameObject { + DECLARE_MESSAGE_MAP; + bool SignalObject(CSignalObject *msg); + bool TimerMsg(CTimerMsg *msg); public: int _fieldBC, _fieldC0; public: diff --git a/engines/titanic/game/credits_button.cpp b/engines/titanic/game/credits_button.cpp index 90bb1b5ebe..ee8f7bb329 100644 --- a/engines/titanic/game/credits_button.cpp +++ b/engines/titanic/game/credits_button.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CCreditsButton, CBackground) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + CCreditsButton::CCreditsButton() : CBackground(), _fieldE0(1) { } @@ -39,4 +44,19 @@ void CCreditsButton::load(SimpleFile *file) { CBackground::load(file); } +bool CCreditsButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + return true; +} + +bool CCreditsButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_fieldE0) { + playSound("a#20.wav"); + CSignalObject signalMsg; + signalMsg._numValue = 1; + signalMsg.execute("CreditsPlayer"); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/credits_button.h b/engines/titanic/game/credits_button.h index 5e0bf96677..4a53083195 100644 --- a/engines/titanic/game/credits_button.h +++ b/engines/titanic/game/credits_button.h @@ -28,6 +28,9 @@ namespace Titanic { class CCreditsButton : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: int _fieldE0; public: diff --git a/engines/titanic/game/desk_click_responder.cpp b/engines/titanic/game/desk_click_responder.cpp index d9b2cb64b4..0650b3a1f5 100644 --- a/engines/titanic/game/desk_click_responder.cpp +++ b/engines/titanic/game/desk_click_responder.cpp @@ -24,10 +24,15 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CDeskClickResponder, CClickResponder) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(LoadSuccessMsg) +END_MESSAGE_MAP() + void CDeskClickResponder::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldD4, indent); - file->writeNumberLine(_fieldD8, indent); + file->writeNumberLine(_ticks, indent); CClickResponder::save(file, indent); } @@ -35,9 +40,28 @@ void CDeskClickResponder::save(SimpleFile *file, int indent) { void CDeskClickResponder::load(SimpleFile *file) { file->readNumber(); _fieldD4 = file->readNumber(); - _fieldD8 = file->readNumber(); + _ticks = file->readNumber(); CClickResponder::load(file); } +bool CDeskClickResponder::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + _fieldD4 = (_fieldD4 + 1) % 3; + if (_fieldD4) + return CClickResponder::MouseButtonDownMsg(msg); + + uint ticks = getTicksCount(); + if (!_ticks || ticks > (_ticks + 4000)) { + playSound("a#22.wav"); + _ticks = ticks; + } + + return true; +} + +bool CDeskClickResponder::LoadSuccessMsg(CLoadSuccessMsg *msg) { + _ticks = 0; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/desk_click_responder.h b/engines/titanic/game/desk_click_responder.h index 12825ba9de..13cf7f4b87 100644 --- a/engines/titanic/game/desk_click_responder.h +++ b/engines/titanic/game/desk_click_responder.h @@ -28,9 +28,12 @@ namespace Titanic { class CDeskClickResponder : public CClickResponder { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool LoadSuccessMsg(CLoadSuccessMsg *msg); protected: int _fieldD4; - int _fieldD8; + uint _ticks; public: CLASSDEF; diff --git a/engines/titanic/game/doorbot_elevator_handler.cpp b/engines/titanic/game/doorbot_elevator_handler.cpp index 13fc368137..39978e9ed7 100644 --- a/engines/titanic/game/doorbot_elevator_handler.cpp +++ b/engines/titanic/game/doorbot_elevator_handler.cpp @@ -24,24 +24,32 @@ namespace Titanic { -int CDoorbotElevatorHandler::_v1; +BEGIN_MESSAGE_MAP(CDoorbotElevatorHandler, CGameObject) + ON_MESSAGE(EnterNodeMsg) +END_MESSAGE_MAP() void CDoorbotElevatorHandler::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value, indent); - file->writeNumberLine(_v1, indent); + file->writeNumberLine(_called, indent); CGameObject::save(file, indent); } void CDoorbotElevatorHandler::load(SimpleFile *file) { file->readNumber(); _value = file->readNumber(); - _v1 = file->readNumber(); + _called = file->readNumber(); CGameObject::load(file); } bool CDoorbotElevatorHandler::EnterNodeMsg(CEnterNodeMsg *msg) { - warning("CDoorbotElevatorHandler::handleEvent"); + if (!_called) { + CDoorbotNeededInElevatorMsg elevatorMsg; + elevatorMsg._value = 0; + elevatorMsg.execute("Doorbot"); + _called = true; + } + return true; } diff --git a/engines/titanic/game/doorbot_elevator_handler.h b/engines/titanic/game/doorbot_elevator_handler.h index 7b39e727e3..f846273f14 100644 --- a/engines/titanic/game/doorbot_elevator_handler.h +++ b/engines/titanic/game/doorbot_elevator_handler.h @@ -29,9 +29,10 @@ namespace Titanic { class CDoorbotElevatorHandler : public CGameObject { + DECLARE_MESSAGE_MAP; bool EnterNodeMsg(CEnterNodeMsg *msg); private: - static int _v1; + bool _called; int _value; public: CLASSDEF; diff --git a/engines/titanic/game/doorbot_home_handler.cpp b/engines/titanic/game/doorbot_home_handler.cpp index b848308845..92898ca626 100644 --- a/engines/titanic/game/doorbot_home_handler.cpp +++ b/engines/titanic/game/doorbot_home_handler.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CDoorbotHomeHandler, CGameObject) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + CDoorbotHomeHandler::CDoorbotHomeHandler() { } @@ -37,4 +41,10 @@ void CDoorbotHomeHandler::load(SimpleFile *file) { CGameObject::load(file); } +bool CDoorbotHomeHandler::EnterViewMsg(CEnterViewMsg *msg) { + CDoorbotNeededInHomeMsg neededMsg; + neededMsg.execute("Doorbot"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/doorbot_home_handler.h b/engines/titanic/game/doorbot_home_handler.h index 99ba6d37a9..10552f2b87 100644 --- a/engines/titanic/game/doorbot_home_handler.h +++ b/engines/titanic/game/doorbot_home_handler.h @@ -28,6 +28,8 @@ namespace Titanic { class CDoorbotHomeHandler : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); public: CLASSDEF; CDoorbotHomeHandler(); diff --git a/engines/titanic/game/ear_sweet_bowl.cpp b/engines/titanic/game/ear_sweet_bowl.cpp index 0f7069356d..646b95f0b4 100644 --- a/engines/titanic/game/ear_sweet_bowl.cpp +++ b/engines/titanic/game/ear_sweet_bowl.cpp @@ -21,9 +21,16 @@ */ #include "titanic/game/ear_sweet_bowl.h" +#include "titanic/core/room_item.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CEarSweetBowl, CSweetBowl) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(ReplaceBowlAndNutsMsg) +END_MESSAGE_MAP() + void CEarSweetBowl::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSweetBowl::save(file, indent); @@ -34,4 +41,30 @@ void CEarSweetBowl::load(SimpleFile *file) { CSweetBowl::load(file); } +bool CEarSweetBowl::MovieEndMsg(CMovieEndMsg *msg) { + CIsEarBowlPuzzleDone doneMsg; + doneMsg.execute(findRoom()); + + if (!doneMsg._value) { + CPetControl *pet = getPetControl(); + if (pet) + pet->hasRoomFlags(); + + CIsParrotPresentMsg parrotMsg; + parrotMsg.execute(findRoom()); + + if (parrotMsg._value) { + CNutPuzzleMsg nutMsg("Jiggle"); + nutMsg.execute("NutsParrotPlayer"); + } + } + + return true; +} + +bool CEarSweetBowl::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) { + setVisible(false); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/ear_sweet_bowl.h b/engines/titanic/game/ear_sweet_bowl.h index 3f41950e47..1324ed224a 100644 --- a/engines/titanic/game/ear_sweet_bowl.h +++ b/engines/titanic/game/ear_sweet_bowl.h @@ -28,6 +28,9 @@ namespace Titanic { class CEarSweetBowl : public CSweetBowl { + DECLARE_MESSAGE_MAP; + bool MovieEndMsg(CMovieEndMsg *msg); + bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/eject_phonograph_button.cpp b/engines/titanic/game/eject_phonograph_button.cpp index 4657f04126..b2ff441ef8 100644 --- a/engines/titanic/game/eject_phonograph_button.cpp +++ b/engines/titanic/game/eject_phonograph_button.cpp @@ -24,24 +24,57 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CEjectPhonographButton, CBackground) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(CylinderHolderReadyMsg) +END_MESSAGE_MAP() + void CEjectPhonographButton::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); - file->writeNumberLine(_fieldE4, indent); - file->writeQuotedLine(_string3, indent); - file->writeQuotedLine(_string4, indent); + file->writeNumberLine(_ejected, indent); + file->writeNumberLine(_readyFlag, indent); + file->writeQuotedLine(_soundName, indent); + file->writeQuotedLine(_readySoundName, indent); CBackground::save(file, indent); } void CEjectPhonographButton::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); - _fieldE4 = file->readNumber(); - _string3 = file->readString(); - _string4 = file->readString(); + _ejected = file->readNumber(); + _readyFlag = file->readNumber(); + _soundName = file->readString(); + _readySoundName = file->readString(); CBackground::load(file); } +bool CEjectPhonographButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CQueryPhonographState queryMsg; + queryMsg.execute(getParent(), nullptr, MSGFLAG_SCAN); + + if (!_ejected && !queryMsg._value) { + loadFrame(1); + playSound(_soundName); + _readyFlag = true; + + CEjectCylinderMsg ejectMsg; + ejectMsg.execute(getParent(), nullptr, MSGFLAG_SCAN); + _ejected = true; + } + + return true; +} + +bool CEjectPhonographButton::CylinderHolderReadyMsg(CCylinderHolderReadyMsg *msg) { + if (_readyFlag) { + loadFrame(0); + playSound(_readySoundName); + _readyFlag = 0; + } + + _ejected = false; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/eject_phonograph_button.h b/engines/titanic/game/eject_phonograph_button.h index 5f5da8053e..df8e602468 100644 --- a/engines/titanic/game/eject_phonograph_button.h +++ b/engines/titanic/game/eject_phonograph_button.h @@ -28,14 +28,17 @@ namespace Titanic { class CEjectPhonographButton : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool CylinderHolderReadyMsg(CCylinderHolderReadyMsg *msg); public: - int _fieldE0; - int _fieldE4; - CString _string3; - CString _string4; + bool _ejected; + bool _readyFlag; + CString _soundName; + CString _readySoundName; public: CLASSDEF; - CEjectPhonographButton() : CBackground(), _fieldE0(0), _fieldE4(0) {} + CEjectPhonographButton() : CBackground(), _ejected(false), _readyFlag(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/elevator_action_area.cpp b/engines/titanic/game/elevator_action_area.cpp index 1cbff8d64d..d59c9b9e7a 100644 --- a/engines/titanic/game/elevator_action_area.cpp +++ b/engines/titanic/game/elevator_action_area.cpp @@ -21,9 +21,14 @@ */ #include "titanic/game/elevator_action_area.h" +#include "titanic/core/room_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CElevatorActionArea, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CElevatorActionArea::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value, indent); @@ -36,4 +41,10 @@ void CElevatorActionArea::load(SimpleFile *file) { CGameObject::load(file); } +bool CElevatorActionArea::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CServiceElevatorMsg elevMsg(_value); + elevMsg.execute(findRoom()->findByName("Service Elevator Entity")); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/elevator_action_area.h b/engines/titanic/game/elevator_action_area.h index 6c756fb95f..75d3a06d29 100644 --- a/engines/titanic/game/elevator_action_area.h +++ b/engines/titanic/game/elevator_action_area.h @@ -28,6 +28,8 @@ namespace Titanic { class CElevatorActionArea : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: int _value; public: diff --git a/engines/titanic/game/emma_control.cpp b/engines/titanic/game/emma_control.cpp index 814cb44d79..e3ba7cc42c 100644 --- a/engines/titanic/game/emma_control.cpp +++ b/engines/titanic/game/emma_control.cpp @@ -21,27 +21,46 @@ */ #include "titanic/game/emma_control.h" +#include "titanic/core/room_item.h" +#include "titanic/sound/auto_music_player.h" namespace Titanic { -int CEmmaControl::_v1; +BEGIN_MESSAGE_MAP(CEmmaControl, CBackground) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(StatusChangeMsg) +END_MESSAGE_MAP() void CEmmaControl::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_v1, indent); - file->writeQuotedLine(_wavFile1, indent); - file->writeQuotedLine(_wavFile2, indent); + file->writeNumberLine(_flag, indent); + file->writeQuotedLine(_hiddenSoundName, indent); + file->writeQuotedLine(_visibleSoundName, indent); CBackground::save(file, indent); } void CEmmaControl::load(SimpleFile *file) { file->readNumber(); - _v1 = file->readNumber(); - _wavFile1 = file->readString(); - _wavFile2 = file->readString(); + _flag = file->readNumber(); + _hiddenSoundName = file->readString(); + _visibleSoundName = file->readString(); CBackground::load(file); } +bool CEmmaControl::EnterViewMsg(CEnterViewMsg *msg) { + setVisible(_flag); + return true; +} + +bool CEmmaControl::StatusChangeMsg(CStatusChangeMsg *msg) { + _flag = !_flag; + setVisible(_flag); + CChangeMusicMsg changeMsg(_flag ? _visibleSoundName : _hiddenSoundName, 0); + changeMsg.execute(findRoom(), CAutoMusicPlayer::_type, + MSGFLAG_SCAN | MSGFLAG_BREAK_IF_HANDLED | MSGFLAG_CLASS_DEF); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/emma_control.h b/engines/titanic/game/emma_control.h index 721660f61e..e4032ca1a5 100644 --- a/engines/titanic/game/emma_control.h +++ b/engines/titanic/game/emma_control.h @@ -28,13 +28,18 @@ namespace Titanic { class CEmmaControl : public CBackground { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); private: - static int _v1; + bool _flag; - CString _wavFile1, _wavFile2; + CString _hiddenSoundName; + CString _visibleSoundName; public: CLASSDEF; - CEmmaControl() : CBackground(), _wavFile1("b#39.wav"), _wavFile2("b#38.wav") {} + CEmmaControl() : CBackground(), _flag(false), + _hiddenSoundName("b#39.wav"), _visibleSoundName("b#38.wav") {} /** * Save the data for the class to file diff --git a/engines/titanic/game/empty_nut_bowl.cpp b/engines/titanic/game/empty_nut_bowl.cpp index ae9cb35e4d..adee2589f6 100644 --- a/engines/titanic/game/empty_nut_bowl.cpp +++ b/engines/titanic/game/empty_nut_bowl.cpp @@ -21,19 +21,58 @@ */ #include "titanic/game/empty_nut_bowl.h" +#include "titanic/core/room_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CEmptyNutBowl, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(ReplaceBowlAndNutsMsg) + ON_MESSAGE(NutPuzzleMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + void CEmptyNutBowl::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_flag, indent); CGameObject::save(file, indent); } void CEmptyNutBowl::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _flag = file->readNumber(); CGameObject::load(file); } +bool CEmptyNutBowl::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_flag) { + CNutPuzzleMsg nutMsg("UnlockBowl"); + nutMsg.execute(getRoom(), nullptr, MSGFLAG_SCAN); + _flag = false; + } + + return true; +} + +bool CEmptyNutBowl::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) { + setVisible(false); + _flag = true; + return true; +} + +bool CEmptyNutBowl::NutPuzzleMsg(CNutPuzzleMsg *msg) { + if (msg->_value == "NutsGone") + setVisible(true); + return true; +} + +bool CEmptyNutBowl::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (!_flag) { + msg->execute("Ear1"); + setVisible(false); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/empty_nut_bowl.h b/engines/titanic/game/empty_nut_bowl.h index 112e2c6075..d67e75b0aa 100644 --- a/engines/titanic/game/empty_nut_bowl.h +++ b/engines/titanic/game/empty_nut_bowl.h @@ -28,11 +28,16 @@ namespace Titanic { class CEmptyNutBowl : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg); + bool NutPuzzleMsg(CNutPuzzleMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); public: - int _value; + bool _flag; public: CLASSDEF; - CEmptyNutBowl() : CGameObject(), _value(1) {} + CEmptyNutBowl() : CGameObject(), _flag(true) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/end_credit_text.cpp b/engines/titanic/game/end_credit_text.cpp index 6e0c21bbe9..4eee13d3fb 100644 --- a/engines/titanic/game/end_credit_text.cpp +++ b/engines/titanic/game/end_credit_text.cpp @@ -24,16 +24,49 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CEndCreditText, CGameObject) + ON_MESSAGE(ActMsg) + ON_MESSAGE(FrameMsg) + ON_MESSAGE(TimerMsg) +END_MESSAGE_MAP() + void CEndCreditText::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_flag, indent); CGameObject::save(file, indent); } void CEndCreditText::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _flag = file->readNumber(); CGameObject::load(file); } +bool CEndCreditText::ActMsg(CActMsg *msg) { + playGlobalSound("z#41.wav", -1, false, false, 0); + createCredits(); + _flag = true; + return true; +} + +bool CEndCreditText::FrameMsg(CFrameMsg *msg) { + if (_flag) { + if (_credits) { + makeDirty(); + } else { + addTimer(5000); + _flag = false; + } + } + + return true; +} + +bool CEndCreditText::TimerMsg(CTimerMsg *msg) { + setGlobalSoundVolume(-4, 2, -1); + sleep(1000); + quitGame(); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/end_credit_text.h b/engines/titanic/game/end_credit_text.h index 54c6c7ff73..a0e0078837 100644 --- a/engines/titanic/game/end_credit_text.h +++ b/engines/titanic/game/end_credit_text.h @@ -28,11 +28,15 @@ namespace Titanic { class CEndCreditText : public CGameObject { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool FrameMsg(CFrameMsg *msg); + bool TimerMsg(CTimerMsg *msg); private: - int _value; + bool _flag; public: CLASSDEF; - CEndCreditText() : CGameObject(), _value(0) {} + CEndCreditText() : CGameObject(), _flag(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/end_credits.cpp b/engines/titanic/game/end_credits.cpp index 61640b92ad..f613e5a008 100644 --- a/engines/titanic/game/end_credits.cpp +++ b/engines/titanic/game/end_credits.cpp @@ -24,16 +24,41 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CEndCredits, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(FrameMsg) +END_MESSAGE_MAP() + void CEndCredits::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_flag, indent); CGameObject::save(file, indent); } void CEndCredits::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _flag = file->readNumber(); CGameObject::load(file); } +bool CEndCredits::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_flag) { + deinit(); + stopGlobalSound(true, -1); + _flag = false; + } else { + loadSound("z#41.wav"); + playGlobalSound("z#41.wav", -1, false, false, 0); + _flag = true; + } + + return true; +} + +bool CEndCredits::FrameMsg(CFrameMsg *msg) { + if (_flag) + makeDirty(); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/end_credits.h b/engines/titanic/game/end_credits.h index d160bc94e8..257c5b64a7 100644 --- a/engines/titanic/game/end_credits.h +++ b/engines/titanic/game/end_credits.h @@ -28,11 +28,14 @@ namespace Titanic { class CEndCredits : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool FrameMsg(CFrameMsg *msg); public: - int _value; + bool _flag; public: CLASSDEF; - CEndCredits() : CGameObject(), _value(0) {} + CEndCredits() : CGameObject(), _flag(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/end_explode_ship.cpp b/engines/titanic/game/end_explode_ship.cpp index f7ac36503f..10c80f5863 100644 --- a/engines/titanic/game/end_explode_ship.cpp +++ b/engines/titanic/game/end_explode_ship.cpp @@ -24,6 +24,13 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CEndExplodeShip, CGameObject) + ON_MESSAGE(ActMsg) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(MovieFrameMsg) +END_MESSAGE_MAP() + void CEndExplodeShip::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value1, indent); @@ -40,4 +47,61 @@ void CEndExplodeShip::load(SimpleFile *file) { CGameObject::load(file); } +bool CEndExplodeShip::ActMsg(CActMsg *msg) { + if (msg->_action == "Arm Bomb") { + _value1 = 1; + } else if (msg->_action == "Disarm Bomb") { + _value1 = 0; + } else if (msg->_action == "TakeOff") { + loadSound("a#31.wav"); + loadSound("a#14.wav"); + playGlobalSound("a#13.wav", -1, true, true, 0); + addTimer(1, 10212, 0); + } + + return true; +} + +bool CEndExplodeShip::TimerMsg(CTimerMsg *msg) { + if (msg->_actionVal == 1) { + setVisible(true); + playMovie(0, 449, 0); + movieEvent(58); + playMovie(516, _value1 ? 550 : 551, MOVIE_NOTIFY_OBJECT); + } + + if (msg->_actionVal == 3) { + setGlobalSoundVolume(-4, 2, -1); + CActMsg actMsg(_value1 ? "ExplodeCredits" : "Credits"); + actMsg.execute("EndGameCredits"); + } + + if (msg->_action == "Room") { + playMovie(550, 583, MOVIE_NOTIFY_OBJECT); + movieEvent(551); + } + + return true; +} + +bool CEndExplodeShip::MovieEndMsg(CMovieEndMsg *msg) { + if (getMovieFrame() == 550) { + playSound("z#399.wav"); + startAnimTimer("Boom", 4200, 0); + } else { + addTimer(3, 8000, 0); + } + + return true; +} + +bool CEndExplodeShip::MovieFrameMsg(CMovieFrameMsg *msg) { + if (getMovieFrame() == 58) + playSound("a#31.wav", 70); + else if (getMovieFrame() == 551) + playSound("a#14.wav"); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/end_explode_ship.h b/engines/titanic/game/end_explode_ship.h index b8159d3ca7..c48f822af8 100644 --- a/engines/titanic/game/end_explode_ship.h +++ b/engines/titanic/game/end_explode_ship.h @@ -28,6 +28,11 @@ namespace Titanic { class CEndExplodeShip : public CGameObject { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool TimerMsg(CTimerMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool MovieFrameMsg(CMovieFrameMsg *msg); public: int _value1, _value2; public: diff --git a/engines/titanic/game/end_game_credits.cpp b/engines/titanic/game/end_game_credits.cpp index 2d1aa79b1d..4edcef0a17 100644 --- a/engines/titanic/game/end_game_credits.cpp +++ b/engines/titanic/game/end_game_credits.cpp @@ -24,23 +24,64 @@ namespace Titanic { -CEndGameCredits::CEndGameCredits() : CGameObject(), _fieldBC(0) { +BEGIN_MESSAGE_MAP(CEndGameCredits, CGameObject) + ON_MESSAGE(ActMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(TimerMsg) +END_MESSAGE_MAP() + +CEndGameCredits::CEndGameCredits() : CGameObject(), _flag(0), + _frameRange(0, 28) { } void CEndGameCredits::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); - file->writePoint(_pos1, indent); + file->writeNumberLine(_flag, indent); + file->writePoint(_frameRange, indent); CGameObject::save(file, indent); } void CEndGameCredits::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); - _pos1 = file->readPoint(); + _flag = file->readNumber(); + _frameRange = file->readPoint(); CGameObject::load(file); } +bool CEndGameCredits::ActMsg(CActMsg *msg) { + if (!_flag) { + if (msg->_action == "ExplodeCredits") + _frameRange = Point(0, 27); + if (msg->_action == "Credits") + _frameRange = Point(28, 46); + + changeView("TheEnd.Node 4.N"); + } + + return true; +} + +bool CEndGameCredits::EnterViewMsg(CEnterViewMsg *msg) { + playMovie(_frameRange.x, _frameRange.y, MOVIE_NOTIFY_OBJECT); + return true; +} + +bool CEndGameCredits::MovieEndMsg(CMovieEndMsg *msg) { + if (getMovieFrame() == 46) { + CVisibleMsg visibleMsg; + visibleMsg.execute("CreditsBackdrop"); + } + + return true; +} + +bool CEndGameCredits::TimerMsg(CTimerMsg *msg) { + CActMsg actMsg; + actMsg.execute("EndCreditsText"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/end_game_credits.h b/engines/titanic/game/end_game_credits.h index 5962950737..13a92423f6 100644 --- a/engines/titanic/game/end_game_credits.h +++ b/engines/titanic/game/end_game_credits.h @@ -28,9 +28,14 @@ namespace Titanic { class CEndGameCredits : public CGameObject { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool TimerMsg(CTimerMsg *msg); private: - int _fieldBC; - Point _pos1; + bool _flag; + Point _frameRange; public: CLASSDEF; CEndGameCredits(); diff --git a/engines/titanic/game/end_sequence_control.cpp b/engines/titanic/game/end_sequence_control.cpp index d32b3d1713..033a7752a3 100644 --- a/engines/titanic/game/end_sequence_control.cpp +++ b/engines/titanic/game/end_sequence_control.cpp @@ -24,6 +24,13 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CEndSequenceControl, CGameObject) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + void CEndSequenceControl::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -34,8 +41,43 @@ void CEndSequenceControl::load(SimpleFile *file) { CGameObject::load(file); } +bool CEndSequenceControl::TimerMsg(CTimerMsg *msg) { + switch (msg->_actionVal) { + case 1: + changeView("TheEnd.Node 2.N"); + break; + case 2: { + playSound("ShipFlyingMusic.wav"); + CActMsg actMsg("TakeOff"); + actMsg.execute("EndExplodeShip"); + break; + } + + default: + break; + } + + return true; +} + +bool CEndSequenceControl::MovieEndMsg(CMovieEndMsg *msg) { + setGlobalSoundVolume(-4, 2, -1); + changeView("TheEnd.Node 3.N"); + addTimer(2, 1000, 0); + return true; +} + bool CEndSequenceControl::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("TODO: CEndSequenceControl::handleEvent"); + petHide(); + disableMouse(); + addTimer(1, 1000, 0); + playGlobalSound("a#15.wav", -1, true, true, 0); + return true; +} + +bool CEndSequenceControl::EnterViewMsg(CEnterViewMsg *msg) { + movieSetAudioTiming(true); + playMovie(MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); return true; } diff --git a/engines/titanic/game/end_sequence_control.h b/engines/titanic/game/end_sequence_control.h index 35e9a934e1..223f25186d 100644 --- a/engines/titanic/game/end_sequence_control.h +++ b/engines/titanic/game/end_sequence_control.h @@ -29,7 +29,11 @@ namespace Titanic { class CEndSequenceControl : public CGameObject { + DECLARE_MESSAGE_MAP; + bool TimerMsg(CTimerMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/fan.cpp b/engines/titanic/game/fan.cpp index eabaf63568..3fdebbd3ef 100644 --- a/engines/titanic/game/fan.cpp +++ b/engines/titanic/game/fan.cpp @@ -24,9 +24,15 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CFan, CGameObject) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CFan::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value1, indent); + file->writeNumberLine(_state, indent); file->writeNumberLine(_value2, indent); CGameObject::save(file, indent); @@ -34,10 +40,87 @@ void CFan::save(SimpleFile *file, int indent) { void CFan::load(SimpleFile *file) { file->readNumber(); - _value1 = file->readNumber(); + _state = file->readNumber(); _value2 = file->readNumber(); CGameObject::load(file); } +bool CFan::EnterViewMsg(CEnterViewMsg *msg) { + switch (_state) { + case 0: + case 1: + loadFrame(0); + break; + case 2: + playMovie(24, 34, MOVIE_REPEAT); + break; + case 3: + playMovie(63, 65, MOVIE_REPEAT); + break; + } + + return true; +} + +bool CFan::StatusChangeMsg(CStatusChangeMsg *msg) { + if (msg->_newStatus >= -1 && msg->_newStatus < 3) { + int oldState = _state; + _state = msg->_newStatus; + switch (_state) { + case -1: + case 0: + if (oldState == 0) + loadFrame(0); + else if (oldState == 1) + playMovie(24, 34, MOVIE_STOP_PREVIOUS | MOVIE_NOTIFY_OBJECT); + else if (oldState == 2) { + playMovie(66, 79, MOVIE_STOP_PREVIOUS); + playMovie(24, 34, MOVIE_NOTIFY_OBJECT); + } + break; + + case 1: + if (oldState == 0) + playMovie(24, 34, MOVIE_REPEAT | MOVIE_STOP_PREVIOUS); + if (oldState == 2) + playMovie(66, 79, MOVIE_NOTIFY_OBJECT | MOVIE_STOP_PREVIOUS); + break; + + case 2: + if (oldState == 1) + playMovie(48, 62, MOVIE_NOTIFY_OBJECT | MOVIE_STOP_PREVIOUS); + break; + + default: + break; + } + } + + msg->execute("PromDeckFanNoises"); + return true; +} + +bool CFan::MovieEndMsg(CMovieEndMsg *msg) { + switch (_state) { + case -1: + case 0: + loadFrame(0); + break; + + case 1: + playMovie(24, 34, MOVIE_REPEAT); + break; + + case 2: + playMovie(63, 65, MOVIE_REPEAT); + break; + + default: + break; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/fan.h b/engines/titanic/game/fan.h index 2c5a2410a8..9cffce8b68 100644 --- a/engines/titanic/game/fan.h +++ b/engines/titanic/game/fan.h @@ -28,11 +28,15 @@ namespace Titanic { class CFan : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: - int _value1, _value2; + int _state, _value2; public: CLASSDEF; - CFan() : CGameObject(), _value1(0), _value2(0) {} + CFan() : CGameObject(), _state(0), _value2(0) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/fan_control.cpp b/engines/titanic/game/fan_control.cpp index a42e4dd5c1..56a1e49dec 100644 --- a/engines/titanic/game/fan_control.cpp +++ b/engines/titanic/game/fan_control.cpp @@ -24,14 +24,22 @@ namespace Titanic { -CFanControl::CFanControl() : CGameObject(), _fieldBC(0), - _fieldC0(0), _fieldC4(0), _fieldC8(0), _fieldCC(0) { +BEGIN_MESSAGE_MAP(CFanControl, CGameObject) + ON_MESSAGE(ActMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(TimerMsg) +END_MESSAGE_MAP() + +CFanControl::CFanControl() : CGameObject(), _state(-1), + _enabled(false), _fieldC4(0), _fieldC8(false), _fieldCC(0) { } void CFanControl::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); + file->writeNumberLine(_state, indent); + file->writeNumberLine(_enabled, indent); file->writeNumberLine(_fieldC4, indent); file->writeNumberLine(_fieldC8, indent); file->writeNumberLine(_fieldCC, indent); @@ -41,8 +49,8 @@ void CFanControl::save(SimpleFile *file, int indent) { void CFanControl::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); + _state = file->readNumber(); + _enabled = file->readNumber(); _fieldC4 = file->readNumber(); _fieldC8 = file->readNumber(); _fieldCC = file->readNumber(); @@ -50,4 +58,125 @@ void CFanControl::load(SimpleFile *file) { CGameObject::load(file); } +bool CFanControl::ActMsg(CActMsg *msg) { + if (msg->_action == "EnableObject") + _enabled = true; + else if (msg->_action == "DisableObject") + _enabled = false; + else if (msg->_action == "StarlingsDead") { + _fieldC4 = 0; + dec54(); + _fieldCC = 0; + } + + return true; +} + +bool CFanControl::StatusChangeMsg(CStatusChangeMsg *msg) { + if (!_fieldCC) { + playSound("z#42.wav"); + if (_enabled) { + switch (msg->_newStatus) { + case 1: + _fieldC8 = !_fieldC8; + if (_fieldC8) { + playMovie(6, 8, 0); + _state = 0; + } else { + switch (_state) { + case 0: + playMovie(4, 6, 0); + _state = -1; + break; + case 1: + playMovie(0, 6, 0); + break; + case 2: + playMovie(18, 24, 0); + playMovie(0, 6, 0); + break; + default: + break; + } + + _state = -1; + } + break; + + case 2: + if (_fieldC8) { + _state = (_state + 1) % 4; + switch (_state) { + case 0: + playMovie(18, 24, 0); + playMovie(0, 4, 0); + break; + case 1: + playMovie(8, 12, 0); + break; + case 2: + if (_fieldC4) { + inc54(); + _fieldCC = 1; + playMovie(12, 18, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } else { + playMovie(12, 18, 0); + } + break; + default: + break; + } + } + break; + + default: + break; + } + + CStatusChangeMsg statusMsg; + statusMsg._newStatus = _state; + statusMsg.execute("RightFan"); + } else { + petDisplayMessage(1, "Unfortunately this fan controller has blown a fuse."); + } + } + + return true; +} + +bool CFanControl::EnterViewMsg(CEnterViewMsg *msg) { + switch (_state) { + case 0: + loadFrame(6); + break; + case 1: + loadFrame(4); + break; + case 2: + loadFrame(0); + break; + case 3: + loadFrame(18); + break; + default: + break; + } + + return true; +} + +bool CFanControl::MovieEndMsg(CMovieEndMsg *msg) { + addTimer(2000); + return true; +} + +bool CFanControl::TimerMsg(CTimerMsg *msg) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 1; + statusMsg.execute("StarlingPuret"); + changeView("PromenadeDeck.Node 3.S"); + changeView("PromenadeDeck.Node 3.E"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/fan_control.h b/engines/titanic/game/fan_control.h index 4d89adb311..1f7402db12 100644 --- a/engines/titanic/game/fan_control.h +++ b/engines/titanic/game/fan_control.h @@ -28,11 +28,17 @@ namespace Titanic { class CFanControl : public CGameObject { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool TimerMsg(CTimerMsg *msg); public: - int _fieldBC; - int _fieldC0; + int _state; + bool _enabled; int _fieldC4; - int _fieldC8; + bool _fieldC8; int _fieldCC; public: CLASSDEF; diff --git a/engines/titanic/game/fan_decrease.cpp b/engines/titanic/game/fan_decrease.cpp index 2049b1ebc9..b0b9cc585f 100644 --- a/engines/titanic/game/fan_decrease.cpp +++ b/engines/titanic/game/fan_decrease.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CFanDecrease, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CFanDecrease::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -34,4 +38,11 @@ void CFanDecrease::load(SimpleFile *file) { CGameObject::load(file); } +bool CFanDecrease::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 2; + statusMsg.execute("FanController"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/fan_decrease.h b/engines/titanic/game/fan_decrease.h index 765c7d1560..2e90d09a3f 100644 --- a/engines/titanic/game/fan_decrease.h +++ b/engines/titanic/game/fan_decrease.h @@ -28,6 +28,8 @@ namespace Titanic { class CFanDecrease : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/fan_increase.cpp b/engines/titanic/game/fan_increase.cpp index aa23dd9275..abd2e019d3 100644 --- a/engines/titanic/game/fan_increase.cpp +++ b/engines/titanic/game/fan_increase.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CFanIncrease, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CFanIncrease::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -34,4 +38,11 @@ void CFanIncrease::load(SimpleFile *file) { CGameObject::load(file); } +bool CFanIncrease::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 1; + statusMsg.execute("FanController"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/fan_increase.h b/engines/titanic/game/fan_increase.h index 08ec1322cd..7ed74e1847 100644 --- a/engines/titanic/game/fan_increase.h +++ b/engines/titanic/game/fan_increase.h @@ -28,6 +28,8 @@ namespace Titanic { class CFanIncrease : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/fan_noises.cpp b/engines/titanic/game/fan_noises.cpp index 14177ab64e..c6e6d203dd 100644 --- a/engines/titanic/game/fan_noises.cpp +++ b/engines/titanic/game/fan_noises.cpp @@ -21,42 +21,185 @@ */ #include "titanic/game/fan_noises.h" +#include "titanic/core/room_item.h" namespace Titanic { -CFanNoises::CFanNoises() : CGameObject(), _fieldBC(-1), - _fieldC0(0), _fieldC4(70), _fieldC8(-1), _fieldCC(0), - _fieldD0(0), _fieldD4(-1) { +BEGIN_MESSAGE_MAP(CFanNoises, CGameObject) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(LeaveRoomMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(SetVolumeMsg) + ON_MESSAGE(LoadSuccessMsg) +END_MESSAGE_MAP() + +CFanNoises::CFanNoises() : CGameObject(), _state(-1), + _soundHandle(0), _soundPercent(70), _soundV3(-1), _soundSeconds(0), + _stopSeconds(0), _startFlag(true) { } void CFanNoises::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); - file->writeNumberLine(_fieldC4, indent); - file->writeNumberLine(_fieldC8, indent); - file->writeNumberLine(_fieldCC, indent); - file->writeNumberLine(_fieldD0, indent); - file->writeNumberLine(_fieldD4, indent); + file->writeNumberLine(_state, indent); + file->writeNumberLine(_soundHandle, indent); + file->writeNumberLine(_soundPercent, indent); + file->writeNumberLine(_soundV3, indent); + file->writeNumberLine(_soundSeconds, indent); + file->writeNumberLine(_stopSeconds, indent); + file->writeNumberLine(_startFlag, indent); CGameObject::save(file, indent); } void CFanNoises::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); - _fieldC4 = file->readNumber(); - _fieldC8 = file->readNumber(); - _fieldCC = file->readNumber(); - _fieldD0 = file->readNumber(); - _fieldD4 = file->readNumber(); + _state = file->readNumber(); + _soundHandle = file->readNumber(); + _soundPercent = file->readNumber(); + _soundV3 = file->readNumber(); + _soundSeconds = file->readNumber(); + _stopSeconds = file->readNumber(); + _startFlag = file->readNumber(); CGameObject::load(file); } bool CFanNoises::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CFanNoises::handleEvent"); + if (getParent() == msg->_newRoom) { + if (_soundHandle != -1) { + if (isSoundActive(_soundHandle)) + stopSound(_soundHandle, _stopSeconds); + _soundHandle = -1; + _startFlag = false; + } + + switch (_state) { + case 1: + _soundHandle = playSound("b#60.wav", 0, _soundV3, true); + setSoundVolume(_soundHandle, _soundPercent, _soundSeconds); + _startFlag = true; + break; + case 2: + _soundHandle = playSound("b#58.wav", 0, _soundV3, true); + setSoundVolume(_soundHandle, _soundPercent, _soundSeconds); + _startFlag = true; + break; + default: + break; + } + } + + return true; +} + +bool CFanNoises::LeaveRoomMsg(CLeaveRoomMsg *msg) { + if (getParent() == msg->_oldRoom && _soundHandle != -1) { + if (isSoundActive(_soundHandle)) + stopSound(_soundHandle, _stopSeconds); + + _soundHandle = -1; + _startFlag = false; + } + + return true; +} + +bool CFanNoises::StatusChangeMsg(CStatusChangeMsg *msg) { + if (msg->_newStatus >= -1 && msg->_newStatus <= 2) { + int oldState = _state; + _state = msg->_newStatus; + + switch (msg->_newStatus) { + case -1: + case 0: + if (_soundHandle != -1) { + if (isSoundActive(_soundHandle)) + stopSound(_soundHandle, 1); + _soundHandle = -1; + _startFlag = false; + } + + switch (oldState) { + case 1: + case 2: + playSound("b#59.wav", _soundPercent, _soundV3); + break; + default: + break; + } + break; + + case 1: + if (_soundHandle != -1) { + if (isSoundActive(_soundHandle)) + stopSound(_soundHandle, 1); + _soundHandle = -1; + _startFlag = false; + } + + switch (oldState) { + case 1: + case 2: + _soundHandle = playSound("b#60.wav", _soundPercent, _soundV3); + break; + default: + break; + } + break; + + case 2: + if (_soundHandle != -1) { + if (isSoundActive(_soundHandle)) + stopSound(_soundHandle, 1); + _soundHandle = -1; + _startFlag = false; + } + + if (oldState == 1) { + _soundHandle = playSound("b#58.wav", _soundPercent, _soundV3); + } + break; + + default: + break; + } + } + + return true; +} + +bool CFanNoises::SetVolumeMsg(CSetVolumeMsg *msg) { + _soundPercent = msg->_volume; + + if (_soundHandle != -1 && isSoundActive(_soundHandle)) + setSoundVolume(_soundHandle, _soundPercent, msg->_secondsTransition); + + return true; +} + +bool CFanNoises::LoadSuccessMsg(CLoadSuccessMsg *msg) { + if (_startFlag) { + _startFlag = false; + _soundHandle = -1; + + switch (_state) { + case 1: + playSound("b#60.wav", 0, _soundV3, true); + setSoundVolume(_soundHandle, _soundPercent, _soundSeconds); + _startFlag = true; + break; + + case 2: + playSound("b#58.wav", 0, _soundV3, true); + setSoundVolume(_soundHandle, _soundPercent, _soundSeconds); + _startFlag = true; + break; + + default: + break; + } + } + return true; } diff --git a/engines/titanic/game/fan_noises.h b/engines/titanic/game/fan_noises.h index bb2c35989d..56c80c0764 100644 --- a/engines/titanic/game/fan_noises.h +++ b/engines/titanic/game/fan_noises.h @@ -29,15 +29,20 @@ namespace Titanic { class CFanNoises : public CGameObject { + DECLARE_MESSAGE_MAP; bool EnterRoomMsg(CEnterRoomMsg *msg); + bool LeaveRoomMsg(CLeaveRoomMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool SetVolumeMsg(CSetVolumeMsg *msg); + bool LoadSuccessMsg(CLoadSuccessMsg *msg); private: - int _fieldBC; - int _fieldC0; - int _fieldC4; - int _fieldC8; - int _fieldCC; - int _fieldD0; - int _fieldD4; + int _state; + int _soundHandle; + int _soundPercent; + int _soundV3; + int _soundSeconds; + int _stopSeconds; + bool _startFlag; public: CLASSDEF; CFanNoises(); diff --git a/engines/titanic/game/floor_indicator.cpp b/engines/titanic/game/floor_indicator.cpp index 360232c38c..3afb03c59d 100644 --- a/engines/titanic/game/floor_indicator.cpp +++ b/engines/titanic/game/floor_indicator.cpp @@ -21,9 +21,14 @@ */ #include "titanic/game/floor_indicator.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CFloorIndicator, CGameObject) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + void CFloorIndicator::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -34,4 +39,10 @@ void CFloorIndicator::load(SimpleFile *file) { CGameObject::load(file); } +bool CFloorIndicator::EnterViewMsg(CEnterViewMsg *msg) { + int floorNum = MAX(1, getPetControl()->getRoomsFloorNum()); + loadFrame(floorNum - 1); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/floor_indicator.h b/engines/titanic/game/floor_indicator.h index 066209e52e..38a1757ad0 100644 --- a/engines/titanic/game/floor_indicator.h +++ b/engines/titanic/game/floor_indicator.h @@ -28,6 +28,8 @@ namespace Titanic { class CFloorIndicator : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/games_console.cpp b/engines/titanic/game/games_console.cpp index b7500f9dd9..40311f70ee 100644 --- a/engines/titanic/game/games_console.cpp +++ b/engines/titanic/game/games_console.cpp @@ -24,16 +24,42 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CGamesConsole, CBackground) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + void CGamesConsole::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); + file->writeNumberLine(_active, indent); CBackground::save(file, indent); } void CGamesConsole::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); + _active = file->readNumber(); CBackground::load(file); } +bool CGamesConsole::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_active) { + playMovie(23, 44, 0); + _active = false; + } else { + playMovie(0, 23, 0); + _active = true; + } + + return true; +} + +bool CGamesConsole::LeaveViewMsg(CLeaveViewMsg *msg) { + if (_active) { + _active = false; + playMovie(23, 44, MOVIE_GAMESTATE); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/games_console.h b/engines/titanic/game/games_console.h index 2b1da70e96..f849fd08cc 100644 --- a/engines/titanic/game/games_console.h +++ b/engines/titanic/game/games_console.h @@ -28,11 +28,14 @@ namespace Titanic { class CGamesConsole : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); public: - int _fieldE0; + bool _active; public: CLASSDEF; - CGamesConsole() : CBackground(), _fieldE0(0) {} + CGamesConsole() : CBackground(), _active(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/get_lift_eye2.cpp b/engines/titanic/game/get_lift_eye2.cpp index 7747f7b0c2..914f306f0e 100644 --- a/engines/titanic/game/get_lift_eye2.cpp +++ b/engines/titanic/game/get_lift_eye2.cpp @@ -21,34 +21,80 @@ */ #include "titanic/game/get_lift_eye2.h" +#include "titanic/game/transport/lift.h" +#include "titanic/core/project_item.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { -CString *CGetLiftEye2::_v1; +BEGIN_MESSAGE_MAP(CGetLiftEye2, CGameObject) + ON_MESSAGE(ActMsg) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(VisibleMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + +CString *CGetLiftEye2::_destObject; void CGetLiftEye2::init() { - _v1 = new CString(); + _destObject = new CString(); } void CGetLiftEye2::deinit() { - delete _v1; + delete _destObject; } void CGetLiftEye2::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(*_v1, indent); + file->writeQuotedLine(*_destObject, indent); CGameObject::save(file, indent); } void CGetLiftEye2::load(SimpleFile *file) { file->readNumber(); - *_v1 = file->readString(); + *_destObject = file->readString(); CGameObject::load(file); } +bool CGetLiftEye2::ActMsg(CActMsg *msg) { + *_destObject = msg->_action; + setVisible(true); + return true; +} + bool CGetLiftEye2::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CGetLiftEye2::handleEvent"); + CPetControl *pet = getPetControl(); + if (pet->getRoomsElevatorNum() == 4 && CLift::_v1 == 1 && !CLift::_v6) { + _cursorId = CURSOR_HAND; + setVisible(true); + } else { + _cursorId = CURSOR_ARROW; + setVisible(false); + } + return true; } +bool CGetLiftEye2::VisibleMsg(CVisibleMsg *msg) { + setVisible(true); + _cursorId = CURSOR_HAND; + return true; +} + +bool CGetLiftEye2::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (checkPoint(msg->_mousePos, false, true)) { + _cursorId = CURSOR_ARROW; + setVisible(false); + CActMsg actMsg("EyeNotHead"); + actMsg.execute(*_destObject); + CPassOnDragStartMsg dragMsg(msg->_mousePos, 1); + dragMsg.execute(*_destObject); + + msg->_dragItem = getRoot()->findByName(*_destObject); + return true; + } else { + return false; + } +} + } // End of namespace Titanic diff --git a/engines/titanic/game/get_lift_eye2.h b/engines/titanic/game/get_lift_eye2.h index 496784a3c1..c0dd49206e 100644 --- a/engines/titanic/game/get_lift_eye2.h +++ b/engines/titanic/game/get_lift_eye2.h @@ -28,9 +28,13 @@ namespace Titanic { class CGetLiftEye2 : public CGameObject { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); + bool VisibleMsg(CVisibleMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); public: - static CString *_v1; + static CString *_destObject; public: CLASSDEF; static void init(); diff --git a/engines/titanic/game/glass_smasher.cpp b/engines/titanic/game/glass_smasher.cpp index 8c33124a47..2123f2dfd0 100644 --- a/engines/titanic/game/glass_smasher.cpp +++ b/engines/titanic/game/glass_smasher.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CGlassSmasher, CGameObject) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CGlassSmasher::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -34,4 +39,18 @@ void CGlassSmasher::load(SimpleFile *file) { CGameObject::load(file); } +bool CGlassSmasher::StatusChangeMsg(CStatusChangeMsg *msg) { + setVisible(true); + playSound("b#40.wav"); + playMovie(MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + return true; +} + +bool CGlassSmasher::MovieEndMsg(CMovieEndMsg *msg) { + setVisible(false); + CVisibleMsg visibleMsg(true); + visibleMsg.execute("LongStickDispenser"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/glass_smasher.h b/engines/titanic/game/glass_smasher.h index 7e38f4e36b..e1eef6f87a 100644 --- a/engines/titanic/game/glass_smasher.h +++ b/engines/titanic/game/glass_smasher.h @@ -28,6 +28,9 @@ namespace Titanic { class CGlassSmasher : public CGameObject { + DECLARE_MESSAGE_MAP; + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/gondolier/gondolier_base.cpp b/engines/titanic/game/gondolier/gondolier_base.cpp index 1f7339cf38..f3dc31c9f5 100644 --- a/engines/titanic/game/gondolier/gondolier_base.cpp +++ b/engines/titanic/game/gondolier/gondolier_base.cpp @@ -24,12 +24,16 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CGondolierBase, CGameObject) + ON_MESSAGE(PuzzleSolvedMsg) +END_MESSAGE_MAP() + int CGondolierBase::_v1; -int CGondolierBase::_v2; -int CGondolierBase::_v3; +bool CGondolierBase::_puzzleSolved; +int CGondolierBase::_volume1; int CGondolierBase::_v4; int CGondolierBase::_v5; -int CGondolierBase::_v6; +int CGondolierBase::_volume2; int CGondolierBase::_v7; int CGondolierBase::_v8; int CGondolierBase::_v9; @@ -38,11 +42,11 @@ int CGondolierBase::_v10; void CGondolierBase::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_v1, indent); - file->writeNumberLine(_v2, indent); - file->writeNumberLine(_v3, indent); + file->writeNumberLine(_puzzleSolved, indent); + file->writeNumberLine(_volume1, indent); file->writeNumberLine(_v4, indent); file->writeNumberLine(_v5, indent); - file->writeNumberLine(_v6, indent); + file->writeNumberLine(_volume2, indent); file->writeNumberLine(_v7, indent); file->writeNumberLine(_v8, indent); file->writeNumberLine(_v9, indent); @@ -54,11 +58,11 @@ void CGondolierBase::save(SimpleFile *file, int indent) { void CGondolierBase::load(SimpleFile *file) { file->readNumber(); _v1 = file->readNumber(); - _v2 = file->readNumber(); - _v3 = file->readNumber(); + _puzzleSolved = file->readNumber(); + _volume1 = file->readNumber(); _v4 = file->readNumber(); _v5 = file->readNumber(); - _v6 = file->readNumber(); + _volume2 = file->readNumber(); _v7 = file->readNumber(); _v8 = file->readNumber(); _v9 = file->readNumber(); @@ -67,4 +71,9 @@ void CGondolierBase::load(SimpleFile *file) { CGameObject::load(file); } +bool CGondolierBase::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) { + _puzzleSolved = true; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/gondolier/gondolier_base.h b/engines/titanic/game/gondolier/gondolier_base.h index a7ea2d4931..06d77ba85f 100644 --- a/engines/titanic/game/gondolier/gondolier_base.h +++ b/engines/titanic/game/gondolier/gondolier_base.h @@ -28,13 +28,15 @@ namespace Titanic { class CGondolierBase : public CGameObject { -private: + DECLARE_MESSAGE_MAP; + bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg); +protected: static int _v1; - static int _v2; - static int _v3; + static bool _puzzleSolved; + static int _volume1; static int _v4; static int _v5; - static int _v6; + static int _volume2; static int _v7; static int _v8; static int _v9; diff --git a/engines/titanic/game/gondolier/gondolier_chest.cpp b/engines/titanic/game/gondolier/gondolier_chest.cpp index b3e7217502..cf6656732b 100644 --- a/engines/titanic/game/gondolier/gondolier_chest.cpp +++ b/engines/titanic/game/gondolier/gondolier_chest.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CGondolierChest, CGondolierBase) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + void CGondolierChest::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGondolierBase::save(file, indent); @@ -34,4 +40,27 @@ void CGondolierChest::load(SimpleFile *file) { CGondolierBase::load(file); } +bool CGondolierChest::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!_v1) + playMovie(0, 14, MOVIE_NOTIFY_OBJECT); + else if (msg->_mousePos.y < 330) + return false; + else if (!_v8 && !_v5) { + playMovie(14, 29, 0); + _v1 = 0; + } + + return true; +} + +bool CGondolierChest::MovieEndMsg(CMovieEndMsg *msg) { + if (msg->_endFrame == 14) + _v1 = 1; + return true; +} + +bool CGondolierChest::MouseDragStartMsg(CMouseDragStartMsg *msg) { + return false; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/gondolier/gondolier_chest.h b/engines/titanic/game/gondolier/gondolier_chest.h index d796917371..8f069241a0 100644 --- a/engines/titanic/game/gondolier/gondolier_chest.h +++ b/engines/titanic/game/gondolier/gondolier_chest.h @@ -28,6 +28,10 @@ namespace Titanic { class CGondolierChest : public CGondolierBase { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/gondolier/gondolier_face.cpp b/engines/titanic/game/gondolier/gondolier_face.cpp index bdab8491ed..d7bcfa3561 100644 --- a/engines/titanic/game/gondolier/gondolier_face.cpp +++ b/engines/titanic/game/gondolier/gondolier_face.cpp @@ -24,16 +24,35 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CGondolierFace, CGondolierBase) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(StatusChangeMsg) +END_MESSAGE_MAP() + void CGondolierFace::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); + file->writeNumberLine(_flag, indent); CGondolierBase::save(file, indent); } void CGondolierFace::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); + _flag = file->readNumber(); CGondolierBase::load(file); } +bool CGondolierFace::EnterViewMsg(CEnterViewMsg *msg) { + if (_flag) + playMovie(MOVIE_REPEAT); + else + setVisible(false); + return true; +} + +bool CGondolierFace::StatusChangeMsg(CStatusChangeMsg *msg) { + _flag = msg->_newStatus != 1; + setVisible(_flag); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/gondolier/gondolier_face.h b/engines/titanic/game/gondolier/gondolier_face.h index 71bdd6d444..b441204d3f 100644 --- a/engines/titanic/game/gondolier/gondolier_face.h +++ b/engines/titanic/game/gondolier/gondolier_face.h @@ -28,11 +28,14 @@ namespace Titanic { class CGondolierFace : public CGondolierBase { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); private: - int _fieldBC; + bool _flag; public: CLASSDEF; - CGondolierFace() : CGondolierBase(), _fieldBC(0) {} + CGondolierFace() : CGondolierBase(), _flag(true) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/gondolier/gondolier_mixer.cpp b/engines/titanic/game/gondolier/gondolier_mixer.cpp index 9b7b72c11b..26deda8bca 100644 --- a/engines/titanic/game/gondolier/gondolier_mixer.cpp +++ b/engines/titanic/game/gondolier/gondolier_mixer.cpp @@ -25,20 +25,30 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CGondolierMixer, CGondolierBase) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(LeaveRoomMsg) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(SetVolumeMsg) + ON_MESSAGE(SignalObject) + ON_MESSAGE(LoadSuccessMsg) +END_MESSAGE_MAP() + CGondolierMixer::CGondolierMixer() : CGondolierBase(), - _string1("c#0.wav"), _string2("c#1.wav"), - _fieldBC(-1), _fieldC0(-1), _fieldC4(0), _fieldC8(0), - _fieldE4(0) { + _soundName1("c#0.wav"), _soundName2("c#1.wav"), + _soundHandle1(-1), _soundHandle2(-1), _fieldC4(0), _fieldC8(0), + _fieldE4(false) { } void CGondolierMixer::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); + file->writeNumberLine(_soundHandle1, indent); + file->writeNumberLine(_soundHandle2, indent); file->writeNumberLine(_fieldC4, indent); file->writeNumberLine(_fieldC8, indent); - file->writeQuotedLine(_string1, indent); - file->writeQuotedLine(_string2, indent); + file->writeQuotedLine(_soundName1, indent); + file->writeQuotedLine(_soundName2, indent); file->writeNumberLine(_fieldE4, indent); CGondolierBase::save(file, indent); @@ -46,12 +56,12 @@ void CGondolierMixer::save(SimpleFile *file, int indent) { void CGondolierMixer::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); + _soundHandle1 = file->readNumber(); + _soundHandle2 = file->readNumber(); _fieldC4 = file->readNumber(); _fieldC8 = file->readNumber(); - _string1 = file->readString(); - _string2 = file->readString(); + _soundName1 = file->readString(); + _soundName2 = file->readString(); _fieldE4 = file->readNumber(); CGondolierBase::load(file); @@ -59,10 +69,126 @@ void CGondolierMixer::load(SimpleFile *file) { bool CGondolierMixer::EnterRoomMsg(CEnterRoomMsg *msg) { CRoomItem *parentRoom = dynamic_cast<CRoomItem *>(getParent()); - if (parentRoom == msg->_newRoom) - msg->execute(parentRoom); + if (parentRoom == msg->_newRoom) { + CTurnOn onMsg; + onMsg.execute(this); + } return true; } +bool CGondolierMixer::LeaveRoomMsg(CLeaveRoomMsg *msg) { + CRoomItem *parentRoom = dynamic_cast<CRoomItem *>(getParent()); + if (parentRoom == msg->_oldRoom) { + CTurnOff offMsg; + offMsg.execute(this); + } + + return true; +} + +bool CGondolierMixer::TurnOn(CTurnOn *msg) { + if (!_puzzleSolved) { + if (_soundHandle1 == -1) { + _soundHandle1 = playSound(_soundName1, _volume1 * _v4 / 10, 0, true); + _fieldE4 = true; + } + + if (_soundHandle2 == -1) { + _soundHandle2 = playSound(_soundName1, _volume2 * _v7 / 10, 0, true); + _fieldE4 = true; + } + } + + return true; +} + +bool CGondolierMixer::TurnOff(CTurnOff *msg) { + if (_soundHandle1 != -1) { + if (isSoundActive(_soundHandle1)) + stopSound(_soundHandle1, 2); + + _soundHandle1 = -1; + _fieldE4 = false; + } + + if (_soundHandle2 != -1) { + if (isSoundActive(_soundHandle2)) + stopSound(_soundHandle2, 2); + + _soundHandle2 = -1; + _fieldE4 = false; + } + + return true; +} + +bool CGondolierMixer::SetVolumeMsg(CSetVolumeMsg *msg) { + if (!_puzzleSolved) { + _volume1 = _volume2 = msg->_volume; + + if (_soundHandle1 != -1 && isSoundActive(_soundHandle1)) + setSoundVolume(_soundHandle1, msg->_volume * _v4 / 10, 2); + if (_soundHandle2 != -1 && isSoundActive(_soundHandle2)) + setSoundVolume(_soundHandle2, msg->_volume * _v7 / 10, 2); + } + + return true; +} + +bool CGondolierMixer::SignalObject(CSignalObject *msg) { + if (!_puzzleSolved) { + if (msg->_strValue == "Fly") { + _v4 = CLIP(msg->_numValue, 0, 10); + + if (!_v8) { + _v7 = 10 - _v4; + CStatusChangeMsg statusMsg; + statusMsg._newStatus = _v7; + statusMsg.execute("GondolierRightLever"); + } + } + + if (msg->_strValue == "Tos") { + _v7 = CLIP(msg->_numValue, 0, 10); + + if (!_v5) { + _v4 = 10 - _v7; + CStatusChangeMsg statusMsg; + statusMsg._newStatus = _v4; + statusMsg.execute("GondolierLeftLever"); + } + } + + if (!_v4 && !_v7 && _v5 && _v8) { + _puzzleSolved = true; + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 1; + statusMsg.execute("GondolierFace"); + CTurnOff offMsg; + offMsg.execute(this); + CVisibleMsg visibleMsg; + visibleMsg.execute("Mouth"); + + playSound("z#47.wav"); + } else { + CSetVolumeMsg volumeMsg(_volume1, 2); + volumeMsg.execute(this); + } + } + + return true; +} + +bool CGondolierMixer::LoadSuccessMsg(CLoadSuccessMsg *msg) { + if (_fieldE4) { + _fieldE4 = 0; + _soundHandle1 = _soundHandle2 = -1; + CTurnOn onMsg; + onMsg.execute(this); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/gondolier/gondolier_mixer.h b/engines/titanic/game/gondolier/gondolier_mixer.h index 247e520ba6..167650f5bb 100644 --- a/engines/titanic/game/gondolier/gondolier_mixer.h +++ b/engines/titanic/game/gondolier/gondolier_mixer.h @@ -29,15 +29,22 @@ namespace Titanic { class CGondolierMixer : public CGondolierBase { + DECLARE_MESSAGE_MAP; bool EnterRoomMsg(CEnterRoomMsg *msg); + bool LeaveRoomMsg(CLeaveRoomMsg *msg); + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool SetVolumeMsg(CSetVolumeMsg *msg); + bool SignalObject(CSignalObject *msg); + bool LoadSuccessMsg(CLoadSuccessMsg *msg); private: - int _fieldBC; - int _fieldC0; + int _soundHandle1; + int _soundHandle2; int _fieldC4; int _fieldC8; - CString _string1; - CString _string2; - int _fieldE4; + CString _soundName1; + CString _soundName2; + bool _fieldE4; public: CLASSDEF; CGondolierMixer(); diff --git a/engines/titanic/game/gondolier/gondolier_slider.cpp b/engines/titanic/game/gondolier/gondolier_slider.cpp index eb6b1a9ad8..e7a46eb33d 100644 --- a/engines/titanic/game/gondolier/gondolier_slider.cpp +++ b/engines/titanic/game/gondolier/gondolier_slider.cpp @@ -24,11 +24,24 @@ namespace Titanic { +static const int ARRAY[11] = { 0, 0, 1, 4, 9, 15, 21, 27, 32, 35, 36 }; + +BEGIN_MESSAGE_MAP(CGondolierSlider, CGondolierBase) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseDragMoveMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MouseDragStartMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MouseDragEndMsg) + ON_MESSAGE(IsHookedOnMsg) + ON_MESSAGE(FrameMsg) + ON_MESSAGE(SignalObject) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + CGondolierSlider::CGondolierSlider() : CGondolierBase(), _fieldBC(0), _fieldC0(0), _fieldC4(0), _fieldC8(0), - _fieldCC(0), _fieldD0(0), _fieldD4(0), _fieldD8(0), - _fieldDC(0), _fieldE0(0), _fieldE4(0), _fieldE8(0), - _fieldEC(0), _string1("NULL"), _fieldFC(0), _field118(0) { + _arrayIndex(0), _string1("NULL"), _fieldFC(0), _field118(0) { } void CGondolierSlider::save(SimpleFile *file, int indent) { @@ -37,15 +50,15 @@ void CGondolierSlider::save(SimpleFile *file, int indent) { file->writeNumberLine(_fieldC0, indent); file->writeNumberLine(_fieldC4, indent); file->writeNumberLine(_fieldC8, indent); - file->writeNumberLine(_fieldCC, indent); - file->writeNumberLine(_fieldD0, indent); - file->writeNumberLine(_fieldD4, indent); - file->writeNumberLine(_fieldD8, indent); - file->writeNumberLine(_fieldDC, indent); - file->writeNumberLine(_fieldE0, indent); - file->writeNumberLine(_fieldE4, indent); - file->writeNumberLine(_fieldE8, indent); - file->writeNumberLine(_fieldCC, indent); + file->writeNumberLine(_sliderRect1.left, indent); + file->writeNumberLine(_sliderRect1.top, indent); + file->writeNumberLine(_sliderRect1.right, indent); + file->writeNumberLine(_sliderRect1.bottom, indent); + file->writeNumberLine(_sliderRect2.left, indent); + file->writeNumberLine(_sliderRect2.top, indent); + file->writeNumberLine(_sliderRect2.right, indent); + file->writeNumberLine(_sliderRect2.bottom, indent); + file->writeNumberLine(_sliderRect1.left, indent); file->writeQuotedLine(_string1, indent); file->writeNumberLine(_fieldFC, indent); file->writeQuotedLine(_string2, indent); @@ -61,15 +74,15 @@ void CGondolierSlider::load(SimpleFile *file) { _fieldC0 = file->readNumber(); _fieldC4 = file->readNumber(); _fieldC8 = file->readNumber(); - _fieldCC = file->readNumber(); - _fieldD0 = file->readNumber(); - _fieldD4 = file->readNumber(); - _fieldD8 = file->readNumber(); - _fieldDC = file->readNumber(); - _fieldE0 = file->readNumber(); - _fieldE4 = file->readNumber(); - _fieldE8 = file->readNumber(); - _fieldEC = file->readNumber(); + _sliderRect1.left = file->readNumber(); + _sliderRect1.top = file->readNumber(); + _sliderRect1.right = file->readNumber(); + _sliderRect1.bottom = file->readNumber(); + _sliderRect2.left = file->readNumber(); + _sliderRect2.top = file->readNumber(); + _sliderRect2.right = file->readNumber(); + _sliderRect2.bottom = file->readNumber(); + _arrayIndex = file->readNumber(); _string1 = file->readString(); _fieldFC = file->readNumber(); _string2 = file->readString(); @@ -79,4 +92,148 @@ void CGondolierSlider::load(SimpleFile *file) { CGondolierBase::load(file); } +bool CGondolierSlider::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!_v1) + return false; + if (_fieldFC ? _v5 : _v8) + return false; + + return _sliderRect1.contains(msg->_mousePos); +} + +bool CGondolierSlider::MouseDragMoveMsg(CMouseDragMoveMsg *msg) { + if (!(_fieldFC ? _v5 : _v8)) { + int minVal = 0x7FFFFFFF; + int foundIndex = -1; + int yp = (_sliderRect2.top + _sliderRect2.bottom) / 2 + + _bounds.top - msg->_mousePos.y; + + for (int idx = 0; idx < 11; ++idx) { + int yv = yp + ARRAY[idx]; + if (yv < 0) + yv = -yv; + if (yv < minVal) { + minVal = yv; + foundIndex = idx; + } + } + + if (foundIndex >= 0) { + _arrayIndex = foundIndex; + CSignalObject signalMsg; + signalMsg.execute(this); + } + } + + return true; +} + +bool CGondolierSlider::EnterViewMsg(CEnterViewMsg *msg) { + CSignalObject signalMsg; + signalMsg.execute(this); + return true; +} + +bool CGondolierSlider::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (!_v1) + return false; + if (_fieldFC ? _v5 : _v8) + return false; + + _field118 = checkStartDragging(msg); + return _field118; +} + +bool CGondolierSlider::StatusChangeMsg(CStatusChangeMsg *msg) { + _arrayIndex = CLIP(10 - msg->_newStatus, 0, 10); + _sliderRect1 = _sliderRect2; + _sliderRect1.translate(_bounds.left, _bounds.top); + _sliderRect1.translate(0, ARRAY[_arrayIndex]); + + loadFrame(_arrayIndex); + return true; +} + +bool CGondolierSlider::MouseDragEndMsg(CMouseDragEndMsg *msg) { + _field118 = false; + return true; +} + +bool CGondolierSlider::IsHookedOnMsg(CIsHookedOnMsg *msg) { + if (_fieldFC ? _v5 : _v8) + return false; + + if (!_sliderRect1.intersects(msg->_rect)) { + _string2 = CString(); + msg->_result = false; + } else { + _string2 = _string1; + if (_fieldFC) { + _v5 = _v9 = 1; + } else { + _v8 = _v10 = 1; + } + + msg->_result = true; + } + + return true; +} + +bool CGondolierSlider::FrameMsg(CFrameMsg *msg) { + if (_fieldFC ? _v5 : _v8) { + if (_arrayIndex < 10) { + ++_arrayIndex; + CSignalObject signalMsg; + signalMsg.execute(this); + + int yp = 0; + if (_arrayIndex > 0) + yp = ARRAY[_arrayIndex] - ARRAY[_arrayIndex - 1]; + + if (!_string2.empty()) { + CTranslateObjectMsg transMsg; + transMsg._delta = Point(0, yp); + transMsg.execute(_string2); + } + } + } else if (_fieldFC ? _v10 : _v9) { + if (!_field118 && !_puzzleSolved && _arrayIndex > 0) { + CSignalObject signalMsg; + signalMsg.execute(this); + } + } + + return true; +} + +bool CGondolierSlider::SignalObject(CSignalObject *msg) { + _arrayIndex = CLIP(_arrayIndex, 0, 10); + _sliderRect1 = _sliderRect2; + _sliderRect1.translate(_bounds.left, _bounds.top); + _sliderRect1.translate(0, ARRAY[_arrayIndex]); + loadFrame(_arrayIndex); + + CSignalObject signalMsg; + signalMsg._numValue = 10 - _arrayIndex; + signalMsg._strValue = _fieldFC ? "Fly" : "Tos"; + signalMsg.execute(_string3); + + return true; +} + +bool CGondolierSlider::ActMsg(CActMsg *msg) { + if (msg->_action == "Unhook") { + if (_fieldFC) { + _v5 = _v9 = 0; + _v10 = _v8; + } else { + _v8 = _v10 = 0; + _v9 = _v5; + } + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/gondolier/gondolier_slider.h b/engines/titanic/game/gondolier/gondolier_slider.h index 0ae14a91a0..d1562f5b2d 100644 --- a/engines/titanic/game/gondolier/gondolier_slider.h +++ b/engines/titanic/game/gondolier/gondolier_slider.h @@ -28,25 +28,30 @@ namespace Titanic { class CGondolierSlider : public CGondolierBase { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseDragMoveMsg(CMouseDragMoveMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MouseDragEndMsg(CMouseDragEndMsg *msg); + bool IsHookedOnMsg(CIsHookedOnMsg *msg); + bool FrameMsg(CFrameMsg *msg); + bool SignalObject(CSignalObject *msg); + bool ActMsg(CActMsg *msg); private: int _fieldBC; int _fieldC0; int _fieldC4; int _fieldC8; - int _fieldCC; - int _fieldD0; - int _fieldD4; - int _fieldD8; - int _fieldDC; - int _fieldE0; - int _fieldE4; - int _fieldE8; - int _fieldEC; + Rect _sliderRect1; + Rect _sliderRect2; + int _arrayIndex; CString _string1; int _fieldFC; CString _string2; CString _string3; - int _field118; + bool _field118; public: CLASSDEF; CGondolierSlider(); diff --git a/engines/titanic/game/hammer_clip.cpp b/engines/titanic/game/hammer_clip.cpp index e3f3a09a90..7fb64350af 100644 --- a/engines/titanic/game/hammer_clip.cpp +++ b/engines/titanic/game/hammer_clip.cpp @@ -21,9 +21,16 @@ */ #include "titanic/game/hammer_clip.h" +#include "titanic/core/project_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CHammerClip, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + void CHammerClip::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value, indent); @@ -36,4 +43,41 @@ void CHammerClip::load(SimpleFile *file) { CGameObject::load(file); } +bool CHammerClip::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + return true; +} + +bool CHammerClip::StatusChangeMsg(CStatusChangeMsg *msg) { + _value = msg->_newStatus == 1; + if (_value) { + CPuzzleSolvedMsg solvedMsg; + solvedMsg.execute("BigHammer"); + _cursorId = CURSOR_HAND; + } + + return true; +} + +bool CHammerClip::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (!checkStartDragging(msg)) + return false; + + if (_value) { + CVisibleMsg visibleMsg(true); + visibleMsg.execute("BigHammer"); + CPassOnDragStartMsg passMsg(msg->_mousePos, 1); + passMsg.execute("BigHammer"); + + msg->_dragItem = getRoot()->findByName("BigHammer"); + + CActMsg actMsg("HammerTaken"); + actMsg.execute("HammerDispensor"); + actMsg.execute("HammerDispensorButton"); + _cursorId = CURSOR_ARROW; + _value = 0; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/hammer_clip.h b/engines/titanic/game/hammer_clip.h index 7f5c5ab5f8..4af58c22a5 100644 --- a/engines/titanic/game/hammer_clip.h +++ b/engines/titanic/game/hammer_clip.h @@ -28,6 +28,10 @@ namespace Titanic { class CHammerClip : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); public: int _value; public: diff --git a/engines/titanic/game/hammer_dispensor.cpp b/engines/titanic/game/hammer_dispensor.cpp index 440fe1bc7b..bc6a3d5ad8 100644 --- a/engines/titanic/game/hammer_dispensor.cpp +++ b/engines/titanic/game/hammer_dispensor.cpp @@ -24,15 +24,22 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CHammerDispensor, CBackground) + ON_MESSAGE(ActMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + CHammerDispensor::CHammerDispensor() : CBackground(), - _fieldE0(0), _fieldE4(0), _fieldE8(0) { + _fieldE0(false), _fieldE4(true), _state(0) { } void CHammerDispensor::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldE0, indent); file->writeNumberLine(_fieldE4, indent); - file->writeNumberLine(_fieldE8, indent); + file->writeNumberLine(_state, indent); CBackground::save(file, indent); } @@ -41,9 +48,57 @@ void CHammerDispensor::load(SimpleFile *file) { file->readNumber(); _fieldE0 = file->readNumber(); _fieldE4 = file->readNumber(); - _fieldE8 = file->readNumber(); + _state = file->readNumber(); CBackground::load(file); } +bool CHammerDispensor::ActMsg(CActMsg *msg) { + if (msg->_action == "DispenseHammer" && !_fieldE0) { + _state = 1; + playMovie(15, 31, MOVIE_NOTIFY_OBJECT); + _fieldE0 = true; + } + + if (msg->_action == "HammerTaken" && _fieldE0) + loadFrame(32); + + return true; +} + +bool CHammerDispensor::EnterViewMsg(CEnterViewMsg *msg) { + if (_fieldE4) { + playMovie(7, 14, 0); + _fieldE4 = false; + } + + return true; +} + +bool CHammerDispensor::LeaveViewMsg(CLeaveViewMsg *msg) { + _fieldE4 = true; + _fieldE0 = 0; + _state = 2; + + if (_fieldE0) + playMovie(32, 50, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + else + playMovie(0, 7, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + return true; +} + +bool CHammerDispensor::MovieEndMsg(CMovieEndMsg *msg) { + if (_state == 1) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 1; + statusMsg.execute("HammerClip"); + } else if (_state == 2) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 2; + statusMsg.execute("HammerClip"); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/hammer_dispensor.h b/engines/titanic/game/hammer_dispensor.h index e1b30d9045..2383a3349e 100644 --- a/engines/titanic/game/hammer_dispensor.h +++ b/engines/titanic/game/hammer_dispensor.h @@ -28,10 +28,15 @@ namespace Titanic { class CHammerDispensor : public CBackground { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); private: - int _fieldE0; - int _fieldE4; - int _fieldE8; + bool _fieldE0; + bool _fieldE4; + int _state; public: CLASSDEF; CHammerDispensor(); diff --git a/engines/titanic/game/hammer_dispensor_button.cpp b/engines/titanic/game/hammer_dispensor_button.cpp index 3346498623..fbda501a24 100644 --- a/engines/titanic/game/hammer_dispensor_button.cpp +++ b/engines/titanic/game/hammer_dispensor_button.cpp @@ -24,9 +24,18 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CHammerDispensorButton, CStartAction) + ON_MESSAGE(PuzzleSolvedMsg) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(FrameMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + CHammerDispensorButton::CHammerDispensorButton() : CStartAction(), - _fieldF8(0), _fieldFC(0), _field100(0), _field104(56), - _field108(6), _field10C(0), _field110(0) { + _fieldF8(0), _fieldFC(0), _field100(0), _btnPos(Point(56, 6)), + _field10C(nullptr), _field110(0) { } void CHammerDispensorButton::save(SimpleFile *file, int indent) { @@ -34,8 +43,8 @@ void CHammerDispensorButton::save(SimpleFile *file, int indent) { file->writeNumberLine(_fieldF8, indent); file->writeNumberLine(_fieldFC, indent); file->writeNumberLine(_field100, indent); - file->writeNumberLine(_field104, indent); - file->writeNumberLine(_field108, indent); + file->writeNumberLine(_btnPos.x, indent); + file->writeNumberLine(_btnPos.y, indent); file->writeNumberLine(_field110, indent); CStartAction::save(file, indent); @@ -46,11 +55,91 @@ void CHammerDispensorButton::load(SimpleFile *file) { _fieldF8 = file->readNumber(); _fieldFC = file->readNumber(); _field100 = file->readNumber(); - _field104 = file->readNumber(); - _field108 = file->readNumber(); + _btnPos.x = file->readNumber(); + _btnPos.y = file->readNumber(); _field110 = file->readNumber(); CStartAction::load(file); } +bool CHammerDispensorButton::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) { + _fieldF8 = 1; + return true; +} + +bool CHammerDispensorButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + playSound("z#93.wav"); + petDisplayMessage(1, "In case of emergency hammer requirement, poke with long stick."); + return true; +} + +bool CHammerDispensorButton::ActMsg(CActMsg *msg) { + if (msg->_action == "HammerTaken") + _field110 = true; + return true; +} + +bool CHammerDispensorButton::FrameMsg(CFrameMsg *msg) { + if (!_fieldF8) + return true; + + if (!_field10C) { + CGameObject *obj = getDraggingObject(); + if (obj) { + if (obj->isEquals("Perch") && getView() == findView()) + _field10C = obj; + } + } + + if (_field10C) { + Point pt(_btnPos.x + _bounds.left, _btnPos.y + _bounds.top); + bool flag = checkPoint(pt, true); + + switch (_fieldFC) { + case 0: + if (flag) { + playSound("z#93.wav"); + if (++_field100 == 5) { + if (!_field110) { + CActMsg actMsg(_msgAction); + actMsg.execute(_msgTarget); + } + + setVisible(false); + _fieldF8 = 0; + _field100 = 0; + } + + _fieldFC = 1; + } + break; + + case 1: + if (!flag) { + _fieldFC = 0; + ++_field100; + } + break; + + default: + break; + } + } + + return true; +} + +bool CHammerDispensorButton::LeaveViewMsg(CLeaveViewMsg *msg) { + _field10C = nullptr; + _field100 = 0; + _fieldFC = 0; + return true; +} + +bool CHammerDispensorButton::EnterViewMsg(CEnterViewMsg *msg) { + setVisible(true); + _fieldF8 = 1; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/hammer_dispensor_button.h b/engines/titanic/game/hammer_dispensor_button.h index 36732adb2d..f497b9dae1 100644 --- a/engines/titanic/game/hammer_dispensor_button.h +++ b/engines/titanic/game/hammer_dispensor_button.h @@ -28,13 +28,19 @@ namespace Titanic { class CHammerDispensorButton : public CStartAction { + DECLARE_MESSAGE_MAP; + bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg); + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool ActMsg(CActMsg *msg); + bool FrameMsg(CFrameMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); private: int _fieldF8; int _fieldFC; int _field100; - int _field104; - int _field108; - int _field10C; + Point _btnPos; + CGameObject *_field10C; int _field110; public: CLASSDEF; diff --git a/engines/titanic/game/head_slot.cpp b/engines/titanic/game/head_slot.cpp index 32650b75e4..f7df02d364 100644 --- a/engines/titanic/game/head_slot.cpp +++ b/engines/titanic/game/head_slot.cpp @@ -21,14 +21,27 @@ */ #include "titanic/game/head_slot.h" +#include "titanic/core/project_item.h" +#include "titanic/game/brain_slot.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CHeadSlot, CGameObject) + ON_MESSAGE(AddHeadPieceMsg) + ON_MESSAGE(SenseWorkingMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(LoadSuccessMsg) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + int CHeadSlot::_v1; CHeadSlot::CHeadSlot() : CGameObject(), _string1("NotWorking"), _string2("NULL"), _fieldBC(0), _fieldD8(0), _fieldDC(27), _fieldE0(56), - _fieldE4(82), _fieldE8(112), _fieldEC(0) { + _fieldE4(82), _fieldE8(112), _fieldEC(false) { } void CHeadSlot::save(SimpleFile *file, int indent) { @@ -63,4 +76,109 @@ void CHeadSlot::load(SimpleFile *file) { CGameObject::load(file); } +bool CHeadSlot::AddHeadPieceMsg(CAddHeadPieceMsg *msg) { + setVisible(true); + _fieldBC = 1; + _string2 = msg->_value; + playMovie(_fieldDC, _fieldE8, 0); + _cursorId = CURSOR_HAND; + msg->execute("TitaniaControl"); + return true; +} + +bool CHeadSlot::SenseWorkingMsg(CSenseWorkingMsg *msg) { + if (_fieldEC) + playMovie(_fieldE4, _fieldE8, 0); + + _string1 = msg->_value; + _fieldEC = false; + return true; +} + +bool CHeadSlot::EnterViewMsg(CEnterViewMsg *msg) { + setVisible(true); + if (_v1) + _cursorId = CURSOR_ARROW; + + if (_v1 == 1 || _string1 == "Working") { + playMovie(_fieldE0, _fieldE4, MOVIE_GAMESTATE); + _fieldEC = true; + } else if (_fieldBC) { + playMovie(_fieldE0, _fieldE8, MOVIE_GAMESTATE); + _fieldEC = false; + } else { + playMovie(0, _fieldDC, MOVIE_GAMESTATE); + } + + addTimer(5000 + getRandomNumber(3000)); + return true; +} + +bool CHeadSlot::LeaveViewMsg(CLeaveViewMsg *msg) { + if (getName() == "YepItsASlot") { + stopMovie(); + + if (_fieldBC) { + loadFrame(_fieldE0); + playMovie(_fieldE0, _fieldE8, MOVIE_GAMESTATE); + _fieldEC = false; + } else { + loadFrame(_fieldDC); + playMovie(_fieldDC, _fieldE0, MOVIE_GAMESTATE); + } + + _fieldEC = false; + } + + return true; +} + +bool CHeadSlot::LoadSuccessMsg(CLoadSuccessMsg *msg) { + return true; +} + +bool CHeadSlot::TimerMsg(CTimerMsg *msg) { + if (compareViewNameTo("Titania.Node 15.S") && CBrainSlot::_added == 5 + && _fieldBC == 1) { + if (_string1 == "Working" && !_fieldEC) { + playMovie(_fieldE0, _fieldE4, 0); + _fieldEC = true; + } else if (_string1 == "Random") { + playMovie(_fieldE0, _fieldE8, 0); + } + } + + if (compareViewNameTo("Titania.Node 15.S")) { + _fieldD8 = 7000 + getRandomNumber(5000); + addTimer(_fieldD8); + } + + return true; +} + +bool CHeadSlot::ActMsg(CActMsg *msg) { + if (msg->_action == "Woken") + _v1 = 1; + return true; +} + +bool CHeadSlot::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (_fieldBC && !_v1 && checkPoint(msg->_mousePos, false, true)) { + CPassOnDragStartMsg passMsg; + passMsg._mousePos = msg->_mousePos; + passMsg.execute(_string2); + + msg->_dragItem = getRoot()->findByName(_string2); + _cursorId = CURSOR_ARROW; + _fieldBC = 0; + _fieldEC = false; + _string2 = "NULL"; + stopMovie(); + loadFrame(0); + playMovie(0, _fieldDC, 0); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/head_slot.h b/engines/titanic/game/head_slot.h index 0080411033..2767db3b61 100644 --- a/engines/titanic/game/head_slot.h +++ b/engines/titanic/game/head_slot.h @@ -28,6 +28,15 @@ namespace Titanic { class CHeadSlot : public CGameObject { + DECLARE_MESSAGE_MAP; + bool AddHeadPieceMsg(CAddHeadPieceMsg *msg); + bool SenseWorkingMsg(CSenseWorkingMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool LoadSuccessMsg(CLoadSuccessMsg *msg); + bool TimerMsg(CTimerMsg *msg); + bool ActMsg(CActMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); public: static int _v1; public: @@ -39,7 +48,7 @@ public: int _fieldE0; int _fieldE4; int _fieldE8; - int _fieldEC; + bool _fieldEC; public: CLASSDEF; CHeadSlot(); diff --git a/engines/titanic/game/head_smash_event.cpp b/engines/titanic/game/head_smash_event.cpp index 5b79acf7a5..5ec3d299d5 100644 --- a/engines/titanic/game/head_smash_event.cpp +++ b/engines/titanic/game/head_smash_event.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CHeadSmashEvent, CBackground) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CHeadSmashEvent::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CBackground::save(file, indent); @@ -34,4 +39,18 @@ void CHeadSmashEvent::load(SimpleFile *file) { CBackground::load(file); } +bool CHeadSmashEvent::ActMsg(CActMsg *msg) { + if (msg->_action == "PlayToEnd") { + setVisible(true); + playMovie(MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + + return true; +} + +bool CHeadSmashEvent::MovieEndMsg(CMovieEndMsg *msg) { + changeView("CreatorsChamber.Node 1.W"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/head_smash_event.h b/engines/titanic/game/head_smash_event.h index 912cf36bf3..09fd7a54cc 100644 --- a/engines/titanic/game/head_smash_event.h +++ b/engines/titanic/game/head_smash_event.h @@ -28,6 +28,9 @@ namespace Titanic { class CHeadSmashEvent : public CBackground { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/head_smash_lever.cpp b/engines/titanic/game/head_smash_lever.cpp index 5a2fe1f4c2..dabed26478 100644 --- a/engines/titanic/game/head_smash_lever.cpp +++ b/engines/titanic/game/head_smash_lever.cpp @@ -24,25 +24,78 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CHeadSmashLever, CBackground) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(FrameMsg) + ON_MESSAGE(LoadSuccessMsg) +END_MESSAGE_MAP() + CHeadSmashLever::CHeadSmashLever() : CBackground(), - _fieldE0(0), _fieldE4(0), _fieldE8(0) {} + _enabled(false), _fieldE4(false), _ticks(0) {} void CHeadSmashLever::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); + file->writeNumberLine(_enabled, indent); file->writeNumberLine(_fieldE4, indent); - file->writeNumberLine(_fieldE8, indent); + file->writeNumberLine(_ticks, indent); CBackground::save(file, indent); } void CHeadSmashLever::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); + _enabled = file->readNumber(); _fieldE4 = file->readNumber(); - _fieldE8 = file->readNumber(); + _ticks = file->readNumber(); CBackground::load(file); } +bool CHeadSmashLever::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_enabled) { + playMovie(0, 14, 0); + playSound("z#54.wav"); + int soundHandle = playSound("z#45.wav"); + queueSound("z#49.wav", soundHandle); + _ticks = getTicksCount(); + _fieldE4 = true; + } else { + playMovie(0); + playSound("z#56.wav"); + } + + return true; +} + +bool CHeadSmashLever::ActMsg(CActMsg *msg) { + if (msg->_action == "EnableObject") + _enabled = true; + else if (msg->_action == "DisableObject") + _enabled = false; + + return true; +} + +bool CHeadSmashLever::FrameMsg(CFrameMsg *msg) { + if (_fieldE4 && msg->_ticks > (_ticks + 750)) { + CActMsg actMsg1("CreatorsChamber.Node 1.S"); + actMsg1.execute("MoveToCreators"); + CActMsg actMsg2("PlayToEnd"); + actMsg2.execute("SmashingStatue"); + + playSound("b#16.wav"); + _fieldE4 = false; + } + + return true; +} + +bool CHeadSmashLever::LoadSuccessMsg(CLoadSuccessMsg *msg) { + if (_fieldE4) + _ticks = getTicksCount(); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/head_smash_lever.h b/engines/titanic/game/head_smash_lever.h index b8f04d39de..19de07922a 100644 --- a/engines/titanic/game/head_smash_lever.h +++ b/engines/titanic/game/head_smash_lever.h @@ -28,10 +28,15 @@ namespace Titanic { class CHeadSmashLever : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool ActMsg(CActMsg *msg); + bool FrameMsg(CFrameMsg *msg); + bool LoadSuccessMsg(CLoadSuccessMsg *msg); public: - int _fieldE0; - int _fieldE4; - int _fieldE8; + bool _enabled; + bool _fieldE4; + uint _ticks; public: CLASSDEF; CHeadSmashLever(); diff --git a/engines/titanic/game/idle_summoner.cpp b/engines/titanic/game/idle_summoner.cpp index 19d760a8db..5ca3209e28 100644 --- a/engines/titanic/game/idle_summoner.cpp +++ b/engines/titanic/game/idle_summoner.cpp @@ -24,10 +24,16 @@ namespace Titanic { -CIdleSummoner::CIdleSummoner() : CGameObject(), _fieldBC(0x57E40), - _fieldC0(0xEA60), _fieldC4(0x57E40), _fieldC8(0xEA60), - _fieldCC(0xEA60), _fieldD0(0xEA60), _fieldD4(0xEA60), - _fieldD8(0xEA60), _fieldDC(0xEA60) { +BEGIN_MESSAGE_MAP(CIdleSummoner, CGameObject) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(LoadSuccessMsg) +END_MESSAGE_MAP() + +CIdleSummoner::CIdleSummoner() : CGameObject(), _fieldBC(360000), + _fieldC0(60000), _fieldC4(360000), _fieldC8(60000), + _fieldCC(0), _fieldD0(0), _fieldD4(0), _fieldD8(0), _ticks(0) { } void CIdleSummoner::save(SimpleFile *file, int indent) { @@ -40,7 +46,7 @@ void CIdleSummoner::save(SimpleFile *file, int indent) { file->writeNumberLine(_fieldD0, indent); file->writeNumberLine(_fieldD4, indent); file->writeNumberLine(_fieldD8, indent); - file->writeNumberLine(_fieldDC, indent); + file->writeNumberLine(_ticks, indent); CGameObject::save(file, indent); } @@ -55,9 +61,70 @@ void CIdleSummoner::load(SimpleFile *file) { _fieldD0 = file->readNumber(); _fieldD4 = file->readNumber(); _fieldD8 = file->readNumber(); - _fieldDC = file->readNumber(); + _ticks = file->readNumber(); CGameObject::load(file); } +bool CIdleSummoner::EnterViewMsg(CEnterViewMsg *msg) { + CActMsg actMsg("Enable"); + actMsg.execute(this); + return true; +} + +bool CIdleSummoner::TimerMsg(CTimerMsg *msg) { + uint nodesCtr = getNodeChangedCtr(); + if (msg->_actionVal == 1 && !petDoorOrBellbotPresent() + && nodesCtr > 0 && _fieldD8) { + if (!compareRoomNameTo("TopOfWell") && !compareRoomNameTo("EmbLobby")) + return true; + + int region = talkGetDialRegion("BellBot", 1); + uint delay = region == 1 ? 15000 : 12000; + uint enterTicks = MIN(getNodeEnterTicks(), _ticks); + + CString name; + uint ticks = getTicksCount() - enterTicks; + if (ticks > delay) { + if (region == 1 || getRandomNumber(1) == 1) { + name = "BellBot"; + } else { + name = "DoorBot"; + } + _fieldD8 = nodesCtr; + + if (getRoom()) { + CSummonBotQueryMsg queryMsg(name); + if (queryMsg.execute(this)) { + CSummonBotMsg summonMsg(name, 1); + summonMsg.execute(this); + } + } + } + } + + return true; +} + +bool CIdleSummoner::ActMsg(CActMsg *msg) { + if (msg->_action == "Enable") { + if (!_fieldD4) + _fieldD4 = addTimer(15000, 15000); + } else if (msg->_action == "Disable") { + if (_fieldD4 > 0) { + stopAnimTimer(_fieldD4); + _fieldD4 = 0; + } + } else if (msg->_action == "DoorbotDismissed" || msg->_action == "BellbotDismissed") { + _ticks = getTicksCount(); + } + + return true; +} + +bool CIdleSummoner::LoadSuccessMsg(CLoadSuccessMsg *msg) { + _ticks = getTicksCount(); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/idle_summoner.h b/engines/titanic/game/idle_summoner.h index 1d9fcdd176..0066694b68 100644 --- a/engines/titanic/game/idle_summoner.h +++ b/engines/titanic/game/idle_summoner.h @@ -28,6 +28,11 @@ namespace Titanic { class CIdleSummoner : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool TimerMsg(CTimerMsg *msg); + bool ActMsg(CActMsg *msg); + bool LoadSuccessMsg(CLoadSuccessMsg *msg); public: int _fieldBC; int _fieldC0; @@ -37,7 +42,7 @@ public: int _fieldD0; int _fieldD4; int _fieldD8; - int _fieldDC; + uint _ticks; public: CIdleSummoner(); CLASSDEF; diff --git a/engines/titanic/game/lemon_dispensor.cpp b/engines/titanic/game/lemon_dispensor.cpp index 8e1674cb2d..31a04cbeca 100644 --- a/engines/titanic/game/lemon_dispensor.cpp +++ b/engines/titanic/game/lemon_dispensor.cpp @@ -24,22 +24,36 @@ namespace Titanic { -int CLemonDispensor::_v1; +BEGIN_MESSAGE_MAP(CLemonDispensor, CBackground) + ON_MESSAGE(FrameMsg) + ON_MESSAGE(ChangeSeasonMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + +bool CLemonDispensor::_isSummer; int CLemonDispensor::_v2; int CLemonDispensor::_v3; +CGameObject *CLemonDispensor::_draggingObject; CLemonDispensor::CLemonDispensor() : CBackground(), - _fieldE0(0), _fieldE4(9), _fieldE8(15), _fieldEC(0) { + _fieldE0(0), _origPt(Point(9, 15)), _fieldEC(0) { +} + +void CLemonDispensor::init() { + _isSummer = false; + _v2 = 0; + _v3 = 0; + _draggingObject = nullptr; } void CLemonDispensor::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_v1, indent); + file->writeNumberLine(_isSummer, indent); file->writeNumberLine(_v2, indent); file->writeNumberLine(_v3, indent); file->writeNumberLine(_fieldE0, indent); - file->writeNumberLine(_fieldE4, indent); - file->writeNumberLine(_fieldE8, indent); + file->writeNumberLine(_origPt.x, indent); + file->writeNumberLine(_origPt.y, indent); file->writeNumberLine(_fieldEC, indent); CBackground::save(file, indent); @@ -47,15 +61,63 @@ void CLemonDispensor::save(SimpleFile *file, int indent) { void CLemonDispensor::load(SimpleFile *file) { file->readNumber(); - _v1 = file->readNumber(); + _isSummer = file->readNumber(); _v2 = file->readNumber(); _v3 = file->readNumber(); _fieldE0 = file->readNumber(); - _fieldE4 = file->readNumber(); - _fieldE8 = file->readNumber(); + _origPt.x = file->readNumber(); + _origPt.y = file->readNumber(); _fieldEC = file->readNumber(); CBackground::load(file); } +bool CLemonDispensor::FrameMsg(CFrameMsg *msg) { + if (_v2 || !_isSummer) + return true; + + if (!_draggingObject) { + CGameObject *obj = getDraggingObject(); + if (obj && getView() == findView()) { + if (obj->isEquals("Perch")) { + petDisplayMessage(1, "This stick is too short to reach the branches."); + return true; + } + + if (obj->isEquals("LongStick")) + _draggingObject = obj; + } + } + + if (_draggingObject) { + Point pt(_origPt.x + _draggingObject->_bounds.left, + _origPt.y + _draggingObject->_bounds.top); + bool flag = checkPoint(pt, true); + + if (_fieldEC == 0) { + if (flag && ++_v3 > 10) { + CLemonFallsFromTreeMsg lemonMsg(pt); + lemonMsg.execute("Lemon"); + _v2 = 1; + } + } else if (_fieldEC == 1 && !flag) { + _fieldEC = 0; + } + } + + return true; +} + +bool CLemonDispensor::ChangeSeasonMsg(CChangeSeasonMsg *msg) { + _isSummer = msg->_season == "Summer"; + return true; +} + +bool CLemonDispensor::LeaveViewMsg(CLeaveViewMsg *msg) { + _draggingObject = nullptr; + _v3 = 0; + _fieldEC = 0; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/lemon_dispensor.h b/engines/titanic/game/lemon_dispensor.h index d6315ed620..933e0b6af0 100644 --- a/engines/titanic/game/lemon_dispensor.h +++ b/engines/titanic/game/lemon_dispensor.h @@ -28,20 +28,29 @@ namespace Titanic { class CLemonDispensor : public CBackground { + DECLARE_MESSAGE_MAP; + bool FrameMsg(CFrameMsg *msg); + bool ChangeSeasonMsg(CChangeSeasonMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); private: - static int _v1; + static bool _isSummer; static int _v2; static int _v3; + static CGameObject *_draggingObject; int _fieldE0; - int _fieldE4; - int _fieldE8; + Point _origPt; int _fieldEC; public: CLASSDEF; CLemonDispensor(); /** + * Initialize statics + */ + static void init(); + + /** * Save the data for the class to file */ virtual void save(SimpleFile *file, int indent); diff --git a/engines/titanic/game/light.cpp b/engines/titanic/game/light.cpp index fd3c446875..65e357047e 100644 --- a/engines/titanic/game/light.cpp +++ b/engines/titanic/game/light.cpp @@ -21,9 +21,22 @@ */ #include "titanic/game/light.h" +#include "titanic/game/television.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CLight, CBackground) + ON_MESSAGE(TurnOff) + ON_MESSAGE(LightsMsg) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(TurnOn) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(EnterRoomMsg) +END_MESSAGE_MAP() + CLight::CLight() : CBackground(), _fieldE0(0), _fieldE4(0), _fieldE8(0), _fieldEC(0), _fieldF0(0), _fieldF4(0), _fieldF8(0), _fieldFC(0) { @@ -57,8 +70,82 @@ void CLight::load(SimpleFile *file) { CBackground::load(file); } +bool CLight::TurnOff(CTurnOff *msg) { + setVisible(false); + return true; +} + +bool CLight::LightsMsg(CLightsMsg *msg) { + if ((msg->_flag2 && _fieldE8) || (msg->_flag3 && _fieldEC) + || (msg->_flag1 && _fieldE4) || (msg->_flag4 && _fieldF0)) { + setVisible(true); + } else { + setVisible(false); + } + + return true; +} + +bool CLight::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + // WORKAROUND: Original code doesn't seem to do anything + return true; +} + +bool CLight::TurnOn(CTurnOn *msg) { + setVisible(true); + return true; +} + +bool CLight::StatusChangeMsg(CStatusChangeMsg *msg) { + CPetControl *pet = getPetControl(); + bool flag = pet ? pet->isRoom59706() : false; + + if (_fieldFC == 1 && flag) { + petDisplayMessage(1, "That light appears to be loose."); + playSound("z#144.wav", 70); + } else { + petDisplayMessage(1, "Lumi-Glow(tm) Lights. They glow in the dark!"); + playSound("z#62.wav", 70); + } + + return true; +} + +bool CLight::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CPetControl *pet = getPetControl(); + bool flag = pet ? pet->isRoom59706() : false; + + if (_fieldFC == 1 && flag) { + petDisplayMessage(1, "That light appears to be loose."); + playSound("z#144.wav", 70); + } else { + petDisplayMessage(1, "Lumi-Glow(tm) Lights. They glow in the dark!"); + playSound("z#62.wav", 70); + } + + return true; +} + +bool CLight::ActMsg(CActMsg *msg) { + if (msg->_action == "Eye Removed") + _fieldFC = 0; + + return true; +} + bool CLight::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CLight::handleEvent"); + CPetControl *pet = getPetControl(); + setVisible(true); + + if (isEquals("6WTL")) { + CLightsMsg lightsMsg(1, 1, 1, 1); + lightsMsg.execute("1stClassState", CLight::_type, MSGFLAG_SCAN); + + bool flag = pet ? pet->isRoom59706() : false; + if (flag) + CTelevision::_turnOn = true; + } + return true; } diff --git a/engines/titanic/game/light.h b/engines/titanic/game/light.h index 79e4bc400e..68223275e5 100644 --- a/engines/titanic/game/light.h +++ b/engines/titanic/game/light.h @@ -29,6 +29,14 @@ namespace Titanic { class CLight : public CBackground { + DECLARE_MESSAGE_MAP; + bool TurnOff(CTurnOff *msg); + bool LightsMsg(CLightsMsg *msg); + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool TurnOn(CTurnOn *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool ActMsg(CActMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); private: int _fieldE0; diff --git a/engines/titanic/game/light_switch.cpp b/engines/titanic/game/light_switch.cpp index 3f5c8d2084..188691033a 100644 --- a/engines/titanic/game/light_switch.cpp +++ b/engines/titanic/game/light_switch.cpp @@ -21,10 +21,24 @@ */ #include "titanic/game/light_switch.h" +#include "titanic/game/light.h" +#include "titanic/game/television.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { -int CLightSwitch::_v1; +BEGIN_MESSAGE_MAP(CLightSwitch, CBackground) + ON_MESSAGE(PETUpMsg) + ON_MESSAGE(PETDownMsg) + ON_MESSAGE(PETLeftMsg) + ON_MESSAGE(PETRightMsg) + ON_MESSAGE(PETActivateMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(EnterRoomMsg) +END_MESSAGE_MAP() + +bool CLightSwitch::_flag; CLightSwitch::CLightSwitch() : CBackground(), _fieldE0(0), _fieldE4(0), _fieldE8(0) { @@ -34,7 +48,7 @@ void CLightSwitch::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldE0, indent); file->writeNumberLine(_fieldE4, indent); - file->writeNumberLine(_v1, indent); + file->writeNumberLine(_flag, indent); file->writeNumberLine(_fieldE8, indent); CBackground::save(file, indent); @@ -44,14 +58,94 @@ void CLightSwitch::load(SimpleFile *file) { file->readNumber(); _fieldE0 = file->readNumber(); _fieldE4 = file->readNumber(); - _v1 = file->readNumber(); + _flag = file->readNumber(); _fieldE8 = file->readNumber(); CBackground::load(file); } +bool CLightSwitch::PETUpMsg(CPETUpMsg *msg) { + if (msg->_name == "Light") { + CLightsMsg lightsMsg(true, true, false, false); + lightsMsg.execute("1stClassState", CLight::_type, MSGFLAG_SCAN); + + if (_fieldE8) + CTelevision::_turnOn = true; + } + + return true; +} + +bool CLightSwitch::PETDownMsg(CPETDownMsg *msg) { + if (msg->_name == "Light") { + CLightsMsg lightsMsg(false, false, true, true); + lightsMsg.execute("1stClassState", CLight::_type, MSGFLAG_SCAN); + + if (_fieldE8) + CTelevision::_turnOn = true; + } + + return true; +} + +bool CLightSwitch::PETLeftMsg(CPETLeftMsg *msg) { + if (msg->_name == "Light") { + CLightsMsg lightsMsg(false, true, true, false); + lightsMsg.execute("1stClassState", CLight::_type, MSGFLAG_SCAN); + + if (_fieldE8) + CTelevision::_turnOn = true; + } + + return true; +} + +bool CLightSwitch::PETRightMsg(CPETRightMsg *msg) { + if (msg->_name == "Light") { + CLightsMsg lightsMsg(true, false, false, true); + lightsMsg.execute("1stClassState", CLight::_type, MSGFLAG_SCAN); + + if (_fieldE8) + CTelevision::_turnOn = true; + } + + return true; +} + +bool CLightSwitch::PETActivateMsg(CPETActivateMsg *msg) { + if (msg->_name == "Light") { + if (_flag) { + CTurnOff offMsg; + offMsg.execute("1stClassState", CLight::_type, MSGFLAG_CLASS_DEF | MSGFLAG_SCAN); + + } else { + CTurnOn onMsg; + onMsg.execute("1stClassState", CLight::_type, MSGFLAG_CLASS_DEF | MSGFLAG_SCAN); + _flag = false; + if (_fieldE8) + CTelevision::_turnOn = false; + } + } + + return true; +} + +bool CLightSwitch::EnterViewMsg(CEnterViewMsg *msg) { + petSetRemoteTarget(); + return true; +} + +bool CLightSwitch::LeaveViewMsg(CLeaveViewMsg *msg) { + petClear(); + return true; +} + bool CLightSwitch::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CLightSwitch::handleEvent"); + _flag = true; + CPetControl *pet = getPetControl(); + if (pet) + _fieldE8 = pet->isRoom59706(); + return true; } diff --git a/engines/titanic/game/light_switch.h b/engines/titanic/game/light_switch.h index ce62d7d68c..f8c01dc8b0 100644 --- a/engines/titanic/game/light_switch.h +++ b/engines/titanic/game/light_switch.h @@ -25,13 +25,22 @@ #include "titanic/core/background.h" #include "titanic/messages/messages.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { class CLightSwitch : public CBackground { + DECLARE_MESSAGE_MAP; + bool PETUpMsg(CPETUpMsg *msg); + bool PETDownMsg(CPETDownMsg *msg); + bool PETLeftMsg(CPETLeftMsg *msg); + bool PETRightMsg(CPETRightMsg *msg); + bool PETActivateMsg(CPETActivateMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); public: - static int _v1; + static bool _flag; private: int _fieldE0; int _fieldE4; diff --git a/engines/titanic/game/little_lift_button.cpp b/engines/titanic/game/little_lift_button.cpp index 5005cb1757..afda4cac1d 100644 --- a/engines/titanic/game/little_lift_button.cpp +++ b/engines/titanic/game/little_lift_button.cpp @@ -21,9 +21,15 @@ */ #include "titanic/game/little_lift_button.h" +#include "titanic/core/room_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CLittleLiftButton, CBackground) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CLittleLiftButton::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value, indent); @@ -36,4 +42,23 @@ void CLittleLiftButton::load(SimpleFile *file) { CBackground::load(file); } +bool CLittleLiftButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + playMovie(MOVIE_NOTIFY_OBJECT); + playSound("z#60.wav"); + return true; +} + +bool CLittleLiftButton::MovieEndMsg(CMovieEndMsg *msg) { + changeView("SecClassLittleLift.Node 1.N"); + + CRoomItem *room = getRoom(); + if (room) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = _value; + statusMsg.execute(room, nullptr, MSGFLAG_SCAN); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/little_lift_button.h b/engines/titanic/game/little_lift_button.h index b14651f4b8..2cbf3b97ff 100644 --- a/engines/titanic/game/little_lift_button.h +++ b/engines/titanic/game/little_lift_button.h @@ -28,6 +28,9 @@ namespace Titanic { class CLittleLiftButton : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); private: int _value; public: diff --git a/engines/titanic/game/long_stick_dispenser.cpp b/engines/titanic/game/long_stick_dispenser.cpp index cb562ec3ca..08a29f2e4b 100644 --- a/engines/titanic/game/long_stick_dispenser.cpp +++ b/engines/titanic/game/long_stick_dispenser.cpp @@ -21,9 +21,21 @@ */ #include "titanic/game/long_stick_dispenser.h" +#include "titanic/core/project_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CLongStickDispenser, CGameObject) + ON_MESSAGE(PuzzleSolvedMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(VisibleMsg) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + void CLongStickDispenser::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldBC, indent); @@ -42,10 +54,97 @@ void CLongStickDispenser::load(SimpleFile *file) { CGameObject::load(file); } +bool CLongStickDispenser::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) { + if (!_fieldBC && !_fieldC4 && !_fieldC0) { + CStatusChangeMsg statusMsg; + statusMsg.execute("ShatterGlass"); + _fieldC0 = 1; + loadFrame(19); + } else if (_fieldC0) { + playSound("z#63.wav"); + petDisplayMessage(1, "'This glass is totally and utterly unbreakable."); + } + + return true; +} + +bool CLongStickDispenser::MovieEndMsg(CMovieEndMsg *msg) { + CPuzzleSolvedMsg puzzleMsg; + puzzleMsg.execute("LongStick"); + _fieldC0 = 1; + return true; +} + +bool CLongStickDispenser::VisibleMsg(CVisibleMsg *msg) { + setVisible(msg->_visible); + return true; +} + bool CLongStickDispenser::EnterRoomMsg(CEnterRoomMsg *msg) { _fieldC0 = 0; _fieldC4 = 1; return true; } +bool CLongStickDispenser::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!_fieldC0) { + playSound("z#62.wav"); + + switch (_fieldBC) { + case 0: + petDisplayMessage(1, "For emergency long stick, smash glass."); + break; + case 1: + petDisplayMessage(1, "This dispenser has suddenly been fitted with unbreakable glass " + "to prevent unseemly hoarding of sticks."); + break; + default: + break; + } + } + + return true; +} + +bool CLongStickDispenser::LeaveViewMsg(CLeaveViewMsg *msg) { + if (_fieldC0 == 1) { + if (_fieldC4) { + playMovie(19, 38, MOVIE_GAMESTATE); + } else { + playMovie(0, 18, MOVIE_GAMESTATE); + _fieldBC = 1; + } + + _fieldC4 = 1; + _fieldC0 = 0; + } + + return true; +} + +bool CLongStickDispenser::EnterViewMsg(CEnterViewMsg *msg) { + setVisible(true); + loadFrame(38); + _cursorId = CURSOR_HAND; + return true; +} + +bool CLongStickDispenser::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (!checkStartDragging(msg)) { + return false; + } else if (_fieldC0 == 1 && _fieldC4 == 1) { + CVisibleMsg visibleMsg(true); + visibleMsg.execute("LongStick"); + CPassOnDragStartMsg dragMsg(msg->_mousePos, 1); + dragMsg.execute("LongStick"); + + msg->_dragItem = getRoot()->findByName("LongStick"); + loadFrame(0); + _fieldC4 = 0; + _cursorId = CURSOR_ARROW; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/long_stick_dispenser.h b/engines/titanic/game/long_stick_dispenser.h index 2a1b86fb84..be05ef9c65 100644 --- a/engines/titanic/game/long_stick_dispenser.h +++ b/engines/titanic/game/long_stick_dispenser.h @@ -29,7 +29,15 @@ namespace Titanic { class CLongStickDispenser : public CGameObject { + DECLARE_MESSAGE_MAP; + bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool VisibleMsg(CVisibleMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); private: int _fieldBC; int _fieldC0; diff --git a/engines/titanic/game/maitred/maitred_arm_holder.cpp b/engines/titanic/game/maitred/maitred_arm_holder.cpp index 4d35277a33..75d95640d2 100644 --- a/engines/titanic/game/maitred/maitred_arm_holder.cpp +++ b/engines/titanic/game/maitred/maitred_arm_holder.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CMaitreDArmHolder, CDropTarget) + ON_MESSAGE(MaitreDArmHolder) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + void CMaitreDArmHolder::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CDropTarget::save(file, indent); @@ -34,4 +39,18 @@ void CMaitreDArmHolder::load(SimpleFile *file) { CDropTarget::load(file); } +bool CMaitreDArmHolder::MaitreDArmHolder(CMaitreDArmHolder *msg) { + _fieldF4 = 0; + return true; +} + +bool CMaitreDArmHolder::ActMsg(CActMsg *msg) { + if (msg->_action == "LoseArm") { + _bounds = Rect(); + setVisible(false); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/maitred/maitred_arm_holder.h b/engines/titanic/game/maitred/maitred_arm_holder.h index 3392d60e43..22f961f236 100644 --- a/engines/titanic/game/maitred/maitred_arm_holder.h +++ b/engines/titanic/game/maitred/maitred_arm_holder.h @@ -28,6 +28,9 @@ namespace Titanic { class CMaitreDArmHolder : public CDropTarget { + DECLARE_MESSAGE_MAP; + bool MaitreDArmHolder(CMaitreDArmHolder *msg); + bool ActMsg(CActMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/maitred/maitred_body.cpp b/engines/titanic/game/maitred/maitred_body.cpp index 6b495e5a1c..4cb12aac8f 100644 --- a/engines/titanic/game/maitred/maitred_body.cpp +++ b/engines/titanic/game/maitred/maitred_body.cpp @@ -20,20 +20,56 @@ * */ -#include "titanic/game/maitred/maitred_legs.h" +#include "titanic/game/maitred/maitred_body.h" namespace Titanic { -void CMaitreDLegs::save(SimpleFile *file, int indent) { +BEGIN_MESSAGE_MAP(CMaitreDBody, CMaitreDProdReceptor) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(AnimateMaitreDMsg) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + +void CMaitreDBody::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldC8, indent); + file->writeNumberLine(_armed, indent); CMaitreDProdReceptor::save(file, indent); } -void CMaitreDLegs::load(SimpleFile *file) { +void CMaitreDBody::load(SimpleFile *file) { file->readNumber(); - _fieldC8 = file->readNumber(); + _armed = file->readNumber(); CMaitreDProdReceptor::load(file); } +bool CMaitreDBody::EnterViewMsg(CEnterViewMsg *msg) { + return true; +} + +bool CMaitreDBody::AnimateMaitreDMsg(CAnimateMaitreDMsg *msg) { + static const char *const ARMED_CLIPS[5] = { + "Talking 1", "Talking 2", "Talking 3", "Talking 4", nullptr + }; + static const char *const UNARMED_CLIPS[5] = { + "Armless Talking 1", "Armless Talking 2", "Armless Talking 3", + "Armless Talking 4", nullptr + }; + + if (!hasActiveMovie()) { + playRandomClip(_armed ? ARMED_CLIPS : UNARMED_CLIPS); + } + + return true; +} + +bool CMaitreDBody::ActMsg(CActMsg *msg) { + if (msg->_action == "LoseArm") { + _armed = false; + loadFrame(262); + playSound("c#75.wav"); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/maitred/maitred_body.h b/engines/titanic/game/maitred/maitred_body.h index 7016c15c71..1798958e84 100644 --- a/engines/titanic/game/maitred/maitred_body.h +++ b/engines/titanic/game/maitred/maitred_body.h @@ -28,11 +28,15 @@ namespace Titanic { class CMaitreDBody : public CMaitreDProdReceptor { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool AnimateMaitreDMsg(CAnimateMaitreDMsg *msg); + bool ActMsg(CActMsg *msg); private: - int _fieldC8; + bool _armed; public: CLASSDEF; - CMaitreDBody() : CMaitreDProdReceptor(), _fieldC8(1) {} + CMaitreDBody() : CMaitreDProdReceptor(), _armed(true) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/maitred/maitred_legs.cpp b/engines/titanic/game/maitred/maitred_legs.cpp index 5071805101..8c0b0db5ea 100644 --- a/engines/titanic/game/maitred/maitred_legs.cpp +++ b/engines/titanic/game/maitred/maitred_legs.cpp @@ -20,20 +20,76 @@ * */ -#include "titanic/game/maitred/maitred_body.h" +#include "titanic/game/maitred/maitred_legs.h" namespace Titanic { -void CMaitreDBody::save(SimpleFile *file, int indent) { +BEGIN_MESSAGE_MAP(CMaitreDLegs, CMaitreDProdReceptor) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(AnimateMaitreDMsg) +END_MESSAGE_MAP() + +void CMaitreDLegs::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldC8, indent); + file->writeNumberLine(_flag, indent); CMaitreDProdReceptor::save(file, indent); } -void CMaitreDBody::load(SimpleFile *file) { +void CMaitreDLegs::load(SimpleFile *file) { file->readNumber(); - _fieldC8 = file->readNumber(); + _flag = file->readNumber(); CMaitreDProdReceptor::load(file); } +bool CMaitreDLegs::EnterViewMsg(CEnterViewMsg *msg) { + _flag = true; + loadFrame(0); + return true; +} + +bool CMaitreDLegs::AnimateMaitreDMsg(CAnimateMaitreDMsg *msg) { + static const char *const WIGGLE_CLIPS[4] = { + "Hip Wiggle", "Knee Bend", "Wire Wiggle", nullptr + }; + static const char *const FIGHTING_CLIPS[4] = { + "Fighting 1", "Fighting 2", "Leg Fidget", nullptr + }; + static const char *const ARCING_SOUNDS[9] = { + "MaitreD Arcing 1.wav", "MaitreD Arcing 2.wav", + "MaitreD Arcing 3.wav", "MaitreD Arcing 4.wav", + "MaitreD Arcing 5.wav", "MaitreD Arcing 6.wav", + "MaitreD Arcing 7.wav", "MaitreD Arcing 8.wav", + "MaitreD Arcing 9.wav" + }; + + switch (msg->_value) { + case 0: + if (_flag) { + playRandomClip(FIGHTING_CLIPS); + + if (getRandomNumber(2) != 0) + playSound(ARCING_SOUNDS[getRandomNumber(9)], + 40 + getRandomNumber(30)); + } else { + playClip("Walk Right"); + _flag = true; + } + break; + + case 1: + if (_flag) { + playClip("Walk Left"); + _flag = false; + } else { + playRandomClip(WIGGLE_CLIPS); + } + break; + + default: + break; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/maitred/maitred_legs.h b/engines/titanic/game/maitred/maitred_legs.h index 24ba01e712..b8a32eef4c 100644 --- a/engines/titanic/game/maitred/maitred_legs.h +++ b/engines/titanic/game/maitred/maitred_legs.h @@ -28,11 +28,14 @@ namespace Titanic { class CMaitreDLegs : public CMaitreDProdReceptor { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool AnimateMaitreDMsg(CAnimateMaitreDMsg *msg); private: - int _fieldC8; + bool _flag; public: CLASSDEF; - CMaitreDLegs() : CMaitreDProdReceptor(), _fieldC8(1) {} + CMaitreDLegs() : CMaitreDProdReceptor(), _flag(true) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/maitred/maitred_prod_receptor.cpp b/engines/titanic/game/maitred/maitred_prod_receptor.cpp index 4823f143b0..2977d417a2 100644 --- a/engines/titanic/game/maitred/maitred_prod_receptor.cpp +++ b/engines/titanic/game/maitred/maitred_prod_receptor.cpp @@ -21,13 +21,21 @@ */ #include "titanic/game/maitred/maitred_prod_receptor.h" +#include "titanic/npcs/maitre_d.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CMaitreDProdReceptor, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseMoveMsg) + ON_MESSAGE(ProdMaitreDMsg) + ON_MESSAGE(DisableMaitreDProdReceptor) +END_MESSAGE_MAP() + void CMaitreDProdReceptor::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); + file->writeNumberLine(_counter, indent); file->writeNumberLine(_fieldC4, indent); CGameObject::save(file, indent); @@ -36,10 +44,79 @@ void CMaitreDProdReceptor::save(SimpleFile *file, int indent) { void CMaitreDProdReceptor::load(SimpleFile *file) { file->readNumber(); _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); + _counter = file->readNumber(); _fieldC4 = file->readNumber(); CGameObject::load(file); } +bool CMaitreDProdReceptor::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_fieldBC == 2 && dynamic_cast<CGameObject *>(getParent())->hasActiveMovie()) { + return false; + } else { + CProdMaitreDMsg prodMsg(126); + prodMsg.execute(this); + return true; + } +} + +bool CMaitreDProdReceptor::MouseMoveMsg(CMouseMoveMsg *msg) { + if (_fieldBC == 2 && dynamic_cast<CGameObject *>(getParent())->hasActiveMovie()) + return false; + else if (++_counter < 20) + return true; + + _counter = 0; + CProdMaitreDMsg prodMsg(126); + + if (isEquals("Stick")) + prodMsg._value = 121; + else if (isEquals("Hammer")) + prodMsg._value = 122; + else if (isEquals("Lemon")) + prodMsg._value = 123; + else if (isEquals("Chicken")) + prodMsg._value = 124; + else if (isEquals("Perch")) + prodMsg._value = 125; + + CMaitreD *maitreD = dynamic_cast<CMaitreD *>(findRoomObject("MaitreD")); + if (maitreD->_field100 <= 0) + prodMsg.execute(this); + + return true; +} + +bool CMaitreDProdReceptor::ProdMaitreDMsg(CProdMaitreDMsg *msg) { + if (_fieldC4) { + CMaitreD *maitreD = dynamic_cast<CMaitreD *>(findRoomObject("MaitreD")); + if (maitreD->_field100 <= 0) { + CViewItem *view = findView(); + startTalking(maitreD, msg->_value, view); + + switch (_fieldBC) { + case 1: + startTalking(maitreD, 128, view); + break; + case 2: + startTalking(maitreD, 129, view); + break; + case 3: + startTalking(maitreD, 127, view); + break; + default: + startTalking(maitreD, 130, view); + break; + } + } + } + + return true; +} + +bool CMaitreDProdReceptor::DisableMaitreDProdReceptor(CDisableMaitreDProdReceptor *msg) { + _fieldC4 = 0; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/maitred/maitred_prod_receptor.h b/engines/titanic/game/maitred/maitred_prod_receptor.h index f3a547b8ef..0b00ce0014 100644 --- a/engines/titanic/game/maitred/maitred_prod_receptor.h +++ b/engines/titanic/game/maitred/maitred_prod_receptor.h @@ -28,14 +28,19 @@ namespace Titanic { class CMaitreDProdReceptor : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseMoveMsg(CMouseMoveMsg *msg); + bool ProdMaitreDMsg(CProdMaitreDMsg *msg); + bool DisableMaitreDProdReceptor(CDisableMaitreDProdReceptor *msg); protected: int _fieldBC; - int _fieldC0; + int _counter; int _fieldC4; public: CLASSDEF; CMaitreDProdReceptor() : CGameObject(), - _fieldBC(0), _fieldC0(0), _fieldC4(1) {} + _fieldBC(0), _counter(0), _fieldC4(1) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/missiveomat.cpp b/engines/titanic/game/missiveomat.cpp index 931b146801..6f47131716 100644 --- a/engines/titanic/game/missiveomat.cpp +++ b/engines/titanic/game/missiveomat.cpp @@ -21,35 +21,310 @@ */ #include "titanic/game/missiveomat.h" +#include "titanic/core/room_item.h" +#include "titanic/titanic.h" namespace Titanic { -CMissiveOMat::CMissiveOMat() : CGameObject(), _fieldBC(1), - _fieldC0(0), _fieldC4(0), _fieldE0(-1) { +BEGIN_MESSAGE_MAP(CMissiveOMat, CGameObject) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(KeyCharMsg) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(MissiveOMatActionMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + +CMissiveOMat::CMissiveOMat() : CGameObject(), _mode(1), + _totalMessages(0), _messageNum(0), _personIndex(-1) { + // Load data for the messages, their from and to names + loadArray(_welcomeMessages, "TEXT/MISSIVEOMAT/WELCOME", 3); + loadArray(_messages, "TEXT/MISSIVEOMAT/MESSAGES", 58); + loadArray(_from, "TEXT/MISSIVEOMAT/FROM", 58); + loadArray(_to, "TEXT/MISSIVEOMAT/TO", 58); +} + +void CMissiveOMat::loadArray(CString *arr, const CString &resName, int count) { + Common::SeekableReadStream *s = g_vm->_filesManager->getResource(resName); + for (int idx = 0; idx < count; ++idx) + arr[idx] = readStringFromStream(s); + delete s; } void CMissiveOMat::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); - file->writeNumberLine(_fieldC4, indent); + file->writeNumberLine(_mode, indent); + file->writeNumberLine(_totalMessages, indent); + file->writeNumberLine(_messageNum, indent); file->writeQuotedLine(_string1, indent); file->writeQuotedLine(_string2, indent); - file->writeNumberLine(_fieldE0, indent); + file->writeNumberLine(_personIndex, indent); CGameObject::save(file, indent); } void CMissiveOMat::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); - _fieldC4 = file->readNumber(); + _mode = file->readNumber(); + _totalMessages = file->readNumber(); + _messageNum = file->readNumber(); _string1 = file->readString(); _string2 = file->readString(); - _fieldE0 = file->readNumber(); + _personIndex = file->readNumber(); CGameObject::load(file); } +bool CMissiveOMat::EnterViewMsg(CEnterViewMsg *msg) { + CMissiveOMatActionMsg actionMsg(9); + actionMsg.execute(this); + return true; +} + +bool CMissiveOMat::KeyCharMsg(CKeyCharMsg *msg) { + CTreeItem *loginControl = findRoom()->findByName("MissiveOMat Login Control"); + CTreeItem *welcome = findRoom()->findByName("MissiveOMat Welcome"); + CTreeItem *scrollUp = findRoom()->findByName("MissiveOMat ScrollUp Button"); + CEditControlMsg editMsg; + + switch (_mode) { + case 1: { + playSound("z#228.wav"); + editMsg._mode = 6; + editMsg._param = msg->_key; + editMsg.execute(loginControl); + + if (editMsg._param == 1000) { + editMsg._mode = 3; + editMsg.execute(loginControl); + + _string1 = editMsg._text; + if (!_string1.empty()) { + loadFrame(2); + _mode = 2; + + editMsg._mode = 1; + editMsg.execute(loginControl); + editMsg._mode = 10; + editMsg._param = 24; + editMsg.execute(loginControl); + } + } + break; + } + + case 2: { + playSound("z#228.wav"); + editMsg._mode = 6; + editMsg._param = msg->_key; + editMsg.execute(loginControl); + + _string2 = editMsg._text; + if (_string1 == "Droot Scraliontis") { + _string1 = "Scraliontis"; + } else if (_string1 == "Antar Brobostigon") { + _string1 = "Brobostigon"; + } else if (_string1 == "colin") { + _string1 = "Leovinus"; + } + + bool flag = false; + if (_string1 == "Leovinus") { + if (_string2 == "Other") { + flag = true; + _personIndex = 0; + } + } else if (_string1 == "Scraliontis") { + if (_string2 == "This") { + flag = true; + _personIndex = 1; + } + } else if (_string1 == "Brobostigon") { + if (_string2 == "That") { + flag = true; + _personIndex = 2; + } + } + + if (flag) { + _mode = 4; + loadFrame(4); + editMsg._mode = 1; + editMsg.execute(loginControl); + + getTextCursor()->hide(); + editMsg._mode = 13; + editMsg.execute(loginControl); + + editMsg._mode = 12; + editMsg.execute(welcome); + + editMsg._mode = 2; + editMsg._text = _welcomeMessages[_personIndex]; + editMsg.execute(welcome); + + editMsg._mode = 12; + editMsg._text = "MissiveOMat OK Button"; + editMsg.execute(welcome); + editMsg.execute(scrollUp); + } else { + _mode = 3; + loadFrame(3); + addTimer(1500); + + editMsg._mode = 1; + editMsg.execute(loginControl); + + getTextCursor()->hide(); + } + break; + } + + default: + break; + } + + return true; +} + +bool CMissiveOMat::TimerMsg(CTimerMsg *msg) { + if (_mode == 3) { + CTreeItem *loginControl = findRoom()->findByName("MissiveOMat Login Control"); + CEditControlMsg editMsg; + editMsg._mode = 10; + editMsg._param = 8; + editMsg.execute(loginControl); + } + + return true; +} + +bool CMissiveOMat::MissiveOMatActionMsg(CMissiveOMatActionMsg *msg) { + CTreeItem *welcome = findByName("MissiveOMat Welcome"); + + switch (msg->_action) { + case MESSAGE_SHOW: { + CTreeItem *btnOk = findRoom()->findByName("MissiveOMat OK Button"); + CTreeItem *btnNext = findRoom()->findByName("MissiveOMat Next Button"); + CTreeItem *btnPrev = findRoom()->findByName("MissiveOMat Prev Button"); + CTreeItem *btnLogout = findRoom()->findByName("MissiveOMat Logout Button"); + + _mode = MESSAGE_5; + CVisibleMsg visibleMsg; + visibleMsg._visible = false; + visibleMsg.execute(btnOk); + visibleMsg._visible = true; + visibleMsg.execute(btnNext); + visibleMsg.execute(btnPrev); + visibleMsg.execute(btnLogout); + + _messageNum = 0; + _totalMessages = 0; + CString *strP = &_messages[_personIndex * 19]; + for (_totalMessages = 0; !strP->empty(); ++strP, ++_totalMessages) + ; + + CMissiveOMatActionMsg actionMsg; + actionMsg._action = REDRAW_MESSAGE; + actionMsg.execute(this); + break; + } + + case NEXT_MESSAGE: + if (_messageNum < (_totalMessages - 1)) { + ++_messageNum; + CMissiveOMatActionMsg actionMsg; + actionMsg._action = REDRAW_MESSAGE; + actionMsg.execute(this); + } + break; + + case PRIOR_MESSAGE: + if (_messageNum > 0) { + --_messageNum; + CMissiveOMatActionMsg actionMsg; + actionMsg._action = REDRAW_MESSAGE; + actionMsg.execute(this); + } + break; + + case MESSAGE_5: { + CMissiveOMatActionMsg actionMsg; + actionMsg._action = MESSAGE_9; + actionMsg.execute(this); + break; + } + + case MESSAGE_DOWN: + if (welcome) + scrollTextDown(); + break; + + case MESSAGE_UP: + if (welcome) + scrollTextUp(); + break; + + case REDRAW_MESSAGE: + if (welcome) { + CString str = CString::format( + "Missive %d of %d.\nFrom: %s\nTo: %s\n\n%s\n", + _messageNum + 1, _totalMessages, _from[_messageNum].c_str(), + _to[_messageNum].c_str(), _messages[_messageNum].c_str()); + + setText(str); + } + break; + + case MESSAGE_9: { + loadFrame(1); + _mode = MESSAGE_NONE; + _personIndex = -1; + + static const char *const WIDGETS[7] = { + "MissiveOMat Login Control", "MissiveOMat OK Button", + "MissiveOMat Next Button", "MissiveOMat Prev Button", + "MissiveOMat Logout Button", "MissiveOMat ScrollDown Button", + "MissiveOMat ScrollUp Button" + }; + CEditControlMsg editMsg; + + for (int idx = 0; idx < 7; ++idx) { + editMsg._mode = 0; + editMsg._param = 12; + editMsg.execute(WIDGETS[idx]); + editMsg._mode = 1; + editMsg.execute(WIDGETS[idx]); + editMsg._mode = 13; + editMsg.execute(WIDGETS[idx]); + } + + editMsg._mode = 12; + editMsg.execute("MissiveOMat Login Control"); + editMsg._mode = 10; + editMsg._param = 8; + editMsg.execute("MissiveOMat Login Control"); + editMsg._mode = 8; + editMsg.execute("MissiveOMat Login Control"); + + _string1.clear(); + _string2.clear(); + break; + } + + default: + break; + } + + return true; +} + +bool CMissiveOMat::LeaveViewMsg(CLeaveViewMsg *msg) { + CEditControlMsg editMsg; + editMsg._mode = 9; + editMsg.execute("MissiveOMat Login Control"); + petShowCursor(); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/missiveomat.h b/engines/titanic/game/missiveomat.h index 7fde8cf25d..9810fcc403 100644 --- a/engines/titanic/game/missiveomat.h +++ b/engines/titanic/game/missiveomat.h @@ -27,14 +27,33 @@ namespace Titanic { +enum MissiveOMatAction { + MESSAGE_NONE = 1, MESSAGE_SHOW = 2, NEXT_MESSAGE = 3, PRIOR_MESSAGE = 4, + MESSAGE_5 = 5, MESSAGE_DOWN = 6, MESSAGE_UP = 7, REDRAW_MESSAGE = 8, + MESSAGE_9 = 9 +}; + class CMissiveOMat : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool KeyCharMsg(CKeyCharMsg *msg); + bool TimerMsg(CTimerMsg *msg); + bool MissiveOMatActionMsg(CMissiveOMatActionMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); +private: + CString _welcomeMessages[3]; + CString _messages[58]; + CString _from[58]; + CString _to[58]; +private: + void loadArray(CString *arr, const CString &resName, int count); public: - int _fieldBC; - int _fieldC0; - int _fieldC4; + int _mode; + int _totalMessages; + int _messageNum; CString _string1; CString _string2; - int _fieldE0; + int _personIndex; public: CLASSDEF; CMissiveOMat(); diff --git a/engines/titanic/game/missiveomat_button.cpp b/engines/titanic/game/missiveomat_button.cpp index d5ae75dbc2..b7ad7f8f6f 100644 --- a/engines/titanic/game/missiveomat_button.cpp +++ b/engines/titanic/game/missiveomat_button.cpp @@ -21,21 +21,47 @@ */ #include "titanic/game/missiveomat_button.h" +#include "titanic/core/room_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CMissiveOMatButton, CEditControl) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(VisibleMsg) + ON_MESSAGE(MouseDoubleClickMsg) +END_MESSAGE_MAP() + void CMissiveOMatButton::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldFC, indent); + file->writeNumberLine(_buttonId, indent); CEditControl::save(file, indent); } void CMissiveOMatButton::load(SimpleFile *file) { file->readNumber(); - _fieldFC = file->readNumber(); + _buttonId = file->readNumber(); CEditControl::load(file); } +bool CMissiveOMatButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CMissiveOMatActionMsg actionMsg; + actionMsg._action = _buttonId; + actionMsg.execute(findRoom()->findByName("MissiveOMat")); + return true; +} + +bool CMissiveOMatButton::VisibleMsg(CVisibleMsg *msg) { + setVisible(msg->_visible); + return true; +} + +bool CMissiveOMatButton::MouseDoubleClickMsg(CMouseDoubleClickMsg *msg) { + CMissiveOMatActionMsg actionMsg; + actionMsg._action = _buttonId; + actionMsg.execute(findRoom()->findByName("MissiveOMat")); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/missiveomat_button.h b/engines/titanic/game/missiveomat_button.h index d36f5bd958..6dbfd4cd56 100644 --- a/engines/titanic/game/missiveomat_button.h +++ b/engines/titanic/game/missiveomat_button.h @@ -28,11 +28,15 @@ namespace Titanic { class CMissiveOMatButton : public CEditControl { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool VisibleMsg(CVisibleMsg *msg); + bool MouseDoubleClickMsg(CMouseDoubleClickMsg *msg); public: - int _fieldFC; + int _buttonId; public: CLASSDEF; - CMissiveOMatButton() : CEditControl(), _fieldFC(2) {} + CMissiveOMatButton() : CEditControl(), _buttonId(2) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/movie_tester.cpp b/engines/titanic/game/movie_tester.cpp index 1b266d9c7e..bbd66a9bce 100644 --- a/engines/titanic/game/movie_tester.cpp +++ b/engines/titanic/game/movie_tester.cpp @@ -24,18 +24,36 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CMovieTester, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CMovieTester::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value1, indent); - file->writeNumberLine(_value2, indent); + file->writeNumberLine(_movieNumFrames, indent); + file->writeNumberLine(_movieFrameNum, indent); CGameObject::save(file, indent); } void CMovieTester::load(SimpleFile *file) { file->readNumber(); - _value1 = file->readNumber(); - _value2 = file->readNumber(); + _movieNumFrames = file->readNumber(); + _movieFrameNum = file->readNumber(); CGameObject::load(file); } +bool CMovieTester::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (msg->_buttons == MB_RIGHT) { + if (--_movieFrameNum < 0) { + _movieFrameNum = _movieNumFrames - 1; + } + } else { + if (++_movieFrameNum >= _movieNumFrames) + _movieFrameNum = 0; + } + + loadFrame(_movieFrameNum); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/movie_tester.h b/engines/titanic/game/movie_tester.h index de2ef2cc5e..17a7d489d8 100644 --- a/engines/titanic/game/movie_tester.h +++ b/engines/titanic/game/movie_tester.h @@ -28,11 +28,13 @@ namespace Titanic { class CMovieTester : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: - int _value1, _value2; + int _movieNumFrames, _movieFrameNum; public: CLASSDEF; - CMovieTester() : CGameObject(), _value1(0), _value2(0) {} + CMovieTester() : CGameObject(), _movieNumFrames(0), _movieFrameNum(0) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/music_console_button.cpp b/engines/titanic/game/music_console_button.cpp index 1bc78ffe23..dc86765476 100644 --- a/engines/titanic/game/music_console_button.cpp +++ b/engines/titanic/game/music_console_button.cpp @@ -21,9 +21,18 @@ */ #include "titanic/game/music_console_button.h" +#include "titanic/core/room_item.h" +#include "titanic/sound/music_room_handler.h" +#include "titanic/titanic.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CMusicConsoleButton, CMusicPlayer) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(SetMusicControlsMsg) +END_MESSAGE_MAP() + void CMusicConsoleButton::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CMusicPlayer::save(file, indent); @@ -34,4 +43,91 @@ void CMusicConsoleButton::load(SimpleFile *file) { CMusicPlayer::load(file); } +bool CMusicConsoleButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_isActive) { + CStopMusicMsg stopMsg(this); + stopMsg.execute(this); + stopMovie(); + loadFrame(0); + } else { + CStartMusicMsg startMsg(this); + startMsg.execute(this); + playMovie(MOVIE_REPEAT); + + CMusicHasStartedMsg startedMsg; + startedMsg.execute("Music Room Phonograph"); + + if (CMusicRoom::_musicHandler->checkSound(1) + && CMusicRoom::_musicHandler->checkSound(2) + && CMusicRoom::_musicHandler->checkSound(3)) { + CCorrectMusicPlayedMsg correctMsg; + correctMsg.execute(findRoom()); + } + } + + return true; +} + +bool CMusicConsoleButton::LeaveViewMsg(CLeaveViewMsg *msg) { + if (_isActive) { + CStopMusicMsg stopMsg(this); + stopMsg.execute(this); + stopMovie(); + loadFrame(0); + } + + return true; +} + +bool CMusicConsoleButton::SetMusicControlsMsg(CSetMusicControlsMsg *msg) { + CMusicRoom *musicRoom = getMusicRoom(); + CQueryMusicControlSettingMsg queryMsg; + + queryMsg.execute("Bells Mute Control"); + musicRoom->setMuteControl(BELLS, queryMsg._value == 1 ? 1 : 0); + queryMsg.execute("Bells Pitch Control"); + musicRoom->setPitchControl(BELLS, queryMsg._value); + queryMsg.execute("Bells Speed Control"); + musicRoom->setSpeedControl(BELLS, queryMsg._value); + queryMsg.execute("Bells Inversion Control"); + musicRoom->setInversionControl(BELLS, queryMsg._value == 0 ? 1 : 0); + queryMsg.execute("Bells Direction Control"); + musicRoom->setDirectionControl(BELLS, queryMsg._value == 0 ? 1 : 0); + + queryMsg.execute("Snake Mute Control"); + musicRoom->setMuteControl(SNAKE, queryMsg._value == 1 ? 1 : 0); + queryMsg.execute("Snake Pitch Control"); + musicRoom->setPitchControl(SNAKE, queryMsg._value); + queryMsg.execute("Snake Speed Control"); + musicRoom->setSpeedControl(SNAKE, queryMsg._value); + queryMsg.execute("Snake Inversion Control"); + musicRoom->setInversionControl(SNAKE, queryMsg._value == 0 ? 1 : 0); + queryMsg.execute("Snake Direction Control"); + musicRoom->setDirectionControl(SNAKE, queryMsg._value == 0 ? 1 : 0); + + queryMsg.execute("Piano Mute Control"); + musicRoom->setMuteControl(PIANO, queryMsg._value == 1 ? 1 : 0); + queryMsg.execute("Piano Pitch Control"); + musicRoom->setPitchControl(PIANO, queryMsg._value); + queryMsg.execute("Piano Speed Control"); + musicRoom->setSpeedControl(PIANO, queryMsg._value); + queryMsg.execute("Piano Inversion Control"); + musicRoom->setInversionControl(PIANO, queryMsg._value == 0 ? 1 : 0); + queryMsg.execute("Piano Direction Control"); + musicRoom->setDirectionControl(PIANO, queryMsg._value == 0 ? 1 : 0); + + queryMsg.execute("Bass Mute Control"); + musicRoom->setMuteControl(BASS, queryMsg._value == 1 ? 1 : 0); + queryMsg.execute("Bass Pitch Control"); + musicRoom->setPitchControl(BASS, queryMsg._value); + queryMsg.execute("Bass Speed Control"); + musicRoom->setSpeedControl(BASS, queryMsg._value); + queryMsg.execute("Bass Inversion Control"); + musicRoom->setInversionControl(BASS, queryMsg._value == 0 ? 1 : 0); + queryMsg.execute("Bass Direction Control"); + musicRoom->setDirectionControl(BASS, queryMsg._value == 0 ? 1 : 0); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/music_console_button.h b/engines/titanic/game/music_console_button.h index 8e05b698d7..80ce719c96 100644 --- a/engines/titanic/game/music_console_button.h +++ b/engines/titanic/game/music_console_button.h @@ -28,6 +28,10 @@ namespace Titanic { class CMusicConsoleButton : public CMusicPlayer { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool SetMusicControlsMsg(CSetMusicControlsMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/music_room_stop_phonograph_button.cpp b/engines/titanic/game/music_room_stop_phonograph_button.cpp index 44342fc2d6..dee2c0883e 100644 --- a/engines/titanic/game/music_room_stop_phonograph_button.cpp +++ b/engines/titanic/game/music_room_stop_phonograph_button.cpp @@ -24,16 +24,52 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CMusicRoomStopPhonographButton, CEjectPhonographButton) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(FrameMsg) +END_MESSAGE_MAP() + void CMusicRoomStopPhonographButton::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_field100, indent); + file->writeNumberLine(_ticks, indent); CEjectPhonographButton::save(file, indent); } void CMusicRoomStopPhonographButton::load(SimpleFile *file) { file->readNumber(); - _field100 = file->readNumber(); + _ticks = file->readNumber(); CEjectPhonographButton::load(file); } +bool CMusicRoomStopPhonographButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!_ejected) { + loadFrame(1); + playSound(_soundName); + _readyFlag = true; + + CPhonographStopMsg stopMsg; + stopMsg.execute(getParent(), nullptr, MSGFLAG_SCAN); + if (stopMsg._value2) { + _ticks = getTicksCount(); + } else { + CEjectCylinderMsg ejectMsg; + ejectMsg.execute(getParent(), nullptr, MSGFLAG_SCAN); + _ejected = true; + } + } + + return true; +} + +bool CMusicRoomStopPhonographButton::FrameMsg(CFrameMsg *msg) { + if (_readyFlag && _ticks && msg->_ticks >= (_ticks + 100)) { + loadFrame(0); + playSound(_readySoundName); + _ticks = 0; + _readyFlag = false; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/music_room_stop_phonograph_button.h b/engines/titanic/game/music_room_stop_phonograph_button.h index 7260e5aaab..dd9e8b4bc0 100644 --- a/engines/titanic/game/music_room_stop_phonograph_button.h +++ b/engines/titanic/game/music_room_stop_phonograph_button.h @@ -28,11 +28,14 @@ namespace Titanic { class CMusicRoomStopPhonographButton : public CEjectPhonographButton { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool FrameMsg(CFrameMsg *msg); private: - int _field100; + uint _ticks; public: CLASSDEF; - CMusicRoomStopPhonographButton() : CEjectPhonographButton(), _field100(0) {} + CMusicRoomStopPhonographButton() : CEjectPhonographButton(), _ticks(0) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/music_system_lock.cpp b/engines/titanic/game/music_system_lock.cpp index f1e062b3ee..074864e7c3 100644 --- a/engines/titanic/game/music_system_lock.cpp +++ b/engines/titanic/game/music_system_lock.cpp @@ -21,9 +21,16 @@ */ #include "titanic/game/music_system_lock.h" +#include "titanic/core/room_item.h" +#include "titanic/carry/carry.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CMusicSystemLock, CDropTarget) + ON_MESSAGE(DropObjectMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CMusicSystemLock::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value, indent); @@ -36,4 +43,25 @@ void CMusicSystemLock::load(SimpleFile *file) { CDropTarget::load(file); } +bool CMusicSystemLock::DropObjectMsg(CDropObjectMsg *msg) { + CTreeItem *key = msg->_item->findByName("Music System Key"); + if (key) { + setVisible(true); + playMovie(MOVIE_NOTIFY_OBJECT); + } + + return true; +} + +bool CMusicSystemLock::MovieEndMsg(CMovieEndMsg *msg) { + CTreeItem *phonograph = findRoom()->findByName("Restaurant Phonograph"); + CQueryPhonographState queryMsg; + queryMsg.execute(phonograph); + CLockPhonographMsg lockMsg(queryMsg._value); + lockMsg.execute(phonograph, nullptr, MSGFLAG_SCAN); + + setVisible(false); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/music_system_lock.h b/engines/titanic/game/music_system_lock.h index ff826f5c77..0947915caa 100644 --- a/engines/titanic/game/music_system_lock.h +++ b/engines/titanic/game/music_system_lock.h @@ -28,6 +28,9 @@ namespace Titanic { class CMusicSystemLock : public CDropTarget { + DECLARE_MESSAGE_MAP; + bool DropObjectMsg(CDropObjectMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); private: int _value; public: diff --git a/engines/titanic/game/nav_helmet.cpp b/engines/titanic/game/nav_helmet.cpp index 770eb7375e..08ff073c26 100644 --- a/engines/titanic/game/nav_helmet.cpp +++ b/engines/titanic/game/nav_helmet.cpp @@ -21,19 +21,114 @@ */ #include "titanic/game/nav_helmet.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CNavHelmet, CGameObject) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(PETHelmetOnOffMsg) + ON_MESSAGE(PETPhotoOnOffMsg) + ON_MESSAGE(PETStarFieldLockMsg) + ON_MESSAGE(PETSetStarDestinationMsg) +END_MESSAGE_MAP() + void CNavHelmet::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_flag, indent); CGameObject::save(file, indent); } void CNavHelmet::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _flag = file->readNumber(); CGameObject::load(file); } +bool CNavHelmet::MovieEndMsg(CMovieEndMsg *msg) { + if (_flag) { + setVisible(false); + + CPetControl *pet = getPetControl(); + if (pet) { + pet->setArea(PET_STARFIELD); + petDisplayMessage(1, "Now would be an excellent opportunity to adjust your viewing apparatus."); + pet->incAreaLocks(); + } + + starFn1(0); + starFn1(12); + } + + return true; +} + +bool CNavHelmet::EnterViewMsg(CEnterViewMsg *msg) { + petSetRemoteTarget(); + return true; +} + +bool CNavHelmet::LeaveViewMsg(CLeaveViewMsg *msg) { + petClear(); + return true; +} + +bool CNavHelmet::PETHelmetOnOffMsg(CPETHelmetOnOffMsg *msg) { + CPetControl *pet = getPetControl(); + + if (_flag) { + _flag = false; + setVisible(true); + starFn1(1); + playMovie(61, 120, MOVIE_NOTIFY_OBJECT); + playSound("a#47.wav"); + playSound("a#48.wav"); + + if (pet) { + pet->decAreaLocks(); + pet->setArea(PET_REMOTE); + } + + dec54(); + } else { + inc54(); + _flag = true; + setVisible(true); + playMovie(0, 60, MOVIE_NOTIFY_OBJECT); + playSound("a#48.wav"); + playSound("a#47.wav"); + } + + return true; +} + +bool CNavHelmet::PETPhotoOnOffMsg(CPETPhotoOnOffMsg *msg) { + if (_flag) + starFn1(9); + + return true; +} + +bool CNavHelmet::PETStarFieldLockMsg(CPETStarFieldLockMsg *msg) { + if (_flag) { + if (msg->_value) { + playSound("a#6.wav"); + starFn1(17); + } else { + playSound("a#5.wav"); + starFn1(18); + } + } + + return true; +} + +bool CNavHelmet::PETSetStarDestinationMsg(CPETSetStarDestinationMsg *msg) { + CActMsg actMsg("SetDestin"); + actMsg.execute("CaptainsWheel"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/nav_helmet.h b/engines/titanic/game/nav_helmet.h index 74caa52534..c408d05c97 100644 --- a/engines/titanic/game/nav_helmet.h +++ b/engines/titanic/game/nav_helmet.h @@ -24,15 +24,24 @@ #define TITANIC_NAV_HELMET_H #include "titanic/core/game_object.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { class CNavHelmet : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MovieEndMsg(CMovieEndMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool PETHelmetOnOffMsg(CPETHelmetOnOffMsg *msg); + bool PETPhotoOnOffMsg(CPETPhotoOnOffMsg *msg); + bool PETStarFieldLockMsg(CPETStarFieldLockMsg *msg); + bool PETSetStarDestinationMsg(CPETSetStarDestinationMsg *msg); private: - int _value; + bool _flag; public: CLASSDEF; - CNavHelmet() : CGameObject(), _value(0) {} + CNavHelmet() : CGameObject(), _flag(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/parrot/parrot_succubus.cpp b/engines/titanic/game/nav_helmet_off.cpp index 02a29b748e..289e9e3f55 100644 --- a/engines/titanic/game/parrot/parrot_succubus.cpp +++ b/engines/titanic/game/nav_helmet_off.cpp @@ -20,30 +20,30 @@ * */ -#include "titanic/game/parrot/parrot_succubus.h" +#include "titanic/game/nav_helmet_off.h" +#include "titanic/pet_control/pet_control.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { -CParrotSuccUBus::CParrotSuccUBus() : CSuccUBus(), _field1DC(0), - _field1EC(0), _field1F0(376), _field1F4(393) { -} +BEGIN_MESSAGE_MAP(CNavHelmetOff, CNavHelmet) + ON_MESSAGE(MouseButtonUpMsg) +END_MESSAGE_MAP() -void CParrotSuccUBus::save(SimpleFile *file, int indent) { +void CNavHelmetOff::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_field1DC, indent); - file->writeQuotedLine(_string3, indent); - file->writeNumberLine(_field1EC, indent); - - CSuccUBus::save(file, indent); + file->writeQuotedLine(_target, indent); } -void CParrotSuccUBus::load(SimpleFile *file) { +void CNavHelmetOff::load(SimpleFile *file) { file->readNumber(); - _field1DC = file->readNumber(); - _string3 = file->readString(); - _field1EC = file->readNumber(); + _target = file->readString(); +} - CSuccUBus::load(file); +bool CNavHelmetOff::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + CDoffNavHelmet doffMsg; + doffMsg.execute(_target); + return true; } } // End of namespace Titanic diff --git a/engines/titanic/game/nav_helmet_off.h b/engines/titanic/game/nav_helmet_off.h new file mode 100644 index 0000000000..c9529fe8e9 --- /dev/null +++ b/engines/titanic/game/nav_helmet_off.h @@ -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 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. + * + */ + +#ifndef TITANIC_NAV_HELMET_OFF_H +#define TITANIC_NAV_HELMET_OFF_H + +#include "titanic/game/nav_helmet.h" +#include "titanic/messages/pet_messages.h" + +namespace Titanic { + +class CNavHelmetOff : public CNavHelmet { + DECLARE_MESSAGE_MAP; + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); +private: + CString _target; +public: + CLASSDEF; + CNavHelmetOff() : CNavHelmet(), _target("NULL") {} + + /** + * Save the data for the class to file + */ + virtual void save(SimpleFile *file, int indent); + + /** + * Load the data for the class from file + */ + virtual void load(SimpleFile *file); +}; + +} // End of namespace Titanic + +#endif /* TITANIC_NAV_HELMET_OFF_H */ diff --git a/engines/titanic/game/nav_helmet_on.cpp b/engines/titanic/game/nav_helmet_on.cpp new file mode 100644 index 0000000000..59ceebc4ad --- /dev/null +++ b/engines/titanic/game/nav_helmet_on.cpp @@ -0,0 +1,49 @@ +/* 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 "titanic/game/nav_helmet_on.h" +#include "titanic/pet_control/pet_control.h" +#include "titanic/messages/pet_messages.h" + +namespace Titanic { + +BEGIN_MESSAGE_MAP(CNavHelmetOn, CNavHelmet) + ON_MESSAGE(MouseButtonUpMsg) +END_MESSAGE_MAP() + +void CNavHelmetOn::save(SimpleFile *file, int indent) { + file->writeNumberLine(1, indent); + file->writeQuotedLine(_target, indent); +} + +void CNavHelmetOn::load(SimpleFile *file) { + file->readNumber(); + _target = file->readString(); +} + +bool CNavHelmetOn::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + CDonNavHelmet donMsg; + donMsg.execute(_target); + return true; +} + +} // End of namespace Titanic diff --git a/engines/titanic/game/bilge_succubus.h b/engines/titanic/game/nav_helmet_on.h index 4b2a626dc2..452637c48a 100644 --- a/engines/titanic/game/bilge_succubus.h +++ b/engines/titanic/game/nav_helmet_on.h @@ -20,22 +20,22 @@ * */ -#ifndef TITANIC_BILGE_SUCCUBUS_H -#define TITANIC_BILGE_SUCCUBUS_H +#ifndef TITANIC_NAV_HELMET_ON_H +#define TITANIC_NAV_HELMET_ON_H -#include "titanic/npcs/succubus.h" +#include "titanic/game/nav_helmet.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { -class CBilgeSuccUBus : public CSuccUBus { -public: - int _field1DC; - int _field1E0; - int _field1E4; - int _field1E8; +class CNavHelmetOn : public CNavHelmet { + DECLARE_MESSAGE_MAP; + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); +private: + CString _target; public: CLASSDEF; - CBilgeSuccUBus(); + CNavHelmetOn() : CNavHelmet(), _target("NULL") {} /** * Save the data for the class to file @@ -50,4 +50,4 @@ public: } // End of namespace Titanic -#endif /* TITANIC_BILGE_SUCCUBUS_H */ +#endif /* TITANIC_NAV_HELMET_ON_H */ diff --git a/engines/titanic/game/no_nut_bowl.cpp b/engines/titanic/game/no_nut_bowl.cpp index 47f9d7901e..8c0a95ac9a 100644 --- a/engines/titanic/game/no_nut_bowl.cpp +++ b/engines/titanic/game/no_nut_bowl.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CNoNutBowl, CBackground) + ON_MESSAGE(ActMsg) + ON_MESSAGE(ReplaceBowlAndNutsMsg) + ON_MESSAGE(NutPuzzleMsg) +END_MESSAGE_MAP() + void CNoNutBowl::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CBackground::save(file, indent); @@ -34,4 +40,19 @@ void CNoNutBowl::load(SimpleFile *file) { CBackground::load(file); } +bool CNoNutBowl::ActMsg(CActMsg *msg) { + return true; +} + +bool CNoNutBowl::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) { + setVisible(false); + return true; +} + +bool CNoNutBowl::NutPuzzleMsg(CNutPuzzleMsg *msg) { + if (msg->_value == "NutsGone") + setVisible(true); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/no_nut_bowl.h b/engines/titanic/game/no_nut_bowl.h index 548b324869..cd8bc65179 100644 --- a/engines/titanic/game/no_nut_bowl.h +++ b/engines/titanic/game/no_nut_bowl.h @@ -28,6 +28,10 @@ namespace Titanic { class CNoNutBowl : public CBackground { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg); + bool NutPuzzleMsg(CNutPuzzleMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/nose_holder.cpp b/engines/titanic/game/nose_holder.cpp index cd9433ee9c..ac6c10dafd 100644 --- a/engines/titanic/game/nose_holder.cpp +++ b/engines/titanic/game/nose_holder.cpp @@ -24,7 +24,16 @@ namespace Titanic { -CNoseHolder::CNoseHolder() : CDropTarget(), _field118(0), _field11C(0) { +BEGIN_MESSAGE_MAP(CNoseHolder, CDropTarget) + ON_MESSAGE(ActMsg) + ON_MESSAGE(FrameMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + +CNoseHolder::CNoseHolder() : CDropTarget(), _dragObject(nullptr), + _field11C(0) { } void CNoseHolder::save(SimpleFile *file, int indent) { @@ -41,4 +50,72 @@ void CNoseHolder::load(SimpleFile *file) { CDropTarget::load(file); } +bool CNoseHolder::ActMsg(CActMsg *msg) { + if (msg->_action == "Sneeze" && !_itemName.empty() && _fieldF4) { + CProximity prox; + prox._positioningMode = POSMODE_VECTOR; + playSound("z#35.wav", prox); + + if (getView() == findView()) { + setVisible(true); + playMovie(1, 13, MOVIE_NOTIFY_OBJECT); + } + } + + return true; +} + +bool CNoseHolder::FrameMsg(CFrameMsg *msg) { + if (!_dragObject) { + CGameObject *dragObj = getDraggingObject(); + if (!dragObj) + return false; + + if (!dragObj->isEquals("Feathers") || getView() != findView()) + return false; + + _dragObject = dragObj; + } + + if (_dragObject) { + if (!checkPoint(Point(_dragObject->_bounds.left, + _dragObject->_bounds.top))) { + _field11C = false; + } else if (!_field11C) { + CActMsg actMsg("Sneeze"); + actMsg.execute(this); + _field11C = true; + } + } + + return true; +} + +bool CNoseHolder::LeaveViewMsg(CLeaveViewMsg *msg) { + _field11C = false; + _dragObject = nullptr; + if (_fieldF4) { + loadFrame(_dropFrame); + setVisible(false); + } + + return true; +} + +bool CNoseHolder::MovieEndMsg(CMovieEndMsg *msg) { + if (_fieldF4) { + loadFrame(_dropFrame); + setVisible(false); + } + + return true; +} + +bool CNoseHolder::EnterViewMsg(CEnterViewMsg *msg) { + if (_fieldF4) + setVisible(false); + + return CDropTarget::EnterViewMsg(msg); +} + } // End of namespace Titanic diff --git a/engines/titanic/game/nose_holder.h b/engines/titanic/game/nose_holder.h index b8cca95869..7b3fbba625 100644 --- a/engines/titanic/game/nose_holder.h +++ b/engines/titanic/game/nose_holder.h @@ -28,8 +28,14 @@ namespace Titanic { class CNoseHolder : public CDropTarget { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool FrameMsg(CFrameMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); private: - int _field118; + CGameObject *_dragObject; int _field11C; public: CLASSDEF; diff --git a/engines/titanic/game/null_port_hole.cpp b/engines/titanic/game/null_port_hole.cpp index e651b1b59f..b1514c7cbf 100644 --- a/engines/titanic/game/null_port_hole.cpp +++ b/engines/titanic/game/null_port_hole.cpp @@ -27,22 +27,22 @@ namespace Titanic { EMPTY_MESSAGE_MAP(CNullPortHole, CClickResponder); CNullPortHole::CNullPortHole() : CClickResponder() { - _string1 = "For a better view, why not visit the Promenade Deck?"; - _string2 = "b#48.wav"; + _message = "For a better view, why not visit the Promenade Deck?"; + _soundName = "b#48.wav"; } void CNullPortHole::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string2, indent); - file->writeQuotedLine(_string1, indent); + file->writeQuotedLine(_soundName, indent); + file->writeQuotedLine(_message, indent); CClickResponder::save(file, indent); } void CNullPortHole::load(SimpleFile *file) { file->readNumber(); - _string2 = file->readString(); - _string1 = file->readString(); + _soundName = file->readString(); + _message = file->readString(); CClickResponder::load(file); } diff --git a/engines/titanic/game/nut_replacer.cpp b/engines/titanic/game/nut_replacer.cpp index 9a73355c91..6b05d1d0e9 100644 --- a/engines/titanic/game/nut_replacer.cpp +++ b/engines/titanic/game/nut_replacer.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CNutReplacer, CGameObject) + ON_MESSAGE(ReplaceBowlAndNutsMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CNutReplacer::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -34,4 +39,15 @@ void CNutReplacer::load(SimpleFile *file) { CGameObject::load(file); } +bool CNutReplacer::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) { + setVisible(true); + playMovie(MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + return true; +} + +bool CNutReplacer::MovieEndMsg(CMovieEndMsg *msg) { + setVisible(false); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/nut_replacer.h b/engines/titanic/game/nut_replacer.h index ead9713801..e2eed4e247 100644 --- a/engines/titanic/game/nut_replacer.h +++ b/engines/titanic/game/nut_replacer.h @@ -28,6 +28,9 @@ namespace Titanic { class CNutReplacer : public CGameObject { + DECLARE_MESSAGE_MAP; + bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/parrot/parrot_lobby_controller.cpp b/engines/titanic/game/parrot/parrot_lobby_controller.cpp index f1e054a8dd..907e7519b8 100644 --- a/engines/titanic/game/parrot/parrot_lobby_controller.cpp +++ b/engines/titanic/game/parrot/parrot_lobby_controller.cpp @@ -21,9 +21,14 @@ */ #include "titanic/game/parrot/parrot_lobby_controller.h" +#include "titanic/core/room_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CParrotLobbyController, CParrotLobbyObject) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + void CParrotLobbyController::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CParrotLobbyObject::save(file, indent); @@ -34,4 +39,34 @@ void CParrotLobbyController::load(SimpleFile *file) { CParrotLobbyObject::load(file); } +bool CParrotLobbyController::ActMsg(CActMsg *msg) { + if (msg->_action == "Refresh") + return false; + else if (msg->_action == "GainParrot") + _haveParrot = true; + else if (msg->_action == "LoseParrot") + _haveParrot = false; + else if (msg->_action == "GainPerch") + _havePerch = true; + else if (msg->_action == "LosePerch") + _havePerch = false; + else if (msg->_action == "GainStick") + _haveStick = true; + else if (msg->_action == "LoseStick") + _haveStick = false; + + _flags = 0; + if (_haveParrot) + _flags = 4; + if (_havePerch) + _flags |= 2; + if (_haveStick) + _flags |= 1; + + CActMsg actMsg("Refresh"); + actMsg.execute(findRoom(), CParrotLobbyObject::_type, MSGFLAG_CLASS_DEF | MSGFLAG_SCAN); + actMsg.execute("ParrotLobbyUpdater_TOW"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/parrot/parrot_lobby_controller.h b/engines/titanic/game/parrot/parrot_lobby_controller.h index d2fa4a1801..896a4e19d2 100644 --- a/engines/titanic/game/parrot/parrot_lobby_controller.h +++ b/engines/titanic/game/parrot/parrot_lobby_controller.h @@ -28,6 +28,8 @@ namespace Titanic { class CParrotLobbyController : public CParrotLobbyObject { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/parrot/parrot_lobby_link_updater.cpp b/engines/titanic/game/parrot/parrot_lobby_link_updater.cpp index 25d5ec724b..47311c31f5 100644 --- a/engines/titanic/game/parrot/parrot_lobby_link_updater.cpp +++ b/engines/titanic/game/parrot/parrot_lobby_link_updater.cpp @@ -21,9 +21,46 @@ */ #include "titanic/game/parrot/parrot_lobby_link_updater.h" +#include "titanic/titanic.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CParrotLobbyLinkUpdater, CParrotLobbyObject) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + +/*------------------------------------------------------------------------*/ + +LinkUpdatorEntry::LinkUpdatorEntry() { + Common::fill(&_vals[0], &_vals[8], 0); +} + +void LinkUpdatorEntry::load(Common::SeekableReadStream *s) { + _linkStr = readStringFromStream(s); + for (int idx = 0; idx < 8; ++idx) + _vals[idx] = s->readByte(); +} + +/*------------------------------------------------------------------------*/ + +void LinkUpdatorEntries::load(Common::SeekableReadStream *s, int count) { + resize(count); + for (int idx = 0; idx < count; ++idx) + (*this)[idx].load(s); +} + +/*------------------------------------------------------------------------*/ + +CParrotLobbyLinkUpdater::CParrotLobbyLinkUpdater() : CParrotLobbyObject(), _fieldBC(1) { + Common::SeekableReadStream *s = g_vm->_filesManager->getResource("DATA/PARROT_LOBBY_LINK_UPDATOR"); + _entries[0].load(s, 7); + _entries[1].load(s, 5); + _entries[2].load(s, 6); + _entries[3].load(s, 9); + _entries[4].load(s, 1); + delete s; +} + void CParrotLobbyLinkUpdater::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CParrotLobbyObject::save(file, indent); @@ -34,4 +71,45 @@ void CParrotLobbyLinkUpdater::load(SimpleFile *file) { CParrotLobbyObject::load(file); } +bool CParrotLobbyLinkUpdater::ActMsg(CActMsg *msg) { + if (msg->_action != "Refresh") + return false; + + CNodeItem *node = findNode(); + LinkUpdatorEntries *entriesP; + if (isEquals("ParrotLobbyUpdater_TOW")) { + entriesP = &_entries[4]; + } else { + if (node->_nodeNumber > 3) + return true; + entriesP = &_entries[node->_nodeNumber]; + } + int count = entriesP->size(); + + for (CTreeItem *item = node->getFirstChild(); item; item = item->scan(node)) { + CLinkItem *link = dynamic_cast<CLinkItem *>(item); + if (!link || count == 0) + continue; + + CString linkName = link->getName(); + char c = linkName.lastChar(); + if (c >= 'a' && c <= 'd') + linkName.deleteLastChar(); + + for (uint idx = 0; idx < entriesP->size(); ++idx) { + const LinkUpdatorEntry &entry = (*entriesP)[idx]; + if (entry._linkStr == linkName) { + int val = entry._vals[CParrotLobbyObject::_flags]; + if (val) + linkName += (char)(0x60 + val); + + link->_name = linkName; + break; + } + } + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/parrot/parrot_lobby_link_updater.h b/engines/titanic/game/parrot/parrot_lobby_link_updater.h index 0470a62dee..93db931a53 100644 --- a/engines/titanic/game/parrot/parrot_lobby_link_updater.h +++ b/engines/titanic/game/parrot/parrot_lobby_link_updater.h @@ -23,16 +23,34 @@ #ifndef TITANIC_PARROT_LOBBY_LINK_UPDATER_H #define TITANIC_PARROT_LOBBY_LINK_UPDATER_H +#include "common/stream.h" #include "titanic/game/parrot/parrot_lobby_object.h" namespace Titanic { +struct LinkUpdatorEntry { + CString _linkStr; + int _vals[8]; + + LinkUpdatorEntry(); + void load(Common::SeekableReadStream *s); +}; + +class LinkUpdatorEntries : public Common::Array<LinkUpdatorEntry> { +public: + void load(Common::SeekableReadStream *s, int count); +}; + class CParrotLobbyLinkUpdater : public CParrotLobbyObject { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); +private: + LinkUpdatorEntries _entries[5]; public: int _fieldBC; public: CLASSDEF; - CParrotLobbyLinkUpdater() : CParrotLobbyObject(), _fieldBC(1) {} + CParrotLobbyLinkUpdater(); /** * Save the data for the class to file diff --git a/engines/titanic/game/parrot/parrot_lobby_object.cpp b/engines/titanic/game/parrot/parrot_lobby_object.cpp index a78ab2b6d9..06222fd063 100644 --- a/engines/titanic/game/parrot/parrot_lobby_object.cpp +++ b/engines/titanic/game/parrot/parrot_lobby_object.cpp @@ -26,34 +26,34 @@ namespace Titanic { EMPTY_MESSAGE_MAP(CParrotLobbyObject, CGameObject); -int CParrotLobbyObject::_v1; -int CParrotLobbyObject::_v2; -int CParrotLobbyObject::_v3; -int CParrotLobbyObject::_v4; +bool CParrotLobbyObject::_haveParrot; +bool CParrotLobbyObject::_havePerch; +bool CParrotLobbyObject::_haveStick; +int CParrotLobbyObject::_flags; void CParrotLobbyObject::init() { - _v1 = 1; - _v2 = 1; - _v3 = 1; - _v4 = 7; + _haveParrot = true; + _havePerch = true; + _haveStick = true; + _flags = 7; } void CParrotLobbyObject::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_v1, indent); - file->writeNumberLine(_v2, indent); - file->writeNumberLine(_v3, indent); - file->writeNumberLine(_v4, indent); + file->writeNumberLine(_haveParrot, indent); + file->writeNumberLine(_havePerch, indent); + file->writeNumberLine(_haveStick, indent); + file->writeNumberLine(_flags, indent); CGameObject::save(file, indent); } void CParrotLobbyObject::load(SimpleFile *file) { file->readNumber(); - _v1 = file->readNumber(); - _v2 = file->readNumber(); - _v3 = file->readNumber(); - _v4 = file->readNumber(); + _haveParrot = file->readNumber(); + _havePerch = file->readNumber(); + _haveStick = file->readNumber(); + _flags = file->readNumber(); CGameObject::load(file); } diff --git a/engines/titanic/game/parrot/parrot_lobby_object.h b/engines/titanic/game/parrot/parrot_lobby_object.h index 5272303888..a210331399 100644 --- a/engines/titanic/game/parrot/parrot_lobby_object.h +++ b/engines/titanic/game/parrot/parrot_lobby_object.h @@ -30,10 +30,10 @@ namespace Titanic { class CParrotLobbyObject : public CGameObject { DECLARE_MESSAGE_MAP; public: - static int _v1; - static int _v2; - static int _v3; - static int _v4; + static bool _haveParrot; + static bool _havePerch; + static bool _haveStick; + static int _flags; static void init(); public: diff --git a/engines/titanic/game/parrot/parrot_lobby_view_object.cpp b/engines/titanic/game/parrot/parrot_lobby_view_object.cpp index ae398036a8..1151325676 100644 --- a/engines/titanic/game/parrot/parrot_lobby_view_object.cpp +++ b/engines/titanic/game/parrot/parrot_lobby_view_object.cpp @@ -24,16 +24,28 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CParrotLobbyViewObject, CParrotLobbyObject) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + void CParrotLobbyViewObject::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); + file->writeNumberLine(_flag, indent); CParrotLobbyObject::save(file, indent); } void CParrotLobbyViewObject::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); + _flag = file->readNumber(); CParrotLobbyObject::load(file); } +bool CParrotLobbyViewObject::ActMsg(CActMsg *msg) { + if (msg->_action != "Refresh") + return false; + + setVisible(_flag ? _haveParrot : _haveStick); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/parrot/parrot_lobby_view_object.h b/engines/titanic/game/parrot/parrot_lobby_view_object.h index 3179bb962d..484d70908e 100644 --- a/engines/titanic/game/parrot/parrot_lobby_view_object.h +++ b/engines/titanic/game/parrot/parrot_lobby_view_object.h @@ -28,11 +28,13 @@ namespace Titanic { class CParrotLobbyViewObject : public CParrotLobbyObject { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); public: - int _fieldBC; + bool _flag; public: CLASSDEF; - CParrotLobbyViewObject() : CParrotLobbyObject(), _fieldBC(1) {} + CParrotLobbyViewObject() : CParrotLobbyObject(), _flag(true) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/parrot/parrot_loser.cpp b/engines/titanic/game/parrot/parrot_loser.cpp index 6e23ef8314..dc854ee9bd 100644 --- a/engines/titanic/game/parrot/parrot_loser.cpp +++ b/engines/titanic/game/parrot/parrot_loser.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CParrotLoser, CGameObject) + ON_MESSAGE(LeaveRoomMsg) +END_MESSAGE_MAP() + void CParrotLoser::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -34,4 +38,10 @@ void CParrotLoser::load(SimpleFile *file) { CGameObject::load(file); } +bool CParrotLoser::LeaveRoomMsg(CLeaveRoomMsg *msg) { + CActMsg actMsg("FreeParrot"); + actMsg.execute("CarryParrot"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/parrot/parrot_loser.h b/engines/titanic/game/parrot/parrot_loser.h index 819fd6614c..e03bfb0727 100644 --- a/engines/titanic/game/parrot/parrot_loser.h +++ b/engines/titanic/game/parrot/parrot_loser.h @@ -28,6 +28,8 @@ namespace Titanic { class CParrotLoser : public CGameObject { + DECLARE_MESSAGE_MAP; + bool LeaveRoomMsg(CLeaveRoomMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/parrot/parrot_nut_bowl_actor.cpp b/engines/titanic/game/parrot/parrot_nut_bowl_actor.cpp index c83d66cbdf..9dfc866c0e 100644 --- a/engines/titanic/game/parrot/parrot_nut_bowl_actor.cpp +++ b/engines/titanic/game/parrot/parrot_nut_bowl_actor.cpp @@ -21,27 +21,95 @@ */ #include "titanic/game/parrot/parrot_nut_bowl_actor.h" +#include "titanic/core/room_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CParrotNutBowlActor, CGameObject) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(BowlStateChangeMsg) + ON_MESSAGE(IsEarBowlPuzzleDone) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(ReplaceBowlAndNutsMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(NutPuzzleMsg) +END_MESSAGE_MAP() + CParrotNutBowlActor::CParrotNutBowlActor() : CGameObject(), - _value1(0), _value2(0) { + _puzzleDone(0), _state(0) { } void CParrotNutBowlActor::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value1, indent); - file->writeNumberLine(_value2, indent); + file->writeNumberLine(_puzzleDone, indent); + file->writeNumberLine(_state, indent); CGameObject::save(file, indent); } void CParrotNutBowlActor::load(SimpleFile *file) { file->readNumber(); - _value1 = file->readNumber(); - _value2 = file->readNumber(); + _puzzleDone = file->readNumber(); + _state = file->readNumber(); CGameObject::load(file); } +bool CParrotNutBowlActor::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + if (!_state) { + CActMsg actMsg("Jiggle"); + actMsg.execute("BowlNutsRustler"); + } + + return true; +} + +bool CParrotNutBowlActor::BowlStateChangeMsg(CBowlStateChangeMsg *msg) { + _state = msg->_state; + if (msg->_state == 3) { + if (!_puzzleDone) { + CReplaceBowlAndNutsMsg replaceMsg; + replaceMsg.execute(findRoom(), nullptr, MSGFLAG_SCAN); + playSound("z#47.wav"); + } + + _puzzleDone = true; + } + + return true; +} + +bool CParrotNutBowlActor::CParrotNutBowlActor::IsEarBowlPuzzleDone(CIsEarBowlPuzzleDone *msg) { + msg->_value = _puzzleDone; + return true; +} + +bool CParrotNutBowlActor::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + return true; +} + +bool CParrotNutBowlActor::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) { + if (!_puzzleDone) + _state = 0; + return true; +} + +bool CParrotNutBowlActor::LeaveViewMsg(CLeaveViewMsg *msg) { + if (!_puzzleDone && _state) { + CReplaceBowlAndNutsMsg replaceMsg; + replaceMsg.execute(findRoom(), nullptr, MSGFLAG_SCAN); + } + + return true; +} + +bool CParrotNutBowlActor::NutPuzzleMsg(CNutPuzzleMsg *msg) { + if (msg->_value == "NutsGone") + _state = 1; + else if (msg->_value == "BowlUnlocked") + _state = 2; + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/parrot/parrot_nut_bowl_actor.h b/engines/titanic/game/parrot/parrot_nut_bowl_actor.h index d8395bb65a..b228c0ea9e 100644 --- a/engines/titanic/game/parrot/parrot_nut_bowl_actor.h +++ b/engines/titanic/game/parrot/parrot_nut_bowl_actor.h @@ -28,8 +28,17 @@ namespace Titanic { class CParrotNutBowlActor : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool BowlStateChangeMsg(CBowlStateChangeMsg *msg); + bool IsEarBowlPuzzleDone(CIsEarBowlPuzzleDone *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool NutPuzzleMsg(CNutPuzzleMsg *msg); public: - int _value1, _value2; + bool _puzzleDone; + int _state; public: CLASSDEF; CParrotNutBowlActor(); diff --git a/engines/titanic/game/parrot/parrot_nut_eater.cpp b/engines/titanic/game/parrot/parrot_nut_eater.cpp index 309b379ab8..751da931ac 100644 --- a/engines/titanic/game/parrot/parrot_nut_eater.cpp +++ b/engines/titanic/game/parrot/parrot_nut_eater.cpp @@ -21,9 +21,17 @@ */ #include "titanic/game/parrot/parrot_nut_eater.h" +#include "titanic/core/room_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CParrotNutEater, CGameObject) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(ReplaceBowlAndNutsMsg) + ON_MESSAGE(NutPuzzleMsg) + ON_MESSAGE(MovieFrameMsg) +END_MESSAGE_MAP() + CParrotNutEater::CParrotNutEater() : CGameObject(), _fieldBC(0), _fieldC0(69), _fieldC4(132), _fieldC8(0), _fieldCC(68) { } @@ -42,4 +50,48 @@ void CParrotNutEater::load(SimpleFile *file) { CGameObject::load(file); } +bool CParrotNutEater::MovieEndMsg(CMovieEndMsg *msg) { + setVisible(false); + CNutPuzzleMsg nutMsg("NutsGone"); + nutMsg.execute(getRoom(), nullptr, MSGFLAG_SCAN); + + playSound("z#47.wav"); + return true; +} + +bool CParrotNutEater::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) { + setVisible(false); + return true; +} + +bool CParrotNutEater::NutPuzzleMsg(CNutPuzzleMsg *msg) { + if (msg->_value == "Jiggle") { + playMovie(MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + movieEvent(68); + movieEvent(132); + playSound("z#215.wav"); + + CTrueTalkTriggerActionMsg triggerMsg; + triggerMsg._param1 = triggerMsg._param2 = 0; + triggerMsg.execute("PerchedParrot"); + } + + return true; +} + +bool CParrotNutEater::MovieFrameMsg(CMovieFrameMsg *msg) { + switch (msg->_frameNumber) { + case 68: + playSound("z#214.wav"); + break; + case 132: + playSound("z#216.wav"); + break; + default: + break; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/parrot/parrot_nut_eater.h b/engines/titanic/game/parrot/parrot_nut_eater.h index 5dcb01ca11..e09ad63947 100644 --- a/engines/titanic/game/parrot/parrot_nut_eater.h +++ b/engines/titanic/game/parrot/parrot_nut_eater.h @@ -28,6 +28,11 @@ namespace Titanic { class CParrotNutEater : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MovieEndMsg(CMovieEndMsg *msg); + bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg); + bool NutPuzzleMsg(CNutPuzzleMsg *msg); + bool MovieFrameMsg(CMovieFrameMsg *msg); public: int _fieldBC; int _fieldC0; diff --git a/engines/titanic/game/parrot/parrot_perch_holder.cpp b/engines/titanic/game/parrot/parrot_perch_holder.cpp index dd8523990b..d594446219 100644 --- a/engines/titanic/game/parrot/parrot_perch_holder.cpp +++ b/engines/titanic/game/parrot/parrot_perch_holder.cpp @@ -21,9 +21,19 @@ */ #include "titanic/game/parrot/parrot_perch_holder.h" +#include "titanic/game/cage.h" +#include "titanic/core/project_item.h" +#include "titanic/npcs/parrot.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CParrotPerchHolder, CMultiDropTarget) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(DropObjectMsg) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + void CParrotPerchHolder::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CMultiDropTarget::save(file, indent); @@ -34,4 +44,39 @@ void CParrotPerchHolder::load(SimpleFile *file) { CMultiDropTarget::load(file); } +bool CParrotPerchHolder::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (CParrot::_v1) { + if (CCage::_open) { + petDisplayMessage("You cannot take this because the cage is locked shut."); + } else if (!CParrot::_v4) { + CTrueTalkTriggerActionMsg triggerMsg(280252, 0, 0); + triggerMsg.execute(getRoot(), CParrot::_type, + MSGFLAG_CLASS_DEF | MSGFLAG_BREAK_IF_HANDLED | MSGFLAG_SCAN); + } + } + + return true; +} + +bool CParrotPerchHolder::StatusChangeMsg(CStatusChangeMsg *msg) { + _fieldF4 = msg->_newStatus; + return true; +} + +bool CParrotPerchHolder::DropObjectMsg(CDropObjectMsg *msg) { + if (CCage::_open) + return false; + else + return CMultiDropTarget::DropObjectMsg(msg); +} + +bool CParrotPerchHolder::ActMsg(CActMsg *msg) { + if (msg->_action == "FlashCore") { + playMovie(2, 2, 0); + playMovie(1, 1, 0); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/parrot/parrot_perch_holder.h b/engines/titanic/game/parrot/parrot_perch_holder.h index ff618f09dc..c1fe243476 100644 --- a/engines/titanic/game/parrot/parrot_perch_holder.h +++ b/engines/titanic/game/parrot/parrot_perch_holder.h @@ -28,6 +28,11 @@ namespace Titanic { class CParrotPerchHolder : public CMultiDropTarget { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool DropObjectMsg(CDropObjectMsg *msg); + bool ActMsg(CActMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/parrot/parrot_trigger.cpp b/engines/titanic/game/parrot/parrot_trigger.cpp index 36e99ada33..b7287ebb6a 100644 --- a/engines/titanic/game/parrot/parrot_trigger.cpp +++ b/engines/titanic/game/parrot/parrot_trigger.cpp @@ -21,9 +21,15 @@ */ #include "titanic/game/parrot/parrot_trigger.h" +#include "titanic/npcs/parrot.h" +#include "titanic/core/project_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CParrotTrigger, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CParrotTrigger::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value, indent); @@ -36,4 +42,11 @@ void CParrotTrigger::load(SimpleFile *file) { CGameObject::load(file); } +bool CParrotTrigger::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CTrueTalkTriggerActionMsg triggerMsg(_value, 0, 0); + triggerMsg.execute(getRoot(), CParrot::_type, + MSGFLAG_CLASS_DEF | MSGFLAG_BREAK_IF_HANDLED | MSGFLAG_SCAN); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/parrot/parrot_trigger.h b/engines/titanic/game/parrot/parrot_trigger.h index 28a1663fa8..6fba77b56d 100644 --- a/engines/titanic/game/parrot/parrot_trigger.h +++ b/engines/titanic/game/parrot/parrot_trigger.h @@ -28,6 +28,8 @@ namespace Titanic { class CParrotTrigger : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: int _value; public: diff --git a/engines/titanic/game/parrot/player_meets_parrot.cpp b/engines/titanic/game/parrot/player_meets_parrot.cpp index 6db9345bc0..cdb14516bf 100644 --- a/engines/titanic/game/parrot/player_meets_parrot.cpp +++ b/engines/titanic/game/parrot/player_meets_parrot.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CPlayerMeetsParrot, CGameObject) + ON_MESSAGE(EnterRoomMsg) +END_MESSAGE_MAP() + void CPlayerMeetsParrot::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -35,7 +39,7 @@ void CPlayerMeetsParrot::load(SimpleFile *file) { } bool CPlayerMeetsParrot::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CPlayerMeetsParrot::handleEvent"); + stateSet24(); return true; } diff --git a/engines/titanic/game/parrot/player_meets_parrot.h b/engines/titanic/game/parrot/player_meets_parrot.h index 9cee9ee322..edae18801f 100644 --- a/engines/titanic/game/parrot/player_meets_parrot.h +++ b/engines/titanic/game/parrot/player_meets_parrot.h @@ -29,6 +29,7 @@ namespace Titanic { class CPlayerMeetsParrot : public CGameObject { + DECLARE_MESSAGE_MAP; protected: bool EnterRoomMsg(CEnterRoomMsg *msg); public: diff --git a/engines/titanic/game/pet/pet.cpp b/engines/titanic/game/pet/pet.cpp index cd4e16d38c..99c9e01eb3 100644 --- a/engines/titanic/game/pet/pet.cpp +++ b/engines/titanic/game/pet/pet.cpp @@ -21,9 +21,14 @@ */ #include "titanic/game/pet/pet.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CPET, CGameObject) + ON_MESSAGE(ShowTextMsg) +END_MESSAGE_MAP() + CPET::CPET() : CGameObject(), _fieldBC(0), _fieldC0(3), _fieldC4(0), _fieldC8(0), _fieldD8(0), _fieldDC(0) { } @@ -54,4 +59,11 @@ void CPET::load(SimpleFile *file) { CGameObject::load(file); } +bool CPET::ShowTextMsg(CShowTextMsg *msg) { + CPetControl *pet = getPetControl(); + if (pet) + pet->petDisplayMessage(1, msg->_value); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pet/pet.h b/engines/titanic/game/pet/pet.h index cdad649401..de31a423d0 100644 --- a/engines/titanic/game/pet/pet.h +++ b/engines/titanic/game/pet/pet.h @@ -28,6 +28,8 @@ namespace Titanic { class CPET : public CGameObject { + DECLARE_MESSAGE_MAP; + bool ShowTextMsg(CShowTextMsg *msg); public: int _fieldBC; int _fieldC0; diff --git a/engines/titanic/game/pet/pet_lift.cpp b/engines/titanic/game/pet/pet_lift.cpp index 39b0d01540..afa9dd04cd 100644 --- a/engines/titanic/game/pet/pet_lift.cpp +++ b/engines/titanic/game/pet/pet_lift.cpp @@ -21,9 +21,14 @@ */ #include "titanic/game/pet/pet_lift.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CPETLift, CPETTransport) + ON_MESSAGE(TransportMsg) +END_MESSAGE_MAP() + void CPETLift::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CPETTransport::save(file, indent); @@ -34,4 +39,36 @@ void CPETLift::load(SimpleFile *file) { CPETTransport::load(file); } +bool CPETLift::TransportMsg(CTransportMsg *msg) { + CPetControl *pet = getPetControl(); + if (msg->_value1 != 1) + return false; + + int floorNum = -1; + if (msg->_roomName == "TopOfWell") { + floorNum = 1; + } else if (msg->_roomName == "BottomOfWell") { + floorNum = 39; + } else if (msg->_roomName == "PlayersRoom" && pet) { + int assignedFloor = pet->getAssignedFloorNum(); + if (assignedFloor < 1 || assignedFloor > 39) { + pet->petDisplayMessage("You have not assigned a room to go to."); + floorNum = -1; + } + } + + if (floorNum != -1) { + int elevatorNum = pet ? pet->getRoomsElevatorNum() : 0; + + if ((elevatorNum == 2 || elevatorNum == 4) && floorNum > 27) { + petDisplayMessage("Sorry, this elevator does not go below floor 27."); + } else { + CTrueTalkTriggerActionMsg triggerMsg(2, floorNum, 0); + triggerMsg.execute("Liftbot"); + } + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pet/pet_lift.h b/engines/titanic/game/pet/pet_lift.h index 88b4e1c029..ce3aace1a6 100644 --- a/engines/titanic/game/pet/pet_lift.h +++ b/engines/titanic/game/pet/pet_lift.h @@ -28,6 +28,8 @@ namespace Titanic { class CPETLift : public CPETTransport { + DECLARE_MESSAGE_MAP; + bool TransportMsg(CTransportMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/pet/pet_monitor.cpp b/engines/titanic/game/pet/pet_monitor.cpp index 6a0d207a55..2716a81fa8 100644 --- a/engines/titanic/game/pet/pet_monitor.cpp +++ b/engines/titanic/game/pet/pet_monitor.cpp @@ -21,6 +21,8 @@ */ #include "titanic/game/pet/pet_monitor.h" +#include "titanic/core/room_item.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { @@ -39,7 +41,23 @@ void CPETMonitor::load(SimpleFile *file) { } bool CPETMonitor::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CPETMonitor::handleEvent"); + bool flag = true; + if (msg->_newRoom && msg->_oldRoom) { + CString oldRoomName = msg->_oldRoom->getName(); + CString newRoomName = msg->_newRoom->getName(); + + if (newRoomName == "SgtLobby" && oldRoomName == "SGTState") + flag = false; + } + + if (flag) { + CPetControl *pet = getPetControl(); + if (pet) { + pet->setRoomsRoomNum(0); + pet->resetRoomsHighlight(); + } + } + return true; } diff --git a/engines/titanic/game/pet/pet_pellerator.cpp b/engines/titanic/game/pet/pet_pellerator.cpp index a29942ca59..59516ebcde 100644 --- a/engines/titanic/game/pet/pet_pellerator.cpp +++ b/engines/titanic/game/pet/pet_pellerator.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CPETPellerator, CPETTransport) + ON_MESSAGE(PETActivateMsg) +END_MESSAGE_MAP() + void CPETPellerator::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CPETTransport::save(file, indent); @@ -34,4 +38,24 @@ void CPETPellerator::load(SimpleFile *file) { CPETTransport::load(file); } +bool CPETPellerator::PETActivateMsg(CPETActivateMsg *msg) { + CStatusChangeMsg statusMsg; + + if (msg->_name == "PromenadeDeck") + statusMsg._newStatus = 0; + else if (msg->_name == "MusicRoom") + statusMsg._newStatus = 1; + else if (msg->_name == "Bar") + statusMsg._newStatus = 2; + else if (msg->_name == "TopOfWell") + statusMsg._newStatus = 4; + else if (msg->_name == "1stClassRestaurant") + statusMsg._newStatus = 5; + else if (msg->_name == "Arboretum") + statusMsg._newStatus = 6; + + statusMsg.execute("PelleratorObject"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pet/pet_pellerator.h b/engines/titanic/game/pet/pet_pellerator.h index 9b90c9af28..51af6f1bcd 100644 --- a/engines/titanic/game/pet/pet_pellerator.h +++ b/engines/titanic/game/pet/pet_pellerator.h @@ -24,10 +24,13 @@ #define TITANIC_PET_PELLERATOR_H #include "titanic/game/pet/pet_transport.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { class CPETPellerator : public CPETTransport { + DECLARE_MESSAGE_MAP; + bool PETActivateMsg(CPETActivateMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/pet/pet_sentinal.cpp b/engines/titanic/game/pet/pet_sentinal.cpp index 1b647d7c62..ac4cbc8418 100644 --- a/engines/titanic/game/pet/pet_sentinal.cpp +++ b/engines/titanic/game/pet/pet_sentinal.cpp @@ -21,17 +21,46 @@ */ #include "titanic/game/pet/pet_sentinal.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CPETSentinal, CGameObject) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + +CPETSentinal::CPETSentinal() : CGameObject(), _elevatorNum(0), + _wellEntry(0), _resetHighlight(0) { +} + void CPETSentinal::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); + file->writeNumberLine(_elevatorNum, indent); + file->writeNumberLine(_wellEntry, indent); + file->writeNumberLine(_resetHighlight, indent); CGameObject::save(file, indent); } void CPETSentinal::load(SimpleFile *file) { file->readNumber(); + _elevatorNum = file->readNumber(); + _wellEntry = file->readNumber(); + _resetHighlight = file->readNumber(); CGameObject::load(file); } +bool CPETSentinal::EnterViewMsg(CEnterViewMsg *msg) { + CPetControl *pet = getPetControl(); + if (pet) { + if (_elevatorNum != -1) + pet->setRoomsElevatorNum(_elevatorNum); + if (_wellEntry) + pet->setRoomsWellEntry(_wellEntry); + if (_resetHighlight) + pet->resetRoomsHighlight(); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pet/pet_sentinal.h b/engines/titanic/game/pet/pet_sentinal.h index f7f9fef0ba..150fe4a87e 100644 --- a/engines/titanic/game/pet/pet_sentinal.h +++ b/engines/titanic/game/pet/pet_sentinal.h @@ -28,8 +28,15 @@ namespace Titanic { class CPETSentinal : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); +private: + int _elevatorNum; + int _wellEntry; + bool _resetHighlight; public: CLASSDEF; + CPETSentinal(); /** * Save the data for the class to file diff --git a/engines/titanic/game/pet/pet_sounds.cpp b/engines/titanic/game/pet/pet_sounds.cpp index d612c745bb..c7f3cd3bf8 100644 --- a/engines/titanic/game/pet/pet_sounds.cpp +++ b/engines/titanic/game/pet/pet_sounds.cpp @@ -24,16 +24,40 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CPETSounds, CGameObject) + ON_MESSAGE(PETPlaySoundMsg) + ON_MESSAGE(LoadSuccessMsg) +END_MESSAGE_MAP() + void CPETSounds::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_ticks, indent); CGameObject::save(file, indent); } void CPETSounds::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _ticks = file->readNumber(); CGameObject::load(file); } +bool CPETSounds::PETPlaySoundMsg(CPETPlaySoundMsg *msg) { + if (msg->_soundNum == 1) { + playSound("z#65.wav"); + } else if (msg->_soundNum == 2 && stateGet24()) { + uint ticks = getTicksCount(); + if (!_ticks || ticks > (_ticks + 12000)) { + playSound("z#36.wav"); + _ticks = ticks; + } + } + + return true; +} + +bool CPETSounds::LoadSuccessMsg(CLoadSuccessMsg *msg) { + _ticks = 0; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pet/pet_sounds.h b/engines/titanic/game/pet/pet_sounds.h index 1d3acdb5f3..2262fde916 100644 --- a/engines/titanic/game/pet/pet_sounds.h +++ b/engines/titanic/game/pet/pet_sounds.h @@ -24,15 +24,19 @@ #define TITANIC_PET_SOUNDS_H #include "titanic/core/game_object.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { class CPETSounds : public CGameObject { + DECLARE_MESSAGE_MAP; + bool PETPlaySoundMsg(CPETPlaySoundMsg *msg); + bool LoadSuccessMsg(CLoadSuccessMsg *msg); public: - int _value; + uint _ticks; public: CLASSDEF; - CPETSounds() : CGameObject(), _value(0) {} + CPETSounds() : CGameObject(), _ticks(0) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/pet/pet_transition.cpp b/engines/titanic/game/pet/pet_transition.cpp index 33cc36ca11..ec10569236 100644 --- a/engines/titanic/game/pet/pet_transition.cpp +++ b/engines/titanic/game/pet/pet_transition.cpp @@ -21,9 +21,15 @@ */ #include "titanic/game/pet/pet_transition.h" +#include "titanic/pet_control/pet_control.h" +#include "titanic/core/view_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CPETTransition, CGameObject) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + void CPETTransition::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -34,4 +40,21 @@ void CPETTransition::load(SimpleFile *file) { CGameObject::load(file); } +bool CPETTransition::EnterViewMsg(CEnterViewMsg *msg) { + CPetControl *pet = getPetControl(); + + if (compareRoomNameTo("1stClassLobby") && pet) { + int elevatorNum = pet->getRoomsElevatorNum(); + CString nodeView = msg->_newView->getNodeViewName(); + + if (nodeView == "Node 1.E") { + pet->setRoomsElevatorNum((elevatorNum == 1 || elevatorNum == 2) ? 1 : 3); + } else if (nodeView == "Node 1.W") { + pet->setRoomsElevatorNum((elevatorNum == 1 || elevatorNum == 2) ? 2 : 4); + } + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pet/pet_transition.h b/engines/titanic/game/pet/pet_transition.h index 4abf16d509..d0fa20ccc5 100644 --- a/engines/titanic/game/pet/pet_transition.h +++ b/engines/titanic/game/pet/pet_transition.h @@ -28,6 +28,8 @@ namespace Titanic { class CPETTransition : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/pet/pet_transport.cpp b/engines/titanic/game/pet/pet_transport.cpp index 9661cace2c..a48e70ed01 100644 --- a/engines/titanic/game/pet/pet_transport.cpp +++ b/engines/titanic/game/pet/pet_transport.cpp @@ -39,7 +39,7 @@ void CPETTransport::load(SimpleFile *file) { } bool CPETTransport::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CPETTransport::handleEvent"); + petClear(); return true; } diff --git a/engines/titanic/game/pet_disabler.cpp b/engines/titanic/game/pet_disabler.cpp index 2275156503..c4946fe39f 100644 --- a/engines/titanic/game/pet_disabler.cpp +++ b/engines/titanic/game/pet_disabler.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CPetDisabler, CGameObject) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + void CPetDisabler::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeQuotedLine(_value, indent); @@ -36,4 +41,14 @@ void CPetDisabler::load(SimpleFile *file) { CGameObject::load(file); } +bool CPetDisabler::EnterViewMsg(CEnterViewMsg *msg) { + petLockInput(); + return true; +} + +bool CPetDisabler::LeaveViewMsg(CLeaveViewMsg *msg) { + petUnlockInput(); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pet_disabler.h b/engines/titanic/game/pet_disabler.h index 92b4dff0a8..06e99be49e 100644 --- a/engines/titanic/game/pet_disabler.h +++ b/engines/titanic/game/pet_disabler.h @@ -28,6 +28,9 @@ namespace Titanic { class CPetDisabler : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); public: CString _value; public: diff --git a/engines/titanic/game/phonograph.cpp b/engines/titanic/game/phonograph.cpp index 9740e29273..408cfa3413 100644 --- a/engines/titanic/game/phonograph.cpp +++ b/engines/titanic/game/phonograph.cpp @@ -24,9 +24,18 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CPhonograph, CMusicPlayer) + ON_MESSAGE(PhonographPlayMsg) + ON_MESSAGE(PhonographStopMsg) + ON_MESSAGE(PhonographRecordMsg) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(LeaveRoomMsg) + ON_MESSAGE(MusicHasStartedMsg) +END_MESSAGE_MAP() + CPhonograph::CPhonograph() : CMusicPlayer(), - _fieldE0(0), _fieldE4(0), _fieldE8(0), _fieldEC(0), - _fieldF0(0), _fieldF4(0) { + _fieldE0(false), _fieldE4(0), _fieldE8(0), _fieldEC(0), + _fieldF0(0), _fieldF4(0) { } void CPhonograph::save(SimpleFile *file, int indent) { @@ -55,8 +64,113 @@ void CPhonograph::load(SimpleFile *file) { CMusicPlayer::load(file); } +bool CPhonograph::PhonographPlayMsg(CPhonographPlayMsg *msg) { + CQueryCylinderHolderMsg holderMsg; + holderMsg.execute(this); + if (!holderMsg._value2) { + _fieldE0 = false; + return true; + } + + CQueryCylinderMsg cylinderMsg; + cylinderMsg.execute(holderMsg._target); + + if (cylinderMsg._name.empty()) { + _fieldE0 = false; + } else if (cylinderMsg._name.hasPrefix("STMusic")) { + CStartMusicMsg startMsg(this); + startMsg.execute(this); + _fieldE0 = true; + msg->_value = 1; + } else { + stopGlobalSound(0, -1); + playGlobalSound(cylinderMsg._name, -2, true, true, 0); + _fieldE0 = true; + msg->_value = 1; + } + + return true; +} + +bool CPhonograph::PhonographStopMsg(CPhonographStopMsg *msg) { + CQueryCylinderHolderMsg holderMsg; + holderMsg.execute(this); + if (!holderMsg._value2) + return true; + + _fieldE0 = false; + CQueryCylinderMsg cylinderMsg; + cylinderMsg.execute(holderMsg._target); + + if (_fieldE0) { + if (!cylinderMsg._name.empty()) { + if (cylinderMsg._name.hasPrefix("STMusic")) { + CStopMusicMsg stopMsg; + stopMsg.execute(this); + } else { + stopGlobalSound(msg->_value1, -1); + } + msg->_value2 = 1; + } + + if (!msg->_value3) + _fieldE0 = false; + } else if (_fieldE4) { + _fieldE4 = false; + msg->_value2 = 1; + } + + return true; +} + +bool CPhonograph::PhonographRecordMsg(CPhonographRecordMsg *msg) { + if (!_fieldE0 && !_fieldE4 && !_fieldE8) { + CQueryCylinderHolderMsg holderMsg; + holderMsg.execute(this); + + if (holderMsg._value2) { + _fieldE4 = true; + CErasePhonographCylinderMsg eraseMsg; + eraseMsg.execute(holderMsg._target); + } else { + _fieldE4 = false; + } + } + + return true; +} + bool CPhonograph::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CPhonograph::handleEvent"); + if (_fieldE0) { + CPhonographPlayMsg playMsg; + playMsg.execute(this); + } + + return true; +} + +bool CPhonograph::LeaveRoomMsg(CLeaveRoomMsg *msg) { + if (_fieldE0) { + CPhonographStopMsg stopMsg; + stopMsg._value1 = 1; + stopMsg.execute(this); + } + + return true; +} + +bool CPhonograph::MusicHasStartedMsg(CMusicHasStartedMsg *msg) { + if (_fieldE4) { + CQueryCylinderHolderMsg holderMsg; + holderMsg.execute(this); + if (holderMsg._value2) { + CRecordOntoCylinderMsg recordMsg; + recordMsg.execute(holderMsg._target); + } else { + _fieldE4 = false; + } + } + return true; } diff --git a/engines/titanic/game/phonograph.h b/engines/titanic/game/phonograph.h index 274d4ba367..b13a5ea910 100644 --- a/engines/titanic/game/phonograph.h +++ b/engines/titanic/game/phonograph.h @@ -29,10 +29,16 @@ namespace Titanic { class CPhonograph : public CMusicPlayer { + DECLARE_MESSAGE_MAP; + bool PhonographPlayMsg(CPhonographPlayMsg *msg); + bool PhonographStopMsg(CPhonographStopMsg *msg); + bool PhonographRecordMsg(CPhonographRecordMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); + bool LeaveRoomMsg(CLeaveRoomMsg *msg); + bool MusicHasStartedMsg(CMusicHasStartedMsg *msg); protected: CString _string2; - int _fieldE0; + bool _fieldE0; int _fieldE4; int _fieldE8; int _fieldEC; diff --git a/engines/titanic/game/phonograph_lid.cpp b/engines/titanic/game/phonograph_lid.cpp index a0518420f7..3741749fbf 100644 --- a/engines/titanic/game/phonograph_lid.cpp +++ b/engines/titanic/game/phonograph_lid.cpp @@ -24,16 +24,63 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CPhonographLid, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(LockPhonographMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + void CPhonographLid::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_open, indent); CGameObject::save(file, indent); } void CPhonographLid::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _open = file->readNumber(); CGameObject::load(file); } +bool CPhonographLid::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CQueryPhonographState stateMsg; + stateMsg.execute(getParent(), nullptr, MSGFLAG_SCAN); + if (stateMsg._value) { + if (_open) { + CGameObject *lock = dynamic_cast<CGameObject *>(findByName("Music System Lock")); + if (lock) + lock->setVisible(false); + playMovie(0, 27, 0); + } else { + playMovie(27, 55, 0); + } + + _open = !_open; + } else { + petDisplayMessage(0, "This is the restaurant music system. It appears to be locked."); + } + + return true; +} + +bool CPhonographLid::MovieEndMsg(CMovieEndMsg *msg) { + // WORKAROUND: Redundant code in original not included + return true; +} + +bool CPhonographLid::LockPhonographMsg(CLockPhonographMsg *msg) { + _cursorId = msg->_value ? CURSOR_INVALID : CURSOR_ARROW; + return true; +} + +bool CPhonographLid::LeaveViewMsg(CLeaveViewMsg *msg) { + if (_open) { + playMovie(27, 55, MOVIE_GAMESTATE); + _open = false; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/phonograph_lid.h b/engines/titanic/game/phonograph_lid.h index ab32be268b..4e71d70ec2 100644 --- a/engines/titanic/game/phonograph_lid.h +++ b/engines/titanic/game/phonograph_lid.h @@ -28,11 +28,16 @@ namespace Titanic { class CPhonographLid : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool LockPhonographMsg(CLockPhonographMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); private: - int _value; + bool _open; public: CLASSDEF; - CPhonographLid() : CGameObject(), _value(0) {} + CPhonographLid() : CGameObject(), _open(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/pickup/pick_up.cpp b/engines/titanic/game/pickup/pick_up.cpp index c660a36a32..64d2d1d0d2 100644 --- a/engines/titanic/game/pickup/pick_up.cpp +++ b/engines/titanic/game/pickup/pick_up.cpp @@ -24,16 +24,26 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CPickUp, CGameObject) + ON_MESSAGE(StatusChangeMsg) +END_MESSAGE_MAP() + void CPickUp::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); + file->writeNumberLine(_enabled, indent); CGameObject::save(file, indent); } void CPickUp::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); + _enabled = file->readNumber(); CGameObject::load(file); } +bool CPickUp::StatusChangeMsg(CStatusChangeMsg *msg) { + _enabled = msg->_newStatus == 1; + setVisible(_enabled); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pickup/pick_up.h b/engines/titanic/game/pickup/pick_up.h index f0b6794442..f5ee06fd32 100644 --- a/engines/titanic/game/pickup/pick_up.h +++ b/engines/titanic/game/pickup/pick_up.h @@ -28,11 +28,13 @@ namespace Titanic { class CPickUp : public CGameObject { -private: - int _fieldBC; + DECLARE_MESSAGE_MAP; + bool StatusChangeMsg(CStatusChangeMsg *msg); +protected: + bool _enabled; public: CLASSDEF; - CPickUp() : CGameObject(), _fieldBC(0) {} + CPickUp() : CGameObject(), _enabled(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/pickup/pick_up_bar_glass.cpp b/engines/titanic/game/pickup/pick_up_bar_glass.cpp index 85b883281e..9da17b139e 100644 --- a/engines/titanic/game/pickup/pick_up_bar_glass.cpp +++ b/engines/titanic/game/pickup/pick_up_bar_glass.cpp @@ -21,9 +21,16 @@ */ #include "titanic/game/pickup/pick_up_bar_glass.h" +#include "titanic/core/project_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CPickUpBarGlass, CPickUp) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MouseDragStartMsg) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CPickUpBarGlass::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CPickUp::save(file, indent); @@ -34,4 +41,48 @@ void CPickUpBarGlass::load(SimpleFile *file) { CPickUp::load(file); } +bool CPickUpBarGlass::StatusChangeMsg(CStatusChangeMsg *msg) { + switch (msg->_newStatus) { + case 0: + setVisible(false); + _enabled = false; + break; + case 1: + setVisible(true); + _enabled = true; + break; + case 2: + setVisible(true); + _enabled = false; + break; + default: + break; + } + + return true; +} + +bool CPickUpBarGlass::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (checkStartDragging(msg) && _enabled) { + CTurnOn onMsg; + onMsg.execute("BeerGlass"); + CVisibleMsg visibleMsg; + visibleMsg.execute("BeerGlass"); + CPassOnDragStartMsg passMsg(msg->_mousePos, 1, 3); + passMsg.execute("BeerGlass"); + + msg->_dragItem = getRoot()->findByName("BeerGlass"); + + CActMsg actMsg("PlayerTakesGlass"); + actMsg.execute("Barbot"); + return true; + } else { + return false; + } +} + +bool CPickUpBarGlass::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pickup/pick_up_bar_glass.h b/engines/titanic/game/pickup/pick_up_bar_glass.h index b5ef6f5a47..d273d96170 100644 --- a/engines/titanic/game/pickup/pick_up_bar_glass.h +++ b/engines/titanic/game/pickup/pick_up_bar_glass.h @@ -28,6 +28,10 @@ namespace Titanic { class CPickUpBarGlass : public CPickUp { + DECLARE_MESSAGE_MAP; + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/pickup/pick_up_hose.cpp b/engines/titanic/game/pickup/pick_up_hose.cpp index 7375ddaa63..d07088cefd 100644 --- a/engines/titanic/game/pickup/pick_up_hose.cpp +++ b/engines/titanic/game/pickup/pick_up_hose.cpp @@ -21,14 +21,24 @@ */ #include "titanic/game/pickup/pick_up_hose.h" +#include "titanic/core/project_item.h" +#include "titanic/core/room_item.h" +#include "titanic/core/view_item.h" namespace Titanic { -int CPickUpHose::_v1; +BEGIN_MESSAGE_MAP(CPickUpHose, CPickUp) + ON_MESSAGE(MouseDragStartMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + +bool CPickUpHose::_v1; void CPickUpHose::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string1, indent); + file->writeQuotedLine(_target, indent); file->writeNumberLine(_v1, indent); CPickUp::save(file, indent); @@ -36,10 +46,61 @@ void CPickUpHose::save(SimpleFile *file, int indent) { void CPickUpHose::load(SimpleFile *file) { file->readNumber(); - _string1 = file->readString(); + _target = file->readString(); _v1 = file->readNumber(); CPickUp::load(file); } +bool CPickUpHose::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (!checkStartDragging(msg)) + return true; + if (_v1 || !_enabled) + return false; + + CViewItem *view = getView(); + if (view) { + _v1 = true; + CRoomItem *room = locateRoom("Arboretum"); + CTreeItem *hose = room ? room->findByName("Hose") : nullptr; + + if (!hose) { + room = locateRoom("FrozenArboretum"); + if (room) + hose = room->findByName("Hose"); + } + + if (hose) { + CVisibleMsg visibleMsg; + visibleMsg.execute(this); + moveUnder(view); + + CPassOnDragStartMsg passMsg(msg->_mousePos, 1); + passMsg.execute("Hose"); + + msg->_dragItem = getRoot()->findByName("Hose"); + _cursorId = CURSOR_IGNORE; + + CActMsg actMsg("PlayerGetsHose"); + actMsg.execute(_target); + } + } + + return true; +} + +bool CPickUpHose::StatusChangeMsg(CStatusChangeMsg *msg) { + _cursorId = msg->_newStatus == 1 ? CURSOR_HAND : CURSOR_IGNORE; + return true; +} + +bool CPickUpHose::EnterViewMsg(CEnterViewMsg *msg) { + _cursorId = CURSOR_IGNORE; + return true; +} + +bool CPickUpHose::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + return _enabled; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pickup/pick_up_hose.h b/engines/titanic/game/pickup/pick_up_hose.h index 80ccedc845..2ad7c2a583 100644 --- a/engines/titanic/game/pickup/pick_up_hose.h +++ b/engines/titanic/game/pickup/pick_up_hose.h @@ -28,10 +28,15 @@ namespace Titanic { class CPickUpHose : public CPickUp { + DECLARE_MESSAGE_MAP; + bool MouseDragStartMsg(CMouseDragStartMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); private: - static int _v1; + static bool _v1; - CString _string1; + CString _target; public: CLASSDEF; diff --git a/engines/titanic/game/pickup/pick_up_lemon.cpp b/engines/titanic/game/pickup/pick_up_lemon.cpp index 772114f76c..5109c36304 100644 --- a/engines/titanic/game/pickup/pick_up_lemon.cpp +++ b/engines/titanic/game/pickup/pick_up_lemon.cpp @@ -21,9 +21,15 @@ */ #include "titanic/game/pickup/pick_up_lemon.h" +#include "titanic/core/project_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CPickUpLemon, CPickUp) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + void CPickUpLemon::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CPickUp::save(file, indent); @@ -34,4 +40,23 @@ void CPickUpLemon::load(SimpleFile *file) { CPickUp::load(file); } +bool CPickUpLemon::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + return true; +} + +bool CPickUpLemon::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (!checkStartDragging(msg)) + return true; + if (!_enabled) + return false; + + CVisibleMsg visibleMsg; + visibleMsg.execute("Lemon"); + CPassOnDragStartMsg passMsg(msg->_mousePos, 1); + passMsg.execute("Lemon"); + + msg->_dragItem = getRoot()->findByName("Lemon"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pickup/pick_up_lemon.h b/engines/titanic/game/pickup/pick_up_lemon.h index 0312c71012..c196acdeaf 100644 --- a/engines/titanic/game/pickup/pick_up_lemon.h +++ b/engines/titanic/game/pickup/pick_up_lemon.h @@ -28,6 +28,9 @@ namespace Titanic { class CPickUpLemon : public CPickUp { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/pickup/pick_up_speech_centre.cpp b/engines/titanic/game/pickup/pick_up_speech_centre.cpp index 0b9a8d2c48..5e99c0a3b7 100644 --- a/engines/titanic/game/pickup/pick_up_speech_centre.cpp +++ b/engines/titanic/game/pickup/pick_up_speech_centre.cpp @@ -21,9 +21,16 @@ */ #include "titanic/game/pickup/pick_up_speech_centre.h" +#include "titanic/core/project_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CPickUpSpeechCentre, CPickUp) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + void CPickUpSpeechCentre::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CPickUp::save(file, indent); @@ -34,4 +41,33 @@ void CPickUpSpeechCentre::load(SimpleFile *file) { CPickUp::load(file); } +bool CPickUpSpeechCentre::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + return true; +} + +bool CPickUpSpeechCentre::StatusChangeMsg(CStatusChangeMsg *msg) { + _enabled = msg->_newStatus == 1; + return true; +} + +bool CPickUpSpeechCentre::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (checkStartDragging(msg)) { + if (_enabled) { + CVisibleMsg visibleMsg; + visibleMsg.execute("SpeechCentre"); + CPassOnDragStartMsg passMsg(msg->_mousePos, 1); + passMsg.execute("SpeechCentre"); + + msg->_dragItem = getRoot()->findByName("SpeechCentre"); + + CActMsg actMsg("PlayerGetsSpeechCentre"); + actMsg.execute("SeasonalAdjust"); + } else { + petDisplayMessage("You can't pick this up on account of it being stuck to the branch."); + } + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pickup/pick_up_speech_centre.h b/engines/titanic/game/pickup/pick_up_speech_centre.h index 29dce04fb3..81ee0b5d77 100644 --- a/engines/titanic/game/pickup/pick_up_speech_centre.h +++ b/engines/titanic/game/pickup/pick_up_speech_centre.h @@ -28,6 +28,10 @@ namespace Titanic { class CPickUpSpeechCentre : public CPickUp { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/pickup/pick_up_vis_centre.cpp b/engines/titanic/game/pickup/pick_up_vis_centre.cpp index 796e46778c..baf1763d09 100644 --- a/engines/titanic/game/pickup/pick_up_vis_centre.cpp +++ b/engines/titanic/game/pickup/pick_up_vis_centre.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CPickUpVisCentre, CPickUp) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + void CPickUpVisCentre::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CPickUp::save(file, indent); @@ -34,4 +39,22 @@ void CPickUpVisCentre::load(SimpleFile *file) { CPickUp::load(file); } + +bool CPickUpVisCentre::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + return true; +} + +bool CPickUpVisCentre::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (!checkStartDragging(msg) || !_enabled) + return false; + + setVisible(false); + CVisibleMsg visibleMsg; + visibleMsg.execute("VisionCentre"); + msg->execute("VisionCentre"); + CActMsg actMsg("PlayerTakesVisCentre"); + actMsg.execute("Barbot"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/pickup/pick_up_vis_centre.h b/engines/titanic/game/pickup/pick_up_vis_centre.h index 4f808f73c5..a5f59211d3 100644 --- a/engines/titanic/game/pickup/pick_up_vis_centre.h +++ b/engines/titanic/game/pickup/pick_up_vis_centre.h @@ -28,6 +28,9 @@ namespace Titanic { class CPickUpVisCentre : public CPickUp { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/placeholder/bar_shelf_vis_centre.cpp b/engines/titanic/game/placeholder/bar_shelf_vis_centre.cpp index a8a33fe1b1..6e5037f237 100644 --- a/engines/titanic/game/placeholder/bar_shelf_vis_centre.cpp +++ b/engines/titanic/game/placeholder/bar_shelf_vis_centre.cpp @@ -24,16 +24,44 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CBarShelfVisCentre, CPlaceHolder) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + void CBarShelfVisCentre::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); - CPlaceHolderItem::save(file, indent); + file->writeNumberLine(_flag, indent); + CPlaceHolder::save(file, indent); } void CBarShelfVisCentre::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); - CPlaceHolderItem::load(file); + _flag = file->readNumber(); + CPlaceHolder::load(file); } +bool CBarShelfVisCentre::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!_flag) { + CActMsg actMsg("ClickOnVision"); + actMsg.execute("Barbot"); + addTimer(3000); + _flag = true; + } + + return true; +} + +bool CBarShelfVisCentre::TimerMsg(CTimerMsg *msg) { + _flag = false; + return true; +} + +bool CBarShelfVisCentre::EnterViewMsg(CEnterViewMsg *msg) { + _flag = false; + return true; +} + + } // End of namespace Titanic diff --git a/engines/titanic/game/placeholder/bar_shelf_vis_centre.h b/engines/titanic/game/placeholder/bar_shelf_vis_centre.h index a53ef2633f..8ad3dcb8d1 100644 --- a/engines/titanic/game/placeholder/bar_shelf_vis_centre.h +++ b/engines/titanic/game/placeholder/bar_shelf_vis_centre.h @@ -23,16 +23,20 @@ #ifndef TITANIC_BAR_SHELF_VIS_CENTRE_H #define TITANIC_BAR_SHELF_VIS_CENTRE_H -#include "titanic/game/placeholder/place_holder_item.h" +#include "titanic/game/placeholder/place_holder.h" namespace Titanic { -class CBarShelfVisCentre : public CPlaceHolderItem { +class CBarShelfVisCentre : public CPlaceHolder { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool TimerMsg(CTimerMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); private: - int _value; + bool _flag; public: CLASSDEF; - CBarShelfVisCentre() : CPlaceHolderItem(), _value(0) {} + CBarShelfVisCentre() : CPlaceHolder(), _flag(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/placeholder/lemon_on_bar.cpp b/engines/titanic/game/placeholder/lemon_on_bar.cpp index 08d686e81a..e9cf6a309a 100644 --- a/engines/titanic/game/placeholder/lemon_on_bar.cpp +++ b/engines/titanic/game/placeholder/lemon_on_bar.cpp @@ -24,16 +24,29 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CLemonOnBar, CPlaceHolder) + ON_MESSAGE(VisibleMsg) +END_MESSAGE_MAP() + void CLemonOnBar::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writePoint(_pos1, indent); - CPlaceHolderItem::save(file, indent); + file->writePoint(_lemonPos, indent); + CPlaceHolder::save(file, indent); } void CLemonOnBar::load(SimpleFile *file) { file->readNumber(); - _pos1 = file->readPoint(); - CPlaceHolderItem::load(file); + _lemonPos = file->readPoint(); + CPlaceHolder::load(file); +} + +bool CLemonOnBar::VisibleMsg(CVisibleMsg *msg) { + setVisible(msg->_visible); + if (msg->_visible) + setPosition(_lemonPos); + else + setPosition(Point(0, 0)); + return true; } } // End of namespace Titanic diff --git a/engines/titanic/game/placeholder/lemon_on_bar.h b/engines/titanic/game/placeholder/lemon_on_bar.h index 92dd54c49b..c6512ced67 100644 --- a/engines/titanic/game/placeholder/lemon_on_bar.h +++ b/engines/titanic/game/placeholder/lemon_on_bar.h @@ -23,13 +23,15 @@ #ifndef TITANIC_LEMON_ON_BAR_H #define TITANIC_LEMON_ON_BAR_H -#include "titanic/game/placeholder/place_holder_item.h" +#include "titanic/game/placeholder/place_holder.h" namespace Titanic { -class CLemonOnBar : public CPlaceHolderItem { +class CLemonOnBar : public CPlaceHolder { + DECLARE_MESSAGE_MAP; + bool VisibleMsg(CVisibleMsg *msg); private: - Point _pos1; + Point _lemonPos; public: CLASSDEF; diff --git a/engines/titanic/game/call_pellerator.cpp b/engines/titanic/game/placeholder/place_holder.cpp index 0ea48131b1..ae42cabc29 100644 --- a/engines/titanic/game/call_pellerator.cpp +++ b/engines/titanic/game/placeholder/place_holder.cpp @@ -20,18 +20,27 @@ * */ -#include "titanic/game/call_pellerator.h" +#include "titanic/game/placeholder/place_holder.h" namespace Titanic { -void CCallPellerator::save(SimpleFile *file, int indent) { +BEGIN_MESSAGE_MAP(CPlaceHolder, CGameObject) + ON_MESSAGE(VisibleMsg) +END_MESSAGE_MAP() + +void CPlaceHolder::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); } -void CCallPellerator::load(SimpleFile *file) { +void CPlaceHolder::load(SimpleFile *file) { file->readNumber(); CGameObject::load(file); } +bool CPlaceHolder::VisibleMsg(CVisibleMsg *msg) { + setVisible(msg->_visible); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/placeholder/place_holder_item.h b/engines/titanic/game/placeholder/place_holder.h index de04a64bf7..b1aa041710 100644 --- a/engines/titanic/game/placeholder/place_holder_item.h +++ b/engines/titanic/game/placeholder/place_holder.h @@ -27,7 +27,9 @@ namespace Titanic { -class CPlaceHolderItem : public CGameObject { +class CPlaceHolder : public CGameObject { + DECLARE_MESSAGE_MAP; + bool VisibleMsg(CVisibleMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/placeholder/tv_on_bar.cpp b/engines/titanic/game/placeholder/tv_on_bar.cpp index efbbe50461..710b5a346e 100644 --- a/engines/titanic/game/placeholder/tv_on_bar.cpp +++ b/engines/titanic/game/placeholder/tv_on_bar.cpp @@ -24,16 +24,30 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CTVOnBar, CPlaceHolder) + ON_MESSAGE(VisibleMsg) +END_MESSAGE_MAP() + void CTVOnBar::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writePoint(_pos1, indent); - CPlaceHolderItem::save(file, indent); + file->writePoint(_tvPos, indent); + CPlaceHolder::save(file, indent); } void CTVOnBar::load(SimpleFile *file) { file->readNumber(); - _pos1 = file->readPoint(); - CPlaceHolderItem::load(file); + _tvPos = file->readPoint(); + CPlaceHolder::load(file); +} + +bool CTVOnBar::VisibleMsg(CVisibleMsg *msg) { + setVisible(msg->_visible); + if (msg->_visible) + setPosition(_tvPos); + else + setPosition(Point(0, 0)); + + return true; } } // End of namespace Titanic diff --git a/engines/titanic/game/placeholder/tv_on_bar.h b/engines/titanic/game/placeholder/tv_on_bar.h index d41d972e73..0157bc8764 100644 --- a/engines/titanic/game/placeholder/tv_on_bar.h +++ b/engines/titanic/game/placeholder/tv_on_bar.h @@ -23,13 +23,15 @@ #ifndef TITANIC_TV_ON_BAR_H #define TITANIC_TV_ON_BAR_H -#include "titanic/game/placeholder/place_holder_item.h" +#include "titanic/game/placeholder/place_holder.h" namespace Titanic { -class CTVOnBar : public CPlaceHolderItem { +class CTVOnBar : public CPlaceHolder { + DECLARE_MESSAGE_MAP; + bool VisibleMsg(CVisibleMsg *msg); private: - Point _pos1; + Point _tvPos; public: CLASSDEF; diff --git a/engines/titanic/game/play_music_button.cpp b/engines/titanic/game/play_music_button.cpp index 8066739f10..21fd3c336a 100644 --- a/engines/titanic/game/play_music_button.cpp +++ b/engines/titanic/game/play_music_button.cpp @@ -21,23 +21,58 @@ */ #include "titanic/game/play_music_button.h" +#include "titanic/sound/music_room.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CPlayMusicButton, CBackground) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(FrameMsg) +END_MESSAGE_MAP() + void CPlayMusicButton::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); - file->writeNumberLine(_fieldE4, indent); + file->writeNumberLine(_flag, indent); + file->writeNumberLine(_ticks, indent); CBackground::save(file, indent); } void CPlayMusicButton::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); - _fieldE4 = file->readNumber(); + _flag = file->readNumber(); + _ticks = file->readNumber(); CBackground::load(file); } +bool CPlayMusicButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CMusicRoom *musicRoom = getMusicRoom(); + if (_flag) { + musicRoom->stopMusic(); + stopMovie(); + loadFrame(0); + _flag = false; + } else { + musicRoom->startMusic(100); + playMovie(MOVIE_REPEAT); + _ticks = getTicksCount(); + _flag = true; + } + + return true; +} + +bool CPlayMusicButton::FrameMsg(CFrameMsg *msg) { + if (_flag && !CMusicRoom::_musicHandler->poll()) { + CMusicRoom *musicRoom = getMusicRoom(); + musicRoom->stopMusic(); + stopMovie(); + loadFrame(0); + _flag = false; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/play_music_button.h b/engines/titanic/game/play_music_button.h index 4e3474181c..824b372bf9 100644 --- a/engines/titanic/game/play_music_button.h +++ b/engines/titanic/game/play_music_button.h @@ -28,12 +28,15 @@ namespace Titanic { class CPlayMusicButton : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool FrameMsg(CFrameMsg *msg); public: - int _fieldE0; - int _fieldE4; + bool _flag; + uint _ticks; public: CLASSDEF; - CPlayMusicButton() : CBackground(), _fieldE0(0), _fieldE4(0) {} + CPlayMusicButton() : CBackground(), _flag(false), _ticks(0) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/play_on_act.cpp b/engines/titanic/game/play_on_act.cpp index e1ef1201c6..9c368c335d 100644 --- a/engines/titanic/game/play_on_act.cpp +++ b/engines/titanic/game/play_on_act.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CPlayOnAct, CBackground) + ON_MESSAGE(ActMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + void CPlayOnAct::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CBackground::save(file, indent); @@ -34,4 +39,20 @@ void CPlayOnAct::load(SimpleFile *file) { CBackground::load(file); } +bool CPlayOnAct::ActMsg(CActMsg *msg) { + if (msg->_action == "PlayMovie") { + setVisible(true); + playMovie(0); + } else if (msg->_action == "PlayToEnd") { + setVisible(true); + playMovie(MOVIE_GAMESTATE); + } + + return true; +} + +bool CPlayOnAct::LeaveViewMsg(CLeaveViewMsg *msg) { + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/play_on_act.h b/engines/titanic/game/play_on_act.h index 197e647943..72615f2fc4 100644 --- a/engines/titanic/game/play_on_act.h +++ b/engines/titanic/game/play_on_act.h @@ -28,6 +28,9 @@ namespace Titanic { class CPlayOnAct : public CBackground { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/port_hole.cpp b/engines/titanic/game/port_hole.cpp index f3c447f443..25807b1b1d 100644 --- a/engines/titanic/game/port_hole.cpp +++ b/engines/titanic/game/port_hole.cpp @@ -24,26 +24,72 @@ namespace Titanic { -CPortHole::CPortHole() : CGameObject(), _fieldBC(0), - _string1("b#47.wav"), _string2("b#46.wav") { +BEGIN_MESSAGE_MAP(CPortHole, CGameObject) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + +CPortHole::CPortHole() : CGameObject(), _open(false), + _closeSoundName("b#47.wav"), _openSoundName("b#46.wav") { } void CPortHole::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); - file->writeQuotedLine(_string1, indent); - file->writeQuotedLine(_string2, indent); + file->writeNumberLine(_open, indent); + file->writeQuotedLine(_closeSoundName, indent); + file->writeQuotedLine(_openSoundName, indent); CGameObject::save(file, indent); } void CPortHole::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); - _string1 = file->readString(); - _string2 = file->readString(); + _open = file->readNumber(); + _closeSoundName = file->readString(); + _openSoundName = file->readString(); CGameObject::load(file); } +bool CPortHole::ActMsg(CActMsg *msg) { + if (msg->_action == "TogglePortHole") { + if (_open) { + playMovie(14, 26, MOVIE_NOTIFY_OBJECT); + playSound(_closeSoundName); + _open = false; + } else { + setVisible(true); + playMovie(1, 13, 0); + playSound(_openSoundName); + _open = true; + } + } + + return true; +} + +bool CPortHole::MovieEndMsg(CMovieEndMsg *msg) { + _open = false; + setVisible(false); + return true; +} + +bool CPortHole::LeaveViewMsg(CLeaveViewMsg *msg) { + if (_open) { + playSound(_closeSoundName); + playMovie(14, 26, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _open = false; + } + + return true; +} + +bool CPortHole::EnterViewMsg(CEnterViewMsg *msg) { + setVisible(false); + _open = false; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/port_hole.h b/engines/titanic/game/port_hole.h index 7bba18d12a..9f1997a517 100644 --- a/engines/titanic/game/port_hole.h +++ b/engines/titanic/game/port_hole.h @@ -28,9 +28,14 @@ namespace Titanic { class CPortHole : public CGameObject { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); private: - int _fieldBC; - CString _string1, _string2; + bool _open; + CString _closeSoundName, _openSoundName; public: CLASSDEF; CPortHole(); diff --git a/engines/titanic/game/record_phonograph_button.cpp b/engines/titanic/game/record_phonograph_button.cpp index f022957dbb..1ffaec4228 100644 --- a/engines/titanic/game/record_phonograph_button.cpp +++ b/engines/titanic/game/record_phonograph_button.cpp @@ -24,16 +24,44 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CRecordPhonographButton, CBackground) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(PhonographStopMsg) +END_MESSAGE_MAP() + void CRecordPhonographButton::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_active, indent); CBackground::save(file, indent); } void CRecordPhonographButton::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _active = file->readNumber(); CBackground::load(file); } +bool CRecordPhonographButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CPhonographRecordMsg recordMsg; + recordMsg.execute(getParent()); + + if (recordMsg._value) { + playSound("z#58.wav"); + loadFrame(1); + _active = true; + } + + return true; +} + +bool CRecordPhonographButton::PhonographStopMsg(CPhonographStopMsg *msg) { + if (_active) { + playSound("z#57.wav"); + loadFrame(0); + _active = false; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/record_phonograph_button.h b/engines/titanic/game/record_phonograph_button.h index 3383c01e31..985a4c4576 100644 --- a/engines/titanic/game/record_phonograph_button.h +++ b/engines/titanic/game/record_phonograph_button.h @@ -28,11 +28,14 @@ namespace Titanic { class CRecordPhonographButton : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool PhonographStopMsg(CPhonographStopMsg *msg); public: - int _value; + bool _active; public: CLASSDEF; - CRecordPhonographButton() : CBackground(), _value(0) {} + CRecordPhonographButton() : CBackground(), _active(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/replacement_ear.cpp b/engines/titanic/game/replacement_ear.cpp index 1f9960365d..e8bd384207 100644 --- a/engines/titanic/game/replacement_ear.cpp +++ b/engines/titanic/game/replacement_ear.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CReplacementEar, CBackground) + ON_MESSAGE(VisibleMsg) +END_MESSAGE_MAP() + void CReplacementEar::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CBackground::save(file, indent); @@ -34,4 +38,11 @@ void CReplacementEar::load(SimpleFile *file) { CBackground::load(file); } +bool CReplacementEar::VisibleMsg(CVisibleMsg *msg) { + setVisible(true); + playMovie(MOVIE_GAMESTATE); + playSound("z#64.wav"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/replacement_ear.h b/engines/titanic/game/replacement_ear.h index 0d282b7fb4..7775a9631a 100644 --- a/engines/titanic/game/replacement_ear.h +++ b/engines/titanic/game/replacement_ear.h @@ -28,6 +28,8 @@ namespace Titanic { class CReplacementEar : public CBackground { + DECLARE_MESSAGE_MAP; + bool VisibleMsg(CVisibleMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/reserved_table.cpp b/engines/titanic/game/reserved_table.cpp index a600190709..734d53af45 100644 --- a/engines/titanic/game/reserved_table.cpp +++ b/engines/titanic/game/reserved_table.cpp @@ -21,22 +21,55 @@ */ #include "titanic/game/reserved_table.h" +#include "titanic/core/room_item.h" +#include "titanic/core/view_item.h" +#include "titanic/npcs/maitre_d.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CReservedTable, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(PlayerTriesRestaurantTableMsg) +END_MESSAGE_MAP() + void CReservedTable::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value1, indent); - file->writeNumberLine(_value2, indent); + file->writeNumberLine(_flag, indent); + file->writeNumberLine(_tableId, indent); CGameObject::save(file, indent); } void CReservedTable::load(SimpleFile *file) { file->readNumber(); - _value1 = file->readNumber(); - _value2 = file->readNumber(); + _flag = file->readNumber(); + _tableId = file->readNumber(); CGameObject::load(file); } +bool CReservedTable::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!_flag) { + CPlayerTriesRestaurantTableMsg tryMsg(_tableId, 0); + tryMsg.execute(findRoom(), CReservedTable::_type, MSGFLAG_CLASS_DEF | MSGFLAG_SCAN); + } + + return true; +} + +bool CReservedTable::PlayerTriesRestaurantTableMsg(CPlayerTriesRestaurantTableMsg *msg) { + if (msg->_tableId == _tableId) { + if (!msg->_result) { + CMaitreD *maitreD = dynamic_cast<CMaitreD *>(findRoomObject("MaitreD")); + startTalking(maitreD, 118, maitreD->findView()); + msg->_result = true; + } + + _cursorId = CURSOR_INVALID; + _flag = true; + return true; + } else { + return false; + } +} + } // End of namespace Titanic diff --git a/engines/titanic/game/reserved_table.h b/engines/titanic/game/reserved_table.h index a3532c7d14..bc037ae3d9 100644 --- a/engines/titanic/game/reserved_table.h +++ b/engines/titanic/game/reserved_table.h @@ -28,11 +28,15 @@ namespace Titanic { class CReservedTable : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool PlayerTriesRestaurantTableMsg(CPlayerTriesRestaurantTableMsg *msg); public: - int _value1, _value2; + bool _flag; + int _tableId; public: CLASSDEF; - CReservedTable() : CGameObject(), _value1(0), _value2(0) {} + CReservedTable() : CGameObject(), _flag(false), _tableId(0) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/restaurant_cylinder_holder.cpp b/engines/titanic/game/restaurant_cylinder_holder.cpp index d70009f151..8726d1a925 100644 --- a/engines/titanic/game/restaurant_cylinder_holder.cpp +++ b/engines/titanic/game/restaurant_cylinder_holder.cpp @@ -24,20 +24,29 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CRestaurantCylinderHolder, CDropTarget) + ON_MESSAGE(EjectCylinderMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(QueryCylinderHolderMsg) + ON_MESSAGE(QueryCylinderNameMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + CRestaurantCylinderHolder::CRestaurantCylinderHolder() : CDropTarget(), _field118(0), _field11C(0), _field12C(0), _field130(0), - _string6("z#61.wav"), _field140(1) { + _ejectSoundName("z#61.wav"), _defaultCursorId(CURSOR_ARROW) { } void CRestaurantCylinderHolder::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_field118, indent); file->writeNumberLine(_field11C, indent); - file->writeQuotedLine(_string5, indent); + file->writeQuotedLine(_target, indent); file->writeNumberLine(_field12C, indent); file->writeNumberLine(_field130, indent); - file->writeQuotedLine(_string6, indent); - file->writeNumberLine(_field140, indent); + file->writeQuotedLine(_ejectSoundName, indent); + file->writeNumberLine(_defaultCursorId, indent); CDropTarget::save(file, indent); } @@ -46,13 +55,98 @@ void CRestaurantCylinderHolder::load(SimpleFile *file) { file->readNumber(); _field118 = file->readNumber(); _field11C = file->readNumber(); - _string5 = file->readString(); + _target = file->readString(); _field12C = file->readNumber(); _field130 = file->readNumber(); - _string6 = file->readString(); - _field140 = file->readNumber(); + _ejectSoundName = file->readString(); + _defaultCursorId = (CursorId)file->readNumber(); CDropTarget::load(file); } +bool CRestaurantCylinderHolder::EjectCylinderMsg(CEjectCylinderMsg *msg) { + _field11C = true; + bool hasCylinder = findByName("Phonograph Cylinder") != nullptr; + + if (_field118) { + playClip(hasCylinder ? "CloseHolder_Full" : "CloseHolder_Empty", + MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _fieldF4 = 1; + } else { + playClip(hasCylinder ? "OpenHolder_Full" : "OpenHolder_Empty", + MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + + playSound(_ejectSoundName, 50); + return true; +} + +bool CRestaurantCylinderHolder::EnterViewMsg(CEnterViewMsg *msg) { + if (_field118) { + CTreeItem *cylinder = findByName("Phonograph Cylinder", true); + if (cylinder) { + loadFrame(_dropFrame); + _cursorId = _dropCursorId; + } else { + loadFrame(_dragFrame); + _cursorId = _dragCursorId; + } + } else { + loadFrame(_field130); + _cursorId = _defaultCursorId; + } + + return true; +} + +bool CRestaurantCylinderHolder::MovieEndMsg(CMovieEndMsg *msg) { + _field11C = false; + if (_field118) { + _field118 = false; + _cursorId = _defaultCursorId; + + CPhonographReadyToPlayMsg readyMsg; + readyMsg.execute(_target); + } else { + _field118 = true; + _fieldF4 = false; + _cursorId = findByName("Phonograph Cylinder") ? _dropCursorId : _dragCursorId; + } + + CCylinderHolderReadyMsg holderMsg; + holderMsg.execute(_target); + return true; +} + +bool CRestaurantCylinderHolder::QueryCylinderHolderMsg(CQueryCylinderHolderMsg *msg) { + CNamedItem *cylinder = findByName("Phonograph Cylinder", true); + + msg->_value1 = _field118; + if (cylinder) { + msg->_value2 = 1; + msg->_target = cylinder; + } + + return true; +} + +bool CRestaurantCylinderHolder::QueryCylinderNameMsg(CQueryCylinderNameMsg *msg) { + CNamedItem *cylinder = findByName("Phonograph Cylinder", true); + + if (cylinder) { + CQueryCylinderMsg queryMsg; + queryMsg.execute(cylinder); + msg->_name = queryMsg._name; + } + + return true; +} + +bool CRestaurantCylinderHolder::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (_field118) + return CDropTarget::MouseDragStartMsg(msg); + else + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/restaurant_cylinder_holder.h b/engines/titanic/game/restaurant_cylinder_holder.h index 3aa979b0a5..cd0b0783bd 100644 --- a/engines/titanic/game/restaurant_cylinder_holder.h +++ b/engines/titanic/game/restaurant_cylinder_holder.h @@ -28,14 +28,21 @@ namespace Titanic { class CRestaurantCylinderHolder : public CDropTarget { + DECLARE_MESSAGE_MAP; + bool EjectCylinderMsg(CEjectCylinderMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool QueryCylinderHolderMsg(CQueryCylinderHolderMsg *msg); + bool QueryCylinderNameMsg(CQueryCylinderNameMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); private: int _field118; int _field11C; - CString _string5; + CString _target; int _field12C; int _field130; - CString _string6; - int _field140; + CString _ejectSoundName; + CursorId _defaultCursorId; public: CLASSDEF; CRestaurantCylinderHolder(); diff --git a/engines/titanic/game/restaurant_phonograph.cpp b/engines/titanic/game/restaurant_phonograph.cpp index 83a4ac3e71..881079e020 100644 --- a/engines/titanic/game/restaurant_phonograph.cpp +++ b/engines/titanic/game/restaurant_phonograph.cpp @@ -21,9 +21,20 @@ */ #include "titanic/game/restaurant_phonograph.h" +#include "titanic/core/room_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CRestaurantPhonograph, CPhonograph) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(PhonographPlayMsg) + ON_MESSAGE(PhonographStopMsg) + ON_MESSAGE(PhonographReadyToPlayMsg) + ON_MESSAGE(EjectCylinderMsg) + ON_MESSAGE(QueryPhonographState) + ON_MESSAGE(LockPhonographMsg) +END_MESSAGE_MAP() + CRestaurantPhonograph::CRestaurantPhonograph() : CPhonograph(), _fieldF8(1), _field114(0) {} @@ -48,4 +59,89 @@ void CRestaurantPhonograph::load(SimpleFile *file) { CPhonograph::load(file); } +bool CRestaurantPhonograph::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!_fieldF8 && !_fieldE0) { + CQueryCylinderHolderMsg holderMsg; + holderMsg.execute(this); + + if (!holderMsg._value1) { + CPhonographPlayMsg playMsg; + playMsg.execute(this); + } else if (holderMsg._value2) { + CEjectCylinderMsg ejectMsg; + ejectMsg.execute(this); + + _fieldE8 = true; + if (_field114) { + loadFrame(_fieldEC); + playSound(_ejectSoundName); + } + } + } + + return true; +} + +bool CRestaurantPhonograph::PhonographPlayMsg(CPhonographPlayMsg *msg) { + if (_fieldE0) { + if (findView() == getView() && (!_fieldE8 || !_field114)) { + loadFrame(_fieldEC); + playSound(_ejectSoundName); + } + + CQueryCylinderNameMsg nameMsg; + nameMsg.execute(this); + CRestaurantMusicChanged musicMsg(nameMsg._name); + musicMsg.execute(findRoom()); + } else { + loadFrame(_fieldF0); + } + + return true; +} + +bool CRestaurantPhonograph::PhonographStopMsg(CPhonographStopMsg *msg) { + bool flag = _fieldE0; + CPhonograph::PhonographStopMsg(msg); + + if (_fieldE0) { + loadFrame(_fieldF0); + if (flag) + playSound(_string3); + } else { + loadFrame(_fieldEC); + } + + return true; +} + +bool CRestaurantPhonograph::PhonographReadyToPlayMsg(CPhonographReadyToPlayMsg *msg) { + if (_fieldE8) { + CPhonographPlayMsg playMsg; + playMsg.execute(this); + _fieldE8 = false; + } + + return true; +} + +bool CRestaurantPhonograph::EjectCylinderMsg(CEjectCylinderMsg *msg) { + if (_fieldE0) { + CPhonographStopMsg stopMsg; + stopMsg.execute(this); + } + + return true; +} + +bool CRestaurantPhonograph::QueryPhonographState(CQueryPhonographState *msg) { + msg->_value = _fieldF8; + return true; +} + +bool CRestaurantPhonograph::LockPhonographMsg(CLockPhonographMsg *msg) { + _fieldF8 = msg->_value; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/restaurant_phonograph.h b/engines/titanic/game/restaurant_phonograph.h index 710a2edd73..9661df0dfb 100644 --- a/engines/titanic/game/restaurant_phonograph.h +++ b/engines/titanic/game/restaurant_phonograph.h @@ -28,9 +28,17 @@ namespace Titanic { class CRestaurantPhonograph : public CPhonograph { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool PhonographPlayMsg(CPhonographPlayMsg *msg); + bool PhonographStopMsg(CPhonographStopMsg *msg); + bool PhonographReadyToPlayMsg(CPhonographReadyToPlayMsg *msg); + bool EjectCylinderMsg(CEjectCylinderMsg *msg); + bool QueryPhonographState(CQueryPhonographState *msg); + bool LockPhonographMsg(CLockPhonographMsg *msg); private: int _fieldF8; - CString _string2; + CString _ejectSoundName; CString _string3; int _field114; public: diff --git a/engines/titanic/game/sauce_dispensor.cpp b/engines/titanic/game/sauce_dispensor.cpp index 8dc818d917..adc0b828a2 100644 --- a/engines/titanic/game/sauce_dispensor.cpp +++ b/engines/titanic/game/sauce_dispensor.cpp @@ -21,9 +21,20 @@ */ #include "titanic/game/sauce_dispensor.h" +#include "titanic/carry/chicken.h" +#include "titanic/carry/glass.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CSauceDispensor, CBackground) + ON_MESSAGE(Use) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(StatusChangeMsg) +END_MESSAGE_MAP() + CSauceDispensor::CSauceDispensor() : CBackground(), _fieldEC(0), _fieldF0(0), _field104(0), _field108(0) { } @@ -54,4 +65,105 @@ void CSauceDispensor::load(SimpleFile *file) { CBackground::load(file); } +bool CSauceDispensor::Use(CUse *msg) { + CVisibleMsg visibleMsg(true); + + if (msg->_item->isEquals("Chicken")) { + CChicken *chicken = dynamic_cast<CChicken *>(msg->_item); + _field104 = true; + if (_fieldF0) { + playSound("b#15.wav", 50); + + if (chicken->_string6 != "None") { + petDisplayMessage(1, "This foodstuff is already sufficiently garnished."); + msg->execute("Chicken"); + } else { + setVisible(true); + if (chicken->_field12C) { + playMovie(_pos1.x, _pos1.y, MOVIE_NOTIFY_OBJECT); + } else { + CActMsg actMsg(_string3); + actMsg.execute("Chicken"); + playMovie(_pos2.x, _pos2.y, MOVIE_NOTIFY_OBJECT); + } + } + + if (_fieldF0) + return true; + } + + CMovieEndMsg endMsg(0, 0); + endMsg.execute(this); + playSound("z#120.wav"); + + petDisplayMessage(1, "Sadly, this dispenser is currently empty."); + } else if (msg->_item->isEquals("BeerGlass")) { + CGlass *glass = dynamic_cast<CGlass *>(msg->_item); + _field108 = true; + + if (_field104 || _fieldF0) { + petAddToInventory(); + } else if (glass->_string6 != "None") { + visibleMsg.execute("BeerGlass"); + } else if (_fieldEC) { + glass->setPosition(Point( + _bounds.left + (_bounds.width() / 2) - (glass->_bounds.width() / 2), + 300)); + setVisible(true); + + CActMsg actMsg(_string3); + actMsg.execute("BeerGlass"); + } + } + + return true; +} + +bool CSauceDispensor::MovieEndMsg(CMovieEndMsg *msg) { + setVisible(false); + _fieldEC = false; + + CActMsg actMsg("GoToPET"); + if (_field104) + actMsg.execute("Chicken"); + if (_field108) + actMsg.execute("BeerGlass"); + + _field104 = false; + _field108 = false; + return true; +} + +bool CSauceDispensor::ActMsg(CActMsg *msg) { + if (msg->_action == "StarlingsDead") + _fieldF0 = true; + + return true; +} + +bool CSauceDispensor::LeaveViewMsg(CLeaveViewMsg *msg) { + setVisible(false); + loadFrame(0); + + if (_field108) { + CGameObject *glass = findRoomObject("Beerglass"); + if (glass) + glass->petAddToInventory(); + } + + _field104 = false; + _field108 = false; + return true; +} + +bool CSauceDispensor::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + petDisplayMessage(1, "Please place food source beneath dispenser for sauce delivery."); + return true; +} + +bool CSauceDispensor::StatusChangeMsg(CStatusChangeMsg *msg) { + petDisplayMessage(1, "Please place food source beneath dispenser for sauce delivery."); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sauce_dispensor.h b/engines/titanic/game/sauce_dispensor.h index aa177050d5..f8021f368b 100644 --- a/engines/titanic/game/sauce_dispensor.h +++ b/engines/titanic/game/sauce_dispensor.h @@ -28,6 +28,13 @@ namespace Titanic { class CSauceDispensor : public CBackground { + DECLARE_MESSAGE_MAP; + bool Use(CUse *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool ActMsg(CActMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); public: CString _string3; int _fieldEC; diff --git a/engines/titanic/game/search_point.cpp b/engines/titanic/game/search_point.cpp index f60a3132b7..bbe923267a 100644 --- a/engines/titanic/game/search_point.cpp +++ b/engines/titanic/game/search_point.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CSearchPoint, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CSearchPoint::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value, indent); @@ -36,4 +40,21 @@ void CSearchPoint::load(SimpleFile *file) { CGameObject::load(file); } +bool CSearchPoint::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_value > 0) { + CGameObject *child = dynamic_cast<CGameObject *>(getFirstChild()); + if (child) { + child->petAddToInventory(); + CVisibleMsg visibleMsg(true); + visibleMsg.execute(child->getName()); + playSound("z#47.wav"); + } + + if (--_value == 0) + _cursorId = CURSOR_ARROW; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/search_point.h b/engines/titanic/game/search_point.h index 3c5639b104..421f272804 100644 --- a/engines/titanic/game/search_point.h +++ b/engines/titanic/game/search_point.h @@ -28,6 +28,8 @@ namespace Titanic { class CSearchPoint : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: int _value; public: diff --git a/engines/titanic/game/season_background.cpp b/engines/titanic/game/season_background.cpp index 1c63f3d892..20ad6aca1d 100644 --- a/engines/titanic/game/season_background.cpp +++ b/engines/titanic/game/season_background.cpp @@ -24,28 +24,113 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CSeasonBackground, CBackground) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(ChangeSeasonMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + CSeasonBackground::CSeasonBackground() : CBackground(), - _fieldE0(0), _fieldE4(0), _fieldE8(46), _fieldEC(0) { + _seasonNum(SEASON_SUMMER), _flag(false), _defaultFrame(46), _unused(0) { } void CSeasonBackground::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); - file->writeNumberLine(_fieldE4, indent); - file->writeNumberLine(_fieldE8, indent); - file->writeNumberLine(_fieldEC, indent); + file->writeNumberLine(_seasonNum, indent); + file->writeNumberLine(_flag, indent); + file->writeNumberLine(_defaultFrame, indent); + file->writeNumberLine(_unused, indent); CBackground::save(file, indent); } void CSeasonBackground::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); - _fieldE4 = file->readNumber(); - _fieldE8 = file->readNumber(); - _fieldEC = file->readNumber(); + _seasonNum = (Season)file->readNumber(); + _flag = file->readNumber(); + _defaultFrame = file->readNumber(); + _unused = file->readNumber(); CBackground::load(file); } +bool CSeasonBackground::EnterViewMsg(CEnterViewMsg *msg) { + loadFrame(_defaultFrame); + return true; +} + +bool CSeasonBackground::ChangeSeasonMsg(CChangeSeasonMsg *msg) { + _seasonNum = (Season)(((int)_seasonNum + 1) % 4); + + switch (_seasonNum) { + case SEASON_SUMMER: + playMovie(0, 45, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _defaultFrame = 45; + break; + + case SEASON_AUTUMN: + if (_flag) { + playMovie(232, 278, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _defaultFrame = 278; + } else { + playMovie(45, 91, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _defaultFrame = 91; + } + break; + + case SEASON_WINTER: + if (_flag) { + playMovie(278, 326, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _defaultFrame = 326; + } else { + CStatusChangeMsg changeMsg; + changeMsg._newStatus = 0; + changeMsg.execute("PickUpSpeechCentre"); + playMovie(91, 139, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _defaultFrame = 139; + } + break; + + case SEASON_SPRING: + if (_flag) { + playMovie(326, 417, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _defaultFrame = 417; + } else { + playMovie(139, 228, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _defaultFrame = 228; + } + break; + + default: + break; + } + + return true; +} + +bool CSeasonBackground::MovieEndMsg(CMovieEndMsg *msg) { + if (msg->_endFrame == _defaultFrame) { + CTurnOn onMsg; + onMsg.execute("SeasonalAdjust"); + } + + if (msg->_endFrame == 91 && !_flag) { + CStatusChangeMsg changeMsg; + changeMsg.execute("PickUpSpeechCentre"); + } + + return true; +} + +bool CSeasonBackground::ActMsg(CActMsg *msg) { + if (msg->_action == "PlayerGetsSpeechCentre") { + loadFrame(278); + _defaultFrame = 278; + _flag = true; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/season_background.h b/engines/titanic/game/season_background.h index f0fd2cdc63..d30fd7aedc 100644 --- a/engines/titanic/game/season_background.h +++ b/engines/titanic/game/season_background.h @@ -28,11 +28,16 @@ namespace Titanic { class CSeasonBackground : public CBackground { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool ChangeSeasonMsg(CChangeSeasonMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool ActMsg(CActMsg *msg); public: - int _fieldE0; - int _fieldE4; - int _fieldE8; - int _fieldEC; + Season _seasonNum; + bool _flag; + int _defaultFrame; + int _unused; public: CLASSDEF; CSeasonBackground(); diff --git a/engines/titanic/game/season_barrel.cpp b/engines/titanic/game/season_barrel.cpp index 9594396885..e08cdf323b 100644 --- a/engines/titanic/game/season_barrel.cpp +++ b/engines/titanic/game/season_barrel.cpp @@ -24,19 +24,38 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CSeasonBarrel, CBackground) + ON_MESSAGE(ChangeSeasonMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + void CSeasonBarrel::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); - file->writeNumberLine(_fieldE4, indent); + file->writeNumberLine(_unused, indent); + file->writeNumberLine(_startFrame, indent); CBackground::save(file, indent); } void CSeasonBarrel::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); - _fieldE4 = file->readNumber(); + _unused = file->readNumber(); + _startFrame = file->readNumber(); CBackground::load(file); } +bool CSeasonBarrel::ChangeSeasonMsg(CChangeSeasonMsg *msg) { + if (_startFrame >= 28) + _startFrame = 0; + + playMovie(_startFrame, _startFrame + 7, 0); + _startFrame += 7; + return true; +} + +bool CSeasonBarrel::EnterViewMsg(CEnterViewMsg *msg) { + loadFrame(_startFrame); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/season_barrel.h b/engines/titanic/game/season_barrel.h index f77864599d..6296b6f7b1 100644 --- a/engines/titanic/game/season_barrel.h +++ b/engines/titanic/game/season_barrel.h @@ -28,12 +28,15 @@ namespace Titanic { class CSeasonBarrel : public CBackground { + DECLARE_MESSAGE_MAP; + bool ChangeSeasonMsg(CChangeSeasonMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); public: - int _fieldE0; - int _fieldE4; + int _unused; + int _startFrame; public: CLASSDEF; - CSeasonBarrel() : CBackground(), _fieldE0(0), _fieldE4(7) {} + CSeasonBarrel() : CBackground(), _unused(0), _startFrame(7) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/seasonal_adjustment.cpp b/engines/titanic/game/seasonal_adjustment.cpp index 33a0ae89c5..1f1cb88afb 100644 --- a/engines/titanic/game/seasonal_adjustment.cpp +++ b/engines/titanic/game/seasonal_adjustment.cpp @@ -21,9 +21,20 @@ */ #include "titanic/game/seasonal_adjustment.h" +#include "titanic/core/project_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CSeasonalAdjustment, CBackground) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + void CSeasonalAdjustment::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldE0, indent); @@ -40,4 +51,86 @@ void CSeasonalAdjustment::load(SimpleFile *file) { CBackground::load(file); } +bool CSeasonalAdjustment::StatusChangeMsg(CStatusChangeMsg *msg) { + CChangeSeasonMsg changeMsg; + switch (stateGetSeason()) { + case SEASON_SUMMER: + changeMsg._season = "Summer"; + break; + case SEASON_AUTUMN: + changeMsg._season = "Autumn"; + break; + case SEASON_WINTER: + changeMsg._season = "Winter"; + break; + case SEASON_SPRING: + changeMsg._season = "Spring"; + break; + default: + break; + } + + changeMsg.execute(getRoot(), nullptr, MSGFLAG_SCAN); + return true; +} + +bool CSeasonalAdjustment::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + return true; +} + +bool CSeasonalAdjustment::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + playSound("z#42.wav"); + if (!_fieldE4) { + petDisplayMessage(1, "The Seasonal Adjustment switch is not operational at the present time."); + } else if (!_fieldE0) { + playMovie(0, 6, MOVIE_NOTIFY_OBJECT); + playMovie(6, 18, 0); + } + + return true; +} + +bool CSeasonalAdjustment::MovieEndMsg(CMovieEndMsg *msg) { + if (msg->_endFrame == 6) { + stateChangeSeason(); + CStatusChangeMsg changeMsg; + changeMsg.execute(this); + CTurnOff offMsg; + offMsg.execute(this); + offMsg.execute("LeftPanExit"); + offMsg.execute("RightPanExit"); + } + + return true; +} + +bool CSeasonalAdjustment::TurnOn(CTurnOn *msg) { + if (_fieldE0) { + _fieldE0 = false; + CTurnOn onMsg; + onMsg.execute("LeftPanExit"); + onMsg.execute("RightPanExit"); + } + + return true; +} + +bool CSeasonalAdjustment::TurnOff(CTurnOff *msg) { + _fieldE0 = true; + return true; +} + +bool CSeasonalAdjustment::ActMsg(CActMsg *msg) { + if (msg->_action == "PlayerGetsSpeechCentre") { + msg->execute("SeasonBackground"); + msg->execute("ArbGate"); + } else if (msg->_action == "EnableObject") { + _fieldE4 = true; + } else if (msg->_action == "DisableObject") { + _fieldE4 = false; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/seasonal_adjustment.h b/engines/titanic/game/seasonal_adjustment.h index 39132d614d..4b7ef3d4f6 100644 --- a/engines/titanic/game/seasonal_adjustment.h +++ b/engines/titanic/game/seasonal_adjustment.h @@ -28,7 +28,15 @@ namespace Titanic { class CSeasonalAdjustment : public CBackground { -public: + DECLARE_MESSAGE_MAP; + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool ActMsg(CActMsg *msg); +private: int _fieldE0; int _fieldE4; public: diff --git a/engines/titanic/game/service_elevator_window.cpp b/engines/titanic/game/service_elevator_window.cpp index 95b2735b37..b0cc53abb4 100644 --- a/engines/titanic/game/service_elevator_window.cpp +++ b/engines/titanic/game/service_elevator_window.cpp @@ -21,9 +21,19 @@ */ #include "titanic/game/service_elevator_window.h" +#include "titanic/core/room_item.h" +#include "titanic/npcs/doorbot.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CServiceElevatorWindow, CBackground) + ON_MESSAGE(ServiceElevatorFloorChangeMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + +static const int FACTORS[4] = { 0, 20, 100, 0 }; + CServiceElevatorWindow::CServiceElevatorWindow() : CBackground(), _fieldE0(0), _fieldE4(0), _fieldE8(0), _fieldEC(0) { } @@ -48,4 +58,57 @@ void CServiceElevatorWindow::load(SimpleFile *file) { CBackground::load(file); } +bool CServiceElevatorWindow::ServiceElevatorFloorChangeMsg(CServiceElevatorFloorChangeMsg *msg) { + if (getView() == findView()) { + CDoorbot *doorbot = dynamic_cast<CDoorbot *>(findRoom()->findByName("Doorbot")); + int val = (_fieldE8 && doorbot) ? 65 : 15; + CMovieClip *clip = _movieClips.findByName("Going Up"); + + if (!clip) + return true; + + int count = _endFrame - _startFrame; + setMovieFrameRate(1.0 * count / val); + + int startFrame = clip->_startFrame + count * FACTORS[msg->_value1] / 100; + int endFrame = clip->_startFrame + count * FACTORS[msg->_value2] / 100; + + if (_fieldE4) { + playMovie(startFrame, endFrame, MOVIE_NOTIFY_OBJECT); + } else { + playMovie(startFrame, endFrame, 0); + if (_fieldEC) + playClip("Into Space"); + } + } + + _fieldE0 = msg->_value2; + return true; +} + +bool CServiceElevatorWindow::MovieEndMsg(CMovieEndMsg *msg) { + CServiceElevatorMsg elevMsg(5); + elevMsg.execute(findRoom()->findByName("Service Elevator Entity")); + return true; +} + +bool CServiceElevatorWindow::EnterViewMsg(CEnterViewMsg *msg) { + if (_fieldEC) { + playClip("Fade Up"); + playMovie(1, 2, 0); + } else { + CMovieClip *clip = _movieClips.findByName("Going Up"); + + if (clip) { + int frameNum = clip->_startFrame + (clip->_endFrame - clip->_startFrame) + * FACTORS[_fieldE0] / 100; + loadFrame(frameNum); + } else { + loadFrame(0); + } + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/service_elevator_window.h b/engines/titanic/game/service_elevator_window.h index 4233b8405a..88e1663aba 100644 --- a/engines/titanic/game/service_elevator_window.h +++ b/engines/titanic/game/service_elevator_window.h @@ -28,6 +28,10 @@ namespace Titanic { class CServiceElevatorWindow : public CBackground { + DECLARE_MESSAGE_MAP; + bool ServiceElevatorFloorChangeMsg(CServiceElevatorFloorChangeMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); public: int _fieldE0; int _fieldE4; diff --git a/engines/titanic/game/sgt/armchair.cpp b/engines/titanic/game/sgt/armchair.cpp index 4c4ef44199..f547c3ef9a 100644 --- a/engines/titanic/game/sgt/armchair.cpp +++ b/engines/titanic/game/sgt/armchair.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CArmchair, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CArmchair::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +40,48 @@ void CArmchair::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CArmchair::TurnOn(CTurnOn *msg) { + if (_statics->_v8 == "Closed" && _statics->_v12 == "Closed") { + CVisibleMsg visibleMsg(false); + visibleMsg.execute("Deskchair"); + + if (_statics->_v9 == "Open") { + CActMsg actMsg("Squash"); + actMsg.execute("Deskchair"); + _startFrame = 22; + _endFrame = 31; + } else { + _startFrame = 0; + _endFrame = 10; + } + + playMovie(_startFrame, _endFrame, MOVIE_GAMESTATE); + playSound("b#0.wav"); + _statics->_v8 = "Open"; + _fieldE0 = 0; + } + + return true; +} + +bool CArmchair::TurnOff(CTurnOff *msg) { + if (_statics->_v8 == "Open") { + _statics->_v8 = "Closed"; + _startFrame = 11; + _endFrame = 21; + _fieldE0 = 1; + playMovie(11, 21, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + playSound("b#0.wav"); + } + + return true; +} + +bool CArmchair::MovieEndMsg(CMovieEndMsg *msg) { + if (_statics->_v8 == "Closed") + loadFrame(0); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/armchair.h b/engines/titanic/game/sgt/armchair.h index b5505554f0..169b9b4aa0 100644 --- a/engines/titanic/game/sgt/armchair.h +++ b/engines/titanic/game/sgt/armchair.h @@ -28,6 +28,10 @@ namespace Titanic { class CArmchair : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sgt/basin.cpp b/engines/titanic/game/sgt/basin.cpp index 1eb1d161c9..775c67deca 100644 --- a/engines/titanic/game/sgt/basin.cpp +++ b/engines/titanic/game/sgt/basin.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CBasin, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CBasin::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +40,39 @@ void CBasin::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CBasin::TurnOn(CTurnOn *msg) { + if (_statics->_v10 == "Open" && _statics->_v11 == "Closed" + && _statics->_v2 == "Closed") { + setVisible(true); + _statics->_v11 = "Open"; + _fieldE0 = 0; + _startFrame = 0; + _endFrame = 6; + playMovie(0, 6, MOVIE_GAMESTATE); + playSound("b#13.wav"); + } + + return true; +} + +bool CBasin::TurnOff(CTurnOff *msg) { + if (_statics->_v11 == "Open") { + _statics->_v11 = "Closed"; + _fieldE0 = 1; + _startFrame = 8; + _endFrame = 14; + playMovie(8, 14, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("b#13.wav"); + } + + return true; +} + +bool CBasin::MovieEndMsg(CMovieEndMsg *msg) { + if (_statics->_v11 == "Closed") + setVisible(false); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/basin.h b/engines/titanic/game/sgt/basin.h index e4a36eb841..1fcb469824 100644 --- a/engines/titanic/game/sgt/basin.h +++ b/engines/titanic/game/sgt/basin.h @@ -28,6 +28,10 @@ namespace Titanic { class CBasin : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sgt/bedfoot.cpp b/engines/titanic/game/sgt/bedfoot.cpp index 18ea07aca0..ff7d64569a 100644 --- a/engines/titanic/game/sgt/bedfoot.cpp +++ b/engines/titanic/game/sgt/bedfoot.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CBedfoot, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) +END_MESSAGE_MAP() + void CBedfoot::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +39,91 @@ void CBedfoot::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CBedfoot::TurnOn(CTurnOn *msg) { + if (_statics->_v2 == "Closed" && _statics->_v11 == "Closed") { + _fieldE0 = 0; + _startFrame = 0; + if (_statics->_v10 == "Open") { + _endFrame = 13; + _statics->_v2 = "Open"; + playSound("b#7.wav"); + } else { + _endFrame = 17; + _statics->_v2 = "NotOnWashstand"; + playSound("b#4.wav"); + } + + playMovie(_startFrame, _endFrame, MOVIE_GAMESTATE); + } else if (_statics->_v2 == "RestingUnderTV") { + _fieldE0 = 0; + _startFrame = 8; + if (_statics->_v10 == "Open") { + _statics->_v2 = "Open"; + playSound("189_436_bed down 1.wav"); + } else { + _statics->_v2 = "NotOnWashstand"; + playSound("192_436_bed hits floor.wav"); + } + + playMovie(_startFrame, _endFrame, MOVIE_GAMESTATE); + } + + if (_statics->_v2 == "Open") + _statics->_v1 = "Closed"; + else if (_statics->_v2 == "NotOnWashstand") + _statics->_v1 = "ClosedWrong"; + + return true; +} + +bool CBedfoot::TurnOff(CTurnOff *msg) { + if (_statics->_v1 == "Closed" || _statics->_v1 == "ClosedWrong") { + setVisible(true); + CVisibleMsg visibleMsg(false); + visibleMsg.execute("Bedhead"); + } + + if (_statics->_v2 == "Open" && _statics->_v1 == "Closed") { + _fieldE0 = 0; + _startFrame = 20; + if (_statics->_v4 == "Closed") { + _statics->_v2 = "Closed"; + _endFrame = 30; + } else { + _statics->_v2 = "RestingUnderTV"; + _endFrame = 25; + } + + playMovie(_startFrame, _endFrame, MOVIE_GAMESTATE); + playSound("b#7.wav"); + + } else if (_statics->_v2 == "NotOnWashstand" && _statics->_v1 == "ClosedWrong") { + _fieldE0 = 0; + _startFrame = 17; + + if (_statics->_v4 == "Closed") { + _statics->_v2 = "Closed"; + _endFrame = 30; + } else { + _statics->_v2 = "RestingUnderTV"; + _endFrame = 25; + } + + playMovie(_startFrame, _endFrame, MOVIE_GAMESTATE); + playSound("b#7.wav"); + + } else if (_statics->_v2 == "RestingUTV" && _statics->_v4 == "Closed") { + _statics->_v2 = "Closed"; + _startFrame = 25; + _endFrame = 30; + playMovie(25, 30, MOVIE_GAMESTATE); + playSound("b#7.wav"); + } + + if (_statics->_v2 == "Closed") + _statics->_v1 = "Closed"; + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/bedfoot.h b/engines/titanic/game/sgt/bedfoot.h index df3db42d6d..cc7b82b075 100644 --- a/engines/titanic/game/sgt/bedfoot.h +++ b/engines/titanic/game/sgt/bedfoot.h @@ -28,6 +28,9 @@ namespace Titanic { class CBedfoot : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sgt/bedhead.cpp b/engines/titanic/game/sgt/bedhead.cpp index fad7272f3a..216d22ee71 100644 --- a/engines/titanic/game/sgt/bedhead.cpp +++ b/engines/titanic/game/sgt/bedhead.cpp @@ -21,9 +21,61 @@ */ #include "titanic/game/sgt/bedhead.h" +#include "titanic/titanic.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CBedhead, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) +END_MESSAGE_MAP() + +void BedheadEntry::load(Common::SeekableReadStream *s) { + _name1 = readStringFromStream(s); + _name2 = readStringFromStream(s); + _name3 = readStringFromStream(s); + _name4 = readStringFromStream(s); + _startFrame = s->readUint32LE(); + _endFrame = s->readUint32LE(); +} + +/*------------------------------------------------------------------------*/ + +void BedheadEntries::load(Common::SeekableReadStream *s, int count) { + resize(count); + for (int idx = 0; idx < count; ++idx) + (*this)[idx].load(s); +} + +/*------------------------------------------------------------------------*/ + +void TurnOnEntries::load(Common::SeekableReadStream *s) { + _closed.load(s, 4); + _restingTV.load(s, 2); + _restingUV.load(s, 2); + _closedWrong.load(s, 2); +} + +/*------------------------------------------------------------------------*/ + +void TurnOffEntries::load(Common::SeekableReadStream *s) { + _open.load(s, 3); + _restingUTV.load(s, 1); + _restingV.load(s, 1); + _restingG.load(s, 3); + _openWrong.load(s, 1); + _restingDWrong.load(s, 1); +} + +/*------------------------------------------------------------------------*/ + +CBedhead::CBedhead() : CSGTStateRoom() { + Common::SeekableReadStream *s = g_vm->_filesManager->getResource("DATA/BEDHEAD"); + _on.load(s); + _off.load(s); + delete s; +} + void CBedhead::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +86,85 @@ void CBedhead::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CBedhead::TurnOn(CTurnOn *msg) { + if (_statics->_v2 == "Closed" || _statics->_v2 == "RestingUnderTV") + return true; + + const BedheadEntries *data = nullptr; + if (_statics->_v1 == "Closed") + data = &_on._closed; + else if (_statics->_v1 == "RestingTV") + data = &_on._restingTV; + else if (_statics->_v1 == "RestingUV") + data = &_on._restingUV; + else if (_statics->_v1 == "ClosedWrong") + data = &_on._closedWrong; + else + return true; + + for (uint idx = 0; idx < data->size(); ++idx) { + const BedheadEntry &entry = (*data)[idx]; + if ((entry._name1 == _statics->_v4 || entry._name1 == "Any") + && (entry._name2 == _statics->_v3 || entry._name2 == "Any") + && (entry._name3 == _statics->_v5 || entry._name3 == "Any")) { + CVisibleMsg visibleMsg(false); + visibleMsg.execute("Bedfoot"); + setVisible(true); + + _statics->_v1 = entry._name4; + playMovie(entry._startFrame, entry._endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("b#6.wav"); + _fieldE0 = false; + } + } + + if (_statics->_v1 == "Open") { + playMovie(71, 78, 0); + playSound("196_436 bed inflate 2.wav"); + } + + return true; +} + +bool CBedhead::TurnOff(CTurnOff *msg) { + if (_statics->_v1 == "Open") { + playMovie(78, 85, 0); + playSound("191_436_bed inflate deflate.wav"); + } + + BedheadEntries *data = nullptr; + if (_statics->_v1 == "Open") + data = &_off._open; + else if (_statics->_v1 == "RestingUTV") + data = &_off._restingUTV; + else if (_statics->_v1 == "RestingV") + data = &_off._restingV; + else if (_statics->_v1 == "RestingG") + data = &_off._restingG; + else if (_statics->_v1 == "OpenWrong") + data = &_off._openWrong; + else if (_statics->_v1 == "RestingDWrong") + data = &_off._restingDWrong; + else + return true; + + for (uint idx = 0; idx < data->size(); ++idx) { + const BedheadEntry &entry = (*data)[idx]; + if ((entry._name1 == _statics->_v4 || entry._name1 == "Any") + && (entry._name2 == _statics->_v3 || entry._name2 == "Any") + && (entry._name3 == _statics->_v5 || entry._name3 == "Any")) { + CVisibleMsg visibleMsg(false); + visibleMsg.execute("Bedfoot"); + setVisible(true); + + _statics->_v1 = entry._name4; + playMovie(entry._startFrame, entry._endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("193_436_bed fold up 1.wav"); + _fieldE0 = false; + } + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/bedhead.h b/engines/titanic/game/sgt/bedhead.h index f1ba31786c..7784cb8caf 100644 --- a/engines/titanic/game/sgt/bedhead.h +++ b/engines/titanic/game/sgt/bedhead.h @@ -23,13 +23,56 @@ #ifndef TITANIC_BEDHEAD_H #define TITANIC_BEDHEAD_H +#include "common/array.h" #include "titanic/game/sgt/sgt_state_room.h" namespace Titanic { +struct BedheadEntry { + CString _name1; + CString _name2; + CString _name3; + CString _name4; + int _startFrame; + int _endFrame; + + void load(Common::SeekableReadStream *s); +}; +class BedheadEntries : public Common::Array<BedheadEntry> { +public: + void load(Common::SeekableReadStream *s, int count); +}; + +struct TurnOnEntries { + BedheadEntries _closed; + BedheadEntries _restingTV; + BedheadEntries _restingUV; + BedheadEntries _closedWrong; + + void load(Common::SeekableReadStream *s); +}; + +struct TurnOffEntries { + BedheadEntries _open; + BedheadEntries _restingUTV; + BedheadEntries _restingV; + BedheadEntries _restingG; + BedheadEntries _openWrong; + BedheadEntries _restingDWrong; + + void load(Common::SeekableReadStream *s); +}; + class CBedhead : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); +private: + TurnOnEntries _on; + TurnOffEntries _off; public: CLASSDEF; + CBedhead(); /** * Save the data for the class to file diff --git a/engines/titanic/game/sgt/chest_of_drawers.cpp b/engines/titanic/game/sgt/chest_of_drawers.cpp index be62e12c8e..d9c72d3021 100644 --- a/engines/titanic/game/sgt/chest_of_drawers.cpp +++ b/engines/titanic/game/sgt/chest_of_drawers.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CChestOfDrawers, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CChestOfDrawers::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +40,41 @@ void CChestOfDrawers::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CChestOfDrawers::TurnOn(CTurnOn *msg) { + if (_statics->_v6 == "Closed" && _statics->_v5 == "Open") { + _fieldE0 = false; + _statics->_v6 = "Open"; + _startFrame = 1; + _endFrame = 14; + playSound("b#11.wav"); + } + + return true; +} + +bool CChestOfDrawers::TurnOff(CTurnOff *msg) { + if (_statics->_v6 == "Open" && _statics->_v5 == "Closed") { + CVisibleMsg visibleMsg; + visibleMsg.execute("Drawer"); + _statics->_v6 = "Closed"; + _fieldE0 = true; + + _startFrame = 14; + _endFrame = 27; + playMovie(14, 27, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("b#11.wav"); + } + + return true; +} + +bool CChestOfDrawers::MovieEndMsg(CMovieEndMsg *msg) { + if (_statics->_v6 == "Open") { + CVisibleMsg visibleMsg; + visibleMsg.execute("Drawer"); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/chest_of_drawers.h b/engines/titanic/game/sgt/chest_of_drawers.h index 16a1bf8fea..5bf852902b 100644 --- a/engines/titanic/game/sgt/chest_of_drawers.h +++ b/engines/titanic/game/sgt/chest_of_drawers.h @@ -28,6 +28,10 @@ namespace Titanic { class CChestOfDrawers : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sgt/desk.cpp b/engines/titanic/game/sgt/desk.cpp index 4dd0fdab92..09ff66f134 100644 --- a/engines/titanic/game/sgt/desk.cpp +++ b/engines/titanic/game/sgt/desk.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CDesk, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CDesk::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +40,44 @@ void CDesk::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CDesk::TurnOn(CTurnOn *msg) { + if (_statics->_v5 == "Closed" && _statics->_v1 != "RestingG" + && _statics->_v1 != "OpenWrong") { + _statics->_v5 = "Open"; + _fieldE0 = false; + _startFrame = 1; + _endFrame = 26; + playMovie(1, 26, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("b#12.wav"); + } + + return true; +} + +bool CDesk::TurnOff(CTurnOff *msg) { + if (_statics->_v5 == "Open" && _statics->_v6 == "Closed" + && _statics->_v1 == "Open") { + CVisibleMsg visibleMsg(false); + visibleMsg.execute("ChestOfDrawers"); + + _statics->_v5 = "Closed"; + _fieldE0 = true; + _startFrame = 26; + _endFrame = 51; + playMovie(26, 51, MOVIE_GAMESTATE); + playSound("b#9.wav"); + } + + return true; +} + +bool CDesk::MovieEndMsg(CMovieEndMsg *msg) { + if (_statics->_v5 == "Open") { + CVisibleMsg visibleMsg(true); + visibleMsg.execute("ChestOfDrawers"); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/desk.h b/engines/titanic/game/sgt/desk.h index 77b5fa17af..8b9e1fe841 100644 --- a/engines/titanic/game/sgt/desk.h +++ b/engines/titanic/game/sgt/desk.h @@ -28,6 +28,10 @@ namespace Titanic { class CDesk : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sgt/deskchair.cpp b/engines/titanic/game/sgt/deskchair.cpp index a4a2badeb0..7f64c2ee34 100644 --- a/engines/titanic/game/sgt/deskchair.cpp +++ b/engines/titanic/game/sgt/deskchair.cpp @@ -24,6 +24,13 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CDeskchair, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CDeskchair::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +41,49 @@ void CDeskchair::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CDeskchair::TurnOn(CTurnOn *msg) { + if (_statics->_v8 == "Closed" && _statics->_v9 == "Closed") { + setVisible(true); + _statics->_v9 = "Open"; + _fieldE0 = false; + _startFrame = 0; + _endFrame = 16; + playMovie(0, 16, MOVIE_GAMESTATE); + playSound("b#8.wav"); + } + + return true; +} + +bool CDeskchair::TurnOff(CTurnOff *msg) { + if (_statics->_v9 == "Open") { + _statics->_v9 = "Closed"; + _fieldE0 = true; + _startFrame = 16; + _endFrame = 32; + playMovie(16, 32, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("b#2.wav"); + } + + return true; +} + +bool CDeskchair::ActMsg(CActMsg *msg) { + if (msg->_action == "Smash") { + setVisible(false); + _statics->_v9 = "Closed"; + _fieldE0 = true; + loadFrame(0); + return true; + } else { + return CSGTStateRoom::ActMsg(msg); + } +} + +bool CDeskchair::MovieEndMsg(CMovieEndMsg *msg) { + if (_statics->_v9 == "Closed") + setVisible(false); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/deskchair.h b/engines/titanic/game/sgt/deskchair.h index 5181b650d2..6e7bbe4169 100644 --- a/engines/titanic/game/sgt/deskchair.h +++ b/engines/titanic/game/sgt/deskchair.h @@ -28,6 +28,11 @@ namespace Titanic { class CDeskchair : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool ActMsg(CActMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sgt/drawer.cpp b/engines/titanic/game/sgt/drawer.cpp index 03aa1b5358..b8e93c37a6 100644 --- a/engines/titanic/game/sgt/drawer.cpp +++ b/engines/titanic/game/sgt/drawer.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CDrawer, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + CDrawer::CDrawer() : CSGTStateRoom(), _fieldF4(0) { } @@ -39,4 +45,39 @@ void CDrawer::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CDrawer::TurnOn(CTurnOn *msg) { + if (_statics->_v7 == "Closed" && _statics->_v6 == "Open") { + _statics->_v7 = "Open"; + _fieldE0 = false; + _startFrame = 50; + _endFrame = 75; + setVisible(true); + _statics->_v7 = "Open"; + playMovie(_startFrame, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("b#10.wav"); + } + + return true; +} + +bool CDrawer::TurnOff(CTurnOff *msg) { + if (_statics->_v7 == "Open") { + _statics->_v7 = "Closed"; + _startFrame = 75; + _endFrame = 100; + _fieldE0 = true; + playMovie(_startFrame, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("b#10.wav"); + } + + return true; +} + +bool CDrawer::MovieEndMsg(CMovieEndMsg *msg) { + if (_statics->_v7 == "Closed") + setVisible(false); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/drawer.h b/engines/titanic/game/sgt/drawer.h index c079be389f..e8afe66068 100644 --- a/engines/titanic/game/sgt/drawer.h +++ b/engines/titanic/game/sgt/drawer.h @@ -28,6 +28,10 @@ namespace Titanic { class CDrawer : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool MovieEndMsg(CMovieEndMsg *msg); private: int _fieldF4; public: diff --git a/engines/titanic/game/sgt/sgt_doors.cpp b/engines/titanic/game/sgt/sgt_doors.cpp index 516b0f1351..71eae9800c 100644 --- a/engines/titanic/game/sgt/sgt_doors.cpp +++ b/engines/titanic/game/sgt/sgt_doors.cpp @@ -21,13 +21,21 @@ */ #include "titanic/game/sgt/sgt_doors.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CSGTDoors, CGameObject) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(LeaveRoomMsg) +END_MESSAGE_MAP() + void CSGTDoors::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value1, indent); - file->writeNumberLine(_value2, indent); + file->writeNumberLine(_open, indent); CGameObject::save(file, indent); } @@ -35,9 +43,58 @@ void CSGTDoors::save(SimpleFile *file, int indent) { void CSGTDoors::load(SimpleFile *file) { file->readNumber(); _value1 = file->readNumber(); - _value2 = file->readNumber(); + _open = file->readNumber(); CGameObject::load(file); } +bool CSGTDoors::EnterViewMsg(CEnterViewMsg *msg) { + setVisible(true); + _open = true; + CPetControl *pet = getPetControl(); + + if (pet) { + int roomNum = pet->getRoomsRoomNum(); + static const int START_FRAMES[7] = { 0, 26, 30, 34, 38, 42, 46 }; + static const int END_FRAMES[7] = { 12, 29, 33, 37, 41, 45, 49 }; + + if (pet->getRooms1CC() == 1) + playMovie(START_FRAMES[roomNum], END_FRAMES[roomNum], + MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + else + playMovie(0, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + + return true; +} + +bool CSGTDoors::LeaveViewMsg(CLeaveViewMsg *msg) { + return true; +} + +bool CSGTDoors::MovieEndMsg(CMovieEndMsg *msg) { + setVisible(!_open); + return true; +} + +bool CSGTDoors::LeaveRoomMsg(CLeaveRoomMsg *msg) { + setVisible(true); + _open = false; + CPetControl *pet = getPetControl(); + + if (pet) { + int roomNum = pet->getRoomsRoomNum(); + static const int START_FRAMES[7] = { 12, 69, 65, 61, 57, 53, 49 }; + static const int END_FRAMES[7] = { 25, 72, 68, 64, 60, 56, 52 }; + + if (pet->getRooms1CC() == 1) + playMovie(START_FRAMES[roomNum], END_FRAMES[roomNum], + MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + else + playMovie(12, 25, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/sgt_doors.h b/engines/titanic/game/sgt/sgt_doors.h index 4b4f4a3153..b19c5860af 100644 --- a/engines/titanic/game/sgt/sgt_doors.h +++ b/engines/titanic/game/sgt/sgt_doors.h @@ -28,11 +28,17 @@ namespace Titanic { class CSGTDoors : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool LeaveRoomMsg(CLeaveRoomMsg *msg); public: - int _value1, _value2; + int _value1; + bool _open; public: CLASSDEF; - CSGTDoors() : CGameObject(), _value1(0), _value2(0) {} + CSGTDoors() : CGameObject(), _value1(0), _open(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/sgt/sgt_nav.cpp b/engines/titanic/game/sgt/sgt_nav.cpp index f98e486fd0..c004f947d2 100644 --- a/engines/titanic/game/sgt/sgt_nav.cpp +++ b/engines/titanic/game/sgt/sgt_nav.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(SGTNav, CSGTStateRoom) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseMoveMsg) +END_MESSAGE_MAP() + void SGTNav::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +39,43 @@ void SGTNav::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool SGTNav::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CTurnOn onMsg; + CTurnOff offMsg; + + if (_statics->_v6 == "Open" && _statics->_v1 == "Open") { + if (_statics->_v3 == "Open") + offMsg.execute("Vase"); + if (_statics->_v4 == "Closed") + onMsg.execute("SGTTV"); + if (_statics->_v7 == "Open") + offMsg.execute("Drawer"); + if (_statics->_v8 == "Open") + offMsg.execute("Armchair"); + if (_statics->_v9 == "Open") + offMsg.execute("Deskchair"); + if (_statics->_v12 == "Open") + offMsg.execute("Toilet"); + + changeView("SGTState.Node 2.E"); + } else if (_statics->_v1 == "Open") { + petDisplayMessage(1, "This is your stateroom. It is for sleeping. If you desire " + "entertainment or relaxation, please visit your local leisure lounge."); + } else if (_statics->_v6 == "Closed") { + petDisplayMessage(1, "The bed will not currently support your weight." + " We are working on this problem but are unlikely to be able to fix it."); + } + + return true; +} + +bool SGTNav::MouseMoveMsg(CMouseMoveMsg *msg) { + if (_statics->_v6 == "Open" && _statics->_v1 == "Open") + _cursorId = CURSOR_MOVE_FORWARD; + else + _cursorId = CURSOR_ARROW; + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/sgt_nav.h b/engines/titanic/game/sgt/sgt_nav.h index 40fdc4eff1..78f6417229 100644 --- a/engines/titanic/game/sgt/sgt_nav.h +++ b/engines/titanic/game/sgt/sgt_nav.h @@ -28,6 +28,9 @@ namespace Titanic { class SGTNav : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseMoveMsg(CMouseMoveMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sgt/sgt_navigation.cpp b/engines/titanic/game/sgt/sgt_navigation.cpp index d0c308457c..031226226f 100644 --- a/engines/titanic/game/sgt/sgt_navigation.cpp +++ b/engines/titanic/game/sgt/sgt_navigation.cpp @@ -21,9 +21,16 @@ */ #include "titanic/game/sgt/sgt_navigation.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CSGTNavigation, CGameObject) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + CSGTNavigationStatics *CSGTNavigation::_statics; void CSGTNavigation::init() { @@ -36,20 +43,88 @@ void CSGTNavigation::deinit() { void CSGTNavigation::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_statics->_v1, indent); - file->writeQuotedLine(_statics->_v2, indent); - file->writeQuotedLine(_statics->_v3, indent); + file->writeNumberLine(_statics->_changeViewNum, indent); + file->writeQuotedLine(_statics->_destView, indent); + file->writeQuotedLine(_statics->_destRoom, indent); CGameObject::save(file, indent); } void CSGTNavigation::load(SimpleFile *file) { file->readNumber(); - _statics->_v1 = file->readNumber(); - _statics->_v2 = file->readString(); - _statics->_v3 = file->readString(); + _statics->_changeViewNum = file->readNumber(); + _statics->_destView = file->readString(); + _statics->_destRoom = file->readString(); CGameObject::load(file); } +bool CSGTNavigation::StatusChangeMsg(CStatusChangeMsg *msg) { + CPetControl *pet = getPetControl(); + + if (isEquals("SGTLL")) { + static const int FRAMES[7] = { 0, 149, 112, 74, 0, 36, 74 }; + _statics->_changeViewNum = msg->_newStatus; + if (pet->getRooms1CC() != _statics->_changeViewNum) { + changeView("SGTLittleLift.Node 1.N"); + } + + int startVal = pet->getRooms1CC(); + if (startVal > _statics->_changeViewNum) + playMovie(FRAMES[startVal], FRAMES[_statics->_changeViewNum], MOVIE_GAMESTATE); + else + playMovie(FRAMES[startVal + 3], FRAMES[_statics->_changeViewNum + 3], MOVIE_GAMESTATE); + + _cursorId = _statics->_changeViewNum != 1 ? CURSOR_MOVE_FORWARD : CURSOR_INVALID; + + pet->setRooms1CC(_statics->_changeViewNum); + pet->resetRoomsHighlight(); + } + + return true; +} + +bool CSGTNavigation::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (compareRoomNameTo("SgtLobby")) { + _statics->_destView = getRoomNodeName(); + _statics->_destRoom = "SgtLobby"; + changeView("SGTState.Node 1.S"); + } else if (compareRoomNameTo("SGTLittleLift")) { + if (_statics->_changeViewNum != 1) { + _statics->_destRoom = "SGTLittleLift"; + changeView("SGTState.Node 1.S"); + } + } else if (compareRoomNameTo("SGTState")) { + if (_statics->_destRoom == "SgtLobby") { + if (compareViewNameTo("SGTState.Node 2.N")) { + changeView("SGTState.Node 1.N"); + _statics->_destView += ".S"; + } else { + _statics->_destView += ".N"; + } + + changeView(_statics->_destView); + } else if (_statics->_destRoom == "SGTLittleLift") { + if (compareViewNameTo("SGTState.Node 1.S")) { + changeView("SGTLittleLift.Node 1.N"); + } else { + changeView("SGTState.Node 1.N"); + changeView("SGTLittleLift.Node 1.S"); + } + } + } + + return true; +} + +bool CSGTNavigation::EnterViewMsg(CEnterViewMsg *msg) { + if (isEquals("SGTLL")) { + static const int FRAMES[3] = { 0, 36, 74 }; + CPetControl *pet = getPetControl(); + loadFrame(FRAMES[pet->getRooms1CC() - 1]); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/sgt_navigation.h b/engines/titanic/game/sgt/sgt_navigation.h index 6d24fe6761..69ecd1267a 100644 --- a/engines/titanic/game/sgt/sgt_navigation.h +++ b/engines/titanic/game/sgt/sgt_navigation.h @@ -28,13 +28,17 @@ namespace Titanic { struct CSGTNavigationStatics { - int _v1; - CString _v2; - CString _v3; + int _changeViewNum; + CString _destView; + CString _destRoom; }; class CSGTNavigation : public CGameObject { -private: + DECLARE_MESSAGE_MAP; + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); +protected: static CSGTNavigationStatics *_statics; public: CLASSDEF; diff --git a/engines/titanic/game/sgt/sgt_restaurant_doors.cpp b/engines/titanic/game/sgt/sgt_restaurant_doors.cpp index 74a71e75b2..5c36ceb0ff 100644 --- a/engines/titanic/game/sgt/sgt_restaurant_doors.cpp +++ b/engines/titanic/game/sgt/sgt_restaurant_doors.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CSGTRestaurantDoors, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CSGTRestaurantDoors::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldBC, indent); @@ -36,4 +40,10 @@ void CSGTRestaurantDoors::load(SimpleFile *file) { CGameObject::load(file); } +bool CSGTRestaurantDoors::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CTurnOff offMsg; + offMsg.execute("ChickenDispenser"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/sgt_restaurant_doors.h b/engines/titanic/game/sgt/sgt_restaurant_doors.h index 2a10d8f059..bcaaa71014 100644 --- a/engines/titanic/game/sgt/sgt_restaurant_doors.h +++ b/engines/titanic/game/sgt/sgt_restaurant_doors.h @@ -28,6 +28,8 @@ namespace Titanic { class CSGTRestaurantDoors : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); private: int _fieldBC; public: diff --git a/engines/titanic/game/sgt/sgt_state_control.cpp b/engines/titanic/game/sgt/sgt_state_control.cpp index 07c1f5efc0..9617f2f127 100644 --- a/engines/titanic/game/sgt/sgt_state_control.cpp +++ b/engines/titanic/game/sgt/sgt_state_control.cpp @@ -24,16 +24,59 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CSGTStateControl, CBackground) + ON_MESSAGE(PETUpMsg) + ON_MESSAGE(PETDownMsg) + ON_MESSAGE(PETActivateMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + void CSGTStateControl::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); + file->writeNumberLine(_state, indent); CBackground::save(file, indent); } void CSGTStateControl::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); + _state = file->readNumber(); CBackground::load(file); } +bool CSGTStateControl::PETUpMsg(CPETUpMsg *msg) { + // WORKAROUND: Redundant code in original not included + return true; +} + +bool CSGTStateControl::PETDownMsg(CPETDownMsg *msg) { + // WORKAROUND: Redundant code in original not included + return true; +} + +bool CSGTStateControl::PETActivateMsg(CPETActivateMsg *msg) { + if (msg->_name == "SGTSelector") { + static const char *const TARGETS[] = { + "Vase", "Bedfoot", "Toilet", "Drawer", "SGTTV", "Armchair", "BedHead", + "WashStand", "Desk", "DeskChair", "Basin", "ChestOfDrawers" + }; + _state = msg->_numValue; + CActMsg actMsg; + actMsg.execute(TARGETS[_state]); + } + + return true; +} + +bool CSGTStateControl::EnterViewMsg(CEnterViewMsg *msg) { + _state = 1; + petSetRemoteTarget(); + return true; +} + +bool CSGTStateControl::LeaveViewMsg(CLeaveViewMsg *msg) { + petClear(); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/sgt_state_control.h b/engines/titanic/game/sgt/sgt_state_control.h index 49fd5113cd..6f96359761 100644 --- a/engines/titanic/game/sgt/sgt_state_control.h +++ b/engines/titanic/game/sgt/sgt_state_control.h @@ -24,15 +24,22 @@ #define TITANIC_SGT_STATE_CONTROL_H #include "titanic/core/background.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { class CSGTStateControl : public CBackground { + DECLARE_MESSAGE_MAP; + bool PETUpMsg(CPETUpMsg *msg); + bool PETDownMsg(CPETDownMsg *msg); + bool PETActivateMsg(CPETActivateMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); private: - int _fieldE0; + int _state; public: CLASSDEF; - CSGTStateControl() : CBackground(), _fieldE0(1) {} + CSGTStateControl() : CBackground(), _state(1) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/sgt/sgt_state_room.cpp b/engines/titanic/game/sgt/sgt_state_room.cpp index 55f08de8b4..c089e401b8 100644 --- a/engines/titanic/game/sgt/sgt_state_room.cpp +++ b/engines/titanic/game/sgt/sgt_state_room.cpp @@ -21,17 +21,22 @@ */ #include "titanic/game/sgt/sgt_state_room.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { BEGIN_MESSAGE_MAP(CSGTStateRoom, CBackground) + ON_MESSAGE(ActMsg) + ON_MESSAGE(VisibleMsg) ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(LeaveRoomMsg) END_MESSAGE_MAP() CSGTStateRoomStatics *CSGTStateRoom::_statics; void CSGTStateRoom::init() { _statics = new CSGTStateRoomStatics(); + _statics->_v1 = "Closed"; } void CSGTStateRoom::deinit() { @@ -94,8 +99,80 @@ void CSGTStateRoom::load(SimpleFile *file) { CBackground::load(file); } +bool CSGTStateRoom::ActMsg(CActMsg *msg) { + CPetControl *pet = getPetControl(); + uint roomFlags = pet->getRoomFlags(); + uint assignedRoom = pet->getAssignedRoomFlags(); + + if (roomFlags != assignedRoom) { + petDisplayMessage("This is not your assigned room. Please do not enjoy."); + } else if (_fieldE0) { + CTurnOn onMsg; + onMsg.execute(this); + } else { + CTurnOff offMsg; + offMsg.execute(this); + } + + return true; +} + +bool CSGTStateRoom::VisibleMsg(CVisibleMsg *msg) { + setVisible(msg->_visible); + return true; +} + bool CSGTStateRoom::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CSGTStateRoom::handleEvent"); + CPetControl *pet = getPetControl(); + uint roomFlags = pet->getRoomFlags(); + uint assignedRoom = pet->getAssignedRoomFlags(); + + if (roomFlags == assignedRoom) { + loadFrame(_fieldE8); + _fieldE0 = _fieldEC; + setVisible(_fieldF0); + + if (isEquals("Desk") && _statics->_v5 == "Closed") + loadFrame(1); + } + + if (isEquals("Drawer")) { + petSetArea(PET_REMOTE); + if (roomFlags == assignedRoom && getPassengerClass() == 3 + && _statics->_v13) { + playSound("b#21.wav"); + _statics->_v13 = 0; + } + + _statics->_v7 = "Closed"; + setVisible(false); + _fieldE0 = true; + } else if (roomFlags != assignedRoom) { + loadFrame(0); + if (_fieldE4) { + setVisible(true); + if (isEquals("Desk")) + loadFrame(1); + } else { + setVisible(false); + } + } + + return true; +} + +bool CSGTStateRoom::LeaveRoomMsg(CLeaveRoomMsg *msg) { + CPetControl *pet = getPetControl(); + uint roomFlags = pet->getRoomFlags(); + uint assignedRoom = pet->getAssignedRoomFlags(); + + if (roomFlags == assignedRoom) { + _fieldE8 = getMovieFrame(); + _fieldEC = _fieldE0; + _fieldF0 = _visible; + } + + _statics->_v14 = roomFlags; return true; } diff --git a/engines/titanic/game/sgt/sgt_state_room.h b/engines/titanic/game/sgt/sgt_state_room.h index 375da71326..3975f7b59b 100644 --- a/engines/titanic/game/sgt/sgt_state_room.h +++ b/engines/titanic/game/sgt/sgt_state_room.h @@ -47,15 +47,18 @@ struct CSGTStateRoomStatics { class CSGTStateRoom : public CBackground { DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool VisibleMsg(CVisibleMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); -private: + bool LeaveRoomMsg(CLeaveRoomMsg *msg); +protected: static CSGTStateRoomStatics *_statics; -private: - int _fieldE0; +protected: + bool _fieldE0; int _fieldE4; int _fieldE8; - int _fieldEC; - int _fieldF0; + bool _fieldEC; + bool _fieldF0; public: CLASSDEF; CSGTStateRoom(); diff --git a/engines/titanic/game/sgt/sgt_tv.cpp b/engines/titanic/game/sgt/sgt_tv.cpp index ae4c59e2f9..08ed381b2b 100644 --- a/engines/titanic/game/sgt/sgt_tv.cpp +++ b/engines/titanic/game/sgt/sgt_tv.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CSGTTV, CSGTStateRoom) + ON_MESSAGE(TurnOff) + ON_MESSAGE(TurnOn) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CSGTTV::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +40,35 @@ void CSGTTV::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CSGTTV::TurnOff(CTurnOff *msg) { + if (CSGTStateRoom::_statics->_v4 == "Open") { + CSGTStateRoom::_statics->_v4 = "Closed"; + _fieldE0 = true; + _startFrame = 6; + _endFrame = 12; + playMovie(6, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + + return true; +} + +bool CSGTTV::TurnOn(CTurnOn *msg) { + if (CSGTStateRoom::_statics->_v4 == "Closed" && + CSGTStateRoom::_statics->_v2 == "Closed") { + CSGTStateRoom::_statics->_v4 = "Open"; + setVisible(true); + _fieldE0 = false; + _startFrame = 1; + _endFrame = 6; + playMovie(1, 6, MOVIE_GAMESTATE); + } + + return true; +} + +bool CSGTTV::MovieEndMsg(CMovieEndMsg *msg) { + setVisible(false); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/sgt_tv.h b/engines/titanic/game/sgt/sgt_tv.h index 90fed90efe..e5de38e84b 100644 --- a/engines/titanic/game/sgt/sgt_tv.h +++ b/engines/titanic/game/sgt/sgt_tv.h @@ -28,6 +28,10 @@ namespace Titanic { class CSGTTV : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOff(CTurnOff *msg); + bool TurnOn(CTurnOn *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sgt/sgt_upper_doors_sound.cpp b/engines/titanic/game/sgt/sgt_upper_doors_sound.cpp index ed37b0a5c7..72cd7f9037 100644 --- a/engines/titanic/game/sgt/sgt_upper_doors_sound.cpp +++ b/engines/titanic/game/sgt/sgt_upper_doors_sound.cpp @@ -25,19 +25,19 @@ namespace Titanic { CSGTUpperDoorsSound::CSGTUpperDoorsSound() { - _string2 = "b#53.wav"; + _soundName = "b#53.wav"; } void CSGTUpperDoorsSound::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string2, indent); + file->writeQuotedLine(_soundName, indent); CClickResponder::save(file, indent); } void CSGTUpperDoorsSound::load(SimpleFile *file) { file->readNumber(); - _string2 = file->readString(); + _soundName = file->readString(); CClickResponder::load(file); } diff --git a/engines/titanic/game/sgt/toilet.cpp b/engines/titanic/game/sgt/toilet.cpp index 799abd6c76..b8e87b645a 100644 --- a/engines/titanic/game/sgt/toilet.cpp +++ b/engines/titanic/game/sgt/toilet.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CToilet, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CToilet::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +40,42 @@ void CToilet::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CToilet::TurnOn(CTurnOn *msg) { + if (CSGTStateRoom::_statics->_v12 == "Closed" + && CSGTStateRoom::_statics->_v10 == "Open" + && CSGTStateRoom::_statics->_v8 == "Closed") { + setVisible(true); + CSGTStateRoom::_statics->_v12 = "Open"; + + _fieldE0 = false; + _startFrame = 0; + _endFrame = 11; + playMovie(0, 11, MOVIE_GAMESTATE); + playSound("b#1.wav"); + } + + return true; +} + +bool CToilet::TurnOff(CTurnOff *msg) { + if (CSGTStateRoom::_statics->_v12 == "Open") { + CSGTStateRoom::_statics->_v12 = "Closed"; + + _fieldE0 = true; + _startFrame = 11; + _endFrame = 18; + playMovie(11, 18, MOVIE_GAMESTATE); + playSound("b#1.wav"); + } + + return true; +} + +bool CToilet::MovieEndMsg(CMovieEndMsg *msg) { + if (CSGTStateRoom::_statics->_v12 == "Closed") + setVisible(false); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/toilet.h b/engines/titanic/game/sgt/toilet.h index d87531ad7a..a4bc318fb2 100644 --- a/engines/titanic/game/sgt/toilet.h +++ b/engines/titanic/game/sgt/toilet.h @@ -28,6 +28,10 @@ namespace Titanic { class CToilet : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sgt/vase.cpp b/engines/titanic/game/sgt/vase.cpp index 3e04b5db9e..2d37818340 100644 --- a/engines/titanic/game/sgt/vase.cpp +++ b/engines/titanic/game/sgt/vase.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CVase, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CVase::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +40,38 @@ void CVase::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CVase::TurnOn(CTurnOn *msg) { + if (CSGTStateRoom::_statics->_v3 == "Closed") { + CSGTStateRoom::_statics->_v3 = "Open"; + setVisible(true); + _fieldE0 = false; + _startFrame = 1; + _endFrame = 12; + playMovie(1, 12, MOVIE_GAMESTATE); + } + + return true; +} + +bool CVase::TurnOff(CTurnOff *msg) { + if (CSGTStateRoom::_statics->_v3 == "Open" + && CSGTStateRoom::_statics->_v1 != "RestingV" + && CSGTStateRoom::_statics->_v1 != "RestingUV") { + CSGTStateRoom::_statics->_v3 = "Closed"; + _fieldE0 = true; + _startFrame = 12; + _endFrame = 25; + playMovie(12, 25, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + + return true; +} + +bool CVase::MovieEndMsg(CMovieEndMsg *msg) { + if (CSGTStateRoom::_statics->_v3 == "Closed") + setVisible(false); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/vase.h b/engines/titanic/game/sgt/vase.h index 8aa35acdf5..e07d9efb80 100644 --- a/engines/titanic/game/sgt/vase.h +++ b/engines/titanic/game/sgt/vase.h @@ -28,6 +28,10 @@ namespace Titanic { class CVase : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sgt/washstand.cpp b/engines/titanic/game/sgt/washstand.cpp index 8127a59a59..afdc74414d 100644 --- a/engines/titanic/game/sgt/washstand.cpp +++ b/engines/titanic/game/sgt/washstand.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CWashstand, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CWashstand::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +40,36 @@ void CWashstand::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CWashstand::TurnOn(CTurnOn *msg) { + if (_statics->_v10 == "Closed" && _statics->_v2 == "NotOnWashstand") { + setVisible(true); + _statics->_v10 = "Open"; + _fieldE0 = false; + _startFrame = 0; + _endFrame = 14; + playMovie(0, 14, MOVIE_GAMESTATE); + playSound("b#14.wav"); + } + + return true; +} + +bool CWashstand::TurnOff(CTurnOff *msg) { + if (_statics->_v10 == "Open" && _statics->_v11 == "Closed" + && _statics->_v12 == "Closed" && _statics->_v2 == "Open") { + _statics->_v10 = "Closed"; + _fieldE0 = true; + _startFrame = 14; + _endFrame = 28; + playMovie(14, 28, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + playSound("b#14.wav"); + } + + return true; +} + +bool CWashstand::MovieEndMsg(CMovieEndMsg *msg) { + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/washstand.h b/engines/titanic/game/sgt/washstand.h index f140b17f49..1b72cfa1c4 100644 --- a/engines/titanic/game/sgt/washstand.h +++ b/engines/titanic/game/sgt/washstand.h @@ -28,6 +28,10 @@ namespace Titanic { class CWashstand : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/ship_setting.cpp b/engines/titanic/game/ship_setting.cpp index 462f396501..93800e899b 100644 --- a/engines/titanic/game/ship_setting.cpp +++ b/engines/titanic/game/ship_setting.cpp @@ -21,35 +21,109 @@ */ #include "titanic/game/ship_setting.h" +#include "titanic/core/project_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CShipSetting, CBackground) + ON_MESSAGE(AddHeadPieceMsg) + ON_MESSAGE(SetFrameMsg) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + CShipSetting::CShipSetting() : CBackground(), - _string4("NULL"), _string5("NULL") { + _itemName("NULL"), _frameTarget("NULL") { } void CShipSetting::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string3, indent); + file->writeQuotedLine(_target, indent); file->writePoint(_pos1, indent); - file->writeQuotedLine(_string4, indent); - file->writeQuotedLine(_string5, indent); + file->writeQuotedLine(_itemName, indent); + file->writeQuotedLine(_frameTarget, indent); CBackground::save(file, indent); } void CShipSetting::load(SimpleFile *file) { file->readNumber(); - _string3 = file->readString(); + _target = file->readString(); _pos1 = file->readPoint(); - _string4 = file->readString(); - _string5 = file->readString(); + _itemName = file->readString(); + _frameTarget = file->readString(); CBackground::load(file); } +bool CShipSetting::AddHeadPieceMsg(CAddHeadPieceMsg *msg) { + _cursorId = CURSOR_HAND; + + if (msg->_value == "Enable") { + CTurnOn onMsg; + onMsg.execute(_target); + + if (isEquals("ChickenSetting")) { + CActMsg actMsg("DecreaseQuantity"); + actMsg.execute("ChickenDispenser"); + } + } else { + CTurnOff offMsg; + offMsg.execute(_target); + } + + return true; +} + +bool CShipSetting::SetFrameMsg(CSetFrameMsg *msg) { + msg->execute(_frameTarget); + return true; +} + bool CShipSetting::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CShipSetting::handleEvent"); + CSetFrameMsg frameMsg; + + if (_itemName == "ChickenBridge") + frameMsg._frameNumber = 1; + else if (_itemName == "FanBridge") + frameMsg._frameNumber = 2; + else if (_itemName == "SeasonBridge") + frameMsg._frameNumber = 3; + else if (_itemName == "BeamBridge") + frameMsg._frameNumber = 4; + + frameMsg.execute(this); + return true; +} + +bool CShipSetting::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (!checkStartDragging(msg)) + return false; + if (_itemName == "NULL") + return true; + + CTurnOff offMsg; + offMsg.execute(_target); + + if (isEquals("ChickenSetting") || _itemName == "ChickenBridge") { + CActMsg actMsg("IncreaseQuantity"); + actMsg.execute("ChickenDispenser"); + } + + if (_itemName != "NULL") { + CPassOnDragStartMsg passMsg(msg->_mousePos, 1); + passMsg.execute(_itemName); + + msg->_dragItem = getRoot()->findByName(_itemName); + + CVisibleMsg visibleMsg(true); + visibleMsg.execute(_itemName); + } + + CSetFrameMsg frameMsg(0); + frameMsg.execute(_frameTarget); + _itemName = "NULL"; + _cursorId = CURSOR_ARROW; return true; } diff --git a/engines/titanic/game/ship_setting.h b/engines/titanic/game/ship_setting.h index 4fcc10a424..198d97d250 100644 --- a/engines/titanic/game/ship_setting.h +++ b/engines/titanic/game/ship_setting.h @@ -29,12 +29,16 @@ namespace Titanic { class CShipSetting : public CBackground { + DECLARE_MESSAGE_MAP; + bool AddHeadPieceMsg(CAddHeadPieceMsg *msg); + bool SetFrameMsg(CSetFrameMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); public: - CString _string3; + CString _target; Point _pos1; - CString _string4; - CString _string5; + CString _itemName; + CString _frameTarget; public: CLASSDEF; CShipSetting(); diff --git a/engines/titanic/game/ship_setting_button.cpp b/engines/titanic/game/ship_setting_button.cpp index 7dc2cabac0..d485e06668 100644 --- a/engines/titanic/game/ship_setting_button.cpp +++ b/engines/titanic/game/ship_setting_button.cpp @@ -24,25 +24,69 @@ namespace Titanic { -CShipSettingButton::CShipSettingButton() : CGameObject(), _fieldC8(0), _fieldCC(0) { +BEGIN_MESSAGE_MAP(CShipSettingButton, CGameObject) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + +CShipSettingButton::CShipSettingButton() : CGameObject(), _pressed(false), _enabled(false) { } void CShipSettingButton::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string1, indent); - file->writeNumberLine(_fieldC8, indent); - file->writeNumberLine(_fieldCC, indent); + file->writeQuotedLine(_target, indent); + file->writeNumberLine(_pressed, indent); + file->writeNumberLine(_enabled, indent); CGameObject::save(file, indent); } void CShipSettingButton::load(SimpleFile *file) { file->readNumber(); - _string1 = file->readString(); - _fieldC8 = file->readNumber(); - _fieldCC = file->readNumber(); + _target = file->readString(); + _pressed = file->readNumber(); + _enabled = file->readNumber(); CGameObject::load(file); } +bool CShipSettingButton::TurnOn(CTurnOn *msg) { + _pressed = true; + return true; +} + +bool CShipSettingButton::TurnOff(CTurnOff *msg) { + _pressed = false; + return true; +} + +bool CShipSettingButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_pressed) { + if (_enabled) + playMovie(8, 16, 0); + else + playMovie(0, 8, 0); + + _enabled = !_enabled; + CActMsg actMsg(_enabled ? "EnableObject" : "DisableObject"); + actMsg.execute(_target); + } else { + if (_enabled) { + playMovie(8, 16, 0); + playMovie(0, 8, 0); + } else { + playMovie(0, 16, 0); + } + } + + return true; +} + +bool CShipSettingButton::EnterViewMsg(CEnterViewMsg *msg) { + loadFrame(_enabled ? 8 : 16); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/ship_setting_button.h b/engines/titanic/game/ship_setting_button.h index e152e8e2c3..e5457fa532 100644 --- a/engines/titanic/game/ship_setting_button.h +++ b/engines/titanic/game/ship_setting_button.h @@ -28,10 +28,15 @@ namespace Titanic { class CShipSettingButton : public CGameObject { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); private: - CString _string1; - int _fieldC8; - int _fieldCC; + CString _target; + bool _pressed; + bool _enabled; public: CLASSDEF; CShipSettingButton(); diff --git a/engines/titanic/game/show_cell_points.cpp b/engines/titanic/game/show_cell_points.cpp index 7d54401a02..985cb93734 100644 --- a/engines/titanic/game/show_cell_points.cpp +++ b/engines/titanic/game/show_cell_points.cpp @@ -21,21 +21,50 @@ */ #include "titanic/game/show_cell_points.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CShowCellpoints, CGameObject) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + void CShowCellpoints::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_strValue, indent); - file->writeNumberLine(_numValue, indent); + file->writeQuotedLine(_npcName, indent); + file->writeNumberLine(_flag, indent); CGameObject::save(file, indent); } void CShowCellpoints::load(SimpleFile *file) { file->readNumber(); - _strValue = file->readString(); - _numValue = file->readNumber(); + _npcName = file->readString(); + _flag = file->readNumber(); CGameObject::load(file); } +bool CShowCellpoints::EnterViewMsg(CEnterViewMsg *msg) { + CPetControl *pet = getPetControl(); + if (pet) { + petSetArea(PET_CONVERSATION); + pet->setActiveNPC(_npcName); + pet->incAreaLocks(); + _flag = true; + } + + return true; +} + +bool CShowCellpoints::LeaveViewMsg(CLeaveViewMsg *msg) { + CPetControl *pet = getPetControl(); + if (pet && _flag) { + pet->resetDials0(); + pet->decAreaLocks(); + _flag = false; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/show_cell_points.h b/engines/titanic/game/show_cell_points.h index 9de2e06dca..205547d7c2 100644 --- a/engines/titanic/game/show_cell_points.h +++ b/engines/titanic/game/show_cell_points.h @@ -28,12 +28,15 @@ namespace Titanic { class CShowCellpoints : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); public: - CString _strValue; - int _numValue; + CString _npcName; + bool _flag; public: CLASSDEF; - CShowCellpoints() : CGameObject(), _numValue(0) {} + CShowCellpoints() : CGameObject(), _flag(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/speech_dispensor.cpp b/engines/titanic/game/speech_dispensor.cpp index f9cc019672..20ff3c69e0 100644 --- a/engines/titanic/game/speech_dispensor.cpp +++ b/engines/titanic/game/speech_dispensor.cpp @@ -24,15 +24,26 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CSpeechDispensor, CBackground) + ON_MESSAGE(FrameMsg) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(ChangeSeasonMsg) +END_MESSAGE_MAP() + +CSpeechDispensor::CSpeechDispensor() : CBackground(), _dragItem(nullptr), + _fieldE0(0), _state(0), _fieldEC(0), _fieldF8(0), _seasonNum(SEASON_SUMMER) { +} + void CSpeechDispensor::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldE0, indent); - file->writeNumberLine(_fieldE4, indent); + file->writeNumberLine(_state, indent); file->writeNumberLine(_fieldEC, indent); - file->writeNumberLine(_fieldF0, indent); - file->writeNumberLine(_fieldF4, indent); + file->writeNumberLine(_itemPos.x, indent); + file->writeNumberLine(_itemPos.y, indent); file->writeNumberLine(_fieldF8, indent); - file->writeNumberLine(_fieldFC, indent); + file->writeNumberLine(_seasonNum, indent); CBackground::save(file, indent); } @@ -40,14 +51,93 @@ void CSpeechDispensor::save(SimpleFile *file, int indent) { void CSpeechDispensor::load(SimpleFile *file) { file->readNumber(); _fieldE0 = file->readNumber(); - _fieldE4 = file->readNumber(); + _state = file->readNumber(); _fieldEC = file->readNumber(); - _fieldF0 = file->readNumber(); - _fieldF4 = file->readNumber(); + _itemPos.x = file->readNumber(); + _itemPos.y = file->readNumber(); _fieldF8 = file->readNumber(); - _fieldFC = file->readNumber(); + _seasonNum = (Season)file->readNumber(); CBackground::load(file); } +bool CSpeechDispensor::FrameMsg(CFrameMsg *msg) { + if (_fieldEC || _seasonNum == SEASON_SUMMER || _seasonNum == SEASON_SPRING) + return true; + + CGameObject *dragObject = getDraggingObject(); + if (!_dragItem && dragObject && getView() == findView()) { + if (dragObject->isEquals("Perch")) { + petDisplayMessage(1, "This stick is too short to reach the branches."); + return true; + } + + if (dragObject->isEquals("LongStick")) + _dragItem = dragObject; + } + + if (_dragItem) { + Point pt(_itemPos.x + _dragItem->_bounds.left, + _itemPos.y + _dragItem->_bounds.top); + if (!checkPoint(pt, true)) + return true; + + switch (_state) { + case 0: + playSound("z#93.wav"); + if (_seasonNum == SEASON_WINTER) { + petDisplayMessage(1, "You cannot get this, it is frozen to the branch."); + _fieldE0 = false; + _state = 1; + } else { + if (++_fieldE0 >= 5) { + CActMsg actMsg("PlayerGetsSpeechCentre"); + actMsg.execute("SeasonalAdjust"); + CSpeechFallsFromTreeMsg fallMsg(pt); + fallMsg.execute("SpeechCentre"); + + _fieldEC = true; + _fieldE0 = false; + } + + _state = 1; + } + break; + + case 2: + _state = 0; + ++_fieldE0; + break; + + default: + break; + } + } + + return true; +} + +bool CSpeechDispensor::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + if (!_fieldEC) { + playSound("z#93.wav"); + if (_fieldF8) { + petDisplayMessage(1, "Sadly, this is out of your reach."); + } else { + petDisplayMessage(1, "You can't pick this up on account of it being stuck to the branch."); + } + } + + return true; +} + +bool CSpeechDispensor::StatusChangeMsg(CStatusChangeMsg *msg) { + _fieldF8 = msg->_newStatus == 1; + return true; +} + +bool CSpeechDispensor::ChangeSeasonMsg(CChangeSeasonMsg *msg) { + _seasonNum = (Season)(((int)_seasonNum + 1) % 4); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/speech_dispensor.h b/engines/titanic/game/speech_dispensor.h index 3b877e8d99..038cc024cc 100644 --- a/engines/titanic/game/speech_dispensor.h +++ b/engines/titanic/game/speech_dispensor.h @@ -28,17 +28,22 @@ namespace Titanic { class CSpeechDispensor : public CBackground { + DECLARE_MESSAGE_MAP; + bool FrameMsg(CFrameMsg *msg); + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool ChangeSeasonMsg(CChangeSeasonMsg *msg); private: int _fieldE0; - int _fieldE4; - int _fieldE8; + int _state; + CGameObject *_dragItem; int _fieldEC; - int _fieldF0; - int _fieldF4; + Point _itemPos; int _fieldF8; - int _fieldFC; + Season _seasonNum; public: CLASSDEF; + CSpeechDispensor(); /** * Save the data for the class to file diff --git a/engines/titanic/game/starling_puret.cpp b/engines/titanic/game/starling_puret.cpp index 359ad774df..2f1909d963 100644 --- a/engines/titanic/game/starling_puret.cpp +++ b/engines/titanic/game/starling_puret.cpp @@ -24,16 +24,56 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CStarlingPuret, CGameObject) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CStarlingPuret::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_flag, indent); CGameObject::save(file, indent); } void CStarlingPuret::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _flag = file->readNumber(); CGameObject::load(file); } +bool CStarlingPuret::StatusChangeMsg(CStatusChangeMsg *msg) { + _flag = msg->_newStatus == 1; + if (_flag) { + CStatusChangeMsg changeMsg; + changeMsg._newStatus = 1; + changeMsg.execute("StarlingLoop01"); + } + + return true; +} + +bool CStarlingPuret::EnterViewMsg(CEnterViewMsg *msg) { + if (_flag) { + CStatusChangeMsg changeMsg; + changeMsg._newStatus = 1; + changeMsg.execute("PromDeckStarlings"); + + playMovie(MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + CSignalObject signalMsg; + signalMsg._numValue = 4; + signalMsg.execute("PromDeckStarlings"); + _flag = false; + } + + return true; +} + +bool CStarlingPuret::MovieEndMsg(CMovieEndMsg *msg) { + CActMsg actMsg("StarlingsDead"); + actMsg.execute("FanController"); + actMsg.execute("BirdSauceDisp"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/starling_puret.h b/engines/titanic/game/starling_puret.h index fcd3319958..62a6173093 100644 --- a/engines/titanic/game/starling_puret.h +++ b/engines/titanic/game/starling_puret.h @@ -28,11 +28,15 @@ namespace Titanic { class CStarlingPuret : public CGameObject { + DECLARE_MESSAGE_MAP; + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); private: - int _value; + bool _flag; public: CLASSDEF; - CStarlingPuret() : CGameObject(), _value(0) {} + CStarlingPuret() : CGameObject(), _flag(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/stop_phonograph_button.cpp b/engines/titanic/game/stop_phonograph_button.cpp index d18f4713ac..75e0ca9337 100644 --- a/engines/titanic/game/stop_phonograph_button.cpp +++ b/engines/titanic/game/stop_phonograph_button.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CStopPhonographButton, CBackground) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CStopPhonographButton::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CBackground::save(file, indent); @@ -34,4 +38,19 @@ void CStopPhonographButton::load(SimpleFile *file) { CBackground::load(file); } +bool CStopPhonographButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CQueryPhonographState queryMsg; + queryMsg.execute(getParent()); + + if (!queryMsg._value) { + playMovie(0, 1, 0); + playMovie(1, 0, 0); + + CPhonographStopMsg stopMsg; + stopMsg.execute(getParent()); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/stop_phonograph_button.h b/engines/titanic/game/stop_phonograph_button.h index b469375e20..d416c4f8fe 100644 --- a/engines/titanic/game/stop_phonograph_button.h +++ b/engines/titanic/game/stop_phonograph_button.h @@ -28,6 +28,8 @@ namespace Titanic { class CStopPhonographButton : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sub_glass.cpp b/engines/titanic/game/sub_glass.cpp index f1349f06ea..041f49097d 100644 --- a/engines/titanic/game/sub_glass.cpp +++ b/engines/titanic/game/sub_glass.cpp @@ -24,17 +24,25 @@ namespace Titanic { -CSUBGlass::CSUBGlass() : _fieldBC(0), _fieldC0(0), _fieldC4(1), _fieldC8(0) { +BEGIN_MESSAGE_MAP(CSUBGlass, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(SignalObject) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + +CSUBGlass::CSUBGlass() : _fieldBC(0), _startFrame(0), _endFrame(1), _signalStartFrame(0) { } void CSUBGlass::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); - file->writeNumberLine(_fieldC4, indent); - file->writeNumberLine(_fieldC8, indent); - file->writeNumberLine(_fieldCC, indent); - file->writeQuotedLine(_string, indent); + file->writeNumberLine(_startFrame, indent); + file->writeNumberLine(_endFrame, indent); + file->writeNumberLine(_signalStartFrame, indent); + file->writeNumberLine(_signalEndFrame, indent); + file->writeQuotedLine(_target, indent); CGameObject::save(file, indent); } @@ -42,13 +50,58 @@ void CSUBGlass::save(SimpleFile *file, int indent) { void CSUBGlass::load(SimpleFile *file) { file->readNumber(); _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); - _fieldC4 = file->readNumber(); - _fieldC8 = file->readNumber(); - _fieldCC = file->readNumber(); - _string = file->readString(); + _startFrame = file->readNumber(); + _endFrame = file->readNumber(); + _signalStartFrame = file->readNumber(); + _signalEndFrame = file->readNumber(); + _target = file->readString(); CGameObject::load(file); } +bool CSUBGlass::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + return true; +} + +bool CSUBGlass::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + if (!_fieldBC && _startFrame >= 0) { + _fieldBC = true; + playMovie(_startFrame, _endFrame, MOVIE_NOTIFY_OBJECT); + playSound("z#30.wav"); + } + + return true; +} + +bool CSUBGlass::MovieEndMsg(CMovieEndMsg *msg) { + if (msg->_endFrame == _endFrame) { + _fieldBC = true; + CSignalObject signalMsg(getName(), 1); + signalMsg.execute(_target); + } + + return true; +} + +bool CSUBGlass::SignalObject(CSignalObject *msg) { + if (msg->_numValue == 1) { + setVisible(true); + + if (_signalStartFrame >= 0) { + playMovie(_signalStartFrame, _signalEndFrame, MOVIE_GAMESTATE); + playSound("z#30.wav"); + _fieldBC = false; + } + } + + return true; +} + +bool CSUBGlass::LeaveViewMsg(CLeaveViewMsg *msg) { + _fieldBC = false; + setVisible(true); + loadFrame(0); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sub_glass.h b/engines/titanic/game/sub_glass.h index aab5c8400e..22d16ff4d5 100644 --- a/engines/titanic/game/sub_glass.h +++ b/engines/titanic/game/sub_glass.h @@ -28,13 +28,19 @@ namespace Titanic { class CSUBGlass : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool SignalObject(CSignalObject *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); private: int _fieldBC; - int _fieldC0; - int _fieldC4; - int _fieldC8; - int _fieldCC; - CString _string; + int _startFrame; + int _endFrame; + int _signalStartFrame; + int _signalEndFrame; + CString _target; public: CLASSDEF; CSUBGlass(); diff --git a/engines/titanic/game/sub_wrapper.cpp b/engines/titanic/game/sub_wrapper.cpp index dcc489316b..4080487d6d 100644 --- a/engines/titanic/game/sub_wrapper.cpp +++ b/engines/titanic/game/sub_wrapper.cpp @@ -24,16 +24,56 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CSUBWrapper, CGameObject) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(SignalObject) +END_MESSAGE_MAP() + void CSUBWrapper::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_flag, indent); CGameObject::save(file, indent); } void CSUBWrapper::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _flag = file->readNumber(); CGameObject::load(file); } +bool CSUBWrapper::MovieEndMsg(CMovieEndMsg *msg) { + if (_flag) { + stopMovie(); + setVisible(false); + _flag = false; + } + + return true; +} + +bool CSUBWrapper::SignalObject(CSignalObject *msg) { + switch (msg->_numValue) { + case 1: + if (!_flag) { + loadFrame(0); + setVisible(true); + playMovie(MOVIE_NOTIFY_OBJECT); + _flag = true; + } + break; + + case 2: + if (!_flag) { + setVisible(true); + _flag = true; + } + break; + + default: + break; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sub_wrapper.h b/engines/titanic/game/sub_wrapper.h index 81f8fdc941..1094a2e677 100644 --- a/engines/titanic/game/sub_wrapper.h +++ b/engines/titanic/game/sub_wrapper.h @@ -28,11 +28,14 @@ namespace Titanic { class CSUBWrapper : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MovieEndMsg(CMovieEndMsg *msg); + bool SignalObject(CSignalObject *msg); public: - int _value; + bool _flag; public: CLASSDEF; - CSUBWrapper() : CGameObject(), _value(0) {} + CSUBWrapper() : CGameObject(), _flag(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/sweet_bowl.cpp b/engines/titanic/game/sweet_bowl.cpp index e14f900e77..d0a2525bc4 100644 --- a/engines/titanic/game/sweet_bowl.cpp +++ b/engines/titanic/game/sweet_bowl.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CSweetBowl, CGameObject) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + void CSweetBowl::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -34,4 +40,28 @@ void CSweetBowl::load(SimpleFile *file) { CGameObject::load(file); } +bool CSweetBowl::MovieEndMsg(CMovieEndMsg *msg) { + setVisible(false); + return true; +} + +bool CSweetBowl::EnterViewMsg(CEnterViewMsg *msg) { + setVisible(false); + loadSound("b#43.wav"); + playSound("b#42.wav"); + return true; +} + +bool CSweetBowl::ActMsg(CActMsg *msg) { + if (msg->_action == "Jiggle") { + setVisible(true); + playMovie(MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + playSound(getRandomNumber(1) == 1 ? "b#42.wav" : "b#43.wav"); + } + + petDisplayMessage(isEquals("BowlNutsRustler") ? + "A bowl of pistachio nuts." : "Not a bowl of pistachio nuts."); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sweet_bowl.h b/engines/titanic/game/sweet_bowl.h index cf3d0023a4..53433c8c08 100644 --- a/engines/titanic/game/sweet_bowl.h +++ b/engines/titanic/game/sweet_bowl.h @@ -28,6 +28,10 @@ namespace Titanic { class CSweetBowl : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MovieEndMsg(CMovieEndMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool ActMsg(CActMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/television.cpp b/engines/titanic/game/television.cpp index af6bdb8c03..ba30fbe281 100644 --- a/engines/titanic/game/television.cpp +++ b/engines/titanic/game/television.cpp @@ -20,10 +20,12 @@ * */ -#include "titanic/titanic.h" #include "titanic/game/television.h" -#include "titanic/pet_control/pet_control.h" #include "titanic/game/get_lift_eye2.h" +#include "titanic/core/project_item.h" +#include "titanic/carry/magazine.h" +#include "titanic/pet_control/pet_control.h" +#include "titanic/titanic.h" namespace Titanic { @@ -121,16 +123,16 @@ bool CTelevision::LeaveViewMsg(CLeaveViewMsg *msg) { } bool CTelevision::ChangeSeasonMsg(CChangeSeasonMsg *msg) { - if (msg->_season.compareTo("Autumn")) { + if (msg->_season == "Autumn") { _v1 = 545; _v3 = 0; - } else if (msg->_season.compareTo("Winter")) { + } else if (msg->_season == "Winter") { _v1 = 503; _v3 = 0; - } else if (msg->_season.compareTo("Spring")) { + } else if (msg->_season == "Spring") { _v1 = 517; _v3 = 0; - } else if (msg->_season.compareTo("Winter")) { + } else if (msg->_season == "Summer") { _v1 = 531; _v3 = 0; } @@ -229,7 +231,7 @@ bool CTelevision::PETActivateMsg(CPETActivateMsg *msg) { } bool CTelevision::MovieEndMsg(CMovieEndMsg *msg) { - if (g_vm->getRandomNumber(6) == 0) { + if (getRandomNumber(6) == 0) { CParrotSpeakMsg parrotMsg("Television", ""); parrotMsg.execute("PerchedParrot"); } @@ -237,10 +239,15 @@ bool CTelevision::MovieEndMsg(CMovieEndMsg *msg) { if (_fieldE0 == 3 && compareRoomNameTo("SGTState") && !getPassengerClass()) { playSound("z#47.wav", 100, 0, 0); _soundHandle = playSound("b#20.wav", 100, 0, 0); - CTreeItem *magazine = getRoot()->findByName("Magazine"); + CMagazine *magazine = dynamic_cast<CMagazine *>(getRoot()->findByName("Magazine")); if (magazine) { - warning("TODO: CTelevision::MovieEndMsg"); + CPetControl *pet = getPetControl(); + uint roomFlags = pet->getRoomFlags(); + + debugC(kDebugScripts, "Assigned room - %d", roomFlags); + magazine->addMail(roomFlags); + magazine->removeMail(roomFlags, roomFlags); } loadFrame(561); @@ -251,7 +258,7 @@ bool CTelevision::MovieEndMsg(CMovieEndMsg *msg) { loadFrame(502); else warning("There is currently nothing available for your viewing pleasure on this channel."); - } else if (_fieldE0 == 5 && *CGetLiftEye2::_v1 != "NULL") { + } else if (_fieldE0 == 5 && *CGetLiftEye2::_destObject != "NULL") { loadFrame(393 + _v4); } else { warning("There is currently nothing available for your viewing pleasure on this channel."); @@ -282,7 +289,7 @@ bool CTelevision::LightsMsg(CLightsMsg *msg) { if (pet) flag = pet->isRoom59706(); - if (msg->_field8 || !flag) + if (msg->_flag2 || !flag) _turnOn = true; return true; diff --git a/engines/titanic/game/television.h b/engines/titanic/game/television.h index 6e6d9b23c2..2e8d469bde 100644 --- a/engines/titanic/game/television.h +++ b/engines/titanic/game/television.h @@ -45,18 +45,18 @@ class CTelevision : public CBackground { bool TurnOn(CTurnOn *msg); bool LightsMsg(CLightsMsg *msg); private: + int _fieldE0; + int _fieldE4; + bool _isOn; + int _fieldEC; + int _soundHandle; +public: static int _v1; static bool _turnOn; static int _v3; static int _v4; static int _v5; static int _v6; -private: - int _fieldE0; - int _fieldE4; - bool _isOn; - int _fieldEC; - int _soundHandle; public: CLASSDEF; CTelevision(); diff --git a/engines/titanic/game/third_class_canal.cpp b/engines/titanic/game/third_class_canal.cpp index 6b0a101f7b..97b559e35d 100644 --- a/engines/titanic/game/third_class_canal.cpp +++ b/engines/titanic/game/third_class_canal.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CThirdClassCanal, CBackground) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CThirdClassCanal::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CBackground::save(file, indent); @@ -34,4 +38,9 @@ void CThirdClassCanal::load(SimpleFile *file) { CBackground::load(file); } +bool CThirdClassCanal::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + petDisplayMessage("This area is off limits to passengers."); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/third_class_canal.h b/engines/titanic/game/third_class_canal.h index f6fc471444..d0be8c05aa 100644 --- a/engines/titanic/game/third_class_canal.h +++ b/engines/titanic/game/third_class_canal.h @@ -28,6 +28,8 @@ namespace Titanic { class CThirdClassCanal : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/throw_tv_down_well.cpp b/engines/titanic/game/throw_tv_down_well.cpp index c8a9fc7c9e..9de028cbde 100644 --- a/engines/titanic/game/throw_tv_down_well.cpp +++ b/engines/titanic/game/throw_tv_down_well.cpp @@ -24,18 +24,73 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CThrowTVDownWell, CGameObject) + ON_MESSAGE(ActMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(MovieFrameMsg) +END_MESSAGE_MAP() + void CThrowTVDownWell::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_strValue, indent); - file->writeNumberLine(_numValue, indent); + file->writeQuotedLine(_viewName, indent); + file->writeNumberLine(_flag, indent); CGameObject::save(file, indent); } void CThrowTVDownWell::load(SimpleFile *file) { file->readNumber(); - _strValue = file->readString(); - _numValue = file->readNumber(); + _viewName = file->readString(); + _flag = file->readNumber(); CGameObject::load(file); } +bool CThrowTVDownWell::ActMsg(CActMsg *msg) { + if (msg->_action == "ThrowTVDownWell" && !_flag) { + CString viewName = getFullViewName(); + lockMouse(); + addTimer(1, 8000, 0); + + CActMsg actMsg("ThrownTVDownWell"); + actMsg.execute("BOWTelevisionMonitor"); + } + + return true; +} + +bool CThrowTVDownWell::EnterViewMsg(CEnterViewMsg *msg) { + playMovie(MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + movieEvent(49); + return true; +} + +bool CThrowTVDownWell::MovieEndMsg(CMovieEndMsg *msg) { + sleep(2000); + changeView("ParrotLobby.Node 11.N"); + playSound("z#471.wav"); + addTimer(2, 7000, 0); + return true; +} + +bool CThrowTVDownWell::TimerMsg(CTimerMsg *msg) { + if (msg->_actionVal == 1) { + changeView("ParrotLobby.Node 10.N"); + } else if (msg->_actionVal == 2) { + playSound("z#468.wav", 50); + sleep(1500); + changeView(_viewName); + _viewName = "NULL"; + unlockMouse(); + playSound("z#47.wav"); + } + + return true; +} + +bool CThrowTVDownWell::MovieFrameMsg(CMovieFrameMsg *msg) { + playSound("z#470.wav"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/throw_tv_down_well.h b/engines/titanic/game/throw_tv_down_well.h index b6003aa3ef..c9e8fd57a9 100644 --- a/engines/titanic/game/throw_tv_down_well.h +++ b/engines/titanic/game/throw_tv_down_well.h @@ -28,12 +28,18 @@ namespace Titanic { class CThrowTVDownWell : public CGameObject { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool TimerMsg(CTimerMsg *msg); + bool MovieFrameMsg(CMovieFrameMsg *msg); public: - CString _strValue; - int _numValue; + CString _viewName; + bool _flag; public: CLASSDEF; - CThrowTVDownWell() : CGameObject(), _numValue(0) {} + CThrowTVDownWell() : CGameObject(), _flag(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/titania_still_control.cpp b/engines/titanic/game/titania_still_control.cpp index 67866ecdcb..1ce0a85b4e 100644 --- a/engines/titanic/game/titania_still_control.cpp +++ b/engines/titanic/game/titania_still_control.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CTitaniaStillControl, CGameObject) + ON_MESSAGE(SetFrameMsg) + ON_MESSAGE(VisibleMsg) +END_MESSAGE_MAP() + void CTitaniaStillControl::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -34,4 +39,15 @@ void CTitaniaStillControl::load(SimpleFile *file) { CGameObject::load(file); } +bool CTitaniaStillControl::SetFrameMsg(CSetFrameMsg *msg) { + loadFrame(msg->_frameNumber); + setVisible(true); + return true; +} + +bool CTitaniaStillControl::VisibleMsg(CVisibleMsg *msg) { + setVisible(false); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/titania_still_control.h b/engines/titanic/game/titania_still_control.h index 66c3045187..b77227a539 100644 --- a/engines/titanic/game/titania_still_control.h +++ b/engines/titanic/game/titania_still_control.h @@ -28,6 +28,9 @@ namespace Titanic { class CTitaniaStillControl : public CGameObject { + DECLARE_MESSAGE_MAP; + bool SetFrameMsg(CSetFrameMsg *msg); + bool VisibleMsg(CVisibleMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/tow_parrot_nav.cpp b/engines/titanic/game/tow_parrot_nav.cpp index 9361808870..57f1649add 100644 --- a/engines/titanic/game/tow_parrot_nav.cpp +++ b/engines/titanic/game/tow_parrot_nav.cpp @@ -21,9 +21,14 @@ */ #include "titanic/game/tow_parrot_nav.h" +#include "titanic/npcs/parrot.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CTOWParrotNav, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CTOWParrotNav::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CGameObject::save(file, indent); @@ -34,4 +39,16 @@ void CTOWParrotNav::load(SimpleFile *file) { CGameObject::load(file); } +bool CTOWParrotNav::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CActMsg actMsg("EnteringFromTOW"); + actMsg.execute("PerchedParrot"); + + CString clipString = "_EXIT,36,1,N,9,3,N"; + if (CParrot::_v4) + clipString += 'a'; + changeView("ParrotLobby.Node 3.N", clipString); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/tow_parrot_nav.h b/engines/titanic/game/tow_parrot_nav.h index 17618ff6de..252d9b631d 100644 --- a/engines/titanic/game/tow_parrot_nav.h +++ b/engines/titanic/game/tow_parrot_nav.h @@ -28,6 +28,8 @@ namespace Titanic { class CTOWParrotNav : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/transport/gondolier.cpp b/engines/titanic/game/transport/gondolier.cpp index f731e45bde..8c28ff9b66 100644 --- a/engines/titanic/game/transport/gondolier.cpp +++ b/engines/titanic/game/transport/gondolier.cpp @@ -24,14 +24,31 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CGondolier, CTransport) + ON_MESSAGE(StatusChangeMsg) +END_MESSAGE_MAP() + +int CGondolier::_v1; +int CGondolier::_v2; + void CGondolier::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); + file->writeNumberLine(_v1, indent); + file->writeNumberLine(_v2, indent); CTransport::save(file, indent); } void CGondolier::load(SimpleFile *file) { file->readNumber(); + _v1 = file->readNumber(); + _v2 = file->readNumber(); CTransport::load(file); } +bool CGondolier::StatusChangeMsg(CStatusChangeMsg *msg) { + CShowTextMsg textMsg("Only First Class passengers are allowed to use the Gondoliers."); + textMsg.execute("PET"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/transport/gondolier.h b/engines/titanic/game/transport/gondolier.h index ac1617256f..3b1e6d5a8a 100644 --- a/engines/titanic/game/transport/gondolier.h +++ b/engines/titanic/game/transport/gondolier.h @@ -28,6 +28,11 @@ namespace Titanic { class CGondolier : public CTransport { + DECLARE_MESSAGE_MAP; + bool StatusChangeMsg(CStatusChangeMsg *msg); +private: + static int _v1; + static int _v2; public: CLASSDEF; diff --git a/engines/titanic/game/transport/lift.cpp b/engines/titanic/game/transport/lift.cpp index 72f832bf76..114e840007 100644 --- a/engines/titanic/game/transport/lift.cpp +++ b/engines/titanic/game/transport/lift.cpp @@ -21,28 +21,35 @@ */ #include "titanic/game/transport/lift.h" +#include "titanic/pet_control/pet_control.h" +#include "titanic/titanic.h" namespace Titanic { BEGIN_MESSAGE_MAP(CLift, CTransport) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(EnterViewMsg) ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(LeaveRoomMsg) + ON_MESSAGE(ActMsg) END_MESSAGE_MAP() int CLift::_v1; -int CLift::_v2; -int CLift::_v3; -int CLift::_v4; -int CLift::_v5; +int CLift::_elevator1Floor; +int CLift::_elevator2Floor; +int CLift::_elevator3Floor; +int CLift::_elevator4Floor; int CLift::_v6; void CLift::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_v1, indent); - file->writeNumberLine(_v2, indent); - file->writeNumberLine(_v3, indent); - file->writeNumberLine(_v4, indent); - file->writeNumberLine(_v5, indent); - file->writeNumberLine(_fieldF8, indent); + file->writeNumberLine(_elevator1Floor, indent); + file->writeNumberLine(_elevator2Floor, indent); + file->writeNumberLine(_elevator3Floor, indent); + file->writeNumberLine(_elevator4Floor, indent); + file->writeNumberLine(_liftNum, indent); file->writeNumberLine(_v6, indent); CTransport::save(file, indent); @@ -51,18 +58,260 @@ void CLift::save(SimpleFile *file, int indent) { void CLift::load(SimpleFile *file) { file->readNumber(); _v1 = file->readNumber(); - _v2 = file->readNumber(); - _v3 = file->readNumber(); - _v4 = file->readNumber(); - _v5 = file->readNumber(); - _fieldF8 = file->readNumber(); + _elevator1Floor = file->readNumber(); + _elevator2Floor = file->readNumber(); + _elevator3Floor = file->readNumber(); + _elevator4Floor = file->readNumber(); + _liftNum = file->readNumber(); _v6 = file->readNumber(); CTransport::load(file); } +bool CLift::StatusChangeMsg(CStatusChangeMsg *msg) { + CPetControl *pet = getPetControl(); + if ((!_v1 && pet->getRoomsElevatorNum() == 4) || + (!_v6 && pet->getRoomsElevatorNum() == 4)) + return true; + + int oldFloorNum = msg->_oldStatus; + int floorNum = msg->_newStatus; + int oldClass = 0, newClass = 0; + if (oldFloorNum == 19) + oldClass = 2; + if (oldFloorNum == 27) + oldClass = 3; + if (floorNum == 19) + newClass = 2; + if (floorNum == 27) + newClass = 3; + + static const int UP_FRAME_NUMBERS[40] = { + 0, 8, 13, 18, 23, 28, 33, 38, 43, 48, 53, 58, + 63, 68, 73, 78, 83, 88, 93, 118, 123, 128, 133, + 138, 143, 148, 153, 228, 233, 238, 243, 248, 253, + 258, 263, 268, 273, 278, 298, 299 + }; + static const int DOWN_FRAME_NUMBERS[39] = { + 598, 589, 584, 579, 574, 569, 564, 559, 554, 549, + 544, 539, 534, 529, 524, 519, 514, 509, 504, 479, + 474, 469, 464, 459, 454, 449, 444, 369, 364, 359, + 354, 349, 344, 339, 334, 329, 324, 319 + }; + + if (pet) + pet->setRoomsFloorNum(floorNum); + if (pet->getRoomsElevatorNum() == 2 || pet->getRoomsElevatorNum() == 4) { + if (floorNum > 27) + floorNum = 27; + if (oldFloorNum > 27) + oldFloorNum = 27; + } + + changeView("Lift.Node 1.N"); + CTurnOn onMsg; + onMsg.execute("LiftHood"); + + CString debugStr; + if (floorNum > oldFloorNum) { + // Animate lift going up + _startFrame = UP_FRAME_NUMBERS[oldFloorNum - 1]; + _endFrame = UP_FRAME_NUMBERS[floorNum - 1]; + + if (oldClass == newClass) { + debugStr = CString::format("Same (%d-%d)", _startFrame, _endFrame); + playMovie(_startFrame, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } else if (oldClass == 1 && newClass == 2) { + debugStr = CString::format("1 to 2 (%d-108, 108-%d)", _startFrame, _endFrame); + playMovie(_startFrame, 108, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playMovie(108, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } else if (oldClass == 1 && newClass == 3) { + debugStr = CString::format("1 to 3 (%d-108, 108-190, 190-%d)", _startFrame, _endFrame); + playMovie(_startFrame, 108, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playMovie(108, 190, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playMovie(190, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } else { + debugStr = CString::format("2 to 3 (%d-190, 190-%d)", _startFrame, _endFrame); + playMovie(_startFrame, 190, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playMovie(190, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + } + + if (floorNum < oldFloorNum) { + // Animate lift going down + _startFrame = DOWN_FRAME_NUMBERS[floorNum - 1]; + _endFrame = DOWN_FRAME_NUMBERS[oldFloorNum - 1]; + + if (oldClass == newClass) { + debugStr = CString::format("Same (%d-%d)", _startFrame, _endFrame); + playMovie(_startFrame, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } else if (oldClass == 3 && newClass == 2) { + debugStr = CString::format("3 to 2 (%d-407, 407-%d)", _startFrame, _endFrame); + playMovie(_startFrame, 407, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playMovie(407, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } else if (oldClass == 3 && newClass == 1) { + debugStr = CString::format("3 to 1 (%d-407, 407-489, 489-%d)", _startFrame, _endFrame); + playMovie(_startFrame, 407, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playMovie(407, 489, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playMovie(489, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } else { + debugStr = CString::format("2 to 1 (%d-489, 489-%d)", _startFrame, _endFrame); + playMovie(_startFrame, 489, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playMovie(489, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + } + + CShipSettingMsg settingMsg; + switch (pet->getRoomsElevatorNum()) { + case 1: + _elevator1Floor = floorNum; + break; + case 2: + _elevator2Floor = floorNum; + _elevator4Floor = oldFloorNum; + settingMsg._value = oldFloorNum; + settingMsg.execute("SGTStateroomTV"); + break; + case 3: + _elevator3Floor = floorNum; + break; + case 4: + _elevator4Floor = floorNum; + break; + default: + break; + } + + debugC(1, kDebugScripts, "%s", debugStr.c_str()); + return true; +} + +bool CLift::MovieEndMsg(CMovieEndMsg *msg) { + switch (msg->_endFrame) { + case 108: + setGlobalSoundVolume(-4, 1, 2); + setGlobalSoundVolume(-2, 1, 1); + break; + + case 190: + setGlobalSoundVolume(-4, 1, 1); + setGlobalSoundVolume(-2, 1, 2); + break; + + case 407: + setGlobalSoundVolume(-4, 1, 0); + setGlobalSoundVolume(-2, 1, 1); + break; + + case 489: + setGlobalSoundVolume(-4, 1, 1); + setGlobalSoundVolume(-2, 1, 0); + break; + + default: { + CActMsg actMsg("LiftArrive"); + actMsg.execute("Liftbot"); + sleep(500); + playSound("352 gp button 1.wav"); + + CTurnOff offMsg; + offMsg.execute("LiftHood"); + changeView("Lift.Node 1.W"); + break; + } + } + + return true; +} + +bool CLift::EnterViewMsg(CEnterViewMsg *msg) { + static const int FRAME_NUMBERS[40] = { + 0, 8, 13, 18, 23, 28, 33, 38, 43, 48, 53, 58, 63, 68, 73, + 78, 83, 88, 93, 118, 123, 128, 133, 138, 143, 148, 153, + 228, 233, 238, 243, 248, 253, 258, 263, 268, 273, 278, 298 + }; + + CPetControl *pet = getPetControl(); + loadFrame(FRAME_NUMBERS[pet->getRoomsFloorNum() - 1]); + return true; +} + bool CLift::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CLift::handleEvent"); + if (isEquals("Well")) { + CPetControl *pet = getPetControl(); + int floorNum = pet->getRoomsFloorNum(); + int elevNum = pet->getRoomsElevatorNum(); + loadSound("z#520.wav"); + loadSound("z#519.wav"); + loadSound("z#518.wav"); + + if (elevNum == 4 && _v1 == 1 && !_v6) { + CVisibleMsg visibleMsg; + visibleMsg.execute("GetLiftEye"); + } + + if (floorNum < 20) { + playGlobalSound("z#520.wav", -2, true, true, 0); + playGlobalSound("z#519.wav", -4, false, true, 1); + playGlobalSound("z#518.wav", -4, false, true, 2); + } else if (floorNum < 28) { + playGlobalSound("z#520.wav", -4, false, true, 0); + playGlobalSound("z#519.wav", -2, true, true, 1); + playGlobalSound("z#518.wav", -4, false, true, 2); + } else { + playGlobalSound("z#520.wav", -4, false, true, 0); + playGlobalSound("z#519.wav", -4, false, true, 1); + playGlobalSound("z#518.wav", -2, true, true, 2); + } + } + + return true; +} + +bool CLift::LeaveRoomMsg(CLeaveRoomMsg *msg) { + stopGlobalSound(true, -1); + + CPetControl *pet = getPetControl(); + if (pet->getRoomsElevatorNum() == 4 && _v1 == 1 && !_v6) { + CVisibleMsg visibleMsg; + visibleMsg.execute("Eye2"); + } + + return true; +} + +bool CLift::ActMsg(CActMsg *msg) { + if (msg->_action == "LoseHead") { + _v1 = 0; + _v6 = 0; + + CActMsg actMsg1("Lift.Node 2.N"); + actMsg1.execute("RPanInLiftW"); + CActMsg actMsg2("Lift.Node 2.S"); + actMsg2.execute("LPanInLiftW"); + } else if (msg->_action == "AddWrongHead") { + _v1 = 1; + _v6 = 0; + + CActMsg actMsg1("Lift.Node 1.N"); + actMsg1.execute("RPanInLiftW"); + CActMsg actMsg2("Lift.Node 1.S"); + actMsg2.execute("LPanInLiftW"); + } else if (msg->_action == "AddRightHead") { + _v1 = 1; + _v6 = 1; + petSetRooms1D4(0); + + CActMsg actMsg1("Lift.Node 1.N"); + actMsg1.execute("RPanInLiftW"); + CActMsg actMsg2("Lift.Node 1.S"); + actMsg2.execute("LPanInLiftW"); + CActMsg actMsg3("ActivateLift"); + actMsg3.execute("Liftbot"); + } + + CVisibleMsg visibleMsg; + visibleMsg.execute("LiftbotWithoutHead"); return true; } diff --git a/engines/titanic/game/transport/lift.h b/engines/titanic/game/transport/lift.h index 4595f0fec2..c45d2b64d0 100644 --- a/engines/titanic/game/transport/lift.h +++ b/engines/titanic/game/transport/lift.h @@ -30,19 +30,24 @@ namespace Titanic { class CLift : public CTransport { DECLARE_MESSAGE_MAP; + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); -private: + bool LeaveRoomMsg(CLeaveRoomMsg *msg); + bool ActMsg(CActMsg *msg); +public: static int _v1; - static int _v2; - static int _v3; - static int _v4; - static int _v5; + static int _elevator1Floor; + static int _elevator2Floor; + static int _elevator3Floor; + static int _elevator4Floor; static int _v6; - int _fieldF8; + int _liftNum; public: CLASSDEF; - CLift() : CTransport(), _fieldF8(1) {} + CLift() : CTransport(), _liftNum(1) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/transport/lift_indicator.cpp b/engines/titanic/game/transport/lift_indicator.cpp index 582de8ad3b..7471affc36 100644 --- a/engines/titanic/game/transport/lift_indicator.cpp +++ b/engines/titanic/game/transport/lift_indicator.cpp @@ -21,23 +21,32 @@ */ #include "titanic/game/transport/lift_indicator.h" +#include "titanic/game/transport/lift.h" +#include "titanic/pet_control/pet_control.h" +#include "titanic/titanic.h" namespace Titanic { BEGIN_MESSAGE_MAP(CLiftindicator, CLift) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(PETActivateMsg) + ON_MESSAGE(MovieEndMsg) ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(LeaveRoomMsg) + ON_MESSAGE(TimerMsg) END_MESSAGE_MAP() CLiftindicator::CLiftindicator() : CLift(), - _fieldFC(0), _field108(0), _field10C(0) { + _fieldFC(0), _start(0), _end(0) { } void CLiftindicator::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldFC, indent); - file->writePoint(_pos2, indent); - file->writeNumberLine(_field108, indent); - file->writeNumberLine(_field10C, indent); + file->writePoint(_indicatorPos, indent); + file->writeNumberLine(_start, indent); + file->writeNumberLine(_end, indent); CLift::save(file, indent); } @@ -45,11 +54,184 @@ void CLiftindicator::save(SimpleFile *file, int indent) { void CLiftindicator::load(SimpleFile *file) { file->readNumber(); _fieldFC = file->readNumber(); - _pos2 = file->readPoint(); - _field108 = file->readNumber(); - _field10C = file->readNumber(); + _indicatorPos = file->readPoint(); + _start = file->readNumber(); + _end = file->readNumber(); CLift::load(file); } +bool CLiftindicator::EnterViewMsg(CEnterViewMsg *msg) { + double multiplier = _fieldFC * 0.037037037; + CPetControl *pet = getPetControl(); + int floorNum = pet->getRoomsFloorNum(); + debugC(kDebugScripts, "Lifts = %d,%d,%d,%d, %d", + CLift::_elevator1Floor, CLift::_elevator2Floor, + CLift::_elevator3Floor, CLift::_elevator4Floor, + floorNum); + + if ((pet->petGetRoomsWellEntry() & 1) == (_fieldFC & 1)) { + petSetRemoteTarget(); + petSetArea(PET_REMOTE); + + CString str = CString::format("You are standing outside Elevator %d", + petGetRoomsWellEntry()); + petDisplayMessage(-1, str); + + debugC(kDebugScripts, "Claiming PET - %d, Multiplier = %f", + _liftNum, multiplier); + } + + switch (_liftNum) { + case 0: + loadFrame(pet->getRoomsFloorNum()); + break; + + case 1: + case 3: + switch (petGetRoomsWellEntry()) { + case 1: + case 2: + setPosition(Point(_bounds.left, _indicatorPos.y + + (int)(multiplier * CLift::_elevator1Floor))); + _startFrame = CLift::_elevator1Floor; + break; + + case 3: + case 4: + setPosition(Point(_bounds.left, _indicatorPos.y + + (int)(multiplier * CLift::_elevator3Floor))); + _startFrame = CLift::_elevator3Floor; + break; + + default: + break; + } + break; + + case 2: + case 4: + switch (petGetRoomsWellEntry()) { + case 1: + case 2: + setPosition(Point(_bounds.left, _indicatorPos.y + + (int)(multiplier * CLift::_elevator2Floor))); + _startFrame = CLift::_elevator2Floor; + break; + + case 3: + case 4: + setPosition(Point(_bounds.left, _indicatorPos.y + + (int)(multiplier * CLift::_elevator4Floor))); + _startFrame = CLift::_elevator4Floor; + break; + + default: + break; + } + break; + + default: + break; + } + + return true; +} + +bool CLiftindicator::LeaveViewMsg(CLeaveViewMsg *msg) { + petClear(); + return true; +} + +bool CLiftindicator::PETActivateMsg(CPETActivateMsg *msg) { + double multiplier = _fieldFC * 0.037037037; + CPetControl *pet = getPetControl(); + + if (msg->_name == "Lift") { + if (petDoorOrBellbotPresent()) { + petDisplayMessage(1, "I'm sorry, you cannot enter this elevator at present " + "as a bot is in the way."); + } else { + _endFrame = pet->getRoomsFloorNum(); + + if (petGetRoomsWellEntry() == 4 && !CLift::_v6 + && pet->getRoomsFloorNum() != CLift::_elevator4Floor) { + petDisplayMessage(1, "This elevator is currently in an advanced state of non-functionality."); + } else { + _start = _indicatorPos.y + (int)(_startFrame * multiplier); + _end = _indicatorPos.y + (int)(_endFrame * multiplier); + lockMouse(); + addTimer(100); + + if (petGetRoomsWellEntry() == 2) { + CLift::_elevator4Floor = CLift::_elevator2Floor; + CShipSettingMsg settingMsg; + settingMsg._value = CLift::_elevator4Floor; + settingMsg.execute("SGTStateroomTV"); + } + + switch (petGetRoomsWellEntry()) { + case 1: + CLift::_elevator1Floor = pet->getRoomsFloorNum(); + break; + case 2: + CLift::_elevator2Floor = pet->getRoomsFloorNum(); + break; + case 3: + CLift::_elevator3Floor = pet->getRoomsFloorNum(); + break; + case 4: + CLift::_elevator4Floor = pet->getRoomsFloorNum(); + break; + default: + break; + } + + debugC(kDebugScripts, "Lifts = %d,%d,%d,%d %d", + CLift::_elevator1Floor, CLift::_elevator2Floor, + CLift::_elevator3Floor, CLift::_elevator4Floor, + petGetRoomsWellEntry()); + } + } + } + + return true; +} + +bool CLiftindicator::MovieEndMsg(CMovieEndMsg *msg) { + playSound("357 gp button 1.wav"); + sleep(100); + changeView("Lift.Node 1.N"); + + unlockMouse(); + return true; +} + +bool CLiftindicator::EnterRoomMsg(CEnterRoomMsg *msg) { + return true; +} + +bool CLiftindicator::LeaveRoomMsg(CLeaveRoomMsg *msg) { + return true; +} + +bool CLiftindicator::TimerMsg(CTimerMsg *msg) { + debugC(kDebugScripts, "Start %d, End %d", _start, _end); + + if (_start > _end) { + setPosition(Point(_bounds.left, _bounds.top - 1)); + --_start; + addTimer(20); + } else if (_start < _end) { + setPosition(Point(_bounds.left, _bounds.top + 1)); + ++_start; + addTimer(20); + } else { + CMovieEndMsg endMsg(0, 0); + endMsg.execute(this); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/transport/lift_indicator.h b/engines/titanic/game/transport/lift_indicator.h index 945f627417..5d0bc45d7b 100644 --- a/engines/titanic/game/transport/lift_indicator.h +++ b/engines/titanic/game/transport/lift_indicator.h @@ -25,17 +25,24 @@ #include "titanic/game/transport/lift.h" #include "titanic/messages/messages.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { class CLiftindicator : public CLift { DECLARE_MESSAGE_MAP; - bool EnterRoomMsg(CEnterRoomMsg *msg) { return true; } + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool PETActivateMsg(CPETActivateMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool EnterRoomMsg(CEnterRoomMsg *msg); + bool LeaveRoomMsg(CLeaveRoomMsg *msg); + bool TimerMsg(CTimerMsg *msg); private: int _fieldFC; - Point _pos2; - int _field108; - int _field10C; + Point _indicatorPos; + int _start; + int _end; public: CLASSDEF; CLiftindicator(); diff --git a/engines/titanic/game/transport/pellerator.cpp b/engines/titanic/game/transport/pellerator.cpp index e789c20a3d..5bc2423478 100644 --- a/engines/titanic/game/transport/pellerator.cpp +++ b/engines/titanic/game/transport/pellerator.cpp @@ -21,34 +21,344 @@ */ #include "titanic/game/transport/pellerator.h" +#include "titanic/core/room_item.h" namespace Titanic { +static const char *const WAVE_NAMES[10] = { + "z#465.wav", "z#456.wav", "z#455.wav", "z#453.wav", + "z#452.wav", "NoStandingInFunnyWays", "z#450.wav", + "z#449.wav", "z#435.wav", "z#434.wav" +}; + BEGIN_MESSAGE_MAP(CPellerator, CTransport) + ON_MESSAGE(StatusChangeMsg) ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(TimerMsg) END_MESSAGE_MAP() -int CPellerator::_v1; -int CPellerator::_v2; +int CPellerator::_soundHandle; +int CPellerator::_destination; + +CPellerator::CPellerator() : CTransport() { +} void CPellerator::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_v1, indent); - file->writeNumberLine(_v2, indent); + file->writeNumberLine(_soundHandle, indent); + file->writeNumberLine(_destination, indent); CTransport::save(file, indent); } void CPellerator::load(SimpleFile *file) { file->readNumber(); - _v1 = file->readNumber(); - _v2 = file->readNumber(); + _soundHandle = file->readNumber(); + _destination = file->readNumber(); CTransport::load(file); } +bool CPellerator::StatusChangeMsg(CStatusChangeMsg *msg) { + setVisible(true); + playGlobalSound("z#74.wav", -2, true, true, 0); + int classNum = getPassengerClass(); + int newDest = msg->_newStatus; + + if (msg->_newStatus == _destination) { + petDisplayMessage(1, "You are already at your chosen destination."); + } else if (classNum == 3 || (msg->_newStatus > 4 && classNum != 1)) { + petDisplayMessage(1, "Passengers of your class are not permitted to enter this area."); + } else if (newDest > _destination) { + CString name = getName(); + changeView(name == "PelleratorObject2" ? + "Pellerator.Node 1.N" : "Pellerator.Node 1.S"); + + if (name == "PelleratorObject") { + for (; _destination < newDest; ++_destination) { + switch (_destination) { + case 0: + case 1: + playMovie(315, 323, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(299, 304, 0); + playMovie(305, 313, MOVIE_GAMESTATE); + break; + + case 2: + playMovie(315, 323, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(299, 304, 0); + for (int idx = 0; idx < 5; ++idx) + playMovie(253, 263, 0); + playMovie(153, 197, 0); + for (int idx = 0; idx < 5; ++idx) + playMovie(253, 263, 0); + playMovie(290, 293, MOVIE_GAMESTATE); + break; + + case 4: + playMovie(267, 270, 0); + for (int idx = 0; idx < 5; ++idx) + playMovie(253, 263, 0); + playMovie(3, 71, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(253, 263, 0); + for (int idx = 0; idx < 7; ++idx) + playMovie(336, 341, 0); + playMovie(342, 348, MOVIE_GAMESTATE); + break; + + case 5: + playMovie(315, 323, 0); + for (int idx = 0; idx < 7; ++idx) + playMovie(299, 304, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(253, 263, 0); + playMovie(3, 71, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(299, 304, 0); + + } + } + } else { + for (; _destination < newDest; ++_destination) { + switch (_destination) { + case 0: + case 1: + playMovie(315, 323, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(299, 304, 0); + playMovie(305, 313, MOVIE_GAMESTATE); + break; + + case 2: + playMovie(315, 323, 0); + for (int idx = 0; idx < 4; ++idx) + playMovie(299, 304, 0); + for (int idx = 0; idx < 15; ++idx) + playMovie(245, 255, 0); + playMovie(264, 267, MOVIE_GAMESTATE); + ++_destination; + break; + + case 4: + playMovie(241, 244, 0); + for (int idx = 0; idx < 15; ++idx) + playMovie(245, 255, 0); + for (int idx = 0; idx < 7; ++idx) + playMovie(336, 341, 0); + playMovie(342, 348, MOVIE_GAMESTATE); + break; + + case 5: + playMovie(315, 323, 0); + for (int idx = 0; idx < 7; ++idx) + playMovie(229, 304, 0); + for (int idx = 0; idx < 12; ++idx) + playMovie(245, 255, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(299, 304, 0); + playMovie(305, 313, MOVIE_GAMESTATE); + break; + + default: + break; + } + } + } + + playMovie(264, 264, MOVIE_NOTIFY_OBJECT); + _destination = newDest; + } else if (newDest < _destination) { + CString name = getName(); + changeView(name == "PelleratorObject2" ? + "Pellerator.Node 1.N" : "Pellerator.Node 1.S"); + + if (name == "PelleratorObject") { + for (; _destination > newDest; --_destination) { + switch (_destination) { + case 0: + case 1: + playMovie(351, 359, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(336, 341, 0); + playMovie(342, 348, MOVIE_GAMESTATE); + break; + + case 3: + playMovie(241, 244, 0); + for (int idx = 0; idx < 5; ++idx) + playMovie(245, 255, 0); + playMovie(197, 239, 0); + for (int idx = 0; idx < 5; ++idx) + playMovie(245, 255, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(336, 341, 0); + playMovie(342, 348, MOVIE_GAMESTATE); + --_destination; + break; + + case 4: + playMovie(315, 323, 0); + for (int idx = 0; idx < 7; ++idx) + playMovie(299, 304, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(245, 255, 0); + playMovie(78, 149, 0); + for (int idx = 0; idx < 5; ++idx) + playMovie(245, 255, 0); + playMovie(264, 267, MOVIE_GAMESTATE); + break; + + case 5: + playMovie(351, 359, 0); + for (int idx = 0; idx < 7; ++idx) + playMovie(336, 341, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(245, 255, 0); + playMovie(78, 149, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(336, 341, 0); + playMovie(342, 348, MOVIE_GAMESTATE); + break; + + default: + break; + } + } + } else { + for (; _destination > newDest; --_destination) { + switch (_destination) { + case 0: + case 1: + playMovie(351, 359, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(336, 341, 0); + playMovie(342, 348, MOVIE_GAMESTATE); + break; + + case 3: + playMovie(267, 270, 0); + for (int idx = 0; idx < 15; ++idx) + playMovie(253, 263, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(336, 341, 0); + playMovie(342, 348, MOVIE_GAMESTATE); + --_destination; + break; + + case 4: + playMovie(315, 323, 0); + for (int idx = 0; idx < 7; ++idx) + playMovie(299, 304, 0); + for (int idx = 0; idx < 15; ++idx) + playMovie(253, 263, 0); + playMovie(290, 293, MOVIE_GAMESTATE); + break; + + case 5: + playMovie(351, 359, 0); + for (int idx = 0; idx < 7; ++idx) + playMovie(336, 341, 0); + for (int idx = 0; idx < 13; ++idx) + playMovie(253, 263, 0); + for (int idx = 0; idx < 3; ++idx) + playMovie(336, 341, 0); + playMovie(342, 348, MOVIE_GAMESTATE); + break; + + default: + break; + } + } + } + + playMovie(264, 264, MOVIE_NOTIFY_OBJECT); + _destination = newDest; + } + + CStatusChangeMsg statusMsg; + statusMsg._newStatus = _destination; + statusMsg.execute("ExitPellerator"); + + return true; +} + bool CPellerator::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CPellerator::handleEvent"); + if (isEquals("PelleratorObject")) { + for (int idx = 0; idx < 10; ++idx) + loadSound(WAVE_NAMES[idx]); + addTimer(10000); + } + + CString name = msg->_oldRoom ? msg->_oldRoom->getName() : ""; + int oldVal = _destination; + + if (name.empty()) { + _destination = 4; + oldVal = 4; + } else if (name == "PromenadeDeck") { + _destination = 0; + } else if (name == "MusicRoomLobby") { + _destination = 1; + } else if (name == "Bar") { + _destination = 2; + } else if (name == "TopOfWell") { + _destination = 4; + } else if (name == "1stClassRestaurant") { + _destination = 5; + } else if (name == "Arboretum" || name == "FrozenArboretum") { + _destination = 6; + } + + if (_destination != oldVal) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = _destination; + statusMsg.execute("ExitPellerator"); + } + + loadFrame(264); + return true; +} + +bool CPellerator::MovieEndMsg(CMovieEndMsg *msg) { + setVisible(false); + stopGlobalSound(true, -1); + + switch (_destination) { + case 0: + _soundHandle = queueSound("z#429.wav", _soundHandle); + break; + case 1: + _soundHandle = queueSound("z#430.wav", _soundHandle); + break; + case 2: + _soundHandle = queueSound("z#431.wav", _soundHandle); + break; + case 4: + _soundHandle = queueSound("z#428.wav", _soundHandle); + break; + case 5: + _soundHandle = queueSound("z#433.wav", _soundHandle); + break; + case 6: + _soundHandle = queueSound("z#432.wav", _soundHandle); + break; + default: + break; + } + + return true; +} + +bool CPellerator::TimerMsg(CTimerMsg *msg) { + if (compareRoomNameTo("Pellerator")) { + _soundHandle = queueSound(WAVE_NAMES[getRandomNumber(9)], _soundHandle); + addTimer(20000 + getRandomNumber(10000)); + } + return true; } diff --git a/engines/titanic/game/transport/pellerator.h b/engines/titanic/game/transport/pellerator.h index fa400a49cd..c634f435cc 100644 --- a/engines/titanic/game/transport/pellerator.h +++ b/engines/titanic/game/transport/pellerator.h @@ -30,12 +30,16 @@ namespace Titanic { class CPellerator : public CTransport { DECLARE_MESSAGE_MAP; + bool StatusChangeMsg(CStatusChangeMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); -private: - static int _v1; - static int _v2; + bool MovieEndMsg(CMovieEndMsg *msg); + bool TimerMsg(CTimerMsg *msg); +public: + static int _soundHandle; + static int _destination; public: CLASSDEF; + CPellerator(); /** * Save the data for the class to file diff --git a/engines/titanic/game/transport/service_elevator.cpp b/engines/titanic/game/transport/service_elevator.cpp index 1ea8d14e36..1980825cb6 100644 --- a/engines/titanic/game/transport/service_elevator.cpp +++ b/engines/titanic/game/transport/service_elevator.cpp @@ -21,15 +21,27 @@ */ #include "titanic/game/transport/service_elevator.h" +#include "titanic/core/room_item.h" +#include "titanic/npcs/doorbot.h" -namespace Titanic { +namespace Titanic { -int CServiceElevator::_v1; +BEGIN_MESSAGE_MAP(CServiceElevator, CTransport) + ON_MESSAGE(BodyInBilgeRoomMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(ServiceElevatorMsg) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(ServiceElevatorFloorRequestMsg) + ON_MESSAGE(LeaveRoomMsg) + ON_MESSAGE(OpeningCreditsMsg) +END_MESSAGE_MAP() + +bool CServiceElevator::_v1; int CServiceElevator::_v2; int CServiceElevator::_v3; CServiceElevator::CServiceElevator() : CTransport(), - _fieldF8(0), _fieldFC(0), _field100(0), _field104(0) { + _fieldF8(0), _soundHandle1(0), _timerId(0), _soundHandle2(0) { } void CServiceElevator::save(SimpleFile *file, int indent) { @@ -38,9 +50,9 @@ void CServiceElevator::save(SimpleFile *file, int indent) { file->writeNumberLine(_v2, indent); file->writeNumberLine(_v3, indent); file->writeNumberLine(_fieldF8, indent); - file->writeNumberLine(_fieldFC, indent); - file->writeNumberLine(_field100, indent); - file->writeNumberLine(_field104, indent); + file->writeNumberLine(_soundHandle1, indent); + file->writeNumberLine(_timerId, indent); + file->writeNumberLine(_soundHandle2, indent); CTransport::save(file, indent); } @@ -51,11 +63,208 @@ void CServiceElevator::load(SimpleFile *file) { _v2 = file->readNumber(); _v3 = file->readNumber(); _fieldF8 = file->readNumber(); - _fieldFC = file->readNumber(); - _field100 = file->readNumber(); - _field104 = file->readNumber(); + _soundHandle1 = file->readNumber(); + _timerId = file->readNumber(); + _soundHandle2 = file->readNumber(); CTransport::load(file); } +bool CServiceElevator::BodyInBilgeRoomMsg(CBodyInBilgeRoomMsg *msg) { + _v2 = true; + _string1 = "BilgeRoomWith.Node 2.N"; + return true; +} + +bool CServiceElevator::EnterViewMsg(CEnterViewMsg *msg) { + petShow(); + return true; +} + +bool CServiceElevator::ServiceElevatorMsg(CServiceElevatorMsg *msg) { + switch (msg->_value) { + case 1: + case 2: + case 3: { + switch (msg->_value) { + case 1: + _v3 = 0; + break; + case 2: + _v3 = 1; + break; + case 3: + _v3 = 2; + break; + } + + CServiceElevatorFloorRequestMsg requestMsg; + requestMsg.execute(this); + break; + } + + case 4: + if (!_string1.empty()) { + if (_string1 == "DeepSpace") { + disableMouse(); + _soundHandle1 = playSound("z#413.wav", 50); + _timerId = addTimer(1, 1000, 500); + } else { + changeView(_string1); + } + } + break; + + case 5: + _fieldF8 = false; + _fieldDC = _v3; + loadSound("z#423.wav"); + stopSound(_soundHandle2); + _soundHandle2 = playSound("z#423.wav", 80); + + switch (_fieldDC) { + case 0: + _string1 = "DeepSpace"; + _string2 = "a#2.wav"; + queueSound("z#416.wav", _soundHandle2, 50); + break; + + case 1: + _string1 = _v2 ? "BilgeRoomWith.Node 2.N" : "BilgeRoom.Node 1.N"; + queueSound("z#421.wav", _soundHandle2, 50); + break; + + case 2: + _string1 = _v1 ? "MoonEmbLobby.Node 1.NE" : "EmbLobby.Node 1.NE"; + queueSound("z#411.wav", _soundHandle2, 50); + break; + + default: + break; + } + + enableMouse(); + if (findRoom()->findByName("Doorbot")) + addTimer(3, 3000, 0); + break; + + default: + break; + } + + return true; +} + +bool CServiceElevator::TimerMsg(CTimerMsg *msg) { + CDoorbot *doorbot = dynamic_cast<CDoorbot *>(findRoom()->findByName("Doorbot")); + + switch (msg->_actionVal) { + case 0: + case 1: + if (!isSoundActive(_soundHandle1)) { + stopAnimTimer(_timerId); + if (msg->_actionVal == 0) { + _fieldF8 = true; + CServiceElevatorFloorChangeMsg changeMsg(_fieldDC, _v3); + changeMsg.execute(getRoom()); + _soundHandle2 = playSound("z#424.wav"); + + if (doorbot) { + CActMsg actMsg("DoorbotPlayerPressedTopButton"); + actMsg.execute(doorbot); + } + } else { + enableMouse(); + if (doorbot) { + CActMsg actMsg; + if (_v3 == 0) + actMsg._action = "DoorbotPlayerPressedBottomButton"; + else if (_v3 == 1) + actMsg._action = "DoorbotPlayerPressedMiddleButton"; + + actMsg.execute(doorbot); + } + } + } + break; + + case 3: { + CActMsg actMsg("DoorbotReachedEmbLobby"); + actMsg.execute(doorbot); + break; + } + + default: + break; + } + + return true; +} + +bool CServiceElevator::ServiceElevatorFloorRequestMsg(CServiceElevatorFloorRequestMsg *msg) { + disableMouse(); + CDoorbot *doorbot = dynamic_cast<CDoorbot *>(findRoom()->findByName("Doorbot")); + + if (doorbot && _v3 == 0) { + _soundHandle1 = playSound("z#415.wav", 50); + addTimer(1, 1000, 500); + } else if (doorbot && _v3 == 1) { + _soundHandle1 = playSound("z#417.wav", 50); + addTimer(1, 1000, 500); + } else if (_fieldDC == _v3) { + switch (_v3) { + case 0: + _soundHandle1 = playSound("z#415.wav", 50); + break; + case 1: + _soundHandle1 = playSound("z#420.wav", 50); + break; + case 2: + _soundHandle1 = playSound("z#410.wav", 50); + break; + default: + break; + } + + addTimer(1, 1000, 500); + } else { + switch (_v3) { + case 0: + _soundHandle1 = playSound("z#414.wav", 50); + break; + case 1: + _soundHandle1 = playSound(_fieldDC ? "z#419.wav" : "z#418.wav", 50); + break; + case 2: + _soundHandle1 = playSound("z#414.wav", 50); + break; + default: + break; + } + + addTimer(0, 1000, 500); + } + + return true; +} + +bool CServiceElevator::LeaveRoomMsg(CLeaveRoomMsg *msg) { + CDoorbot *doorbot = dynamic_cast<CDoorbot *>(findRoom()->findByName("Doorbot")); + + if (doorbot) { + CPutBotBackInHisBoxMsg boxMsg(0); + boxMsg.execute("Doorbot"); + doorbot->performAction(false); + enableMouse(); + } + + return true; +} + +bool CServiceElevator::OpeningCreditsMsg(COpeningCreditsMsg *msg) { + _v1 = false; + _string1 = "EmbLobby.Node 1.NE"; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/transport/service_elevator.h b/engines/titanic/game/transport/service_elevator.h index b2c135021a..5cf1f6f0d5 100644 --- a/engines/titanic/game/transport/service_elevator.h +++ b/engines/titanic/game/transport/service_elevator.h @@ -28,15 +28,23 @@ namespace Titanic { class CServiceElevator : public CTransport { + DECLARE_MESSAGE_MAP; + bool BodyInBilgeRoomMsg(CBodyInBilgeRoomMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool ServiceElevatorMsg(CServiceElevatorMsg *msg); + bool TimerMsg(CTimerMsg *msg); + bool ServiceElevatorFloorRequestMsg(CServiceElevatorFloorRequestMsg *msg); + bool LeaveRoomMsg(CLeaveRoomMsg *msg); + bool OpeningCreditsMsg(COpeningCreditsMsg *msg); private: - static int _v1; + static bool _v1; static int _v2; static int _v3; int _fieldF8; - int _fieldFC; - int _field100; - int _field104; + int _soundHandle1; + int _timerId; + int _soundHandle2; public: CLASSDEF; CServiceElevator(); diff --git a/engines/titanic/game/up_lighter.cpp b/engines/titanic/game/up_lighter.cpp index f03b8f37a0..d133a7e9df 100644 --- a/engines/titanic/game/up_lighter.cpp +++ b/engines/titanic/game/up_lighter.cpp @@ -21,9 +21,21 @@ */ #include "titanic/game/up_lighter.h" +#include "titanic/core/project_item.h" +#include "titanic/npcs/parrot.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CUpLighter, CDropTarget) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(PumpingMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(ChangeSeasonMsg) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(LeaveRoomMsg) +END_MESSAGE_MAP() + CUpLighter::CUpLighter() : CDropTarget(), _field118(0), _field11C(0), _field120(0), _field124(0) { } @@ -48,8 +60,61 @@ void CUpLighter::load(SimpleFile *file) { CDropTarget::load(file); } +bool CUpLighter::MovieEndMsg(CMovieEndMsg *msg) { + if (_field118) { + playSound("z#47.wav"); + _field124 = true; + + CVisibleMsg visibleMsg(true); + visibleMsg.execute("NoseHolder"); + CDropZoneLostObjectMsg lostMsg(nullptr); + lostMsg.execute(this); + _clipName.clear(); + _itemMatchName = "Nothing"; + _field118 = 0; + } + + return true; +} + +bool CUpLighter::PumpingMsg(CPumpingMsg *msg) { + _field118 = msg->_value; + _clipName = (_field118 && !_field124) ? "WholeSequence" : "HoseToNose"; + return true; +} + +bool CUpLighter::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CTrueTalkTriggerActionMsg triggerMsg(280245, 0, 0); + triggerMsg.execute(getRoot(), CParrot::_type, + MSGFLAG_BREAK_IF_HANDLED | MSGFLAG_CLASS_DEF | MSGFLAG_SCAN); + return true; +} + bool CUpLighter::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CUpLighter::handleEvent"); + _field11C = true; + addTimer(5000 + getRandomNumber(15000), 0); + return true; +} + +bool CUpLighter::ChangeSeasonMsg(CChangeSeasonMsg *msg) { + _field120 = msg->_season == "Spring"; + if (_field120) + addTimer(5000 + getRandomNumber(15000), 0); + return true; +} + +bool CUpLighter::TimerMsg(CTimerMsg *msg) { + if (_field120 && _field11C & !_field118) { + CActMsg actMsg("Sneeze"); + actMsg.execute(findRoom()->findByName("NoseHolder")); + addTimer(1000 + getRandomNumber(19000), 0); + } + + return true; +} + +bool CUpLighter::LeaveRoomMsg(CLeaveRoomMsg *msg) { + _field11C = false; return true; } diff --git a/engines/titanic/game/up_lighter.h b/engines/titanic/game/up_lighter.h index 2367e41314..e6a53cf7bd 100644 --- a/engines/titanic/game/up_lighter.h +++ b/engines/titanic/game/up_lighter.h @@ -29,7 +29,14 @@ namespace Titanic { class CUpLighter : public CDropTarget { + DECLARE_MESSAGE_MAP; + bool MovieEndMsg(CMovieEndMsg *msg); + bool PumpingMsg(CPumpingMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); + bool ChangeSeasonMsg(CChangeSeasonMsg *msg); + bool TimerMsg(CTimerMsg *msg); + bool LeaveRoomMsg(CLeaveRoomMsg *msg); private: int _field118; int _field11C; diff --git a/engines/titanic/game/useless_lever.cpp b/engines/titanic/game/useless_lever.cpp index e48ad55a71..82d8983710 100644 --- a/engines/titanic/game/useless_lever.cpp +++ b/engines/titanic/game/useless_lever.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CUselessLever, CToggleButton) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + void CUselessLever::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CToggleButton::save(file, indent); @@ -34,4 +39,23 @@ void CUselessLever::load(SimpleFile *file) { CToggleButton::load(file); } +bool CUselessLever::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_fieldE0) { + playMovie(15, 30, 0); + playSound("z#56.wav"); + _fieldE0 = false; + } else { + playMovie(0, 14, 0); + playSound("z#56.wav"); + _fieldE0 = true; + } + + return true; +} + +bool CUselessLever::EnterViewMsg(CEnterViewMsg *msg) { + loadFrame(_fieldE0 ? 15 : 0); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/useless_lever.h b/engines/titanic/game/useless_lever.h index 27397de216..33ed94b2ac 100644 --- a/engines/titanic/game/useless_lever.h +++ b/engines/titanic/game/useless_lever.h @@ -28,6 +28,9 @@ namespace Titanic { class CUselessLever : public CToggleButton { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/wheel_button.cpp b/engines/titanic/game/wheel_button.cpp index 19c42a8807..730a5d9005 100644 --- a/engines/titanic/game/wheel_button.cpp +++ b/engines/titanic/game/wheel_button.cpp @@ -24,14 +24,20 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CWheelButton, CBackground) + ON_MESSAGE(SignalObject) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(LeaveViewMsg) +END_MESSAGE_MAP() + CWheelButton::CWheelButton() : CBackground(), - _fieldE0(0), _fieldE4(0), _fieldE8(0) { + _fieldE0(false), _timerId(0), _fieldE8(0) { } void CWheelButton::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldE0, indent); - file->writeNumberLine(_fieldE4, indent); + file->writeNumberLine(_timerId, indent); file->writeNumberLine(_fieldE8, indent); CBackground::save(file, indent); @@ -40,10 +46,43 @@ void CWheelButton::save(SimpleFile *file, int indent) { void CWheelButton::load(SimpleFile *file) { file->readNumber(); _fieldE0 = file->readNumber(); - _fieldE4 = file->readNumber(); + _timerId = file->readNumber(); _fieldE8 = file->readNumber(); CBackground::load(file); } +bool CWheelButton::SignalObject(CSignalObject *msg) { + bool oldFlag = _fieldE0; + _fieldE0 = msg->_numValue != 0; + + if (oldFlag != _fieldE0) { + if (_fieldE0) { + _timerId = addTimer(500, 500); + } else { + stopAnimTimer(_timerId); + _timerId = 0; + setVisible(false); + } + } + + return true; +} + +bool CWheelButton::TimerMsg(CTimerMsg *msg) { + setVisible(!_visible); + makeDirty(); + return true; +} + +bool CWheelButton::LeaveViewMsg(CLeaveViewMsg *msg) { + if (_timerId) { + stopAnimTimer(_timerId); + _timerId = 0; + setVisible(false); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/wheel_button.h b/engines/titanic/game/wheel_button.h index cb089a660f..2725e60622 100644 --- a/engines/titanic/game/wheel_button.h +++ b/engines/titanic/game/wheel_button.h @@ -28,9 +28,13 @@ namespace Titanic { class CWheelButton : public CBackground { + DECLARE_MESSAGE_MAP; + bool SignalObject(CSignalObject *msg); + bool TimerMsg(CTimerMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); public: - int _fieldE0; - int _fieldE4; + bool _fieldE0; + int _timerId; int _fieldE8; public: CLASSDEF; diff --git a/engines/titanic/game/wheel_hotspot.cpp b/engines/titanic/game/wheel_hotspot.cpp index f9af594cd5..544e6f5470 100644 --- a/engines/titanic/game/wheel_hotspot.cpp +++ b/engines/titanic/game/wheel_hotspot.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CWheelHotSpot, CBackground) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(SignalObject) +END_MESSAGE_MAP() + void CWheelHotSpot::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldE0, indent); @@ -40,4 +45,39 @@ void CWheelHotSpot::load(SimpleFile *file) { CBackground::load(file); } +bool CWheelHotSpot::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_fieldE0) { + CActMsg actMsg; + + switch (_fieldE4) { + case 1: + actMsg._action = "Stop"; + actMsg.execute("CaptainsWheel"); + break; + + case 2: + actMsg._action = "Cruise"; + actMsg.execute("CaptainsWheel"); + break; + + case 3: + actMsg._action = "Go"; + actMsg.execute("CaptainsWheel"); + break; + + default: + break; + } + } else if (_fieldE4 == 3) { + petDisplayMessage("Go where?"); + } + + return true; +} + +bool CWheelHotSpot::SignalObject(CSignalObject *msg) { + _fieldE0 = msg->_numValue != 0; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/wheel_hotspot.h b/engines/titanic/game/wheel_hotspot.h index 364fe9a060..e9071a2fa4 100644 --- a/engines/titanic/game/wheel_hotspot.h +++ b/engines/titanic/game/wheel_hotspot.h @@ -28,6 +28,9 @@ namespace Titanic { class CWheelHotSpot : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool SignalObject(CSignalObject *msg); public: int _fieldE0; int _fieldE4; diff --git a/engines/titanic/game/wheel_spin.cpp b/engines/titanic/game/wheel_spin.cpp index daa9918e29..79f83873e1 100644 --- a/engines/titanic/game/wheel_spin.cpp +++ b/engines/titanic/game/wheel_spin.cpp @@ -24,16 +24,36 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CWheelSpin, CBackground) + ON_MESSAGE(SignalObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CWheelSpin::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_active, indent); CBackground::save(file, indent); } void CWheelSpin::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _active = file->readNumber(); CBackground::load(file); } +bool CWheelSpin::SignalObject(CSignalObject *msg) { + _active = msg->_numValue != 0; + setVisible(_active); + return true; +} + +bool CWheelSpin::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_active) { + CActMsg actMsg("Spin"); + actMsg.execute("CaptainsWheel"); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/wheel_spin.h b/engines/titanic/game/wheel_spin.h index 509db1a4bf..f7993f0188 100644 --- a/engines/titanic/game/wheel_spin.h +++ b/engines/titanic/game/wheel_spin.h @@ -28,11 +28,14 @@ namespace Titanic { class CWheelSpin : public CBackground { + DECLARE_MESSAGE_MAP; + bool SignalObject(CSignalObject *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: - int _value; + bool _active; public: CLASSDEF; - CWheelSpin() : CBackground(), _value(0) {} + CWheelSpin() : CBackground(), _active(false) {} /** * Save the data for the class to file diff --git a/engines/titanic/game_manager.cpp b/engines/titanic/game_manager.cpp index 9bc9e4d067..7d9dc37a10 100644 --- a/engines/titanic/game_manager.cpp +++ b/engines/titanic/game_manager.cpp @@ -153,7 +153,7 @@ void CGameManager::playClip(CMovieClip *clip, CRoomItem *oldRoom, CRoomItem *new lockInputHandler(); CScreenManager::_screenManagerPtr->_mouseCursor->hide(); - _movie->playClip(tempRect, clip->_startFrame, clip->_endFrame); + _movie->playCutscene(tempRect, clip->_startFrame, clip->_endFrame); CScreenManager::_screenManagerPtr->_mouseCursor->show(); unlockInputHandler(); } @@ -164,7 +164,7 @@ void CGameManager::update() { frameMessage(getRoom()); _timers.update(g_vm->_events->getTicksCount()); _trueTalkManager.removeCompleted(); - _trueTalkManager.update2(); + CScreenManager::_screenManagerPtr->_mouseCursor->update(); CViewItem *view = getView(); diff --git a/engines/titanic/game_state.cpp b/engines/titanic/game_state.cpp index 5628161558..8814dba831 100644 --- a/engines/titanic/game_state.cpp +++ b/engines/titanic/game_state.cpp @@ -47,7 +47,7 @@ bool CGameStateMovieList::clear() { CGameState::CGameState(CGameManager *gameManager) : _gameManager(gameManager), _gameLocation(this), _passengerClass(0), _priorClass(0), _mode(GSMODE_NONE), - _field14(0), _petActive(false), _field1C(false), _quitGame(false), + _seasonNum(SEASON_SUMMER), _petActive(false), _field1C(false), _quitGame(false), _field24(0), _nodeChangeCtr(0), _nodeEnterTicks(0), _field38(0) { } @@ -55,7 +55,7 @@ void CGameState::save(SimpleFile *file) const { file->writeNumber(_petActive); file->writeNumber(_passengerClass); file->writeNumber(_priorClass); - file->writeNumber(_field14); + file->writeNumber(_seasonNum); file->writeNumber(_field24); file->writeNumber(_field38); _gameLocation.save(file); @@ -66,7 +66,7 @@ void CGameState::load(SimpleFile *file) { _petActive = file->readNumber() != 0; _passengerClass = file->readNumber(); _priorClass = file->readNumber(); - _field14 = file->readNumber(); + _seasonNum = (Season)file->readNumber(); _field24 = file->readNumber(); _field38 = file->readNumber(); _gameLocation.load(file); diff --git a/engines/titanic/game_state.h b/engines/titanic/game_state.h index 0bfa0d5b31..70d47b55c1 100644 --- a/engines/titanic/game_state.h +++ b/engines/titanic/game_state.h @@ -35,7 +35,14 @@ class CGameManager; enum GameStateMode { GSMODE_NONE = 0, GSMODE_INTERACTIVE = 1, GSMODE_CUTSCENE = 2, - GSMODE_3 = 3, GSMODE_4 = 4, GSMODE_5 = 5, GSMODE_PENDING_LOAD = 6 + GSMODE_3 = 3, GSMODE_4 = 4, GSMODE_INSERT_CD = 5, GSMODE_PENDING_LOAD = 6 +}; + +enum Season { + SEASON_SUMMER = 0, + SEASON_AUTUMN = 1, + SEASON_WINTER = 2, + SEASON_SPRING = 3 }; PTR_LIST_ITEM(CMovie); @@ -60,7 +67,7 @@ public: int _passengerClass; int _priorClass; GameStateMode _mode; - int _field14; + Season _seasonNum; bool _petActive; bool _field1C; bool _quitGame; @@ -127,7 +134,13 @@ public: */ void addMovie(CMovie *movie); - void inc14() { _field14 = (_field14 + 1) & 3; } + /** + * Change to the next season + */ + void changeSeason() { + _seasonNum = (Season)(((int)_seasonNum + 1) & 3); + } + void set24(int v) { _field24 = v; } int get24() const { return _field24; } int getNodeChangedCtr() const { return _nodeChangeCtr; } diff --git a/engines/titanic/gfx/act_button.cpp b/engines/titanic/gfx/act_button.cpp index c84f358ca9..75c999b10f 100644 --- a/engines/titanic/gfx/act_button.cpp +++ b/engines/titanic/gfx/act_button.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CActButton, CSTButton) + ON_MESSAGE(MouseButtonUpMsg) +END_MESSAGE_MAP() + CActButton::CActButton() : CSTButton() { } @@ -37,4 +41,10 @@ void CActButton::load(SimpleFile *file) { CSTButton::load(file); } +bool CActButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + CActMsg actMsg(_actionName); + actMsg.execute(_actionTarget); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/gfx/act_button.h b/engines/titanic/gfx/act_button.h index 26e5595411..910ace1d13 100644 --- a/engines/titanic/gfx/act_button.h +++ b/engines/titanic/gfx/act_button.h @@ -28,6 +28,8 @@ namespace Titanic { class CActButton : public CSTButton { + DECLARE_MESSAGE_MAP; + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); public: CLASSDEF; CActButton(); diff --git a/engines/titanic/gfx/changes_season_button.cpp b/engines/titanic/gfx/changes_season_button.cpp index d5242ad890..584a9542f3 100644 --- a/engines/titanic/gfx/changes_season_button.cpp +++ b/engines/titanic/gfx/changes_season_button.cpp @@ -21,9 +21,14 @@ */ #include "titanic/gfx/changes_season_button.h" +#include "titanic/core/project_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CChangesSeasonButton, CSTButton) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + CChangesSeasonButton::CChangesSeasonButton() : CSTButton() { } @@ -37,4 +42,10 @@ void CChangesSeasonButton::load(SimpleFile *file) { CSTButton::load(file); } +bool CChangesSeasonButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CChangeSeasonMsg changeMsg(_actionName); + changeMsg.execute(getRoot(), nullptr, MSGFLAG_SCAN); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/gfx/changes_season_button.h b/engines/titanic/gfx/changes_season_button.h index 2b58a3199b..4f588187eb 100644 --- a/engines/titanic/gfx/changes_season_button.h +++ b/engines/titanic/gfx/changes_season_button.h @@ -28,6 +28,8 @@ namespace Titanic { class CChangesSeasonButton : public CSTButton { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: CLASSDEF; CChangesSeasonButton(); diff --git a/engines/titanic/gfx/edit_control.cpp b/engines/titanic/gfx/edit_control.cpp index 3b611e9bbe..3f3c4d4035 100644 --- a/engines/titanic/gfx/edit_control.cpp +++ b/engines/titanic/gfx/edit_control.cpp @@ -24,26 +24,27 @@ namespace Titanic { -CEditControl::CEditControl() : CGameObject(), _fieldBC(0), _fieldC0(0), - _fieldC4(0), _fieldC8(0), _fieldCC(0), _fieldD0(0), _fieldD4(2), - _fieldD8(0), _fieldDC(0), _fieldE0(0), _fieldF0(0), _fieldF4(0) +BEGIN_MESSAGE_MAP(CEditControl, CGameObject) + ON_MESSAGE(EditControlMsg) +END_MESSAGE_MAP() -{ +CEditControl::CEditControl() : CGameObject(), _fieldBC(false), _fontNumber(0), _fieldD4(2), + _textR(0), _textG(0), _textB(0), _fieldF0(0), _fieldF4(0) { } void CEditControl::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); - file->writeNumberLine(_fieldC4, indent); - file->writeNumberLine(_fieldC8, indent); - file->writeNumberLine(_fieldCC, indent); - file->writeNumberLine(_fieldD0, indent); + file->writeNumberLine(_editLeft, indent); + file->writeNumberLine(_editBottom, indent); + file->writeNumberLine(_editHeight, indent); + file->writeNumberLine(_maxTextChars, indent); + file->writeNumberLine(_fontNumber, indent); file->writeNumberLine(_fieldD4, indent); - file->writeNumberLine(_fieldD8, indent); - file->writeNumberLine(_fieldDC, indent); - file->writeNumberLine(_fieldC0, indent); - file->writeQuotedLine(_string1, indent); + file->writeNumberLine(_textR, indent); + file->writeNumberLine(_textG, indent); + file->writeNumberLine(_textB, indent); + file->writeQuotedLine(_text, indent); file->writeNumberLine(_fieldF0, indent); file->writeNumberLine(_fieldF4, indent); @@ -53,20 +54,169 @@ void CEditControl::save(SimpleFile *file, int indent) { void CEditControl::load(SimpleFile *file) { file->readNumber(); _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); - _fieldC4 = file->readNumber(); - _fieldC8 = file->readNumber(); - _fieldCC = file->readNumber(); - _fieldD0 = file->readNumber(); + _editLeft = file->readNumber(); + _editBottom = file->readNumber(); + _editHeight = file->readNumber(); + _maxTextChars = file->readNumber(); + _fontNumber = file->readNumber(); _fieldD4 = file->readNumber(); - _fieldD8 = file->readNumber(); - _fieldDC = file->readNumber(); - _fieldE0 = file->readNumber(); - _string1 = file->readString(); + _textR = file->readNumber(); + _textG = file->readNumber(); + _textB = file->readNumber(); + _text = file->readString(); _fieldF0 = file->readNumber(); _fieldF4 = file->readNumber(); CGameObject::load(file); } +bool CEditControl::EditControlMsg(CEditControlMsg *msg) { + switch (msg->_mode) { + case 0: + if (!_editLeft) { + _editHeight = _bounds.height(); + _editBottom = _bounds.bottom; + _editLeft = _bounds.left + _bounds.width() / 2; + _maxTextChars = msg->_param; + setTextFontNumber(_fontNumber); + + CEditControlMsg ctlMsg; + ctlMsg._mode = 10; + ctlMsg._param = _fieldD4; + ctlMsg.execute(this); + + ctlMsg._mode = 11; + ctlMsg._textR = _textR; + ctlMsg._textG = _textG; + ctlMsg._textB = _textB; + ctlMsg.execute(this); + } + break; + + case 1: { + _text = ""; + CEditControlMsg ctlMsg; + ctlMsg._mode = 14; + ctlMsg.execute(this); + break; + } + + case 2: { + _text = msg->_text; + CEditControlMsg ctlMsg; + ctlMsg._mode = 14; + ctlMsg.execute(this); + break; + } + + case 3: + msg->_text = _text; + break; + + case 4: + msg->_param = _text.size(); + break; + + case 5: + _maxTextChars = msg->_param; + break; + + case 6: + if (msg->_param == 8 && !_text.empty()) { + _text = _text.left(_text.size() - 1); + CEditControlMsg ctlMsg; + ctlMsg._mode = 14; + ctlMsg.execute(this); + } else if (msg->_param == 13) { + msg->_param = 1000; + } else if (msg->_param >= 32 && msg->_param < 127 + && _text.size() < _maxTextChars) { + char c = (char)msg->_param; + _text += c; + + CEditControlMsg ctlMsg; + ctlMsg._mode = 14; + ctlMsg.execute(this); + } + break; + + case 7: + setTextFontNumber(msg->_param); + break; + + case 8: + if (!_fieldBC) { + _fieldBC = true; + CEditControlMsg ctlMsg; + ctlMsg._mode = 14; + ctlMsg.execute(this); + } + break; + + case 9: + if (_fieldBC) { + _fieldBC = false; + getTextCursor()->hide(); + } + break; + + case 10: { + setTextHasBorders((msg->_param & 1) != 0); + if (msg->_param & 4) + _fieldF0 = 1; + else if (msg->_param & 8) + _fieldF0 = 2; + else + _fieldF0 = 0; + + _fieldF4 = msg->_param & 0x10; + CEditControlMsg ctlMsg; + ctlMsg._mode = 14; + ctlMsg.execute(this); + break; + } + + case 11: + setTextColor(msg->_textR, msg->_textG, msg->_textB); + break; + + case 12: + setVisible(true); + break; + + case 13: + setVisible(false); + break; + + case 14: { + makeDirty(); + CString str = _fieldF4 ? CString('*', _text.size()) : _text; + setText(str); + + int textWidth = getTextWidth(); + if (_fieldF0 == 2) { + _bounds.left = _editLeft - textWidth / 2; + _bounds.setWidth(textWidth + 16); + setTextBounds(); + makeDirty(); + } + + if (_fieldBC) { + CTextCursor *textCursor = getTextCursor(); + textCursor->show(); + textCursor->setPos(Point(_bounds.left + textWidth + 1, _bounds.top + 3)); + textCursor->setSize(Point(2, _editHeight - 6)); + textCursor->setColor(0xff, 0xff, 0xff); + textCursor->clearBounds(); + } + break; + } + + default: + break; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/gfx/edit_control.h b/engines/titanic/gfx/edit_control.h index 77d03cb225..6c02f7afb9 100644 --- a/engines/titanic/gfx/edit_control.h +++ b/engines/titanic/gfx/edit_control.h @@ -28,18 +28,20 @@ namespace Titanic { class CEditControl : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EditControlMsg(CEditControlMsg *msg); protected: - int _fieldBC; - int _fieldC0; - int _fieldC4; - int _fieldC8; - int _fieldCC; - int _fieldD0; + bool _fieldBC; + int _editLeft; + int _editBottom; + int _editHeight; + uint _maxTextChars; + int _fontNumber; int _fieldD4; - int _fieldD8; - int _fieldDC; - int _fieldE0; - CString _string1; + byte _textR; + byte _textG; + byte _textB; + CString _text; int _fieldF0; int _fieldF4; public: diff --git a/engines/titanic/gfx/move_object_button.cpp b/engines/titanic/gfx/move_object_button.cpp index bdc90a673c..bcd2b2bd76 100644 --- a/engines/titanic/gfx/move_object_button.cpp +++ b/engines/titanic/gfx/move_object_button.cpp @@ -21,9 +21,14 @@ */ #include "titanic/gfx/move_object_button.h" +#include "titanic/core/project_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CMoveObjectButton, CSTButton) + ON_MESSAGE(MouseButtonUpMsg) +END_MESSAGE_MAP() + CMoveObjectButton::CMoveObjectButton() : CSTButton(), _field11C(1) { } @@ -43,4 +48,14 @@ void CMoveObjectButton::load(SimpleFile *file) { CSTButton::load(file); } +bool CMoveObjectButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + CGameObject *obj = dynamic_cast<CGameObject *>(getRoot()->findByName(_actionTarget)); + if (obj) { + obj->petAddToInventory(); + obj->setVisible(_field11C); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/gfx/move_object_button.h b/engines/titanic/gfx/move_object_button.h index eb2fdc4ff2..46c49c36e2 100644 --- a/engines/titanic/gfx/move_object_button.h +++ b/engines/titanic/gfx/move_object_button.h @@ -28,6 +28,8 @@ namespace Titanic { class CMoveObjectButton : public CSTButton { + DECLARE_MESSAGE_MAP; + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); private: Point _pos1; int _field11C; diff --git a/engines/titanic/gfx/music_control.cpp b/engines/titanic/gfx/music_control.cpp index 85a3d777ef..317bec209f 100644 --- a/engines/titanic/gfx/music_control.cpp +++ b/engines/titanic/gfx/music_control.cpp @@ -24,15 +24,20 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CMusicControl, CBackground) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseDoubleClickMsg) +END_MESSAGE_MAP() + CMusicControl::CMusicControl() : CBackground(), - _fieldE0(0), _fieldE4(0), _fieldE8(1), _fieldEC(1) { + _controlArea(BELLS), _controlVal(0), _controlMax(1), _fieldEC(1) { } void CMusicControl::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); - file->writeNumberLine(_fieldE4, indent); - file->writeNumberLine(_fieldE8, indent); + file->writeNumberLine(_controlArea, indent); + file->writeNumberLine(_controlVal, indent); + file->writeNumberLine(_controlMax, indent); file->writeNumberLine(_fieldEC, indent); CBackground::save(file, indent); @@ -40,12 +45,24 @@ void CMusicControl::save(SimpleFile *file, int indent) { void CMusicControl::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); - _fieldE4 = file->readNumber(); - _fieldE8 = file->readNumber(); + _controlArea = (MusicControlArea)file->readNumber(); + _controlVal = file->readNumber(); + _controlMax = file->readNumber(); _fieldEC = file->readNumber(); CBackground::load(file); } +bool CMusicControl::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CMusicSettingChangedMsg changedMsg; + changedMsg.execute(this); + return true; +} + +bool CMusicControl::MouseDoubleClickMsg(CMouseDoubleClickMsg *msg) { + CMusicSettingChangedMsg changedMsg; + changedMsg.execute(this); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/gfx/music_control.h b/engines/titanic/gfx/music_control.h index 04085f789c..a0e73392f9 100644 --- a/engines/titanic/gfx/music_control.h +++ b/engines/titanic/gfx/music_control.h @@ -24,14 +24,18 @@ #define TITANIC_MUSIC_CONTROL_H #include "titanic/core/background.h" +#include "titanic/sound/music_room.h" namespace Titanic { class CMusicControl : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseDoubleClickMsg(CMouseDoubleClickMsg *msg); public: - int _fieldE0; - int _fieldE4; - int _fieldE8; + MusicControlArea _controlArea; + int _controlVal; + int _controlMax; int _fieldEC; public: CLASSDEF; diff --git a/engines/titanic/game/bilge_succubus.cpp b/engines/titanic/gfx/music_slider_pitch.cpp index ceee3f7740..5f0432e742 100644 --- a/engines/titanic/game/bilge_succubus.cpp +++ b/engines/titanic/gfx/music_slider_pitch.cpp @@ -20,32 +20,48 @@ * */ -#include "titanic/game/bilge_succubus.h" +#include "titanic/gfx/music_slider_pitch.h" namespace Titanic { -CBilgeSuccUBus::CBilgeSuccUBus() : CSuccUBus(), _field1DC(0), - _field1E0(0), _field1E4(0), _field1E8(0) { -} +BEGIN_MESSAGE_MAP(CMusicSliderPitch, CMusicSlider) + ON_MESSAGE(MusicSettingChangedMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(QueryMusicControlSettingMsg) +END_MESSAGE_MAP() -void CBilgeSuccUBus::save(SimpleFile *file, int indent) { +void CMusicSliderPitch::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_field1DC, indent); - file->writeNumberLine(_field1E0, indent); - file->writeNumberLine(_field1E4, indent); - file->writeNumberLine(_field1E8, indent); - - CSuccUBus::save(file, indent); + CMusicSlider::save(file, indent); } -void CBilgeSuccUBus::load(SimpleFile *file) { +void CMusicSliderPitch::load(SimpleFile *file) { file->readNumber(); - _field1DC = file->readNumber(); - _field1E0 = file->readNumber(); - _field1E4 = file->readNumber(); - _field1E8 = file->readNumber(); + CMusicSlider::load(file); +} + +bool CMusicSliderPitch::MusicSettingChangedMsg(CMusicSettingChangedMsg *msg) { + if (_fieldEC) { + if (++_controlVal > _controlMax) + _controlVal = 0; + + loadFrame(3 - _controlVal); + playSound("z#54.wav", 50); + } else { + playSound("z#46.wav"); + } + + return true; +} + +bool CMusicSliderPitch::EnterViewMsg(CEnterViewMsg *msg) { + loadFrame(3 - _controlVal); + return true; +} - CSuccUBus::load(file); +bool CMusicSliderPitch::QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg) { + msg->_value = _controlVal - 2; + return true; } } // End of namespace Titanic diff --git a/engines/titanic/gfx/music_slider_pitch.h b/engines/titanic/gfx/music_slider_pitch.h index 10c1d62c3a..c375c6db33 100644 --- a/engines/titanic/gfx/music_slider_pitch.h +++ b/engines/titanic/gfx/music_slider_pitch.h @@ -28,24 +28,22 @@ namespace Titanic { class CMusicSliderPitch : public CMusicSlider { + DECLARE_MESSAGE_MAP; + bool MusicSettingChangedMsg(CMusicSettingChangedMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg); public: CLASSDEF; /** * Save the data for the class to file */ - virtual void save(SimpleFile *file, int indent) { - file->writeNumberLine(1, indent); - CMusicSlider::save(file, indent); - } + virtual void save(SimpleFile *file, int indent); /** * Load the data for the class from file */ - virtual void load(SimpleFile *file) { - file->readNumber(); - CMusicSlider::load(file); - } + virtual void load(SimpleFile *file); }; } // End of namespace Titanic diff --git a/engines/titanic/gfx/music_slider_speed.cpp b/engines/titanic/gfx/music_slider_speed.cpp new file mode 100644 index 0000000000..93af5d82b7 --- /dev/null +++ b/engines/titanic/gfx/music_slider_speed.cpp @@ -0,0 +1,67 @@ +/* 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 "titanic/gfx/music_slider_speed.h" + +namespace Titanic { + +BEGIN_MESSAGE_MAP(CMusicSliderSpeed, CMusicSlider) + ON_MESSAGE(MusicSettingChangedMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(QueryMusicControlSettingMsg) +END_MESSAGE_MAP() + +void CMusicSliderSpeed::save(SimpleFile *file, int indent) { + file->writeNumberLine(1, indent); + CMusicSlider::save(file, indent); +} + +void CMusicSliderSpeed::load(SimpleFile *file) { + file->readNumber(); + CMusicSlider::load(file); +} + +bool CMusicSliderSpeed::MusicSettingChangedMsg(CMusicSettingChangedMsg *msg) { + if (_fieldEC) { + if (++_controlVal > _controlMax) + _controlVal = 0; + + loadFrame(3 - _controlVal); + playSound("z#54.wav", 50); + } else { + playSound("z#46.wav"); + } + + return true; +} + +bool CMusicSliderSpeed::EnterViewMsg(CEnterViewMsg *msg) { + loadFrame(3 - _controlVal); + return true; +} + +bool CMusicSliderSpeed::QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg) { + msg->_value = _controlVal - 1; + return true; +} + +} // End of namespace Titanic diff --git a/engines/titanic/gfx/music_slider_speed.h b/engines/titanic/gfx/music_slider_speed.h index 9814ca0312..2d54f4487c 100644 --- a/engines/titanic/gfx/music_slider_speed.h +++ b/engines/titanic/gfx/music_slider_speed.h @@ -27,26 +27,24 @@ namespace Titanic { - class CMusicSliderSpeed : public CMusicSlider { - public: - CLASSDEF; - - /** - * Save the data for the class to file - */ - virtual void save(SimpleFile *file, int indent) { - file->writeNumberLine(1, indent); - CMusicSlider::save(file, indent); - } - - /** - * Load the data for the class from file - */ - virtual void load(SimpleFile *file) { - file->readNumber(); - CMusicSlider::load(file); - } - }; +class CMusicSliderSpeed : public CMusicSlider { + DECLARE_MESSAGE_MAP; + bool MusicSettingChangedMsg(CMusicSettingChangedMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg); +public: + CLASSDEF; + + /** + * Save the data for the class to file + */ + virtual void save(SimpleFile *file, int indent); + + /** + * Load the data for the class from file + */ + virtual void load(SimpleFile *file); +}; } // End of namespace Titanic diff --git a/engines/titanic/gfx/music_switch_inversion.cpp b/engines/titanic/gfx/music_switch_inversion.cpp new file mode 100644 index 0000000000..d11df79ab4 --- /dev/null +++ b/engines/titanic/gfx/music_switch_inversion.cpp @@ -0,0 +1,67 @@ +/* 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 "titanic/gfx/music_switch_inversion.h" + +namespace Titanic { + +BEGIN_MESSAGE_MAP(CMusicSwitchInversion, CMusicSwitch) + ON_MESSAGE(MusicSettingChangedMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(QueryMusicControlSettingMsg) +END_MESSAGE_MAP() + +void CMusicSwitchInversion::save(SimpleFile *file, int indent) { + file->writeNumberLine(1, indent); + CMusicSwitch::save(file, indent); +} + +void CMusicSwitchInversion::load(SimpleFile *file) { + file->readNumber(); + CMusicSwitch::load(file); +} + +bool CMusicSwitchInversion::MusicSettingChangedMsg(CMusicSettingChangedMsg *msg) { + if (_fieldEC) { + if (++_controlVal > _controlMax) + _controlVal = 0; + + loadFrame(_controlVal); + playSound("z#59.wav", 50); + } else { + playSound("z#46.wav"); + } + + return true; +} + +bool CMusicSwitchInversion::EnterViewMsg(CEnterViewMsg *msg) { + loadFrame(_controlVal); + return true; +} + +bool CMusicSwitchInversion::QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg) { + msg->_value = _controlVal; + return true; +} + +} // End of namespace Titanic diff --git a/engines/titanic/gfx/music_switch_inversion.h b/engines/titanic/gfx/music_switch_inversion.h index 8b3718cf14..869b4745ea 100644 --- a/engines/titanic/gfx/music_switch_inversion.h +++ b/engines/titanic/gfx/music_switch_inversion.h @@ -28,24 +28,22 @@ namespace Titanic { class CMusicSwitchInversion : public CMusicSwitch { + DECLARE_MESSAGE_MAP; + bool MusicSettingChangedMsg(CMusicSettingChangedMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg); public: CLASSDEF; /** * Save the data for the class to file */ - virtual void save(SimpleFile *file, int indent) { - file->writeNumberLine(1, indent); - CMusicSwitch::save(file, indent); - } + virtual void save(SimpleFile *file, int indent); /** * Load the data for the class from file */ - virtual void load(SimpleFile *file) { - file->readNumber(); - CMusicSwitch::load(file); - } + virtual void load(SimpleFile *file); }; } // End of namespace Titanic diff --git a/engines/titanic/gfx/music_switch_reverse.cpp b/engines/titanic/gfx/music_switch_reverse.cpp new file mode 100644 index 0000000000..9fe6d51d47 --- /dev/null +++ b/engines/titanic/gfx/music_switch_reverse.cpp @@ -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 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 "titanic/gfx/music_switch_reverse.h" + +namespace Titanic { + +BEGIN_MESSAGE_MAP(CMusicSwitchReverse, CMusicSwitch) + ON_MESSAGE(MusicSettingChangedMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(QueryMusicControlSettingMsg) +END_MESSAGE_MAP() + +void CMusicSwitchReverse::save(SimpleFile *file, int indent) { + file->writeNumberLine(1, indent); + CMusicSwitch::save(file, indent); +} + +void CMusicSwitchReverse::load(SimpleFile *file) { + file->readNumber(); + CMusicSwitch::load(file); +} +bool CMusicSwitchReverse::MusicSettingChangedMsg(CMusicSettingChangedMsg *msg) { + if (_fieldEC) { + if (++_controlVal > _controlMax) + _controlVal = 0; + + loadFrame(_controlVal); + playSound("z#59.wav", 50); + } else { + playSound("z#46.wav"); + } + + return true; +} + +bool CMusicSwitchReverse::EnterViewMsg(CEnterViewMsg *msg) { + loadFrame(_controlVal); + return true; +} + +bool CMusicSwitchReverse::QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg) { + msg->_value = _controlVal; + return true; +} + +} // End of namespace Titanic diff --git a/engines/titanic/gfx/music_switch_reverse.h b/engines/titanic/gfx/music_switch_reverse.h index 3bfcb53b00..c101f19d25 100644 --- a/engines/titanic/gfx/music_switch_reverse.h +++ b/engines/titanic/gfx/music_switch_reverse.h @@ -27,26 +27,24 @@ namespace Titanic { - class CMusicSwitchReverse : public CMusicSwitch { - public: - CLASSDEF; - - /** - * Save the data for the class to file - */ - virtual void save(SimpleFile *file, int indent) { - file->writeNumberLine(1, indent); - CMusicSwitch::save(file, indent); - } - - /** - * Load the data for the class from file - */ - virtual void load(SimpleFile *file) { - file->readNumber(); - CMusicSwitch::load(file); - } - }; +class CMusicSwitchReverse : public CMusicSwitch { + DECLARE_MESSAGE_MAP; + bool MusicSettingChangedMsg(CMusicSettingChangedMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg); +public: + CLASSDEF; + + /** + * Save the data for the class to file + */ + virtual void save(SimpleFile *file, int indent); + + /** + * Load the data for the class from file + */ + virtual void load(SimpleFile *file); +}; } // End of namespace Titanic diff --git a/engines/titanic/gfx/music_voice_mute.cpp b/engines/titanic/gfx/music_voice_mute.cpp new file mode 100644 index 0000000000..034cb4f6a6 --- /dev/null +++ b/engines/titanic/gfx/music_voice_mute.cpp @@ -0,0 +1,59 @@ +/* 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 "titanic/gfx/music_voice_mute.h" +#include "titanic/sound/music_room.h" + +namespace Titanic { + +BEGIN_MESSAGE_MAP(CMusicVoiceMute, CMusicControl) + ON_MESSAGE(MusicSettingChangedMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(QueryMusicControlSettingMsg) +END_MESSAGE_MAP() + +bool CMusicVoiceMute::MusicSettingChangedMsg(CMusicSettingChangedMsg *msg) { + if (++_controlVal > _controlMax) + _controlVal = 0; + + CMusicRoom *musicRoom = getMusicRoom(); + musicRoom->setMuteControl(_controlArea, _controlVal == 1 ? 1 : 0); + loadFrame(1 - _controlVal); + playSound("z#55.wav", 50); + + return true; +} + +bool CMusicVoiceMute::EnterViewMsg(CEnterViewMsg *msg) { + loadFrame(1 - _controlVal); + CMusicRoom *musicRoom = getMusicRoom(); + musicRoom->setMuteControl(_controlArea, _controlVal == 1 ? 1 : 0); + + return true; +} + +bool CMusicVoiceMute::QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg) { + msg->_value = _controlVal; + return true; +} + +} // End of namespace Titanic diff --git a/engines/titanic/gfx/music_voice_mute.h b/engines/titanic/gfx/music_voice_mute.h index ca15806c09..f64b107423 100644 --- a/engines/titanic/gfx/music_voice_mute.h +++ b/engines/titanic/gfx/music_voice_mute.h @@ -28,6 +28,10 @@ namespace Titanic { class CMusicVoiceMute : public CMusicControl { + DECLARE_MESSAGE_MAP; + bool MusicSettingChangedMsg(CMusicSettingChangedMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/gfx/slider_button.cpp b/engines/titanic/gfx/slider_button.cpp index 0633158e97..b3dbbed08f 100644 --- a/engines/titanic/gfx/slider_button.cpp +++ b/engines/titanic/gfx/slider_button.cpp @@ -24,6 +24,14 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CSliderButton, CSTButton) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MouseDragMoveMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + CSliderButton::CSliderButton() : CSTButton(), _field114(0), _field118(0), _field11C(0) { } @@ -48,4 +56,39 @@ void CSliderButton::load(SimpleFile *file) { CSTButton::load(file); } +bool CSliderButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + _pos1 = msg->_mousePos; + CStatusChangeMsg changeMsg; + changeMsg.execute(this); + return true; +} + +bool CSliderButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + _pos1 = msg->_mousePos; + return true; +} + +bool CSliderButton::MouseDragMoveMsg(CMouseDragMoveMsg *msg) { + _pos1 = msg->_mousePos; + if (_field118) { + CStatusChangeMsg changeMsg; + changeMsg.execute(this); + } + + return true; +} + +bool CSliderButton::StatusChangeMsg(CStatusChangeMsg *msg) { + CStatusChangeMsg changeMsg; + changeMsg._oldStatus = _currentStatus; + _currentStatus = (_pos1.y - _bounds.top) / _field11C; + changeMsg._newStatus = _currentStatus; + changeMsg.execute(_actionTarget); + return true; +} + +bool CSliderButton::EnterViewMsg(CEnterViewMsg *msg) { + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/gfx/slider_button.h b/engines/titanic/gfx/slider_button.h index 398290bb05..18ebbae3db 100644 --- a/engines/titanic/gfx/slider_button.h +++ b/engines/titanic/gfx/slider_button.h @@ -28,6 +28,12 @@ namespace Titanic { class CSliderButton : public CSTButton { + DECLARE_MESSAGE_MAP; + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MouseDragMoveMsg(CMouseDragMoveMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); private: int _field114; int _field118; diff --git a/engines/titanic/gfx/st_button.cpp b/engines/titanic/gfx/st_button.cpp index 4b93d46595..6fc31f4c64 100644 --- a/engines/titanic/gfx/st_button.cpp +++ b/engines/titanic/gfx/st_button.cpp @@ -32,10 +32,10 @@ END_MESSAGE_MAP() CSTButton::CSTButton() : CBackground() { _statusInc = 0; - _statusTarget = "NULL"; + _actionTarget = "NULL"; _fieldF0 = 0; _currentStatus = 0; - _string4 = "NULL"; + _actionName = "NULL"; _soundName = "NULL"; _buttonFrame = 0; } @@ -43,10 +43,10 @@ CSTButton::CSTButton() : CBackground() { void CSTButton::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_statusInc, indent); - file->writeQuotedLine(_statusTarget, indent); + file->writeQuotedLine(_actionTarget, indent); file->writeNumberLine(_fieldF0, indent); file->writeNumberLine(_currentStatus, indent); - file->writeQuotedLine(_string4, indent); + file->writeQuotedLine(_actionName, indent); file->writeQuotedLine(_soundName, indent); file->writeNumberLine(_buttonFrame, indent); @@ -56,10 +56,10 @@ void CSTButton::save(SimpleFile *file, int indent) { void CSTButton::load(SimpleFile *file) { file->readNumber(); _statusInc = file->readNumber(); - _statusTarget = file->readString(); + _actionTarget = file->readString(); _fieldF0 = file->readNumber(); _currentStatus = file->readNumber(); - _string4 = file->readString(); + _actionName = file->readString(); _soundName = file->readString(); _buttonFrame = file->readNumber() != 0; @@ -79,7 +79,7 @@ bool CSTButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { CStatusChangeMsg statusMsg(oldStatus, newStatus, false); _currentStatus = newStatus; - statusMsg.execute(_statusTarget); + statusMsg.execute(_actionTarget); if (!statusMsg._success) { _currentStatus -= _statusInc; diff --git a/engines/titanic/gfx/st_button.h b/engines/titanic/gfx/st_button.h index 789437691b..444c883f59 100644 --- a/engines/titanic/gfx/st_button.h +++ b/engines/titanic/gfx/st_button.h @@ -34,12 +34,12 @@ class CSTButton : public CBackground { bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); bool EnterViewMsg(CEnterViewMsg *msg); -private: +protected: int _statusInc; - CString _statusTarget; + CString _actionTarget; int _fieldF0; int _currentStatus; - CString _string4; + CString _actionName; CString _soundName; int _buttonFrame; public: diff --git a/engines/titanic/gfx/status_change_button.cpp b/engines/titanic/gfx/status_change_button.cpp index 6644247ff2..e38f1ee07e 100644 --- a/engines/titanic/gfx/status_change_button.cpp +++ b/engines/titanic/gfx/status_change_button.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CStatusChangeButton, CSTButton) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + CStatusChangeButton::CStatusChangeButton() : CSTButton() { } @@ -37,4 +41,11 @@ void CStatusChangeButton::load(SimpleFile *file) { CSTButton::load(file); } +bool CStatusChangeButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CStatusChangeMsg changeMsg; + changeMsg._newStatus = _statusInc; + changeMsg.execute(_actionTarget); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/gfx/status_change_button.h b/engines/titanic/gfx/status_change_button.h index 9e410c66f2..9d187493a7 100644 --- a/engines/titanic/gfx/status_change_button.h +++ b/engines/titanic/gfx/status_change_button.h @@ -28,6 +28,8 @@ namespace Titanic { class CStatusChangeButton : public CSTButton { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: CLASSDEF; CStatusChangeButton(); diff --git a/engines/titanic/gfx/toggle_button.h b/engines/titanic/gfx/toggle_button.h index 5328072982..4fb7cdfaba 100644 --- a/engines/titanic/gfx/toggle_button.h +++ b/engines/titanic/gfx/toggle_button.h @@ -29,7 +29,7 @@ namespace Titanic { class CToggleButton : public CBackground { DECLARE_MESSAGE_MAP; -private: +protected: int _fieldE0; public: CLASSDEF; diff --git a/engines/titanic/gfx/toggle_switch.cpp b/engines/titanic/gfx/toggle_switch.cpp index 20cbb863ee..815f96cb5a 100644 --- a/engines/titanic/gfx/toggle_switch.cpp +++ b/engines/titanic/gfx/toggle_switch.cpp @@ -24,12 +24,18 @@ namespace Titanic { -CToggleSwitch::CToggleSwitch() : CGameObject(), _fieldBC(0) { +BEGIN_MESSAGE_MAP(CToggleSwitch, CGameObject) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(ChildDragStartMsg) + ON_MESSAGE(ChildDragMoveMsg) +END_MESSAGE_MAP() + +CToggleSwitch::CToggleSwitch() : CGameObject(), _pressed(false) { } void CToggleSwitch::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); + file->writeNumberLine(_pressed, indent); file->writePoint(_pos1, indent); CGameObject::save(file, indent); @@ -37,10 +43,30 @@ void CToggleSwitch::save(SimpleFile *file, int indent) { void CToggleSwitch::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); + _pressed = file->readNumber(); _pos1 = file->readPoint(); CGameObject::load(file); } +bool CToggleSwitch::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + _pressed = !_pressed; + if (_pressed) + fn10(0, 0, 0); + else + fn10(0xff, 0xff, 0xff); + return true; +} + +bool CToggleSwitch::ChildDragStartMsg(CChildDragStartMsg *msg) { + _pos1.x = msg->_mousePos.x - _bounds.left; + _pos1.y = msg->_mousePos.y - _bounds.top; + return true; +} + +bool CToggleSwitch::ChildDragMoveMsg(CChildDragMoveMsg *msg) { + setPosition(Point(msg->_mousePos.x - _pos1.x, msg->_mousePos.y - _pos1.y)); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/gfx/toggle_switch.h b/engines/titanic/gfx/toggle_switch.h index ae96c75ebd..69b8c50eec 100644 --- a/engines/titanic/gfx/toggle_switch.h +++ b/engines/titanic/gfx/toggle_switch.h @@ -28,8 +28,12 @@ namespace Titanic { class CToggleSwitch : public CGameObject { -private: - int _fieldBC; + DECLARE_MESSAGE_MAP; + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool ChildDragStartMsg(CChildDragStartMsg *msg); + bool ChildDragMoveMsg(CChildDragMoveMsg *msg); +protected: + bool _pressed; Point _pos1; public: CLASSDEF; diff --git a/engines/titanic/input_handler.cpp b/engines/titanic/input_handler.cpp index 7c35a5d855..2c51f3ece7 100644 --- a/engines/titanic/input_handler.cpp +++ b/engines/titanic/input_handler.cpp @@ -150,7 +150,7 @@ CGameObject *CInputHandler::dragEnd(const Point &pt, CTreeItem *dragItem) { // Scan through the view items to find the item being dropped on CGameObject *target = nullptr; for (CTreeItem *treeItem = view->scan(view); treeItem; treeItem = treeItem->scan(view)) { - CGameObject *gameObject = static_cast<CGameObject *>(treeItem); + CGameObject *gameObject = dynamic_cast<CGameObject *>(treeItem); if (gameObject && gameObject != dragItem) { if (gameObject->checkPoint(pt)) target = gameObject; diff --git a/engines/titanic/main_game_window.cpp b/engines/titanic/main_game_window.cpp index 486bc417d7..690acdc25f 100644 --- a/engines/titanic/main_game_window.cpp +++ b/engines/titanic/main_game_window.cpp @@ -49,19 +49,24 @@ CMainGameWindow::CMainGameWindow(TitanicEngine *vm): _vm(vm), CMainGameWindow::~CMainGameWindow() { } -bool CMainGameWindow::Create() { - Image image; - image.load("TITANIC"); - - // TODO: Stuff - return true; -} - void CMainGameWindow::applicationStarting() { // Set the video mode CScreenManager *screenManager = CScreenManager::setCurrent(); screenManager->setMode(640, 480, 16, 0, true); +#if 0 + // Show the initial copyright & info screen for the game + if (gDebugLevel <= 0) { + Image image; + image.load("Bitmap/TITANIC"); + _vm->_screen->blitFrom(image, Point( + SCREEN_WIDTH / 2 - image.w / 2, + SCREEN_HEIGHT / 2 - image.h / 2 + )); + _vm->_events->sleep(5000); + } +#endif + // Set up the game project, and get game slot int saveSlot = getSavegameSlot(); if (saveSlot == -2) @@ -77,8 +82,6 @@ void CMainGameWindow::applicationStarting() { _inputAllowed = true; _gameManager->_gameState.setMode(GSMODE_INTERACTIVE); - // TODO: Cursor/image - // Generate starting messages for entering the view, node, and room. // Note the old fields are nullptr, since there's no previous view/node/room CViewItem *view = _gameManager->_gameState._gameLocation.getView(); @@ -157,8 +160,9 @@ void CMainGameWindow::draw() { scrManager->drawCursors(); break; - case GSMODE_5: - g_vm->_filesManager->debug(scrManager); + case GSMODE_INSERT_CD: + scrManager->drawCursors(); + _vm->_filesManager->insertCD(scrManager); break; case GSMODE_PENDING_LOAD: @@ -216,7 +220,7 @@ void CMainGameWindow::drawViewContents(CScreenManager *screenManager) { } void CMainGameWindow::mouseChanged() { - if (_gameManager->_gameState._mode != GSMODE_5) + if (_gameManager->_gameState._mode != GSMODE_INSERT_CD) _gameManager->update(); } diff --git a/engines/titanic/main_game_window.h b/engines/titanic/main_game_window.h index 82e24e250e..530d5796f4 100644 --- a/engines/titanic/main_game_window.h +++ b/engines/titanic/main_game_window.h @@ -104,11 +104,6 @@ public: virtual void keyUp(Common::KeyState keyState); /** - * Creates the window - */ - bool Create(); - - /** * Called when the application starts */ void applicationStarting(); @@ -136,7 +131,9 @@ public: /* * Return whether a given special key is currently pressed */ - bool isSpecialPressed(SpecialButtons btn) const { return _specialButtons; } + bool isSpecialPressed(SpecialButtons btn) const { + return (_specialButtons & btn) != 0; + } /** * Returns the bitset of the currently pressed special buttons diff --git a/engines/titanic/messages/auto_sound_event.cpp b/engines/titanic/messages/auto_sound_event.cpp index baa11c7d41..bc2cd7d074 100644 --- a/engines/titanic/messages/auto_sound_event.cpp +++ b/engines/titanic/messages/auto_sound_event.cpp @@ -24,7 +24,11 @@ namespace Titanic { -CAutoSoundEvent::CAutoSoundEvent() : CGameObject(), _value1(0), _value2(70) { +BEGIN_MESSAGE_MAP(CAutoSoundEvent, CGameObject) + ON_MESSAGE(FrameMsg) +END_MESSAGE_MAP() + +CAutoSoundEvent::CAutoSoundEvent() : CGameObject(), _value1(0), _value2(0xFFFFFF) { } void CAutoSoundEvent::save(SimpleFile *file, int indent) { @@ -43,4 +47,11 @@ void CAutoSoundEvent::load(SimpleFile *file) { CGameObject::load(file); } +bool CAutoSoundEvent::FrameMsg(CFrameMsg *msg) { + if (_value1 >= 0) + _value1 = (_value1 + 1) & _value2; + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/messages/auto_sound_event.h b/engines/titanic/messages/auto_sound_event.h index eb1c11c4ff..d88976708e 100644 --- a/engines/titanic/messages/auto_sound_event.h +++ b/engines/titanic/messages/auto_sound_event.h @@ -28,6 +28,8 @@ namespace Titanic { class CAutoSoundEvent : public CGameObject { + DECLARE_MESSAGE_MAP; + bool FrameMsg(CFrameMsg *msg); public: int _value1; int _value2; diff --git a/engines/titanic/messages/bilge_dispensor_event.cpp b/engines/titanic/messages/bilge_dispensor_event.cpp index 043ffe75d3..584da00a6f 100644 --- a/engines/titanic/messages/bilge_dispensor_event.cpp +++ b/engines/titanic/messages/bilge_dispensor_event.cpp @@ -24,6 +24,13 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CBilgeDispensorEvent, CAutoSoundEvent) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(LeaveRoomMsg) + ON_MESSAGE(FrameMsg) + ON_MESSAGE(StatusChangeMsg) +END_MESSAGE_MAP() + void CBilgeDispensorEvent::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CAutoSoundEvent::save(file, indent); @@ -39,4 +46,32 @@ bool CBilgeDispensorEvent::EnterRoomMsg(CEnterRoomMsg *msg) { return true; } +bool CBilgeDispensorEvent::LeaveRoomMsg(CLeaveRoomMsg *msg) { + _value1 = -1; + return true; +} + +bool CBilgeDispensorEvent::FrameMsg(CFrameMsg *msg) { + if (_value1 >= 0 && (_value1 & 0xffff) == 0x4000) { + int volume = 20 + getRandomNumber(30); + int val3 = getRandomNumber(20) - 10; + + if (getRandomNumber(2) == 0) { + playSound("b#18.wav", volume, val3); + } + } + + CAutoSoundEvent::FrameMsg(msg); + return true; +} + +bool CBilgeDispensorEvent::StatusChangeMsg(CStatusChangeMsg *msg) { + if (msg->_newStatus == 1) + _value1 = -1; + else if (msg->_newStatus == 2) + _value1 = 0; + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/messages/bilge_dispensor_event.h b/engines/titanic/messages/bilge_dispensor_event.h index 96ef92a54e..61d3116db4 100644 --- a/engines/titanic/messages/bilge_dispensor_event.h +++ b/engines/titanic/messages/bilge_dispensor_event.h @@ -29,9 +29,14 @@ namespace Titanic { class CBilgeDispensorEvent : public CAutoSoundEvent { + DECLARE_MESSAGE_MAP; bool EnterRoomMsg(CEnterRoomMsg *msg); + bool LeaveRoomMsg(CLeaveRoomMsg *msg); + bool FrameMsg(CFrameMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); public: CLASSDEF; + CBilgeDispensorEvent() : CAutoSoundEvent() {} /** * Save the data for the class to file diff --git a/engines/titanic/messages/door_auto_sound_event.cpp b/engines/titanic/messages/door_auto_sound_event.cpp index b9cedae6de..7618577e50 100644 --- a/engines/titanic/messages/door_auto_sound_event.cpp +++ b/engines/titanic/messages/door_auto_sound_event.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CDoorAutoSoundEvent, CAutoSoundEvent) + ON_MESSAGE(PreEnterNodeMsg) + ON_MESSAGE(LeaveNodeMsg) + ON_MESSAGE(TimerMsg) +END_MESSAGE_MAP() + void CDoorAutoSoundEvent::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeQuotedLine(_string1, indent); @@ -44,4 +50,16 @@ void CDoorAutoSoundEvent::load(SimpleFile *file) { CAutoSoundEvent::load(file); } +bool CDoorAutoSoundEvent::PreEnterNodeMsg(CPreEnterNodeMsg *msg) { + return true; +} + +bool CDoorAutoSoundEvent::LeaveNodeMsg(CLeaveNodeMsg *msg) { + return true; +} + +bool CDoorAutoSoundEvent::TimerMsg(CTimerMsg *msg) { + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/messages/door_auto_sound_event.h b/engines/titanic/messages/door_auto_sound_event.h index e6ea1b0f98..8b064a7221 100644 --- a/engines/titanic/messages/door_auto_sound_event.h +++ b/engines/titanic/messages/door_auto_sound_event.h @@ -28,6 +28,10 @@ namespace Titanic { class CDoorAutoSoundEvent : public CAutoSoundEvent { + DECLARE_MESSAGE_MAP; + bool PreEnterNodeMsg(CPreEnterNodeMsg *msg); + bool LeaveNodeMsg(CLeaveNodeMsg *msg); + bool TimerMsg(CTimerMsg *msg); public: CString _string1; CString _string2; diff --git a/engines/titanic/messages/messages.h b/engines/titanic/messages/messages.h index c1d962f656..b70bc5e16c 100644 --- a/engines/titanic/messages/messages.h +++ b/engines/titanic/messages/messages.h @@ -149,38 +149,21 @@ public: class CEditControlMsg : public CMessage { public: - int _field4; - int _field8; - CString _string1; - int _field18; - int _field1C; - int _field20; + int _mode; + int _param; + CString _text; + byte _textR; + byte _textG; + byte _textB; public: CLASSDEF; - CEditControlMsg() : _field4(0), _field8(0), _field18(0), - _field1C(0), _field20(0) {} + CEditControlMsg() : _mode(0), _param(0), _textR(0), _textG(0), _textB(0) {} static bool isSupportedBy(const CTreeItem *item) { return CMessage::supports(item, _type); } }; -class CLightsMsg : public CMessage { -public: - int _field4; - int _field8; - int _fieldC; - int _field10; -public: - CLASSDEF; - CLightsMsg() : CMessage(), _field4(0), _field8(0), - _fieldC(0), _field10(0) {} - - static bool isSupportedBy(const CTreeItem *item) { - return supports(item, _type); - } -}; - MESSAGE1(CTimeMsg, uint, _ticks, 0); class CTimerMsg : public CTimeMsg { @@ -206,15 +189,13 @@ MESSAGE1(CAnimateMaitreDMsg, int, value, 0); MESSAGE1(CArboretumGateMsg, int, value, 0); MESSAGE0(CArmPickedUpFromTableMsg); MESSAGE0(CBodyInBilgeRoomMsg); -MESSAGE1(CBowlStateChange, int, value, 0); +MESSAGE1(CBowlStateChangeMsg, int, state, 0); MESSAGE2(CCarryObjectArrivedMsg, CString, strValue, "", int, numValue, 0); MESSAGE2(CChangeMusicMsg, CString, filename, "", int, flags, 0); MESSAGE1(CChangeSeasonMsg, CString, season, "Summer"); MESSAGE0(CCheckAllPossibleCodes); -MESSAGE2(CCheckChevCode, int, value1, 0, int, value2, 0); +MESSAGE2(CCheckChevCode, int, classNum, 0, uint, chevCode, 0); MESSAGE1(CChildDragEndMsg, int, value, 0); -MESSAGE2(CChildDragMoveMsg, int, value1, 0, int, value2, 0); -MESSAGE2(CChildDragStartMsg, int, value1, 0, int, value2, 0); MESSAGE0(CClearChevPanelBits); MESSAGE0(CCorrectMusicPlayedMsg); MESSAGE0(CCreateMusicPlayerMsg); @@ -228,8 +209,8 @@ MESSAGE0(CDonNavHelmet); MESSAGE1(CDoorbotNeededInElevatorMsg, int, value, 0); MESSAGE0(CDoorbotNeededInHomeMsg); MESSAGE1(CDropObjectMsg, CCarry *, item, nullptr); -MESSAGE1(CDropZoneGotObjectMsg, int, value, 0); -MESSAGE1(CDropZoneLostObjectMsg, int, value, 0); +MESSAGE1(CDropZoneGotObjectMsg, CGameObject *, object, nullptr); +MESSAGE1(CDropZoneLostObjectMsg, CGameObject *, object, nullptr); MESSAGE1(CEjectCylinderMsg, int, value, 0); MESSAGE2(CPreEnterNodeMsg, CNodeItem *, oldNode, nullptr, CNodeItem *, newNode, nullptr); MESSAGE2(CPreEnterRoomMsg, CRoomItem *, oldRoom, nullptr, CRoomItem *, newRoom, nullptr); @@ -239,17 +220,17 @@ MESSAGE2(CEnterRoomMsg, CRoomItem *, oldRoom, nullptr, CRoomItem *, newRoom, nul MESSAGE2(CEnterViewMsg, CViewItem *, oldView, nullptr, CViewItem *, newView, nullptr); MESSAGE0(CErasePhonographCylinderMsg); MESSAGE1(CFrameMsg, uint, ticks, 0); -MESSAGE2(CFreshenCookieMsg, int, value1, 0, int, value2, 0); -MESSAGE1(CGetChevClassBits, int, value, 0); -MESSAGE1(CGetChevClassNum, int, value, 0); -MESSAGE2(CGetChevCodeFromRoomNameMsg, CString, strValue, "", int, numValue, 0); -MESSAGE1(CGetChevFloorBits, int, value, 0); -MESSAGE1(CGetChevFloorNum, int, value, 0); -MESSAGE1(CGetChevLiftBits, int, value, 0); -MESSAGE1(CGetChevLiftNum, int, value, 0); -MESSAGE1(CGetChevRoomBits, int, value, 0); -MESSAGE1(CGetChevRoomNum, int, value, 0); -MESSAGE2(CHoseConnectedMsg, int, value1, 1, int, value2, 0); +MESSAGE2(CFreshenCookieMsg, int, value1, 0, int, value2, 1); +MESSAGE1(CGetChevClassBits, int, classBits, 0); +MESSAGE1(CGetChevClassNum, int, classNum, 0); +MESSAGE2(CGetChevCodeFromRoomNameMsg, CString, roomName, "", uint, chevCode, 0); +MESSAGE1(CGetChevFloorBits, int, floorBits, 0); +MESSAGE1(CGetChevFloorNum, int, floorNum, 0); +MESSAGE1(CGetChevLiftBits, int, liftBits, 0); +MESSAGE1(CGetChevLiftNum, int, liftNum, 0); +MESSAGE1(CGetChevRoomBits, int, roomNum, 0); +MESSAGE1(CGetChevRoomNum, int, roomNum, 0); +MESSAGE2(CHoseConnectedMsg, int, value, 1, CGameObject *, object, nullptr); MESSAGE0(CInitializeAnimMsg); MESSAGE1(CIsEarBowlPuzzleDone, int, value, 0); MESSAGE3(CIsHookedOnMsg, Rect, rect, Rect(), bool, result, false, CString, string1, ""); @@ -258,41 +239,42 @@ MESSAGE1(CKeyCharMsg, int, key, 32); MESSAGE2(CLeaveNodeMsg, CNodeItem *, oldNode, nullptr, CNodeItem *, newNode, nullptr); MESSAGE2(CLeaveRoomMsg, CRoomItem *, oldRoom, nullptr, CRoomItem *, newRoom, nullptr); MESSAGE2(CLeaveViewMsg, CViewItem *, oldView, nullptr, CViewItem *, newView, nullptr); -MESSAGE2(CLemonFallsFromTreeMsg, int, value1, 0, int, value2, 0); +MESSAGE1(CLemonFallsFromTreeMsg, Point, pt, Point()); +MESSAGE4(CLightsMsg, bool, flag1, false, bool, flag2, false, bool, flag3, false, bool, flag4, false); MESSAGE1(CLoadSuccessMsg, int, ticks, 0); MESSAGE1(CLockPhonographMsg, int, value, 0); MESSAGE0(CMaitreDDefeatedMsg); MESSAGE0(CMaitreDHappyMsg); -MESSAGE1(CMissiveOMatActionMsg, int, value, 0); +MESSAGE1(CMissiveOMatActionMsg, int, action, 0); MESSAGE0(CMoveToStartPosMsg); MESSAGE2(CMovieEndMsg, int, startFrame, 0, int, endFrame, 0); MESSAGE2(CMovieFrameMsg, int, frameNumber, 0, int, value2, 0); MESSAGE0(CMusicHasStartedMsg); MESSAGE0(CMusicHasStoppedMsg); MESSAGE0(CMusicSettingChangedMsg); -MESSAGE2(CNPCPlayAnimationMsg, const char *const *, names, nullptr, int, value2, 0); +MESSAGE2(CNPCPlayAnimationMsg, const char *const *, names, nullptr, int, maxDuration, 0); MESSAGE1(CNPCPlayIdleAnimationMsg, const char *const *, names, 0); MESSAGE3(CNPCPlayTalkingAnimationMsg, int, value1, 0, int, value2, 0, const char *const *, names, nullptr); MESSAGE0(CNPCQueueIdleAnimMsg); MESSAGE1(CNutPuzzleMsg, CString, value, ""); MESSAGE1(COnSummonBotMsg, int, value, 0); MESSAGE0(COpeningCreditsMsg); -MESSAGE1(CPanningAwayFromParrotMsg, int, value, 0); -MESSAGE2(CParrotSpeakMsg, CString, value1, "", CString, value2, ""); +MESSAGE1(CPanningAwayFromParrotMsg, CTreeItem *, target, nullptr); +MESSAGE2(CParrotSpeakMsg, CString, target, "", CString, action, ""); MESSAGE2(CParrotTriesChickenMsg, int, value1, 0, int, value2, 0); MESSAGE1(CPhonographPlayMsg, int, value, 0); MESSAGE0(CPhonographReadyToPlayMsg); MESSAGE1(CPhonographRecordMsg, int, value, 0); MESSAGE3(CPhonographStopMsg, int, value1, 0, int, value2, 0, int, value3, 0); MESSAGE2(CPlayRangeMsg, int, value1, 0, int, value2, 0); -MESSAGE2(CPlayerTriesRestaurantTableMsg, int, value1, 0, int, value2, 0); +MESSAGE2(CPlayerTriesRestaurantTableMsg, int, tableId, 0, bool, result, false); MESSAGE1(CPreSaveMsg, int, value, 0); MESSAGE1(CProdMaitreDMsg, int, value, 0); -MESSAGE2(CPumpingMsg, int, value1, 0, int, value2, 0); +MESSAGE2(CPumpingMsg, int, value, 0, CGameObject *, object, nullptr); MESSAGE1(CPutBotBackInHisBoxMsg, int, value, 0); MESSAGE1(CPutParrotBackMsg, int, value, 0); MESSAGE0(CPuzzleSolvedMsg); -MESSAGE3(CQueryCylinderHolderMsg, int, value1, 0, int, value2, 0, int, value3, 0); +MESSAGE3(CQueryCylinderHolderMsg, int, value1, 0, int, value2, 0, CTreeItem *, target, (CTreeItem *)nullptr); MESSAGE1(CQueryCylinderMsg, CString, name, ""); MESSAGE1(CQueryCylinderNameMsg, CString, name, ""); MESSAGE3(CQueryCylinderTypeMsg, int, value1, 0, int, value2, 0, int, value3, 0); @@ -308,12 +290,12 @@ MESSAGE2(CServiceElevatorFloorChangeMsg, int, value1, 0, int, value2, 0); MESSAGE0(CServiceElevatorFloorRequestMsg); MESSAGE1(CServiceElevatorMsg, int, value, 4); MESSAGE2(CSetChevButtonImageMsg, int, value1, 0, int, value2, 0); -MESSAGE1(CSetChevClassBits, int, value, 0); -MESSAGE1(CSetChevFloorBits, int, value, 0); -MESSAGE1(CSetChevLiftBits, int, value, 0); +MESSAGE1(CSetChevClassBits, int, classNum, 0); +MESSAGE1(CSetChevFloorBits, int, floorNum, 0); +MESSAGE1(CSetChevLiftBits, int, liftNum, 0); MESSAGE2(CSetChevPanelBitMsg, int, value1, 0, int, value2, 0); -MESSAGE1(CSetChevPanelButtonsMsg, int, value, 0); -MESSAGE1(CSetChevRoomBits, int, value, 0); +MESSAGE1(CSetChevPanelButtonsMsg, int, chevCode, 0); +MESSAGE1(CSetChevRoomBits, int, roomNum, 0); MESSAGE1(CSetFrameMsg, int, frameNumber, 0); MESSAGE0(CSetMusicControlsMsg); MESSAGE2(CSetVarMsg, CString, varName, "", int, value, 0); @@ -321,17 +303,17 @@ MESSAGE2(CSetVolumeMsg, int, volume, 70, int, secondsTransition, 0); MESSAGE2(CShipSettingMsg, int, value, 0, CString, name, ""); MESSAGE1(CShowTextMsg, CString, value, "NO TEXT INCLUDED!!!"); MESSAGE2(CSignalObject, CString, strValue, "", int, numValue, 0); -MESSAGE2(CSpeechFallsFromTreeMsg, int, value1, 0, int, value2, 0); +MESSAGE1(CSpeechFallsFromTreeMsg, Point, pos, Point()); MESSAGE1(CStartMusicMsg, CMusicPlayer *, musicPlayer, (CMusicPlayer *)nullptr); MESSAGE3(CStatusChangeMsg, int, oldStatus, 0, int, newStatus, 0, bool, success, false); -MESSAGE1(CStopMusicMsg, int, value, 0); +MESSAGE1(CStopMusicMsg, CMusicPlayer *, musicPlayer, (CMusicPlayer *)nullptr); MESSAGE4(CSubAcceptCCarryMsg, CString, string1, "", int, value1, 0, int, value2, 0, CCarry *, item, nullptr); MESSAGE0(CSubDeliverCCarryMsg); MESSAGE0(CSubSendCCarryMsg); MESSAGE0(CSUBTransition); MESSAGE0(CSubTurnOffMsg); MESSAGE0(CSubTurnOnMsg); -MESSAGE2(CSummonBotMsg, CString, strValue, "", int, numValue, 0); +MESSAGE2(CSummonBotMsg, CString, npcName, "", int, value, 0); MESSAGE1(CSummonBotQueryMsg, CString, npcName, ""); MESSAGE1(CTakeHeadPieceMsg, CString, value, "NULL"); MESSAGE2(CTextInputMsg, CString, input, "", CString, response, ""); @@ -352,7 +334,7 @@ MESSAGE0(CTrueTalkSelfQueueAnimSetMsg); MESSAGE3(CTrueTalkTriggerActionMsg, int, action, 0, int, param1, 0, int, param2, 0); MESSAGE0(CTurnOff); MESSAGE0(CTurnOn); -MESSAGE1(CUse, CCarry *, item, nullptr); +MESSAGE1(CUse, CGameObject *, item, nullptr); MESSAGE1(CUseWithCharMsg, CCharacter *, character, nullptr); MESSAGE1(CUseWithOtherMsg, CGameObject *, other, 0); MESSAGE1(CVirtualKeyCharMsg, Common::KeyState, keyState, Common::KeyState()); diff --git a/engines/titanic/messages/mouse_messages.cpp b/engines/titanic/messages/mouse_messages.cpp index 8ef7f38fd3..18fa625c1c 100644 --- a/engines/titanic/messages/mouse_messages.cpp +++ b/engines/titanic/messages/mouse_messages.cpp @@ -23,7 +23,6 @@ #include "titanic/messages/mouse_messages.h" #include "titanic/support/mouse_cursor.h" #include "titanic/support/screen_manager.h" -#include "titanic/titanic.h" namespace Titanic { diff --git a/engines/titanic/messages/mouse_messages.h b/engines/titanic/messages/mouse_messages.h index d17bd51c78..e7c419bbdc 100644 --- a/engines/titanic/messages/mouse_messages.h +++ b/engines/titanic/messages/mouse_messages.h @@ -179,6 +179,32 @@ public: } }; +class CChildDragMoveMsg : public CMessage { +public: + Point _mousePos; +public: + CLASSDEF; + CChildDragMoveMsg() : CMessage() {} + CChildDragMoveMsg(const Point &pt) : CMessage(), _mousePos(pt) {} + + static bool isSupportedBy(const CTreeItem *item) { + return supports(item, _type); + } +}; + +class CChildDragStartMsg : public CMessage { +public: + Point _mousePos; +public: + CLASSDEF; + CChildDragStartMsg() : CMessage() {} + CChildDragStartMsg(const Point &pt) : CMessage(), _mousePos(pt) {} + + static bool isSupportedBy(const CTreeItem *item) { + return supports(item, _type); + } +}; + } // End of namespace Titanic #endif /* TITANIC_MOUSE_MESSAGES_H */ diff --git a/engines/titanic/messages/pet_messages.h b/engines/titanic/messages/pet_messages.h index 48e5bab64c..60981726ed 100644 --- a/engines/titanic/messages/pet_messages.h +++ b/engines/titanic/messages/pet_messages.h @@ -35,7 +35,7 @@ MESSAGE0(CPETLostObjectMsg); MESSAGE0(CPETObjectSelectedMsg); MESSAGE1(CPETObjectStateMsg, int, value, 0); MESSAGE0(CPETPhotoOnOffMsg); -MESSAGE1(CPETPlaySoundMsg, int, value, 0); +MESSAGE1(CPETPlaySoundMsg, int, soundNum, 0); MESSAGE0(CPETReceiveMsg); MESSAGE0(CPETSetStarDestinationMsg); MESSAGE1(CPETStarFieldLockMsg, int, value, 0); diff --git a/engines/titanic/messages/service_elevator_door.cpp b/engines/titanic/messages/service_elevator_door.cpp index 748790e4aa..7011b1ad44 100644 --- a/engines/titanic/messages/service_elevator_door.cpp +++ b/engines/titanic/messages/service_elevator_door.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CServiceElevatorDoor, CDoorAutoSoundEvent) + ON_MESSAGE(PreEnterNodeMsg) +END_MESSAGE_MAP() + CServiceElevatorDoor::CServiceElevatorDoor() : CDoorAutoSoundEvent() { _string1 = "z#31.wav"; _string2 = "z#32.wav"; @@ -45,4 +49,10 @@ void CServiceElevatorDoor::load(SimpleFile *file) { CDoorAutoSoundEvent::load(file); } +bool CServiceElevatorDoor::PreEnterNodeMsg(CPreEnterNodeMsg *msg) { + if (!findRoom()->isEquals("BilgeRoomWith")) + CDoorAutoSoundEvent::PreEnterNodeMsg(msg); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/messages/service_elevator_door.h b/engines/titanic/messages/service_elevator_door.h index cc8da0917d..69ad1e15b9 100644 --- a/engines/titanic/messages/service_elevator_door.h +++ b/engines/titanic/messages/service_elevator_door.h @@ -28,6 +28,8 @@ namespace Titanic { class CServiceElevatorDoor : public CDoorAutoSoundEvent { + DECLARE_MESSAGE_MAP; + bool PreEnterNodeMsg(CPreEnterNodeMsg *msg); public: CLASSDEF; CServiceElevatorDoor(); diff --git a/engines/titanic/module.mk b/engines/titanic/module.mk index 5c041174a2..01ad617d6c 100644 --- a/engines/titanic/module.mk +++ b/engines/titanic/module.mk @@ -81,7 +81,6 @@ MODULE_OBJS := \ game/arb_background.o \ game/arboretum_gate.o \ game/auto_animate.o \ - game/bilge_succubus.o \ game/bar_menu.o \ game/bar_menu_button.o \ game/bar_bell.o \ @@ -95,7 +94,6 @@ MODULE_OBJS := \ game/broken_pell_base.o \ game/broken_pellerator.o \ game/broken_pellerator_froz.o \ - game/call_pellerator.o \ game/cage.o \ game/captains_wheel.o \ game/cdrom.o \ @@ -159,6 +157,8 @@ MODULE_OBJS := \ game/music_system_lock.o \ game/musical_instrument.o \ game/nav_helmet.o \ + game/nav_helmet_off.o \ + game/nav_helmet_on.o \ game/navigation_computer.o \ game/no_nut_bowl.o \ game/nose_holder.o \ @@ -221,7 +221,6 @@ MODULE_OBJS := \ game/parrot/parrot_nut_bowl_actor.o \ game/parrot/parrot_nut_eater.o \ game/parrot/parrot_perch_holder.o \ - game/parrot/parrot_succubus.o \ game/parrot/parrot_trigger.o \ game/parrot/player_meets_parrot.o \ game/pet/pet.o \ @@ -243,7 +242,7 @@ MODULE_OBJS := \ game/pickup/pick_up_speech_centre.o \ game/pickup/pick_up_vis_centre.o \ game/placeholder/bar_shelf_vis_centre.o \ - game/placeholder/place_holder_item.o \ + game/placeholder/place_holder.o \ game/placeholder/lemon_on_bar.o \ game/placeholder/tv_on_bar.o \ game/transport/gondolier.o \ @@ -278,7 +277,6 @@ MODULE_OBJS := \ gfx/chev_right_off.o \ gfx/chev_right_on.o \ gfx/chev_send_rec_switch.o \ - gfx/chev_switch.o \ gfx/edit_control.o \ gfx/elevator_button.o \ gfx/get_from_succ.o \ @@ -297,7 +295,12 @@ MODULE_OBJS := \ gfx/move_object_button.o \ gfx/music_control.o \ gfx/music_slider.o \ + gfx/music_slider_pitch.o \ + gfx/music_slider_speed.o \ gfx/music_switch.o \ + gfx/music_switch_inversion.o \ + gfx/music_switch_reverse.o \ + gfx/music_voice_mute.o \ gfx/send_to_succ.o \ gfx/sgt_selector.o \ gfx/slider_button.o \ @@ -319,6 +322,7 @@ MODULE_OBJS := \ messages/messages.o \ messages/mouse_messages.o \ messages/service_elevator_door.o \ + moves/call_pellerator.o \ moves/enter_bomb_room.o \ moves/enter_bridge.o \ moves/enter_exit_first_class_state.o \ @@ -343,6 +347,7 @@ MODULE_OBJS := \ moves/trip_down_canal.o \ npcs/barbot.o \ npcs/bellbot.o \ + npcs/bilge_succubus.o \ npcs/callbot.o \ npcs/character.o \ npcs/deskbot.o \ @@ -351,6 +356,7 @@ MODULE_OBJS := \ npcs/maitre_d.o \ npcs/mobile.o \ npcs/parrot.o \ + npcs/parrot_succubus.o \ npcs/robot_controller.o \ npcs/starlings.o \ npcs/succubus.o \ @@ -399,8 +405,8 @@ MODULE_OBJS := \ sound/dome_from_top_of_well.o \ sound/enter_view_toggles_other_music.o \ sound/gondolier_song.o \ - sound/music_handler.o \ sound/music_room.o \ + sound/music_room_handler.o \ sound/music_player.o \ sound/music_wave.o \ sound/node_auto_sound_player.o \ @@ -468,6 +474,7 @@ MODULE_OBJS := \ support/screen_manager.o \ support/simple_file.o \ support/string.o \ + support/string_parser.o \ support/text_cursor.o \ support/time_event_info.o \ support/video_surface.o \ diff --git a/engines/titanic/moves/call_pellerator.cpp b/engines/titanic/moves/call_pellerator.cpp new file mode 100644 index 0000000000..0dd8195277 --- /dev/null +++ b/engines/titanic/moves/call_pellerator.cpp @@ -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 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 "titanic/moves/call_pellerator.h" + +namespace Titanic { + +BEGIN_MESSAGE_MAP(CCallPellerator, CGameObject) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(PETActivateMsg) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + +void CCallPellerator::save(SimpleFile *file, int indent) { + file->writeNumberLine(1, indent); + CGameObject::save(file, indent); +} + +void CCallPellerator::load(SimpleFile *file) { + file->readNumber(); + CGameObject::load(file); +} + +bool CCallPellerator::EnterViewMsg(CEnterViewMsg *msg) { + petSetArea(PET_REMOTE); + petHighlightGlyph(1); + CString name = getFullViewName(); + + if (name == "TopOfWell.Node 6.S") { + petDisplayMessage(2, "You are standing outside the Pellerator."); + } + + petSetRemoteTarget(); + return true; +} + +bool CCallPellerator::LeaveViewMsg(CLeaveViewMsg *msg) { + petClear(); + return true; +} + +bool CCallPellerator::PETActivateMsg(CPETActivateMsg *msg) { + CString name = getFullViewName(); + + if (msg->_name == "Pellerator") { + if (petDoorOrBellbotPresent()) { + petDisplayMessage("I'm sorry, you cannot enter this pellerator at present as a bot is in the way."); + } else if (name == "Bar.Node 1.S") { + changeView("Pellerator.Node 1.S"); + } else { + changeView("Pellerator.Node 1.N"); + } + } + + return true; +} + +bool CCallPellerator::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + return true; +} + +} // End of namespace Titanic diff --git a/engines/titanic/game/call_pellerator.h b/engines/titanic/moves/call_pellerator.h index 7da4b40794..3a1ef3823a 100644 --- a/engines/titanic/game/call_pellerator.h +++ b/engines/titanic/moves/call_pellerator.h @@ -24,10 +24,16 @@ #define TITANIC_CALL_PELLERATOR_H #include "titanic/core/game_object.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { class CCallPellerator : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool PETActivateMsg(CPETActivateMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/moves/enter_bomb_room.cpp b/engines/titanic/moves/enter_bomb_room.cpp index 55b838d026..9956c669ee 100644 --- a/engines/titanic/moves/enter_bomb_room.cpp +++ b/engines/titanic/moves/enter_bomb_room.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CEnterBombRoom, CMovePlayerTo) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + CEnterBombRoom::CEnterBombRoom() : CMovePlayerTo(), _fieldC8(0) { } @@ -37,4 +41,10 @@ void CEnterBombRoom::load(SimpleFile *file) { CMovePlayerTo::load(file); } +bool CEnterBombRoom::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + changeView("Titania.Node 2.SE"); + changeView(_destination); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/enter_bomb_room.h b/engines/titanic/moves/enter_bomb_room.h index 7fe8287eae..ccdd51f37b 100644 --- a/engines/titanic/moves/enter_bomb_room.h +++ b/engines/titanic/moves/enter_bomb_room.h @@ -28,6 +28,8 @@ namespace Titanic { class CEnterBombRoom : public CMovePlayerTo { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); protected: int _fieldC8; public: diff --git a/engines/titanic/moves/enter_bridge.cpp b/engines/titanic/moves/enter_bridge.cpp index 2600ee699f..fb44fe2e02 100644 --- a/engines/titanic/moves/enter_bridge.cpp +++ b/engines/titanic/moves/enter_bridge.cpp @@ -24,20 +24,31 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CEnterBridge, CGameObject) + ON_MESSAGE(EnterRoomMsg) +END_MESSAGE_MAP() + void CEnterBridge::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value, indent); + file->writeNumberLine(_flag, indent); CGameObject::save(file, indent); } void CEnterBridge::load(SimpleFile *file) { file->readNumber(); - _value = file->readNumber(); + _flag = file->readNumber(); CGameObject::load(file); } bool CEnterBridge::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CEnterBridge::handlEvent"); + if (_flag) { + CActMsg actMsg("Disable"); + actMsg.execute("ShipAnnouncements"); + + setState1C(false); + _flag = false; + } + return true; } diff --git a/engines/titanic/moves/enter_bridge.h b/engines/titanic/moves/enter_bridge.h index a2410a6f1f..837c0e9f7d 100644 --- a/engines/titanic/moves/enter_bridge.h +++ b/engines/titanic/moves/enter_bridge.h @@ -29,12 +29,13 @@ namespace Titanic { class CEnterBridge : public CGameObject { + DECLARE_MESSAGE_MAP; bool EnterRoomMsg(CEnterRoomMsg *msg); private: - int _value; + bool _flag; public: CLASSDEF; - CEnterBridge() : CGameObject(), _value(1) {} + CEnterBridge() : CGameObject(), _flag(true) {} /** * Save the data for the class to file diff --git a/engines/titanic/moves/enter_exit_first_class_state.cpp b/engines/titanic/moves/enter_exit_first_class_state.cpp index 0e2c6c0b6c..34e9984aa7 100644 --- a/engines/titanic/moves/enter_exit_first_class_state.cpp +++ b/engines/titanic/moves/enter_exit_first_class_state.cpp @@ -24,26 +24,44 @@ namespace Titanic { -CString *CEnterExitFirstClassState::_v1; - -void CEnterExitFirstClassState::init() { - _v1 = new CString(); -} - -void CEnterExitFirstClassState::deinit() { - delete _v1; -} +BEGIN_MESSAGE_MAP(CEnterExitFirstClassState, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() void CEnterExitFirstClassState::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(*_v1, indent); + file->writeQuotedLine(_viewName, indent); CGameObject::save(file, indent); } void CEnterExitFirstClassState::load(SimpleFile *file) { file->readNumber(); - *_v1 = file->readString(); + _viewName = file->readString(); CGameObject::load(file); } +bool CEnterExitFirstClassState::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + switch (getPassengerClass()) { + case 1: + if (compareRoomNameTo("1stClassLobby")) { + _viewName = getRoomNodeName() + ".E"; + changeView(_viewName); + } else if (compareRoomNameTo("1stClassState")) { + changeView(_viewName); + } + break; + + case 2: + petDisplayMessage(1, "This room is reserved for the exclusive use of first class passengeres." + " That does not currently include you"); + break; + + default: + petDisplayMessage("No losers."); + break; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/enter_exit_first_class_state.h b/engines/titanic/moves/enter_exit_first_class_state.h index a08de07711..fe63e553de 100644 --- a/engines/titanic/moves/enter_exit_first_class_state.h +++ b/engines/titanic/moves/enter_exit_first_class_state.h @@ -28,18 +28,10 @@ namespace Titanic { class CEnterExitFirstClassState : public CGameObject { -public: - static CString *_v1; - - /** - * Initialize static data - */ - static void init(); - - /** - * De-initialize static data - */ - static void deinit(); + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); +private: + CString _viewName; public: CLASSDEF; diff --git a/engines/titanic/moves/enter_exit_mini_lift.cpp b/engines/titanic/moves/enter_exit_mini_lift.cpp index eb56bdb3bd..3caa674ab8 100644 --- a/engines/titanic/moves/enter_exit_mini_lift.cpp +++ b/engines/titanic/moves/enter_exit_mini_lift.cpp @@ -21,23 +21,52 @@ */ #include "titanic/moves/enter_exit_mini_lift.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CEnterExitMiniLift, CSGTNavigation) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + void CEnterExitMiniLift::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); - + file->writeNumberLine(_destRoomNum, indent); + CSGTNavigation::save(file, indent); } void CEnterExitMiniLift::load(SimpleFile *file) { file->readNumber(); _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); + _destRoomNum = file->readNumber(); CSGTNavigation::load(file); } +bool CEnterExitMiniLift::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (compareRoomNameTo("SgtLobby")) { + _statics->_destView = getRoomNodeName() + ".S"; + _statics->_destRoom = "SgtLobby"; + changeView("SGTLittleLift.Node 1.E"); + + CPetControl *pet = getPetControl(); + if (pet) + pet->setRoomsRoomNum(_destRoomNum); + } else if (compareRoomNameTo("SGTLittleLift")) { + if (_statics->_changeViewNum) { + changeView(_statics->_destView); + } + } + + return true; +} + +bool CEnterExitMiniLift::EnterViewMsg(CEnterViewMsg *msg) { + _cursorId = _statics->_changeViewNum ? CURSOR_MOVE_FORWARD : CURSOR_INVALID; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/enter_exit_mini_lift.h b/engines/titanic/moves/enter_exit_mini_lift.h index 26f3dba8d4..f2671a89b2 100644 --- a/engines/titanic/moves/enter_exit_mini_lift.h +++ b/engines/titanic/moves/enter_exit_mini_lift.h @@ -28,12 +28,15 @@ namespace Titanic { class CEnterExitMiniLift : public CSGTNavigation { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); private: int _fieldBC; - int _fieldC0; + int _destRoomNum; public: CLASSDEF; - CEnterExitMiniLift() : CSGTNavigation(), _fieldBC(0), _fieldC0(1) {} + CEnterExitMiniLift() : CSGTNavigation(), _fieldBC(0), _destRoomNum(1) {} /** * Save the data for the class to file diff --git a/engines/titanic/moves/enter_exit_sec_class_mini_lift.cpp b/engines/titanic/moves/enter_exit_sec_class_mini_lift.cpp index b571a255c5..c7e16ef84e 100644 --- a/engines/titanic/moves/enter_exit_sec_class_mini_lift.cpp +++ b/engines/titanic/moves/enter_exit_sec_class_mini_lift.cpp @@ -21,9 +21,15 @@ */ #include "titanic/moves/enter_exit_sec_class_mini_lift.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CEnterExitSecClassMiniLift, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(StatusChangeMsg) +END_MESSAGE_MAP() + CEnterExitSecClassMiniLiftStatics *CEnterExitSecClassMiniLift::_statics; void CEnterExitSecClassMiniLift::init() { @@ -36,20 +42,53 @@ void CEnterExitSecClassMiniLift::deinit() { void CEnterExitSecClassMiniLift::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_statics->_v1, indent); - file->writeNumberLine(_statics->_v2, indent); - file->writeNumberLine(_value, indent); + file->writeQuotedLine(_statics->_viewName, indent); + file->writeNumberLine(_statics->_state, indent); + file->writeNumberLine(_roomNum, indent); CGameObject::save(file, indent); } void CEnterExitSecClassMiniLift::load(SimpleFile *file) { file->readNumber(); - _statics->_v1 = file->readString(); - _statics->_v2 = file->readNumber(); - _value = file->readNumber(); + _statics->_viewName = file->readString(); + _statics->_state = file->readNumber(); + _roomNum = file->readNumber(); CGameObject::load(file); } +bool CEnterExitSecClassMiniLift::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (compareRoomNameTo("2ndClassLobby")) { + _statics->_viewName = getRoomNodeName() + ".W"; + changeView("SecClassLittleLift.Node 1.E"); + _statics->_state = 1; + + CPetControl *pet = getPetControl(); + if (pet) { + pet->setRoomsRoomNum(_roomNum); + pet->setRooms1CC(1); + } + } else if (compareRoomNameTo("SecClassLittleLift")) { + if (_statics->_state == 1) { + changeView(_statics->_viewName); + } + } + + return true; +} + +bool CEnterExitSecClassMiniLift::StatusChangeMsg(CStatusChangeMsg *msg) { + _statics->_state = msg->_newStatus; + if (msg->_newStatus == 3) + _statics->_state = 2; + + CPetControl *pet = getPetControl(); + if (pet) + pet->setRooms1CC(_statics->_state); + + _cursorId = _statics->_state == 1 ? CURSOR_MOVE_FORWARD : CURSOR_INVALID; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/enter_exit_sec_class_mini_lift.h b/engines/titanic/moves/enter_exit_sec_class_mini_lift.h index 10c7edca7d..839d2c04fa 100644 --- a/engines/titanic/moves/enter_exit_sec_class_mini_lift.h +++ b/engines/titanic/moves/enter_exit_sec_class_mini_lift.h @@ -28,19 +28,22 @@ namespace Titanic { struct CEnterExitSecClassMiniLiftStatics { - CString _v1; - int _v2; + CString _viewName; + int _state; - CEnterExitSecClassMiniLiftStatics() : _v2(1) {} + CEnterExitSecClassMiniLiftStatics() : _state(1) {} }; class CEnterExitSecClassMiniLift : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); private: static CEnterExitSecClassMiniLiftStatics *_statics; - int _value; + int _roomNum; public: CLASSDEF; - CEnterExitSecClassMiniLift() : CGameObject(), _value(0) {} + CEnterExitSecClassMiniLift() : CGameObject(), _roomNum(0) {} static void init(); static void deinit(); diff --git a/engines/titanic/moves/enter_exit_view.cpp b/engines/titanic/moves/enter_exit_view.cpp index 825156acce..6778ebb52a 100644 --- a/engines/titanic/moves/enter_exit_view.cpp +++ b/engines/titanic/moves/enter_exit_view.cpp @@ -24,30 +24,55 @@ namespace Titanic { -CEnterExitView::CEnterExitView() : CGameObject(), _fieldBC(0), - _fieldC0(0), _fieldC4(0), _fieldC8(0), _fieldCC(0) { +BEGIN_MESSAGE_MAP(CEnterExitView, CGameObject) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + +CEnterExitView::CEnterExitView() : CGameObject(), _leaveEndFrame(0), + _leaveStartFrame(0), _enterEndFrame(0), _enterStartFrame(0), + _visibleAfterMovie(true) { } void CEnterExitView::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); - file->writeNumberLine(_fieldC4, indent); - file->writeNumberLine(_fieldC8, indent); - file->writeNumberLine(_fieldCC, indent); + file->writeNumberLine(_leaveEndFrame, indent); + file->writeNumberLine(_leaveStartFrame, indent); + file->writeNumberLine(_enterEndFrame, indent); + file->writeNumberLine(_enterStartFrame, indent); + file->writeNumberLine(_visibleAfterMovie, indent); CGameObject::save(file, indent); } void CEnterExitView::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); - _fieldC4 = file->readNumber(); - _fieldC8 = file->readNumber(); - _fieldCC = file->readNumber(); + _leaveEndFrame = file->readNumber(); + _leaveStartFrame = file->readNumber(); + _enterEndFrame = file->readNumber(); + _enterStartFrame = file->readNumber(); + _visibleAfterMovie = file->readNumber(); CGameObject::load(file); } +bool CEnterExitView::EnterViewMsg(CEnterViewMsg *msg) { + setVisible(true); + playMovie(_enterStartFrame, _enterEndFrame, MOVIE_NOTIFY_OBJECT); + return true; +} + +bool CEnterExitView::LeaveViewMsg(CLeaveViewMsg *msg) { + setVisible(true); + playMovie(_leaveStartFrame, _leaveEndFrame, MOVIE_NOTIFY_OBJECT); + return true; +} + +bool CEnterExitView::MovieEndMsg(CMovieEndMsg *msg) { + if (!_visibleAfterMovie) + setVisible(false); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/enter_exit_view.h b/engines/titanic/moves/enter_exit_view.h index 4a3f1a967b..67aa5643ff 100644 --- a/engines/titanic/moves/enter_exit_view.h +++ b/engines/titanic/moves/enter_exit_view.h @@ -28,12 +28,16 @@ namespace Titanic { class CEnterExitView : public CGameObject { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: - int _fieldBC; - int _fieldC0; - int _fieldC4; - int _fieldC8; - int _fieldCC; + int _leaveEndFrame; + int _leaveStartFrame; + int _enterEndFrame; + int _enterStartFrame; + bool _visibleAfterMovie; public: CLASSDEF; CEnterExitView(); diff --git a/engines/titanic/moves/enter_sec_class_state.cpp b/engines/titanic/moves/enter_sec_class_state.cpp index dced724de7..2a35621003 100644 --- a/engines/titanic/moves/enter_sec_class_state.cpp +++ b/engines/titanic/moves/enter_sec_class_state.cpp @@ -21,23 +21,88 @@ */ #include "titanic/moves/enter_sec_class_state.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CEnterSecClassState, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CEnterSecClassState::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_value1, indent); - file->writeNumberLine(_value2, indent); + file->writeNumberLine(_mode, indent); + file->writeNumberLine(_soundHandle, indent); CGameObject::save(file, indent); } void CEnterSecClassState::load(SimpleFile *file) { file->readNumber(); - _value1 = file->readNumber(); - _value2 = file->readNumber(); + _mode = file->readNumber(); + _soundHandle = file->readNumber(); CGameObject::load(file); } +bool CEnterSecClassState::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (getPassengerClass() > 2) { + playSound("b#105.wav"); + petDisplayMessage(1, "Passengers of your class are not permitted to enter this area."); + } else if (!compareRoomNameTo("SecClassLittleLift") || _mode == 2) { + CActMsg actMsg(getFullViewName().deleteRight(3) + ".S"); + actMsg.execute("SecClassRoomLeaver"); + changeView("secClassState.Node 01.N"); + } + + return true; +} + +bool CEnterSecClassState::StatusChangeMsg(CStatusChangeMsg *msg) { + stopSound(_soundHandle); + + if (msg->_newStatus == _mode || (_mode == 2 && msg->_newStatus == 3)) { + if (_mode == 2) { + _soundHandle = queueSound("b#36.wav", _soundHandle); + } else { + _soundHandle = queueSound("b#31.wav", _soundHandle); + } + if (msg->_newStatus == 3) + msg->_newStatus = 2; + } else { + changeView("SecClassLittleLift.Node 1.N"); + if (msg->_newStatus == 1) { + _soundHandle = queueSound("b#32.wav", _soundHandle); + } else if (msg->_newStatus == 2) { + _soundHandle = queueSound("b#25.wav", _soundHandle); + } else if (msg->_newStatus == 3) { + _soundHandle = queueSound("b#33.wav", _soundHandle); + msg->_newStatus = 2; + } + } + + if (msg->_newStatus != 3) { + if (msg->_newStatus == 2 && _mode == 1) + playMovie(0, 10, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + else if (msg->_newStatus == 1) + playMovie(11, 21, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + + _cursorId = msg->_newStatus == 2 ? CURSOR_MOVE_FORWARD : CURSOR_INVALID; + _mode = msg->_newStatus; + return true; +} + +bool CEnterSecClassState::MovieEndMsg(CMovieEndMsg *msg) { + CPetControl *pet = getPetControl(); + if (pet) { + pet->setRooms1CC(_mode); + pet->resetRoomsHighlight(); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/enter_sec_class_state.h b/engines/titanic/moves/enter_sec_class_state.h index c3e3cabf20..2b1bcaa401 100644 --- a/engines/titanic/moves/enter_sec_class_state.h +++ b/engines/titanic/moves/enter_sec_class_state.h @@ -28,11 +28,15 @@ namespace Titanic { class CEnterSecClassState : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: - int _value1, _value2; + int _mode, _soundHandle; public: CLASSDEF; - CEnterSecClassState() : CGameObject(), _value1(0), _value2(0) {} + CEnterSecClassState() : CGameObject(), _mode(1), _soundHandle(0) {} /** * Save the data for the class to file diff --git a/engines/titanic/moves/exit_arboretum.cpp b/engines/titanic/moves/exit_arboretum.cpp index d606510c6e..ba162843b5 100644 --- a/engines/titanic/moves/exit_arboretum.cpp +++ b/engines/titanic/moves/exit_arboretum.cpp @@ -21,29 +21,85 @@ */ #include "titanic/moves/exit_arboretum.h" +#include "titanic/game/seasonal_adjustment.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CExitArboretum, CMovePlayerTo) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(ChangeSeasonMsg) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) +END_MESSAGE_MAP() + CExitArboretum::CExitArboretum() : CMovePlayerTo(), - _fieldC8(0), _fieldCC(0), _fieldD0(1) { + _seasonNum(0), _fieldCC(0), _enabled(true) { } void CExitArboretum::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldC8, indent); + file->writeNumberLine(_seasonNum, indent); file->writeNumberLine(_fieldCC, indent); - file->writeNumberLine(_fieldD0, indent); + file->writeNumberLine(_enabled, indent); CMovePlayerTo::save(file, indent); } void CExitArboretum::load(SimpleFile *file) { file->readNumber(); - _fieldC8 = file->readNumber(); + _seasonNum = file->readNumber(); _fieldCC = file->readNumber(); - _fieldD0 = file->readNumber(); + _enabled = file->readNumber(); CMovePlayerTo::load(file); } +bool CExitArboretum::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_enabled) { + CActMsg actMsg; + if (_seasonNum == SEASON_WINTER) { + switch (_fieldCC) { + case 0: + actMsg._action = "ExitLFrozen"; + break; + case 1: + actMsg._action = "ExitRFrozen"; + break; + default: + break; + } + } else { + switch (_fieldCC) { + case 0: + actMsg._action = "ExitLNormal"; + break; + case 1: + actMsg._action = "ExitRNormal"; + break; + default: + break; + } + } + + actMsg.execute("ArbGate"); + } + + return true; +} + +bool CExitArboretum::ChangeSeasonMsg(CChangeSeasonMsg *msg) { + _seasonNum = (_seasonNum + 1) % 4; + return true; +} + +bool CExitArboretum::TurnOn(CTurnOn *msg) { + _enabled = false; + return true; +} + +bool CExitArboretum::TurnOff(CTurnOff *msg) { + _enabled = true; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/exit_arboretum.h b/engines/titanic/moves/exit_arboretum.h index f6ebf71515..b65eb92b17 100644 --- a/engines/titanic/moves/exit_arboretum.h +++ b/engines/titanic/moves/exit_arboretum.h @@ -28,10 +28,15 @@ namespace Titanic { class CExitArboretum : public CMovePlayerTo { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool ChangeSeasonMsg(CChangeSeasonMsg *msg); + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); protected: - int _fieldC8; + int _seasonNum; int _fieldCC; - int _fieldD0; + bool _enabled; public: CLASSDEF; CExitArboretum(); diff --git a/engines/titanic/moves/exit_bridge.cpp b/engines/titanic/moves/exit_bridge.cpp index b913911341..6b69b88004 100644 --- a/engines/titanic/moves/exit_bridge.cpp +++ b/engines/titanic/moves/exit_bridge.cpp @@ -24,21 +24,35 @@ namespace Titanic { -CExitBridge::CExitBridge() : CMovePlayerTo() { +BEGIN_MESSAGE_MAP(CExitBridge, CMovePlayerTo) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + +CExitBridge::CExitBridge() : CMovePlayerTo(), _viewName("Titania.Node 1.S") { } void CExitBridge::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string1, indent); + file->writeQuotedLine(_viewName, indent); CMovePlayerTo::save(file, indent); } void CExitBridge::load(SimpleFile *file) { file->readNumber(); - _string1 = file->readString(); + _viewName = file->readString(); CMovePlayerTo::load(file); } +bool CExitBridge::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (getGameManager()) { + changeView(_destination); + playSound("a#53.wav"); + changeView(_viewName); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/exit_bridge.h b/engines/titanic/moves/exit_bridge.h index 4ab29524db..6d8ba01c91 100644 --- a/engines/titanic/moves/exit_bridge.h +++ b/engines/titanic/moves/exit_bridge.h @@ -28,8 +28,10 @@ namespace Titanic { class CExitBridge : public CMovePlayerTo { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); private: - CString _string1; + CString _viewName; public: CLASSDEF; CExitBridge(); diff --git a/engines/titanic/moves/exit_lift.cpp b/engines/titanic/moves/exit_lift.cpp index a264be883d..de9a3117af 100644 --- a/engines/titanic/moves/exit_lift.cpp +++ b/engines/titanic/moves/exit_lift.cpp @@ -21,19 +21,101 @@ */ #include "titanic/moves/exit_lift.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CExitLift, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + +CExitLift::CExitLift() : CGameObject(), _viewName("NULL") { +} + void CExitLift::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_value, indent); + file->writeQuotedLine(_viewName, indent); CGameObject::save(file, indent); } void CExitLift::load(SimpleFile *file) { file->readNumber(); - _value = file->readString(); + _viewName = file->readString(); CGameObject::load(file); } +bool CExitLift::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CPetControl *pet = getPetControl(); + int floorNum = pet->getRoomsFloorNum();//ebx + int elevNum = pet->getRoomsElevatorNum(); //eax + + if (floorNum == 39) { + switch (elevNum) { + case 1: + _viewName = "BottomOfWell.Node 5.SE"; + break; + case 3: + _viewName = "BottomOfWell.Node 1.NW"; + break; + default: + break; + } + } else if (floorNum > 27) { + switch (elevNum) { + case 1: + case 2: + _viewName = "SgtLobby.Node 1.N"; + break; + default: + break; + } + } else if (floorNum > 19) { + switch (elevNum) { + case 0: + case 2: + _viewName = "2ndClassLobby.Node 8.N"; + break; + case 1: + case 3: + _viewName = "2ndClassLobby.Node 1.N"; + break; + default: + break; + } + } else if (floorNum > 1) { + switch (elevNum) { + case 0: + case 2: + _viewName = "1stClassLobby.Node 1.W"; + break; + case 1: + case 3: + _viewName = "1stClassLobby.Node 1.E"; + break; + default: + break; + } + } else { + switch (elevNum) { + case 0: + _viewName = "TopOfWell.Node 6.E"; + break; + case 1: + _viewName = "TopOfWell.Node 6.W"; + break; + case 2: + _viewName = "TopOfWell.Node 10.W"; + break; + case 3: + _viewName = "TopOfWell.Node 10.E"; + break; + default: + break; + } + } + + changeView(_viewName); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/exit_lift.h b/engines/titanic/moves/exit_lift.h index 04dabfaf13..93d760c35a 100644 --- a/engines/titanic/moves/exit_lift.h +++ b/engines/titanic/moves/exit_lift.h @@ -28,10 +28,13 @@ namespace Titanic { class CExitLift : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: - CString _value; + CString _viewName; public: CLASSDEF; + CExitLift(); /** * Save the data for the class to file diff --git a/engines/titanic/moves/exit_pellerator.cpp b/engines/titanic/moves/exit_pellerator.cpp index 68a2a8da91..12ca2c1e3c 100644 --- a/engines/titanic/moves/exit_pellerator.cpp +++ b/engines/titanic/moves/exit_pellerator.cpp @@ -21,9 +21,16 @@ */ #include "titanic/moves/exit_pellerator.h" +#include "titanic/game/transport/pellerator.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CExitPellerator, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(ChangeSeasonMsg) +END_MESSAGE_MAP() + CExitPelleratorStatics *CExitPellerator::_statics; void CExitPellerator::init() { @@ -38,7 +45,7 @@ void CExitPellerator::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeQuotedLine(_statics->_v1, indent); file->writeNumberLine(_statics->_v2, indent); - file->writeNumberLine(_statics->_v3, indent); + file->writeNumberLine(_statics->_isWinter, indent); CGameObject::save(file, indent); } @@ -47,9 +54,84 @@ void CExitPellerator::load(SimpleFile *file) { file->readNumber(); _statics->_v1 = file->readString(); _statics->_v2 = file->readNumber(); - _statics->_v3 = file->readNumber(); + _statics->_isWinter = file->readNumber(); CGameObject::load(file); } +bool CExitPellerator::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CString name = getName(); + + if (name == "ExitPellerator") { + if (_statics->_v2 != 2) { + switch (getRandomNumber(2)) { + case 0: + CPellerator::_soundHandle = queueSound("z#457.wav", CPellerator::_soundHandle); + break; + case 1: + CPellerator::_soundHandle = queueSound("z#458.wav", CPellerator::_soundHandle); + break; + default: + CPellerator::_soundHandle = queueSound("z#464.wav", CPellerator::_soundHandle); + break; + } + } + + switch (_statics->_v2) { + case 0: + changeView("PromenadeDeck.Node 1.W"); + break; + case 1: + changeView("MusicRoomLobby.Node 1.S"); + break; + case 4: + changeView("TopOfWell.Node 6.N"); + break; + case 5: + changeView("1stClassRestaurant.Lobby Node.E"); + break; + case 6: + changeView(_statics->_isWinter ? "FrozenArboretum.Node 4.S" : "Arboretum.Node 4.W"); + break; + default: + petDisplayMessage(2, "Please exit from the other side."); + CPellerator::_soundHandle = queueSound("z#438.wav", CPellerator::_soundHandle); + + } + } else if (name == "ExitPellerator2") { + if (_statics->_v2 == 2) { + switch (getRandomNumber(2)) { + case 0: + CPellerator::_soundHandle = queueSound("z#457.wav", CPellerator::_soundHandle); + break; + case 1: + CPellerator::_soundHandle = queueSound("z#458.wav", CPellerator::_soundHandle); + break; + default: + CPellerator::_soundHandle = queueSound("z#464.wav", CPellerator::_soundHandle); + break; + } + } + + if (_statics->_v2 == 2) { + changeView("Bar.Node 1.N"); + } else { + petDisplayMessage(2, "Please exit from the other side."); + CPellerator::_soundHandle = queueSound("z#438.wav", CPellerator::_soundHandle); + } + } + + return true; +} + +bool CExitPellerator::StatusChangeMsg(CStatusChangeMsg *msg) { + _statics->_v2 = msg->_newStatus; + return true; +} + +bool CExitPellerator::ChangeSeasonMsg(CChangeSeasonMsg *msg) { + _statics->_isWinter = msg->_season == "Winter"; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/exit_pellerator.h b/engines/titanic/moves/exit_pellerator.h index 280d1e9a6c..8819d64355 100644 --- a/engines/titanic/moves/exit_pellerator.h +++ b/engines/titanic/moves/exit_pellerator.h @@ -30,10 +30,14 @@ namespace Titanic { struct CExitPelleratorStatics { CString _v1; int _v2; - int _v3; + bool _isWinter; }; class CExitPellerator : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool ChangeSeasonMsg(CChangeSeasonMsg *msg); private: static CExitPelleratorStatics *_statics; public: diff --git a/engines/titanic/moves/exit_state_room.cpp b/engines/titanic/moves/exit_state_room.cpp index 1c78a69ac2..f0b0534c12 100644 --- a/engines/titanic/moves/exit_state_room.cpp +++ b/engines/titanic/moves/exit_state_room.cpp @@ -24,7 +24,11 @@ namespace Titanic { -CExitStateRoom::CExitStateRoom() : CMovePlayerTo(), _fieldC8(0) { +BEGIN_MESSAGE_MAP(CExitStateRoom, CMovePlayerTo) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + +CExitStateRoom::CExitStateRoom() : CMovePlayerTo() { } void CExitStateRoom::save(SimpleFile *file, int indent) { @@ -37,4 +41,9 @@ void CExitStateRoom::load(SimpleFile *file) { CMovePlayerTo::load(file); } +bool CExitStateRoom::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + changeView(_destination); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/exit_state_room.h b/engines/titanic/moves/exit_state_room.h index c0f9737817..19322ced7f 100644 --- a/engines/titanic/moves/exit_state_room.h +++ b/engines/titanic/moves/exit_state_room.h @@ -28,8 +28,8 @@ namespace Titanic { class CExitStateRoom : public CMovePlayerTo { -protected: - int _fieldC8; + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: CLASSDEF; CExitStateRoom(); diff --git a/engines/titanic/moves/exit_tiania.cpp b/engines/titanic/moves/exit_tiania.cpp index 6cb2422b1f..fb0f149ba9 100644 --- a/engines/titanic/moves/exit_tiania.cpp +++ b/engines/titanic/moves/exit_tiania.cpp @@ -24,16 +24,20 @@ namespace Titanic { -CExitTiania::CExitTiania() : CMovePlayerTo(), _fieldC8(0), - _string1("NULL"), _string2("NULL"), _string3("NULL") { +BEGIN_MESSAGE_MAP(CExitTiania, CMovePlayerTo) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + +CExitTiania::CExitTiania() : CMovePlayerTo(), _fieldC8(0) { + _viewNames[0] = _viewNames[1] = _viewNames[2] = "NULL"; } void CExitTiania::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldC8, indent); - file->writeQuotedLine(_string1, indent); - file->writeQuotedLine(_string2, indent); - file->writeQuotedLine(_string3, indent); + file->writeQuotedLine(_viewNames[0], indent); + file->writeQuotedLine(_viewNames[1], indent); + file->writeQuotedLine(_viewNames[2], indent); CMovePlayerTo::save(file, indent); } @@ -41,11 +45,29 @@ void CExitTiania::save(SimpleFile *file, int indent) { void CExitTiania::load(SimpleFile *file) { file->readNumber(); _fieldC8 = file->readNumber(); - _string1 = file->readString(); - _string2 = file->readString(); - _string3 = file->readString(); + _viewNames[0] = file->readString(); + _viewNames[1] = file->readString(); + _viewNames[2] = file->readString(); CMovePlayerTo::load(file); } +bool CExitTiania::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (getPassengerClass() == 4) { + petDisplayMessage(1, "For mysterious and unknowable reasons, " + "this transport is temporarily out of order."); + } else { + lockMouse(); + for (int idx = 0; idx < 3; ++idx) + changeView(_viewNames[idx]); + changeView("Titania.Node 16.N"); + changeView("Dome.Node 4.N"); + changeView("Dome.Node 3.N"); + changeView("Dome.Node 3.S"); + unlockMouse(); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/exit_tiania.h b/engines/titanic/moves/exit_tiania.h index c2b7772ce7..b911e102d1 100644 --- a/engines/titanic/moves/exit_tiania.h +++ b/engines/titanic/moves/exit_tiania.h @@ -28,11 +28,11 @@ namespace Titanic { class CExitTiania : public CMovePlayerTo { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); private: int _fieldC8; - CString _string1; - CString _string2; - CString _string3; + CString _viewNames[3]; public: CLASSDEF; CExitTiania(); diff --git a/engines/titanic/moves/move_player_in_parrot_room.cpp b/engines/titanic/moves/move_player_in_parrot_room.cpp index df38c63cd4..1ef2e96e92 100644 --- a/engines/titanic/moves/move_player_in_parrot_room.cpp +++ b/engines/titanic/moves/move_player_in_parrot_room.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CMovePlayerInParrotRoom, CMovePlayerTo) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + CMovePlayerInParrotRoom::CMovePlayerInParrotRoom() : CMovePlayerTo() { } @@ -37,4 +42,20 @@ void CMovePlayerInParrotRoom::load(SimpleFile *file) { CMovePlayerTo::load(file); } +bool CMovePlayerInParrotRoom::ActMsg(CActMsg *msg) { + if (msg->_action == "PanAwayFromParrot") { + unlockMouse(); + changeView(_destination); + } + + return true; +} + +bool CMovePlayerInParrotRoom::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + lockMouse(); + CPanningAwayFromParrotMsg awayMsg(this); + awayMsg.execute("PerchedParrot"); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/move_player_in_parrot_room.h b/engines/titanic/moves/move_player_in_parrot_room.h index de693fe2e2..54dc2eb992 100644 --- a/engines/titanic/moves/move_player_in_parrot_room.h +++ b/engines/titanic/moves/move_player_in_parrot_room.h @@ -28,6 +28,9 @@ namespace Titanic { class CMovePlayerInParrotRoom : public CMovePlayerTo { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: CLASSDEF; CMovePlayerInParrotRoom(); diff --git a/engines/titanic/moves/move_player_to.cpp b/engines/titanic/moves/move_player_to.cpp index 9b6000c4f8..a91215b539 100644 --- a/engines/titanic/moves/move_player_to.cpp +++ b/engines/titanic/moves/move_player_to.cpp @@ -21,9 +21,15 @@ */ #include "titanic/moves/move_player_to.h" +#include "titanic/game_manager.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CMovePlayerTo, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + CMovePlayerTo::CMovePlayerTo() : CGameObject() { } @@ -41,4 +47,17 @@ void CMovePlayerTo::load(SimpleFile *file) { CGameObject::load(file); } +bool CMovePlayerTo::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CGameManager *gameManager = getGameManager(); + if (gameManager) + changeView(_destination); + + return true; +} + +bool CMovePlayerTo::ActMsg(CActMsg *msg) { + _destination = msg->_action; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/move_player_to.h b/engines/titanic/moves/move_player_to.h index 4bfffcb0b2..822df69422 100644 --- a/engines/titanic/moves/move_player_to.h +++ b/engines/titanic/moves/move_player_to.h @@ -28,6 +28,9 @@ namespace Titanic { class CMovePlayerTo : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool ActMsg(CActMsg *msg); protected: CString _destination; public: diff --git a/engines/titanic/moves/move_player_to_from.cpp b/engines/titanic/moves/move_player_to_from.cpp index 1a67dc8505..c57cc2cf51 100644 --- a/engines/titanic/moves/move_player_to_from.cpp +++ b/engines/titanic/moves/move_player_to_from.cpp @@ -21,10 +21,16 @@ */ #include "titanic/moves/move_player_to_from.h" +#include "titanic/core/view_item.h" +#include "titanic/core/link_item.h" namespace Titanic { -CMovePlayerToFrom::CMovePlayerToFrom() : CGameObject() { +BEGIN_MESSAGE_MAP(CMovePlayerToFrom, CMovePlayerTo) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + +CMovePlayerToFrom::CMovePlayerToFrom() : CMovePlayerTo() { } void CMovePlayerToFrom::save(SimpleFile *file, int indent) { @@ -41,4 +47,17 @@ void CMovePlayerToFrom::load(SimpleFile *file) { CGameObject::load(file); } +bool CMovePlayerToFrom::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_string2.empty()) { + changeView(_destination); + } else { + CViewItem *view = parseView(_string2); + CViewItem *destView = parseView(_destination); + CLinkItem *link = view->findLink(destView); + changeView(_destination, link->getName()); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/move_player_to_from.h b/engines/titanic/moves/move_player_to_from.h index c9eefe532f..fde4e94ab5 100644 --- a/engines/titanic/moves/move_player_to_from.h +++ b/engines/titanic/moves/move_player_to_from.h @@ -23,11 +23,13 @@ #ifndef TITANIC_MOVE_PLAYER_TO_FROM_H #define TITANIC_MOVE_PLAYER_TO_FROM_H -#include "titanic/core/game_object.h" +#include "titanic/moves/move_player_to.h" namespace Titanic { -class CMovePlayerToFrom : public CGameObject { +class CMovePlayerToFrom : public CMovePlayerTo { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); private: CString _string2; public: diff --git a/engines/titanic/moves/multi_move.cpp b/engines/titanic/moves/multi_move.cpp index fb5570df9b..4ca4fdb8f3 100644 --- a/engines/titanic/moves/multi_move.cpp +++ b/engines/titanic/moves/multi_move.cpp @@ -24,29 +24,37 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CMultiMove, CMovePlayerTo) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + CMultiMove::CMultiMove() : CMovePlayerTo() { } void CMultiMove::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string1, indent); - file->writeQuotedLine(_string2, indent); - file->writeQuotedLine(_string3, indent); - file->writeQuotedLine(_string4, indent); - file->writeQuotedLine(_string5, indent); + for (int idx = 0; idx < 5; ++idx) + file->writeQuotedLine(_viewNames[idx], indent); CMovePlayerTo::save(file, indent); } void CMultiMove::load(SimpleFile *file) { file->readNumber(); - _string1 = file->readString(); - _string2 = file->readString(); - _string3 = file->readString(); - _string5 = file->readString(); - _string4 = file->readString(); + for (int idx = 0; idx < 5; ++idx) + _viewNames[idx] = file->readString(); CMovePlayerTo::load(file); } +bool CMultiMove::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + lockMouse(); + + for (int idx = 0; idx < 5 && _viewNames[idx] != "NULL"; ++idx) + changeView(_viewNames[idx]); + + unlockMouse(); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/multi_move.h b/engines/titanic/moves/multi_move.h index 977afc2a20..12dd246823 100644 --- a/engines/titanic/moves/multi_move.h +++ b/engines/titanic/moves/multi_move.h @@ -28,12 +28,10 @@ namespace Titanic { class CMultiMove : public CMovePlayerTo { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); private: - CString _string1; - CString _string2; - CString _string3; - CString _string4; - CString _string5; + CString _viewNames[5]; public: CLASSDEF; CMultiMove(); diff --git a/engines/titanic/moves/pan_from_pel.cpp b/engines/titanic/moves/pan_from_pel.cpp index fccc643ec5..ca48e888c1 100644 --- a/engines/titanic/moves/pan_from_pel.cpp +++ b/engines/titanic/moves/pan_from_pel.cpp @@ -24,23 +24,33 @@ namespace Titanic { -CPanFromPel::CPanFromPel() : CMovePlayerTo(), _fieldC8(0) { +BEGIN_MESSAGE_MAP(CPanFromPel, CMovePlayerTo) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + +CPanFromPel::CPanFromPel() : CMovePlayerTo(), _closeLeft(false) { } void CPanFromPel::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldC8, indent); - file->writeQuotedLine(_string1, indent); + file->writeNumberLine(_closeLeft, indent); + file->writeQuotedLine(_target, indent); CMovePlayerTo::save(file, indent); } void CPanFromPel::load(SimpleFile *file) { file->readNumber(); - _fieldC8 = file->readNumber(); - _string1 = file->readString(); + _closeLeft = file->readNumber(); + _target = file->readString(); CMovePlayerTo::load(file); } +bool CPanFromPel::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CActMsg actMsg(_closeLeft ? "CloseLeft" : "CloseRight"); + actMsg.execute(_target); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/pan_from_pel.h b/engines/titanic/moves/pan_from_pel.h index c81be9f338..0a01aefea3 100644 --- a/engines/titanic/moves/pan_from_pel.h +++ b/engines/titanic/moves/pan_from_pel.h @@ -28,9 +28,11 @@ namespace Titanic { class CPanFromPel : public CMovePlayerTo { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); protected: - int _fieldC8; - CString _string1; + bool _closeLeft; + CString _target; public: CLASSDEF; CPanFromPel(); diff --git a/engines/titanic/moves/restaurant_pan_handler.cpp b/engines/titanic/moves/restaurant_pan_handler.cpp index 92f55b46cc..d93e331254 100644 --- a/engines/titanic/moves/restaurant_pan_handler.cpp +++ b/engines/titanic/moves/restaurant_pan_handler.cpp @@ -24,24 +24,40 @@ namespace Titanic { -int CRestaurantPanHandler::_v1; +BEGIN_MESSAGE_MAP(CRestaurantPanHandler, CMovePlayerTo) + ON_MESSAGE(ArmPickedUpFromTableMsg) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + +bool CRestaurantPanHandler::_armPickedUp; void CRestaurantPanHandler::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_v1, indent); - file->writeQuotedLine(_string1, indent); - file->writeQuotedLine(_string2, indent); + file->writeNumberLine(_armPickedUp, indent); + file->writeQuotedLine(_armlessDestination, indent); + file->writeQuotedLine(_armDestination, indent); CMovePlayerTo::save(file, indent); } void CRestaurantPanHandler::load(SimpleFile *file) { file->readNumber(); - _v1 = file->readNumber(); - _string1 = file->readString(); - _string2 = file->readString(); + _armPickedUp = file->readNumber(); + _armlessDestination = file->readString(); + _armDestination = file->readString(); CMovePlayerTo::load(file); } +bool CRestaurantPanHandler::ArmPickedUpFromTableMsg(CArmPickedUpFromTableMsg *msg) { + _armPickedUp = true; + return true; +} + +bool CRestaurantPanHandler::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + _destination = _armPickedUp ? _armDestination : _armlessDestination; + changeView(_destination); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/restaurant_pan_handler.h b/engines/titanic/moves/restaurant_pan_handler.h index 4925aa685b..9684fd07f5 100644 --- a/engines/titanic/moves/restaurant_pan_handler.h +++ b/engines/titanic/moves/restaurant_pan_handler.h @@ -28,11 +28,14 @@ namespace Titanic { class CRestaurantPanHandler : public CMovePlayerTo { + DECLARE_MESSAGE_MAP; + bool ArmPickedUpFromTableMsg(CArmPickedUpFromTableMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); protected: - static int _v1; - - CString _string1; - CString _string2; + CString _armDestination; + CString _armlessDestination; +public: + static bool _armPickedUp; public: CLASSDEF; diff --git a/engines/titanic/moves/restricted_move.cpp b/engines/titanic/moves/restricted_move.cpp index 5f18dab8ff..37cb1c46dc 100644 --- a/engines/titanic/moves/restricted_move.cpp +++ b/engines/titanic/moves/restricted_move.cpp @@ -24,21 +24,59 @@ namespace Titanic { -CRestrictedMove::CRestrictedMove() : CMovePlayerTo(), _fieldC8(0) { +BEGIN_MESSAGE_MAP(CRestrictedMove, CMovePlayerTo) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + +CRestrictedMove::CRestrictedMove() : CMovePlayerTo(), _classNum(0) { } void CRestrictedMove::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldC8, indent); + file->writeNumberLine(_classNum, indent); CMovePlayerTo::save(file, indent); } void CRestrictedMove::load(SimpleFile *file) { file->readNumber(); - _fieldC8 = file->readNumber(); + _classNum = file->readNumber(); CMovePlayerTo::load(file); } +bool CRestrictedMove::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + int classNum = getPassengerClass(); + if (classNum <= _classNum) { + // Okay to change to the given destination + changeView(_destination); + } else if (classNum != 4) { + petDisplayMessage(1, "Passengers of your class are not permitted to enter this area."); + } else if (compareRoomNameTo("EmbLobby")) { + playSound("a#17.wav"); + petDisplayMessage(1, "Please check in at the reception desk."); + } else if (compareViewNameTo("Titania.Node 1.S")) { + playSound("z#226.wav"); + changeView(_destination); + } + + return true; +} + +bool CRestrictedMove::EnterViewMsg(CEnterViewMsg *msg) { + int classNum = getPassengerClass(); + bool flag = classNum > _classNum; + + if (classNum == 4) { + if (compareRoomNameTo("EmbLobby")) + flag = false; + else if (compareViewNameTo("Titania.Node 1.S")) + flag = true; + } + + _cursorId = flag ? CURSOR_MOVE_FORWARD : CURSOR_INVALID; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/restricted_move.h b/engines/titanic/moves/restricted_move.h index bdf093c353..639b024701 100644 --- a/engines/titanic/moves/restricted_move.h +++ b/engines/titanic/moves/restricted_move.h @@ -28,8 +28,11 @@ namespace Titanic { class CRestrictedMove : public CMovePlayerTo { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); protected: - int _fieldC8; + int _classNum; public: CLASSDEF; CRestrictedMove(); diff --git a/engines/titanic/moves/scraliontis_table.cpp b/engines/titanic/moves/scraliontis_table.cpp index 77d2f9df60..8d39e2104f 100644 --- a/engines/titanic/moves/scraliontis_table.cpp +++ b/engines/titanic/moves/scraliontis_table.cpp @@ -24,15 +24,21 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CScraliontisTable, CRestaurantPanHandler) + ON_MESSAGE(MouseMoveMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MaitreDDefeatedMsg) +END_MESSAGE_MAP() + CScraliontisTable::CScraliontisTable() : CRestaurantPanHandler(), - _fieldE0(0), _fieldE4(0), _fieldE8(0), _fieldEC(0) { + _fieldE0(false), _counter(0), _ticks(0), _fieldEC(false) { } void CScraliontisTable::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldE0, indent); - file->writeNumberLine(_fieldE4, indent); - file->writeNumberLine(_fieldE8, indent); + file->writeNumberLine(_counter, indent); + file->writeNumberLine(_ticks, indent); file->writeNumberLine(_fieldEC, indent); CRestaurantPanHandler::save(file, indent); @@ -41,11 +47,42 @@ void CScraliontisTable::save(SimpleFile *file, int indent) { void CScraliontisTable::load(SimpleFile *file) { file->readNumber(); _fieldE0 = file->readNumber(); - _fieldE4 = file->readNumber(); - _fieldE8 = file->readNumber(); + _counter = file->readNumber(); + _ticks = file->readNumber(); _fieldEC = file->readNumber(); CRestaurantPanHandler::load(file); } +bool CScraliontisTable::MouseMoveMsg(CMouseMoveMsg *msg) { + if (!_fieldEC && !_fieldE0) { + if (++_counter > 20) { + CTriggerNPCEvent triggerMsg; + triggerMsg.execute("MaitreD"); + _fieldE0 = true; + } + } + + return true; +} + +bool CScraliontisTable::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_fieldEC) { + changeView(_destination, _armPickedUp ? _armDestination : _armlessDestination); + } + else if (!_ticks || (getTicksCount() - _ticks) >= 5000) { + CTriggerNPCEvent triggerMsg(119); + triggerMsg.execute("MaitreD"); + _ticks = getTicksCount(); + } + + return true; +} + +bool CScraliontisTable::MaitreDDefeatedMsg(CMaitreDDefeatedMsg *msg) { + _cursorId = CURSOR_MOVE_FORWARD; + _fieldEC = true; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/scraliontis_table.h b/engines/titanic/moves/scraliontis_table.h index 2ce3745654..b0d8c6b5af 100644 --- a/engines/titanic/moves/scraliontis_table.h +++ b/engines/titanic/moves/scraliontis_table.h @@ -28,11 +28,15 @@ namespace Titanic { class CScraliontisTable : public CRestaurantPanHandler { + DECLARE_MESSAGE_MAP; + bool MouseMoveMsg(CMouseMoveMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MaitreDDefeatedMsg(CMaitreDDefeatedMsg *msg); private: - int _fieldE0; - int _fieldE4; - int _fieldE8; - int _fieldEC; + bool _fieldE0; + int _counter; + uint _ticks; + bool _fieldEC; public: CLASSDEF; CScraliontisTable(); diff --git a/engines/titanic/moves/trip_down_canal.cpp b/engines/titanic/moves/trip_down_canal.cpp index c8051dda03..e9818fa96d 100644 --- a/engines/titanic/moves/trip_down_canal.cpp +++ b/engines/titanic/moves/trip_down_canal.cpp @@ -24,6 +24,10 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CTripDownCanal, CMovePlayerTo) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + CTripDownCanal::CTripDownCanal() : CMovePlayerTo() { } @@ -37,4 +41,14 @@ void CTripDownCanal::load(SimpleFile *file) { CMovePlayerTo::load(file); } +bool CTripDownCanal::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (stateGetSeason() == SEASON_WINTER) { + petDisplayMessage("Sadly, the Grand Canal transport system is closed for the winter."); + } else if (getGameManager()) { + changeView(_destination); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/moves/trip_down_canal.h b/engines/titanic/moves/trip_down_canal.h index 736caf4131..f43fb05de1 100644 --- a/engines/titanic/moves/trip_down_canal.h +++ b/engines/titanic/moves/trip_down_canal.h @@ -28,6 +28,8 @@ namespace Titanic { class CTripDownCanal : public CMovePlayerTo { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: CLASSDEF; CTripDownCanal(); diff --git a/engines/titanic/npcs/barbot.cpp b/engines/titanic/npcs/barbot.cpp index 8f1c5e6ab3..2524a835f8 100644 --- a/engines/titanic/npcs/barbot.cpp +++ b/engines/titanic/npcs/barbot.cpp @@ -21,11 +21,43 @@ */ #include "titanic/npcs/barbot.h" +#include "titanic/titanic.h" namespace Titanic { int CBarbot::_v0; +BEGIN_MESSAGE_MAP(CBarbot, CTrueTalkNPC) + ON_MESSAGE(ActMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(TrueTalkSelfQueueAnimSetMsg) + ON_MESSAGE(TrueTalkQueueUpAnimSetMsg) + ON_MESSAGE(TrueTalkGetStateValueMsg) + ON_MESSAGE(TrueTalkTriggerActionMsg) + ON_MESSAGE(FrameMsg) + ON_MESSAGE(LoadSuccessMsg) + ON_MESSAGE(MovieFrameMsg) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(TimerMsg) +END_MESSAGE_MAP() + +CBarbot::FrameRanges::FrameRanges() : Common::Array<FrameRange>() { + resize(60); + Common::SeekableReadStream *stream = g_vm->_filesManager->getResource("FRAMES/BARBOT"); + for (int idx = 0; idx < 60; ++idx) { + (*this)[idx]._startFrame = stream->readUint32LE(); + (*this)[idx]._endFrame = stream->readUint32LE(); + } + + delete stream; +} + +/*------------------------------------------------------------------------*/ + CBarbot::CBarbot() : CTrueTalkNPC() { _field108 = 0; _field10C = 0; @@ -41,8 +73,8 @@ CBarbot::CBarbot() : CTrueTalkNPC() { _field134 = 0; _field138 = 0; _field13C = -1; - _field140 = 30; - _field144 = -1; + _volume = 30; + _frameNum = -1; _field148 = -1; _field14C = 0; _field150 = 0; @@ -50,125 +82,6 @@ CBarbot::CBarbot() : CTrueTalkNPC() { _field158 = -1; _field15C = 0; _field160 = 0; - _field164 = 558; - _field168 = 585; - _field16C = 659; - _field170 = 692; - _field174 = 802; - _field178 = 816; - _field17C = 1941; - _field180 = 1977; - _field184 = 1901; - _field188 = 1941; - _field18C = 810; - _field190 = 816; - _field194 = 857; - _field198 = 865; - _field19C = 842; - _field1A0 = 857; - _field1A4 = 821; - _field1A8 = 842; - _field1AC = 682; - _field1B0 = 692; - _field1B4 = 1977; - _field1B8 = 2018; - _field1BC = 2140; - _field1C0 = 2170; - _field1C4 = 2101; - _field1C8 = 2139; - _field1CC = 2018; - _field1D0 = 2099; - _field1D4 = 1902; - _field1D8 = 2015; - _field1E0 = 1811; - _field1E4 = 1901; - _field1E8 = 1810; - _field1EC = 1703; - _field1F0 = 1750; - _field1F4 = 1681; - _field1F8 = 1702; - _field1FC = 1642; - - _field200 = 1702; - _field204 = 1571; - _field208 = 1641; - _field20C = 1499; - _field210 = 1570; - _field214 = 1403; - _field218 = 1463; - _field21C = 1464; - _field220 = 1499; - _field224 = 1288; - _field228 = 1295; - _field22C = 1266; - _field230 = 1287; - _field234 = 1245; - _field238 = 1265; - _field23C = 1208; - _field240 = 1244; - _field244 = 1171; - _field248 = 1207; - _field24C = 1120; - _field250 = 1170; - _field254 = 1092; - _field258 = 1120; - _field25C = 1092; - _field260 = 1092; - _field264 = 1044; - _field268 = 1091; - _field26C = 1011; - _field270 = 1043; - _field274 = 1001; - _field278 = 1010; - _field27C = 985; - _field280 = 1001; - _field284 = 927; - _field288 = 984; - _field28C = 912; - _field290 = 926; - _field294 = 898; - _field298 = 906; - _field29C = 802; - _field2A0 = 896; - _field2A4 = 865; - _field2A8 = 896; - _field2AC = 842; - _field2B0 = 865; - _field2B4 = 816; - _field2B8 = 842; - _field2BC = 802; - _field2C0 = 842; - _field2C4 = 740; - _field2C8 = 740; - _field2CC = 740; - _field2D0 = 692; - _field2D4 = 610; - _field2D8 = 558; - _field2E0 = 610; - _field2E4 = 500; - _field2E8 = 558; - _field2EC = 467; - _field2F0 = 500; - _field2F4 = 421; - _field2F8 = 466; - _field2FC = 349; - _field300 = 306; - _field304 = 306; - _field308 = 348; - _field30C = 305; - _field310 = 306; - _field314 = 281; - _field318 = 305; - _field31C = 202; - _field320 = 281; - _field324 = 182; - _field328 = 202; - _field32C = 165; - _field330 = 182; - _field334 = 96; - _field338 = 165; - _field33C = 0; - _field340 = 95; } void CBarbot::save(SimpleFile *file, int indent) { @@ -189,8 +102,8 @@ void CBarbot::save(SimpleFile *file, int indent) { file->writeNumberLine(_field134, indent); file->writeNumberLine(_field138, indent); file->writeNumberLine(_field13C, indent); - file->writeNumberLine(_field140, indent); - file->writeNumberLine(_field144, indent); + file->writeNumberLine(_volume, indent); + file->writeNumberLine(_frameNum, indent); file->writeNumberLine(_field148, indent); file->writeNumberLine(_field14C, indent); file->writeNumberLine(_field150, indent); @@ -220,8 +133,8 @@ void CBarbot::load(SimpleFile *file) { _field134 = file->readNumber(); _field138 = file->readNumber(); _field13C = file->readNumber(); - _field140 = file->readNumber(); - _field144 = file->readNumber(); + _volume = file->readNumber(); + _frameNum = file->readNumber(); _field148 = file->readNumber(); _field14C = file->readNumber(); _field150 = file->readNumber(); @@ -233,9 +146,588 @@ void CBarbot::load(SimpleFile *file) { CTrueTalkNPC::load(file); } +bool CBarbot::ActMsg(CActMsg *msg) { + if (msg->_action == "Vodka") { + if (!_field12C) { + playRange(_frames[47], MOVIE_NOTIFY_OBJECT); + playRange(_frames[46]); + playRange(_frames[40]); + playRange(_frames[7]); + playRange(_frames[13]); + playRange(_frames[8]); + playRange(_frames[40]); + playRange(_frames[7]); + playRange(_frames[13]); + playRange(_frames[8]); + playRange(_frames[7]); + playRange(_frames[40]); + playRange(_frames[13]); + playRange(_frames[40]); + playRange(_frames[7]); + playRange(_frames[8]); + playRange(_frames[13]); + playRange(_frames[40], MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _frameNum = _frames[40]._endFrame; + } + } else if (msg->_action == "GiveBackVisCentre") { + if (_field134) { + playRange(_frames[27]); + _frameNum = _frames[27]._endFrame; + } + } else if (msg->_action == "Bird") { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 2; + statusMsg.execute("PickUpGlass"); + _field158 = 3; + + playRange(_frames[32], MOVIE_NOTIFY_OBJECT); + playRange(_frames[30], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[30]._endFrame; + + if (!_field114 || !_field118 || !_field12C) { + playRange(_frames[42], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[42]._endFrame; + } + + CActMsg actMsg("InTitilator"); + actMsg.execute("BeerGlass"); + } else if (msg->_action == "None") { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 2; + statusMsg.execute("PickUpGlass"); + _field158 = 0; + + playRange(_frames[55], MOVIE_NOTIFY_OBJECT); + playRange(_frames[54], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[54]._endFrame; + } else if (msg->_action == "Mustard" || msg->_action == "Tomato") { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 2; + statusMsg.execute("PickUpGlass"); + _field158 = 1; + + playRange(_frames[55], MOVIE_NOTIFY_OBJECT); + playRange(_frames[54], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[54]._endFrame; + + CActMsg actMsg("InTitilator"); + actMsg.execute("BeerGlass"); + } else if (msg->_action == "Fruit") { + if (!_field114) { + CActMsg visibleMsg; + visibleMsg.execute("LemonOnBar"); + startTalking(this, 250576); + _field114 = 1; + + playRange(_frames[36], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[36]._endFrame; + + if (!_field11C || !_field118 || _field12C) { + playRange(_frames[43], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[43]._endFrame; + } + + CRemoveFromGameMsg removeMsg; + removeMsg.execute("Lemon"); + } + } else if (msg->_action == "CrushedTV") { + if (!_field118) { + CVisibleMsg visibleMsg; + visibleMsg.execute("TVOnBar"); + startTalking(this, 250584); + _field160 = 1; + + playSound("c#5.wav", _volume); + playRange(_frames[35], MOVIE_NOTIFY_OBJECT); + playRange(_frames[34]); + playRange(_frames[33], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[33]._endFrame; + + if (!_field11C || !_field114 || !_field12C) { + playRange(_frames[41], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[41]._endFrame; + } + + CRemoveFromGameMsg removeMsg; + removeMsg.execute("CrushedTV"); + } + } else if (msg->_action == "PlayerTakesGlass") { + playRange(_frames[53]); + _field124 = 0; + + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 0; + statusMsg.execute("PickUpGlass"); + } else if (msg->_action == "PlayerTakesVisCentre") { + _field128 = 0; + loadFrame(0); + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 0; + statusMsg.execute("PickUpVisCentre"); + } else if (msg->_action == "BellRing1") { + startTalking(this, 251105); + } else if (msg->_action == "BellRing2") { + startTalking(this, 251107); + } else if (msg->_action == "BellRing3") { + startTalking(this, 250285); + } else if (msg->_action == "GoRingBell") { + startTalking(this, 250285); + } else if (msg->_action == "ClickOnVision") { + startTalking(this, 251858); + } + + return true; +} + +bool CBarbot::EnterViewMsg(CEnterViewMsg *msg) { + // I think this is a remnant of early debugging code + if (getName() != "Barbot") + playMovie(MOVIE_REPEAT); + + return true; +} + bool CBarbot::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("TODO: Barbot::CEnterRoomMsg"); + // I think this is a remnant of early debugging code + if (getName() != "Barbot") + addTimer(g_vm->getRandomNumber(20000)); + + return true; +} + +bool CBarbot::TurnOn(CTurnOn *msg) { + if (!_fieldC4) { + _field13C = -1; + setVisible(true); + + CGameObject *glass = findInRoom("BeerGlass"); + if (!_field130) { + CVisibleMsg visibleMsg(false); + visibleMsg.execute("BarShelfVisCentre"); + } + + if (glass && !_field11C) { + playRange(_frames[38], MOVIE_NOTIFY_OBJECT); + playRange(_frames[58], MOVIE_NOTIFY_OBJECT); + playRange(_frames[57], MOVIE_NOTIFY_OBJECT); + playRange(_frames[56], MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _frameNum = _frames[56]._endFrame; + } else { + playRange(_frames[38]); + playRange(_frames[23], MOVIE_NOTIFY_OBJECT); + playRange(_frames[21], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[21]._endFrame; + + switch (g_vm->getRandomNumber(2)) { + case 0: + playRange(_frames[10], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[10]._endFrame; + break; + case 1: + playRange(_frames[12], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[12]._endFrame; + break; + default: + break; + } + _field124 = 0; + } + + _fieldC4 = 1; + ++_v0; + petSetArea(PET_CONVERSATION); + endTalking(this, true); + } + + return true; +} + +bool CBarbot::TurnOff(CTurnOff *msg) { + if (_fieldC4) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 0; + statusMsg.execute("PickUpGlass"); + statusMsg.execute("PickUpVisCentre"); + + if (_field124) { + playRange(_frames[17], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[17]._endFrame; + _field124 = 0; + } + + if (_field128) { + playRange(_frames[28], MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _frameNum = _frames[28]._endFrame; + _field128 = 0; + _field134 = 1; + } + + playRange(_frames[29], MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + movieEvent(_frames[29]._startFrame); + _frameNum = _frames[29]._endFrame; + _fieldC4 = 0; + } + + return true; +} + +bool CBarbot::LeaveViewMsg(CLeaveViewMsg *msg) { + CTurnOff offMsg; + offMsg.execute(this); + return true; +} + +bool CBarbot::MovieEndMsg(CMovieEndMsg *msg) { + if (msg->_endFrame == _frameNum) { + _frameNum = -1; + _field14C = getTicksCount(); + } + + if (msg->_endFrame == _field148) { + _field148 = -1; + _field150 = getTicksCount(); + } + + if (msg->_endFrame == _field13C) { + if (_field124) + playMovie(_frames[53]._startFrame, _frames[53]._startFrame, 0); + else if (_field128) + playMovie(_frames[27]._endFrame, _frames[27]._endFrame, 0); + + _field13C = -1; + return true; + } + + if (msg->_endFrame == _frames[58]._endFrame || msg->_endFrame == _frames[21]._endFrame) { + CVisibleMsg visibleMsg(true); + visibleMsg.execute("BarShelfVisCentre"); + } + + if (msg->_endFrame == _frames[57]._endFrame) { + startTalking(this, 250575); + playSound("c#10.wav", _volume); + return true; + } + + if (msg->_endFrame == _frames[55]._endFrame) { + playSound("c#10.wav", _volume); + return true; + } + + if (msg->_endFrame == _frames[56]._endFrame + || msg->_endFrame == _frames[54]._endFrame) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 1; + statusMsg.execute("PickUpGlass"); + CMoveToStartPosMsg moveMsg; + moveMsg.execute("BeerGlass"); + return true; + } + + if (msg->_endFrame == _frames[30]._endFrame) { + _field124 = 0; + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 0; + statusMsg.execute("PickUpGlass"); + } + + if (msg->_endFrame == _frames[45]._endFrame) { + if (!_field130) { + CVisibleMsg visibleMsg(false); + visibleMsg.execute("BarShelfVisCentre"); + } + + return true; + } + + if (msg->_endFrame == _frames[44]._endFrame) { + _field128 = _field130 = 1; + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 1; + statusMsg.execute("PickUpVisCentre"); + CPuzzleSolvedMsg solvedMsg; + solvedMsg.execute("VisionCentre"); + } + + if (msg->_endFrame == _frames[46]._endFrame) { + if (!_field130 && !_field12C && _field11C && _field114 && _field118) + startTalking(this, 250571); + return true; + } + + if (msg->_endFrame == _frames[43]._endFrame + || msg->_endFrame == _frames[42]._endFrame + || msg->_endFrame == _frames[41]._endFrame) { + if (_field124) + playMovie(_frames[53]._startFrame, _frames[53]._startFrame, 0); + return true; + } + + if (msg->_endFrame == _frames[38]._endFrame || msg->_endFrame == _frames[23]._endFrame) { + playSound("c#3.wav", _volume); + } else if (msg->_endFrame == _frames[36]._endFrame) { + playSound("c#6.wav", _volume); + } + else if (msg->_endFrame == _frames[35]._endFrame) { + playSound("c#8.wav", _volume); + } + else if (msg->_endFrame == _frames[33]._endFrame) { + playSound("c#4.wav", _volume); + } else if (msg->_endFrame == _frames[32]._endFrame) { + startTalking(this, 145); + playSound("c#9.wav", _volume); + } else if (msg->_endFrame == _frames[47]._endFrame) { + playSound("c#9.wav", _volume); + _field12C = _field15C = 1; + } else if (msg->_endFrame == _frames[30]._endFrame) { + playSound("c#4.wav", 60); + } else if (msg->_endFrame == _frames[29]._endFrame) { + if (!_fieldC4) { + performAction(true, nullptr); + setVisible(false); + CActMsg actMsg("ResetCount"); + actMsg.execute("BarBell"); + } + } else if (msg->_endFrame == _frames[27]._endFrame) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 1; + statusMsg.execute("PickUpVisCentre"); + _field128 = 1; + _field134 = 0; + startTalking(this, 250586); + } + return true; } +bool CBarbot::TrueTalkSelfQueueAnimSetMsg(CTrueTalkSelfQueueAnimSetMsg *msg) { + return true; +} + +bool CBarbot::TrueTalkQueueUpAnimSetMsg(CTrueTalkQueueUpAnimSetMsg *msg) { + return true; +} + +bool CBarbot::TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg) { + switch (msg->_stateNum) { + case 2: + if (!_field130) { + if (_field15C) { + msg->_stateVal = _field134 | 1; + return true; + } + } + + msg->_stateVal = _field134; + break; + + case 3: + msg->_stateVal = 0; + if (_field114) + msg->_stateVal = 1; + if (_field11C) + msg->_stateVal |= 4; + if (_field118) + msg->_stateVal |= 8; + if (_field12C) + msg->_stateVal |= 2; + break; + + case 9: + msg->_stateVal = _field15C ? 1 : 0; + break; + + default: + break; + } + + return true; +} + +bool CBarbot::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) { + switch (msg->_action) { + case 6: + if (_field134) { + playRange(_frames[27], MOVIE_NOTIFY_OBJECT); + _frameNum = _frames[27]._endFrame; + } else if (!_field130 && _field15C) { + playRange(_frames[45], MOVIE_NOTIFY_OBJECT); + playRange(_frames[44], MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _frameNum = _frames[44]._endFrame; + } + break; + + case 7: { + CActMsg actMsg("Vodka"); + actMsg.execute(this); + break; + } + + case 30: + _field11C = 1; + break; + + default: + break; + } + + return true; +} + +bool CBarbot::FrameMsg(CFrameMsg *msg) { + if (!_fieldC4 || _frameNum != -1 || _field148 != -1 + || (msg->_ticks - _field14C) <= 5000 + || (msg->_ticks - _field150) <= 1000) + return true; + + if (!_field15C) { + if (++_field154 > 2) { + playRange(_frames[0]); + playRange(_frames[1], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[1]._endFrame; + _field154 = 0; + + return true; + } + + switch (g_vm->getRandomNumber(5)) { + case 0: + playRange(_frames[4], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[4]._endFrame; + break; + + case 1: + playRange(_frames[10], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[10]._endFrame; + break; + + case 2: + playRange(_frames[7], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[7]._endFrame; + break; + + case 3: + playRange(_frames[0]); + playRange(_frames[1], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[1]._endFrame; + break; + + case 4: + playRange(_frames[3], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[3]._endFrame; + break; + + case 5: + if (!_field160 && !_field128) { + playRange(_frames[15], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[15]._endFrame; + } + break; + + default: + break; + } + } else { + static const int CASES[23] = { + 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7 + }; + switch (CASES[g_vm->getRandomNumber(22)]) { + case 0: + playRange(_frames[13], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[13]._endFrame; + break; + + case 1: + playRange(_frames[4], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[4]._endFrame; + break; + + case 2: + playRange(_frames[8], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[8]._endFrame; + break; + + case 3: + playRange(_frames[7], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[7]._endFrame; + break; + + case 4: + playRange(_frames[10], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[10]._endFrame; + break; + + case 5: + playRange(_frames[2], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[2]._endFrame; + break; + + case 6: + playRange(_frames[6], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[6]._endFrame; + break; + + default: + playRange(_frames[3], MOVIE_NOTIFY_OBJECT); + _field148 = _frames[3]._endFrame; + break; + } + } + + return true; +} + +bool CBarbot::LoadSuccessMsg(CLoadSuccessMsg *msg) { + _field14C = _field150 = getTicksCount(); + _frameNum = -1; + _field148 = -1; + + return true; +} + +bool CBarbot::MovieFrameMsg(CMovieFrameMsg *msg) { + if (msg->_frameNumber == _frames[29]._startFrame) { + playSound("c#2.wav", _volume); + + } else if (msg->_frameNumber == _frames[55]._startFrame + || msg->_frameNumber == _frames[32]._startFrame) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 0; + statusMsg.execute("PickUpGlass"); + + if (_field158 == 0) { + startTalking(this, 250574); + } else if (_field158 > 0 && _field158 <= 3) { + startTalking(this, 250580); + petSetArea(PET_CONVERSATION); + } + + _field158 = -1; + + } else if (msg->_frameNumber == _frames[36]._startFrame) { + CVisibleMsg visibleMsg(false); + visibleMsg.execute("LemonOnBar"); + + } else if (msg->_frameNumber == _frames[35]._startFrame) { + CVisibleMsg visibleMsg(false); + visibleMsg.execute("TVOnBar"); + } + + return true; +} + +bool CBarbot::TimerMsg(CTimerMsg *msg) { + if (!_fieldC4 && compareRoomNameTo("Bar")) { + CParrotSpeakMsg speakMsg("Barbot", "AskForDrink"); + speakMsg.execute("PerchedParrot"); + addTimer(10000 + getRandomNumber(20000)); + } + + return true; +} + +void CBarbot::playRange(const FrameRange &range, uint flags) { + playMovie(range._startFrame, range._endFrame, flags); +} + } // End of namespace Titanic diff --git a/engines/titanic/npcs/barbot.h b/engines/titanic/npcs/barbot.h index 7557fdd2c6..2bd4cb2f1e 100644 --- a/engines/titanic/npcs/barbot.h +++ b/engines/titanic/npcs/barbot.h @@ -29,10 +29,36 @@ namespace Titanic { class CBarbot : public CTrueTalkNPC { + struct FrameRange { + int _startFrame; + int _endFrame; + FrameRange() : _startFrame(0), _endFrame(0) {} + }; + class FrameRanges : public Common::Array<FrameRange> { + public: + FrameRanges(); + }; + + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool TrueTalkSelfQueueAnimSetMsg(CTrueTalkSelfQueueAnimSetMsg *msg); + bool TrueTalkQueueUpAnimSetMsg(CTrueTalkQueueUpAnimSetMsg *msg); + bool TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg); + bool TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg); + bool FrameMsg(CFrameMsg *msg); + bool LoadSuccessMsg(CLoadSuccessMsg *msg); + bool MovieFrameMsg(CMovieFrameMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); + bool TimerMsg(CTimerMsg *msg); private: static int _v0; private: + FrameRanges _frames; int _field108; int _field10C; int _field110; @@ -47,8 +73,8 @@ private: int _field134; int _field138; int _field13C; - int _field140; - int _field144; + int _volume; + int _frameNum; int _field148; int _field14C; int _field150; @@ -56,124 +82,11 @@ private: int _field158; int _field15C; int _field160; - int _field164; - int _field168; - int _field16C; - int _field170; - int _field174; - int _field178; - int _field17C; - int _field180; - int _field184; - int _field188; - int _field18C; - int _field190; - int _field194; - int _field198; - int _field19C; - int _field1A0; - int _field1A4; - int _field1A8; - int _field1AC; - int _field1B0; - int _field1B4; - int _field1B8; - int _field1BC; - int _field1C0; - int _field1C4; - int _field1C8; - int _field1CC; - int _field1D0; - int _field1D4; - int _field1D8; - int _field1E0; - int _field1E4; - int _field1E8; - int _field1EC; - int _field1F0; - int _field1F4; - int _field1F8; - int _field1FC; - int _field200; - int _field204; - int _field208; - int _field20C; - int _field210; - int _field214; - int _field218; - int _field21C; - int _field220; - int _field224; - int _field228; - int _field22C; - int _field230; - int _field234; - int _field238; - int _field23C; - int _field240; - int _field244; - int _field248; - int _field24C; - int _field250; - int _field254; - int _field258; - int _field25C; - int _field260; - int _field264; - int _field268; - int _field26C; - int _field270; - int _field274; - int _field278; - int _field27C; - int _field280; - int _field284; - int _field288; - int _field28C; - int _field290; - int _field294; - int _field298; - int _field29C; - int _field2A0; - int _field2A4; - int _field2A8; - int _field2AC; - int _field2B0; - int _field2B4; - int _field2B8; - int _field2BC; - int _field2C0; - int _field2C4; - int _field2C8; - int _field2CC; - int _field2D0; - int _field2D4; - int _field2D8; - int _field2E0; - int _field2E4; - int _field2E8; - int _field2EC; - int _field2F0; - int _field2F4; - int _field2F8; - int _field2FC; - int _field300; - int _field304; - int _field308; - int _field30C; - int _field310; - int _field314; - int _field318; - int _field31C; - int _field320; - int _field324; - int _field328; - int _field32C; - int _field330; - int _field334; - int _field338; - int _field33C; - int _field340; +private: + /** + * Plays a given range of movie frames + */ + void playRange(const FrameRange &range, uint flags = 0); public: CLASSDEF; CBarbot(); diff --git a/engines/titanic/npcs/bellbot.cpp b/engines/titanic/npcs/bellbot.cpp index 7ee0128a1e..0170491270 100644 --- a/engines/titanic/npcs/bellbot.cpp +++ b/engines/titanic/npcs/bellbot.cpp @@ -21,9 +21,28 @@ */ #include "titanic/npcs/bellbot.h" +#include "titanic/carry/carry.h" +#include "titanic/core/room_item.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CBellBot, CTrueTalkNPC) + ON_MESSAGE(OnSummonBotMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(Use) + ON_MESSAGE(DismissBotMsg) + ON_MESSAGE(TrueTalkTriggerActionMsg) + ON_MESSAGE(MovieFrameMsg) + ON_MESSAGE(PutBotBackInHisBoxMsg) + ON_MESSAGE(NPCPlayIdleAnimationMsg) + ON_MESSAGE(NPCPlayTalkingAnimationMsg) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(TrueTalkGetStateValueMsg) + ON_MESSAGE(TrueTalkNotifySpeechEndedMsg) +END_MESSAGE_MAP() + CBellBot::CBellBot() : CTrueTalkNPC(), _field108(0) { } @@ -41,4 +60,217 @@ void CBellBot::load(SimpleFile *file) { CTrueTalkNPC::load(file); } +bool CBellBot::OnSummonBotMsg(COnSummonBotMsg *msg) { + if (msg->_value == 1) { + _npcFlags |= NPCFLAG_40000; + } else { + static const char *const ROOM_WAVES[8][2] = { + { "EmbLobby", "z#193.wav" }, + { "PromenadeDeck", "z#191.wav" }, + { "Arboretum", "z#195.wav" }, + { "Frozen Arboretum", "z#195.wav" }, + { "Bar", "z#194.wav" }, + { "MusicRoom", "z#192.wav" }, + { "MusicRoomLobby", "z#192.wav" }, + { "1stClassRestaurant", "z#190.wav" } + }; + + int idx; + for (idx = 0; idx < 8; ++idx) { + if (compareRoomNameTo(ROOM_WAVES[idx][0])) { + playSound(ROOM_WAVES[idx][1]); + + } + } + if (idx == 8) + playSound("z#147.wav"); + + sleep(2000); + _npcFlags &= ~NPCFLAG_40000; + } + + playClip("Walk On", MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + movieEvent(); + _npcFlags |= NPCFLAG_10000; + + return true; +} + +bool CBellBot::LeaveViewMsg(CLeaveViewMsg *msg) { + if (_npcFlags & NPCFLAG_10000) { + performAction(1); + _npcFlags &= ~NPCFLAG_4; + CDismissBotMsg dismissMsg; + dismissMsg.execute(this); + } + + return true; +} + +bool CBellBot::MovieEndMsg(CMovieEndMsg *msg) { + if (!(_npcFlags & NPCFLAG_10000)) { + CTrueTalkNPC::MovieEndMsg(msg); + } else if (clipExistsByEnd("Walk On", msg->_endFrame)) { + setPosition(Point(80, 10)); + loadFrame(543); + _npcFlags |= NPCFLAG_4; + if (_npcFlags & NPCFLAG_40000) { + startTalking(this, 157); + _npcFlags &= ~NPCFLAG_40000; + } + + endTalking(this, true); + petSetArea(PET_CONVERSATION); + } else if (clipExistsByEnd("Walk Off", msg->_endFrame)) { + CPutBotBackInHisBoxMsg boxMsg; + boxMsg.execute(this); + + if (_npcFlags & NPCFLAG_20000) + startAnimTimer("SummonDoorbot", 1500); + } else { + CTrueTalkNPC::MovieEndMsg(msg); + } + + return true; +} + +bool CBellBot::Use(CUse *msg) { + dynamic_cast<CCarry *>(msg->_item)->_string1 = "Bellbot"; + return true; +} + +bool CBellBot::DismissBotMsg(CDismissBotMsg *msg) { + if (_npcFlags & NPCFLAG_10000) { + playClip("Walk Off", MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + if (_npcFlags & NPCFLAG_4) { + _npcFlags &= ~NPCFLAG_4; + performAction(true); + } else { + performAction(false); + } + + CActMsg actMsg("BellbotDismissed"); + actMsg.execute("BotIdleSummons"); + } + + return true; +} + +bool CBellBot::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) { + switch (msg->_action) { + case 1: + case 28: { + _npcFlags &= ~NPCFLAG_2; + CDismissBotMsg dismissMsg; + dismissMsg.execute(this); + break; + } + + case 5: + _npcFlags &= ~NPCFLAG_20000; + playClip("Walk Off", MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + movieEvent(); + break; + + case 17: { + CActMsg actMsg("ThrowTVDownWell"); + actMsg.execute("ThrowTVDownWellControl"); + break; + } + + case 29: { + CActMsg actMsg("BellbotGetLight"); + actMsg.execute("BellbotGetLightCutScene"); + startTalking(this, 158); + break; + } + + default: + break; + } + + return true; +} + +bool CBellBot::MovieFrameMsg(CMovieFrameMsg *msg) { + if (clipExistsByStart("Walk Off", msg->_frameNumber) + || clipExistsByStart("Walk On", msg->_frameNumber)) { + setPosition(Point(20, 10)); + } + + return true; +} + +bool CBellBot::PutBotBackInHisBoxMsg(CPutBotBackInHisBoxMsg *msg) { + petMoveToHiddenRoom(); + _npcFlags &= ~NPCFLAG_4; + return true; +} + +bool CBellBot::NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg) { + static const char *const NAMES[] = { + "Sway Side To Side", "Hit Head", "Hands On Hips", "Sway", + "Hand Wave", "Slow Sway", "Lean Backwards", + "Sway Side To Side 2", "Bob Up And Down", nullptr + }; + + msg->_names = NAMES; + return true; +} + +bool CBellBot::NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg) { + static const char *const NAMES[] = { + "Hand On Hip Talking", "Hand On Hip Talking", "Hand On Hip Talking", + "Sway Side To Side", "Lean Forward", "Hit Head", "Confidential Talking", + "Hands On Hips", "Hands On Hips", "Hands On Hips", "Sway", "Laugh", + "Hand Wave", "Slow Sway", "Lean Backwards", "Sway Side To Side 2", + "Bob Up And Down", "Elbow In Hand", "Elbow In Hand", "Elbow In Hand", + nullptr + }; + + if (msg->_value2 == 2) + playClip("Mother Frame", 0); + else + msg->_names = NAMES; + + return true; +} + +bool CBellBot::TimerMsg(CTimerMsg *msg) { + if (msg->_action == "SummonDoorbot") { + CTrueTalkNPC::TimerMsg(msg); + } else { + CRoomItem *room = getRoom(); + if (room) { + CSummonBotMsg botMsg; + botMsg._npcName = "Doorbot"; + botMsg._value = 2; + botMsg.execute(room); + } + + _npcFlags &= ~NPCFLAG_20000; + } + + return true; +} + +bool CBellBot::TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg) { + CPetControl *pet = getPetControl(); + bool flag = pet ? pet->isRoom59706() : false; + + if (msg->_stateNum == 7) + msg->_stateVal = flag ? 1 : 0; + + return true; +} + +bool CBellBot::TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg) { + CTrueTalkNPC::TrueTalkNotifySpeechEndedMsg(msg); + + if (msg->_dialogueId == 20991) + petDismissBot("DoorBot"); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/npcs/bellbot.h b/engines/titanic/npcs/bellbot.h index 93c4a2857d..c246901cfe 100644 --- a/engines/titanic/npcs/bellbot.h +++ b/engines/titanic/npcs/bellbot.h @@ -28,6 +28,20 @@ namespace Titanic { class CBellBot : public CTrueTalkNPC { + DECLARE_MESSAGE_MAP; + bool OnSummonBotMsg(COnSummonBotMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool Use(CUse *msg); + bool DismissBotMsg(CDismissBotMsg *msg); + bool TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg); + bool MovieFrameMsg(CMovieFrameMsg *msg); + bool PutBotBackInHisBoxMsg(CPutBotBackInHisBoxMsg *msg); + bool NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg); + bool NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg); + bool TimerMsg(CTimerMsg *msg); + bool TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg); + bool TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg); private: int _field108; public: diff --git a/engines/titanic/npcs/bilge_succubus.cpp b/engines/titanic/npcs/bilge_succubus.cpp new file mode 100644 index 0000000000..16064bf212 --- /dev/null +++ b/engines/titanic/npcs/bilge_succubus.cpp @@ -0,0 +1,467 @@ +/* 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 "titanic/npcs/bilge_succubus.h" +#include "titanic/carry/chicken.h" +#include "titanic/core/view_item.h" +#include "titanic/pet_control/pet_control.h" + +namespace Titanic { + +BEGIN_MESSAGE_MAP(CBilgeSuccUBus, CSuccUBus) + ON_MESSAGE(FrameMsg) + ON_MESSAGE(PETReceiveMsg) + ON_MESSAGE(PETDeliverMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(SubAcceptCCarryMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(TrueTalkGetStateValueMsg) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) +END_MESSAGE_MAP() + +CBilgeSuccUBus::CBilgeSuccUBus() : CSuccUBus(), + _bilgeStartFrame1(-1), _bilgeEndFrame1(-1), + _bilgeStartFrame2(-1), _bilgeEndFrame2(-1) { +} + +void CBilgeSuccUBus::save(SimpleFile *file, int indent) { + file->writeNumberLine(1, indent); + file->writeNumberLine(_bilgeStartFrame1, indent); + file->writeNumberLine(_bilgeEndFrame1, indent); + file->writeNumberLine(_bilgeStartFrame2, indent); + file->writeNumberLine(_bilgeEndFrame2, indent); + + CSuccUBus::save(file, indent); +} + +void CBilgeSuccUBus::load(SimpleFile *file) { + file->readNumber(); + _bilgeStartFrame1 = file->readNumber(); + _bilgeEndFrame1 = file->readNumber(); + _bilgeStartFrame2 = file->readNumber(); + _bilgeEndFrame2 = file->readNumber(); + + CSuccUBus::load(file); +} + +bool CBilgeSuccUBus::FrameMsg(CFrameMsg *msg) { + return true; +} + +bool CBilgeSuccUBus::PETReceiveMsg(CPETReceiveMsg *msg) { + CPetControl *pet = getPetControl(); + + if (_v2) { + if (_startFrame4 >= 0) + playMovie(_startFrame4, _endFrame4, MOVIE_GAMESTATE); + if (_startFrame5 >= 0) + playMovie(_startFrame5, _endFrame5, MOVIE_GAMESTATE); + + playSound("z#28.wav", 70); + } else if (!_enabled) { + petDisplayMessage(2, "The Succ-U-Bus is in Standby, or \"Off\" mode at present."); + return false; + } else if (!pet) { + return false; + } else { + uint roomFlags = pet->getRoomFlags(); + CGameObject *mailObject = findMailByFlags( + _v3 && compareRoomNameTo("Titania") ? 3 : _field140, + roomFlags); + + if (mailObject) { + _mailP = mailObject; + if (_startFrame4 >= 0) + playMovie(_startFrame4, _endFrame4, MOVIE_GAMESTATE); + } else { + petDisplayMessage(2, "There is currently nothing to deliver."); + } + } + + return true; +} + +bool CBilgeSuccUBus::PETDeliverMsg(CPETDeliverMsg *msg) { + CPetControl *pet = getPetControl(); + if (!_enabled || !pet) + return true; + + uint petRoomFlags = pet->getRoomFlags(); + CGameObject *mailObject = findMail(petRoomFlags); + + if (!mailObject) { + petDisplayMessage(2, "There is currently nothing in the tray to send."); + return true; + } + + _field19C = 0; + _mailP = mailObject; + + uint roomFlags = _roomFlags; + if (!pet->testRooms5(roomFlags) || + getPassengerClass() > pet->getMailDest(roomFlags)) { + roomFlags = pet->getSpecialRoomFlags("BilgeRoom"); + _field19C = 1; + } + + _isChicken = mailObject->getName() == "Chicken"; + _isFeathers = mailObject->getName() == "Feathers"; + _field158 = 0; + + if (_v2) { + if (_isFeathers) { + startTalking(this, 230022); + _field158 = 1; + + if (_startFrame3 >= 0) + playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT); + + if (_bilgeStartFrame1 >= 0) { + playMovie(_startFrame12, _endFrame12, MOVIE_GAMESTATE); + playMovie(_bilgeStartFrame2, _bilgeEndFrame2, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playMovie(_bilgeStartFrame1, _bilgeEndFrame1, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + inc54(); + } + } else { + startTalking(this, 230012); + _field158 = 2; + if (_startFrame3 >= 0) + playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + if (_startFrame4 >= 0) + playMovie(_startFrame4, _endFrame4, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + if (_startFrame5 >= 0) + playMovie(_startFrame5, _endFrame5, MOVIE_GAMESTATE); + } + } else { + if (_isFeathers) { + startTalking(this, 230022); + _field158 = 3; + + if (_startFrame3 >= 0) + playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + if (_startFrame4 >= 0) + playMovie(_startFrame4, _endFrame4, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + if (_startFrame5 >= 0) + playMovie(_startFrame5, _endFrame5, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } else { + removeMail(petRoomFlags, roomFlags); + startTalking(this, 230012); + if (_startFrame3 >= 0) { + _field158 = 4; + playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + } + } + + return true; +} + +bool CBilgeSuccUBus::MovieEndMsg(CMovieEndMsg *msg) { + CPetControl *pet = getPetControl(); + + if (msg->_endFrame == _endFrame12) { + if (_startFrame10 >= 0) + playSound("z#27.wav"); + } else if (msg->_endFrame == _endFrame10) { + if (_startFrame11 >= 0) + playSound("z#30.wav"); + } else { + if (_endFrame9 == _endFrame10 && pet) { + if (_v2) { + startTalking(this, getRandomNumber(1) ? 230062 : 230063); + } else if (!findMail(pet->getRoomFlags())) { + switch (getRandomNumber(4)) { + case 0: + startTalking(this, 230001); + break; + case 1: + startTalking(this, 230002); + break; + case 2: + startTalking(this, 230003); + break; + default: + break; + } + } + } + + if (msg->_endFrame == _endFrame3) { + switch (_field158) { + case 1: + stopSound(_soundHandle); + _soundHandle = playSound("z#3.wav"); + break; + case 2: + stopSound(_soundHandle); + _soundHandle = playSound("z#12.wav"); + break; + case 3: + if (_isChicken) { + startTalking(this, 230018); + _isChicken = false; + } else { + startTalking(this, 230013); + } + break; + case 4: + startTalking(this, 230017); + break; + default: + break; + } + + CSUBTransition transMsg; + transMsg.execute(this); + + } else if (msg->_endFrame == _bilgeEndFrame2) { + playSound("z#25.wav", 70); + playSound("z#24.wav", 70); + + } else if (msg->_endFrame == _endFrame4) { + if (_mailP) { + _mailP->petAddToInventory(); + CVisibleMsg visibleMsg(true); + visibleMsg.execute(_mailP); + + _mailP = nullptr; + petSetArea(PET_INVENTORY); + + CSUBTransition transMsg; + transMsg.execute(this); + } + + } else if (msg->_endFrame == _bilgeEndFrame1) { + changeView("BilgeRoomWith.Node 1.N", ""); + _v2 = 0; + resetMail(); + + if (_mailP) { + _mailP->petAddToInventory(); + CVisibleMsg visibleMsg(true); + visibleMsg.execute(_mailP); + + _mailP = nullptr; + petSetArea(PET_INVENTORY); + } + + startTalking(this, 150); + CBodyInBilgeRoomMsg bodyMsg; + bodyMsg.execute("Service Elevator Entity"); + dec54(); + _field158 = 0; + + } else { + _field158 = 0; + } + } + + return true; +} + +bool CBilgeSuccUBus::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_enabled) { + switch (getRandomNumber(4)) { + case 0: + case 4: { + _enabled = false; + CTurnOff offMsg; + offMsg.execute(this); + break; + } + + case 1: + startTalking(this, 230055); + break; + + case 2: + startTalking(this, 230067); + break; + + case 3: + startTalking(this, 230045); + break; + + default: + break; + } + } else { + CTurnOn onMsg; + onMsg.execute(this); + _enabled = true; + } + + return true; +} + +bool CBilgeSuccUBus::SubAcceptCCarryMsg(CSubAcceptCCarryMsg *msg) { + CPetControl *pet = getPetControl(); + if (!msg->_item) + return false; + + CCarry *item = dynamic_cast<CCarry *>(msg->_item); + if (!_enabled || !pet || !item) { + item->petAddToInventory(); + return true; + } + + uint petRoomFlags = pet->getRoomFlags(); + if (mailExists(petRoomFlags)) { + petDisplayMessage(2, "The Succ-U-Bus is a Single Entity Delivery Device."); + item->petAddToInventory(); + return true; + } + + petContainerRemove(item); + pet->phonographAction(""); + playSound("z#23.wav"); + + CChicken *chicken = dynamic_cast<CChicken *>(item); + bool chickenFlag = chicken ? chicken->_string6 == "None" : false; + + if (chickenFlag) { + if (_startFrame2 >= 0) { + startTalking(this, 70219); + playMovie(_startFrame2, _endFrame2, 0); + } + + if (_startFrame3 >= 0) { + _field158 = 5; + playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT); + } + + CViewItem *view = parseView(item->_fullViewName); + if (view) { + item->setVisible(false); + setPosition(item->_origPos); + item->moveUnder(view); + + CSUBTransition transMsg; + transMsg.execute(this); + } else { + return false; + } + } else { + item->addMail(petRoomFlags); + if (_startFrame2 >= 0) + playMovie(_startFrame2, _endFrame2, 0); + + petSetArea(PET_REMOTE); + CSUBTransition transMsg; + transMsg.execute(this); + } + + return true; +} + +bool CBilgeSuccUBus::EnterViewMsg(CEnterViewMsg *msg) { + petSetRemoteTarget(); + _mailP = nullptr; + + if (_startFrame8 >= 0) + loadFrame(_startFrame8); + + return true; +} + +bool CBilgeSuccUBus::LeaveViewMsg(CLeaveViewMsg *msg) { + petDisplayMessage(2, ""); + petClear(); + + if (_soundHandle != -1) { + stopSound(_soundHandle); + _soundHandle = -1; + } + + if (_enabled) { + _enabled = false; + if (_startFrame10 >= 0) + playSound("z#27.wav"); + } + + performAction(true); + CSUBTransition transMsg; + transMsg.execute(this); + + return true; +} + +bool CBilgeSuccUBus::TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg) { + if (msg->_stateNum == 1) + msg->_stateVal = _enabled; + + return true; +} + +bool CBilgeSuccUBus::TurnOn(CTurnOn *msg) { + CPetControl *pet = getPetControl(); + + if (pet) { + if (_startFrame9 >= 0) { + playMovie(_startFrame9, _endFrame9, MOVIE_NOTIFY_OBJECT); + playSound("z#26.wav"); + } + + if (mailExists(pet->getRoomFlags()) && _startFrame2 >= 0) + playMovie(_startFrame2, _endFrame2, 0); + + _enabled = true; + CSUBTransition transMsg; + transMsg.execute(this); + + endTalking(this, true); + petSetArea(PET_REMOTE); + petHighlightGlyph(16); + } + + return true; +} + +bool CBilgeSuccUBus::TurnOff(CTurnOff *msg) { + CPetControl *pet = getPetControl(); + + if (pet && mailExists(pet->getRoomFlags()) && _startFrame12 >= 0) + playMovie(_startFrame12, _endFrame12, MOVIE_NOTIFY_OBJECT); + else if (_endFrame12 >= 0) + playMovie(_endFrame12, _endFrame12, MOVIE_NOTIFY_OBJECT); + + if (_soundHandle != -1) { + stopSound(_soundHandle); + _soundHandle = -1; + } + + if (_startFrame10 >= 0) + playMovie(_startFrame10, _endFrame10, MOVIE_NOTIFY_OBJECT); + + _enabled = false; + performAction(true); + + CSUBTransition transMsg; + transMsg.execute(this); + + return true; +} + +} // End of namespace Titanic diff --git a/engines/titanic/npcs/bilge_succubus.h b/engines/titanic/npcs/bilge_succubus.h new file mode 100644 index 0000000000..754949a306 --- /dev/null +++ b/engines/titanic/npcs/bilge_succubus.h @@ -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 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. + * + */ + +#ifndef TITANIC_BILGE_SUCCUBUS_H +#define TITANIC_BILGE_SUCCUBUS_H + +#include "titanic/npcs/succubus.h" + +namespace Titanic { + +class CBilgeSuccUBus : public CSuccUBus { + DECLARE_MESSAGE_MAP; + bool FrameMsg(CFrameMsg *msg); + bool PETReceiveMsg(CPETReceiveMsg *msg); + bool PETDeliverMsg(CPETDeliverMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool SubAcceptCCarryMsg(CSubAcceptCCarryMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg); + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); +public: + int _bilgeStartFrame1; + int _bilgeEndFrame1; + int _bilgeStartFrame2; + int _bilgeEndFrame2; +public: + CLASSDEF; + CBilgeSuccUBus(); + + /** + * Save the data for the class to file + */ + virtual void save(SimpleFile *file, int indent); + + /** + * Load the data for the class from file + */ + virtual void load(SimpleFile *file); +}; + +} // End of namespace Titanic + +#endif /* TITANIC_BILGE_SUCCUBUS_H */ diff --git a/engines/titanic/npcs/callbot.cpp b/engines/titanic/npcs/callbot.cpp index eb0d4b71d5..4af9876b35 100644 --- a/engines/titanic/npcs/callbot.cpp +++ b/engines/titanic/npcs/callbot.cpp @@ -21,26 +21,54 @@ */ #include "titanic/npcs/callbot.h" +#include "titanic/core/room_item.h" namespace Titanic { -CCallBot::CCallBot() : CGameObject(), _fieldC8(0) { +BEGIN_MESSAGE_MAP(CCallBot, CGameObject) + ON_MESSAGE(TurnOn) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + +CCallBot::CCallBot() : CGameObject(), _enabled(0) { } void CCallBot::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string1, indent); - file->writeNumberLine(_fieldC8, indent); + file->writeQuotedLine(_npcName, indent); + file->writeNumberLine(_enabled, indent); CGameObject::save(file, indent); } void CCallBot::load(SimpleFile *file) { file->readNumber(); - _string1 = file->readString(); - _fieldC8 = file->readNumber(); + _npcName = file->readString(); + _enabled = file->readNumber(); CGameObject::load(file); } +bool CCallBot::TurnOn(CTurnOn *msg) { + _enabled = true; + return true; +} + +bool CCallBot::EnterViewMsg(CEnterViewMsg *msg) { + if (_enabled) { + CRoomItem *room = getRoom(); + + if (room) { + CSummonBotQueryMsg queryMsg; + queryMsg._npcName = _npcName; + if (queryMsg.execute(room)) + petOnSummonBot(_npcName, 0); + } + + _enabled = false; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/npcs/callbot.h b/engines/titanic/npcs/callbot.h index 9b89d59d3f..ca0e0c55b2 100644 --- a/engines/titanic/npcs/callbot.h +++ b/engines/titanic/npcs/callbot.h @@ -28,9 +28,12 @@ namespace Titanic { class CCallBot : public CGameObject { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool EnterViewMsg(CEnterViewMsg *msg); protected: - CString _string1; - int _fieldC8; + CString _npcName; + bool _enabled; public: CLASSDEF; CCallBot(); diff --git a/engines/titanic/npcs/character.cpp b/engines/titanic/npcs/character.cpp index ed36db16d3..2713a6a1aa 100644 --- a/engines/titanic/npcs/character.cpp +++ b/engines/titanic/npcs/character.cpp @@ -30,13 +30,13 @@ BEGIN_MESSAGE_MAP(CCharacter, CGameObject) ON_MESSAGE(TurnOff) END_MESSAGE_MAP() -CCharacter::CCharacter() : CGameObject(), _fieldBC(0), _fieldC0(0), _fieldC4(1) { +CCharacter::CCharacter() : CGameObject(), _startFrame(0), _endFrame(0), _fieldC4(1) { } void CCharacter::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldBC, indent); - file->writeNumberLine(_fieldC0, indent); + file->writeNumberLine(_startFrame, indent); + file->writeNumberLine(_endFrame, indent); file->writeNumberLine(_fieldC4, indent); file->writeQuotedLine(_charName, indent); @@ -45,8 +45,8 @@ void CCharacter::save(SimpleFile *file, int indent) { void CCharacter::load(SimpleFile *file) { file->readNumber(); - _fieldBC = file->readNumber(); - _fieldC0 = file->readNumber(); + _startFrame = file->readNumber(); + _endFrame = file->readNumber(); _fieldC4 = file->readNumber(); _charName = file->readString(); diff --git a/engines/titanic/npcs/character.h b/engines/titanic/npcs/character.h index 4912740189..e27cf4ec35 100644 --- a/engines/titanic/npcs/character.h +++ b/engines/titanic/npcs/character.h @@ -33,8 +33,8 @@ class CCharacter : public CGameObject { bool TurnOn(CTurnOn *msg); bool TurnOff(CTurnOff *msg); protected: - int _fieldBC; - int _fieldC0; + int _startFrame; + int _endFrame; int _fieldC4; CString _charName; public: diff --git a/engines/titanic/npcs/doorbot.cpp b/engines/titanic/npcs/doorbot.cpp index 76db55f92c..41ef2b2366 100644 --- a/engines/titanic/npcs/doorbot.cpp +++ b/engines/titanic/npcs/doorbot.cpp @@ -21,15 +21,35 @@ */ #include "titanic/npcs/doorbot.h" +#include "titanic/core/room_item.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CDoorbot, CTrueTalkNPC) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(OnSummonBotMsg) + ON_MESSAGE(TrueTalkTriggerActionMsg) + ON_MESSAGE(DoorbotNeededInHomeMsg) + ON_MESSAGE(DoorbotNeededInElevatorMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(NPCPlayTalkingAnimationMsg) + ON_MESSAGE(NPCPlayIdleAnimationMsg) + ON_MESSAGE(PutBotBackInHisBoxMsg) + ON_MESSAGE(DismissBotMsg) + ON_MESSAGE(MovieFrameMsg) + ON_MESSAGE(TrueTalkNotifySpeechEndedMsg) + ON_MESSAGE(TextInputMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + int CDoorbot::_v1; int CDoorbot::_v2; CDoorbot::CDoorbot() : CTrueTalkNPC() { _field108 = 0; - _field10C = 0; + _timerId = 0; _field110 = 0; _field114 = 0; } @@ -40,7 +60,7 @@ void CDoorbot::save(SimpleFile *file, int indent) { file->writeNumberLine(_v2, indent); file->writeNumberLine(_field108, indent); - file->writeNumberLine(_field10C, indent); + file->writeNumberLine(_timerId, indent); file->writeNumberLine(_field110, indent); file->writeNumberLine(_field114, indent); @@ -53,11 +73,498 @@ void CDoorbot::load(SimpleFile *file) { _v2 = file->readNumber(); _field108 = file->readNumber(); - _field10C = file->readNumber(); + _timerId = file->readNumber(); _field110 = file->readNumber(); _field114 = file->readNumber(); CTrueTalkNPC::load(file); } +bool CDoorbot::MovieEndMsg(CMovieEndMsg *msg) { + if (_npcFlags & NPCFLAG_8000000) { + switch (_field108) { + case 3: + startTalking(this, 221482); + _field108 = 4; + break; + + case 6: + if (clipExistsByEnd("Cloak On", msg->_endFrame)) { + petShow(); + setState1C(true); + changeView("ServiceElevator.Node 1.S"); + changeView("ServiceElevator.Node 1.N"); + } + break; + + case 7: + startTalking(this, 221467); + _field108 = 8; + break; + + case 9: + startTalking(this, 221468); + break; + + case 11: + changeView("ServiceElevator.Node 1.S"); + changeView("MoonEmbLobby.Node 1.NE"); + break; + + default: + break; + } + + CTrueTalkNPC::MovieEndMsg(msg); + } else if (_npcFlags & NPCFLAG_100000) { + if (clipExistsByEnd("Cloak Off", msg->_endFrame)) { + _npcFlags = (_npcFlags & ~NPCFLAG_8) | NPCFLAG_4; + endTalking(this, false); + startTalking(this, 221474); + _npcFlags &= ~NPCFLAG_8000000; + _field108 = 0; + } else if (clipExistsByEnd("Cloak On", msg->_endFrame)) { + petShow(); + setState1C(true); + changeView("ServiceElevator.Node 1.S"); + } else { + CTrueTalkNPC::MovieEndMsg(msg); + } + } else if (_npcFlags & NPCFLAG_400000) { + if (clipExistsByEnd("Whizz On Left", msg->_endFrame) + || clipExistsByEnd("Whizz On Right", msg->_endFrame)) { + setPosition(Point((600 - _bounds.width()) / 2 + 18, 42)); + loadFrame(0); + endTalking(this, true); + _npcFlags |= NPCFLAG_4; + petSetArea(PET_CONVERSATION); + } else if (clipExistsByEnd("Whizz Off Left", msg->_endFrame) + || clipExistsByEnd("Whizz Off Right", msg->_endFrame)) { + CPutBotBackInHisBoxMsg boxMsg; + boxMsg.execute(this); + if (_npcFlags & NPCFLAG_4000000) + startAnimTimer("SummonBellbot", 1500); + } else { + CTrueTalkNPC::MovieEndMsg(msg); + } + } else { + CTrueTalkNPC::MovieEndMsg(msg); + } + + return true; +} + +bool CDoorbot::OnSummonBotMsg(COnSummonBotMsg *msg) { + const char *const ROOM_WAVES[8][2] = { + { "EmbLobby", "z#186.wav" }, + { "PromenadeDeck", "z#184.wav" }, + { "Arboretum", "z#188.wav" }, + { "Frozen Arboretum", "z#188.wav" }, + { "Bar", "z#187.wav" }, + { "MusicRoom", "z#185.wav" }, + { "MusicRoomLobby", "z#185.wav" }, + { "1stClassRestaurant", "z#183.wav" }, + }; + + if (msg->_value != -1) { + int idx; + for (idx = 0; idx < 8; ++idx) { + if (compareRoomNameTo(ROOM_WAVES[idx][0])) { + playSound(ROOM_WAVES[idx][1]); + + } + } + if (idx == 8) + playSound("z#146.wav"); + + sleep(2000); + } + + playClip(getRandomNumber(1) ? "Whizz On Left" : "Whizz On Right", + MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + movieEvent(); + _npcFlags |= NPCFLAG_400000; + + return true; +} + +bool CDoorbot::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) { + switch (msg->_action) { + case 3: + playClip("Cloak On", MOVIE_NOTIFY_OBJECT); + break; + + case 4: + _npcFlags = (_npcFlags & ~NPCFLAG_2) | NPCFLAG_4000000; + playClip("Whizz Off Left", MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + break; + + case 28: { + _npcFlags &= ~(NPCFLAG_2 | NPCFLAG_4); + CDismissBotMsg dismissMsg; + dismissMsg.execute(this); + break; + } + } + + return true; +} + +bool CDoorbot::DoorbotNeededInHomeMsg(CDoorbotNeededInHomeMsg *msg) { + moveToView(); + setPosition(Point(90, 42)); + _npcFlags = NPCFLAG_100000; + + stopMovie(); + playClip("Cloak Off", MOVIE_NOTIFY_OBJECT); + + _npcFlags |= NPCFLAG_8; + return true; +} + +bool CDoorbot::DoorbotNeededInElevatorMsg(CDoorbotNeededInElevatorMsg *msg) { + moveToView("ServiceElevator.Node 1.N"); + setPosition(Point(100, 42)); + + if (_npcFlags & NPCFLAG_8000000) { + _field108 = 7; + _npcFlags |= NPCFLAG_200000; + loadFrame(797); + } else { + _npcFlags = 0; + if (msg->_value) + endTalking(this, true); + } + + return true; +} + +bool CDoorbot::LeaveViewMsg(CLeaveViewMsg *msg) { + if (!(_npcFlags & NPCFLAG_8000000) && (_npcFlags & NPCFLAG_400000)) { + performAction(true); + _npcFlags &= ~NPCFLAG_4; + } + + return true; +} + +bool CDoorbot::TimerMsg(CTimerMsg *msg) { + if (msg->_action == "NPCIdleAnim") { + return CTrueTalkNPC::TimerMsg(msg); + } else if (_npcFlags & NPCFLAG_8000000) { + switch (msg->_actionVal) { + case 0: + startTalking(this, 221475); + break; + + case 1: + startTalking(this, 221476); + break; + + case 2: + startTalking(this, 221477); + break; + + case 3: + playClip("DoubleTake Start", 0); + playClip("DoubleTake End", 0); + playClip("DoubleTake Start", 0); + playClip("DoubleTake End", MOVIE_NOTIFY_OBJECT); + _field108 = 3; + break; + + case 4: + startTalking(this, 221483); + lockInputHandler(); + _field114 = true; + break; + + case 5: + lockInputHandler(); + mouseLockE4(); + _field114 = true; + startTalking(this, 221485); + break; + + case 6: + CMouseButtonDownMsg::generate(); + mouseSetPosition(Point(200, 430), 2500); + _timerId = addTimer(7, 2500, 0); + break; + + case 7: + CMouseButtonDownMsg::generate(); + startTalking(this, 221486); + mouseUnlockE4(); + unlockInputHandler(); + _field114 = false; + disableMouse(); + break; + + default: + break; + } + } else if (msg->_action == "SummonBellbot") { + CRoomItem *room = getRoom(); + if (room) { + CSummonBotMsg botMsg; + botMsg._npcName = "Bellbot"; + botMsg.execute(room); + } + + _npcFlags &= ~NPCFLAG_4000000; + } + + return true; +} + +bool CDoorbot::NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg) { + const char *const NAMES1[] = { + "Mutter Aside", "Rub Chin", "Drunken Eye Roll", "Drunken Head Move", + "Look down and mutter", "Look side to side", "Gesture forward and around", + "Arms behind back", "Look down", "Rolling around", "Hold face", + "Touch chin", "Cross hands in front", nullptr + }; + const char *const NAMES2[] = { + "SE Talking 1", "SE Talking 2", "SE Talking 3", "SE Talking 4" + }; + const char *const NAMES3[] = { + "SE Ask For Help", nullptr + }; + + if (msg->_value2 != 2) { + if (_npcFlags & NPCFLAG_200000) { + if (_field108 == 8 || _field110) { + msg->_names = NAMES2; + } else if (_field108 == 9) { + msg->_names = NAMES3; + _field108 = 10; + } + } else if (_npcFlags & (NPCFLAG_100000 | NPCFLAG_400000)) { + msg->_names = NAMES1; + } + } + + return true; +} + +bool CDoorbot::NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg) { + const char *const NAMES[] = { + "Hand swivel", "Prompt Push", "Eye Roll", "Say something", nullptr + }; + + if (!(_npcFlags & (NPCFLAG_100000 | NPCFLAG_200000)) + && (_npcFlags & NPCFLAG_400000)) + msg->_names = NAMES; + + return true; +} + +bool CDoorbot::PutBotBackInHisBoxMsg(CPutBotBackInHisBoxMsg *msg) { + petMoveToHiddenRoom(); + _npcFlags &= ~(NPCFLAG_4 | NPCFLAG_100000 | NPCFLAG_200000 | NPCFLAG_8000000); + if (msg->_value) + performAction(true); + + return true; +} + +bool CDoorbot::DismissBotMsg(CDismissBotMsg *msg) { + if (_npcFlags & NPCFLAG_400000) { + playClip(getRandomNumber(1) ? "Whizz Off Left" : "Whizz Off Right", + MOVIE_STOP_PREVIOUS | MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + movieEvent(); + + if (_npcFlags & NPCFLAG_4) { + _npcFlags &= ~NPCFLAG_4; + performAction(true); + } else { + performAction(false); + } + + CActMsg actMsg("DoorbotDismissed"); + actMsg.execute("BotIdleSummons"); + } + + return true; +} + +bool CDoorbot::MovieFrameMsg(CMovieFrameMsg *msg) { + if (clipExistsByStart("Whizz Off Left", msg->_frameNumber) + || clipExistsByStart("Whizz On Left", msg->_frameNumber)) { + setPosition(Point(20, 42)); + } else if (clipExistsByStart("Whizz Off Right", msg->_frameNumber) + || clipExistsByStart("Whizz On Right", msg->_frameNumber)) { + setPosition(Point(620 - _bounds.width(), 42)); + } + + return true; +} + +bool CDoorbot::TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg) { + if (_npcFlags & NPCFLAG_8000000) { + switch (msg->_dialogueId) { + case 10552: + playClip("SE Try Buttons", MOVIE_NOTIFY_OBJECT); + _field108 = 9; + break; + + case 10553: + enableMouse(); + break; + + case 10557: + playClip("SE Move To Right", MOVIE_NOTIFY_OBJECT); + _field108 = 11; + break; + + case 10559: + stopAnimTimer(_timerId); + _timerId = addTimer(0, 2500, 0); + break; + + case 10560: + petShow(); + petSetArea(PET_CONVERSATION); + stopAnimTimer(_timerId); + _timerId = addTimer(1, 1000, 0); + break; + + case 10561: + enableMouse(); + _field108 = 1; + stopAnimTimer(_timerId); + _timerId = addTimer(2, 10000, 0); + break; + + case 10562: + if (_field108 == 1) { + stopAnimTimer(_timerId); + _timerId = addTimer(2, getRandomNumber(5000), 0); + } + break; + + case 10563: + case 10564: + disableMouse(); + startTalking(this, 221480); + break; + + case 10565: + startTalking(this, 221481); + break; + + case 10566: + stopAnimTimer(_timerId); + _timerId = 0; + if (_field110 == 2) { + playClip("Cloak On", MOVIE_NOTIFY_OBJECT); + _field108 = 6; + } else { + _timerId = addTimer(3, 2000, 0); + } + break; + + case 10567: { + CActMsg actMsg("BecomeGettable"); + actMsg.execute("Photograph"); + enableMouse(); + stopAnimTimer(_timerId); + _timerId = addTimer(4, 5000, 0); + break; + } + + case 10568: + mouseLockE4(); + mouseSetPosition(Point(600, 250), 2500); + _timerId = addTimer(6, 2500, 0); + break; + + case 10569: + if (_field110 != 2) { + stopAnimTimer(_timerId); + _timerId = addTimer(5, 3000, 0); + } + break; + + case 10570: + mouseSetPosition(Point(200, 430), 2500); + _timerId = addTimer(7, 3000, 0); + break; + + case 10571: + playClip("Cloak On", MOVIE_NOTIFY_OBJECT); + _field108 = 6; + break; + + default: + break; + } + } + + return true; +} + +bool CDoorbot::TextInputMsg(CTextInputMsg *msg) { + if (!(_npcFlags & NPCFLAG_8000000)) + return CTrueTalkNPC::TextInputMsg(msg); + + if (_field108 == 1) { + stopAnimTimer(_timerId); + _field108 = 2; + _timerId = 0; + + if (msg->_input == "yes" || msg->_input == "yeah" + || msg->_input == "yea" || msg->_input == "yup" + || msg->_input == "yep" || msg->_input == "sure" + || msg->_input == "alright" || msg->_input == "all right" + || msg->_input == "ok") { + startTalking(this, 221479); + } else { + startTalking(this, 221478); + } + } + + return true; +} + +bool CDoorbot::EnterViewMsg(CEnterViewMsg *msg) { + if ((_npcFlags & NPCFLAG_8000000) && _field108 == 7) + playClip("SE Move And Turn", MOVIE_NOTIFY_OBJECT); + + return true; +} + +bool CDoorbot::ActMsg(CActMsg *msg) { + if (msg->_action == "DoorbotPlayerPressedTopButton") { + disableMouse(); + startTalking(this, 221471); + } else if (msg->_action == "DoorbotPlayerPressedMiddleButton") { + startTalking(this, 221470); + } + else if (msg->_action == "DoorbotPlayerPressedBottomButton") { + startTalking(this, 221469); + } else if (msg->_action == "DoorbotReachedEmbLobby") { + startTalking(this, 221472); + } else if (msg->_action == "PlayerPicksUpPhoto") { + _field110 = 1; + if (!_field114 && _field108 == 4) { + stopAnimTimer(_timerId); + _timerId = 0; + _field108 = 5; + startTalking(this, 221484); + } + } else if (msg->_action == "PlayerPutsPhotoInPet") { + _field110 = 2; + if (!_field114 && _field108 == 5) { + stopAnimTimer(_timerId); + _timerId = 0; + startTalking(this, 221486); + disableMouse(); + } + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/npcs/doorbot.h b/engines/titanic/npcs/doorbot.h index b62026c7d9..9095ebc7e7 100644 --- a/engines/titanic/npcs/doorbot.h +++ b/engines/titanic/npcs/doorbot.h @@ -28,12 +28,29 @@ namespace Titanic { class CDoorbot : public CTrueTalkNPC { + DECLARE_MESSAGE_MAP; + bool MovieEndMsg(CMovieEndMsg *msg); + bool OnSummonBotMsg(COnSummonBotMsg *msg); + bool TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg); + bool DoorbotNeededInHomeMsg(CDoorbotNeededInHomeMsg *msg); + bool DoorbotNeededInElevatorMsg(CDoorbotNeededInElevatorMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool TimerMsg(CTimerMsg *msg); + bool NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg); + bool NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg); + bool PutBotBackInHisBoxMsg(CPutBotBackInHisBoxMsg *msg); + bool DismissBotMsg(CDismissBotMsg *msg); + bool MovieFrameMsg(CMovieFrameMsg *msg); + bool TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg); + bool TextInputMsg(CTextInputMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool ActMsg(CActMsg *msg); private: static int _v1; static int _v2; private: int _field108; - int _field10C; + int _timerId; int _field110; int _field114; public: diff --git a/engines/titanic/npcs/liftbot.cpp b/engines/titanic/npcs/liftbot.cpp index 43daa017c1..272617ee62 100644 --- a/engines/titanic/npcs/liftbot.cpp +++ b/engines/titanic/npcs/liftbot.cpp @@ -21,35 +21,166 @@ */ #include "titanic/npcs/liftbot.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { -int CLiftBot::_v1; -int CLiftBot::_v2; +BEGIN_MESSAGE_MAP(CLiftBot, CTrueTalkNPC) + ON_MESSAGE(TextInputMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(TrueTalkTriggerActionMsg) + ON_MESSAGE(LeaveRoomMsg) + ON_MESSAGE(TurnOff) + ON_MESSAGE(TurnOn) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(TrueTalkGetStateValueMsg) + ON_MESSAGE(NPCPlayTalkingAnimationMsg) + ON_MESSAGE(ActMsg) +END_MESSAGE_MAP() + +bool CLiftBot::_flag; +bool CLiftBot::_enabled; CLiftBot::CLiftBot() : CTrueTalkNPC(), _field108(1) { } void CLiftBot::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_v1, indent); + file->writeNumberLine(_flag, indent); file->writeNumberLine(_field108, indent); - file->writeNumberLine(_v2, indent); + file->writeNumberLine(_enabled, indent); CTrueTalkNPC::save(file, indent); } void CLiftBot::load(SimpleFile *file) { file->readNumber(); - _v1 = file->readNumber(); + _flag = file->readNumber(); _field108 = file->readNumber(); - _v2 = file->readNumber(); + _enabled = file->readNumber(); CTrueTalkNPC::load(file); } +bool CLiftBot::TextInputMsg(CTextInputMsg *msg) { + CPetControl *pet = getPetControl(); + if (_enabled || pet->getRoomsElevatorNum() != 4) { + if (getName() != "LiftBot") { + CViewItem *view = findView(); + processInput(msg, view); + } + } + + return true; +} + +bool CLiftBot::EnterViewMsg(CEnterViewMsg *msg) { + CPetControl *pet = getPetControl(); + if (!_enabled && pet->getRoomsElevatorNum() == 4) { + loadFrame(700); + } else if (!_flag) { + if (getName() != "LiftBot") { + CViewItem *view = findView(); + endTalking(this, true, view); + petSetArea(PET_CONVERSATION); + _flag = 1; + } + } + + return true; +} + bool CLiftBot::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CLiftBot::handleEvent"); + _flag = 0; + changeView("Lift.Node 1.W", ""); + return true; +} + +bool CLiftBot::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) { + if (msg->_action == 2 && msg->_param1 != _field108) { + CStatusChangeMsg statusMsg(_field108, msg->_param1, false); + statusMsg.execute("Well"); + + _field108 = msg->_param1; + } + + return true; +} + +bool CLiftBot::LeaveRoomMsg(CLeaveRoomMsg *msg) { + if (getName() != "LiftBot") + performAction(false); + + return true; +} + +bool CLiftBot::TurnOff(CTurnOff *msg) { + _enabled = false; + return true; +} + +bool CLiftBot::TurnOn(CTurnOn *msg) { + _enabled = true; + if (!_flag) { + if (isEquals("LiftBotTalking")) { + endTalking(this, MOVIE_REPEAT, findView()); + petSetArea(PET_CONVERSATION); + _flag = true; + } + } + + return true; +} + +bool CLiftBot::LeaveViewMsg(CLeaveViewMsg *msg) { + return true; +} + +bool CLiftBot::TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg) { + if (msg->_stateNum == 4) { + CPetControl *pet = getPetControl(); + if (pet) + msg->_stateVal = pet->getAssignedFloorNum(); + } else if (msg->_stateNum == 5) { + msg->_stateVal = _field108; + } else if (msg->_stateNum == 6) { + CPetControl *pet = getPetControl(); + if (pet) + msg->_stateVal = pet->getRoomsElevatorNum(); + } else { + msg->_stateVal = _field108; + } + + return true; +} + +bool CLiftBot::NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg) { + const char *const NAMES[] = { + "Groaning", "Groaning 2", "Talking 1", "Talking 2", "Talking 3", + "Happy Talking", "Complaining", "Aggressive", "Explaining", + "Happy Talking 2", "Happy Talking 3", "Happy Talking 4" + "Confidential", nullptr + }; + + if (msg->_value2 == 2) + playClip("At Rest", 0); + else + msg->_names = NAMES; + return true; +} + +bool CLiftBot::ActMsg(CActMsg *msg) { + if (msg->_action == "ActivateLift") { + _enabled = true; + CViewItem *view = findView(); + endTalking(this, true, view); + startTalking(this, 155, view); + } else if (msg->_action == "LiftArrive") { + CViewItem *view = findView(); + startTalking(this, 156, view); + } + return true; } diff --git a/engines/titanic/npcs/liftbot.h b/engines/titanic/npcs/liftbot.h index 7550a8a6f0..ccac53d5c7 100644 --- a/engines/titanic/npcs/liftbot.h +++ b/engines/titanic/npcs/liftbot.h @@ -29,10 +29,21 @@ namespace Titanic { class CLiftBot : public CTrueTalkNPC { + DECLARE_MESSAGE_MAP; + bool TextInputMsg(CTextInputMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); + bool TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg); + bool LeaveRoomMsg(CLeaveRoomMsg *msg); + bool TurnOff(CTurnOff *msg); + bool TurnOn(CTurnOn *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg); + bool NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg); + bool ActMsg(CActMsg *msg); private: - static int _v1; - static int _v2; + static bool _flag; + static bool _enabled; private: int _field108; public: diff --git a/engines/titanic/npcs/maitre_d.cpp b/engines/titanic/npcs/maitre_d.cpp index 903f3a49c9..09444f5611 100644 --- a/engines/titanic/npcs/maitre_d.cpp +++ b/engines/titanic/npcs/maitre_d.cpp @@ -21,14 +21,30 @@ */ #include "titanic/npcs/maitre_d.h" +#include "titanic/core/room_item.h" +#include "titanic/sound/music_room.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CMaitreD, CTrueTalkNPC) + ON_MESSAGE(RestaurantMusicChanged) + ON_MESSAGE(TrueTalkTriggerActionMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(NPCPlayTalkingAnimationMsg) + ON_MESSAGE(TimerMsg) + ON_MESSAGE(TrueTalkNotifySpeechStartedMsg) + ON_MESSAGE(TrueTalkNotifySpeechEndedMsg) + ON_MESSAGE(LoadSuccessMsg) + ON_MESSAGE(TextInputMsg) + ON_MESSAGE(TriggerNPCEvent) +END_MESSAGE_MAP() + int CMaitreD::_v1; CMaitreD::CMaitreD() : CTrueTalkNPC(), _string2("z#40.wav"), _string3("z#40.wav"), _field108(0), _field118(1), - _field11C(0), _field12C(0), _field130(1), _field134(0), _field138(0) { + _field11C(0), _field12C(0), _field130(1), _field134(0), _timerId(0) { } void CMaitreD::save(SimpleFile *file, int indent) { @@ -43,7 +59,7 @@ void CMaitreD::save(SimpleFile *file, int indent) { file->writeNumberLine(_v1, indent); file->writeNumberLine(_field134, indent); - file->writeNumberLine(_field138, indent); + file->writeNumberLine(_timerId, indent); CTrueTalkNPC::save(file, indent); } @@ -60,9 +76,144 @@ void CMaitreD::load(SimpleFile *file) { _v1 = file->readNumber(); _field134 = file->readNumber(); - _field138 = file->readNumber(); + _timerId = file->readNumber(); CTrueTalkNPC::load(file); } +bool CMaitreD::RestaurantMusicChanged(CRestaurantMusicChanged *msg) { + if (msg->_value.empty()) { + _field118 = 0; + } else { + _string3 = msg->_value; + _field118 = _field11C = 1; + } + + return true; +} + +bool CMaitreD::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) { + if (msg->_action == 8) { + _field12C = 1; + stopAnimTimer(_timerId); + _timerId = startAnimTimer("MD Fight", 3500, 0); + } else if (msg->_action == 9) { + stopAnimTimer(_timerId); + _timerId = 0; + } else if (msg->_action == 10) { + _field12C = 0; + _v1 = 1; + stopAnimTimer(_timerId); + _timerId = 0; + + CMaitreDDefeatedMsg defeatedMsg; + defeatedMsg.execute(findRoom()); + } + + return true; +} + +bool CMaitreD::EnterViewMsg(CEnterViewMsg *msg) { + endTalking(this, true, findView()); + _field12C = _field134; + + if (_string3 == "STMusic" && (!_field11C || _string2 == _string3)) + return true; + + if (_string3.contains("nasty ambient")) + startTalking(this, 111, findView()); + else if (!CMusicRoom::_musicHandler->checkSound(1)) + startTalking(this, 114, findView()); + else if (!CMusicRoom::_musicHandler->checkSound(3)) + startTalking(this, 113, findView()); + else if (!CMusicRoom::_musicHandler->checkSound(2)) + startTalking(this, 115, findView()); + else { + startTalking(this, 110, findView()); + CMaitreDHappyMsg happyMsg; + happyMsg.execute("MaitreD Left Arm"); + happyMsg.execute("MaitreD Right Arm"); + } + + return true; +} + +bool CMaitreD::LeaveViewMsg(CLeaveViewMsg *msg) { + _field134 = _field12C; + performAction(true); + stopAnimTimer(_timerId); + _timerId = 0; + + _field12C = 0; + return true; +} + +bool CMaitreD::NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg) { + static const char *const NAMES[] = { + "Talking0", "Talking1", "Talking2", "Talking3", "Talking4", + "Talking5", "Talking6", "Talking7", nullptr + }; + + if (msg->_value2 != 2) { + msg->_names = NAMES; + + CAnimateMaitreDMsg animMsg; + if (_field12C) + animMsg._value = 0; + animMsg.execute(this); + } + + return true; +} + +bool CMaitreD::TimerMsg(CTimerMsg *msg) { + if (msg->_action == "MD Fight") { + if (_field12C && compareViewNameTo("1stClassRestaurant.MaitreD Node.N")) { + startTalking(this, 131, findView()); + } + } else { + CTrueTalkNPC::TimerMsg(msg); + } + + return true; +} + +bool CMaitreD::TrueTalkNotifySpeechStartedMsg(CTrueTalkNotifySpeechStartedMsg *msg) { + if (_field12C) { + stopAnimTimer(_timerId); + _timerId = 0; + } + + CTrueTalkNPC::TrueTalkNotifySpeechStartedMsg(msg); + return true; +} + +bool CMaitreD::TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg) { + if (_field12C) { + stopAnimTimer(_timerId); + _timerId = startAnimTimer("MD Fight", 3000 + getRandomNumber(3000)); + } + + CTrueTalkNPC::TrueTalkNotifySpeechEndedMsg(msg); + return true; +} + +bool CMaitreD::LoadSuccessMsg(CLoadSuccessMsg *msg) { + if (_field12C) { + _timerId = startAnimTimer("MD Fight", 3000 + getRandomNumber(3000)); + } + + return true; +} + +bool CMaitreD::TextInputMsg(CTextInputMsg *msg) { + CTrueTalkNPC::processInput(msg, findView()); + return true; +} + +bool CMaitreD::TriggerNPCEvent(CTriggerNPCEvent *msg) { + startTalking(this, msg->_value, findView()); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/npcs/maitre_d.h b/engines/titanic/npcs/maitre_d.h index af73f02a9a..878c32cc0b 100644 --- a/engines/titanic/npcs/maitre_d.h +++ b/engines/titanic/npcs/maitre_d.h @@ -28,6 +28,18 @@ namespace Titanic { class CMaitreD : public CTrueTalkNPC { + DECLARE_MESSAGE_MAP; + bool RestaurantMusicChanged(CRestaurantMusicChanged *msg); + bool TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg); + bool TimerMsg(CTimerMsg *msg); + bool TrueTalkNotifySpeechStartedMsg(CTrueTalkNotifySpeechStartedMsg *msg); + bool TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg); + bool LoadSuccessMsg(CLoadSuccessMsg *msg); + bool TextInputMsg(CTextInputMsg *msg); + bool TriggerNPCEvent(CTriggerNPCEvent *msg); private: static int _v1; private: @@ -39,7 +51,7 @@ private: int _field12C; int _field130; int _field134; - int _field138; + int _timerId; public: CLASSDEF; CMaitreD(); diff --git a/engines/titanic/npcs/mobile.h b/engines/titanic/npcs/mobile.h index 68e74a5afe..2ad939afa6 100644 --- a/engines/titanic/npcs/mobile.h +++ b/engines/titanic/npcs/mobile.h @@ -29,7 +29,7 @@ namespace Titanic { class CMobile : public CCharacter { DECLARE_MESSAGE_MAP; -private: +protected: Point _pos1; int _fieldDC; public: diff --git a/engines/titanic/npcs/parrot.cpp b/engines/titanic/npcs/parrot.cpp index 49e4f4066e..6e7aa4ec57 100644 --- a/engines/titanic/npcs/parrot.cpp +++ b/engines/titanic/npcs/parrot.cpp @@ -21,9 +21,30 @@ */ #include "titanic/npcs/parrot.h" +#include "titanic/core/project_item.h" +#include "titanic/carry/carry.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CParrot, CTrueTalkNPC) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(TrueTalkTriggerActionMsg) + ON_MESSAGE(MouseDragStartMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(ParrotSpeakMsg) + ON_MESSAGE(NPCPlayTalkingAnimationMsg) + ON_MESSAGE(NPCPlayIdleAnimationMsg) + ON_MESSAGE(FrameMsg) + ON_MESSAGE(MovieFrameMsg) + ON_MESSAGE(PutParrotBackMsg) + ON_MESSAGE(PreEnterViewMsg) + ON_MESSAGE(PanningAwayFromParrotMsg) + ON_MESSAGE(LeaveRoomMsg) +END_MESSAGE_MAP() + int CParrot::_v1; int CParrot::_v2; int CParrot::_v3; @@ -40,7 +61,7 @@ CParrot::CParrot() : CTrueTalkNPC() { _field128 = 58; _field12C = 0; _field130 = 0; - _field134 = 0; + _field134 = nullptr; _field138 = 851; _field13C = 851; _field140 = 265; @@ -140,4 +161,596 @@ void CParrot::load(SimpleFile *file) { CTrueTalkNPC::load(file); } +bool CParrot::ActMsg(CActMsg *msg) { + if (msg->_action == "PistaccioEaten") { + CActMsg actMsg("NutsEaten"); + actMsg.execute("Ear2"); + } else if (msg->_action == "Chicken") { + // Nothing to do + } else if (msg->_action == "CarryParrotLeftView") { + if (!_v2) { + _v1 = 0; + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 1; + statusMsg.execute("PerchCoreHolder"); + } + } else if (msg->_action == "StartChickenDrag") { + if (!_v4) { + stopMovie(); + startTalking(this, 280275, findView()); + _field12C = 0; + } + } else if (msg->_action == "EnteringFromTOW" && + (_v4 == 0 || _v4 == 2)) { + if (_v2) { + _v2 = 2; + } else { + setVisible(true); + CTreeItem *cageBar = getRoot()->findByName("CageBar"); + detach(); + attach(cageBar); + + _v4 = 0; + CActMsg actMsg1("OpenNow"); + actMsg1.execute("ParrotCage"); + CActMsg actMsg2("GainParrot"); + actMsg2.execute("ParrotLobbyController"); + } + } + + return true; +} + +bool CParrot::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!(_npcFlags & NPCFLAG_2000000) && _field100 <= 0) { + CTrueTalkTriggerActionMsg triggerMsg(280250, 280250, 1); + triggerMsg.execute(this); + } + + return true; +} + +bool CParrot::MovieEndMsg(CMovieEndMsg *msg) { + if ((_npcFlags & NPCFLAG_2000000) && clipExistsByEnd("Take Off", msg->_endFrame)) { + setVisible(false); + moveUnder(findRoom()); + stopMovie(); + + CActMsg actMsg1("LoseParrot"); + actMsg1.execute("ParrotLobbyController"); + + if (_field134) { + CActMsg actMsg2("PanAwayFromParrot"); + actMsg2.execute(_field134); + _field134 = nullptr; + } else { + CActMsg actMsg2("Shut"); + actMsg2.execute("ParrotCage"); + } + + _npcFlags &= ~NPCFLAG_2000000; + _v4 = 2; + } else if (_npcFlags & NPCFLAG_10000) { + if (_npcFlags & NPCFLAG_20000) { + _npcFlags = (_npcFlags & ~NPCFLAG_20000) | NPCFLAG_40000; + if (_npcFlags & NPCFLAG_100000) { + playClip("Walk Left Loop", MOVIE_NOTIFY_OBJECT); + movieEvent(236); + } else { + playClip("Walk Right Loop", MOVIE_NOTIFY_OBJECT); + } + } else if (_npcFlags & NPCFLAG_40000) { + int xp = _bounds.left + _bounds.width() / 2; + + if (_npcFlags & NPCFLAG_100000) { + if ((xp - _field128) > 32) { + setPosition(Point(_bounds.left - 40, _bounds.top)); + playClip("Walk Left Loop", MOVIE_NOTIFY_OBJECT); + movieEvent(236); + } else { + setPosition(Point(_bounds.left - 10, _bounds.top)); + playClip("Walk Left Outro", MOVIE_NOTIFY_OBJECT); + _npcFlags = (_npcFlags & ~NPCFLAG_40000) | NPCFLAG_80000; + } + } else { + if ((_field128 - xp) > 32) { + playClip("Walk Right Loop", MOVIE_NOTIFY_OBJECT); + movieEvent(244); + } else { + playClip("Walk Right Outro", MOVIE_NOTIFY_OBJECT); + _npcFlags = (_npcFlags & NPCFLAG_40000) | NPCFLAG_80000; + } + } + } else if (_npcFlags & NPCFLAG_80000) { + loadFrame(0); + if (_npcFlags & NPCFLAG_100000) + setPosition(Point(_bounds.left - 30, _bounds.top)); + else + setPosition(Point(_bounds.left + 14, _bounds.top)); + + _npcFlags &= ~(NPCFLAG_10000 | NPCFLAG_80000 | NPCFLAG_100000 | NPCFLAG_200000); + CTrueTalkNPC::MovieEndMsg(msg); + } else { + if (_npcFlags & NPCFLAG_1000000) { + Point pt = getMousePos(); + if (pt.x > 70 || pt.y < 90 || pt.y > 280) { + stopMovie(); + loadFrame(0); + _npcFlags &= ~NPCFLAG_1000000; + } + + if (clipExistsByEnd("Walk Left Loop", msg->_endFrame)) { + playClip("Lean Over To Chicken", MOVIE_NOTIFY_OBJECT); + setPosition(Point(_bounds.left - 55, _bounds.top)); + _field130 = (-100 - _bounds.left) / 5; + movieEvent(261); + movieEvent(262); + movieEvent(265); + movieEvent(268); + movieEvent(271); + return true; + + } else if (clipExistsByEnd("Lean Over To Chicken", msg->_endFrame)) { + playClip("Eat Chicken", 0); + playClip("Eat Chicken 2", MOVIE_NOTIFY_OBJECT); + _v1 = 1; + + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 0; + statusMsg.execute("PerchCoreHolder"); + + CTrueTalkTriggerActionMsg actionMsg; + actionMsg._param1 = 280266; + actionMsg._param2 = 1; + actionMsg.execute(this); + + CCarry *chicken = dynamic_cast<CCarry *>(findUnder(getRoot(), "Chicken")); + if (chicken) { + CActMsg actMsg("Eaten"); + actMsg.execute(chicken); + } + + _npcFlags &= ~NPCFLAG_1000000; + return true; + } + } + + if (clipExistsByEnd("Eat Chicken 2", msg->_endFrame)) { + CStatusChangeMsg statusMsg; + statusMsg._newStatus = 1; + statusMsg.execute("PerchCoreHolder"); + + if (_v2) { + loadMovie("z168.avi", false); + playClip("Take Off", MOVIE_NOTIFY_OBJECT); + setPosition(Point(20, 10)); + _npcFlags |= NPCFLAG_2000000; + } else { + _npcFlags &= ~(NPCFLAG_10000 | NPCFLAG_20000 | NPCFLAG_40000 | NPCFLAG_80000 | NPCFLAG_100000 | NPCFLAG_200000); + _npcFlags |= NPCFLAG_400000; + stopMovie(); + loadFrame(0); + setPosition(Point(-90, _bounds.top)); + } + } else { + CTrueTalkNPC::MovieEndMsg(msg); + } + } + } + + return true; +} + +bool CParrot::EnterViewMsg(CEnterViewMsg *msg) { + static const char *const NAMES[] = { + "Talking0", "Talking1", "Talking2", "Talking3", "Talking4", + "Talking5", "Talking6", "Talking7", nullptr + }; + + if (!_v4) { + setPosition(Point(_field124, _bounds.top)); + _field118 = 1; + _npcFlags &= ~(NPCFLAG_10000 | NPCFLAG_20000 | NPCFLAG_40000 | NPCFLAG_80000 | NPCFLAG_100000 | NPCFLAG_200000 | NPCFLAG_400000); + loadFrame(0); + endTalking(this, true, findView()); + + if (_field100 > 0) { + playRandomClip(NAMES, MOVIE_NOTIFY_OBJECT); + } else { + startTalking(this, 280258, findView()); + } + + petSetArea(PET_CONVERSATION); + _field12C = 0; + _npcFlags |= NPCFLAG_4; + } + + return true; +} + +bool CParrot::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) { + if (_v4) { + CViewItem *view = msg->_param2 ? findView() : nullptr; + startTalking(this, msg->_action, view); + } + + return true; +} + +bool CParrot::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (_field118 && !_v4 && checkPoint(msg->_mousePos, false, true)) { + setVisible(false); + CRoomItem *room = findRoom(); + + moveUnder(room); + startTalking(this, 280129); + performAction(true); + + CCarry *item = dynamic_cast<CCarry *>(getRoot()->findByName(_string2)); + if (item) { + item->_fieldE0 = 1; + CPassOnDragStartMsg passMsg; + passMsg._mousePos = msg->_mousePos; + passMsg.execute(item); + msg->_dragItem = item; + + CActMsg actMsg("LoseParrot"); + actMsg.execute("ParrotLobbyController"); + } + } + + return true; +} + +bool CParrot::LeaveViewMsg(CLeaveViewMsg *msg) { + performAction(true); + _npcFlags &= ~NPCFLAG_4; + + return true; +} + +bool CParrot::ParrotSpeakMsg(CParrotSpeakMsg *msg) { + const char *const ROOM_NAMES[] = { + "SGTState", "SGTLittleLift", "SecClassLittleLift", "SecClassState", + "Lift", "ServiceElevator", "Dome", "Home", "MoonEmbLobby", nullptr + }; + + if (!stateGet24() || _v4 == 3 || compareViewNameTo("Titania.Node 18.N")) + return true; + + // Check for rooms not to speak in + for (const char *const *s = &ROOM_NAMES[0]; *s; ++s) { + if (isEquals(*s)) + return true; + } + + // Don't have the parrot speak too often + if ((getTicksCount() - _field120) < 20000 || _field100) + return true; + + playSound("z#475.wav", 50); + + if (msg->_target == "Bomb") { + startTalking("PerchedParrot", 280236); + } else if (msg->_target == "Announcements") { + startTalking("PerchedParrot", 280263); + } else if (msg->_target == "Television") { + startTalking("PerchedParrot", 280264); + } else if (msg->_target == "Barbot") { + if (msg->_action == "AskForDrink") + startTalking("PerchedParrot", 280262); + } else if (msg->_target == "SuccUBus") { + if (msg->_action == "TurnOn") + startTalking("PerchedParrot", 80161); + else if (msg->_action == "EnterView") + startTalking("PerchedParrot", 80159); + } else if (msg->_target == "Cellpoints") { + if (getRandomNumber(2) == 0) { + switch (getRandomNumber(2)) { + case 0: + startTalking("PerchedParrot", 80193); + break; + case 1: + startTalking("PerchedParrot", 80197); + break; + case 2: + startTalking("PerchedParrot", 80198); + break; + default: + break; + } + } else if (msg->_action == "DoorBot") { + startTalking("PerchedParrot", 80195); + } else if (msg->_action == "DeskBot") { + startTalking("PerchedParrot", 80194); + } else if (msg->_action == "BarBot") { + startTalking("PerchedParrot", 80191); + } else if (msg->_action == "BellBot") { + startTalking("PerchedParrot", 80192); + } else if (msg->_action == "LiftBot") { + startTalking("PerchedParrot", 80196); + } + } + + _field120 = getTicksCount(); + return true; +} + +bool CParrot::NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg) { + const char *const NAMES[] = { + "Talking0", "Talking1", "Talking2", "Talking3", "Talking4", + "Talking5", "Talking6", "Talking7", nullptr + }; + + if (!(_npcFlags & (NPCFLAG_10000 | NPCFLAG_20000 | NPCFLAG_40000 | NPCFLAG_80000 | NPCFLAG_100000 | NPCFLAG_200000 | NPCFLAG_400000)) + && _visible && !_v4) { + if (!compareViewNameTo("ParrotLobby.Node 1.N")) + msg->_names = NAMES; + } + + return true; +} + +bool CParrot::NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg) { + const char *const NAMES[] = { + "Idle0", "Idle1", "Peck At Feet", "Peck At Feet Left" + "Peck At Feet Right", nullptr + }; + + if (!(_npcFlags & (NPCFLAG_10000 | NPCFLAG_20000 | NPCFLAG_40000 | NPCFLAG_80000 | NPCFLAG_100000 | NPCFLAG_200000 | NPCFLAG_400000)) + && _visible && !_v4 && !compareViewNameTo("ParrotLobby.Node 1.N")) { + CGameObject *dragItem = getDraggingObject(); + if (!dragItem || dragItem->getName() == "Chicken") { + if (!_v5 ||getRandomNumber(3) != 0) { + if (getRandomNumber(1)) { + startTalking(this, 280267, findView()); + } else { + msg->_names = NAMES; + } + } else { + int id = -1; + switch (stateGet38()) { + case 0: + id = 280107; + break; + case 1: + id = 280106; + break; + case 2: + id = 280115; + break; + case 3: + id = 280114; + break; + case 4: + id = 280113; + break; + case 5: + id = 280112; + break; + case 6: + id = 280111; + break; + case 7: + id = 280110; + break; + case 8: + id = 280109; + break; + case 9: + id = 280108; + break; + case 10: + id = 280105; + break; + case 11: + id = 280000; + break; + default: + break; + } + + if (id != -1) + startTalking(this, id, findView()); + + CActMsg actMsg("FlashCore"); + actMsg.execute("PerchCoreHolder"); + } + } + } + + return true; +} + +bool CParrot::FrameMsg(CFrameMsg *msg) { + if (compareViewNameTo("ParrotLobby.Node 1.N")) + return false; + if (_v4) + return true; + + Point pt = getMousePos(); + CGameObject *dragObject = getDraggingObject(); + int xp = _bounds.left + _bounds.width() / 2; + + if ((_npcFlags & NPCFLAG_400000) && !hasActiveMovie()) { + _field128 = xp - (_field124 + _bounds.width() / 2); + + if (xp < 64) { + if (_field134) { + CActMsg actMsg("PanAwayFromParrot"); + actMsg.execute(_field134); + } + + _npcFlags &= ~(NPCFLAG_10000 | NPCFLAG_20000 | NPCFLAG_40000 + | NPCFLAG_80000 | NPCFLAG_100000 | NPCFLAG_200000 | NPCFLAG_400000); + return true; + } + } + + bool chickenFlag = dragObject && dragObject->isEquals("Chicken"); + + if (_npcFlags & NPCFLAG_1000000) { + if (!chickenFlag || pt.x > 70 || pt.y < 90 || pt.y > 280) { + stopMovie(); + loadFrame(0); + setPosition(Point(-90, _bounds.top)); + } + } else { + if (!chickenFlag) + return false; + } + + _field128 = CLIP((int)pt.x, 230, 480); + if ((_npcFlags & NPCFLAG_10000) || hasActiveMovie()) + return true; + + if (_field128 > 64) { + _npcFlags |= NPCFLAG_10000 | NPCFLAG_20000; + + if (_field128 >= xp) { + setPosition(Point(_bounds.left + 30, _bounds.top)); + _npcFlags |= NPCFLAG_200000; + playClip("Walk Right Intro", MOVIE_NOTIFY_OBJECT); + } else { + _npcFlags |= NPCFLAG_100000; + playClip("Walk Left Intro", MOVIE_NOTIFY_OBJECT); + } + } else if (chickenFlag && pt.y >= 90 && pt.y <= 280 && !_field12C) { + CParrotTriesChickenMsg triesMsg; + triesMsg.execute(dragObject); + + CTrueTalkTriggerActionMsg triggerMsg; + int id; + switch (triesMsg._value2) { + case 1: + id = 280056 + (triesMsg._value1 ? 234 : 0); + break; + case 2: + id = 280055 + (triesMsg._value1 ? 234 : 0); + break; + case 3: + id = 280054 + (triesMsg._value1 ? 234 : 0); + break; + default: + id = 280053 + (triesMsg._value1 ? 234 : 0); + break; + } + + if (id < 280266) { + if (pt.x < 75) { + _npcFlags |= NPCFLAG_1000000; + playClip("Walk Left Intro", MOVIE_STOP_PREVIOUS); + playClip("Walk Left Loop", MOVIE_NOTIFY_OBJECT); + movieEvent(236); + chickenFlag = false; + } else if ((pt.x - xp) > 15) { + _npcFlags |= NPCFLAG_800000; + playClip("Peck At Feet Right", MOVIE_NOTIFY_OBJECT); + movieEvent(170); + } else if ((xp - pt.x) > 15) { + _npcFlags |= NPCFLAG_800000; + playClip("Peck At Feet Left", MOVIE_NOTIFY_OBJECT); + movieEvent(142); + } else { + _npcFlags |= NPCFLAG_800000; + playClip("Peck At Feet", MOVIE_NOTIFY_OBJECT); + movieEvent(157); + } + } + + if (chickenFlag) { + triggerMsg._param2 = 1; + triggerMsg.execute(this); + _field12C = 1; + } + } + + return true; +} + +bool CParrot::MovieFrameMsg(CMovieFrameMsg *msg) { + if (_npcFlags & NPCFLAG_800000) { + CCarry *chicken = dynamic_cast<CCarry *>(findUnder(getRoot(), "Chicken")); + if (chicken) { + CActMsg actMsg("Eaten"); + actMsg.execute(chicken); + } + + _npcFlags &= ~NPCFLAG_800000; + } + + switch (msg->_frameNumber) { + case 244: + setPosition(Point(_bounds.left, _bounds.top + 45)); + break; + case 261: + case 262: + case 265: + case 268: + case 271: + setPosition(Point(_bounds.left + _field130, _bounds.top)); + break; + default: + break; + } + + return true; +} + +bool CParrot::PutParrotBackMsg(CPutParrotBackMsg *msg) { + const char *const NAMES[] = { + "Talking0", "Talking1", "Talking2", "Talking3", "Talking4", + "Talking5", "Talking6", "Talking7", nullptr + }; + + int xp = CLIP(msg->_value, 230, 480); + setVisible(true); + moveToView(); + _v4 = 0; + + setPosition(Point(xp - _bounds.width() / 2, _bounds.top)); + playRandomClip(NAMES, MOVIE_NOTIFY_OBJECT); + + CActMsg actMsg("GainParrot"); + actMsg.execute("ParrotLobbyController"); + + return true; +} + +bool CParrot::PreEnterViewMsg(CPreEnterViewMsg *msg) { + if (!_v4) { + loadMovie("z167.avi", false); + loadFrame(0); + } + + return true; +} + +bool CParrot::PanningAwayFromParrotMsg(CPanningAwayFromParrotMsg *msg) { + if (_v4) { + CActMsg actMsg("PanAwayFromParrot"); + actMsg.execute(msg->_target); + _field134 = 0; + } else if (_v2) { + _field134 = msg->_target; + loadMovie("z168.avi", false); + stopMovie(); + playClip("Take Off", MOVIE_NOTIFY_OBJECT); + _npcFlags |= NPCFLAG_2000000; + } else { + _npcFlags |= NPCFLAG_400000; + _field134 = msg->_target; + stopMovie(); + } + + return true; +} + +bool CParrot::LeaveRoomMsg(CLeaveRoomMsg *msg) { + if (!_v4) + startTalking(this, 280259); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/npcs/parrot.h b/engines/titanic/npcs/parrot.h index a3c8540f0e..93e0643857 100644 --- a/engines/titanic/npcs/parrot.h +++ b/engines/titanic/npcs/parrot.h @@ -28,6 +28,23 @@ namespace Titanic { class CParrot : public CTrueTalkNPC { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool ParrotSpeakMsg(CParrotSpeakMsg *msg); + bool NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg); + bool NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg); + bool FrameMsg(CFrameMsg *msg); + bool MovieFrameMsg(CMovieFrameMsg *msg); + bool PutParrotBackMsg(CPutParrotBackMsg *msg); + bool PreEnterViewMsg(CPreEnterViewMsg *msg); + bool PanningAwayFromParrotMsg(CPanningAwayFromParrotMsg *msg); + bool LeaveRoomMsg(CLeaveRoomMsg *msg); public: static int _v1; static int _v2; @@ -44,7 +61,7 @@ private: int _field128; int _field12C; int _field130; - int _field134; + CTreeItem *_field134; int _field138; int _field13C; int _field140; diff --git a/engines/titanic/npcs/parrot_succubus.cpp b/engines/titanic/npcs/parrot_succubus.cpp new file mode 100644 index 0000000000..d285c219b5 --- /dev/null +++ b/engines/titanic/npcs/parrot_succubus.cpp @@ -0,0 +1,152 @@ +/* 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 "titanic/npcs/parrot_succubus.h" +#include "titanic/pet_control/pet_control.h" +#include "titanic/carry/hose.h" + +namespace Titanic { + +BEGIN_MESSAGE_MAP(CParrotSuccUBus, CSuccUBus) + ON_MESSAGE(HoseConnectedMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(LeaveNodeMsg) +END_MESSAGE_MAP() + +CParrotSuccUBus::CParrotSuccUBus() : CSuccUBus(), _field1DC(0), + _field1EC(0), _field1F0(376), _field1F4(393) { +} + +void CParrotSuccUBus::save(SimpleFile *file, int indent) { + file->writeNumberLine(1, indent); + file->writeNumberLine(_field1DC, indent); + file->writeQuotedLine(_string3, indent); + file->writeNumberLine(_field1EC, indent); + + CSuccUBus::save(file, indent); +} + +void CParrotSuccUBus::load(SimpleFile *file) { + file->readNumber(); + _field1DC = file->readNumber(); + _string3 = file->readString(); + _field1EC = file->readNumber(); + + CSuccUBus::load(file); +} + +bool CParrotSuccUBus::HoseConnectedMsg(CHoseConnectedMsg *msg) { + CPetControl *pet = getPetControl(); + if (msg->_value == _field1DC) + return true; + if (mailExists(pet->getRoomFlags())) + return false; + + _field1DC = msg->_value; + if (_field1DC) { + CGameObject *item = msg->_object; + _string3 = item->getName(); + CHoseConnectedMsg hoseMsg(1, this); + hoseMsg.execute(msg->_object); + item->petMoveToHiddenRoom(); + + CPumpingMsg pumpingMsg(1, this); + pumpingMsg.execute(this); + _field1DC = 1; + + if (_enabled) { + _enabled = false; + } else { + playMovie(_startFrame9, _endFrame9, 0); + playSound("z#26.wav"); + } + + playMovie(_field1C4, _field1C8, MOVIE_NOTIFY_OBJECT); + } else { + stopMovie(); + stopSound(_field1EC); + playMovie(_field1F0, _field1F4, MOVIE_NOTIFY_OBJECT); + + CPumpingMsg pumpingMsg(0, this); + pumpingMsg.execute(_string3); + + CGameObject *obj = getHiddenObject(_string3); + if (obj) { + obj->petAddToInventory(); + obj->setVisible(true); + } + + _enabled = true; + CTurnOff offMsg; + offMsg.execute(this); + } + + return true; +} + +bool CParrotSuccUBus::EnterViewMsg(CEnterViewMsg *msg) { + if (_field1DC) { + playMovie(_field1CC, _field1D0, MOVIE_REPEAT); + return true; + } else { + return CSuccUBus::EnterViewMsg(msg); + } +} + +bool CParrotSuccUBus::MovieEndMsg(CMovieEndMsg *msg) { + if (msg->_endFrame == _field1C8) { + playMovie(_field1CC, _field1D0, MOVIE_REPEAT); + _field1EC = playSound("z#472.wav"); + return true; + } else { + return CSuccUBus::MovieEndMsg(msg); + } +} + +bool CParrotSuccUBus::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_field1DC) { + CHoseConnectedMsg hoseMsg; + hoseMsg._value = 0; + hoseMsg.execute(this); + return true; + } else { + return CSuccUBus::MouseButtonDownMsg(msg); + } +} + +bool CParrotSuccUBus::LeaveNodeMsg(CLeaveNodeMsg *msg) { + if (_field1DC) { + getHiddenObject(_string3); + if (CHose::_statics->_actionTarget.empty()) { + playSound("z#51.wav"); + CHoseConnectedMsg hoseMsg; + hoseMsg._value = 0; + hoseMsg.execute(this); + } + } + + return true; +} + +} // End of namespace Titanic diff --git a/engines/titanic/game/parrot/parrot_succubus.h b/engines/titanic/npcs/parrot_succubus.h index 6f5d9e602a..74a4a032eb 100644 --- a/engines/titanic/game/parrot/parrot_succubus.h +++ b/engines/titanic/npcs/parrot_succubus.h @@ -28,6 +28,12 @@ namespace Titanic { class CParrotSuccUBus : public CSuccUBus { + DECLARE_MESSAGE_MAP; + bool HoseConnectedMsg(CHoseConnectedMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool LeaveNodeMsg(CLeaveNodeMsg *msg); public: int _field1DC; CString _string3; diff --git a/engines/titanic/npcs/robot_controller.cpp b/engines/titanic/npcs/robot_controller.cpp index 98866e4505..34c75e30eb 100644 --- a/engines/titanic/npcs/robot_controller.cpp +++ b/engines/titanic/npcs/robot_controller.cpp @@ -24,21 +24,37 @@ namespace Titanic { -CRobotController::CRobotController() : CGameObject(), _string1("BellBot") { +BEGIN_MESSAGE_MAP(CRobotController, CGameObject) + ON_MESSAGE(SummonBotMsg) + ON_MESSAGE(SummonBotQueryMsg) +END_MESSAGE_MAP() + +CRobotController::CRobotController() : CGameObject(), _robotName("BellBot") { } void CRobotController::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string1, indent); + file->writeQuotedLine(_robotName, indent); CGameObject::save(file, indent); } void CRobotController::load(SimpleFile *file) { file->readNumber(); - _string1 = file->readString(); + _robotName = file->readString(); CGameObject::load(file); } +bool CRobotController::SummonBotMsg(CSummonBotMsg *msg) { + if (!petDismissBot(msg->_npcName)) + petOnSummonBot(msg->_npcName, msg->_value); + + return true; +} + +bool CRobotController::SummonBotQueryMsg(CSummonBotQueryMsg *msg) { + return _robotName == msg->_npcName; +} + } // End of namespace Titanic diff --git a/engines/titanic/npcs/robot_controller.h b/engines/titanic/npcs/robot_controller.h index 6cbf57aef2..326c2280dd 100644 --- a/engines/titanic/npcs/robot_controller.h +++ b/engines/titanic/npcs/robot_controller.h @@ -28,8 +28,11 @@ namespace Titanic { class CRobotController : public CGameObject { + DECLARE_MESSAGE_MAP; + bool SummonBotMsg(CSummonBotMsg *msg); + bool SummonBotQueryMsg(CSummonBotQueryMsg *msg); protected: - CString _string1; + CString _robotName; public: CLASSDEF; CRobotController(); diff --git a/engines/titanic/npcs/starlings.cpp b/engines/titanic/npcs/starlings.cpp index 333f4c4b7a..7e5907f577 100644 --- a/engines/titanic/npcs/starlings.cpp +++ b/engines/titanic/npcs/starlings.cpp @@ -24,23 +24,40 @@ namespace Titanic { -int CStarlings::_v1; +BEGIN_MESSAGE_MAP(CStarlings, CCharacter) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(StatusChangeMsg) +END_MESSAGE_MAP() -CStarlings::CStarlings() : CCharacter() { +CStarlings::CStarlings() : CCharacter(), _enabled(false) { } void CStarlings::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_v1, indent); + file->writeNumberLine(_enabled, indent); CCharacter::save(file, indent); } void CStarlings::load(SimpleFile *file) { file->readNumber(); - _v1 = file->readNumber(); + _enabled = file->readNumber(); CCharacter::load(file); } +bool CStarlings::EnterViewMsg(CEnterViewMsg *msg) { + if (_enabled) + setVisible(false); + else + playMovie(MOVIE_REPEAT); + return true; +} + +bool CStarlings::StatusChangeMsg(CStatusChangeMsg *msg) { + _enabled = msg->_newStatus == 1; + setVisible(!_enabled); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/npcs/starlings.h b/engines/titanic/npcs/starlings.h index 4d96e5c77f..1998e6490d 100644 --- a/engines/titanic/npcs/starlings.h +++ b/engines/titanic/npcs/starlings.h @@ -28,8 +28,11 @@ namespace Titanic { class CStarlings : public CCharacter { + DECLARE_MESSAGE_MAP; + bool EnterViewMsg(CEnterViewMsg *msg); + bool StatusChangeMsg(CStatusChangeMsg *msg); private: - static int _v1; + bool _enabled; public: CLASSDEF; CStarlings(); diff --git a/engines/titanic/npcs/succubus.cpp b/engines/titanic/npcs/succubus.cpp index f66a59cb84..5588d862c3 100644 --- a/engines/titanic/npcs/succubus.cpp +++ b/engines/titanic/npcs/succubus.cpp @@ -21,61 +21,77 @@ */ #include "titanic/npcs/succubus.h" +#include "titanic/carry/carry.h" +#include "titanic/carry/chicken.h" +#include "titanic/core/view_item.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { -int CSuccUBus::_v0; +BEGIN_MESSAGE_MAP(CSuccUBus, CTrueTalkNPC) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(SubAcceptCCarryMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(PETDeliverMsg) + ON_MESSAGE(PETReceiveMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(TrueTalkGetStateValueMsg) + ON_MESSAGE(SignalObject) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(SUBTransition) + ON_MESSAGE(SetChevRoomBits) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MouseDragStartMsg) +END_MESSAGE_MAP() + +bool CSuccUBus::_enabled; int CSuccUBus::_v1; int CSuccUBus::_v2; int CSuccUBus::_v3; int CSuccUBus::_v4; CSuccUBus::CSuccUBus() : CTrueTalkNPC() { - _field108 = -1; - _field10C = -1; - _field110 = -1; - _field114 = -1; - _field118 = 0x44; - _field11C = 0xA8; - _field120 = 0xA8; - _field124 = 0xF8; - _field128 = 0; - _field12C = 0x0E; - _field130 = 0x0E; - _field134 = 27; - _field138 = 40; - _field13C = 0x44; + _startFrame8 = -1; + _endFrame8 = -1; + _startFrame11 = -1; + _endFrame11 = -1; + _startFrame3 = 68; + _endFrame3 = 168; + _startFrame4 = 168; + _endFrame4 = 248; + _startFrame9 = 0; + _endFrame9 = 0x0E; + _startFrame10 = 0x0E; + _endFrame10 = 27; + _startFrame2 = 40; + _endFrame2 = 68; _field140 = 1; - _field144 = 0; - _field148 = 0; - _field14C = 0; - _field150 = 0xE0; - _field154 = 0; + _mailP = nullptr; + _startFrame5 = 0; + _endFrame5 = 0; + _startFrame12 = 224; + _endFrame12 = 248; _field158 = 0; _field15C = 0; _string2 = "NULL"; - _field16C = 28; - _field170 = 40; - _field174 = 82; - _field178 = 284; - _field17C = 148; - _field180 = 339; + _startFrame1 = 28; + _endFrame1 = 40; + _rect1 = Rect(82, 284, 148, 339); _field184 = 15; _field188 = 0; - _field18C = 0; - _field190 = 0; - _field194 = 240; - _field198 = 340; + _rect2 = Rect(0, 0, 240, 340); _field19C = 0; - _field1A0 = -1; - _field1A4 = 0; - _field1A8 = 0; + _soundHandle = -1; + _isChicken = false; + _isFeathers = false; _field1AC = 0; _field1B0 = 0; - _field1B4 = 303; - _field1B8 = 312; - _field1BC = 313; - _field1C0 = 325; + _startFrame6 = 303; + _endFrame6 = 312; + _startFrame7 = 313; + _endFrame7 = 325; _field1C4 = 326; _field1C8 = 347; _field1CC = 348; @@ -87,54 +103,54 @@ CSuccUBus::CSuccUBus() : CTrueTalkNPC() { void CSuccUBus::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_v0, indent); - file->writeNumberLine(_field108, indent); - file->writeNumberLine(_field10C, indent); - file->writeNumberLine(_field110, indent); - file->writeNumberLine(_field114, indent); - file->writeNumberLine(_field118, indent); - file->writeNumberLine(_field11C, indent); - file->writeNumberLine(_field120, indent); - file->writeNumberLine(_field124, indent); - file->writeNumberLine(_field128, indent); - file->writeNumberLine(_field12C, indent); - file->writeNumberLine(_field130, indent); - file->writeNumberLine(_field134, indent); - file->writeNumberLine(_field138, indent); - file->writeNumberLine(_field13C, indent); + file->writeNumberLine(_enabled, indent); + file->writeNumberLine(_startFrame8, indent); + file->writeNumberLine(_endFrame8, indent); + file->writeNumberLine(_startFrame11, indent); + file->writeNumberLine(_endFrame11, indent); + file->writeNumberLine(_startFrame3, indent); + file->writeNumberLine(_endFrame3, indent); + file->writeNumberLine(_startFrame4, indent); + file->writeNumberLine(_endFrame4, indent); + file->writeNumberLine(_startFrame9, indent); + file->writeNumberLine(_endFrame9, indent); + file->writeNumberLine(_startFrame10, indent); + file->writeNumberLine(_endFrame10, indent); + file->writeNumberLine(_startFrame2, indent); + file->writeNumberLine(_endFrame2, indent); file->writeNumberLine(_field140, indent); file->writeNumberLine(_v2, indent); - file->writeNumberLine(_field148, indent); - file->writeNumberLine(_field14C, indent); - file->writeNumberLine(_field150, indent); - file->writeNumberLine(_field154, indent); + file->writeNumberLine(_startFrame5, indent); + file->writeNumberLine(_endFrame5, indent); + file->writeNumberLine(_startFrame12, indent); + file->writeNumberLine(_endFrame12, indent); file->writeNumberLine(_field158, indent); file->writeNumberLine(_field15C, indent); file->writeQuotedLine(_string2, indent); - file->writeNumberLine(_field16C, indent); - file->writeNumberLine(_field170, indent); - file->writeNumberLine(_field174, indent); - file->writeNumberLine(_field178, indent); - file->writeNumberLine(_field17C, indent); - file->writeNumberLine(_field180, indent); + file->writeNumberLine(_startFrame1, indent); + file->writeNumberLine(_endFrame1, indent); + file->writeNumberLine(_rect1.left, indent); + file->writeNumberLine(_rect1.top, indent); + file->writeNumberLine(_rect1.right, indent); + file->writeNumberLine(_rect1.bottom, indent); file->writeNumberLine(_field184, indent); file->writeNumberLine(_field188, indent); - file->writeNumberLine(_field18C, indent); - file->writeNumberLine(_field190, indent); - file->writeNumberLine(_field194, indent); - file->writeNumberLine(_field198, indent); + file->writeNumberLine(_rect2.left, indent); + file->writeNumberLine(_rect2.top, indent); + file->writeNumberLine(_rect2.right, indent); + file->writeNumberLine(_rect2.bottom, indent); file->writeNumberLine(_field19C, indent); - file->writeNumberLine(_field1A0, indent); - file->writeNumberLine(_field1A4, indent); - file->writeNumberLine(_field1A8, indent); + file->writeNumberLine(_soundHandle, indent); + file->writeNumberLine(_isChicken, indent); + file->writeNumberLine(_isFeathers, indent); file->writeNumberLine(_field1AC, indent); file->writeNumberLine(_field1B0, indent); - file->writeNumberLine(_field1B4, indent); - file->writeNumberLine(_field1B8, indent); - file->writeNumberLine(_field1BC, indent); - file->writeNumberLine(_field1C0, indent); + file->writeNumberLine(_startFrame6, indent); + file->writeNumberLine(_endFrame6, indent); + file->writeNumberLine(_startFrame7, indent); + file->writeNumberLine(_endFrame7, indent); file->writeNumberLine(_field1C4, indent); file->writeNumberLine(_field1C8, indent); file->writeNumberLine(_field1CC, indent); @@ -151,54 +167,54 @@ void CSuccUBus::save(SimpleFile *file, int indent) { void CSuccUBus::load(SimpleFile *file) { file->readNumber(); - _v0 = file->readNumber(); - _field108 = file->readNumber(); - _field10C = file->readNumber(); - _field110 = file->readNumber(); - _field114 = file->readNumber(); - _field118 = file->readNumber(); - _field11C = file->readNumber(); - _field120 = file->readNumber(); - _field124 = file->readNumber(); - _field128 = file->readNumber(); - _field12C = file->readNumber(); - _field130 = file->readNumber(); - _field134 = file->readNumber(); - _field138 = file->readNumber(); - _field13C = file->readNumber(); + _enabled = file->readNumber(); + _startFrame8 = file->readNumber(); + _endFrame8 = file->readNumber(); + _startFrame11 = file->readNumber(); + _endFrame11 = file->readNumber(); + _startFrame3 = file->readNumber(); + _endFrame3 = file->readNumber(); + _startFrame4 = file->readNumber(); + _endFrame4 = file->readNumber(); + _startFrame9 = file->readNumber(); + _endFrame9 = file->readNumber(); + _startFrame10 = file->readNumber(); + _endFrame10 = file->readNumber(); + _startFrame2 = file->readNumber(); + _endFrame2 = file->readNumber(); _field140 = file->readNumber(); _v2 = file->readNumber(); - _field148 = file->readNumber(); - _field14C = file->readNumber(); - _field150 = file->readNumber(); - _field154 = file->readNumber(); + _startFrame5 = file->readNumber(); + _endFrame5 = file->readNumber(); + _startFrame12 = file->readNumber(); + _endFrame12 = file->readNumber(); _field158 = file->readNumber(); _field15C = file->readNumber(); _string2 = file->readString(); - _field16C = file->readNumber(); - _field170 = file->readNumber(); - _field174 = file->readNumber(); - _field178 = file->readNumber(); - _field17C = file->readNumber(); - _field180 = file->readNumber(); + _startFrame1 = file->readNumber(); + _endFrame1 = file->readNumber(); + _rect1.left = file->readNumber(); + _rect1.top = file->readNumber(); + _rect1.right = file->readNumber(); + _rect1.bottom = file->readNumber(); _field184 = file->readNumber(); _field188 = file->readNumber(); - _field18C = file->readNumber(); - _field190 = file->readNumber(); - _field194 = file->readNumber(); - _field198 = file->readNumber(); + _rect2.left = file->readNumber(); + _rect2.top = file->readNumber(); + _rect2.right = file->readNumber(); + _rect2.bottom = file->readNumber(); _field19C = file->readNumber(); - _field1A0 = file->readNumber(); - _field1A4 = file->readNumber(); - _field1A8 = file->readNumber(); + _soundHandle = file->readNumber(); + _isChicken = file->readNumber(); + _isFeathers = file->readNumber(); _field1AC = file->readNumber(); _field1B0 = file->readNumber(); - _field1B4 = file->readNumber(); - _field1B8 = file->readNumber(); - _field1BC = file->readNumber(); - _field1C0 = file->readNumber(); + _startFrame6 = file->readNumber(); + _endFrame6 = file->readNumber(); + _startFrame7 = file->readNumber(); + _endFrame7 = file->readNumber(); _field1C4 = file->readNumber(); _field1C8 = file->readNumber(); _field1CC = file->readNumber(); @@ -212,4 +228,559 @@ void CSuccUBus::load(SimpleFile *file) { CTrueTalkNPC::load(file); } +bool CSuccUBus::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!_field1D8) { + Rect tempRect = _rect1; + tempRect.translate(_bounds.left, _bounds.top); + + if (!_enabled || (_field188 && tempRect.contains(msg->_mousePos))) { + CTurnOn onMsg; + onMsg.execute(this); + _enabled = true; + } else if (getRandomNumber(256) < 130) { + _enabled = false; + CTurnOff offMsg; + offMsg.execute(this); + } else { + switch (getRandomNumber(2)) { + case 0: + startTalking(this, 230055, findView()); + break; + case 1: + startTalking(this, 230067, findView()); + break; + case 2: + startTalking(this, 230045, findView()); + break; + default: + break; + } + } + } + + return true; +} + +bool CSuccUBus::SubAcceptCCarryMsg(CSubAcceptCCarryMsg *msg) { + if (!msg->_item) + return false; + + CPetControl *pet = getPetControl(); + CCarry *item = dynamic_cast<CCarry *>(msg->_item); + Rect tempRect = _rect2; + tempRect.translate(_bounds.left, _bounds.top); + uint roomFlags = pet ? pet->getRoomFlags() : 0; + + if (!_enabled || !pet || !item || !tempRect.contains(item->getControid())) { + item->petAddToInventory(); + } else if (mailExists(roomFlags)) { + petDisplayMessage("The Succ-U-Bus is a Single Entity Delivery Device."); + item->petAddToInventory(); + } else { + petContainerRemove(item); + pet->phonographAction(""); + + CChicken *chicken = dynamic_cast<CChicken *>(item); + bool chickenFlag = chicken ? chicken->_string6 != "None" : false; + + item->setVisible(false); + if (_startFrame1 >= 0) { + playSound("z#23.wav"); + playMovie(_startFrame1, _endFrame1, 0); + } + + if (!chickenFlag) { + _field188 = 1; + item->addMail(roomFlags); + petSetArea(PET_REMOTE); + petHighlightGlyph(16); + CSUBTransition transMsg; + transMsg.execute(this); + } else { + if (_startFrame2 >= 0) { + startTalking(this, 70219, findView()); + playMovie(_startFrame2, _endFrame2, 0); + } + + if (_startFrame3 >= 0) { + playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT); + _field158 = 2; + } + + CViewItem *view = parseView(chicken->_fullViewName); + if (!view) + return false; + + item->setPosition(item->_origPos); + item->moveUnder(view); + + CSUBTransition transMsg; + transMsg.execute(this); + } + } + + return true; +} + +bool CSuccUBus::EnterViewMsg(CEnterViewMsg *msg) { + if (getRandomNumber(4) == 0 && compareRoomNameTo("PromenadeDeck")) { + CParrotSpeakMsg speakMsg("SuccUBus", "EnterView"); + speakMsg.execute("PerchedParrot"); + } + + petSetRemoteTarget(); + _mailP = nullptr; + if (_startFrame8 >= 0) + loadFrame(_startFrame8); + + return true; +} + +bool CSuccUBus::LeaveViewMsg(CLeaveViewMsg *msg) { + petDisplayMessage(2, ""); + if (_startFrame8 >= 0) + loadFrame(_startFrame8); + else if (!_field15C && _startFrame9 >= 0) + loadFrame(_startFrame9); + + petClear(); + if (_soundHandle != -1) { + stopSound(_soundHandle, 1); + _soundHandle = -1; + } + + if (_enabled) { + _enabled = false; + if (_startFrame10 >= 0) + playSound("z#27.wav", 100); + + if (_field15C) + setVisible(false); + } + + performAction(true, findView()); + CSUBTransition transMsg; + transMsg.execute(this); + + return true; +} + +bool CSuccUBus::PETDeliverMsg(CPETDeliverMsg *msg) { + if (_field1D8) + return true; + + if (!_enabled) { + petDisplayMessage(2, "The Succ-U-Bus is in Standby, or \"Off\" mode at present."); + return true; + } + + CPetControl *pet = getPetControl(); + if (!pet) + return true; + + CGameObject *mailObject = findMail(pet->getRoomFlags()); + if (!mailObject) { + switch (getRandomNumber(2)) { + case 0: + startTalking(this, 70111, findView()); + break; + case 1: + startTalking(this, 70112, findView()); + break; + case 2: + startTalking(this, 70113, findView()); + break; + default: + break; + } + + petDisplayMessage(2, "There is currently nothing in the tray to send."); + } else { + _field19C = 0; + + CRoomFlags roomFlags = _roomFlags; + if (!pet->testRooms5(roomFlags) || getPassengerClass() > 0) { + roomFlags = pet->getSpecialRoomFlags("BilgeRoom"); + _field19C = 1; + } else { + pet->getMailDest(roomFlags); + } + + _isFeathers = mailObject->getName() == "Feathers"; + _isChicken = mailObject->getName() == "Chicken"; + _field158 = 0; + _field188 = 0; + _field1D8 = 1; + inc54(); + + if (_isFeathers) { + _field19C = 0; + removeMail(roomFlags, roomFlags); + pet->phonographAction(""); + + if (_startFrame2 >= 0) { + playMovie(_startFrame2, _endFrame2, 0); + startTalking(this, 230022, findView()); + } + + _field158 = 1; + if (_startFrame3 >= 0) + playMovie(_startFrame3, _endFrame3, 0); + + if (_startFrame4 >= 0) { + _mailP = mailObject; + playMovie(_startFrame4, _endFrame4, MOVIE_NOTIFY_OBJECT); + } + + if (_startFrame5 >= 0) { + playMovie(_startFrame5, _endFrame5, 0); + } + } else { + removeMail(pet->getRoomFlags(), roomFlags); + pet->phonographAction(""); + + if (_startFrame2 >= 0) { + playMovie(_startFrame2, _endFrame2, 0); + startTalking(this, 230012, findView()); + } + + if (_startFrame3 >= 0) + playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT); + } + } + + return true; +} + +bool CSuccUBus::PETReceiveMsg(CPETReceiveMsg *msg) { + CPetControl *pet = getPetControl(); + + if (_field1D8 || !pet) + return true; + if (!_enabled) { + petDisplayMessage(2, "The Succ-U-Bus is in Standby, or \"Off\" mode at present."); + return true; + } + + uint petRoomFlags = pet->getRoomFlags(); + if (mailExists(petRoomFlags)) { + switch (getRandomNumber(2)) { + case 0: + startTalking(this, 70080, findView()); + break; + case 1: + startTalking(this, 70081, findView()); + break; + case 2: + startTalking(this, 70082, findView()); + break; + default: + break; + } + } else { + CGameObject *mailObject = findMailByFlags(compareRoomNameTo("Titania") + ? 3 : _field140, petRoomFlags); + if (!mailObject) { + if (getRandomNumber(1) == 0) { + startTalking(this, 70104, findView()); + } else { + startTalking(this, 70105, findView()); + } + + playMovie(_startFrame6, _endFrame6, 0); + playMovie(_startFrame7, _endFrame7, 0); + petDisplayMessage(2, "There is currently nothing to deliver."); + } else { + startTalking(this, 230004, findView()); + + if (_startFrame4 >= 0) { + _field158 = 1; + _field1D8 = 1; + inc54(); + playMovie(_startFrame4, _endFrame4, MOVIE_NOTIFY_OBJECT); + } + } + } + + return true; +} + +bool CSuccUBus::MovieEndMsg(CMovieEndMsg *msg) { + CPetControl *pet = getPetControl(); + uint petRoomFlags = pet ? pet->getRoomFlags() : 0; + + if (msg->_endFrame == _endFrame10) { + if (_startFrame11 >= 0) + playSound("z#30.wav", 100); + + if (_field15C) { + _field15C = false; + setVisible(false); + CSignalObject signalMsg; + signalMsg._numValue = 1; + signalMsg.execute(_string2); + } + } + + if (msg->_endFrame == _endFrame9) { + bool flag = false; + + if (pet && mailExists(petRoomFlags)) { + CGameObject *mailObject = _v3 && compareRoomNameTo("Titania") ? + findMailByFlags(3, petRoomFlags) : + findMailByFlags(_field140, petRoomFlags); + + if (mailObject) { + switch (getRandomNumber(4)) { + case 0: + startTalking(this, 70094, findView()); + break; + case 1: + startTalking(this, 70095, findView()); + break; + case 2: + startTalking(this, 70096, findView()); + break; + case 3: + startTalking(this, 70098, findView()); + break; + case 4: + startTalking(this, 70099, findView()); + break; + default: + break; + } + flag = true; + } + } + + if (!_field188 && !flag) { + stopSound(_soundHandle); + _soundHandle = -1; + + switch (getRandomNumber(_v2 ? 7 : 5, &_field1B0)) { + case 2: + startTalking(this, 230001, findView()); + break; + case 3: + startTalking(this, 230002, findView()); + break; + case 4: + startTalking(this, 230003, findView()); + break; + case 5: + startTalking(this, 230064, findView()); + break; + case 6: + startTalking(this, 230062, findView()); + break; + case 7: + startTalking(this, 230063, findView()); + break; + default: + break; + } + } + } + + if (msg->_endFrame == _endFrame3) { + if (_field158 == 1) { + startTalking(this, 230022, findView()); + } else if (_field158 == 2) { + startTalking(this, 230017, findView()); + } else if (_field19C) { + startTalking(this, 230019, findView()); + _field19C = 0; + } else if (_isChicken) { + startTalking(this, 230018, findView()); + _isChicken = false; + } else { + startTalking(this, 230013, findView()); + } + + if (_field1D8) { + _field1D8 = 0; + dec54(); + } + + CSUBTransition transMsg; + transMsg.execute(this); + } + + if (msg->_endFrame == _endFrame4) { + if (pet && _mailP) { + _mailP->setMailId(petRoomFlags); + } + + _field188 = 1; + _mailP = 0; + if (_field1D8) { + _field1D8 = 0; + dec54(); + } + + CSUBTransition transMsg; + transMsg.execute(this); + } + + return true; +} + +bool CSuccUBus::TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg) { + if (msg->_stateNum == 1) + msg->_stateVal = _enabled; + + return true; +} + +bool CSuccUBus::SignalObject(CSignalObject *msg) { + if (msg->_numValue == 1) { + _string2 = msg->_strValue; + _field15C = 1; + setVisible(true); + CTurnOn onMsg; + onMsg.execute(this); + } + + return true; +} + +bool CSuccUBus::TurnOn(CTurnOn *msg) { + if (getRandomNumber(9) == 0) { + CParrotSpeakMsg speakMsg("SuccUBus", "TurnOn"); + speakMsg.execute("PerchedParrot"); + } + + CPetControl *pet = getPetControl(); + if (pet) { + if (!_field15C && _startFrame8 >= 0) { + playMovie(_startFrame8, _endFrame8, 0); + playSound("z#30.wav", 100); + } + + if (_startFrame9 >= 0) { + playMovie(_startFrame9, _endFrame9, MOVIE_NOTIFY_OBJECT); + playSound("z#26.wav", 100); + } + + uint petRoomFlags = pet->getRoomFlags(); + if (mailExists(petRoomFlags) && _endFrame1 >= 0) + playMovie(_endFrame1, _endFrame1, 0); + + _enabled = true; + CSUBTransition transMsg; + transMsg.execute(this); + + endTalking(this, true, findView()); + petSetArea(PET_REMOTE); + petHighlightGlyph(16); + } + + return true; +} + +bool CSuccUBus::TurnOff(CTurnOff *msg) { + if (_soundHandle != -1) { + stopSound(_soundHandle); + _soundHandle = -1; + } + + if (_startFrame10 >= 0) { + playSound("z#27.wav", 100); + playMovie(_startFrame10, _endFrame10, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + } + + if (!_field15C && _startFrame11 >= 0) + playMovie(_startFrame11, _endFrame11, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + + _enabled = false; + performAction(true); + CSUBTransition transMsg; + transMsg.execute(this); + + return true; +} + +bool CSuccUBus::SUBTransition(CSUBTransition *msg) { + CPetControl *pet = getPetControl(); + + if (pet) { + uint petRoomFlags = pet->getRoomFlags(); + + if (_enabled) { + CGameObject *mailObject = findMail(petRoomFlags); + if (mailObject) + pet->phonographAction("Send"); + else + pet->phonographAction("Receive"); + } else { + if (pet->isSuccUBusRoom(petRoomFlags)) + pet->phonographAction("Record"); + else + pet->phonographAction(""); + } + } + + return true; +} + +bool CSuccUBus::SetChevRoomBits(CSetChevRoomBits *msg) { + if (_enabled) { + _roomFlags = msg->_roomNum; + playSound("z#98.wav", 100); + } + + return true; +} + +bool CSuccUBus::ActMsg(CActMsg *msg) { + if (msg->_action == "EnableObject") + _v3 = 1; + else if (msg->_action == "DisableObject") + _v3 = 0; + + return true; +} + +bool CSuccUBus::MouseDragStartMsg(CMouseDragStartMsg *msg) { + CPetControl *pet = getPetControl(); + Rect tempRect = _rect1; + tempRect.translate(_bounds.left, _bounds.top); + + if (_field1D8 || !_enabled || !_field188 || !tempRect.contains(msg->_mousePos) + || !pet) + return true; + + uint petRoomFlags = pet->getRoomFlags(); + CGameObject *mailObject = findMail(petRoomFlags); + if (!mailObject) + return true; + + petAddToCarryParcel(mailObject); + CViewItem *view = getView(); + if (!view) + return true; + + mailObject->moveUnder(view); + mailObject->setPosition(Point(msg->_mousePos.x + mailObject->_bounds.width() / 2, + msg->_mousePos.y + mailObject->_bounds.height() / 2)); + + CVisibleMsg visibleMsg(true); + visibleMsg.execute(mailObject); + CPassOnDragStartMsg dragMsg; + dragMsg._mousePos = msg->_mousePos; + dragMsg._value3 = 1; + dragMsg.execute(mailObject); + + if (!dragMsg._value4) + msg->_dragItem = mailObject; + + loadFrame(_field184); + _field188 = 0; + CSUBTransition transMsg; + transMsg.execute(this); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/npcs/succubus.h b/engines/titanic/npcs/succubus.h index f6f5a6b9e9..7ca8037a0a 100644 --- a/engines/titanic/npcs/succubus.h +++ b/engines/titanic/npcs/succubus.h @@ -24,62 +24,76 @@ #define TITANIC_SUCCUBUS_H #include "titanic/npcs/true_talk_npc.h" +#include "titanic/messages/pet_messages.h" namespace Titanic { class CSuccUBus : public CTrueTalkNPC { -private: - static int _v0; + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool SubAcceptCCarryMsg(CSubAcceptCCarryMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool PETDeliverMsg(CPETDeliverMsg *msg); + bool PETReceiveMsg(CPETReceiveMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg); + bool SignalObject(CSignalObject *msg); + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool SUBTransition(CSUBTransition *msg); + bool SetChevRoomBits(CSetChevRoomBits *msg); + bool ActMsg(CActMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); +protected: + static bool _enabled; static int _v1; static int _v2; static int _v3; static int _v4; -private: - int _field108; - int _field10C; - int _field110; - int _field114; - int _field118; - int _field11C; - int _field120; - int _field124; - int _field128; - int _field12C; - int _field130; - int _field134; - int _field138; - int _field13C; +protected: + int _startFrame8; + int _endFrame8; + int _startFrame11; + int _endFrame11; + int _startFrame3; + int _endFrame3; + int _startFrame4; + int _endFrame4; + int _startFrame9; + int _endFrame9; + int _startFrame10; + int _endFrame10; + int _startFrame2; + int _endFrame2; int _field140; - int _field144; - int _field148; - int _field14C; - int _field150; - int _field154; + CGameObject *_mailP; + int _startFrame5; + int _endFrame5; + int _startFrame12; + int _endFrame12; int _field158; - int _field15C; + bool _field15C; CString _string2; - int _field16C; - int _field170; - int _field174; - int _field178; - int _field17C; - int _field180; + int _startFrame1; + int _endFrame1; + Rect _rect1; int _field184; int _field188; - int _field18C; + Rect _rect2; int _field190; int _field194; int _field198; int _field19C; - int _field1A0; - int _field1A4; - int _field1A8; + int _soundHandle; + bool _isChicken; + bool _isFeathers; int _field1AC; int _field1B0; - int _field1B4; - int _field1B8; - int _field1BC; - int _field1C0; + int _startFrame6; + int _endFrame6; + int _startFrame7; + int _endFrame7; int _field1C4; int _field1C8; int _field1CC; diff --git a/engines/titanic/npcs/summon_bots.cpp b/engines/titanic/npcs/summon_bots.cpp index 8796e5ffda..6d71847548 100644 --- a/engines/titanic/npcs/summon_bots.cpp +++ b/engines/titanic/npcs/summon_bots.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CSummonBots, CRobotController) + ON_MESSAGE(SummonBotQueryMsg) + ON_MESSAGE(SummonBotMsg) +END_MESSAGE_MAP() + CSummonBots::CSummonBots() : CRobotController(), _string2("NULL"), _fieldC8(0), _fieldCC(0) { } @@ -46,4 +51,36 @@ void CSummonBots::load(SimpleFile *file) { CRobotController::load(file); } +bool CSummonBots::SummonBotQueryMsg(CSummonBotQueryMsg *msg) { + if (msg->_npcName == "BellBot") { + if (_fieldC8 && !petCheckNode(_string2)) + return true; + } else if (msg->_npcName == "DoorBot") { + if (_fieldCC && !petCheckNode(_string2)) + return true; + } + + return false; +} + +bool CSummonBots::SummonBotMsg(CSummonBotMsg *msg) { + if (msg->_npcName == "BellBot") { + if (!_fieldC8) + return false; + + if (petDismissBot("BellBot")) + petOnSummonBot("Bellbot", msg->_value); + } else if (msg->_npcName == "DoorBot") { + if (!_fieldCC) + return false; + + if (petDismissBot("Doorbot")) + petOnSummonBot("Doorbot", msg->_value); + } else { + return false; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/npcs/summon_bots.h b/engines/titanic/npcs/summon_bots.h index ee537fee76..1da6e68245 100644 --- a/engines/titanic/npcs/summon_bots.h +++ b/engines/titanic/npcs/summon_bots.h @@ -28,6 +28,9 @@ namespace Titanic { class CSummonBots : public CRobotController { + DECLARE_MESSAGE_MAP; + bool SummonBotQueryMsg(CSummonBotQueryMsg *msg); + bool SummonBotMsg(CSummonBotMsg *msg); protected: CString _string2; int _fieldC8; diff --git a/engines/titanic/npcs/titania.cpp b/engines/titanic/npcs/titania.cpp index 34c21d0efe..000595f6b7 100644 --- a/engines/titanic/npcs/titania.cpp +++ b/engines/titanic/npcs/titania.cpp @@ -24,54 +24,202 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CTitania, CCharacter) + ON_MESSAGE(AddHeadPieceMsg) + ON_MESSAGE(TakeHeadPieceMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(TimerMsg) +END_MESSAGE_MAP() + CTitania::CTitania() : CCharacter() { - _fieldD4 = 0; - _fieldD8 = 0; - _fieldE0 = 0; - _fieldE4 = 0; - _fieldE8 = 0; - _fieldEC = 0; - _fieldF0 = 0; - _fieldF4 = 0; - _fieldF8 = 0; - _fieldFC = 0; - _field100 = 1; + _speechCentre = false; + _olfactoryCentre = false; + _centralCore = false; + _visionCentre = false; + _eye1 = false; + _eye2 = false; + _ear1 = false; + _ear2 = false; + _nose = false; + _mouth = false; + _showIntro = true; } void CTitania::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldD4, indent); - file->writeNumberLine(_fieldD8, indent); - file->writeNumberLine(_fieldDC, indent); - file->writeNumberLine(_fieldE0, indent); - file->writeNumberLine(_fieldE4, indent); - file->writeNumberLine(_fieldE8, indent); - file->writeNumberLine(_fieldEC, indent); - file->writeNumberLine(_fieldF0, indent); - file->writeNumberLine(_fieldF4, indent); - file->writeNumberLine(_fieldF8, indent); - file->writeNumberLine(_fieldFC, indent); - file->writeNumberLine(_field100, indent); + file->writeNumberLine(_speechCentre, indent); + file->writeNumberLine(_olfactoryCentre, indent); + file->writeNumberLine(_auditoryCentre, indent); + file->writeNumberLine(_centralCore, indent); + file->writeNumberLine(_visionCentre, indent); + file->writeNumberLine(_eye1, indent); + file->writeNumberLine(_eye2, indent); + file->writeNumberLine(_ear1, indent); + file->writeNumberLine(_ear2, indent); + file->writeNumberLine(_nose, indent); + file->writeNumberLine(_mouth, indent); + file->writeNumberLine(_showIntro, indent); CCharacter::save(file, indent); } void CTitania::load(SimpleFile *file) { file->readNumber(); - _fieldD4 = file->readNumber(); - _fieldD8 = file->readNumber(); - _fieldDC = file->readNumber(); - _fieldE0 = file->readNumber(); - _fieldE4 = file->readNumber(); - _fieldE8 = file->readNumber(); - _fieldEC = file->readNumber(); - _fieldF0 = file->readNumber(); - _fieldF4 = file->readNumber(); - _fieldF8 = file->readNumber(); - _fieldFC = file->readNumber(); - _field100 = file->readNumber(); + _speechCentre = file->readNumber(); + _olfactoryCentre = file->readNumber(); + _auditoryCentre = file->readNumber(); + _centralCore = file->readNumber(); + _visionCentre = file->readNumber(); + _eye1 = file->readNumber(); + _eye2 = file->readNumber(); + _ear1 = file->readNumber(); + _ear2 = file->readNumber(); + _nose = file->readNumber(); + _mouth = file->readNumber(); + _showIntro = file->readNumber(); CCharacter::load(file); } +bool CTitania::AddHeadPieceMsg(CAddHeadPieceMsg *msg) { + if (msg->_value == "VisionCentre") { + _visionCentre = true; + } else if (msg->_value == "AuditoryCentre") { + _auditoryCentre = true; + } else if (msg->_value == "OlfactoryCentre") { + _olfactoryCentre = true; + } else if (msg->_value == "SpeechCentre") { + _speechCentre = true; + } else if (msg->_value == "CentralCore") { + _centralCore = true; + } else if (msg->_value == "Eye1") { + _eye1 = true; + } else if (msg->_value == "Eye2") { + _eye2 = true; + } else if (msg->_value == "Ear1") { + _ear1 = true; + } else if (msg->_value == "Ear2") { + _ear2 = true; + } else if (msg->_value == "Mouth") { + _mouth = true; + } else if (msg->_value == "Nose") { + _nose = true; + } + + CActMsg actMsg("CheckHead"); + actMsg.execute(this); + return true; +} + +bool CTitania::TakeHeadPieceMsg(CTakeHeadPieceMsg *msg) { + if (msg->_value == "VisionCentre") { + _visionCentre = false; + } else if (msg->_value == "AuditoryCentre") { + _auditoryCentre = false; + } else if (msg->_value == "OlfactoryCentre") { + _olfactoryCentre = false; + } else if (msg->_value == "SpeechCentre") { + _speechCentre = false; + } else if (msg->_value == "CentralCore") { + _centralCore = false; + } else if (msg->_value == "Eye1") { + _eye1 = false; + } else if (msg->_value == "Eye2") { + _eye2 = false; + } else if (msg->_value == "Ear1") { + _ear1 = false; + } else if (msg->_value == "Ear2") { + _ear2 = false; + } else if (msg->_value == "Mouth") { + _mouth = false; + } else if (msg->_value == "Nose") { + _nose = false; + } + + CActMsg actMsg("CheckHead"); + actMsg.execute(this); + return true; +} + +bool CTitania::ActMsg(CActMsg *msg) { + if (msg->_action == "SleepTitania") { + setVisible(true); + playCutscene(52, 104); + playSound("z#47.wav", 100); + changeView("Titania.Node 7.S", ""); + + petShow(); + enableMouse(); + CSetFrameMsg frameMsg; + frameMsg.execute("Bomb"); + + } else if (msg->_action == "CheckHead") { + CSenseWorkingMsg workingMsg1("Not Working"); + CSenseWorkingMsg workingMsg2("Not Working"); + CSenseWorkingMsg workingMsg3("Not Working"); + CSenseWorkingMsg workingMsg4("Not Working"); + + if (_eye1 && _eye2) { + workingMsg1._value = _visionCentre ? "Working" : "Random"; + } + if (_ear1 && _ear2) { + workingMsg2._value = _auditoryCentre ? "Working" : "Random"; + } + if (_nose) { + workingMsg4._value = _olfactoryCentre ? "Working" : "Random"; + } + if (_mouth) { + workingMsg3._value = _speechCentre ? "Working" : "Random"; + } + + if (_centralCore && _eye1 && _eye2 && _ear1 && _ear2 && _nose && _mouth + && _speechCentre && _olfactoryCentre && _auditoryCentre) { + playSound("z#47.wav"); + + CActMsg actMsg("Woken"); + actMsg.execute("MouthSlot"); + actMsg.execute("VisionCentreSlot"); + setPassengerClass(4); + + addTimer(1000); + } else { + workingMsg1.execute("Eye1Slot"); + workingMsg1.execute("Eye2Slot"); + workingMsg2.execute("Ear1Slot"); + workingMsg2.execute("Ear2Slot"); + workingMsg3.execute("MouthSlot"); + workingMsg4.execute("NoseSlot"); + } + } + + return true; +} + +bool CTitania::EnterViewMsg(CEnterViewMsg *msg) { + if (_showIntro) { + _showIntro = false; + disableMouse(); + petHide(); + + CSetFrameMsg frameMsg; + frameMsg._frameNumber = 25; + frameMsg.execute("Bomb"); + playCutscene(0, 52); + + setVisible(false); + CActMsg actMsg("TitaniaSpeech"); + actMsg.execute("TitaniaSpeech"); + } + + return true; +} + +bool CTitania::TimerMsg(CTimerMsg *msg) { + changeView("Titania.Node 18.N", ""); + startTalking("PerchedParrot", 80022); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/npcs/titania.h b/engines/titanic/npcs/titania.h index 4edd626ab6..61f8c86018 100644 --- a/engines/titanic/npcs/titania.h +++ b/engines/titanic/npcs/titania.h @@ -28,19 +28,25 @@ namespace Titanic { class CTitania : public CCharacter { + DECLARE_MESSAGE_MAP; + bool AddHeadPieceMsg(CAddHeadPieceMsg *msg); + bool TakeHeadPieceMsg(CTakeHeadPieceMsg *msg); + bool ActMsg(CActMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool TimerMsg(CTimerMsg *msg); private: - int _fieldD4; - int _fieldD8; - int _fieldDC; - int _fieldE0; - int _fieldE4; - int _fieldE8; - int _fieldEC; - int _fieldF0; - int _fieldF4; - int _fieldF8; - int _fieldFC; - int _field100; + bool _speechCentre; + bool _olfactoryCentre; + bool _auditoryCentre; + bool _centralCore; + bool _visionCentre; + bool _eye1; + bool _eye2; + bool _ear1; + bool _ear2; + bool _nose; + bool _mouth; + bool _showIntro; public: CLASSDEF; CTitania(); diff --git a/engines/titanic/npcs/true_talk_npc.cpp b/engines/titanic/npcs/true_talk_npc.cpp index 9310f285e5..97913dffea 100644 --- a/engines/titanic/npcs/true_talk_npc.cpp +++ b/engines/titanic/npcs/true_talk_npc.cpp @@ -23,7 +23,7 @@ #include "titanic/npcs/true_talk_npc.h" #include "titanic/core/view_item.h" #include "titanic/pet_control/pet_control.h" -#include "titanic/titanic.h" +#include "titanic/game_manager.h" namespace Titanic { @@ -103,7 +103,7 @@ bool CTrueTalkNPC::TrueTalkNotifySpeechStartedMsg(CTrueTalkNotifySpeechStartedMs stopTimer(_speechTimerId); _soundId = msg->_soundId; - _fieldF0 = g_vm->_events->getTicksCount(); + _fieldF0 = getTicksCount(); if (hasActiveMovie() || (_npcFlags & NPCFLAG_2)) { _npcFlags &= ~NPCFLAG_2; @@ -147,7 +147,7 @@ bool CTrueTalkNPC::MovieEndMsg(CMovieEndMsg *msg) { return false; } - int diff = g_vm->_events->getTicksCount() - _fieldF0; + int diff = getTicksCount() - _fieldF0; int ticks = MAX((int)_soundId - diff, 0); CNPCPlayTalkingAnimationMsg msg1(ticks, ticks > 1000 ? 2 : 1, 0); msg1.execute(this); @@ -161,7 +161,7 @@ bool CTrueTalkNPC::MovieEndMsg(CMovieEndMsg *msg) { } bool CTrueTalkNPC::NPCQueueIdleAnimMsg(CNPCQueueIdleAnimMsg *msg) { - int rndVal = g_vm->getRandomNumber(_fieldF8 - 1) - (_fieldF8 / 2); + int rndVal = getRandomNumber(_fieldF8 - 1) - (_fieldF8 / 2); _speechTimerId = startAnimTimer("NPCIdleAnim", _fieldF4 + rndVal, 0); return true; @@ -188,7 +188,34 @@ bool CTrueTalkNPC::TimerMsg(CTimerMsg *msg) { } bool CTrueTalkNPC::NPCPlayAnimationMsg(CNPCPlayAnimationMsg *msg) { - warning("CTrueTalkNPC::NPCPlayAnimationMsg"); +// const char *const *nameP = msg->_names; + int count; + for (count = 0; msg->_names[count]; ++count) + ; + + if (msg->_maxDuration) { + // Randomly pick a clip that's less than the allowed maximum + int tries = 10, index; + do { + index = getRandomNumber(count - 1); + } while (getClipDuration(msg->_names[index]) > msg->_maxDuration && --tries); + + if (tries) { + // Sequentially go through the clips to find any below the maximum + index = 0; + for (int idx = 0; idx < count; ++idx) { + if (getClipDuration(msg->_names[idx]) < msg->_maxDuration) { + index = idx; + break; + } + } + } + + playClip(msg->_names[index], MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); + } else { + playClip(msg->_names[getRandomNumber(count - 1)]); + } + return true; } @@ -198,10 +225,6 @@ void CTrueTalkNPC::processInput(CTextInputMsg *msg, CViewItem *view) { talkManager->processInput(this, msg, view); } -void CTrueTalkNPC::stopAnimTimer(int id) { - getGameManager()->stopTimer(id); -} - void CTrueTalkNPC::setView(CViewItem *view) { CTrueTalkManager *talkManager = getGameManager()->getTalkManager(); if (talkManager) diff --git a/engines/titanic/npcs/true_talk_npc.h b/engines/titanic/npcs/true_talk_npc.h index 0319f7e059..5254eaf9b7 100644 --- a/engines/titanic/npcs/true_talk_npc.h +++ b/engines/titanic/npcs/true_talk_npc.h @@ -31,8 +31,12 @@ namespace Titanic { enum NpcFlag { NPCFLAG_SPEAKING = 1, NPCFLAG_2 = 2, NPCFLAG_4 = 4, NPCFLAG_8 = 8, - NPCFLAG_10000 = 0x10000, NPCFLAG_20000 = 0x20000, NPCFLAG_40000 = 0x40000, - NPCFLAG_80000 = 0x80000, NPCFLAG_100000 = 0x100000 + NPCFLAG_10000 = 0x10000, NPCFLAG_20000 = 0x20000, + NPCFLAG_40000 = 0x40000, NPCFLAG_80000 = 0x80000, + NPCFLAG_100000 = 0x100000, NPCFLAG_200000 = 0x200000, + NPCFLAG_400000 = 0x400000, NPCFLAG_800000 = 0x800000, + NPCFLAG_1000000 = 0x1000000, NPCFLAG_2000000 = 0x2000000, + NPCFLAG_4000000 = 0x4000000, NPCFLAG_8000000 = 0x8000000 }; class CViewItem; @@ -58,20 +62,11 @@ protected: int _fieldF4; int _fieldF8; int _speechTimerId; - int _field100; int _field104; protected: void processInput(CTextInputMsg *msg, CViewItem *view); - - /** - * Stop an animation timer - */ - void stopAnimTimer(int id); - - /** - * Perform an action - */ - void performAction(bool startTalking, CViewItem *view); +public: + int _field100; public: CLASSDEF; CTrueTalkNPC(); @@ -95,6 +90,11 @@ public: * Start the talker in the given view */ void startTalker(CViewItem *view); + + /** + * Perform an action + */ + void performAction(bool startTalking, CViewItem *view = nullptr); }; } // End of namespace Titanic diff --git a/engines/titanic/pet_control/pet_control.cpp b/engines/titanic/pet_control/pet_control.cpp index b32a7907a4..7ab76ddc1d 100644 --- a/engines/titanic/pet_control/pet_control.cpp +++ b/engines/titanic/pet_control/pet_control.cpp @@ -152,7 +152,7 @@ void CPetControl::postLoad() { if (!_activeNPCName.empty() && root) _activeNPC = root->findByName(_activeNPCName); if (!_remoteTargetName.empty() && root) - _remoteTarget = static_cast<CGameObject *>(root->findByName(_remoteTargetName)); + _remoteTarget = dynamic_cast<CGameObject *>(root->findByName(_remoteTargetName)); setArea(_currentArea); loaded(); @@ -194,6 +194,10 @@ void CPetControl::setActiveNPC(CTrueTalkNPC *npc) { } } +void CPetControl::setActiveNPC(const CString &name) { + _conversations.setActiveNPC(name); +} + void CPetControl::refreshNPC() { _conversations.setNPC(_activeNPCName); } @@ -251,7 +255,7 @@ CRoomItem *CPetControl::getHiddenRoom() { CGameObject *CPetControl::getHiddenObject(const CString &name) { CRoomItem *room = getHiddenRoom(); - return room ? static_cast<CGameObject *>(findUnder(room, name)) : nullptr; + return room ? dynamic_cast<CGameObject *>(findUnder(room, name)) : nullptr; } bool CPetControl::containsPt(const Common::Point &pt) const { @@ -355,7 +359,8 @@ bool CPetControl::VirtualKeyCharMsg(CVirtualKeyCharMsg *msg) { } bool CPetControl::TimerMsg(CTimerMsg *msg) { - warning("TODO: CPetControl::CTimerMsg"); + if (_timers[msg->_actionVal]._target) + _timers[msg->_actionVal]._target->timerExpired(msg->_actionVal); return true; } @@ -376,21 +381,21 @@ void CPetControl::displayMessage(const CString &msg) const { } CGameObject *CPetControl::getFirstObject() const { - return static_cast<CGameObject *>(getFirstChild()); + return dynamic_cast<CGameObject *>(getFirstChild()); } CGameObject *CPetControl::getNextObject(CGameObject *prior) const { if (!prior || prior->getParent() != this) return nullptr; - return static_cast<CGameObject *>(prior->getNextSibling()); + return dynamic_cast<CGameObject *>(prior->getNextSibling()); } void CPetControl::addToInventory(CGameObject *item) { item->detach(); if (item->getName() == "CarryParcel") { - CCarry *child = static_cast<CCarry *>(getLastChild()); + CCarry *child = dynamic_cast<CCarry *>(getLastChild()); if (child) child->detach(); @@ -541,7 +546,7 @@ bool CPetControl::isBotInView(const CString &name) const { // Iterate to find NPC for (CTreeItem *child = view->getFirstChild(); child; child = child->scan(view)) { - CGameObject *gameObject = static_cast<CGameObject *>(child); + CGameObject *gameObject = dynamic_cast<CGameObject *>(child); if (gameObject) { if (!gameObject->getName().compareToIgnoreCase(name)) return true; @@ -609,7 +614,7 @@ bool CPetControl::isDoorOrBellbotPresent() const { for (CTreeItem *treeItem = view->getFirstChild(); treeItem; treeItem = treeItem->scan(view)) { CString name = treeItem->getName(); - if (static_cast<CGameObject *>(treeItem) && + if (dynamic_cast<CGameObject *>(treeItem) && (name.contains("Doorbot") || name.contains("BellBot"))) return true; } @@ -638,7 +643,7 @@ void CPetControl::setTimerPersisent(int id, bool flag) { CGameObject *CPetControl::findBot(const CString &name, CTreeItem *root) { for (CTreeItem *item = root; item; item = item->scan(root)) { if (!item->getName().compareToIgnoreCase(name)) { - CGameObject *obj = static_cast<CGameObject *>(item); + CGameObject *obj = dynamic_cast<CGameObject *>(item); if (obj) return obj; } @@ -660,6 +665,10 @@ void CPetControl::convResetDials(int flag) { _conversations.resetDials(_activeNPCName); } +void CPetControl::resetDials0() { + _conversations.resetDials0(); +} + int CPetControl::getMailDest(const CRoomFlags &roomFlags) const { if (!roomFlags.isSuccUBusRoomFlags()) return roomFlags.getPassengerClassNum(); diff --git a/engines/titanic/pet_control/pet_control.h b/engines/titanic/pet_control/pet_control.h index a86d110458..439a94e2d3 100644 --- a/engines/titanic/pet_control/pet_control.h +++ b/engines/titanic/pet_control/pet_control.h @@ -358,9 +358,7 @@ public: /** * Sets the active NPC */ - void setActiveNPC(const CString &name) { - _conversations.setActiveNPC(name); - } + void setActiveNPC(const CString &name); /** * Sets the actie NPC @@ -387,7 +385,7 @@ public: /** * Resets the conversation dials back to 0 position */ - void resetDials0() { _conversations.resetDials0(); } + void resetDials0(); /** * Resets the dial display in the conversation tab to reflect new values diff --git a/engines/titanic/pet_control/pet_conversations.h b/engines/titanic/pet_control/pet_conversations.h index 9e8b093d62..3333bdc523 100644 --- a/engines/titanic/pet_control/pet_conversations.h +++ b/engines/titanic/pet_control/pet_conversations.h @@ -100,16 +100,6 @@ private: void summonBot(const CString &name); /** - * Starts the NPC timer - */ - void startNPCTimer(); - - /** - * Stops the NPC timer - */ - void stopNPCTimer(); - - /** * Get the TrueTalk script associated with a given NPC */ TTnpcScript *getNPCScript(const CString &name) const; @@ -260,6 +250,16 @@ public: * Adds a line to the log */ void addLine(const CString &line); + + /** + * Starts the NPC timer + */ + void startNPCTimer(); + + /** + * Stops the NPC timer + */ + void stopNPCTimer(); }; } // End of namespace Titanic diff --git a/engines/titanic/pet_control/pet_drag_chev.cpp b/engines/titanic/pet_control/pet_drag_chev.cpp index d437d43799..7816570a23 100644 --- a/engines/titanic/pet_control/pet_drag_chev.cpp +++ b/engines/titanic/pet_control/pet_drag_chev.cpp @@ -55,7 +55,7 @@ bool CPetDragChev::MouseDragMoveMsg(CMouseDragMoveMsg *msg) { bool CPetDragChev::MouseDragEndMsg(CMouseDragEndMsg *msg) { if (msg->_dropTarget) { - CSuccUBus *succubus = static_cast<CSuccUBus *>(msg->_dropTarget); + CSuccUBus *succubus = dynamic_cast<CSuccUBus *>(msg->_dropTarget); if (succubus) { CSetChevRoomBits chevMsg(_id); diff --git a/engines/titanic/pet_control/pet_inventory_glyphs.cpp b/engines/titanic/pet_control/pet_inventory_glyphs.cpp index ae306649a2..783a8a9717 100644 --- a/engines/titanic/pet_control/pet_inventory_glyphs.cpp +++ b/engines/titanic/pet_control/pet_inventory_glyphs.cpp @@ -165,7 +165,7 @@ void CPetInventoryGlyph::getTooltip(CPetText *text) { bool CPetInventoryGlyph::doAction(CGlyphAction *action) { CInventoryGlyphAction *invAction = static_cast<CInventoryGlyphAction *>(action); - CPetInventoryGlyphs *owner = static_cast<CPetInventoryGlyphs *>(_owner); + CPetInventoryGlyphs *owner = dynamic_cast<CPetInventoryGlyphs *>(_owner); if (!invAction) return false; @@ -203,8 +203,8 @@ void CPetInventoryGlyph::setItem(CGameObject *item, int val) { if (_owner && item) { int v1 = populateItem(item, val); - _background = static_cast<CPetInventoryGlyphs *>(_owner)->getBackground(v1); - _image = static_cast<CPetInventory *>(getPetSection())->getImage(v1); + _background = dynamic_cast<CPetInventoryGlyphs *>(_owner)->getBackground(v1); + _image = dynamic_cast<CPetInventory *>(getPetSection())->getImage(v1); } } @@ -293,7 +293,7 @@ int CPetInventoryGlyph::subMode(CGameObject *item, int val) { void CPetInventoryGlyph::startBackgroundMovie() { if (_owner) { - CPetInventory *section = static_cast<CPetInventory *>(_owner->getOwner()); + CPetInventory *section = dynamic_cast<CPetInventory *>(_owner->getOwner()); if (section) section->playMovie(_background, 1); } @@ -301,7 +301,7 @@ void CPetInventoryGlyph::startBackgroundMovie() { void CPetInventoryGlyph::startForegroundMovie() { if (_owner) { - CPetInventory *section = static_cast<CPetInventory *>(_owner->getOwner()); + CPetInventory *section = dynamic_cast<CPetInventory *>(_owner->getOwner()); if (section) section->playMovie(_image, 1); } @@ -309,7 +309,7 @@ void CPetInventoryGlyph::startForegroundMovie() { void CPetInventoryGlyph::stopMovie() { if (_owner) { - CPetInventory *section = static_cast<CPetInventory *>(_owner->getOwner()); + CPetInventory *section = dynamic_cast<CPetInventory *>(_owner->getOwner()); if (section) section->playMovie(nullptr, 1); } diff --git a/engines/titanic/pet_control/pet_load_save.h b/engines/titanic/pet_control/pet_load_save.h index dd1c907ef1..26ddec0ff9 100644 --- a/engines/titanic/pet_control/pet_load_save.h +++ b/engines/titanic/pet_control/pet_load_save.h @@ -38,11 +38,6 @@ private: Rect getSlotBounds(int index); /** - * Highlight one of the slots - */ - void highlightSlot(int index); - - /** * Called when savegame slot highlight changes or the view is reset */ void highlightChange(); @@ -67,6 +62,11 @@ protected: * Reset the slot names list */ void resetSlots(); + + /** + * Highlight one of the slots + */ + void highlightSlot(int index); public: /** * Setup the glyph diff --git a/engines/titanic/pet_control/pet_remote_glyphs.cpp b/engines/titanic/pet_control/pet_remote_glyphs.cpp index 6b7c8cb4ae..35a7ab39ac 100644 --- a/engines/titanic/pet_control/pet_remote_glyphs.cpp +++ b/engines/titanic/pet_control/pet_remote_glyphs.cpp @@ -29,7 +29,7 @@ namespace Titanic { CPetRemote *CPetRemoteGlyphs::getOwner() const { - return static_cast<CPetRemote *>(_owner); + return dynamic_cast<CPetRemote *>(_owner); } void CPetRemoteGlyphs::generateMessage(RemoteMessage msgNum, const CString &name, int num) { @@ -44,11 +44,11 @@ void CPetRemoteGlyph::setDefaults(const CString &name, CPetControl *petControl) } CPetRemoteGlyphs *CPetRemoteGlyph::getOwner() const { - return static_cast<CPetRemoteGlyphs *>(_owner); + return dynamic_cast<CPetRemoteGlyphs *>(_owner); } CPetGfxElement *CPetRemoteGlyph::getElement(uint id) const { - CPetRemote *remote = static_cast<CPetRemote *>(_owner->getOwner()); + CPetRemote *remote = dynamic_cast<CPetRemote *>(_owner->getOwner()); return remote->getElement(id); } diff --git a/engines/titanic/pet_control/pet_rooms.cpp b/engines/titanic/pet_control/pet_rooms.cpp index 2415c96966..2ec66b08e2 100644 --- a/engines/titanic/pet_control/pet_rooms.cpp +++ b/engines/titanic/pet_control/pet_rooms.cpp @@ -304,7 +304,7 @@ CPetRoomsGlyph *CPetRooms::addRoom(uint roomFlags, bool highlight_) { // Do a preliminary scan of the glyph list for any glyph that is // no longer valid, and thus can be removed for (CPetRoomsGlyphs::iterator i = _glyphs.begin(); i != _glyphs.end(); ++i) { - CPetRoomsGlyph *glyph = static_cast<CPetRoomsGlyph *>(*i); + CPetRoomsGlyph *glyph = dynamic_cast<CPetRoomsGlyph *>(*i); if (!glyph->isAssigned()) { _glyphs.erase(i); break; @@ -340,7 +340,7 @@ bool CPetRooms::changeLocationClass(int newClassNum) { bool CPetRooms::hasRoomFlags(uint roomFlags) const { for (CPetRoomsGlyphs::const_iterator i = _glyphs.begin(); i != _glyphs.end(); ++i) { - const CPetRoomsGlyph *glyph = static_cast<const CPetRoomsGlyph *>(*i); + const CPetRoomsGlyph *glyph = dynamic_cast<const CPetRoomsGlyph *>(*i); if (glyph->isAssigned() && glyph->getRoomFlags() == roomFlags) return true; } diff --git a/engines/titanic/pet_control/pet_rooms_glyphs.cpp b/engines/titanic/pet_control/pet_rooms_glyphs.cpp index d9e19b1f67..d7ac634f5d 100644 --- a/engines/titanic/pet_control/pet_rooms_glyphs.cpp +++ b/engines/titanic/pet_control/pet_rooms_glyphs.cpp @@ -141,7 +141,7 @@ bool CPetRoomsGlyph::dragGlyph(const Point &topLeft, CMouseDragStartMsg *msg) { void CPetRoomsGlyph::getTooltip(CPetText *text) { CRoomFlags roomFlags(_roomFlags); - CPetRooms *owner = static_cast<CPetRooms *>(getPetSection()); + CPetRooms *owner = dynamic_cast<CPetRooms *>(getPetSection()); CString msg; if (isCurrentlyAssigned()) { @@ -172,7 +172,7 @@ void CPetRoomsGlyph::saveGlyph(SimpleFile *file, int indent) { } bool CPetRoomsGlyph::proc33(CPetGlyph *glyph) { - CPetRoomsGlyph *roomGlyph = static_cast<CPetRoomsGlyph *>(glyph); + CPetRoomsGlyph *roomGlyph = dynamic_cast<CPetRoomsGlyph *>(glyph); return CPetGlyph::proc33(glyph) && _roomFlags == roomGlyph->_roomFlags; } @@ -236,7 +236,7 @@ void CPetRoomsGlyphs::saveGlyphs(SimpleFile *file, int indent) { CPetRoomsGlyph *CPetRoomsGlyphs::findAssignedRoom() const { for (const_iterator i = begin(); i != end(); ++i) { - CPetRoomsGlyph *glyph = static_cast<CPetRoomsGlyph *>(*i); + CPetRoomsGlyph *glyph = dynamic_cast<CPetRoomsGlyph *>(*i); if (glyph->isCurrentlyAssigned()) return glyph; } @@ -246,7 +246,7 @@ CPetRoomsGlyph *CPetRoomsGlyphs::findAssignedRoom() const { CPetRoomsGlyph *CPetRoomsGlyphs::findGlyphByFlags(uint flags) const { for (const_iterator i = begin(); i != end(); ++i) { - CPetRoomsGlyph *glyph = static_cast<CPetRoomsGlyph *>(*i); + CPetRoomsGlyph *glyph = dynamic_cast<CPetRoomsGlyph *>(*i); if (glyph->getRoomFlags() == flags) return glyph; } diff --git a/engines/titanic/pet_control/pet_save.cpp b/engines/titanic/pet_control/pet_save.cpp index b5e16736bc..9305759117 100644 --- a/engines/titanic/pet_control/pet_save.cpp +++ b/engines/titanic/pet_control/pet_save.cpp @@ -22,6 +22,7 @@ #include "titanic/pet_control/pet_save.h" #include "titanic/pet_control/pet_control.h" +#include "titanic/core/project_item.h" namespace Titanic { @@ -58,15 +59,28 @@ void CPetSave::getTooltip(CPetText *text) { } void CPetSave::highlightSave(int index) { - warning("TODO: CPetSave::highlightSave"); + if (index >= 0) + _slotNames[index].showCursor(-2); } void CPetSave::unhighlightSave(int index) { - warning("TODO: CPetSave::unhighlightSave"); + if (index >= 0) + _slotNames[index].hideCursor(); } void CPetSave::execute() { - warning("TODO: CPetSave::execute"); + CPetControl *pet = getPetControl(); + if (_savegameSlotNum >= 0) { + highlightSlot(-1); + CProjectItem *project = pet ? pet->getRoot() : nullptr; + + if (project) { + project->saveGame(_savegameSlotNum, _slotNames[_savegameSlotNum].getText()); + pet->displayMessage(""); + } + } else if (pet) { + pet->displayMessage("You must select a game to save first."); + } } } // End of namespace Titanic diff --git a/engines/titanic/room_flags.cpp b/engines/titanic/room_flags.cpp index 4b3aeb424e..9be8ea3d33 100644 --- a/engines/titanic/room_flags.cpp +++ b/engines/titanic/room_flags.cpp @@ -339,12 +339,12 @@ bool CRoomFlags::getBit0() const { uint CRoomFlags::getSpecialRoomFlags(const CString &roomName) { for (int idx = 0; idx < SUCCUBUS_ROOMS_SIZE; ++idx) { - if (!roomName.compareTo(SUCCUBUS_ROOMS[idx]._roomName)) + if (roomName == SUCCUBUS_ROOMS[idx]._roomName) return SUCCUBUS_ROOMS[idx]._roomFlags; } for (int idx = 0; idx < TRANSPORT_ROOMS_SIZE; ++idx) { - if (!roomName.compareTo(TRANSPORT_ROOMS[idx]._roomName)) + if (roomName == TRANSPORT_ROOMS[idx]._roomName) return TRANSPORT_ROOMS[idx]._roomFlags; } @@ -353,7 +353,7 @@ uint CRoomFlags::getSpecialRoomFlags(const CString &roomName) { uint CRoomFlags::getSuccUBusNum(const CString &roomName) const { for (int idx = 0; idx < SUCCUBUS_ROOMS_SIZE; ++idx) { - if (!roomName.compareTo(SUCCUBUS_ROOMS[idx]._roomName)) + if (roomName == SUCCUBUS_ROOMS[idx]._roomName) return SUCCUBUS_ROOMS[idx]._succubusNum; } diff --git a/engines/titanic/sound/auto_sound_player.cpp b/engines/titanic/sound/auto_sound_player.cpp index 1fb67858da..8267d65037 100644 --- a/engines/titanic/sound/auto_sound_player.cpp +++ b/engines/titanic/sound/auto_sound_player.cpp @@ -75,7 +75,7 @@ bool CAutoSoundPlayer::TurnOn(CTurnOn *msg) { prox._fieldC = _fieldD0; prox._repeated = _repeated; if (_fieldE8) - prox._field28 = 2; + prox._positioningMode = POSMODE_VECTOR; prox._channelVolume = (_startSeconds == -1) ? _volume : 0; _soundHandle = playSound(_filename, prox); diff --git a/engines/titanic/sound/music_player.cpp b/engines/titanic/sound/music_player.cpp index fb5a596875..a1aaf8ff8b 100644 --- a/engines/titanic/sound/music_player.cpp +++ b/engines/titanic/sound/music_player.cpp @@ -39,7 +39,7 @@ void CMusicPlayer::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_isActive, indent); file->writeQuotedLine(_stopTarget, indent); - file->writeNumberLine(_fieldCC, indent); + file->writeNumberLine(_stopWaves, indent); file->writeNumberLine(_musicId, indent); CGameObject::save(file, indent); @@ -49,7 +49,7 @@ void CMusicPlayer::load(SimpleFile *file) { file->readNumber(); _isActive = file->readNumber(); _stopTarget = file->readString(); - _fieldCC = file->readNumber(); + _stopWaves = file->readNumber(); _musicId = file->readNumber(); CGameObject::load(file); @@ -97,7 +97,7 @@ bool CMusicPlayer::StopMusicMsg(CStopMusicMsg *msg) { } bool CMusicPlayer::FrameMsg(CFrameMsg *msg) { - if (_isActive && !CMusicRoom::_musicHandler->isBusy()) { + if (_isActive && !CMusicRoom::_musicHandler->poll()) { getMusicRoom()->stopMusic(); _isActive = false; @@ -120,11 +120,11 @@ bool CMusicPlayer::LeaveRoomMsg(CLeaveRoomMsg *msg) { bool CMusicPlayer::CreateMusicPlayerMsg(CCreateMusicPlayerMsg *msg) { if (CMusicRoom::_musicHandler) { - CMusicRoom::_musicHandler->set124(_fieldCC); + CMusicRoom::_musicHandler->setStopWaves(_stopWaves); return true; } - CMusicHandler *musicHandler = getMusicRoom()->createMusicHandler(); + CMusicRoomHandler *musicHandler = getMusicRoom()->createMusicHandler(); CMusicWave *wave; if (musicHandler) { @@ -156,7 +156,7 @@ bool CMusicPlayer::CreateMusicPlayerMsg(CCreateMusicPlayerMsg *msg) { wave->load(5, "z#505.wav", 53); wave->load(6, "z#501.wav", 58); - CMusicRoom::_musicHandler->set124(_fieldCC); + CMusicRoom::_musicHandler->setStopWaves(_stopWaves); } return true; diff --git a/engines/titanic/sound/music_player.h b/engines/titanic/sound/music_player.h index 3ed1bffdbd..7b82d4da00 100644 --- a/engines/titanic/sound/music_player.h +++ b/engines/titanic/sound/music_player.h @@ -41,12 +41,12 @@ class CMusicPlayer : public CGameObject { protected: bool _isActive; CString _stopTarget; - int _fieldCC; + bool _stopWaves; int _musicId; public: CLASSDEF; CMusicPlayer() : CGameObject(), - _isActive(false), _fieldCC(0), _musicId(100) {} + _isActive(false), _stopWaves(false), _musicId(100) {} /** * Save the data for the class to file diff --git a/engines/titanic/sound/music_room.cpp b/engines/titanic/sound/music_room.cpp index 06cf866811..9586f55c58 100644 --- a/engines/titanic/sound/music_room.cpp +++ b/engines/titanic/sound/music_room.cpp @@ -24,27 +24,26 @@ #include "titanic/sound/music_room.h" #include "titanic/sound/sound.h" #include "titanic/game_manager.h" -#include "titanic/titanic.h" namespace Titanic { -CMusicHandler *CMusicRoom::_musicHandler; +CMusicRoomHandler *CMusicRoom::_musicHandler; CMusicRoom::CMusicRoom(CGameManager *gameManager) : _gameManager(gameManager) { _sound = &_gameManager->_sound; - _items.resize(4); + _controls.resize(4); } CMusicRoom::~CMusicRoom() { destroyMusicHandler(); } -CMusicHandler *CMusicRoom::createMusicHandler() { +CMusicRoomHandler *CMusicRoom::createMusicHandler() { if (_musicHandler) destroyMusicHandler(); - _musicHandler = new CMusicHandler(_gameManager->_project, &_sound->_soundManager); + _musicHandler = new CMusicRoomHandler(_gameManager->_project, &_sound->_soundManager); return _musicHandler; } @@ -53,12 +52,45 @@ void CMusicRoom::destroyMusicHandler() { _musicHandler = nullptr; } -void CMusicRoom::startMusic(int musicId) { - // TODO +void CMusicRoom::startMusic(int volume) { + if (_musicHandler) { + _musicHandler->setSpeedControl2(BELLS, 0); + _musicHandler->setSpeedControl2(SNAKE, 1); + _musicHandler->setSpeedControl2(PIANO, -1); + _musicHandler->setSpeedControl2(BASS, -2); + + _musicHandler->setPitchControl2(BELLS, 1); + _musicHandler->setPitchControl2(SNAKE, 2); + _musicHandler->setPitchControl2(PIANO, 0); + _musicHandler->setPitchControl2(BELLS, 1); + + _musicHandler->setInversionControl2(BELLS, 1); + _musicHandler->setInversionControl2(SNAKE, 0); + _musicHandler->setInversionControl2(PIANO, 1); + _musicHandler->setInversionControl2(BASS, 0); + + _musicHandler->setDirectionControl2(BELLS, 0); + _musicHandler->setDirectionControl2(SNAKE, 0); + _musicHandler->setDirectionControl2(PIANO, 1); + _musicHandler->setDirectionControl2(BASS, 1); + + for (MusicControlArea idx = BELLS; idx <= BASS; + idx = (MusicControlArea)((int)idx + 1)) { + Controls &controls = _controls[idx]; + _musicHandler->setSpeedControl(idx, controls._speedControl); + _musicHandler->setPitchControl(idx, controls._pitchControl); + _musicHandler->setDirectionControl(idx, controls._directionControl); + _musicHandler->setInversionControl(idx, controls._inversionControl); + _musicHandler->setMuteControl(idx, controls._muteControl); + } + + _musicHandler->createWaveFile(volume); + } } void CMusicRoom::stopMusic() { - // TODO + if (_musicHandler) + _musicHandler->stop(); } } // End of namespace Titanic diff --git a/engines/titanic/sound/music_room.h b/engines/titanic/sound/music_room.h index 15363ef392..4b584a0dd4 100644 --- a/engines/titanic/sound/music_room.h +++ b/engines/titanic/sound/music_room.h @@ -24,7 +24,7 @@ #define TITANIC_MUSIC_ROOM_H #include "common/array.h" -#include "titanic/sound/music_handler.h" +#include "titanic/sound/music_room_handler.h" namespace Titanic { @@ -32,19 +32,20 @@ class CGameManager; class CSound; class CMusicRoom { - struct Entry { - uint _val1; - uint _val2; - uint _val3; - uint _val4; - uint _val5; + struct Controls { + int _speedControl; + int _pitchControl; + int _directionControl; + int _inversionControl; + int _muteControl; - Entry() : _val1(0), _val2(0), _val3(0), _val4(0), _val5(0) {} + Controls() : _speedControl(0), _pitchControl(0), _directionControl(0), + _inversionControl(0), _muteControl(0) {} }; private: - Common::Array<Entry> _items; + Common::Array<Controls> _controls; public: - static CMusicHandler *_musicHandler; + static CMusicRoomHandler *_musicHandler; public: CGameManager *_gameManager; CSound *_sound; @@ -55,23 +56,23 @@ public: /** * Creates a music handler */ - CMusicHandler *createMusicHandler(); + CMusicRoomHandler *createMusicHandler(); /** * Destroys and currently active music handler */ void destroyMusicHandler(); - void setItem1(int index, int val) { _items[index]._val1 = val; } - void setItem2(int index, int val) { _items[index]._val2 = val; } - void setItem3(int index, int val) { _items[index]._val3 = val; } - void setItem4(int index, int val) { _items[index]._val4 = val; } - void setItem5(int index, int val) { _items[index]._val5 = val; } + void setSpeedControl(MusicControlArea index, int val) { _controls[index]._speedControl = val; } + void setPitchControl(MusicControlArea index, int val) { _controls[index]._pitchControl = val; } + void setDirectionControl(MusicControlArea index, int val) { _controls[index]._directionControl = val; } + void setInversionControl(MusicControlArea index, int val) { _controls[index]._inversionControl = val; } + void setMuteControl(MusicControlArea index, int val) { _controls[index]._muteControl = val; } /** * Start playing a given music number */ - void startMusic(int musicId); + void startMusic(int volume = 100); /** * Stop playing music diff --git a/engines/titanic/sound/music_room_handler.cpp b/engines/titanic/sound/music_room_handler.cpp new file mode 100644 index 0000000000..ca37485eab --- /dev/null +++ b/engines/titanic/sound/music_room_handler.cpp @@ -0,0 +1,138 @@ +/* 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 "titanic/sound/music_room_handler.h" +#include "titanic/sound/sound_manager.h" +#include "titanic/core/project_item.h" + +namespace Titanic { + +CMusicRoomHandler::CMusicRoomHandler(CProjectItem *project, CSoundManager *soundManager) : + _project(project), _soundManager(soundManager), _stopWaves(false), + _soundHandle(-1), _waveFile(nullptr), _soundVolume(100), _ticks(0), + _field108(0) { + Common::fill(&_musicWaves[0], &_musicWaves[4], (CMusicWave *)nullptr); +} + +CMusicRoomHandler::~CMusicRoomHandler() { + stop(); + for (int idx = 0; idx < 4; ++idx) + delete _musicWaves[idx]; +} + +CMusicWave *CMusicRoomHandler::createMusicWave(int waveIndex, int count) { + switch (waveIndex) { + case 0: + _musicWaves[waveIndex] = new CMusicWave(_project, _soundManager, 2); + break; + case 1: + _musicWaves[waveIndex] = new CMusicWave(_project, _soundManager, 3); + break; + case 2: + _musicWaves[waveIndex] = new CMusicWave(_project, _soundManager, 0); + break; + case 3: + _musicWaves[waveIndex] = new CMusicWave(_project, _soundManager, 1); + break; + default: + return nullptr; + } + + _musicWaves[waveIndex]->setSize(count); + return _musicWaves[waveIndex]; +} + +void CMusicRoomHandler::createWaveFile(int musicVolume) { + _soundVolume = musicVolume; +// _waveFile = _soundManager->loadMusic() +} + +bool CMusicRoomHandler::poll() { + // TODO + return false; +} + +void CMusicRoomHandler::stop() { + if (_waveFile) { + _soundManager->stopSound(_soundHandle); + delete _waveFile; + _waveFile = nullptr; + _soundHandle = -1; + } + + for (int idx = 0; idx < 4; ++idx) { + if (_stopWaves && _musicWaves[idx]) + _musicWaves[idx]->stop(); + } +} + +bool CMusicRoomHandler::checkSound(int index) const { + // TODO + return false; +} + +void CMusicRoomHandler::setSpeedControl2(MusicControlArea area, int value) { + if (area >= 0 && area <= 3 && value >= -2 && value <= 2) + _array2[area]._speedControl = value; +} + +void CMusicRoomHandler::setPitchControl2(MusicControlArea area, int value) { + if (area >= 0 && area <= 3 && value >= -2 && value <= 2) + _array2[area]._pitchControl = value * 3; +} + +void CMusicRoomHandler::setInversionControl2(MusicControlArea area, int value) { + if (area >= 0 && area <= 3 && value >= -2 && value <= 2) + _array2[area]._inversionControl = value; +} + +void CMusicRoomHandler::setDirectionControl2(MusicControlArea area, int value) { + if (area >= 0 && area <= 3 && value >= -2 && value <= 2) + _array2[area]._directionControl = value; +} + +void CMusicRoomHandler::setPitchControl(MusicControlArea area, int value) { + if (area >= 0 && area <= 3 && value >= -2 && value <= 2) + _array1[area]._pitchControl = value; +} + +void CMusicRoomHandler::setSpeedControl(MusicControlArea area, int value) { + if (area >= 0 && area <= 3 && value >= -2 && value <= 2) + _array1[area]._speedControl = value; +} + +void CMusicRoomHandler::setDirectionControl(MusicControlArea area, int value) { + if (area >= 0 && area <= 3 && value >= -2 && value <= 2) + _array1[area]._directionControl = value; +} + +void CMusicRoomHandler::setInversionControl(MusicControlArea area, int value) { + if (area >= 0 && area <= 3 && value >= -2 && value <= 2) + _array1[area]._inversionControl = value; +} + +void CMusicRoomHandler::setMuteControl(MusicControlArea area, int value) { + if (area >= 0 && area <= 3 && value >= -2 && value <= 2) + _array1[area]._muteControl = value; +} + +} // End of namespace Titanic diff --git a/engines/titanic/sound/music_room_handler.h b/engines/titanic/sound/music_room_handler.h new file mode 100644 index 0000000000..61b332dc7a --- /dev/null +++ b/engines/titanic/sound/music_room_handler.h @@ -0,0 +1,125 @@ +/* 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. + * + */ + +#ifndef TITANIC_MUSIC_ROOM_HANDLER_H +#define TITANIC_MUSIC_ROOM_HANDLER_H + +#include "titanic/sound/music_wave.h" +#include "titanic/sound/wave_file.h" + +namespace Titanic { + +class CProjectItem; +class CSoundManager; + +enum MusicControlArea { BELLS = 0, SNAKE = 1, PIANO = 2, BASS = 3 }; + +class CMusicRoomHandler { + struct Controls { + int _pitchControl; + int _speedControl; + int _directionControl; + int _inversionControl; + int _muteControl; + Controls() : _pitchControl(0), _speedControl(0), _directionControl(0), + _inversionControl(0), _muteControl(0) {} + }; + struct Array5Entry { + int _v1; + int _v2; + Array5Entry() : _v1(0), _v2(0) {} + }; +private: + CProjectItem *_project; + CSoundManager *_soundManager; + CMusicWave *_musicWaves[4]; + Controls _array1[4]; + Controls _array2[4]; + Array5Entry _array5[4]; + bool _stopWaves; + CWaveFile *_waveFile; + int _soundHandle; + int _soundVolume; + uint _ticks; + int _field108; +public: + CMusicRoomHandler(CProjectItem *project, CSoundManager *soundManager); + ~CMusicRoomHandler(); + + /** + * Creates a new music wave class instance, and assigns it to a slot + * in the music handler + * @param waveIndex Slot to save new instance in + * @param count Number of files the new instance will contain + */ + CMusicWave *createMusicWave(int waveIndex, int count); + + void createWaveFile(int musicVolume); + + /** + * Handles regular polling the music handler + */ + bool poll(); + + /** + * Flags whether the loaded music waves will be stopped when the + * music handler is stopped + */ + void setStopWaves(bool flag) { _stopWaves = flag; } + + /** + * Stop playing the music + */ + void stop(); + + bool checkSound(int index) const; + + /** + * Set a setting + */ + void setSpeedControl2(MusicControlArea area, int value); + + /** + * Set a setting + */ + void setPitchControl2(MusicControlArea area, int value); + + /** + * Set a setting + */ + void setInversionControl2(MusicControlArea area, int value); + + /** + * Set a setting + */ + void setDirectionControl2(MusicControlArea area, int value); + + void setPitchControl(MusicControlArea area, int value); + void setSpeedControl(MusicControlArea area, int value); + void setDirectionControl(MusicControlArea area, int value); + void setInversionControl(MusicControlArea area, int value); + void setMuteControl(MusicControlArea area, int value); +}; + +} // End of namespace Titanic + +#endif /* TITANIC_MUSIC_ROOM_HANDLER_H */ diff --git a/engines/titanic/sound/music_wave.cpp b/engines/titanic/sound/music_wave.cpp index 348f3bdbd4..6b5b187805 100644 --- a/engines/titanic/sound/music_wave.cpp +++ b/engines/titanic/sound/music_wave.cpp @@ -26,11 +26,29 @@ namespace Titanic { -CMusicWave::CMusicWave(CProjectItem *project, CSoundManager *soundManager, int index) { +CMusicWave::CMusicWave(CProjectItem *project, CSoundManager *soundManager, int index) : + _project(project), _soundManager(soundManager) { +} + +void CMusicWave::setSize(uint count) { + assert(_items.empty()); + _items.resize(count); } void CMusicWave::load(int index, const CString &filename, int v3) { - // TODO + assert(!_items[index]._waveFile); + _items[index]._waveFile = createWaveFile(filename); + _items[index]._value = v3; +} + +CWaveFile *CMusicWave::createWaveFile(const CString &name) { + if (name.empty()) + return nullptr; + return _soundManager->loadSound(name); +} + +void CMusicWave::stop() { + } } // End of namespace Titanic diff --git a/engines/titanic/sound/music_wave.h b/engines/titanic/sound/music_wave.h index d40b2ce74d..b240f4a856 100644 --- a/engines/titanic/sound/music_wave.h +++ b/engines/titanic/sound/music_wave.h @@ -23,19 +23,47 @@ #ifndef TITANIC_MUSIC_WAVE_H #define TITANIC_MUSIC_WAVE_H +#include "common/array.h" #include "titanic/support/string.h" namespace Titanic { class CProjectItem; class CSoundManager; +class CWaveFile; class CMusicWave { + struct CMusicWaveFile { + CWaveFile *_waveFile; + int _value; + CMusicWaveFile() : _waveFile(nullptr), _value(0) {} + }; private: + CProjectItem *_project; + CSoundManager *_soundManager; + Common::Array<CMusicWaveFile> _items; +private: + /** + * Loads the specified wave file, and returns a CWaveFile instance for it + */ + CWaveFile *createWaveFile(const CString &name); public: CMusicWave(CProjectItem *project, CSoundManager *soundManager, int index); + /** + * Sets the maximum number of allowed files that be defined + */ + void setSize(uint count); + + /** + * Loads a new file into the list of available entries + */ void load(int index, const CString &filename, int v3); + + /** + * Stops the music + */ + void stop(); }; } // End of namespace Titanic diff --git a/engines/titanic/sound/proximity.cpp b/engines/titanic/sound/proximity.cpp index 7502eb3ef8..9e70722520 100644 --- a/engines/titanic/sound/proximity.cpp +++ b/engines/titanic/sound/proximity.cpp @@ -27,11 +27,11 @@ namespace Titanic { CProximity::CProximity() : _field4(0), _channelVolume(100), _fieldC(0), _priorSoundHandle(-1), _field14(0), _frequencyMultiplier(0.0), _field1C(1.875), - _repeated(false), _channel(10), _field28(0), _azimuth(0.0), + _repeated(false), _channelMode(10), _positioningMode(POSMODE_NONE), _azimuth(0.0), _range(0.5), _elevation(0), _posX(0.0), _posY(0.0), _posZ(0.0), _hasVelocity(false), _velocityX(0), _velocityY(0), _velocityZ(0), - _field54(0), _field58(0), _field5C(0), _field60(0), _endTalkerFn(nullptr), - _talker(nullptr), _field6C(0) { + _field54(0), _field58(0), _field5C(0), _freeSoundFlag(false), _endTalkerFn(nullptr), + _talker(nullptr), _field6C(0), _soundType(Audio::Mixer::kPlainSoundType) { } } // End of namespace Titanic diff --git a/engines/titanic/sound/proximity.h b/engines/titanic/sound/proximity.h index 7c1f8598e8..41c2268c2f 100644 --- a/engines/titanic/sound/proximity.h +++ b/engines/titanic/sound/proximity.h @@ -23,10 +23,13 @@ #ifndef TITANIC_PROXIMITY_H #define TITANIC_PROXIMITY_H +#include "audio/mixer.h" #include "common/scummsys.h" namespace Titanic { +enum PositioningMode { POSMODE_NONE = 0, POSMODE_POLAR = 1, POSMODE_VECTOR = 2 }; + class TTtalker; typedef void (*CEndTalkerFn)(TTtalker *talker); @@ -41,8 +44,8 @@ public: double _frequencyMultiplier; double _field1C; bool _repeated; - int _channel; - int _field28; + int _channelMode; + PositioningMode _positioningMode; double _azimuth; double _range; double _elevation; @@ -56,10 +59,11 @@ public: int _field54; int _field58; int _field5C; - int _field60; + bool _freeSoundFlag; CEndTalkerFn _endTalkerFn; TTtalker *_talker; int _field6C; + Audio::Mixer::SoundType _soundType; public: CProximity(); }; diff --git a/engines/titanic/sound/qmixer.cpp b/engines/titanic/sound/qmixer.cpp index da707c9527..c095b84e17 100644 --- a/engines/titanic/sound/qmixer.cpp +++ b/engines/titanic/sound/qmixer.cpp @@ -20,6 +20,7 @@ * */ +#include "common/system.h" #include "titanic/sound/qmixer.h" namespace Titanic { @@ -28,7 +29,10 @@ QMixer::QMixer(Audio::Mixer *mixer) : _mixer(mixer) { } bool QMixer::qsWaveMixInitEx(const QMIXCONFIG &config) { - // Not currently implemented in ScummVM + assert(_channels.empty()); + assert(config.iChannels > 0 && config.iChannels < 256); + + _channels.resize(config.iChannels); return true; } @@ -48,6 +52,7 @@ int QMixer::qsWaveMixEnableChannel(int iChannel, uint flags, bool enabled) { void QMixer::qsWaveMixCloseSession() { _mixer->stopAll(); + _channels.clear(); } void QMixer::qsWaveMixFreeWave(Audio::SoundHandle &handle) { @@ -59,11 +64,22 @@ void QMixer::qsWaveMixFlushChannel(int iChannel, uint flags) { } void QMixer::qsWaveMixSetPanRate(int iChannel, uint flags, uint rate) { - // Not currently implemented in ScummVM + ChannelEntry &channel = _channels[iChannel]; + channel._panRate = rate; + channel._volumeChangeStart = channel._volumeChangeEnd = 0; } void QMixer::qsWaveMixSetVolume(int iChannel, uint flags, uint volume) { - // Not currently implemented in ScummVM + ChannelEntry &channel = _channels[iChannel]; + + // QMixer volumes go from 0-32767, but we need to convert to 0-255 for ScummVM + assert(volume <= 32767); + byte newVolume = (volume >= 32700) ? 255 : volume * 255 / 32767; + + channel._volumeStart = newVolume; + channel._volumeEnd = volume * 255 / 100; // Convert from 0-100 (percent) to 0-255 + channel._volumeChangeStart = g_system->getMillis(); + channel._volumeChangeEnd = channel._volumeChangeStart + channel._panRate; } void QMixer::qsWaveMixSetSourcePosition(int iChannel, uint flags, const QSVECTOR &position) { @@ -94,18 +110,98 @@ void QMixer::qsWaveMixSetSourceVelocity(int iChannel, uint flags, const QSVECTOR // Not currently implemented in ScummVM } -int QMixer::qsWaveMixPlayEx(int iChannel, uint flags, CWaveFile *mixWave, int loops, const QMIXPLAYPARAMS ¶ms) { - // Not currently implemented in ScummVM +int QMixer::qsWaveMixPlayEx(int iChannel, uint flags, CWaveFile *waveFile, int loops, const QMIXPLAYPARAMS ¶ms) { + if (iChannel == -1) { + // Find a free channel + for (iChannel = 0; iChannel < (int)_channels.size(); ++iChannel) { + if (_channels[iChannel]._sounds.empty()) + break; + } + assert(iChannel != (int)_channels.size()); + } + + // If the new sound replaces current ones, then clear the channel + ChannelEntry &channel = _channels[iChannel]; + if (flags & QMIX_CLEARQUEUE) { + if (!channel._sounds.empty() && channel._sounds.front()._started) + _mixer->stopHandle(channel._sounds.front()._soundHandle); + + channel._sounds.clear(); + } + + // Add the sound to the channel + channel._sounds.push_back(SoundEntry(waveFile, params.callback, loops, params.dwUser)); + qsWaveMixPump(); + return 0; } bool QMixer::qsWaveMixIsChannelDone(int iChannel) const { - // Not currently implemented in ScummVM - return true; + return _channels[iChannel]._sounds.empty(); } void QMixer::qsWaveMixPump() { - // TODO: Handle checking for done sounds, and calling their end functions -} - -} // End of namespace Titanic z + // Iterate through each of the channels + for (uint iChannel = 0; iChannel < _channels.size(); ++iChannel) { + ChannelEntry &channel = _channels[iChannel]; + + // If there's a transition in sound volume in progress, handle it + if (channel._volumeChangeEnd) { + byte oldVolume = channel._volume; + uint currentTicks = g_system->getMillis(); + + if (currentTicks >= channel._volumeChangeEnd) { + // Reached end of transition period + channel._volume = channel._volumeEnd; + channel._volumeChangeStart = channel._volumeChangeEnd = 0; + } else { + // Transition in progress, so figure out new volume + channel._volume = (int)channel._volumeStart + + ((int)channel._volumeEnd - (int)channel._volumeStart) * + (int)(currentTicks - channel._volumeChangeStart) / (int)channel._panRate; + } + + if (channel._volume != oldVolume && !channel._sounds.empty() + && channel._sounds.front()._started) { + _mixer->setChannelVolume(channel._sounds.front()._soundHandle, channel._volume); + } + } + + // If the playing sound on the channel is finished, then call + // the callback registered for it, and remove it from the list + if (!channel._sounds.empty()) { + SoundEntry &sound = channel._sounds.front(); + if (sound._started && !_mixer->isSoundHandleActive(sound._soundHandle)) { + if (sound._loops == -1 || sound._loops-- > 0) { + // Need to loop the sound again + sound._waveFile->_stream->rewind(); + _mixer->playStream(sound._waveFile->_soundType, + &sound._soundHandle, sound._waveFile->_stream, + -1, channel._volume, 0, DisposeAfterUse::NO); + } else { + // Sound is finished + if (sound._callback) + // Call the callback to signal end + sound._callback(iChannel, sound._waveFile, sound._userData); + + // Remove sound record from channel + channel._sounds.erase(channel._sounds.begin()); + } + } + } + + // If there's an unstarted sound at the front of a channel's + // sound list, then start it playing + if (!channel._sounds.empty()) { + SoundEntry &sound = channel._sounds.front(); + if (!sound._started) { + _mixer->playStream(sound._waveFile->_soundType, + &sound._soundHandle, sound._waveFile->_stream, + -1, channel._volume, 0, DisposeAfterUse::NO); + sound._started = true; + } + } + } +} + +} // End of namespace Titanic diff --git a/engines/titanic/sound/qmixer.h b/engines/titanic/sound/qmixer.h index fa4604cd2e..6a25484c29 100644 --- a/engines/titanic/sound/qmixer.h +++ b/engines/titanic/sound/qmixer.h @@ -152,9 +152,12 @@ struct QMIXPLAYPARAMS { int lEndLoop; int lEnd; const void *lpChannelParams; // initialize with these parameters + // Properties introduced by ScummVM + Audio::Mixer::SoundType _soundType; QMIXPLAYPARAMS() : dwSize(36), lpImage(nullptr), hwndNotify(0), callback(nullptr), - dwUser(nullptr), lStart(0), lStartLoop(0), lEndLoop(0), lEnd(0), lpChannelParams(nullptr) {} + dwUser(nullptr), lStart(0), lStartLoop(0), lEndLoop(0), lEnd(0), + lpChannelParams(nullptr), _soundType(Audio::Mixer::kPlainSoundType) {} }; /** @@ -169,8 +172,38 @@ struct QMIXPLAYPARAMS { * currently ignored, and all sounds play at full volume. */ class QMixer { + struct SoundEntry { + bool _started; + CWaveFile *_waveFile; + Audio::SoundHandle _soundHandle; + LPQMIXDONECALLBACK _callback; + int _loops; + void *_userData; + SoundEntry() : _started(false), _waveFile(nullptr), _callback(nullptr), + _loops(0), _userData(nullptr) {} + + SoundEntry(CWaveFile *waveFile, LPQMIXDONECALLBACK callback, int loops, void *userData) : + _started(false), _waveFile(waveFile), _callback(callback), _loops(loops), _userData(userData) {} + }; + struct ChannelEntry { + // Currently playing and any following queued sounds for the channel + Common::List<SoundEntry> _sounds; + // Current channel volume + byte _volume; + // Current time in milliseconds for paning (volume) changes + uint _panRate; + // Fields used to transition between volume levels + uint _volumeChangeStart; + uint _volumeChangeEnd; + byte _volumeStart; + byte _volumeEnd; + + ChannelEntry() : _volume(0), _panRate(0), _volumeChangeStart(0), + _volumeChangeEnd(0), _volumeStart(0), _volumeEnd(0) {} + }; private: Audio::Mixer *_mixer; + Common::Array<ChannelEntry> _channels; public: QMixer(Audio::Mixer *mixer); virtual ~QMixer() {} diff --git a/engines/titanic/sound/sound.cpp b/engines/titanic/sound/sound.cpp index d14c628a78..6d27d1de49 100644 --- a/engines/titanic/sound/sound.cpp +++ b/engines/titanic/sound/sound.cpp @@ -68,8 +68,18 @@ void CSound::setVolume(uint handle, uint volume, uint seconds) { _soundManager.setVolume(handle, volume, seconds); } -void CSound::fn4(CWaveFile *waveFile, int val) { - // TODO +void CSound::activateSound(CWaveFile *waveFile, bool freeFlag) { + for (CSoundItemList::iterator i = _sounds.begin(); i != _sounds.end(); ++i) { + CSoundItem *sound = *i; + if (sound->_waveFile == waveFile) { + sound->_active = true; + sound->_freeFlag = freeFlag; + + if (!freeFlag && waveFile->size() > 51200) + sound->_freeFlag = true; + break; + } + } } void CSound::stopChannel(int channel) { @@ -77,14 +87,18 @@ void CSound::stopChannel(int channel) { } void CSound::checkSounds() { - for (CSoundItemList::iterator i = _sounds.begin(); i != _sounds.end(); ++i) { + for (CSoundItemList::iterator i = _sounds.begin(); i != _sounds.end(); ) { CSoundItem *soundItem = *i; - if (soundItem->_field24 && soundItem->_field28) { + + if (soundItem->_active && soundItem->_freeFlag) { if (_soundManager.isActive(soundItem->_waveFile)) { - _sounds.remove(soundItem); + i = _sounds.erase(i); delete soundItem; + continue; } } + + ++i; } } @@ -92,7 +106,7 @@ void CSound::removeOldest() { for (CSoundItemList::iterator i = _sounds.reverse_begin(); i != _sounds.end(); --i) { CSoundItem *soundItem = *i; - if (soundItem->_field28 && !_soundManager.isActive(soundItem->_waveFile)) { + if (soundItem->_active && !_soundManager.isActive(soundItem->_waveFile)) { _sounds.remove(soundItem); delete soundItem; break; @@ -145,7 +159,10 @@ int CSound::playSound(const CString &name, CProximity &prox) { return -1; prox._field6C = waveFile->fn1(); - fn4(waveFile, prox._field60); + if (prox._soundType != Audio::Mixer::kPlainSoundType) + waveFile->_soundType = prox._soundType; + + activateSound(waveFile, prox._freeSoundFlag); return _soundManager.playSound(*waveFile, prox); } @@ -192,7 +209,7 @@ int CSound::playSpeech(CDialogueFile *dialogueFile, int speechId, CProximity &pr return -1; prox._field6C = waveFile->fn1(); - fn4(waveFile, prox._field60); + activateSound(waveFile, prox._freeSoundFlag); return _soundManager.playSound(*waveFile, prox); } diff --git a/engines/titanic/sound/sound.h b/engines/titanic/sound/sound.h index b6c77e76b2..de95f9edf1 100644 --- a/engines/titanic/sound/sound.h +++ b/engines/titanic/sound/sound.h @@ -41,15 +41,15 @@ public: CWaveFile *_waveFile; File *_dialogueFileHandle; int _speechId; - int _field24; - int _field28; + bool _freeFlag; + bool _active; public: CSoundItem() : ListItem(), _waveFile(nullptr), _dialogueFileHandle(nullptr), - _speechId(0), _field24(0), _field28(0) {} + _speechId(0), _freeFlag(false), _active(false) {} CSoundItem(const CString &name) : ListItem(), _name(name), _waveFile(nullptr), - _dialogueFileHandle(nullptr), _speechId(0), _field24(0), _field28(0) {} + _dialogueFileHandle(nullptr), _speechId(0), _freeFlag(false), _active(false) {} CSoundItem(File *dialogueFile, int speechId) : ListItem(), _waveFile(nullptr), - _dialogueFileHandle(dialogueFile), _speechId(speechId), _field24(0), _field28(0) {} + _dialogueFileHandle(dialogueFile), _speechId(speechId), _freeFlag(false), _active(false) {} }; class CSoundItemList : public List<CSoundItem> { @@ -123,7 +123,10 @@ public: */ void setVolume(uint handle, uint volume, uint seconds); - void fn4(CWaveFile *waveFile, int val); + /** + * Flags a sound about to be played as activated + */ + void activateSound(CWaveFile *waveFile, bool freeFlag); /** * Stops any sounds attached to a given channel diff --git a/engines/titanic/sound/sound_manager.cpp b/engines/titanic/sound/sound_manager.cpp index a8bd0dfbe9..81ec5bc475 100644 --- a/engines/titanic/sound/sound_manager.cpp +++ b/engines/titanic/sound/sound_manager.cpp @@ -22,6 +22,7 @@ #include "titanic/sound/sound_manager.h" #include "titanic/titanic.h" + namespace Titanic { const uint SAMPLING_RATE = 22050; @@ -35,11 +36,11 @@ CSoundManager::CSoundManager() : _musicPercent(75.0), _speechPercent(75.0), uint CSoundManager::getModeVolume(int mode) { switch (mode) { case -1: - return _masterPercent; + return (uint)_masterPercent; case -2: - return _masterPercent * 30 / 100; + return (uint)(_masterPercent * 30 / 100); case -3: - return _masterPercent * 15 / 100; + return (uint)(_masterPercent * 15 / 100); default: return 0; } @@ -97,7 +98,7 @@ void QSoundManager::Slot::clear() { _ticks = 0; _channel = -1; _handle = 0; - _val3 = 0; + _positioningMode = POSMODE_NONE; } /*------------------------------------------------------------------------*/ @@ -142,9 +143,16 @@ CWaveFile *QSoundManager::loadSpeech(CDialogueFile *dialogueFile, int speechId) return waveFile; } -int QSoundManager::proc5() const { - error("TODO"); - return 0; +CWaveFile *QSoundManager::loadMusic(const CString &name) { + CWaveFile *waveFile = new CWaveFile(); + + // Try to load the specified sound + if (!waveFile->loadMusic(name)) { + delete waveFile; + return nullptr; + } + + return waveFile; } int QSoundManager::playSound(CWaveFile &waveFile, CProximity &prox) { @@ -163,14 +171,14 @@ int QSoundManager::playSound(CWaveFile &waveFile, CProximity &prox) { } } - if (channel >= 0 || (channel = resetChannel(prox._channel)) != -1) { + if (channel >= 0 || (channel = resetChannel(prox._channelMode)) != -1) { return playWave(&waveFile, channel, flags, prox); } return 0; } -void QSoundManager::stopSound(uint handle) { +void QSoundManager::stopSound(int handle) { resetChannel(10); for (uint idx = 0; idx < _slots.size(); ++idx) { @@ -206,7 +214,7 @@ void QSoundManager::stopChannel(int channel) { } } -void QSoundManager::setCanFree(uint handle) { +void QSoundManager::setCanFree(int handle) { for (uint idx = 0; idx < _slots.size(); ++idx) { if (_slots[idx]._handle == handle) _slots[idx]._isTimed = true; @@ -260,10 +268,11 @@ int QSoundManager::resetChannel(int iChannel) { return newChannel; } -void QSoundManager::setVolume(uint handle, uint volume, uint seconds) { +void QSoundManager::setVolume(int handle, uint volume, uint seconds) { for (uint idx = 0; idx < _slots.size(); ++idx) { Slot &slot = _slots[idx]; if (slot._handle == handle) { + assert(slot._channel >= 0); _channelsVolume[slot._channel] = volume; updateVolume(slot._channel, seconds * 1000); @@ -279,7 +288,7 @@ void QSoundManager::setVolume(uint handle, uint volume, uint seconds) { } } -void QSoundManager::setVectorPosition(uint handle, double x, double y, double z, uint panRate) { +void QSoundManager::setVectorPosition(int handle, double x, double y, double z, uint panRate) { for (uint idx = 0; idx < _slots.size(); ++idx) { Slot &slot = _slots[idx]; if (slot._handle == handle) { @@ -290,7 +299,7 @@ void QSoundManager::setVectorPosition(uint handle, double x, double y, double z, } } -void QSoundManager::setPolarPosition(uint handle, double range, double azimuth, double elevation, uint panRate) { +void QSoundManager::setPolarPosition(int handle, double range, double azimuth, double elevation, uint panRate) { for (uint idx = 0; idx < _slots.size(); ++idx) { Slot &slot = _slots[idx]; if (slot._handle == handle) { @@ -302,7 +311,7 @@ void QSoundManager::setPolarPosition(uint handle, double range, double azimuth, } } -bool QSoundManager::isActive(uint handle) const { +bool QSoundManager::isActive(int handle) const { for (uint idx = 0; idx < _slots.size(); ++idx) { if (_slots[idx]._handle == handle) return true; @@ -316,7 +325,7 @@ bool QSoundManager::isActive(const CWaveFile *waveFile) const { } void QSoundManager::waveMixPump() { - + qsWaveMixPump(); } uint QSoundManager::getLatency() const { @@ -347,7 +356,7 @@ void QSoundManager::setListenerPosition(double posX, double posY, double posZ, if (stopSounds) { // Stop any running sounds for (uint idx = 0; idx < _slots.size(); ++idx) { - if (_slots[idx]._val3) + if (_slots[idx]._positioningMode != 0) stopSound(_slots[idx]._handle); } } @@ -368,14 +377,17 @@ int QSoundManager::playWave(CWaveFile *waveFile, int iChannel, uint flags, CProx if (slotIndex == -1) return -1; - switch (prox._field28) { - case 1: + // Set the volume + setChannelVolume(iChannel, prox._channelVolume, prox._channelMode); + + switch (prox._positioningMode) { + case POSMODE_POLAR: qsWaveMixSetPolarPosition(iChannel, 8, QSPOLAR(prox._azimuth, prox._range, prox._elevation)); qsWaveMixEnableChannel(iChannel, QMIX_CHANNEL_ELEVATION, true); qsWaveMixSetDistanceMapping(iChannel, 8, QMIX_DISTANCES(5.0, 3.0, 1.0)); break; - case 2: + case POSMODE_VECTOR: qsWaveMixSetSourcePosition(iChannel, 8, QSVECTOR(prox._posX, prox._posY, prox._posZ)); qsWaveMixEnableChannel(iChannel, QMIX_CHANNEL_ELEVATION, true); qsWaveMixSetDistanceMapping(iChannel, 8, QMIX_DISTANCES(5.0, 3.0, 1.0)); @@ -402,12 +414,12 @@ int QSoundManager::playWave(CWaveFile *waveFile, int iChannel, uint flags, CProx slot._handle = _handleCtr++; slot._channel = iChannel; slot._waveFile = waveFile; - slot._val3 = prox._field28; + slot._positioningMode = prox._positioningMode; return slot._handle; } else { _sounds.flushChannel(waveFile, iChannel); - if (prox._field60) + if (prox._freeSoundFlag) delete waveFile; return 0; } @@ -418,7 +430,7 @@ void QSoundManager::soundFreed(Audio::SoundHandle &handle) { } void QSoundManager::updateVolume(int channel, uint panRate) { - uint volume = _channelsVolume[channel] * 327; + double volume = _channelsVolume[channel] * 327; switch (_channelsMode[channel]) { case 0: @@ -443,7 +455,7 @@ void QSoundManager::updateVolume(int channel, uint panRate) { volume = (_musicPercent * volume) / 100; qsWaveMixSetPanRate(channel, 0, panRate); - qsWaveMixSetVolume(channel, 0, volume); + qsWaveMixSetVolume(channel, 0, (uint)volume); } void QSoundManager::updateVolumes() { diff --git a/engines/titanic/sound/sound_manager.h b/engines/titanic/sound/sound_manager.h index 2c9975ede4..d1afdb4ad4 100644 --- a/engines/titanic/sound/sound_manager.h +++ b/engines/titanic/sound/sound_manager.h @@ -60,7 +60,15 @@ public: */ virtual CWaveFile *loadSpeech(CDialogueFile *dialogueFile, int speechId) { return 0; } - virtual int proc5() const { return 0; } + /** + * Loads a music file + * @param name Name of music resource + * @returns Loaded wave file + * @remarks The original created a streaming audio buffer for the wave file, + * and passed this to the method. For ScummVM, this has been discarded + * in favor of simply passing the filename. + */ + virtual CWaveFile *loadMusic(const CString &name) { return nullptr; } /** * Start playing a previously loaded wave file @@ -70,14 +78,14 @@ public: /** * Stop playing the specified sound */ - virtual void stopSound(uint handle) = 0; + virtual void stopSound(int handle) = 0; /** * Stops a designated range of channels */ virtual void stopChannel(int channel) = 0; - virtual void proc9(uint handle) {} + virtual void proc9(int handle) {} /** * Stops sounds on all playing channels @@ -90,7 +98,7 @@ public: * @param volume New volume * @param seconds Number of seconds to transition to the new volume */ - virtual void setVolume(uint handle, uint volume, uint seconds) = 0; + virtual void setVolume(int handle, uint volume, uint seconds) = 0; /** * Set the position for a sound @@ -100,7 +108,7 @@ public: * @param z z position in metres * @param panRate Rate in milliseconds to transition */ - virtual void setVectorPosition(uint handle, double x, double y, double z, uint panRate) {} + virtual void setVectorPosition(int handle, double x, double y, double z, uint panRate) {} /** * Set the position for a sound @@ -110,12 +118,12 @@ public: * @param elevation Elevation value in degrees * @param panRate Rate in milliseconds to transition */ - virtual void setPolarPosition(uint handle, double range, double azimuth, double elevation, uint panRate) {} + virtual void setPolarPosition(int handle, double range, double azimuth, double elevation, uint panRate) {} /** * Returns true if the given sound is currently active */ - virtual bool isActive(uint handle) const = 0; + virtual bool isActive(int handle) const = 0; /** * Returns true if the given sound is currently active @@ -256,9 +264,10 @@ class QSoundManager : public CSoundManager, public QMixer { uint _ticks; int _channel; int _handle; - uint _val3; + PositioningMode _positioningMode; - Slot() : _waveFile(0), _isTimed(0), _ticks(0), _channel(-1), _handle(0), _val3(0) {} + Slot() : _waveFile(0), _isTimed(0), _ticks(0), _channel(-1), + _handle(0), _positioningMode(POSMODE_NONE) {} void clear(); }; private: @@ -320,7 +329,15 @@ public: */ virtual CWaveFile *loadSpeech(CDialogueFile *dialogueFile, int speechId); - virtual int proc5() const; + /** + * Loads a music file + * @param name Name of music resource + * @returns Loaded wave file + * @remarks The original created a streaming audio buffer for the wave file, + * and passed this to the method. For ScummVM, this has been discarded + * in favor of simply passing the filename. + */ + virtual CWaveFile *loadMusic(const CString &name); /** * Start playing a previously loaded sound resource @@ -330,7 +347,7 @@ public: /** * Stop playing the specified sound */ - virtual void stopSound(uint handle); + virtual void stopSound(int handle); /** * Stops a designated range of channels @@ -340,7 +357,7 @@ public: /** * Flags that a sound can be freed if a timeout is set */ - virtual void setCanFree(uint handle); + virtual void setCanFree(int handle); /** * Stops sounds on all playing channels @@ -353,7 +370,7 @@ public: * @param volume New volume * @param seconds Number of seconds to transition to the new volume */ - virtual void setVolume(uint handle, uint volume, uint seconds); + virtual void setVolume(int handle, uint volume, uint seconds); /** * Set the position for a sound @@ -363,7 +380,7 @@ public: * @param z z position in metres * @param panRate Rate in milliseconds to transition */ - virtual void setVectorPosition(uint handle, double x, double y, double z, uint panRate); + virtual void setVectorPosition(int handle, double x, double y, double z, uint panRate); /** * Set the position for a sound @@ -373,12 +390,12 @@ public: * @param elevation Elevation value in degrees * @param panRate Rate in milliseconds to transition */ - virtual void setPolarPosition(uint handle, double range, double azimuth, double elevation, uint panRate); + virtual void setPolarPosition(int handle, double range, double azimuth, double elevation, uint panRate); /** * Returns true if the given sound is currently active */ - virtual bool isActive(uint handle) const; + virtual bool isActive(int handle) const; /** * Returns true if the given sound is currently active diff --git a/engines/titanic/sound/titania_speech.cpp b/engines/titanic/sound/titania_speech.cpp index a07cc79334..d0ff423342 100644 --- a/engines/titanic/sound/titania_speech.cpp +++ b/engines/titanic/sound/titania_speech.cpp @@ -59,7 +59,7 @@ bool CTitaniaSpeech::ActMsg(CActMsg *msg) { movieSetAudioTiming(true); loadSound("a#12.wav"); sleep(1000); - playMovie(0, 187, MOVIE_GAMESTATE || MOVIE_NOTIFY_OBJECT); + playMovie(0, 187, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); movieEvent(0); break; @@ -78,7 +78,7 @@ bool CTitaniaSpeech::ActMsg(CActMsg *msg) { visibleMsg._visible = false; visibleMsg.execute("TitaniaStillControl"); loadSound("a#10.wav"); - playMovie(585, 706, MOVIE_GAMESTATE || MOVIE_NOTIFY_OBJECT); + playMovie(585, 706, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); playSound("a#10.wav"); break; @@ -86,7 +86,7 @@ bool CTitaniaSpeech::ActMsg(CActMsg *msg) { visibleMsg._visible = false; visibleMsg.execute("TitaniaStillControl"); loadSound("a#9.wav"); - playMovie(707, 905, MOVIE_GAMESTATE || MOVIE_NOTIFY_OBJECT); + playMovie(707, 905, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); playSound("a#9.wav"); break; @@ -94,7 +94,7 @@ bool CTitaniaSpeech::ActMsg(CActMsg *msg) { visibleMsg._visible = false; visibleMsg.execute("TitaniaStillControl"); loadSound("a#8.wav"); - playMovie(906, 938, MOVIE_GAMESTATE || MOVIE_NOTIFY_OBJECT); + playMovie(906, 938, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT); playSound("a#8.wav"); break; diff --git a/engines/titanic/sound/wave_file.cpp b/engines/titanic/sound/wave_file.cpp index 7093856217..8c00637d73 100644 --- a/engines/titanic/sound/wave_file.cpp +++ b/engines/titanic/sound/wave_file.cpp @@ -28,11 +28,12 @@ namespace Titanic { -CWaveFile::CWaveFile() : _owner(nullptr), _stream(nullptr), _soundType(SOUND_SFX) { +CWaveFile::CWaveFile() : _owner(nullptr), _stream(nullptr), + _soundType(Audio::Mixer::kPlainSoundType) { } CWaveFile::CWaveFile(QSoundManager *owner) : _owner(owner), _stream(nullptr), - _soundType(SOUND_SFX) { + _soundType(Audio::Mixer::kPlainSoundType) { } CWaveFile::~CWaveFile() { @@ -57,7 +58,8 @@ bool CWaveFile::loadSound(const CString &name) { Common::SeekableReadStream *stream = file.readStream(); _size = stream->size(); _stream = Audio::makeWAVStream(stream->readStream(_size), DisposeAfterUse::YES); - _soundType = SOUND_SFX; + _soundType = Audio::Mixer::kSFXSoundType; + return true; } @@ -72,7 +74,23 @@ bool CWaveFile::loadSpeech(CDialogueFile *dialogueFile, int speechIndex) { _size = res->_size; _stream = Audio::makeWAVStream(new Common::MemoryReadStream(data, _size, DisposeAfterUse::YES), DisposeAfterUse::YES); - _soundType = SOUND_SPEECH; + _soundType = Audio::Mixer::kSpeechSoundType; + + return true; +} + +bool CWaveFile::loadMusic(const CString &name) { + assert(!_stream); + + StdCWadFile file; + if (!file.open(name)) + return false; + + Common::SeekableReadStream *stream = file.readStream(); + _size = stream->size(); + _stream = Audio::makeWAVStream(stream->readStream(_size), DisposeAfterUse::YES); + _soundType = Audio::Mixer::kMusicSoundType; + return true; } diff --git a/engines/titanic/sound/wave_file.h b/engines/titanic/sound/wave_file.h index 33b2a8dedf..aede0c9328 100644 --- a/engines/titanic/sound/wave_file.h +++ b/engines/titanic/sound/wave_file.h @@ -32,19 +32,14 @@ namespace Titanic { class QSoundManager; -enum SoundType { - SOUND_SFX = 0, - SOUND_SPEECH = 1 -}; - class CWaveFile { private: uint _size; public: QSoundManager *_owner; - Audio::AudioStream *_stream; + Audio::SeekableAudioStream *_stream; Audio::SoundHandle _soundHandle; - SoundType _soundType; + Audio::Mixer::SoundType _soundType; public: CWaveFile(); CWaveFile(QSoundManager *owner); @@ -68,6 +63,11 @@ public: bool loadSpeech(CDialogueFile *dialogueFile, int speechIndex); /** + * Tries to load the specified music wave file + */ + bool loadMusic(const CString &name); + + /** * Returns true if the wave file has data loaded */ bool isLoaded() const { return _stream != nullptr; } diff --git a/engines/titanic/star_control/star_control_sub13.cpp b/engines/titanic/star_control/star_control_sub13.cpp index c721b395c6..cc9e239194 100644 --- a/engines/titanic/star_control/star_control_sub13.cpp +++ b/engines/titanic/star_control/star_control_sub13.cpp @@ -21,7 +21,6 @@ */ #include "titanic/star_control/star_control_sub13.h" -#include "titanic/titanic.h" namespace Titanic { diff --git a/engines/titanic/star_control/surface_fader.cpp b/engines/titanic/star_control/surface_fader.cpp index 089ad51717..0ee03673a4 100644 --- a/engines/titanic/star_control/surface_fader.cpp +++ b/engines/titanic/star_control/surface_fader.cpp @@ -29,9 +29,9 @@ namespace Titanic { CSurfaceFader::CSurfaceFader() : CSurfaceFaderBase() { _dataP = new byte[_count]; - for (int idx = 0; idx < _count; ++idx) { - // TODO: Setup data bytes - } + for (int idx = 0; idx < _count; ++idx) + _dataP[idx] = (byte)(pow((double)idx / (double)_count, 1.299999952316284) + * (double)_count + 0.5); } CSurfaceFader::~CSurfaceFader() { diff --git a/engines/titanic/support/avi_surface.cpp b/engines/titanic/support/avi_surface.cpp index c37bd83616..d4ebd5cef1 100644 --- a/engines/titanic/support/avi_surface.cpp +++ b/engines/titanic/support/avi_surface.cpp @@ -20,19 +20,20 @@ * */ -#include "titanic/support/avi_surface.h" -#include "titanic/support/screen_manager.h" -#include "titanic/support/video_surface.h" #include "common/system.h" #include "graphics/pixelformat.h" #include "video/avi_decoder.h" +#include "titanic/support/avi_surface.h" +#include "titanic/support/screen_manager.h" +#include "titanic/support/video_surface.h" +#include "titanic/titanic.h" namespace Titanic { Video::AVIDecoder::AVIVideoTrack &AVIDecoder::getVideoTrack() { for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++) if ((*it)->getTrackType() == Track::kTrackTypeVideo) - return *static_cast<AVIVideoTrack *>(*it); + return *dynamic_cast<AVIVideoTrack *>(*it); error("Could not find video track"); } @@ -338,9 +339,9 @@ bool AVISurface::addEvent(int frameNumber, CGameObject *obj) { } void AVISurface::setFrameRate(double rate) { - _decoders[0]->setRate(Common::Rational(rate)); + _decoders[0]->setRate(Common::Rational((int)rate)); if (_decoders[1]) - _decoders[1]->setRate(Common::Rational(rate)); + _decoders[1]->setRate(Common::Rational((int)rate)); } Graphics::ManagedSurface *AVISurface::getSecondarySurface() { @@ -358,4 +359,36 @@ Graphics::ManagedSurface *AVISurface::duplicateSecondaryFrame() const { } } +void AVISurface::playCutscene(const Rect &r, uint startFrame, uint endFrame) { + bool isDifferent = _movieFrameSurface[0]->w != r.width() || + _movieFrameSurface[0]->h != r.height(); + + startAtFrame(startFrame); + while (_currentFrame < (int)endFrame && !g_vm->shouldQuit()) { + if (isNextFrame()) { + renderFrame(); + _currentFrame = _decoders[0]->getCurFrame(); + + if (isDifferent) { + // Clear the destination area, and use the transBlitFrom method, + // which supports arbitrary scaling, to reduce to the desired size + g_vm->_screen->fillRect(r, 0); + g_vm->_screen->transBlitFrom(*_movieFrameSurface[0], + Common::Rect(0, 0, _movieFrameSurface[0]->w, _movieFrameSurface[0]->h), r); + } else { + g_vm->_screen->blitFrom(*_movieFrameSurface[0], Common::Point(r.left, r.top)); + } + + g_vm->_screen->update(); + g_vm->_events->pollEvents(); + } + + // Brief wait, and check at the same time for clicks to abort the clip + if (g_vm->_events->waitForPress(10)) + break; + } + + stop(); +} + } // End of namespace Titanic diff --git a/engines/titanic/support/avi_surface.h b/engines/titanic/support/avi_surface.h index d21182bca9..54b0155bdd 100644 --- a/engines/titanic/support/avi_surface.h +++ b/engines/titanic/support/avi_surface.h @@ -176,6 +176,11 @@ public: * Returns true if it's time for the next */ bool isNextFrame() const; + + /** + * Plays an interruptable cutscene + */ + void playCutscene(const Rect &r, uint startFrame, uint endFrame); }; } // End of namespace Titanic diff --git a/engines/titanic/support/credit_text.cpp b/engines/titanic/support/credit_text.cpp index 0e9715aaa6..009c3f4944 100644 --- a/engines/titanic/support/credit_text.cpp +++ b/engines/titanic/support/credit_text.cpp @@ -28,7 +28,7 @@ namespace Titanic { CCreditText::CCreditText() : _screenManagerP(nullptr), _field14(0), _ticks(0), _fontHeight(1), _objectP(nullptr), _totalHeight(0), _field40(0), _field44(0), _field48(0), _field4C(0), _field50(0), - _field54(0), _field58(0), _field5C(0) { + _field54(0), _field58(0), _counter(0) { } void CCreditText::clear() { @@ -52,7 +52,7 @@ void CCreditText::load(CGameObject *obj, CScreenManager *screenManager, _field50 = 0; _field54 = 0; _field58 = 0; - _field5C = 0; + _counter = 0; } void CCreditText::setup() { @@ -87,8 +87,11 @@ void CCreditText::setup() { } _groups.push_back(group); + if (hasDots) + handleDots(group); } + _screenManagerP->setFontNumber(oldFontNumber); _groupIt = _groups.begin(); _lineIt = (*_groupIt)->_lines.begin(); _totalHeight = _objectP->getBounds().height() + _fontHeight * 2; @@ -147,7 +150,108 @@ void CCreditText::handleDots(CCreditLineGroup *group) { } bool CCreditText::draw() { - return false; + if (_groupIt == _groups.end()) + return false; + + if (++_counter > 200) { + _field44 += _field50; + _field48 += _field54; + _field4C += _field58; + _field50 = g_vm->getRandomNumber(63) + 192 - _field44; + _field54 = g_vm->getRandomNumber(63) + 192 - _field48; + _field58 = g_vm->getRandomNumber(63) + 192 - _field4C; + _counter = 0; + } + + // Positioning adjustment, changing lines and/or group if necessary + int yDiff = (int)(g_vm->_events->getTicksCount() - _ticks) / 22 - _field40; + while (yDiff > 0) { + if (_totalHeight > 0) { + if (yDiff < _totalHeight) { + _totalHeight -= yDiff; + _field40 += yDiff; + yDiff = 0; + } else { + yDiff -= _totalHeight; + _field40 += _totalHeight; + _totalHeight = 0; + } + } else { + if (yDiff < _fontHeight) + break; + + ++_lineIt; + yDiff -= _fontHeight; + _field40 += _fontHeight; + + if (_lineIt == (*_groupIt)->_lines.end()) { + // Move to next line group + ++_groupIt; + if (_groupIt == _groups.end()) + // Reached end of groups + return false; + + _lineIt = (*_groupIt)->_lines.begin(); + _totalHeight = _fontHeight * 3 / 2; + } + } + } + + int oldFontNumber = _screenManagerP->setFontNumber(3); + CCreditLineGroups::iterator groupIt = _groupIt; + CCreditLines::iterator lineIt = _lineIt; + + Point textPos; + for (textPos.y = _rect.top + _totalHeight; textPos.y <= _rect.bottom; + textPos.y += _fontHeight) { + int textR = _field44 + _field50 * _counter / 200; + int textG = _field48 + _field54 * _counter / 200; + int textB = _field4C + _field58 * _counter / 200; + + // Single iteration loop to figure out RGB values for the line + do { + int percent = 0; + if (textPos.y < (_rect.top + 2 * _fontHeight)) { + percent = (textPos.y - _rect.top) * 100 / (_fontHeight * 2); + if (percent < 0) + percent = 0; + } else { + int bottom = _rect.bottom - 2 * _fontHeight; + if (textPos.y < bottom) + break; + + percent = (_rect.bottom - textPos.y) * 100 + / (_fontHeight * 2); + } + + // Adjust the RGB to the specified percentage intensity + textR = textR * percent / 100; + textG = textG * percent / 100; + textB = textB * percent / 100; + } while (0); + + // Write out the line + _screenManagerP->setFontColor(textR, textG, textB); + textPos.x = _rect.left + (_rect.width() - (*lineIt)->_lineWidth) / 2; + _screenManagerP->writeString(SURFACE_BACKBUFFER, textPos, + _rect, (*lineIt)->_line, (*lineIt)->_lineWidth); + + // Move to next line + ++lineIt; + if (lineIt == (*groupIt)->_lines.end()) { + ++groupIt; + if (groupIt == _groups.end()) + // Finished all lines + break; + + lineIt = (*groupIt)->_lines.begin(); + textPos.y += _fontHeight * 3 / 2; + } + } + + _objectP->makeDirty(); + _screenManagerP->setFontNumber(oldFontNumber); + return true; } } // End of namespace Titanic diff --git a/engines/titanic/support/credit_text.h b/engines/titanic/support/credit_text.h index ec8fc22cda..3e5bfca0c2 100644 --- a/engines/titanic/support/credit_text.h +++ b/engines/titanic/support/credit_text.h @@ -68,11 +68,11 @@ public: int _field14; CCreditLineGroups _groups; uint _ticks; - uint _fontHeight; + int _fontHeight; CGameObject *_objectP; CCreditLineGroups::iterator _groupIt; CCreditLines::iterator _lineIt; - uint _totalHeight; + int _totalHeight; int _field40; int _field44; int _field48; @@ -80,7 +80,7 @@ public: int _field50; int _field54; int _field58; - int _field5C; + int _counter; public: CCreditText(); diff --git a/engines/titanic/support/direct_draw.cpp b/engines/titanic/support/direct_draw.cpp index 5ddb25bec9..8e510861ae 100644 --- a/engines/titanic/support/direct_draw.cpp +++ b/engines/titanic/support/direct_draw.cpp @@ -23,14 +23,13 @@ #include "common/debug.h" #include "engines/util.h" #include "graphics/pixelformat.h" -#include "titanic/titanic.h" #include "titanic/support/direct_draw.h" +#include "titanic/titanic.h" namespace Titanic { -DirectDraw::DirectDraw(TitanicEngine *vm) : _vm(vm), - _windowed(false), _fieldC(0), _width(0), _height(0), - _bpp(0), _numBackSurfaces(0), _field24(0) { +DirectDraw::DirectDraw() : _windowed(false), _width(0), _height(0), + _bpp(0), _numBackSurfaces(0) { } void DirectDraw::setDisplayMode(int width, int height, int bpp, int refreshRate) { @@ -55,7 +54,7 @@ DirectDrawSurface *DirectDraw::createSurfaceFromDesc(const DDSurfaceDesc &desc) /*------------------------------------------------------------------------*/ -DirectDrawManager::DirectDrawManager(TitanicEngine *vm, bool windowed) : _directDraw(vm) { +DirectDrawManager::DirectDrawManager(TitanicEngine *vm, bool windowed) { _mainSurface = nullptr; _backSurfaces[0] = _backSurfaces[1] = nullptr; _directDraw._windowed = windowed; @@ -75,18 +74,6 @@ void DirectDrawManager::initVideo(int width, int height, int bpp, int numBackSur } } -void DirectDrawManager::setResolution() { - // TODO -} - -void DirectDrawManager::proc2() { - -} - -void DirectDrawManager::proc3() { - -} - void DirectDrawManager::initFullScreen() { debugC(ERROR_BASIC, kDebugGraphics, "Creating surfaces"); _directDraw.setDisplayMode(_directDraw._width, _directDraw._height, diff --git a/engines/titanic/support/direct_draw.h b/engines/titanic/support/direct_draw.h index 85c344c600..a7e9cc8d93 100644 --- a/engines/titanic/support/direct_draw.h +++ b/engines/titanic/support/direct_draw.h @@ -32,18 +32,14 @@ namespace Titanic { class TitanicEngine; class DirectDraw { -private: - TitanicEngine *_vm; public: bool _windowed; - int _fieldC; int _width; int _height; int _bpp; int _numBackSurfaces; - int _field24; public: - DirectDraw(TitanicEngine *vm); + DirectDraw(); /** * Sets a new display mode @@ -78,12 +74,6 @@ public: */ void initVideo(int width, int height, int bpp, int numBackSurfaces); - void setResolution(); - - void proc2(); - - void proc3(); - /** * Initializes the surfaces in windowed mode */ diff --git a/engines/titanic/support/exe_resources.cpp b/engines/titanic/support/exe_resources.cpp index 522e92f718..2b2c9c7635 100644 --- a/engines/titanic/support/exe_resources.cpp +++ b/engines/titanic/support/exe_resources.cpp @@ -21,6 +21,7 @@ */ #include "titanic/support/exe_resources.h" +#include "titanic/true_talk/script_handler.h" #include "titanic/titanic.h" namespace Titanic { diff --git a/engines/titanic/support/files_manager.cpp b/engines/titanic/support/files_manager.cpp index 04928b96d6..3ee17e9769 100644 --- a/engines/titanic/support/files_manager.cpp +++ b/engines/titanic/support/files_manager.cpp @@ -21,6 +21,7 @@ */ #include "common/file.h" +#include "common/memstream.h" #include "titanic/support/files_manager.h" #include "titanic/game_manager.h" @@ -51,7 +52,7 @@ void CFilesManager::loadResourceIndex() { for (;;) { offset = _datFile.readUint32LE(); size = _datFile.readUint32LE(); - if (size == 0) + if (offset == 0 && size == 0) break; Common::String resName; @@ -103,8 +104,9 @@ void CFilesManager::loadDrive() { resetView(); } -void CFilesManager::debug(CScreenManager *screenManager) { - warning("TODO: CFilesManager::debug"); +void CFilesManager::insertCD(CScreenManager *screenManager) { + // We not support running game directly from the original CDs, + // so this method can remain stubbed } void CFilesManager::resetView() { @@ -114,10 +116,6 @@ void CFilesManager::resetView() { } } -void CFilesManager::fn4(const CString &name) { - warning("TODO: CFilesManager::fn4"); -} - void CFilesManager::preload(const CString &name) { // We don't currently do any preloading of resources } @@ -126,7 +124,8 @@ Common::SeekableReadStream *CFilesManager::getResource(const CString &str) { ResourceEntry resEntry = _resources[str]; _datFile.seek(resEntry._offset); - return _datFile.readStream(resEntry._size); + return (resEntry._size > 0) ? _datFile.readStream(resEntry._size) : + new Common::MemoryReadStream(nullptr, 0); } } // End of namespace Titanic diff --git a/engines/titanic/support/files_manager.h b/engines/titanic/support/files_manager.h index ec0c7fc008..c530b05ece 100644 --- a/engines/titanic/support/files_manager.h +++ b/engines/titanic/support/files_manager.h @@ -84,15 +84,16 @@ public: */ void loadDrive(); - void debug(CScreenManager *screenManager); + /** + * Shows a dialog for inserting a new CD + */ + void insertCD(CScreenManager *screenManager); /** * Resets the view being displayed */ void resetView(); - void fn4(const CString &name); - /** * Preloads and caches a file for access shortly */ diff --git a/engines/titanic/support/font.cpp b/engines/titanic/support/font.cpp index 69c0efe504..e519237c3b 100644 --- a/engines/titanic/support/font.cpp +++ b/engines/titanic/support/font.cpp @@ -179,6 +179,67 @@ int STFont::writeString(CVideoSurface *surface, const Rect &rect1, const Rect &d return endP ? endP - str.c_str() : 0; } +void STFont::writeString(CVideoSurface *surface, const Point &destPos, Rect &clipRect, + const CString &str, int lineWidth) { + if (!_fontHeight || !_dataPtr || str.empty()) + return; + if (!lineWidth) + // No line width specified, so get in the width + lineWidth = stringWidth(str); + + Rect textRect(0, 0, lineWidth, _fontHeight); + Point textPt = destPos; + + // Perform clipping as necessary if the text will fall outside clipping area + if (textPt.y > clipRect.bottom) + return; + + if ((textPt.y + textRect.height()) > clipRect.bottom) + textRect.bottom = textRect.top - textPt.y + clipRect.bottom; + + if (textPt.y < clipRect.top) { + if ((textPt.y + textRect.height()) < clipRect.top) + return; + + textRect.top += clipRect.top - textPt.y; + textPt.y = clipRect.top; + } + + // Iterate through each character of the string + for (const byte *srcP = (const byte *)str.c_str(); *srcP; ++srcP) { + byte c = *srcP; + if (c == 0xE9) + c = '$'; + + // Form a rect of the area of the next character to draw + Rect charRect(_chars[c]._offset, textRect.top, + _chars[c]._offset + _chars[c]._width, textRect.bottom); + + if (textPt.x < clipRect.left) { + // Character is either partially or entirely left off-screen + if ((textPt.x + charRect.width()) < clipRect.left) { + textPt.x += _chars[c]._width; + continue; + } + + // Partially clipped on left-hand side + charRect.left = clipRect.left - textPt.x; + textPt.x = clipRect.left; + } else if ((textPt.x + charRect.width()) > clipRect.right) { + if (textPt.x > clipRect.right) + // Now entirely off right-hand side, so stop drawing + break; + + // Partially clipped on right-hand side + charRect.right += clipRect.right - textPt.x - charRect.width(); + } + + // At this point, we know we've got to draw at least part of a character, + // and have figured out the area of the character to draw + copyRect(surface, textPt, charRect); + } +} + WriteCharacterResult STFont::writeChar(CVideoSurface *surface, unsigned char c, const Point &pt, const Rect &destRect, const Rect *srcRect) { if (c == 233) diff --git a/engines/titanic/support/font.h b/engines/titanic/support/font.h index 591fb4661c..6c4fe8e9c3 100644 --- a/engines/titanic/support/font.h +++ b/engines/titanic/support/font.h @@ -99,6 +99,12 @@ public: int yOffset, const CString &str, CTextCursor *textCursor); /** + * Write a string to the specified surface + */ + void writeString(CVideoSurface *surface, const Point &destPos, Rect &clipRect, + const CString &str, int lineWidth = 0); + + /** * Get the text area a string will fit into * @param str String * @param maxWidth Maximum width in pixels diff --git a/engines/titanic/support/image.cpp b/engines/titanic/support/image.cpp index 1e936b6940..2da2179af0 100644 --- a/engines/titanic/support/image.cpp +++ b/engines/titanic/support/image.cpp @@ -21,8 +21,8 @@ */ #include "titanic/support/image.h" -#include "titanic/titanic.h" #include "image/bmp.h" +#include "titanic/titanic.h" namespace Titanic { diff --git a/engines/titanic/support/mouse_cursor.cpp b/engines/titanic/support/mouse_cursor.cpp index d6a42823f5..d342e6cccb 100644 --- a/engines/titanic/support/mouse_cursor.cpp +++ b/engines/titanic/support/mouse_cursor.cpp @@ -26,9 +26,9 @@ #include "titanic/support/mouse_cursor.h" #include "titanic/support/movie.h" #include "titanic/support/screen_manager.h" -#include "titanic/titanic.h" #include "titanic/support/video_surface.h" #include "titanic/core/resource_key.h" +#include "titanic/titanic.h" namespace Titanic { @@ -67,7 +67,6 @@ CMouseCursor::~CMouseCursor() { void CMouseCursor::loadCursorImages() { const CResourceKey key("ycursors.avi"); - g_vm->_filesManager->fn4(key.getString()); // Iterate through getting each cursor for (int idx = 0; idx < NUM_CURSORS; ++idx) { @@ -128,8 +127,11 @@ void CMouseCursor::unlockE4() { CScreenManager::_screenManagerPtr->_inputHandler->decLockCount(); } -void CMouseCursor::saveState(int v1, int v2, int v3) { - // TODO +void CMouseCursor::setPosition(const Point &pt, double rate) { + assert(rate >= 0.0 && rate <= 1.0); + + // TODO: Figure out use of the rate parameter + g_system->warpMouse(pt.x, pt.y); } } // End of namespace Titanic diff --git a/engines/titanic/support/mouse_cursor.h b/engines/titanic/support/mouse_cursor.h index 7a81ad43fa..74fb1f6113 100644 --- a/engines/titanic/support/mouse_cursor.h +++ b/engines/titanic/support/mouse_cursor.h @@ -24,8 +24,8 @@ #define TITANIC_MOUSE_CURSOR_H #include "common/scummsys.h" -#include "common/rect.h" #include "graphics/managed_surface.h" +#include "titanic/support/rect.h" namespace Titanic { @@ -105,7 +105,10 @@ public: void lockE4(); void unlockE4(); - void saveState(int v1, int v2, int v3); + /** + * Sets the mouse to a new position + */ + void setPosition(const Point &pt, double rate); }; diff --git a/engines/titanic/support/movie.cpp b/engines/titanic/support/movie.cpp index 3bb9fb88cf..e863185f84 100644 --- a/engines/titanic/support/movie.cpp +++ b/engines/titanic/support/movie.cpp @@ -122,33 +122,25 @@ void OSMovie::play(uint startFrame, uint endFrame, uint initialFrame, uint flags movieStarted(); } -void OSMovie::playClip(const Point &drawPos, uint startFrame, uint endFrame) { +void OSMovie::playCutscene(const Rect &drawRect, uint startFrame, uint endFrame) { if (!_movieSurface) _movieSurface = CScreenManager::_screenManagerPtr->createSurface(600, 340); bool widthLess = _videoSurface->getWidth() < 600; bool heightLess = _videoSurface->getHeight() < 340; - Rect r(drawPos.x, drawPos.y, - drawPos.x + (widthLess ? CLIP_WIDTH_REDUCED : CLIP_WIDTH), - drawPos.y + (heightLess ? CLIP_HEIGHT_REDUCED : CLIP_HEIGHT) + Rect r(drawRect.left, drawRect.top, + drawRect.left + (widthLess ? CLIP_WIDTH_REDUCED : CLIP_WIDTH), + drawRect.top + (heightLess ? CLIP_HEIGHT_REDUCED : CLIP_HEIGHT) ); - uint timePerFrame = (uint)(1000.0 / _aviSurface._frameRate); + // Set a new event target whilst the clip plays, so standard scene drawing isn't called + CEventTarget eventTarget; + g_vm->_events->addTarget(&eventTarget); - for (; endFrame >= startFrame; ++startFrame) { - // Set the frame - _aviSurface.setFrame(startFrame); + _aviSurface.setFrame(startFrame); + _aviSurface.playCutscene(r, startFrame, endFrame); - // TODO: See if we need to do anything further here. The original had a bunch - // of calls and using of the _movieSurface; perhaps to allow scaling down - // videos to half-size - if (widthLess || heightLess) - warning("Not properly reducing clip size: %d %d", r.width(), r.height()); - - // Wait for the next frame, unless the user interrupts the clip - if (g_vm->_events->waitForPress(timePerFrame)) - break; - } + g_vm->_events->removeTarget(); } void OSMovie::stop() { diff --git a/engines/titanic/support/movie.h b/engines/titanic/support/movie.h index 2d1c264f03..8c89f9e6dd 100644 --- a/engines/titanic/support/movie.h +++ b/engines/titanic/support/movie.h @@ -84,9 +84,10 @@ public: virtual void play(uint startFrame, uint endFrame, uint initialFrame, uint flags, CGameObject *obj) = 0; /** - * Plays a sub-section of a movie + * Plays a sub-section of a movie, and doesn't return until either + * the playback ends or a key has been pressed */ - virtual void playClip(const Point &drawPos, uint startFrame, uint endFrame) = 0; + virtual void playCutscene(const Rect &drawRect, uint startFrame, uint endFrame) = 0; /** * Stops the movie @@ -182,9 +183,10 @@ public: virtual void play(uint startFrame, uint endFrame, uint initialFrame, uint flags, CGameObject *obj); /** - * Plays a sub-section of a movie + * Plays a sub-section of a movie, and doesn't return until either + * the playback ends or a key has been pressed */ - virtual void playClip(const Point &drawPos, uint startFrame, uint endFrame); + virtual void playCutscene(const Rect &drawRect, uint startFrame, uint endFrame); /** * Stops the movie diff --git a/engines/titanic/support/screen_manager.cpp b/engines/titanic/support/screen_manager.cpp index d795f78764..bcf43fc8cb 100644 --- a/engines/titanic/support/screen_manager.cpp +++ b/engines/titanic/support/screen_manager.cpp @@ -21,8 +21,8 @@ */ #include "titanic/support/screen_manager.h" -#include "titanic/titanic.h" #include "titanic/support/video_surface.h" +#include "titanic/titanic.h" namespace Titanic { @@ -38,7 +38,6 @@ CScreenManager::CScreenManager(TitanicEngine *vm): _vm(vm) { _textCursor = nullptr; _inputHandler = nullptr; _fontNumber = 0; - // TODO: Further initialization _screenManagerPtr = this; } @@ -240,10 +239,25 @@ int OSScreenManager::writeString(int surfaceNum, const Rect &destRect, yOffset, str, textCursor); } -int OSScreenManager::writeString(int surfaceNum, const Rect &srcRect, - const Rect &destRect, const CString &str, CTextCursor *textCursor) { - // TODO - return 0; +void OSScreenManager::writeString(int surfaceNum, const Point &destPos, + const Rect &clipRect, const CString &str, int lineWidth) { + CVideoSurface *surface; + Rect bounds; + + if (surfaceNum >= 0 && surfaceNum < (int)_backSurfaces.size()) { + surface = _backSurfaces[surfaceNum]._surface; + bounds = _backSurfaces[surfaceNum]._bounds; + } else if (surfaceNum == -1) { + surface = _frontRenderSurface; + bounds = Rect(0, 0, surface->getWidth(), surface->getHeight()); + } else { + return; + } + + Rect destRect = clipRect; + destRect.constrain(bounds); + + _fonts[_fontNumber].writeString(surface, destPos, destRect, str, lineWidth); } void OSScreenManager::setFontColor(byte r, byte g, byte b) { diff --git a/engines/titanic/support/screen_manager.h b/engines/titanic/support/screen_manager.h index 0736f1393c..cad6901b02 100644 --- a/engines/titanic/support/screen_manager.h +++ b/engines/titanic/support/screen_manager.h @@ -140,13 +140,13 @@ public: /** * Write a string * @param surfaceNum Destination surface - * @param srcRect Drawing area - * @param destRect Bounds of dest surface + * @param destPos Position to start writing text at + * @param clipRect Clipping area to constrain text to * @param str Line or lines to write - * @param textCursor Optional text cursor pointer + * @param maxWidth Maximum allowed line width */ - virtual int writeString(int surfaceNum, const Rect &srcRect, - const Rect &destRect, const CString &str, CTextCursor *textCursor) = 0; + virtual void writeString(int surfaceNum, const Point &destPos, + const Rect &clipRect, const CString &str, int maxWidth) = 0; /** * Set the font color @@ -322,13 +322,13 @@ public: /** * Write a string * @param surfaceNum Destination surface - * @param srcRect Drawing area - * @param destRect Bounds of dest surface + * @param destPos Position to start writing text at + * @param clipRect Clipping area to constrain text to * @param str Line or lines to write - * @param textCursor Optional text cursor pointer + * @param lineWidth Width in pixels of the string, if known. */ - virtual int writeString(int surfaceNum, const Rect &srcRect, - const Rect &destRect, const CString &str, CTextCursor *textCursor); + virtual void writeString(int surfaceNum, const Point &destPos, + const Rect &clipRect, const CString &str, int lineWidth = 0); /** * Set the font color diff --git a/engines/titanic/support/simple_file.h b/engines/titanic/support/simple_file.h index f5d0bc7c1b..01aaa86925 100644 --- a/engines/titanic/support/simple_file.h +++ b/engines/titanic/support/simple_file.h @@ -278,7 +278,7 @@ public: * Set up a stream for write access */ virtual void open(Common::OutSaveFile *stream) { - SimpleFile::open(Common::wrapCompressedWriteStream(stream)); + SimpleFile::open(new Common::OutSaveFile(Common::wrapCompressedWriteStream(stream))); } }; diff --git a/engines/titanic/support/string.cpp b/engines/titanic/support/string.cpp index cd39c03861..9961cfce59 100644 --- a/engines/titanic/support/string.cpp +++ b/engines/titanic/support/string.cpp @@ -58,6 +58,10 @@ CString CString::mid(uint start) const { return mid(start, strSize - start); } +CString CString::deleteRight(uint count) const { + return (count >= size()) ? CString() : left(size() - count); +} + int CString::indexOf(char c) const { const char *charP = strchr(c_str(), c); return charP ? charP - c_str() : -1; @@ -122,4 +126,20 @@ CString CString::format(const char *fmt, ...) { return output; } +bool CString::operator==(const CString &x) const { + return compareToIgnoreCase(x) == 0; +} + +bool CString::operator==(const char *x) const { + return compareToIgnoreCase(x) == 0; +} + +bool CString::operator!=(const CString &x) const { + return compareToIgnoreCase(x) != 0; +} + +bool CString::operator!=(const char *x) const { + return compareToIgnoreCase(x) != 0; +} + } // End of namespace Titanic diff --git a/engines/titanic/support/string.h b/engines/titanic/support/string.h index 9550ce88a7..71242c01c9 100644 --- a/engines/titanic/support/string.h +++ b/engines/titanic/support/string.h @@ -49,6 +49,11 @@ public: explicit CString(char c) : Common::String(c) {} explicit CString(int val); + bool operator==(const CString &x) const; + bool operator==(const char *x) const; + bool operator!=(const CString &x) const; + bool operator!=(const char *x) const; + /** * Returns the left n characters of the string */ @@ -70,6 +75,12 @@ public: CString mid(uint start) const; /** + * Returns a substring consisting of the entire string + * except for a specified number of characters at the end + */ + CString deleteRight(uint count) const; + + /** * Returns the index of the first occurance of a given character */ int indexOf(char c) const; diff --git a/engines/titanic/support/string_parser.cpp b/engines/titanic/support/string_parser.cpp new file mode 100644 index 0000000000..496440a973 --- /dev/null +++ b/engines/titanic/support/string_parser.cpp @@ -0,0 +1,97 @@ +/* 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 "titanic/support/string_parser.h" +#include "common/util.h" + +namespace Titanic { + +void CStringParser::skipSeperators(const CString &seperatorChars) { + for (; _index < size(); ++_index) { + char c = (*this)[_index]; + if (seperatorChars.indexOf(c) == -1) + break; + } +} + +bool CStringParser::parse(CString &resultStr, const CString &seperatorChars, bool allowQuotes) { + if (_index >= size()) + return false; + + resultStr.clear(); + bool quoteFlag = false; + while (_index < size()) { + char c = (*this)[_index]; + if (!quoteFlag && seperatorChars.indexOf(c) >= 0) + break; + + if (allowQuotes) { + if (quoteFlag) { + if (c == '"') { + // End of quoted string + ++_index; + break; + } + } else { + if (c == '"') { + // Start of quoted string + ++_index; + quoteFlag = true; + continue; + } + } + } + + resultStr += c; + ++_index; + } + + return true; +} + +uint CStringParser::readInt() { + // Get digits from the string + CString numStr; + while (Common::isDigit(currentChar())) + numStr += getNextChar(); + + // Throw a wobbly if there wasn't a number + if (numStr.empty()) + error("ReadInt(): No number to read"); + + return atoi(numStr.c_str()); +} + +char CStringParser::currentChar() const { + return (_index >= size()) ? '\0' : (*this)[_index]; +} + +char CStringParser::getNextChar() { + return (_index >= size()) ? '\0' : (*this)[_index++]; +} + +void CStringParser::skipSpaces() { + while (_index < size() && Common::isSpace(currentChar())) + ++_index; +} + +} // End of namespace Titanic diff --git a/engines/titanic/support/string_parser.h b/engines/titanic/support/string_parser.h new file mode 100644 index 0000000000..f89caacfb5 --- /dev/null +++ b/engines/titanic/support/string_parser.h @@ -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 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. + * + */ + +#ifndef TITANIC_STRING_PARSER_H +#define TITANIC_STRING_PARSER_H + +#include "titanic/support/string.h" + +namespace Titanic { + +class CStringParser : public CString { +private: + uint _index; +private: + /** + * Gets the character at the current index + */ + char currentChar() const; + + /** + * Gets the next character, and increments the parsing index + */ + char getNextChar(); + + /** + * Skips over any spaces + */ + void skipSpaces(); +public: + CStringParser() : CString(), _index(0) {} + CStringParser(const CString &str) : CString(str), _index(0) {} + + /** + * Skips over any specified seperator characters in our string + * at the current index + */ + void skipSeperators(const CString &seperatorChars); + + /** + * Parses out a string from a source string at the current index + * @param resultStr String to hold the resulting sring + * @param seperatorChras List of characters that seperate string values + * @param allowQuotes If true, handles double-quoted substrings + * @returns True if a string entry was extracted + */ + bool parse(CString &resultStr, const CString &seperatorChars, bool allowQuotes = false); + + /** + * Reads an integer from the string + */ + uint readInt(); + +}; + +} // End of namespace Titanic + +#endif /* TITANIC_STRING_PARSER_H */ diff --git a/engines/titanic/support/video_surface.cpp b/engines/titanic/support/video_surface.cpp index 594f660937..b5f668793a 100644 --- a/engines/titanic/support/video_surface.cpp +++ b/engines/titanic/support/video_surface.cpp @@ -163,7 +163,19 @@ void CVideoSurface::blitRect2(const Rect &srcRect, const Rect &destRect, CVideoS } void CVideoSurface::movieBlitRect(const Rect &srcRect, const Rect &destRect, CVideoSurface *src) { - // TODO + if (lock()) { + if (src->lock()) { + Graphics::ManagedSurface *srcSurface = src->_rawSurface; + Graphics::ManagedSurface *destSurface = _rawSurface; + + // TODO: Handle the transparency mode correctly + destSurface->blitFrom(*srcSurface, srcRect, Point(srcRect.left, srcRect.top)); + + src->unlock(); + } + + unlock(); + } } uint CVideoSurface::getTransparencyColor() { diff --git a/engines/titanic/titanic.cpp b/engines/titanic/titanic.cpp index af894d6997..3a721b6095 100644 --- a/engines/titanic/titanic.cpp +++ b/engines/titanic/titanic.cpp @@ -33,6 +33,7 @@ #include "titanic/carry/hose.h" #include "titanic/core/saveable_object.h" #include "titanic/game/get_lift_eye2.h" +#include "titanic/game/lemon_dispensor.h" #include "titanic/game/television.h" #include "titanic/game/parrot/parrot_lobby_object.h" #include "titanic/game/sgt/sgt_navigation.h" @@ -91,6 +92,7 @@ void TitanicEngine::initialize() { CGameObject::init(); CGetLiftEye2::init(); CHose::init(); + CLemonDispensor::init(); CMovie::init(); CParrotLobbyObject::init(); CSGTNavigation::init(); diff --git a/engines/titanic/true_talk/script_handler.cpp b/engines/titanic/true_talk/script_handler.cpp index 64e789a4b9..f434822870 100644 --- a/engines/titanic/true_talk/script_handler.cpp +++ b/engines/titanic/true_talk/script_handler.cpp @@ -60,19 +60,18 @@ ScriptChangedResult CScriptHandler::scriptChanged(TTroomScript *roomScript, TTnp if (result == SCR_1) result = npcScript->notifyScript(roomScript, dialogueId); - if (result != SCR_3 && result != SCR_4) - return result; + if (dialogueId == 3 || dialogueId == 4) { + delete _concept1P; + delete _concept2P; + delete _concept3P; + delete _concept4P; + _concept1P = nullptr; + _concept2P = nullptr; + _concept3P = nullptr; + _concept4P = nullptr; + } ++_inputCtr; - delete _concept1P; - delete _concept2P; - delete _concept3P; - delete _concept4P; - _concept1P = nullptr; - _concept2P = nullptr; - _concept3P = nullptr; - _concept4P = nullptr; - return result; } diff --git a/engines/titanic/true_talk/title_engine.cpp b/engines/titanic/true_talk/title_engine.cpp index 4dd45ba335..363cc3454c 100644 --- a/engines/titanic/true_talk/title_engine.cpp +++ b/engines/titanic/true_talk/title_engine.cpp @@ -66,10 +66,6 @@ int STtitleEngine::setResponse(TTscriptBase *script, TTresponse *response) { return 0; } -void STtitleEngine::dump(int val1, int val2) { - // TODO -} - SimpleFile *STtitleEngine::open(const CString &name) { Common::SeekableReadStream *stream = g_vm->_filesManager->getResource( CString::format("TEXT/%s", name.c_str())); diff --git a/engines/titanic/true_talk/title_engine.h b/engines/titanic/true_talk/title_engine.h index afd2d3b92f..a980e52215 100644 --- a/engines/titanic/true_talk/title_engine.h +++ b/engines/titanic/true_talk/title_engine.h @@ -57,12 +57,6 @@ public: * Sets a conversation reponse */ virtual int setResponse(TTscriptBase *script, TTresponse *response) { return SS_4; } - - virtual int proc4(int unused) const = 0; - virtual int proc5(int64 unused) const = 0; - virtual int proc6(int64 unused) const = 0; - virtual int proc7(int64 unused) const = 0; - virtual int proc8() const = 0; /** * Open a designated file @@ -94,14 +88,6 @@ public: */ virtual int setResponse(TTscriptBase *script, TTresponse *response); - virtual void dump(int val1, int val2); - - virtual int proc4(int unused) const { return 0; } - virtual int proc5(int64 unused) const { return 0; } - virtual int proc6(int64 unused) const { return 0; } - virtual int proc7(int64 unused) const { return 0; } - virtual int proc8() const { return 0; } - /** * Open a designated file */ diff --git a/engines/titanic/true_talk/true_talk_manager.cpp b/engines/titanic/true_talk/true_talk_manager.cpp index 61ad924d90..085f0bd310 100644 --- a/engines/titanic/true_talk/true_talk_manager.cpp +++ b/engines/titanic/true_talk/true_talk_manager.cpp @@ -114,7 +114,7 @@ void CTrueTalkManager::loadStatics(SimpleFile *file) { _v9 = file->readNumber(); _v10 = file->readNumber() != 0; - for (int idx = count; count > 10; --idx) + for (int idx = count; idx > 10; --idx) file->readNumber(); int count2 = file->readNumber(); @@ -219,10 +219,6 @@ void CTrueTalkManager::removeCompleted() { } } -void CTrueTalkManager::update2() { - //warning("CTrueTalkManager::update2"); -} - void CTrueTalkManager::start(CTrueTalkNPC *npc, uint id, CViewItem *view) { TTnpcScript *npcScript = getNpcScript(npc); TTroomScript *roomScript = getRoomScript(); @@ -494,32 +490,32 @@ void CTrueTalkManager::playSpeech(TTtalker *talker, TTroomScript *roomScript, CV // Setup proximities CProximity p1, p2, p3; if (isParrot) { - p1._channel = 3; - p2._channel = 5; - p3._channel = 4; + p1._channelMode = 3; + p2._channelMode = 5; + p3._channelMode = 4; } else { - p1._channel = 0; - p2._channel = 1; - p3._channel = 2; + p1._channelMode = 0; + p2._channelMode = 1; + p3._channelMode = 2; } if (milli > 0) { p3._channelVolume = (index * 3) / 2; - p3._field28 = 1; + p3._positioningMode = POSMODE_POLAR; p3._azimuth = -135.0; p3._range = 1.0; p3._elevation = 0; p2._channelVolume = (index * 3) / 4; - p2._field28 = 0; + p2._positioningMode = POSMODE_NONE; p2._azimuth = 135.0; p2._range = 1.0; p2._elevation = 0; } - _gameManager->_sound.stopChannel(p1._channel); + _gameManager->_sound.stopChannel(p1._channelMode); if (view) { - p1._field28 = 2; + p1._positioningMode = POSMODE_VECTOR; view->getPosition(p1._posX, p1._posY, p1._posZ); } @@ -589,9 +585,9 @@ int CTrueTalkManager::getPassengerClass() const { return gameState ? gameState->_passengerClass : 4; } -int CTrueTalkManager::getState14() const { +Season CTrueTalkManager::getCurrentSeason() const { CGameState *gameState = getGameState(); - return gameState ? gameState->_field14 : 0; + return gameState ? gameState->_seasonNum : SEASON_SUMMER; } } // End of namespace Titanic diff --git a/engines/titanic/true_talk/true_talk_manager.h b/engines/titanic/true_talk/true_talk_manager.h index 8a8895917a..e891f6112a 100644 --- a/engines/titanic/true_talk/true_talk_manager.h +++ b/engines/titanic/true_talk/true_talk_manager.h @@ -31,6 +31,7 @@ #include "titanic/true_talk/tt_quotes_tree.h" #include "titanic/true_talk/tt_scripts.h" #include "titanic/true_talk/tt_talker.h" +#include "titanic/game_state.h" namespace Titanic { @@ -200,8 +201,6 @@ public: */ CGameManager *getGameManager() const; - void update2(); - /** * Start a TrueTalk conversation */ @@ -237,7 +236,7 @@ public: */ int getPassengerClass() const; - int getState14() const; + Season getCurrentSeason() const; }; } // End of namespace Titanic diff --git a/engines/titanic/true_talk/tt_npc_script.cpp b/engines/titanic/true_talk/tt_npc_script.cpp index 61c3b0e00c..74e2f4f66b 100644 --- a/engines/titanic/true_talk/tt_npc_script.cpp +++ b/engines/titanic/true_talk/tt_npc_script.cpp @@ -332,7 +332,7 @@ int TTnpcScript::handleQuote(const TTroomScript *roomScript, const TTsentence *s uint TTnpcScript::getRangeValue(uint id) { TTscriptRange *range = findRange(id); if (!range) - return 0; + return id; switch (range->_mode) { case SF_RANDOM: { @@ -579,14 +579,14 @@ int TTnpcScript::getValue(int testNum) const { case 4: if (g_vm->_trueTalkManager) { - switch (g_vm->_trueTalkManager->getState14()) { - case 1: + switch (g_vm->_trueTalkManager->getCurrentSeason()) { + case SEASON_AUTUMN: CTrueTalkManager::_v6 = 3; break; - case 2: + case SEASON_WINTER: CTrueTalkManager::_v6 = 0; break; - case 3: + case SEASON_SPRING: CTrueTalkManager::_v6 = 1; break; default: @@ -634,13 +634,12 @@ uint TTnpcScript::getDialogueId(uint tagId) { } } - uint oldTagId = tagId; tagId = getRangeValue(tagId); - if (tagId != oldTagId) + if (tagId != origId) tagId = getRangeValue(tagId); - oldTagId = getDialsBitset(); - uint newId = updateState(origId, tagId, oldTagId); + uint dialBits = getDialsBitset(); + uint newId = updateState(origId, tagId, dialBits); if (!newId) return 0; @@ -654,7 +653,7 @@ uint TTnpcScript::getDialogueId(uint tagId) { if (tableP->_id == newId) break; } - uint newVal = tableP->_values[oldTagId]; + uint newVal = tableP->_values[dialBits]; // First slot dialogue Ids idx = 0; diff --git a/engines/titanic/true_talk/tt_parser.cpp b/engines/titanic/true_talk/tt_parser.cpp index 1d9c199054..95a302d53c 100644 --- a/engines/titanic/true_talk/tt_parser.cpp +++ b/engines/titanic/true_talk/tt_parser.cpp @@ -575,7 +575,7 @@ int TTparser::loadRequests(TTword *word) { if (_sentenceConcept) { if (_sentenceConcept->get18() == 0 || _sentenceConcept->get18() == 2) { - TTaction *action = static_cast<TTaction *>(word); + TTaction *action = dynamic_cast<TTaction *>(word); _sentenceConcept->set18(action->getVal()); } } @@ -1273,7 +1273,7 @@ int TTparser::considerRequests(TTword *word) { break; } - TTparserNode *nextP = static_cast<TTparserNode *>(nodeP->_nextP); + TTparserNode *nextP = dynamic_cast<TTparserNode *>(nodeP->_nextP); if (flag) delete nodeP; nodeP = nextP; @@ -1375,7 +1375,7 @@ void TTparser::removeConcept(TTconcept *concept) { void TTparser::removeNode(TTparserNode *node) { if (!node->_priorP) // Node is the head of the chain, so reset parser's nodes pointer - _nodesP = static_cast<TTparserNode *>(node->_nextP); + _nodesP = dynamic_cast<TTparserNode *>(node->_nextP); delete node; } @@ -1525,7 +1525,7 @@ int TTparser::fn2(TTword *word) { case 602: case 607: - return checkReferent(static_cast<TTpronoun *>(word)); + return checkReferent(dynamic_cast<TTpronoun *>(word)); case 608: return 1; diff --git a/engines/titanic/true_talk/tt_room_script.cpp b/engines/titanic/true_talk/tt_room_script.cpp index b8fbca7d39..a49a5a5bd1 100644 --- a/engines/titanic/true_talk/tt_room_script.cpp +++ b/engines/titanic/true_talk/tt_room_script.cpp @@ -34,7 +34,7 @@ TTroomScriptBase::TTroomScriptBase(int scriptId, /*------------------------------------------------------------------------*/ TTroomScript::TTroomScript(int scriptId) : - TTroomScriptBase(scriptId, "", "", 0, -1, -1, -1, 0, 0) { + TTroomScriptBase(scriptId, "", "", 0, -1, -1, -1, 0, 0), _field54(0) { } bool TTroomScript::proc8() const { diff --git a/engines/titanic/true_talk/tt_sentence.cpp b/engines/titanic/true_talk/tt_sentence.cpp index 9588ee021e..f187710de7 100644 --- a/engines/titanic/true_talk/tt_sentence.cpp +++ b/engines/titanic/true_talk/tt_sentence.cpp @@ -83,7 +83,7 @@ void TTsentence::copyFrom(const TTsentence &src) { if (src._nodesP) { // Source has processed nodes, so duplicate them for (TTsentenceNode *node = src._nodesP; node; - node = static_cast<TTsentenceNode *>(node->_nextP)) { + node = dynamic_cast<TTsentenceNode *>(node->_nextP)) { TTsentenceNode *newNode = new TTsentenceNode(node->_wordP); if (_nodesP) _nodesP->addToTail(newNode); @@ -319,7 +319,7 @@ bool TTsentence::localWord(const char *str) const { bool result = false; for (TTsentenceNode *nodeP = _nodesP; nodeP && !result; - nodeP = static_cast<TTsentenceNode *>(nodeP->_nextP)) { + nodeP = dynamic_cast<TTsentenceNode *>(nodeP->_nextP)) { TTsynonym syn; if (!nodeP->_wordP) continue; diff --git a/engines/titanic/true_talk/tt_string_node.cpp b/engines/titanic/true_talk/tt_string_node.cpp index 2bb0c5a74b..5a21d73a9b 100644 --- a/engines/titanic/true_talk/tt_string_node.cpp +++ b/engines/titanic/true_talk/tt_string_node.cpp @@ -55,7 +55,7 @@ void TTstringNode::initialize(TTstringNode *oldNode) { } TTstringNode *TTstringNode::findByName(const TTstring &str, int mode) { - for (TTstringNode *nodeP = this; nodeP; nodeP = static_cast<TTstringNode *>(nodeP->_nextP)) { + for (TTstringNode *nodeP = this; nodeP; nodeP = dynamic_cast<TTstringNode *>(nodeP->_nextP)) { if (nodeP->_mode == mode || (mode == 3 && nodeP->_mode < 3)) { if (nodeP->_string == str) return nodeP; diff --git a/engines/titanic/true_talk/tt_synonym.cpp b/engines/titanic/true_talk/tt_synonym.cpp index 0f56c5cb22..b476efefcb 100644 --- a/engines/titanic/true_talk/tt_synonym.cpp +++ b/engines/titanic/true_talk/tt_synonym.cpp @@ -60,7 +60,7 @@ TTsynonym *TTsynonym::copyFrom(const TTsynonym *src) { } int TTsynonym::save(SimpleFile *file) { - for (TTstringNode *synP = this; synP; synP = static_cast<TTstringNode *>(synP->_nextP)) { + for (TTstringNode *synP = this; synP; synP = dynamic_cast<TTstringNode *>(synP->_nextP)) { file->writeFormat("%s", " 0 "); synP->_string.save(file); file->writeFormat("%c", ' '); diff --git a/engines/titanic/true_talk/tt_vocab.cpp b/engines/titanic/true_talk/tt_vocab.cpp index 08d6e9e1a7..1d4d2ebbf2 100644 --- a/engines/titanic/true_talk/tt_vocab.cpp +++ b/engines/titanic/true_talk/tt_vocab.cpp @@ -288,7 +288,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const { if (word) {
if (word->_wordClass == WC_ACTION) {
- static_cast<TTaction *>(word)->setVal(1);
+ dynamic_cast<TTaction *>(word)->setVal(1);
}
} else {
tempStr = str;
@@ -311,7 +311,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const { if (word) {
if (word->_wordClass == WC_ADJECTIVE) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
@@ -331,7 +331,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const { if (word) {
if (word->_wordClass == WC_ADJECTIVE) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
@@ -350,7 +350,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const { word = getPrimeWord(tempStr);
if (word && word->_wordClass == WC_ADJECTIVE) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
@@ -373,7 +373,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const { if (word) {
if (word->_wordClass == WC_ADJECTIVE) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
@@ -393,7 +393,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const { if (word) {
if (word->_wordClass == WC_ADJECTIVE) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
@@ -412,7 +412,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const { word = getPrimeWord(tempStr);
if (word) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
@@ -529,7 +529,7 @@ TTword *TTvocab::getPrefixedWord(TTstring &str) const { if (!word)
tempStr = str;
else if (word->_wordClass == WC_ADJECTIVE) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
diff --git a/engines/titanic/true_talk/tt_word.cpp b/engines/titanic/true_talk/tt_word.cpp index df6ee5c7bc..c8676e4b1e 100644 --- a/engines/titanic/true_talk/tt_word.cpp +++ b/engines/titanic/true_talk/tt_word.cpp @@ -173,7 +173,7 @@ bool TTword::findSynByName(const TTstring &str, TTsynonym *dest, int mode) const if (!_synP) return false; - const TTsynonym *synP = static_cast<const TTsynonym *>(_synP->findByName(str, mode)); + const TTsynonym *synP = dynamic_cast<const TTsynonym *>(_synP->findByName(str, mode)); if (synP) { dest->copyFrom(synP); dest->_priorP = nullptr; diff --git a/engines/toltecs/detection.cpp b/engines/toltecs/detection.cpp index 7c707895e6..cc27341e10 100644 --- a/engines/toltecs/detection.cpp +++ b/engines/toltecs/detection.cpp @@ -234,7 +234,8 @@ bool ToltecsMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || (f == kSavesSupportCreationDate) || - (f == kSavesSupportPlayTime); + (f == kSavesSupportPlayTime) || + (f == kSimpleSavesNames); } bool Toltecs::ToltecsEngine::hasFeature(EngineFeature f) const { diff --git a/engines/toon/detection.cpp b/engines/toon/detection.cpp index 5d2e0a9bca..e93d676d87 100644 --- a/engines/toon/detection.cpp +++ b/engines/toon/detection.cpp @@ -159,7 +159,8 @@ bool ToonMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsDeleteSave) || (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || - (f == kSavesSupportCreationDate); + (f == kSavesSupportCreationDate) || + (f == kSimpleSavesNames); } void ToonMetaEngine::removeSaveState(const char *target, int slot) const { diff --git a/engines/tsage/detection.cpp b/engines/tsage/detection.cpp index 584ad87742..e476391f71 100644 --- a/engines/tsage/detection.cpp +++ b/engines/tsage/detection.cpp @@ -95,6 +95,7 @@ public: case kSavesSupportThumbnail: case kSavesSupportCreationDate: case kSavesSupportPlayTime: + case kSimpleSavesNames: return true; default: return false; diff --git a/engines/tsage/stP1kAlM b/engines/tsage/stP1kAlM Binary files differnew file mode 100644 index 0000000000..dfbb3b9786 --- /dev/null +++ b/engines/tsage/stP1kAlM diff --git a/engines/voyeur/detection.cpp b/engines/voyeur/detection.cpp index 7b9fa6722e..eefe174e94 100644 --- a/engines/voyeur/detection.cpp +++ b/engines/voyeur/detection.cpp @@ -92,7 +92,8 @@ bool VoyeurMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsLoadingDuringStartup) || (f == kSupportsDeleteSave) || (f == kSavesSupportMetaInfo) || - (f == kSavesSupportThumbnail); + (f == kSavesSupportThumbnail) || + (f == kSimpleSavesNames); } bool Voyeur::VoyeurEngine::hasFeature(EngineFeature f) const { diff --git a/engines/wage/design.cpp b/engines/wage/design.cpp index 86b325e2b9..fd2a67b81e 100644 --- a/engines/wage/design.cpp +++ b/engines/wage/design.cpp @@ -235,9 +235,9 @@ void drawPixel(int x, int y, int color, void *data) { color : kColorWhite; } } else { - int x1 = x; + int x1 = x - p->thickness / 2; int x2 = x1 + p->thickness; - int y1 = y; + int y1 = y - p->thickness / 2; int y2 = y1 + p->thickness; for (y = y1; y < y2; y++) diff --git a/engines/wage/detection.cpp b/engines/wage/detection.cpp index a27bfd7fde..778cd3c7a7 100644 --- a/engines/wage/detection.cpp +++ b/engines/wage/detection.cpp @@ -78,7 +78,8 @@ bool WageMetaEngine::hasFeature(MetaEngineFeature f) const { return (f == kSupportsListSaves) || (f == kSupportsLoadingDuringStartup) || - (f == kSupportsDeleteSave); + (f == kSupportsDeleteSave) || + (f == kSimpleSavesNames); } bool Wage::WageEngine::hasFeature(EngineFeature f) const { diff --git a/engines/wage/saveload.cpp b/engines/wage/saveload.cpp index c3b20bdf2f..da10ad41c1 100644 --- a/engines/wage/saveload.cpp +++ b/engines/wage/saveload.cpp @@ -109,7 +109,7 @@ Obj *WageEngine::getObjByOffset(int offset, int objBaseOffset) const { objIndex = (offset - objBaseOffset) / CHR_SIZE; } - if (objIndex >= 0 && objIndex < _world->_orderedObjs.size()) { + if (objIndex >= 0 && (uint)objIndex < _world->_orderedObjs.size()) { return _world->_orderedObjs[objIndex]; } @@ -133,7 +133,7 @@ Chr *WageEngine::getChrByOffset(int offset, int chrBaseOffset) const { chrIndex = (offset - chrBaseOffset) / CHR_SIZE; } - if (chrIndex >= 0 && chrIndex < _world->_orderedChrs.size()) { + if (chrIndex >= 0 && (uint)chrIndex < _world->_orderedChrs.size()) { return _world->_orderedChrs[chrIndex]; } @@ -160,7 +160,7 @@ Scene *WageEngine::getSceneByOffset(int offset) const { sceneIndex = 1 + (offset - SCENES_INDEX) / SCENE_SIZE; } - if (sceneIndex >= 0 && sceneIndex < _world->_orderedScenes.size()) { + if (sceneIndex >= 0 && (uint)sceneIndex < _world->_orderedScenes.size()) { if (sceneIndex == 0) return _world->_storageScene; return _world->_orderedScenes[sceneIndex]; } @@ -224,10 +224,10 @@ int WageEngine::saveGame(const Common::String &fileName, const Common::String &d out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::MAGIC_ARMOR])); //sprtArmIndex // TODO: - out->writeSint16LE(0xffff); // ???? - always FFFF - out->writeSint16LE(0xffff); // ???? - always FFFF - out->writeSint16LE(0xffff); // ???? - always FFFF - out->writeSint16LE(0xffff); // ???? - always FFFF + out->writeUint16LE(0xffff); // ???? - always FFFF + out->writeUint16LE(0xffff); // ???? - always FFFF + out->writeUint16LE(0xffff); // ???? - always FFFF + out->writeUint16LE(0xffff); // ???? - always FFFF // did a character just escape? out->writeSint32LE(GET_HEX_CHR_OFFSET(_running)); //getRunCharHexOffset() == getHexOffsetForChr(running) @@ -260,7 +260,7 @@ int WageEngine::saveGame(const Common::String &fileName, const Common::String &d // write user vars for (uint32 i = 0; i < 26 * 9; ++i) out->writeSint16LE(playerContext._userVariables[i]); - + // write updated info for all scenes Common::Array<Scene *> &orderedScenes = _world->_orderedScenes; for (uint32 i = 0; i < orderedScenes.size(); ++i) { @@ -520,12 +520,12 @@ int WageEngine::loadGame(int slotId) { // set all user variables for (uint32 i = 0; i < 26 * 9; ++i) { - playerContext._userVariables[i] = data->readSint16LE(); + playerContext._userVariables[i] = data->readSint16LE(); } // update all scene stats Common::Array<Scene *> &orderedScenes = _world->_orderedScenes; - if (numScenes != orderedScenes.size()) { + if ((uint)numScenes != orderedScenes.size()) { warning("scenes number in file (%d) differs from the one in world (%d)", numScenes, orderedScenes.size()); } for (uint32 i = 0; i < orderedScenes.size(); ++i) { @@ -559,7 +559,7 @@ int WageEngine::loadGame(int slotId) { // update all char locations and stats Common::Array<Chr *> &orderedChrs = _world->_orderedChrs; - if (numChars != orderedChrs.size()) { + if ((uint)numChars != orderedChrs.size()) { warning("characters number in file (%d) differs from the one in world (%d)", numChars, orderedChrs.size()); } for (uint32 i = 0; i < orderedChrs.size(); ++i) { @@ -613,7 +613,7 @@ int WageEngine::loadGame(int slotId) { // update all object locations and stats Common::Array<Obj *> &orderedObjs = _world->_orderedObjs; - if (numObjs != orderedObjs.size()) { + if ((uint)numObjs != orderedObjs.size()) { warning("objects number in file (%d) differs from the one in world (%d)", numObjs, orderedObjs.size()); } for (uint32 i = 0; i < orderedObjs.size(); ++i) { @@ -727,7 +727,7 @@ Common::String WageEngine::getSavegameFilename(int16 slotId) const { return saveLoadSlot; } -Common::Error WageEngine::loadGameState(int slot) { +Common::Error WageEngine::loadGameState(int slot) { if (loadGame(slot) == 0) return Common::kNoError; else diff --git a/engines/wintermute/base/base_game.cpp b/engines/wintermute/base/base_game.cpp index ce4c5fdda5..1af7e2b56d 100644 --- a/engines/wintermute/base/base_game.cpp +++ b/engines/wintermute/base/base_game.cpp @@ -71,7 +71,7 @@ #include "common/system.h" #include "common/file.h" -#if EXTENDED_DEBUGGER_ENABLED == true +#if EXTENDED_DEBUGGER_ENABLED #include "engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.h" #endif @@ -402,7 +402,7 @@ bool BaseGame::initialize1() { break; } -#if EXTENDED_DEBUGGER_ENABLED == true +#if EXTENDED_DEBUGGER_ENABLED _scEngine = new DebuggableScEngine(this); #else _scEngine = new ScEngine(this); diff --git a/engines/wintermute/base/base_game.h b/engines/wintermute/base/base_game.h index 409cc20ba4..6aacc1feab 100644 --- a/engines/wintermute/base/base_game.h +++ b/engines/wintermute/base/base_game.h @@ -36,7 +36,7 @@ #include "engines/wintermute/math/rect32.h" #include "engines/wintermute/debugger.h" #include "common/events.h" -#if EXTENDED_DEBUGGER_ENABLED == true +#if EXTENDED_DEBUGGER_ENABLED #include "engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.h" #endif @@ -152,7 +152,7 @@ public: BaseRenderer *_renderer; BaseSoundMgr *_soundMgr; -#if EXTENDED_DEBUGGER_ENABLED == true +#if EXTENDED_DEBUGGER_ENABLED DebuggableScEngine *_scEngine; #else ScEngine *_scEngine; diff --git a/engines/wintermute/base/base_script_holder.cpp b/engines/wintermute/base/base_script_holder.cpp index 7427a9b082..fd9dd6a2a5 100644 --- a/engines/wintermute/base/base_script_holder.cpp +++ b/engines/wintermute/base/base_script_holder.cpp @@ -466,7 +466,7 @@ void BaseScriptHolder::makeFreezable(bool freezable) { ScScript *BaseScriptHolder::invokeMethodThread(const char *methodName) { for (int i = _scripts.size() - 1; i >= 0; i--) { if (_scripts[i]->canHandleMethod(methodName)) { -#if EXTENDED_DEBUGGER_ENABLED == true +#if EXTENDED_DEBUGGER_ENABLED DebuggableScEngine* debuggableEngine; debuggableEngine = dynamic_cast<DebuggableScEngine*>(_scripts[i]->_engine); // TODO: Not pretty diff --git a/engines/wintermute/base/scriptables/script.cpp b/engines/wintermute/base/scriptables/script.cpp index 938ec031da..c13310255d 100644 --- a/engines/wintermute/base/scriptables/script.cpp +++ b/engines/wintermute/base/scriptables/script.cpp @@ -32,7 +32,7 @@ #include "engines/wintermute/base/scriptables/script_engine.h" #include "engines/wintermute/base/scriptables/script_stack.h" #include "common/memstream.h" -#if EXTENDED_DEBUGGER_ENABLED == true +#if EXTENDED_DEBUGGER_ENABLED #include "engines/wintermute/base/scriptables/debuggable/debuggable_script.h" #endif namespace Wintermute { @@ -1320,7 +1320,7 @@ ScScript *ScScript::invokeEventHandler(const Common::String &eventName, bool unb if (!pos) { return nullptr; } -#if EXTENDED_DEBUGGER_ENABLED == true +#if EXTENDED_DEBUGGER_ENABLED // TODO: Not pretty DebuggableScEngine* debuggableEngine; debuggableEngine = dynamic_cast<DebuggableScEngine*>(_engine); diff --git a/engines/wintermute/base/scriptables/script_engine.cpp b/engines/wintermute/base/scriptables/script_engine.cpp index 26122094f1..8d957c6951 100644 --- a/engines/wintermute/base/scriptables/script_engine.cpp +++ b/engines/wintermute/base/scriptables/script_engine.cpp @@ -144,7 +144,7 @@ ScScript *ScEngine::runScript(const char *filename, BaseScriptHolder *owner) { } // add new script -#if EXTENDED_DEBUGGER_ENABLED == true +#if EXTENDED_DEBUGGER_ENABLED DebuggableScEngine* debuggableEngine; debuggableEngine = dynamic_cast<DebuggableScEngine*>(this); // TODO: Not pretty diff --git a/engines/wintermute/debugger.h b/engines/wintermute/debugger.h index e5008bee3b..6b1d2312ba 100644 --- a/engines/wintermute/debugger.h +++ b/engines/wintermute/debugger.h @@ -23,11 +23,11 @@ #ifndef WINTERMUTE_DEBUGGER_H #define WINTERMUTE_DEBUGGER_H -#define EXTENDED_DEBUGGER_ENABLED true +#define EXTENDED_DEBUGGER_ENABLED 1 #include "gui/debugger.h" -#if EXTENDED_DEBUGGER_ENABLED == true +#if EXTENDED_DEBUGGER_ENABLED #include "engines/wintermute/base/scriptables/debuggable/debuggable_script.h" #else #include "engines/wintermute/base/scriptables/script.h" @@ -71,7 +71,7 @@ public: bool Cmd_ShowFps(int argc, const char **argv); bool Cmd_DumpFile(int argc, const char **argv); -#if EXTENDED_DEBUGGER_ENABLED == true +#if EXTENDED_DEBUGGER_ENABLED /** * Step - break again on next line */ diff --git a/engines/zvision/detection.cpp b/engines/zvision/detection.cpp index cc967070d9..5e535a9954 100644 --- a/engines/zvision/detection.cpp +++ b/engines/zvision/detection.cpp @@ -87,7 +87,8 @@ bool ZVisionMetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsDeleteSave) || (f == kSavesSupportMetaInfo) || (f == kSavesSupportThumbnail) || - (f == kSavesSupportCreationDate); + (f == kSavesSupportCreationDate) || + (f == kSimpleSavesNames); //(f == kSavesSupportPlayTime); } diff --git a/graphics/VectorRenderer.h b/graphics/VectorRenderer.h index 0352808706..5f7b6e60d3 100644 --- a/graphics/VectorRenderer.h +++ b/graphics/VectorRenderer.h @@ -28,6 +28,7 @@ #include "common/str.h" #include "graphics/surface.h" +#include "graphics/transparent_surface.h" #include "gui/ThemeEngine.h" @@ -79,8 +80,11 @@ struct DrawStep { uint32 scale; /**< scale of all the coordinates in FIXED POINT with 16 bits mantissa */ + GUI::ThemeEngine::AutoScaleMode autoscale; /**< scale alphaimage if present */ + DrawingFunctionCallback drawingCall; /**< Pointer to drawing function */ Graphics::Surface *blitSrc; + Graphics::TransparentSurface *blitAlphaSrc; }; VectorRenderer *createRenderer(int mode); @@ -281,7 +285,7 @@ public: * * @param surface Pointer to a Surface object. */ - virtual void setSurface(Surface *surface) { + virtual void setSurface(TransparentSurface *surface) { _activeSurface = surface; } @@ -420,7 +424,13 @@ public: void drawCallback_BITMAP(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { uint16 x, y, w, h; stepGetPositions(step, area, x, y, w, h); - blitAlphaBitmapClip(step.blitSrc, Common::Rect(x, y, x + w, y + h), clip); + blitKeyBitmapClip(step.blitSrc, Common::Rect(x, y, x + w, y + h), clip); + } + + void drawCallback_ALPHABITMAP(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { + uint16 x, y, w, h; + stepGetPositions(step, area, x, y, w, h); + blitAlphaBitmap(step.blitAlphaSrc, Common::Rect(x, y, x + w, y + h), step.autoscale, step.xAlign, step.yAlign); //TODO } void drawCallback_CROSS(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { @@ -482,8 +492,14 @@ public: virtual void blitSubSurface(const Graphics::Surface *source, const Common::Rect &r) = 0; virtual void blitSubSurfaceClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) = 0; - virtual void blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r) = 0; - virtual void blitAlphaBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) = 0; + virtual void blitKeyBitmap(const Graphics::Surface *source, const Common::Rect &r) = 0; + virtual void blitKeyBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) = 0; + + virtual void blitAlphaBitmap(Graphics::TransparentSurface *source, const Common::Rect &r, + GUI::ThemeEngine::AutoScaleMode autoscale = GUI::ThemeEngine::kAutoScaleNone, + Graphics::DrawStep::VectorAlignment xAlign = Graphics::DrawStep::kVectorAlignManual, + Graphics::DrawStep::VectorAlignment yAlign = Graphics::DrawStep::kVectorAlignManual, + int alpha = 255) = 0; /** * Draws a string into the screen. Wrapper for the Graphics::Font string drawing @@ -507,7 +523,7 @@ public: virtual void applyScreenShading(GUI::ThemeEngine::ShadingStyle) = 0; protected: - Surface *_activeSurface; /**< Pointer to the surface currently being drawn */ + TransparentSurface *_activeSurface; /**< Pointer to the surface currently being drawn */ FillMode _fillMode; /**< Defines in which way (if any) are filled the drawn shapes */ ShadowFillMode _shadowFillMode; diff --git a/graphics/VectorRendererSpec.cpp b/graphics/VectorRendererSpec.cpp index fc741f6e77..9aed3301fa 100644 --- a/graphics/VectorRendererSpec.cpp +++ b/graphics/VectorRendererSpec.cpp @@ -25,6 +25,8 @@ #include "common/frac.h" #include "graphics/surface.h" +#include "graphics/transparent_surface.h" +#include "graphics/nine_patch.h" #include "graphics/colormasks.h" #include "gui/ThemeEngine.h" @@ -848,8 +850,8 @@ blitSubSurfaceClip(const Graphics::Surface *source, const Common::Rect &r, const } template<typename PixelType> -void VectorRendererSpec<PixelType>:: -blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r) { +void VectorRendererSpec<PixelType>:: +blitKeyBitmap(const Graphics::Surface *source, const Common::Rect &r) { int16 x = r.left; int16 y = r.top; @@ -885,9 +887,43 @@ blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r) { template<typename PixelType> void VectorRendererSpec<PixelType>:: -blitAlphaBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) { +blitAlphaBitmap(Graphics::TransparentSurface *source, const Common::Rect &r, GUI::ThemeEngine::AutoScaleMode autoscale, + Graphics::DrawStep::VectorAlignment xAlign, Graphics::DrawStep::VectorAlignment yAlign, int alpha) { + if (autoscale == GUI::ThemeEngine::kAutoScaleStretch) { + source->blit(*_activeSurface, r.left, r.top, Graphics::FLIP_NONE, + nullptr, TS_ARGB(alpha, 255, 255, 255), + r.width(), r.height()); + } else if (autoscale == GUI::ThemeEngine::kAutoScaleFit) { + double ratio = (double)r.width() / source->w; + double ratio2 = (double)r.height() / source->h; + + if (ratio2 < ratio) + ratio = ratio2; + + int offx = 0, offy = 0; + if (xAlign == Graphics::DrawStep::kVectorAlignCenter) + offx = (r.width() - (int)(source->w * ratio)) >> 1; + + if (yAlign == Graphics::DrawStep::kVectorAlignCenter) + offy = (r.height() - (int)(source->h * ratio)) >> 1; + + source->blit(*_activeSurface, r.left + offx, r.top + offy, Graphics::FLIP_NONE, + nullptr, TS_ARGB(alpha, 255, 255, 255), + (int)(source->w * ratio), (int)(source->h * ratio)); + + } else if (autoscale == GUI::ThemeEngine::kAutoScaleNinePatch) { + Graphics::NinePatchBitmap nine(source, false); + nine.blit(*_activeSurface, r.left, r.top, r.width(), r.height()); + } else { + source->blit(*_activeSurface, r.left, r.top); + } +} + +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +blitKeyBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) { if (clipping.isEmpty() || clipping.contains(r)) { - blitAlphaBitmap(source, r); + blitKeyBitmap(source, r); return; } diff --git a/graphics/VectorRendererSpec.h b/graphics/VectorRendererSpec.h index bee6d4c425..84c802f6df 100644 --- a/graphics/VectorRendererSpec.h +++ b/graphics/VectorRendererSpec.h @@ -93,8 +93,13 @@ public: void blitSurface(const Graphics::Surface *source, const Common::Rect &r); void blitSubSurface(const Graphics::Surface *source, const Common::Rect &r); void blitSubSurfaceClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping); - void blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r); - void blitAlphaBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping); + void blitKeyBitmap(const Graphics::Surface *source, const Common::Rect &r); + void blitKeyBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping); + void blitAlphaBitmap(Graphics::TransparentSurface *source, const Common::Rect &r, + GUI::ThemeEngine::AutoScaleMode autoscale = GUI::ThemeEngine::kAutoScaleNone, + Graphics::DrawStep::VectorAlignment xAlign = Graphics::DrawStep::kVectorAlignManual, + Graphics::DrawStep::VectorAlignment yAlign = Graphics::DrawStep::kVectorAlignManual, + int alpha = 255); void applyScreenShading(GUI::ThemeEngine::ShadingStyle shadingStyle); diff --git a/graphics/primitives.cpp b/graphics/primitives.cpp index ac1c58b1d8..8663a61606 100644 --- a/graphics/primitives.cpp +++ b/graphics/primitives.cpp @@ -108,15 +108,13 @@ void drawThickLine2(int x1, int y1, int x2, int y2, int thick, int color, void ( int dy = abs(y2 - y1); if (dx == 0) { - if (y1 > y2) - SWAP(y1, y2); - Common::Rect r(x1, y1, x1 + thick - 1, y2); + int xn = x1 - thick / 2; + Common::Rect r(xn, MIN(y1, y2), xn + thick - 1, MAX(y1, y2)); drawFilledRect(r, color, plotProc, data); return; } else if (dy == 0) { - if (x1 > x2) - SWAP(x1, x2); - Common::Rect r(x1, y1, x2, y1 + thick - 1); + int yn = y1 - thick / 2; + Common::Rect r(MIN(x1, x2), yn, MAX(x1, x2), yn + thick - 1); drawFilledRect(r, color, plotProc, data); return; } diff --git a/graphics/transparent_surface.cpp b/graphics/transparent_surface.cpp index c2903d42c3..81b7f3d647 100644 --- a/graphics/transparent_surface.cpp +++ b/graphics/transparent_surface.cpp @@ -346,7 +346,7 @@ Common::Rect TransparentSurface::blit(Graphics::Surface &target, int posX, int p TransparentSurface srcImage(*this, false); // TODO: Is the data really in the screen format? if (format.bytesPerPixel != 4) { - warning("TransparentSurface can only blit 32bpp images, but got %d", format.bytesPerPixel * 8); + warning("TransparentSurface can only blit 32bpp images, but got %d", format.bytesPerPixel * 8); return retSize; } @@ -979,4 +979,82 @@ TransparentSurface *TransparentSurface::scale(uint16 newWidth, uint16 newHeight) } +TransparentSurface *TransparentSurface::convertTo(const PixelFormat &dstFormat, const byte *palette) const { + assert(pixels); + + TransparentSurface *surface = new TransparentSurface(); + + // If the target format is the same, just copy + if (format == dstFormat) { + surface->copyFrom(*this); + return surface; + } + + if (format.bytesPerPixel == 0 || format.bytesPerPixel > 4) + error("Surface::convertTo(): Can only convert from 1Bpp, 2Bpp, 3Bpp, and 4Bpp"); + + if (dstFormat.bytesPerPixel != 2 && dstFormat.bytesPerPixel != 4) + error("Surface::convertTo(): Can only convert to 2Bpp and 4Bpp"); + + surface->create(w, h, dstFormat); + + if (format.bytesPerPixel == 1) { + // Converting from paletted to high color + assert(palette); + + for (int y = 0; y < h; y++) { + const byte *srcRow = (const byte *)getBasePtr(0, y); + byte *dstRow = (byte *)surface->getBasePtr(0, y); + + for (int x = 0; x < w; x++) { + byte index = *srcRow++; + byte r = palette[index * 3]; + byte g = palette[index * 3 + 1]; + byte b = palette[index * 3 + 2]; + + uint32 color = dstFormat.RGBToColor(r, g, b); + + if (dstFormat.bytesPerPixel == 2) + *((uint16 *)dstRow) = color; + else + *((uint32 *)dstRow) = color; + + dstRow += dstFormat.bytesPerPixel; + } + } + } else { + // Converting from high color to high color + for (int y = 0; y < h; y++) { + const byte *srcRow = (const byte *)getBasePtr(0, y); + byte *dstRow = (byte *)surface->getBasePtr(0, y); + + for (int x = 0; x < w; x++) { + uint32 srcColor; + if (format.bytesPerPixel == 2) + srcColor = READ_UINT16(srcRow); + else if (format.bytesPerPixel == 3) + srcColor = READ_UINT24(srcRow); + else + srcColor = READ_UINT32(srcRow); + + srcRow += format.bytesPerPixel; + + // Convert that color to the new format + byte r, g, b, a; + format.colorToARGB(srcColor, a, r, g, b); + uint32 color = dstFormat.ARGBToColor(a, r, g, b); + + if (dstFormat.bytesPerPixel == 2) + *((uint16 *)dstRow) = color; + else + *((uint32 *)dstRow) = color; + + dstRow += dstFormat.bytesPerPixel; + } + } + } + + return surface; +} + } // End of namespace Graphics diff --git a/graphics/transparent_surface.h b/graphics/transparent_surface.h index c0d3d26e52..8654183548 100644 --- a/graphics/transparent_surface.h +++ b/graphics/transparent_surface.h @@ -151,6 +151,16 @@ struct TransparentSurface : public Graphics::Surface { * */ TransparentSurface *rotoscale(const TransformStruct &transform) const; + + TransparentSurface *convertTo(const PixelFormat &dstFormat, const byte *palette = 0) const; + + float getRatio() { + if (!w) + return 0; + + return h / (float)w; + } + AlphaType getAlphaMode() const; void setAlphaMode(AlphaType); private: diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp index c850a6a02e..96108bccce 100644 --- a/gui/ThemeEngine.cpp +++ b/gui/ThemeEngine.cpp @@ -31,11 +31,13 @@ #include "graphics/cursorman.h" #include "graphics/fontman.h" #include "graphics/surface.h" +#include "graphics/transparent_surface.h" #include "graphics/VectorRenderer.h" #include "graphics/fonts/bdf.h" #include "graphics/fonts/ttf.h" #include "image/bmp.h" +#include "image/png.h" #include "gui/widget.h" #include "gui/ThemeEngine.h" @@ -59,6 +61,10 @@ const char *const ThemeEngine::kImageStopSmallButton = "stopbtn_small.bmp"; const char *const ThemeEngine::kImageEditSmallButton = "editbtn_small.bmp"; const char *const ThemeEngine::kImageSwitchModeSmallButton = "switchbtn_small.bmp"; const char *const ThemeEngine::kImageFastReplaySmallButton = "fastreplay_small.bmp"; +const char *const ThemeEngine::kImageDropboxLogo = "dropbox.bmp"; +const char *const ThemeEngine::kImageOneDriveLogo = "onedrive.bmp"; +const char *const ThemeEngine::kImageGoogleDriveLogo = "googledrive.bmp"; +const char *const ThemeEngine::kImageBoxLogo = "box.bmp"; struct TextDrawData { const Graphics::Font *_fontPtr; @@ -168,11 +174,23 @@ protected: bool _alpha; }; +class ThemeItemABitmap : public ThemeItem { +public: + ThemeItemABitmap(ThemeEngine *engine, const Common::Rect &area, Graphics::TransparentSurface *bitmap, ThemeEngine::AutoScaleMode autoscale, int alpha) : + ThemeItem(engine, area), _bitmap(bitmap), _autoscale(autoscale), _alpha(alpha) {} + + void drawSelf(bool draw, bool restore); + +protected: + Graphics::TransparentSurface *_bitmap; + ThemeEngine::AutoScaleMode _autoscale; + int _alpha; +}; + class ThemeItemBitmapClip : public ThemeItem { public: ThemeItemBitmapClip(ThemeEngine *engine, const Common::Rect &area, const Common::Rect &clip, const Graphics::Surface *bitmap, bool alpha) : ThemeItem(engine, area), _bitmap(bitmap), _alpha(alpha), _clip(clip) {} - void drawSelf(bool draw, bool restore); protected: @@ -308,7 +326,7 @@ void ThemeItemBitmap::drawSelf(bool draw, bool restore) { if (draw) { if (_alpha) - _engine->renderer()->blitAlphaBitmap(_bitmap, _area); + _engine->renderer()->blitKeyBitmap(_bitmap, _area); else _engine->renderer()->blitSubSurface(_bitmap, _area); } @@ -316,13 +334,23 @@ void ThemeItemBitmap::drawSelf(bool draw, bool restore) { _engine->addDirtyRect(_area); } -void ThemeItemBitmapClip::drawSelf(bool draw, bool restore) { +void ThemeItemABitmap::drawSelf(bool draw, bool restore) { if (restore) _engine->restoreBackground(_area); + if (draw) + _engine->renderer()->blitAlphaBitmap(_bitmap, _area, _autoscale, Graphics::DrawStep::kVectorAlignManual, Graphics::DrawStep::kVectorAlignManual, _alpha); + + _engine->addDirtyRect(_area); +} + +void ThemeItemBitmapClip::drawSelf(bool draw, bool restore) { + if (restore) + _engine->restoreBackground(_area); + if (draw) { if (_alpha) - _engine->renderer()->blitAlphaBitmapClip(_bitmap, _area, _clip); + _engine->renderer()->blitKeyBitmapClip(_bitmap, _area, _clip); else _engine->renderer()->blitSubSurfaceClip(_bitmap, _area, _clip); } @@ -401,6 +429,15 @@ ThemeEngine::~ThemeEngine() { } _bitmaps.clear(); + for (AImagesMap::iterator i = _abitmaps.begin(); i != _abitmaps.end(); ++i) { + Graphics::TransparentSurface *surf = i->_value; + if (surf) { + surf->free(); + delete surf; + } + } + _abitmaps.clear(); + delete _parser; delete _themeEval; delete[] _cursor; @@ -525,6 +562,15 @@ void ThemeEngine::refresh() { } } _bitmaps.clear(); + + for (AImagesMap::iterator i = _abitmaps.begin(); i != _abitmaps.end(); ++i) { + Graphics::TransparentSurface *surf = i->_value; + if (surf) { + surf->free(); + delete surf; + } + } + _abitmaps.clear(); } init(); @@ -706,24 +752,51 @@ bool ThemeEngine::addBitmap(const Common::String &filename) { if (surf) return true; - // If not, try to load the bitmap via the BitmapDecoder class. - Image::BitmapDecoder bitmapDecoder; const Graphics::Surface *srcSurface = 0; - Common::ArchiveMemberList members; - _themeFiles.listMatchingMembers(members, filename); - for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) { - Common::SeekableReadStream *stream = (*i)->createReadStream(); - if (stream) { - bitmapDecoder.loadStream(*stream); - srcSurface = bitmapDecoder.getSurface(); - delete stream; - if (srcSurface) - break; + + if (filename.hasSuffix(".png")) { + // Maybe it is PNG? +#ifdef USE_PNG + Image::PNGDecoder decoder; + Common::ArchiveMemberList members; + _themeFiles.listMatchingMembers(members, filename); + for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) { + Common::SeekableReadStream *stream = (*i)->createReadStream(); + if (stream) { + if (!decoder.loadStream(*stream)) + error("Error decoding PNG"); + + srcSurface = decoder.getSurface(); + delete stream; + if (srcSurface) + break; + } } - } - if (srcSurface && srcSurface->format.bytesPerPixel != 1) - surf = srcSurface->convertTo(_overlayFormat); + if (srcSurface && srcSurface->format.bytesPerPixel != 1) + surf = srcSurface->convertTo(_overlayFormat); +#else + error("No PNG support compiled in"); +#endif + } else { + // If not, try to load the bitmap via the BitmapDecoder class. + Image::BitmapDecoder bitmapDecoder; + Common::ArchiveMemberList members; + _themeFiles.listMatchingMembers(members, filename); + for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) { + Common::SeekableReadStream *stream = (*i)->createReadStream(); + if (stream) { + bitmapDecoder.loadStream(*stream); + srcSurface = bitmapDecoder.getSurface(); + delete stream; + if (srcSurface) + break; + } + } + + if (srcSurface && srcSurface->format.bytesPerPixel != 1) + surf = srcSurface->convertTo(_overlayFormat); + } // Store the surface into our hashmap (attention, may store NULL entries!) _bitmaps[filename] = surf; @@ -731,6 +804,48 @@ bool ThemeEngine::addBitmap(const Common::String &filename) { return surf != 0; } +bool ThemeEngine::addAlphaBitmap(const Common::String &filename) { + // Nothing has to be done if the bitmap already has been loaded. + Graphics::TransparentSurface *surf = _abitmaps[filename]; + if (surf) + return true; + + const Graphics::TransparentSurface *srcSurface = 0; + + if (filename.hasSuffix(".png")) { + // Maybe it is PNG? +#ifdef USE_PNG + Image::PNGDecoder decoder; + Common::ArchiveMemberList members; + _themeFiles.listMatchingMembers(members, filename); + for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) { + Common::SeekableReadStream *stream = (*i)->createReadStream(); + if (stream) { + if (!decoder.loadStream(*stream)) + error("Error decoding PNG"); + + srcSurface = new Graphics::TransparentSurface(*decoder.getSurface(), true); + delete stream; + if (srcSurface) + break; + } + } + + if (srcSurface && srcSurface->format.bytesPerPixel != 1) + surf = srcSurface->convertTo(_overlayFormat); +#else + error("No PNG support compiled in"); +#endif + } else { + error("Only PNG is supported as alphabitmap"); + } + + // Store the surface into our hashmap (attention, may store NULL entries!) + _abitmaps[filename] = surf; + + return surf != 0; +} + bool ThemeEngine::addDrawData(const Common::String &data, bool cached) { DrawData id = parseDrawDataId(data); @@ -977,7 +1092,10 @@ void ThemeEngine::queueDDTextClip(TextData type, TextColor color, const Common:: area.clip(_screen.w, _screen.h); Common::Rect textArea = drawableTextArea; if (textArea.isEmpty()) textArea = clippingArea; - else textArea.clip(clippingArea); + else { + textArea.clip(clippingArea); + if (textArea.isEmpty()) textArea = Common::Rect(0, 0, 1, 1); // one small pixel should be invisible enough + } ThemeItemTextData *q = new ThemeItemTextData(this, _texts[type], _textColors[color], area, textArea, text, alignH, alignV, ellipsis, restoreBg, deltax); @@ -1004,6 +1122,21 @@ void ThemeEngine::queueBitmap(const Graphics::Surface *bitmap, const Common::Rec } } +void ThemeEngine::queueABitmap(Graphics::TransparentSurface *bitmap, const Common::Rect &r, AutoScaleMode autoscale, int alpha) { + + Common::Rect area = r; + area.clip(_screen.w, _screen.h); + + ThemeItemABitmap *q = new ThemeItemABitmap(this, area, bitmap, autoscale, alpha); + + if (_buffering) { + _screenQueue.push_back(q); + } else { + q->drawSelf(true, false); + delete q; + } +} + void ThemeEngine::queueBitmapClip(const Graphics::Surface *bitmap, const Common::Rect &r, const Common::Rect &clip, bool alpha) { Common::Rect area = r; @@ -1291,6 +1424,8 @@ void ThemeEngine::drawDialogBackground(const Common::Rect &r, DialogBackground b case kDialogBackgroundDefault: queueDD(kDDDefaultBackground, r); break; + case kDialogBackgroundNone: + break; } } @@ -1318,6 +1453,9 @@ void ThemeEngine::drawDialogBackgroundClip(const Common::Rect &r, const Common:: case kDialogBackgroundDefault: queueDDClip(kDDDefaultBackground, r, clip); break; + case kDialogBackgroundNone: + // no op + break; } } @@ -1392,6 +1530,13 @@ void ThemeEngine::drawSurface(const Common::Rect &r, const Graphics::Surface &su queueBitmap(&surface, r, themeTrans); } +void ThemeEngine::drawASurface(const Common::Rect &r, Graphics::TransparentSurface &surface, AutoScaleMode autoscale, int alpha) { + if (!ready()) + return; + + queueABitmap(&surface, r, autoscale, alpha); +} + void ThemeEngine::drawSurfaceClip(const Common::Rect &r, const Common::Rect &clip, const Graphics::Surface &surface, WidgetStateInfo state, int alpha, bool themeTrans) { if (!ready()) return; diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h index 3c259b4f9d..7506cee95f 100644 --- a/gui/ThemeEngine.h +++ b/gui/ThemeEngine.h @@ -32,6 +32,7 @@ #include "common/rect.h" #include "graphics/surface.h" +#include "graphics/transparent_surface.h" #include "graphics/font.h" #include "graphics/pixelformat.h" @@ -140,6 +141,7 @@ enum TextColor { class ThemeEngine { protected: typedef Common::HashMap<Common::String, Graphics::Surface *> ImagesMap; + typedef Common::HashMap<Common::String, Graphics::TransparentSurface *> AImagesMap; friend class GUI::Dialog; friend class GUI::GuiObject; @@ -169,7 +171,8 @@ public: kDialogBackgroundSpecial, kDialogBackgroundPlain, kDialogBackgroundTooltip, - kDialogBackgroundDefault + kDialogBackgroundDefault, + kDialogBackgroundNone }; /// State of the widget to be drawn @@ -223,6 +226,14 @@ public: kShadingLuminance ///< Converting colors to luminance for unused areas }; + /// AlphaBitmap scale mode selector + enum AutoScaleMode { + kAutoScaleNone = 0, ///< Use image dimensions + kAutoScaleStretch = 1, ///< Stretch image to full widget size + kAutoScaleFit = 2, ///< Scale image to widget size but keep aspect ratio + kAutoScaleNinePatch = 3 ///< 9-patch image + }; + // Special image ids for images used in the GUI static const char *const kImageLogo; ///< ScummVM logo used in the launcher static const char *const kImageLogoSmall; ///< ScummVM logo used in the GMM @@ -239,6 +250,10 @@ public: static const char *const kImageEditSmallButton; ///< Edit recording metadata in recorder onscreen dialog (for 320xY) static const char *const kImageSwitchModeSmallButton; ///< Switch mode button in recorder onscreen dialog (for 320xY) static const char *const kImageFastReplaySmallButton; ///< Fast playback mode button in recorder onscreen dialog (for 320xY) + static const char *const kImageDropboxLogo; ///< Dropbox logo used in the StorageWizardDialog + static const char *const kImageOneDriveLogo; ///< OneDrive logo used in the StorageWizardDialog + static const char *const kImageGoogleDriveLogo; ///< Google Drive logo used in the StorageWizardDialog + static const char *const kImageBoxLogo; ///< Box logo used in the StorageWizardDialog /** * Graphics mode enumeration. @@ -353,6 +368,8 @@ public: void drawSurfaceClip(const Common::Rect &r, const Common::Rect &clippingRect, const Graphics::Surface &surface, WidgetStateInfo state = kStateEnabled, int alpha = 255, bool themeTrans = false); + void drawASurface(const Common::Rect &r, Graphics::TransparentSurface &surface, AutoScaleMode autoscale, int alpha); + void drawSlider(const Common::Rect &r, int width, WidgetStateInfo state = kStateEnabled); void drawSliderClip(const Common::Rect &r, const Common::Rect &clippingRect, int width, @@ -483,6 +500,14 @@ public: bool addBitmap(const Common::String &filename); /** + * Interface for the ThemeParser class: Loads a bitmap with transparency file to use on the GUI. + * The filename is also used as its identifier. + * + * @param filename Name of the bitmap file. + */ + bool addAlphaBitmap(const Common::String &filename); + + /** * Adds a new TextStep from the ThemeParser. This will be deprecated/removed once the * new Font API is in place. FIXME: Is that so ??? */ @@ -526,10 +551,18 @@ public: return _bitmaps.contains(name) ? _bitmaps[name] : 0; } + Graphics::TransparentSurface *getAlphaBitmap(const Common::String &name) { + return _abitmaps.contains(name) ? _abitmaps[name] : 0; + } + const Graphics::Surface *getImageSurface(const Common::String &name) const { return _bitmaps.contains(name) ? _bitmaps[name] : 0; } + const Graphics::TransparentSurface *getAImageSurface(const Common::String &name) const { + return _abitmaps.contains(name) ? _abitmaps[name] : 0; + } + /** * Interface for the Theme Parser: Creates a new cursor by loading the given * bitmap and sets it as the active cursor. @@ -616,6 +649,7 @@ protected: bool elipsis, Graphics::TextAlign alignH = Graphics::kTextAlignLeft, TextAlignVertical alignV = kTextAlignVTop, int deltax = 0, const Common::Rect &drawableTextArea = Common::Rect(0, 0, 0, 0)); void queueBitmap(const Graphics::Surface *bitmap, const Common::Rect &r, bool alpha); void queueBitmapClip(const Graphics::Surface *bitmap, const Common::Rect &clippingRect, const Common::Rect &r, bool alpha); + void queueABitmap(Graphics::TransparentSurface *bitmap, const Common::Rect &r, AutoScaleMode autoscale, int alpha); /** * DEBUG: Draws a white square and writes some text next to it. @@ -656,10 +690,10 @@ protected: GUI::ThemeEval *_themeEval; /** Main screen surface. This is blitted straight into the overlay. */ - Graphics::Surface _screen; + Graphics::TransparentSurface _screen; /** Backbuffer surface. Stores previous states of the screen to blit back */ - Graphics::Surface _backBuffer; + Graphics::TransparentSurface _backBuffer; /** Sets whether the current drawing is being buffered (stored for later processing) or drawn directly to the screen. */ @@ -688,6 +722,7 @@ protected: TextColorData *_textColors[kTextColorMAX]; ImagesMap _bitmaps; + AImagesMap _abitmaps; Graphics::PixelFormat _overlayFormat; #ifdef USE_RGB_COLOR Graphics::PixelFormat _cursorFormat; diff --git a/gui/ThemeParser.cpp b/gui/ThemeParser.cpp index bd5b406ca8..bd0d2c4898 100644 --- a/gui/ThemeParser.cpp +++ b/gui/ThemeParser.cpp @@ -241,6 +241,18 @@ bool ThemeParser::parserCallback_bitmap(ParserNode *node) { return true; } +bool ThemeParser::parserCallback_alphabitmap(ParserNode *node) { + if (resolutionCheck(node->values["resolution"]) == false) { + node->ignore = true; + return true; + } + + if (!_theme->addAlphaBitmap(node->values["filename"])) + return parserError("Error loading Bitmap file '" + node->values["filename"] + "'"); + + return true; +} + bool ThemeParser::parserCallback_text(ParserNode *node) { Graphics::TextAlign alignH; GUI::ThemeEngine::TextAlignVertical alignV; @@ -323,6 +335,8 @@ static Graphics::DrawingFunctionCallback getDrawingFunctionCallback(const Common return &Graphics::VectorRenderer::drawCallback_BITMAP; if (name == "cross") return &Graphics::VectorRenderer::drawCallback_CROSS; + if (name == "alphabitmap") + return &Graphics::VectorRenderer::drawCallback_ALPHABITMAP; return 0; } @@ -448,6 +462,58 @@ bool ThemeParser::parseDrawStep(ParserNode *stepNode, Graphics::DrawStep *drawst return parserError("The given filename hasn't been loaded into the GUI."); } + if (functionName == "alphabitmap") { + if (!stepNode->values.contains("file")) + return parserError("Need to specify a filename for AlphaBitmap blitting."); + + drawstep->blitAlphaSrc = _theme->getAlphaBitmap(stepNode->values["file"]); + + if (!drawstep->blitAlphaSrc) + return parserError("The given filename hasn't been loaded into the GUI."); + + if (stepNode->values.contains("autoscale")) { + if (stepNode->values["autoscale"] == "true" || stepNode->values["autoscale"] == "stretch") { + drawstep->autoscale = ThemeEngine::kAutoScaleStretch; + } else if (stepNode->values["autoscale"] == "fit") { + drawstep->autoscale = ThemeEngine::kAutoScaleFit; + } else if (stepNode->values["autoscale"] == "9patch") { + drawstep->autoscale = ThemeEngine::kAutoScaleNinePatch; + } else { + drawstep->autoscale = ThemeEngine::kAutoScaleNone; + } + } + + if (stepNode->values.contains("xpos")) { + val = stepNode->values["xpos"]; + + if (parseIntegerKey(val, 1, &x)) + drawstep->x = x; + else if (val == "center") + drawstep->xAlign = Graphics::DrawStep::kVectorAlignCenter; + else if (val == "left") + drawstep->xAlign = Graphics::DrawStep::kVectorAlignLeft; + else if (val == "right") + drawstep->xAlign = Graphics::DrawStep::kVectorAlignRight; + else + return parserError("Invalid value for X Position"); + } + + if (stepNode->values.contains("ypos")) { + val = stepNode->values["ypos"]; + + if (parseIntegerKey(val, 1, &x)) + drawstep->y = x; + else if (val == "center") + drawstep->yAlign = Graphics::DrawStep::kVectorAlignCenter; + else if (val == "top") + drawstep->yAlign = Graphics::DrawStep::kVectorAlignTop; + else if (val == "bottom") + drawstep->yAlign = Graphics::DrawStep::kVectorAlignBottom; + else + return parserError("Invalid value for Y Position"); + } + } + if (functionName == "roundedsq" || functionName == "circle" || functionName == "tab") { if (stepNode->values.contains("radius") && stepNode->values["radius"] == "auto") { drawstep->radius = 0xFF; diff --git a/gui/ThemeParser.h b/gui/ThemeParser.h index 360e3da009..155731467f 100644 --- a/gui/ThemeParser.h +++ b/gui/ThemeParser.h @@ -80,6 +80,10 @@ protected: XML_PROP(filename, true) XML_PROP(resolution, false) KEY_END() + XML_KEY(alphabitmap) + XML_PROP(filename, true) + XML_PROP(resolution, false) + KEY_END() KEY_END() XML_KEY(cursor) @@ -142,6 +146,7 @@ protected: XML_PROP(padding, false) XML_PROP(orientation, false) XML_PROP(file, false) + XML_PROP(autoscale, false) KEY_END() XML_KEY(text) @@ -224,6 +229,7 @@ protected: bool parserCallback_drawdata(ParserNode *node); bool parserCallback_bitmaps(ParserNode *node) { return true; } bool parserCallback_bitmap(ParserNode *node); + bool parserCallback_alphabitmap(ParserNode *node); bool parserCallback_cursor(ParserNode *node); diff --git a/engines/titanic/gfx/chev_switch.h b/gui/animation/AccelerateInterpolator.h index 0305a6ca83..31494d369f 100644 --- a/engines/titanic/gfx/chev_switch.h +++ b/gui/animation/AccelerateInterpolator.h @@ -20,29 +20,26 @@ * */ -#ifndef TITANIC_CHEV_SWITCH_H -#define TITANIC_CHEV_SWITCH_H +// Based on code by omergilad. -#include "titanic/gfx/toggle_switch.h" +#ifndef GUI_ANIMATION_ACCELERATEINTERPOLATOR_H +#define GUI_ANIMATION_ACCELERATEINTERPOLATOR_H -namespace Titanic { +#include "gui/animation/Interpolator.h" -class CChevSwitch : public CToggleSwitch { +namespace GUI { + +class AccelerateInterpolator: public Interpolator { public: - CLASSDEF; - CChevSwitch(); - - /** - * Save the data for the class to file - */ - virtual void save(SimpleFile *file, int indent); - - /** - * Load the data for the class from file - */ - virtual void load(SimpleFile *file); + AccelerateInterpolator() {} + virtual ~AccelerateInterpolator() {} + + virtual float interpolate(float linearValue) { + return pow(linearValue, 2); + } + }; -} // End of namespace Titanic +} // End of namespace GUI -#endif /* TITANIC_CHEV_SWITCH_H */ +#endif /* GUI_ANIMATION_ACCELERATEINTERPOLATOR_H */ diff --git a/gui/animation/AlphaAnimation.h b/gui/animation/AlphaAnimation.h new file mode 100644 index 0000000000..82cf3d4ba9 --- /dev/null +++ b/gui/animation/AlphaAnimation.h @@ -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 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. + * + */ + +// Based on code by omergilad. + +#ifndef GUI_ANIMATION_ANIMATION_H +#define GUI_ANIMATION_ANIMATION_H + +#include "gui/animation/Animation.h" + +namespace GUI { + +class AlphaAnimation: public Animation { +public: + AlphaAnimation() {} + virtual ~AlphaAnimation() {} + float getEndAlpha() const { return _endAlpha; } + void setEndAlpha(float endAlpha) { _endAlpha = endAlpha; } + float getStartAlpha() const { return _startAlpha; } + void setStartAlpha(float startAlpha) { _startAlpha = startAlpha; } + +protected: + virtual void updateInternal(Drawable* drawable, float interpolation) { + // Calculate alpha value based on properties and interpolation + drawable->setAlpha(_startAlpha * (1 - interpolation) + _endAlpha * interpolation); + } + + float _startAlpha; + float _endAlpha; +}; + +} // End of namespace GUI + +#endif /* GUI_ANIMATION_ANIMATION_H */ diff --git a/gui/animation/Animation.cpp b/gui/animation/Animation.cpp new file mode 100644 index 0000000000..ee4d900b4d --- /dev/null +++ b/gui/animation/Animation.cpp @@ -0,0 +1,98 @@ +/* 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. + * + */ + +// Based on code by omergilad. + +#include "gui/animation/Animation.h" + +namespace GUI { + +Animation::Animation() + : _startTime(0), _duration(0), _finished(false), _finishOnEnd(true) { +} + +Animation::~Animation() { +} + +void Animation::start(long currentTime) { + _finished = false; + _startTime = currentTime; +} + +void Animation::setDuration(long duration) { + _duration = duration; +} + +void Animation::update(Drawable *drawable, long currentTime) { + float interpolation; + + if (currentTime < _startTime) { + // If the start time is in the future, nothing changes - the interpolated value is 0 + interpolation = 0; + } else if (currentTime > _startTime + _duration) { + // If the animation is finished, the interpolated value is 1 and the animation is marked as finished + interpolation = 1; + finishAnimation(); + } else { + // Calculate the interpolated value + interpolation = (currentTime - _startTime) / (float) (_duration); + } + + // Activate the interpolator if present + if (_interpolator.get() != NULL) { + interpolation = _interpolator->interpolate(interpolation); + } + + updateInternal(drawable, interpolation); +} + +void Animation::finishAnimation() { + if (_finishOnEnd) { + _finished = true; + } +} + +void Animation::updateInternal(Drawable *drawable, float interpolation) { + // Default implementation +} + +bool Animation::isFinished() const { + return _finished; +} + +bool Animation::isFinishOnEnd() const { + return _finishOnEnd; +} + +void Animation::setFinishOnEnd(bool finishOnEnd) { + _finishOnEnd = finishOnEnd; +} + +InterpolatorPtr Animation::getInterpolator() const { + return _interpolator; +} + +void Animation::setInterpolator(InterpolatorPtr interpolator) { + _interpolator = interpolator; +} + +} // End of namespace GUI diff --git a/gui/animation/Animation.h b/gui/animation/Animation.h new file mode 100644 index 0000000000..300720b419 --- /dev/null +++ b/gui/animation/Animation.h @@ -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 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. + * + */ + +// Based on code by omergilad. + +#ifndef GUI_ANIMATION_ANIMATION_H +#define GUI_ANIMATION_ANIMATION_H + +#include "gui/animation/Interpolator.h" + +namespace GUI { + +class Drawable; + +class Animation { +public: + Animation(); + virtual ~Animation() = 0; + + virtual void update(Drawable *drawable, long currentTime); + + /** + * Set start time in millis + */ + virtual void start(long currentTime); + + /** + * Set duration in millis + */ + virtual void setDuration(long duration); + + virtual bool isFinished() const; + + bool isFinishOnEnd() const; + + void setFinishOnEnd(bool finishOnEnd); + + InterpolatorPtr getInterpolator() const; + void setInterpolator(InterpolatorPtr interpolator); + +protected: + void finishAnimation(); + + virtual void updateInternal(Drawable *drawable, float interpolation); + + long _startTime; + long _duration; + bool _finished; + bool _finishOnEnd; + InterpolatorPtr _interpolator; +}; + +typedef Common::SharedPtr<Animation> AnimationPtr; + +} // End of namespace GUI + +#endif /* GUI_ANIMATION_ANIMATION_H */ diff --git a/gui/animation/DeccelerateInterpolator.h b/gui/animation/DeccelerateInterpolator.h new file mode 100644 index 0000000000..e25ff6a4ba --- /dev/null +++ b/gui/animation/DeccelerateInterpolator.h @@ -0,0 +1,41 @@ +/* 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. + * + */ + +// Based on code by omergilad. + +#ifndef GUI_ANIMATION_DECCELERATEINTERPOLATOR_H +#define GUI_ANIMATION_DECCELERATEINTERPOLATOR_H + +#include "gui/animation/Interpolator.h" + +namespace GUI { + +class DeccelerateInterpolator: public Interpolator { +public: + DeccelerateInterpolator() {} + virtual ~DeccelerateInterpolator() {} + virtual float interpolate(float linearValue) { return sqrt(linearValue); } +}; + +} // End of namespace GUI + +#endif /* GUI_ANIMATION_DECCELERATEINTERPOLATOR_H */ diff --git a/gui/animation/Drawable.h b/gui/animation/Drawable.h new file mode 100644 index 0000000000..cf604270aa --- /dev/null +++ b/gui/animation/Drawable.h @@ -0,0 +1,109 @@ +/* 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. + * + */ + +// Based on code by omergilad. + +#ifndef GUI_ANIMATION_DRAWABLE_H +#define GUI_ANIMATION_DRAWABLE_H + +#include "common/ptr.h" +#include "graphics/transparent_surface.h" + +#include "gui/animation/Animation.h" + +namespace GUI { + +class Animation; +typedef Common::SharedPtr<Animation> AnimationPtr; + +class Drawable { +public: + Drawable() : + _bitmap(NULL), _positionX(0), _positionY(0), _width(0), _height(0), _alpha(1), + _usingSnapshot(false), _shouldCenter(false) { + _displayRatio = 1.0; + } + + virtual ~Drawable() { + if (_usingSnapshot) + delete _bitmap; + } + + void updateAnimation(long currentTime) { + if (_animation.get() != NULL) { + _animation->update(this, currentTime); + } + } + + bool isAnimationFinished() { + if (_animation.get() != NULL) + return _animation->isFinished(); + + return false; + } + + float getAlpha() const { return _alpha; } + void setAlpha(float alpha) { _alpha = alpha; } + AnimationPtr getAnimation() const { return _animation; } + void setAnimation(AnimationPtr animation) { _animation = animation; } + Graphics::TransparentSurface *getBitmap() const { return _bitmap; } + void setBitmap(Graphics::TransparentSurface *bitmap) { _bitmap = bitmap; } + float getPositionX() const { return _positionX; } + void setPositionX(float positionX) { _positionX = positionX; } + float getPositionY() const { return _positionY; } + void setPositionY(float positionY) { _positionY = positionY; } + virtual float getWidth() const { return _width; } + void setWidth(float width) { _width = width; } + + virtual float getHeight() const { + if (_height == 0) + return getWidth() * _bitmap->getRatio() * _displayRatio; + + return _height; + } + + void setHeight(float height) { _height = height; } + void setDisplayRatio(float ratio) { _displayRatio = ratio; } + inline bool shouldCenter() const { return _shouldCenter; } + void setShouldCenter(bool shouldCenter) { _shouldCenter = shouldCenter; } + +protected: + bool _usingSnapshot; + +private: + Graphics::TransparentSurface *_bitmap; + float _positionX; + float _positionY; + float _width; + float _height; + float _alpha; + bool _shouldCenter; + AnimationPtr _animation; + + float _displayRatio; +}; + +typedef Common::SharedPtr<Drawable> DrawablePtr; + +} // End of namespace GUI + +#endif /* GUI_ANIMATION_DRAWABLE_H */ diff --git a/gui/animation/Interpolator.h b/gui/animation/Interpolator.h new file mode 100644 index 0000000000..72d7acb8d4 --- /dev/null +++ b/gui/animation/Interpolator.h @@ -0,0 +1,44 @@ +/* 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. + * + */ + +// Based on code by omergilad. + +#ifndef GUI_ANIMATION_INTERPOLATOR_H +#define GUI_ANIMATION_INTERPOLATOR_H + +#include "common/ptr.h" + +namespace GUI { + +class Interpolator { +public: + Interpolator() {} + virtual ~Interpolator() {} + + virtual float interpolate(float linearValue) = 0; +}; + +typedef Common::SharedPtr<Interpolator> InterpolatorPtr; + +} // End of namespace GUI + +#endif /* GUI_ANIMATION_INTERPOLATOR_H */ diff --git a/gui/animation/ParallelAnimation.h b/gui/animation/ParallelAnimation.h new file mode 100644 index 0000000000..ce1f599fb1 --- /dev/null +++ b/gui/animation/ParallelAnimation.h @@ -0,0 +1,72 @@ +/* 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. + * + */ + +// Based on code by omergilad. + +#ifndef GUI_ANIMATION_PARALLELANIMATION_H +#define GUI_ANIMATION_PARALLELANIMATION_H + +#include "gui/animation/Animation.h" +#include "common/array.h" + +namespace GUI { + +class ParallelAnimation: public Animation { +public: + ParallelAnimation() {} + virtual ~ParallelAnimation() {} + + virtual void addAnimation(AnimationPtr animation) { + _animations.push_back(animation); + } + + virtual void update(Drawable *drawable, long currentTime) { + for (AnimationPtr anim : _animations) { + anim->update(drawable, currentTime); + if (anim->isFinished()) { + finishAnimation(); + } + } + } + + virtual void start(long currentTime) { + Animation::start(currentTime); + + for (AnimationPtr anim : _animations) + anim->start(currentTime); + } + + virtual void setDuration(long duration) { + Animation::setDuration(duration); + + for (AnimationPtr anim : _animations) + anim->setDuration(duration); + } + +private: + + Common::Array<AnimationPtr> _animations; +}; + +} // End of namespace GUI + +#endif /* GUI_ANIMATION_PARALLELANIMATION_H */ diff --git a/gui/animation/RepeatAnimationWrapper.cpp b/gui/animation/RepeatAnimationWrapper.cpp new file mode 100644 index 0000000000..a7e1413093 --- /dev/null +++ b/gui/animation/RepeatAnimationWrapper.cpp @@ -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 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. + * + */ + +// Based on code by omergilad. + +#include "gui/animation/RepeatAnimationWrapper.h" + +namespace GUI { + +void RepeatAnimationWrapper::update(Drawable* drawable, long currentTime) { + // Update wrapped animation + _animation->update(drawable, currentTime); + + // If the animation is finished, increase the repeat count and restart it if needed + if (_animation->isFinished()) { + ++_repeatCount; + if (_timesToRepeat > 0 && _repeatCount >= _timesToRepeat) { + finishAnimation(); + } else { + _animation->start(currentTime); + } + } +} + +void RepeatAnimationWrapper::start(long currentTime) { + Animation::start(currentTime); + _repeatCount = 0; + + // Start wrapped animation + _animation->start(currentTime); +} + +} // End of namespace GUI diff --git a/engines/titanic/sound/music_handler.h b/gui/animation/RepeatAnimationWrapper.h index cab2ef8074..3d766dd1c5 100644 --- a/engines/titanic/sound/music_handler.h +++ b/gui/animation/RepeatAnimationWrapper.h @@ -20,37 +20,42 @@ * */ -#ifndef TITANIC_MUSIC_HANDLER_H -#define TITANIC_MUSIC_HANDLER_H +// Based on code by omergilad. -#include "titanic/sound/music_wave.h" +#ifndef GUI_ANIMATION_REPEATANIMATIONWRAPPER_H +#define GUI_ANIMATION_REPEATANIMATIONWRAPPER_H -namespace Titanic { +#include "gui/animation/Animation.h" -class CProjectItem; -class CSoundManager; +namespace GUI { -class CMusicHandler { -private: - CProjectItem *_project; - CSoundManager *_soundManager; - int _field124; +class RepeatAnimationWrapper: public Animation { public: - CMusicHandler(CProjectItem *project, CSoundManager *soundManager); + /** + * Animation - animation to repeat + * + * timesToRepeat - 0 means infinite + */ + RepeatAnimationWrapper(AnimationPtr animation, uint16 timesToRepeat) : + _animation(animation), _timesToRepeat(timesToRepeat) {} + + virtual ~RepeatAnimationWrapper() {} + + virtual void update(Drawable* drawable, long currentTime); /** - * Creates a new music wave class instance, and assigns it to a slot - * in the music handler - * @param waveIndex Slot to save new instance in - * @param count Number of files the new instance will contain + * Set start time in millis */ - CMusicWave *createMusicWave(int waveIndex, int count); + virtual void start(long currentTime); + +private: + uint16 _timesToRepeat; + uint16 _repeatCount; - bool isBusy(); + AnimationPtr _animation; - void set124(int val) { _field124 = val; } }; -} // End of namespace Titanic +} // End of namespace GUI -#endif /* TITANIC_MUSIC_HANDLER_H */ +#endif /* GUI_ANIMATION_REPEATANIMATIONWRAPPER_H */ diff --git a/gui/animation/ScaleAnimation.h b/gui/animation/ScaleAnimation.h new file mode 100644 index 0000000000..80a4ae6305 --- /dev/null +++ b/gui/animation/ScaleAnimation.h @@ -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 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. + * + */ + +// Based on code by omergilad. + +#ifndef GUI_ANIMATION_SCALEANIMATION_H +#define GUI_ANIMATION_SCALEANIMATION_H + +#include "gui/animation/Animation.h" + +namespace GUI { + +class ScaleAnimation: public Animation { +public: + ScaleAnimation() : _endWidth(0), _endWidthFactor(0) {} + + virtual ~ScaleAnimation() {} + + float getEndWidth() const { return _endWidth; } + void setEndWidth(float endWidth) { _endWidth = endWidth; } + float getEndWidthFactor() const { return _endWidthFactor; } + void setEndWidthFactor(float endWidthFactor) { _endWidthFactor = endWidthFactor; } + float getStartWidth() const { return _startWidth; } + void setStartWidth(float startWidth) { _startWidth = startWidth; } + + void updateInternal(Drawable *drawable, float interpolation) { + // If start width was set as 0 -> use the current width as the start dimension + if (_startWidth == 0) + _startWidth = drawable->getWidth(); + + // If end width was set as 0 - multiply the start width by the given factor + if (_endWidth == 0) + _endWidth = _startWidth * _endWidthFactor; + + // Calculate width based on interpolation + float width = _startWidth * (1 - interpolation) + _endWidth * interpolation; + drawable->setWidth(width); + } + +private: + virtual void updateInternal(Drawable *drawable, float interpolation); + float _startWidth; + float _endWidth; + float _endWidthFactor; +}; + +} // End of namespace GUI + + +#endif /* GUI_ANIMATION_SCALEANIMATION_H */ diff --git a/gui/animation/SequenceAnimationComposite.cpp b/gui/animation/SequenceAnimationComposite.cpp new file mode 100644 index 0000000000..9ecfeebc8c --- /dev/null +++ b/gui/animation/SequenceAnimationComposite.cpp @@ -0,0 +1,72 @@ +/* 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. + * + */ + +// Based on code by omergilad. + +#include "gui/animation/SequenceAnimationComposite.h" + +namespace GUI { + +void SequenceAnimationComposite::start(long currentTime) { + Animation::start(currentTime); + + // The first animation in the sequence should a start time equal to this sequence + if (_sequence.size() >= 1) + _sequence[0]->start(currentTime); + + // Set the index to 0 + _index = 0; +} + +void SequenceAnimationComposite::addAnimation(AnimationPtr animation) { + _sequence.push_back(animation); +} + +void SequenceAnimationComposite::update(Drawable *drawable, long currentTime) { + uint16 sequenceSize = _sequence.size(); + + // Check index bounds + if (_index >= sequenceSize) + return; + + // Get the current animation in the sequence + AnimationPtr anim = _sequence[_index]; + + // Update the drawable + anim->update(drawable, currentTime); + + // Check if the current animation is finished + if (anim->isFinished()) { + // Increase the index - move to the next animation + ++_index; + + if (_index >= sequenceSize) { + // Finished the sequence + finishAnimation(); + } else { + // Set the start time for the next animation + _sequence[_index]->start(currentTime); + } + } +} + +} // End of namespace GUI diff --git a/gui/animation/SequenceAnimationComposite.h b/gui/animation/SequenceAnimationComposite.h new file mode 100644 index 0000000000..4ec0331751 --- /dev/null +++ b/gui/animation/SequenceAnimationComposite.h @@ -0,0 +1,51 @@ +/* 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. + * + */ + +// Based on code by omergilad. + +#ifndef GUI_ANIMATION_SEQUENCEANIMATION_H +#define GUI_ANIMATION_SEQUENCEANIMATION_H + +#include "gui/animation/Animation.h" +#include "common/array.h" + +namespace GUI { + +class SequenceAnimationComposite: public Animation { +public: + SequenceAnimationComposite() {} + virtual ~SequenceAnimationComposite() {} + + virtual void addAnimation(AnimationPtr animation); + + virtual void update(Drawable* drawable, long currentTime); + + virtual void start(long currentTime); + +private: + uint16 _index; + Common::Array<AnimationPtr> _sequence; +}; + +} // End of namespace GUI + +#endif /* GUI_ANIMATION_SEQUENCEANIMATION_H */ diff --git a/gui/animation/WaitForConditionAnimation.h b/gui/animation/WaitForConditionAnimation.h new file mode 100644 index 0000000000..5a67a37493 --- /dev/null +++ b/gui/animation/WaitForConditionAnimation.h @@ -0,0 +1,71 @@ +/* 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. + * + */ + +// Based on code by omergilad. + +#ifndef GUI_ANIMATION_WAITFORCONDITIONANIMATION_H +#define GUI_ANIMATION_WAITFORCONDITIONANIMATION_H + +#include "gui/animation/Animation.h" + +namespace GUI { + +class Condition { + +public: + virtual ~Condition() {} + + virtual bool evaluate() = 0; +}; + +typedef Common::SharedPtr<Condition> ConditionPtr; + +/** + * Used for delaying the animation sequence until a certain condition has been met + */ +class WaitForConditionAnimation: public Animation { +public: + WaitForConditionAnimation() {} + virtual ~WaitForConditionAnimation() {} + + virtual void update(Drawable *drawable, long currentTime) { + // Check the condition - if it has been met, finish. + if (_condition.get() != NULL && _condition->evaluate()) { + finishAnimation(); + } + } + + ConditionPtr getCondition() const { + return _condition; + } + + void setCondition(ConditionPtr condition) { + _condition = condition; + } + +private: + ConditionPtr _condition; +}; + +} // End of namespace GUI + +#endif /* GUI_ANIMATION_WAITFORCONDITIONANIMATION_H */ diff --git a/gui/browser.cpp b/gui/browser.cpp index 83e240a5bc..19fa791cee 100644 --- a/gui/browser.cpp +++ b/gui/browser.cpp @@ -49,7 +49,7 @@ BrowserDialog::BrowserDialog(const char *title, bool dirBrowser) _isDirBrowser = dirBrowser; _fileList = NULL; _currentPath = NULL; - _showHidden = ConfMan.getBool("gui_browser_show_hidden", Common::ConfigManager::kApplicationDomain); + _showHidden = false; // Headline - TODO: should be customizable during creation time new StaticTextWidget(this, "Browser.Headline", title); @@ -85,8 +85,10 @@ void BrowserDialog::open() { if (!_node.isDirectory()) _node = Common::FSNode("."); - // Alway refresh file list - updateListing(); + _showHidden = ConfMan.getBool("gui_browser_show_hidden", Common::ConfigManager::kApplicationDomain); + _showHiddenWidget->setState(_showHidden); + + // At this point the file list has already been refreshed by the kHiddenCmd handler } void BrowserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { diff --git a/gui/credits.h b/gui/credits.h index c2c4c84ec6..720f917182 100644 --- a/gui/credits.h +++ b/gui/credits.h @@ -845,7 +845,7 @@ static const char *credits[] = { "C2""For the original Reinherit (SAGA) code", "C0""Sander Buskens", "C2""For his work on the initial reversing of Monkey2", -"C0""Canadacow", +"C0""Dean Beeler", "C2""For the original MT-32 emulator", "C0""Kevin Carnes", "C2""For Scumm16, the basis of ScummVM's older gfx codecs", @@ -863,18 +863,18 @@ static const char *credits[] = { "C2""For contributing some GUI icons", "C0""Till Kresslein", "C2""For design of modern ScummVM GUI", -"C0""Jezar", +"C0""Jezar Wakefield", "C2""For his freeverb filter implementation", "C0""Jim Leiterman", "C2""Various info on his FM-TOWNS/Marty SCUMM ports", -"C0""lloyd", +"C0""Lloyd Rosen", "C2""For deep tech details about C64 Zak & MM", "C0""Sarien Team", "C2""Original AGI engine code", "A0""Jimmi Thogersen", "C0""Jimmi Th\370gersen", "C2""For ScummRev, and much obscure code/documentation", -"C0""Tristan", +"C0""Tristan Matthews", "C2""For additional work on the original MT-32 emulator", "C0""James Woodcock", "C2""Soundtrack enhancements", diff --git a/gui/debugger.cpp b/gui/debugger.cpp index 72d05e2973..7595efcfbc 100644 --- a/gui/debugger.cpp +++ b/gui/debugger.cpp @@ -584,7 +584,7 @@ bool Debugger::cmdMd5(int argc, const char **argv) { length = atoi(argv[2]); paramOffset = 2; } - + // Assume that spaces are part of a single filename. Common::String filename = argv[1 + paramOffset]; for (int i = 2 + paramOffset; i < argc; i++) { @@ -624,7 +624,7 @@ bool Debugger::cmdMd5Mac(int argc, const char **argv) { length = atoi(argv[2]); paramOffset = 2; } - + // Assume that spaces are part of a single filename. Common::String filename = argv[1 + paramOffset]; for (int i = 2 + paramOffset; i < argc; i++) { diff --git a/gui/downloaddialog.cpp b/gui/downloaddialog.cpp new file mode 100644 index 0000000000..ea7ace2f2a --- /dev/null +++ b/gui/downloaddialog.cpp @@ -0,0 +1,272 @@ +/* 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 "gui/downloaddialog.h" +#include "backends/cloud/cloudmanager.h" +#include "backends/networking/connection/islimited.h" +#include "common/config-manager.h" +#include "common/translation.h" +#include "engines/metaengine.h" +#include "gui/browser.h" +#include "gui/chooser.h" +#include "gui/editgamedialog.h" +#include "gui/launcher.h" +#include "gui/message.h" +#include "gui/remotebrowser.h" +#include "gui/widgets/edittext.h" +#include "gui/widgets/list.h" + +namespace GUI { + +enum { + kDownloadDialogButtonCmd = 'Dldb' +}; + +DownloadDialog::DownloadDialog(uint32 storageId, LauncherDialog *launcher) : + Dialog("GlobalOptions_Cloud_DownloadDialog"), _launcher(launcher), _close(false) { + _backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain; + + _browser = new BrowserDialog(_("Select directory where to download game data"), true); + _remoteBrowser = new RemoteBrowserDialog(_("Select directory with game data")); + + _remoteDirectoryLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.RemoteDirectory", _("From: ")); + _localDirectoryLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.LocalDirectory", _("To: ")); + uint32 progress = (uint32)(100 * CloudMan.getDownloadingProgress()); + _progressBar = new SliderWidget(this, "GlobalOptions_Cloud_DownloadDialog.ProgressBar"); + _progressBar->setMinValue(0); + _progressBar->setMaxValue(100); + _progressBar->setValue(progress); + _progressBar->setEnabled(false); + _percentLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.PercentText", Common::String::format("%u %%", progress)); + _downloadSizeLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.DownloadSize", ""); + _downloadSpeedLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.DownloadSpeed", ""); + if (g_system->getOverlayWidth() > 320) + _cancelButton = new ButtonWidget(this, "GlobalOptions_Cloud_DownloadDialog.MainButton", _("Cancel download"), 0, kDownloadDialogButtonCmd); + else + _cancelButton = new ButtonWidget(this, "GlobalOptions_Cloud_DownloadDialog.MainButton", _c("Cancel download", "lowres"), 0, kDownloadDialogButtonCmd); + + _closeButton = new ButtonWidget(this, "GlobalOptions_Cloud_DownloadDialog.CloseButton", _("Hide"), 0, kCloseCmd); + refreshWidgets(); + + CloudMan.setDownloadTarget(this); +} + +DownloadDialog::~DownloadDialog() { + CloudMan.setDownloadTarget(nullptr); +} + +void DownloadDialog::open() { + Dialog::open(); + CloudMan.setDownloadTarget(this); + if (!CloudMan.isDownloading()) + if (!selectDirectories()) + close(); + reflowLayout(); + draw(); +} + +void DownloadDialog::close() { + CloudMan.setDownloadTarget(nullptr); + Dialog::close(); +} + +void DownloadDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { + switch (cmd) { + case kDownloadDialogButtonCmd: + { + CloudMan.setDownloadTarget(nullptr); + CloudMan.cancelDownload(); + close(); + break; + } + case kDownloadProgressCmd: + if (!_close) { + refreshWidgets(); + draw(); + } + break; + case kDownloadEndedCmd: + _close = true; + break; + default: + Dialog::handleCommand(sender, cmd, data); + } +} + +bool DownloadDialog::selectDirectories() { + if (Networking::Connection::isLimited()) { + MessageDialog alert(_("It looks like your connection is limited. " + "Do you really want to download files with it?"), _("Yes"), _("No")); + if (alert.runModal() != GUI::kMessageOK) + return false; + } + + //first user should select remote directory to download + if (_remoteBrowser->runModal() <= 0) + return false; + + Cloud::StorageFile remoteDirectory = _remoteBrowser->getResult(); + + //now user should select local directory to download into + if (_browser->runModal() <= 0) + return false; + + Common::FSNode dir(_browser->getResult()); + Common::FSList files; + if (!dir.getChildren(files, Common::FSNode::kListAll)) { + MessageDialog alert(_("ScummVM couldn't open the specified directory!")); + alert.runModal(); + return false; + } + + //check that there is no file with the remote directory's name in the local one + for (Common::FSList::iterator i = files.begin(); i != files.end(); ++i) { + if (i->getName().equalsIgnoreCase(remoteDirectory.name())) { + //if there is, ask user whether it's OK + if (!i->isDirectory()) { + GUI::MessageDialog alert(_("Cannot create a directory to download - the specified directory has a file with the same name."), _("OK")); + alert.runModal(); + return false; + } + GUI::MessageDialog alert( + Common::String::format(_("The \"%s\" already exists in the specified directory.\nDo you really want to download files into that directory?"), remoteDirectory.name().c_str()), + _("Yes"), + _("No") + ); + if (alert.runModal() != GUI::kMessageOK) + return false; + break; + } + } + + //make a local path + Common::String localPath = dir.getPath(); + + //simple heuristic to determine which path separator to use + if (localPath.size() && localPath.lastChar() != '/' && localPath.lastChar() != '\\') { + int backslashes = 0; + for (uint32 i = 0; i < localPath.size(); ++i) + if (localPath[i] == '/') + --backslashes; + else if (localPath[i] == '\\') + ++backslashes; + + if (backslashes > 0) + localPath += '\\' + remoteDirectory.name(); + else + localPath += '/' + remoteDirectory.name(); + } else { + localPath += remoteDirectory.name(); + } + + CloudMan.startDownload(remoteDirectory.path(), localPath); + CloudMan.setDownloadTarget(this); + _localDirectory = localPath; + return true; +} + +void DownloadDialog::handleTickle() { + if (_close) { + if (_launcher) + _launcher->doGameDetection(_localDirectory); + close(); + _close = false; + return; + } + + uint32 progress = (uint32)(100 * CloudMan.getDownloadingProgress()); + if (_progressBar->getValue() != progress) { + refreshWidgets(); + draw(); + } + + Dialog::handleTickle(); +} + +void DownloadDialog::reflowLayout() { + Dialog::reflowLayout(); + refreshWidgets(); +} + +namespace { +Common::String getHumanReadableBytes(uint64 bytes, Common::String &unitsOut) { + Common::String result = Common::String::format("%lu", bytes); + unitsOut = "B"; + + if (bytes >= 1024) { + bytes /= 1024; + result = Common::String::format("%lu", bytes); + unitsOut = "KB"; + } + + double floating = bytes; + + if (bytes >= 1024) { + bytes /= 1024; + floating /= 1024.0; + unitsOut = "MB"; + } + + if (bytes >= 1024) { + bytes /= 1024; + floating /= 1024.0; + unitsOut = "GB"; + } + + if (bytes >= 1024) { // woah + bytes /= 1024; + floating /= 1024.0; + unitsOut = "TB"; + } + + // print one digit after floating point + result = Common::String::format("%.1lf", floating); + return result; +} +} + +Common::String DownloadDialog::getSizeLabelText() { + Common::String downloaded, downloadedUnits, total, totalUnits; + downloaded = getHumanReadableBytes(CloudMan.getDownloadBytesNumber(), downloadedUnits); + total = getHumanReadableBytes(CloudMan.getDownloadTotalBytesNumber(), totalUnits); + return Common::String::format(_("Downloaded %s %s / %s %s"), downloaded.c_str(), _(downloadedUnits.c_str()), total.c_str(), _(totalUnits.c_str())); +} + +Common::String DownloadDialog::getSpeedLabelText() { + Common::String speed, speedUnits; + speed = getHumanReadableBytes(CloudMan.getDownloadSpeed(), speedUnits); + speedUnits += "/s"; + return Common::String::format("Download speed: %s %s", speed.c_str(), _(speedUnits.c_str())); +} + +void DownloadDialog::refreshWidgets() { + _localDirectory = CloudMan.getDownloadLocalDirectory(); + _remoteDirectoryLabel->setLabel(_("From: ") + CloudMan.getDownloadRemoteDirectory()); + _localDirectoryLabel->setLabel(_("To: ") + _localDirectory); + uint32 progress = (uint32)(100 * CloudMan.getDownloadingProgress()); + _percentLabel->setLabel(Common::String::format("%u %%", progress)); + _downloadSizeLabel->setLabel(getSizeLabelText()); + _downloadSpeedLabel->setLabel(getSpeedLabelText()); + _progressBar->setValue(progress); +} + +} // End of namespace GUI diff --git a/gui/downloaddialog.h b/gui/downloaddialog.h new file mode 100644 index 0000000000..9080717195 --- /dev/null +++ b/gui/downloaddialog.h @@ -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 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. + * + */ + +#ifndef GUI_DOWNLOADDIALOG_H +#define GUI_DOWNLOADDIALOG_H + +#include "gui/dialog.h" +#include "common/str.h" + +namespace GUI { +class LauncherDialog; + +class CommandSender; +class EditTextWidget; +class StaticTextWidget; +class ButtonWidget; +class SliderWidget; +class BrowserDialog; +class RemoteBrowserDialog; + +enum DownloadProgress { + kDownloadProgressCmd = 'DLPR', + kDownloadEndedCmd = 'DLEN' +}; + +class DownloadDialog : public Dialog { + LauncherDialog *_launcher; + BrowserDialog *_browser; + RemoteBrowserDialog *_remoteBrowser; + + StaticTextWidget *_remoteDirectoryLabel; + StaticTextWidget *_localDirectoryLabel; + StaticTextWidget *_percentLabel; + StaticTextWidget *_downloadSizeLabel; + StaticTextWidget *_downloadSpeedLabel; + SliderWidget *_progressBar; + ButtonWidget *_cancelButton; + ButtonWidget *_closeButton; + + Common::String _localDirectory; + bool _close; + + Common::String getSizeLabelText(); + Common::String getSpeedLabelText(); + + void refreshWidgets(); + bool selectDirectories(); + +public: + DownloadDialog(uint32 storageId, LauncherDialog *launcher); + virtual ~DownloadDialog(); + + virtual void open(); + virtual void close(); + virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); + virtual void handleTickle(); + virtual void reflowLayout(); +}; + +} // End of namespace GUI + +#endif diff --git a/gui/editgamedialog.cpp b/gui/editgamedialog.cpp new file mode 100644 index 0000000000..fbe76d228a --- /dev/null +++ b/gui/editgamedialog.cpp @@ -0,0 +1,550 @@ +/* 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 "gui/editgamedialog.h" + +#include "common/config-manager.h" +#include "common/gui_options.h" +#include "common/translation.h" +#include "common/system.h" + +#include "gui/browser.h" +#include "gui/message.h" +#ifdef ENABLE_EVENTRECORDER +#include "gui/onscreendialog.h" +#include "gui/recorderdialog.h" +#include "gui/EventRecorder.h" +#endif +#include "gui/widgets/edittext.h" +#include "gui/widgets/tab.h" +#include "gui/widgets/popup.h" + +#ifdef USE_LIBCURL +#include "backends/cloud/cloudmanager.h" +#endif + +using Common::ConfigManager; + +namespace GUI { + +enum { + kStartCmd = 'STRT', + kAboutCmd = 'ABOU', + kOptionsCmd = 'OPTN', + kAddGameCmd = 'ADDG', + kEditGameCmd = 'EDTG', + kRemoveGameCmd = 'REMG', + kLoadGameCmd = 'LOAD', + kQuitCmd = 'QUIT', + kSearchCmd = 'SRCH', + kListSearchCmd = 'LSSR', + kSearchClearCmd = 'SRCL', + + kCmdGlobalGraphicsOverride = 'OGFX', + kCmdGlobalAudioOverride = 'OSFX', + kCmdGlobalMIDIOverride = 'OMID', + kCmdGlobalMT32Override = 'OM32', + kCmdGlobalVolumeOverride = 'OVOL', + + kCmdChooseSoundFontCmd = 'chsf', + + kCmdExtraBrowser = 'PEXT', + kCmdExtraPathClear = 'PEXC', + kCmdGameBrowser = 'PGME', + kCmdSaveBrowser = 'PSAV', + kCmdSavePathClear = 'PSAC' +}; + +/* +* TODO: Clean up this ugly design: we subclass EditTextWidget to perform +* input validation. It would be much more elegant to use a decorator pattern, +* or a validation callback, or something like that. +*/ +class DomainEditTextWidget : public EditTextWidget { +public: + DomainEditTextWidget(GuiObject *boss, const String &name, const String &text, const char *tooltip = 0) + : EditTextWidget(boss, name, text, tooltip) {} + +protected: + bool tryInsertChar(byte c, int pos) { + if (Common::isAlnum(c) || c == '-' || c == '_') { + _editString.insertChar(c, pos); + return true; + } + return false; + } +}; + +EditGameDialog::EditGameDialog(const String &domain, const String &desc) + : OptionsDialog(domain, "GameOptions") { + // Retrieve all game specific options. + const EnginePlugin *plugin = 0; + // To allow for game domains without a gameid. + // TODO: Is it intentional that this is still supported? + String gameId(ConfMan.get("gameid", domain)); + if (gameId.empty()) + gameId = domain; + // Retrieve the plugin, since we need to access the engine's MetaEngine + // implementation. + EngineMan.findGame(gameId, &plugin); + if (plugin) { + _engineOptions = (*plugin)->getExtraGuiOptions(domain); + } else { + warning("Plugin for target \"%s\" not found! Game specific settings might be missing", domain.c_str()); + } + + // GAME: Path to game data (r/o), extra data (r/o), and save data (r/w) + String gamePath(ConfMan.get("path", _domain)); + String extraPath(ConfMan.get("extrapath", _domain)); + String savePath(ConfMan.get("savepath", _domain)); + + // GAME: Determine the description string + String description(ConfMan.get("description", domain)); + if (description.empty() && !desc.empty()) { + description = desc; + } + + // GUI: Add tab widget + TabWidget *tab = new TabWidget(this, "GameOptions.TabWidget"); + + // + // 1) The game tab + // + tab->addTab(_("Game")); + + // GUI: Label & edit widget for the game ID + if (g_system->getOverlayWidth() > 320) + new StaticTextWidget(tab, "GameOptions_Game.Id", _("ID:"), _("Short game identifier used for referring to saved games and running the game from the command line")); + else + new StaticTextWidget(tab, "GameOptions_Game.Id", _c("ID:", "lowres"), _("Short game identifier used for referring to saved games and running the game from the command line")); + _domainWidget = new DomainEditTextWidget(tab, "GameOptions_Game.Domain", _domain, _("Short game identifier used for referring to saved games and running the game from the command line")); + + // GUI: Label & edit widget for the description + if (g_system->getOverlayWidth() > 320) + new StaticTextWidget(tab, "GameOptions_Game.Name", _("Name:"), _("Full title of the game")); + else + new StaticTextWidget(tab, "GameOptions_Game.Name", _c("Name:", "lowres"), _("Full title of the game")); + _descriptionWidget = new EditTextWidget(tab, "GameOptions_Game.Desc", description, _("Full title of the game")); + + // Language popup + _langPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.LangPopupDesc", _("Language:"), _("Language of the game. This will not turn your Spanish game version into English")); + _langPopUp = new PopUpWidget(tab, "GameOptions_Game.LangPopup", _("Language of the game. This will not turn your Spanish game version into English")); + _langPopUp->appendEntry(_("<default>"), (uint32)Common::UNK_LANG); + _langPopUp->appendEntry("", (uint32)Common::UNK_LANG); + const Common::LanguageDescription *l = Common::g_languages; + for (; l->code; ++l) { + if (checkGameGUIOptionLanguage(l->id, _guioptionsString)) + _langPopUp->appendEntry(l->description, l->id); + } + + // Platform popup + if (g_system->getOverlayWidth() > 320) + _platformPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.PlatformPopupDesc", _("Platform:"), _("Platform the game was originally designed for")); + else + _platformPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.PlatformPopupDesc", _c("Platform:", "lowres"), _("Platform the game was originally designed for")); + _platformPopUp = new PopUpWidget(tab, "GameOptions_Game.PlatformPopup", _("Platform the game was originally designed for")); + _platformPopUp->appendEntry(_("<default>")); + _platformPopUp->appendEntry(""); + const Common::PlatformDescription *p = Common::g_platforms; + for (; p->code; ++p) { + _platformPopUp->appendEntry(p->description, p->id); + } + + // + // 2) The engine tab (shown only if there are custom engine options) + // + if (_engineOptions.size() > 0) { + tab->addTab(_("Engine")); + + addEngineControls(tab, "GameOptions_Engine.", _engineOptions); + } + + // + // 3) The graphics tab + // + _graphicsTabId = tab->addTab(g_system->getOverlayWidth() > 320 ? _("Graphics") : _("GFX")); + + if (g_system->getOverlayWidth() > 320) + _globalGraphicsOverride = new CheckboxWidget(tab, "GameOptions_Graphics.EnableTabCheckbox", _("Override global graphic settings"), 0, kCmdGlobalGraphicsOverride); + else + _globalGraphicsOverride = new CheckboxWidget(tab, "GameOptions_Graphics.EnableTabCheckbox", _c("Override global graphic settings", "lowres"), 0, kCmdGlobalGraphicsOverride); + + addGraphicControls(tab, "GameOptions_Graphics."); + + // + // 4) The audio tab + // + tab->addTab(_("Audio")); + + if (g_system->getOverlayWidth() > 320) + _globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _("Override global audio settings"), 0, kCmdGlobalAudioOverride); + else + _globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _c("Override global audio settings", "lowres"), 0, kCmdGlobalAudioOverride); + + addAudioControls(tab, "GameOptions_Audio."); + addSubtitleControls(tab, "GameOptions_Audio."); + + // + // 5) The volume tab + // + if (g_system->getOverlayWidth() > 320) + tab->addTab(_("Volume")); + else + tab->addTab(_c("Volume", "lowres")); + + if (g_system->getOverlayWidth() > 320) + _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _("Override global volume settings"), 0, kCmdGlobalVolumeOverride); + else + _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _c("Override global volume settings", "lowres"), 0, kCmdGlobalVolumeOverride); + + addVolumeControls(tab, "GameOptions_Volume."); + + // + // 6) The MIDI tab + // + _globalMIDIOverride = NULL; + if (!_guioptions.contains(GUIO_NOMIDI)) { + tab->addTab(_("MIDI")); + + if (g_system->getOverlayWidth() > 320) + _globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _("Override global MIDI settings"), 0, kCmdGlobalMIDIOverride); + else + _globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _c("Override global MIDI settings", "lowres"), 0, kCmdGlobalMIDIOverride); + + addMIDIControls(tab, "GameOptions_MIDI."); + } + + // + // 7) The MT-32 tab + // + _globalMT32Override = NULL; + if (!_guioptions.contains(GUIO_NOMIDI)) { + tab->addTab(_("MT-32")); + + if (g_system->getOverlayWidth() > 320) + _globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _("Override global MT-32 settings"), 0, kCmdGlobalMT32Override); + else + _globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _c("Override global MT-32 settings", "lowres"), 0, kCmdGlobalMT32Override); + + addMT32Controls(tab, "GameOptions_MT32."); + } + + // + // 8) The Paths tab + // + if (g_system->getOverlayWidth() > 320) + tab->addTab(_("Paths")); + else + tab->addTab(_c("Paths", "lowres")); + + // These buttons have to be extra wide, or the text will be truncated + // in the small version of the GUI. + + // GUI: Button + Label for the game path + if (g_system->getOverlayWidth() > 320) + new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _("Game Path:"), 0, kCmdGameBrowser); + else + new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _c("Game Path:", "lowres"), 0, kCmdGameBrowser); + _gamePathWidget = new StaticTextWidget(tab, "GameOptions_Paths.GamepathText", gamePath); + + // GUI: Button + Label for the additional path + if (g_system->getOverlayWidth() > 320) + new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _("Extra Path:"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser); + else + new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _c("Extra Path:", "lowres"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser); + _extraPathWidget = new StaticTextWidget(tab, "GameOptions_Paths.ExtrapathText", extraPath, _("Specifies path to additional data used by the game")); + + _extraPathClearButton = addClearButton(tab, "GameOptions_Paths.ExtraPathClearButton", kCmdExtraPathClear); + + // GUI: Button + Label for the save path + if (g_system->getOverlayWidth() > 320) + new ButtonWidget(tab, "GameOptions_Paths.Savepath", _("Save Path:"), _("Specifies where your saved games are put"), kCmdSaveBrowser); + else + new ButtonWidget(tab, "GameOptions_Paths.Savepath", _c("Save Path:", "lowres"), _("Specifies where your saved games are put"), kCmdSaveBrowser); + _savePathWidget = new StaticTextWidget(tab, "GameOptions_Paths.SavepathText", savePath, _("Specifies where your saved games are put")); + + _savePathClearButton = addClearButton(tab, "GameOptions_Paths.SavePathClearButton", kCmdSavePathClear); + + // Activate the first tab + tab->setActiveTab(0); + _tabWidget = tab; + + // Add OK & Cancel buttons + new ButtonWidget(this, "GameOptions.Cancel", _("Cancel"), 0, kCloseCmd); + new ButtonWidget(this, "GameOptions.Ok", _("OK"), 0, kOKCmd); +} + +void EditGameDialog::open() { + OptionsDialog::open(); + + String extraPath(ConfMan.get("extrapath", _domain)); + if (extraPath.empty() || !ConfMan.hasKey("extrapath", _domain)) { + _extraPathWidget->setLabel(_c("None", "path")); + } + + String savePath(ConfMan.get("savepath", _domain)); + if (savePath.empty() || !ConfMan.hasKey("savepath", _domain)) { + _savePathWidget->setLabel(_("Default")); + } + + int sel, i; + bool e; + + // En-/disable dialog items depending on whether overrides are active or not. + + e = ConfMan.hasKey("gfx_mode", _domain) || + ConfMan.hasKey("render_mode", _domain) || + ConfMan.hasKey("fullscreen", _domain) || + ConfMan.hasKey("aspect_ratio", _domain); + _globalGraphicsOverride->setState(e); + + e = ConfMan.hasKey("music_driver", _domain) || + ConfMan.hasKey("output_rate", _domain) || + ConfMan.hasKey("opl_driver", _domain) || + ConfMan.hasKey("subtitles", _domain) || + ConfMan.hasKey("talkspeed", _domain); + _globalAudioOverride->setState(e); + + e = ConfMan.hasKey("music_volume", _domain) || + ConfMan.hasKey("sfx_volume", _domain) || + ConfMan.hasKey("speech_volume", _domain); + _globalVolumeOverride->setState(e); + + if (!_guioptions.contains(GUIO_NOMIDI)) { + e = ConfMan.hasKey("soundfont", _domain) || + ConfMan.hasKey("multi_midi", _domain) || + ConfMan.hasKey("midi_gain", _domain); + _globalMIDIOverride->setState(e); + } + + if (!_guioptions.contains(GUIO_NOMIDI)) { + e = ConfMan.hasKey("native_mt32", _domain) || + ConfMan.hasKey("enable_gs", _domain); + _globalMT32Override->setState(e); + } + + // TODO: game path + + const Common::Language lang = Common::parseLanguage(ConfMan.get("language", _domain)); + + if (ConfMan.hasKey("language", _domain)) { + _langPopUp->setSelectedTag(lang); + } else { + _langPopUp->setSelectedTag((uint32)Common::UNK_LANG); + } + + if (_langPopUp->numEntries() <= 3) { // If only one language is avaliable + _langPopUpDesc->setEnabled(false); + _langPopUp->setEnabled(false); + } + + // Set the state of engine-specific checkboxes + for (uint j = 0; j < _engineOptions.size(); ++j) { + // The default values for engine-specific checkboxes are not set when + // ScummVM starts, as this would require us to load and poll all of the + // engine plugins on startup. Thus, we set the state of each custom + // option checkbox to what is specified by the engine plugin, and + // update it only if a value has been set in the configuration of the + // currently selected game. + bool isChecked = _engineOptions[j].defaultState; + if (ConfMan.hasKey(_engineOptions[j].configOption, _domain)) + isChecked = ConfMan.getBool(_engineOptions[j].configOption, _domain); + _engineCheckboxes[j]->setState(isChecked); + } + + const Common::PlatformDescription *p = Common::g_platforms; + const Common::Platform platform = Common::parsePlatform(ConfMan.get("platform", _domain)); + sel = 0; + for (i = 0; p->code; ++p, ++i) { + if (platform == p->id) + sel = i + 2; + } + _platformPopUp->setSelected(sel); +} + + +void EditGameDialog::close() { + if (getResult()) { + ConfMan.set("description", _descriptionWidget->getEditString(), _domain); + + Common::Language lang = (Common::Language)_langPopUp->getSelectedTag(); + if (lang < 0) + ConfMan.removeKey("language", _domain); + else + ConfMan.set("language", Common::getLanguageCode(lang), _domain); + + String gamePath(_gamePathWidget->getLabel()); + if (!gamePath.empty()) + ConfMan.set("path", gamePath, _domain); + + String extraPath(_extraPathWidget->getLabel()); + if (!extraPath.empty() && (extraPath != _c("None", "path"))) + ConfMan.set("extrapath", extraPath, _domain); + else + ConfMan.removeKey("extrapath", _domain); + + String savePath(_savePathWidget->getLabel()); + if (!savePath.empty() && (savePath != _("Default"))) + ConfMan.set("savepath", savePath, _domain); + else + ConfMan.removeKey("savepath", _domain); + + Common::Platform platform = (Common::Platform)_platformPopUp->getSelectedTag(); + if (platform < 0) + ConfMan.removeKey("platform", _domain); + else + ConfMan.set("platform", Common::getPlatformCode(platform), _domain); + + // Set the state of engine-specific checkboxes + for (uint i = 0; i < _engineOptions.size(); i++) { + ConfMan.setBool(_engineOptions[i].configOption, _engineCheckboxes[i]->getState(), _domain); + } + } + OptionsDialog::close(); +} + +void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { + switch (cmd) { + case kCmdGlobalGraphicsOverride: + setGraphicSettingsState(data != 0); + draw(); + break; + case kCmdGlobalAudioOverride: + setAudioSettingsState(data != 0); + setSubtitleSettingsState(data != 0); + if (_globalVolumeOverride == NULL) + setVolumeSettingsState(data != 0); + draw(); + break; + case kCmdGlobalMIDIOverride: + setMIDISettingsState(data != 0); + draw(); + break; + case kCmdGlobalMT32Override: + setMT32SettingsState(data != 0); + draw(); + break; + case kCmdGlobalVolumeOverride: + setVolumeSettingsState(data != 0); + draw(); + break; + case kCmdChooseSoundFontCmd: + { + BrowserDialog browser(_("Select SoundFont"), false); + + if (browser.runModal() > 0) { + // User made this choice... + Common::FSNode file(browser.getResult()); + _soundFont->setLabel(file.getPath()); + + if (!file.getPath().empty() && (file.getPath() != _c("None", "path"))) + _soundFontClearButton->setEnabled(true); + else + _soundFontClearButton->setEnabled(false); + + draw(); + } + break; + } + + // Change path for the game + case kCmdGameBrowser: + { + BrowserDialog browser(_("Select directory with game data"), true); + if (browser.runModal() > 0) { + // User made his choice... + Common::FSNode dir(browser.getResult()); + + // TODO: Verify the game can be found in the new directory... Best + // done with optional specific gameid to pluginmgr detectgames? + // FSList files = dir.listDir(FSNode::kListFilesOnly); + + _gamePathWidget->setLabel(dir.getPath()); + draw(); + } + draw(); + break; + } + + // Change path for extra game data (eg, using sword cutscenes when playing via CD) + case kCmdExtraBrowser: + { + BrowserDialog browser(_("Select additional game directory"), true); + if (browser.runModal() > 0) { + // User made his choice... + Common::FSNode dir(browser.getResult()); + _extraPathWidget->setLabel(dir.getPath()); + draw(); + } + draw(); + break; + } + // Change path for stored save game (perm and temp) data + case kCmdSaveBrowser: + { + BrowserDialog browser(_("Select directory for saved games"), true); + if (browser.runModal() > 0) { + // User made his choice... + Common::FSNode dir(browser.getResult()); + _savePathWidget->setLabel(dir.getPath()); +#ifdef USE_LIBCURL + MessageDialog warningMessage(_("Saves sync feature doesn't work with non-default directories. If you want your saves to sync, use default directory.")); + warningMessage.runModal(); +#endif + draw(); + } + draw(); + break; + } + + case kCmdExtraPathClear: + _extraPathWidget->setLabel(_c("None", "path")); + break; + + case kCmdSavePathClear: + _savePathWidget->setLabel(_("Default")); + break; + + case kOKCmd: + { + // Write back changes made to config object + String newDomain(_domainWidget->getEditString()); + if (newDomain != _domain) { + if (newDomain.empty() + || newDomain.hasPrefix("_") + || newDomain == ConfigManager::kApplicationDomain + || ConfMan.hasGameDomain(newDomain)) { + MessageDialog alert(_("This game ID is already taken. Please choose another one.")); + alert.runModal(); + return; + } + ConfMan.renameGameDomain(_domain, newDomain); + _domain = newDomain; + } + } + // FALL THROUGH to default case + default: + OptionsDialog::handleCommand(sender, cmd, data); + } +} + +} // End of namespace GUI diff --git a/gui/editgamedialog.h b/gui/editgamedialog.h new file mode 100644 index 0000000000..0be6c1650e --- /dev/null +++ b/gui/editgamedialog.h @@ -0,0 +1,97 @@ +/* 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. + * + */ + +#ifndef GUI_EDITGAMEDIALOG_H +#define GUI_EDITGAMEDIALOG_H + +#include "engines/game.h" +#include "gui/dialog.h" +#include "gui/options.h" + +namespace GUI { + +class BrowserDialog; +class CommandSender; +class DomainEditTextWidget; +class ListWidget; +class ButtonWidget; +class PicButtonWidget; +class GraphicsWidget; +class StaticTextWidget; +class EditTextWidget; +class SaveLoadChooser; + +Common::String addGameToConf(const GameDescriptor &result); + +/* +* A dialog that allows the user to edit a config game entry. +* TODO: add widgets for some/all of the following +* - Maybe scaler/graphics mode. But there are two problems: +* 1) Different backends can have different scalers with different names, +* so we first have to add a way to query those... no Ender, I don't +* think a bitmasked property() value is nice for this, because we would +* have to add to the bitmask values whenever a backends adds a new scaler). +* 2) At the time the launcher is running, the GFX backend is already setup. +* So when a game is run via the launcher, the custom scaler setting for it won't be +* used. So we'd also have to add an API to change the scaler during runtime +* (the SDL backend can already do that based on user input, but there is no API +* to achieve it) +* If the APIs for 1&2 are in place, we can think about adding this to the Edit&Option dialogs +*/ + +class EditGameDialog : public OptionsDialog { + typedef Common::String String; + typedef Common::Array<Common::String> StringArray; +public: + EditGameDialog(const String &domain, const String &desc); + + void open(); + void close(); + virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); + +protected: + EditTextWidget *_descriptionWidget; + DomainEditTextWidget *_domainWidget; + + StaticTextWidget *_gamePathWidget; + StaticTextWidget *_extraPathWidget; + StaticTextWidget *_savePathWidget; + ButtonWidget *_extraPathClearButton; + ButtonWidget *_savePathClearButton; + + StaticTextWidget *_langPopUpDesc; + PopUpWidget *_langPopUp; + StaticTextWidget *_platformPopUpDesc; + PopUpWidget *_platformPopUp; + + CheckboxWidget *_globalGraphicsOverride; + CheckboxWidget *_globalAudioOverride; + CheckboxWidget *_globalMIDIOverride; + CheckboxWidget *_globalMT32Override; + CheckboxWidget *_globalVolumeOverride; + + ExtraGuiOptions _engineOptions; +}; + +} // End of namespace GUI + +#endif diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp index 9acd9434ff..9d68d76c9c 100644 --- a/gui/gui-manager.cpp +++ b/gui/gui-manager.cpp @@ -216,7 +216,7 @@ void GuiManager::redraw() { // Tanoku: Do not apply shading more than once when opening many dialogs // on top of each other. Screen ends up being too dark and it's a // performance hog. - if (_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 2) + if (_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 3) shading = ThemeEngine::kShadingNone; switch (_redrawStatus) { diff --git a/gui/launcher.cpp b/gui/launcher.cpp index 9a3300b11f..50f060de65 100644 --- a/gui/launcher.cpp +++ b/gui/launcher.cpp @@ -33,6 +33,7 @@ #include "gui/about.h" #include "gui/browser.h" #include "gui/chooser.h" +#include "gui/editgamedialog.h" #include "gui/launcher.h" #include "gui/massadd.h" #include "gui/message.h" @@ -51,6 +52,9 @@ #include "gui/ThemeEval.h" #include "graphics/cursorman.h" +#ifdef USE_LIBCURL +#include "backends/cloud/cloudmanager.h" +#endif using Common::ConfigManager; @@ -84,521 +88,6 @@ enum { kCmdSavePathClear = 'PSAC' }; -/* - * TODO: Clean up this ugly design: we subclass EditTextWidget to perform - * input validation. It would be much more elegant to use a decorator pattern, - * or a validation callback, or something like that. - */ -class DomainEditTextWidget : public EditTextWidget { -public: - DomainEditTextWidget(GuiObject *boss, const String &name, const String &text, const char *tooltip = 0) - : EditTextWidget(boss, name, text, tooltip) { - } - -protected: - bool tryInsertChar(byte c, int pos) { - if (Common::isAlnum(c) || c == '-' || c == '_') { - _editString.insertChar(c, pos); - return true; - } - return false; - } -}; - -/* - * A dialog that allows the user to edit a config game entry. - * TODO: add widgets for some/all of the following - * - Maybe scaler/graphics mode. But there are two problems: - * 1) Different backends can have different scalers with different names, - * so we first have to add a way to query those... no Ender, I don't - * think a bitmasked property() value is nice for this, because we would - * have to add to the bitmask values whenever a backends adds a new scaler). - * 2) At the time the launcher is running, the GFX backend is already setup. - * So when a game is run via the launcher, the custom scaler setting for it won't be - * used. So we'd also have to add an API to change the scaler during runtime - * (the SDL backend can already do that based on user input, but there is no API - * to achieve it) - * If the APIs for 1&2 are in place, we can think about adding this to the Edit&Option dialogs - */ - -class EditGameDialog : public OptionsDialog { - typedef Common::String String; - typedef Common::Array<Common::String> StringArray; -public: - EditGameDialog(const String &domain, const String &desc); - - void open(); - void close(); - virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); - -protected: - EditTextWidget *_descriptionWidget; - DomainEditTextWidget *_domainWidget; - - StaticTextWidget *_gamePathWidget; - StaticTextWidget *_extraPathWidget; - StaticTextWidget *_savePathWidget; - ButtonWidget *_extraPathClearButton; - ButtonWidget *_savePathClearButton; - - StaticTextWidget *_langPopUpDesc; - PopUpWidget *_langPopUp; - StaticTextWidget *_platformPopUpDesc; - PopUpWidget *_platformPopUp; - - CheckboxWidget *_globalGraphicsOverride; - CheckboxWidget *_globalAudioOverride; - CheckboxWidget *_globalMIDIOverride; - CheckboxWidget *_globalMT32Override; - CheckboxWidget *_globalVolumeOverride; - - ExtraGuiOptions _engineOptions; -}; - -EditGameDialog::EditGameDialog(const String &domain, const String &desc) - : OptionsDialog(domain, "GameOptions") { - // Retrieve all game specific options. - const EnginePlugin *plugin = 0; - // To allow for game domains without a gameid. - // TODO: Is it intentional that this is still supported? - String gameId(ConfMan.get("gameid", domain)); - if (gameId.empty()) - gameId = domain; - // Retrieve the plugin, since we need to access the engine's MetaEngine - // implementation. - EngineMan.findGame(gameId, &plugin); - if (plugin) { - _engineOptions = (*plugin)->getExtraGuiOptions(domain); - } else { - warning("Plugin for target \"%s\" not found! Game specific settings might be missing", domain.c_str()); - } - - // GAME: Path to game data (r/o), extra data (r/o), and save data (r/w) - String gamePath(ConfMan.get("path", _domain)); - String extraPath(ConfMan.get("extrapath", _domain)); - String savePath(ConfMan.get("savepath", _domain)); - - // GAME: Determine the description string - String description(ConfMan.get("description", domain)); - if (description.empty() && !desc.empty()) { - description = desc; - } - - // GUI: Add tab widget - TabWidget *tab = new TabWidget(this, "GameOptions.TabWidget"); - - // - // 1) The game tab - // - tab->addTab(_("Game")); - - // GUI: Label & edit widget for the game ID - if (g_system->getOverlayWidth() > 320) - new StaticTextWidget(tab, "GameOptions_Game.Id", _("ID:"), _("Short game identifier used for referring to saved games and running the game from the command line")); - else - new StaticTextWidget(tab, "GameOptions_Game.Id", _c("ID:", "lowres"), _("Short game identifier used for referring to saved games and running the game from the command line")); - _domainWidget = new DomainEditTextWidget(tab, "GameOptions_Game.Domain", _domain, _("Short game identifier used for referring to saved games and running the game from the command line")); - - // GUI: Label & edit widget for the description - if (g_system->getOverlayWidth() > 320) - new StaticTextWidget(tab, "GameOptions_Game.Name", _("Name:"), _("Full title of the game")); - else - new StaticTextWidget(tab, "GameOptions_Game.Name", _c("Name:", "lowres"), _("Full title of the game")); - _descriptionWidget = new EditTextWidget(tab, "GameOptions_Game.Desc", description, _("Full title of the game")); - - // Language popup - _langPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.LangPopupDesc", _("Language:"), _("Language of the game. This will not turn your Spanish game version into English")); - _langPopUp = new PopUpWidget(tab, "GameOptions_Game.LangPopup", _("Language of the game. This will not turn your Spanish game version into English")); - _langPopUp->appendEntry(_("<default>"), (uint32)Common::UNK_LANG); - _langPopUp->appendEntry("", (uint32)Common::UNK_LANG); - const Common::LanguageDescription *l = Common::g_languages; - for (; l->code; ++l) { - if (checkGameGUIOptionLanguage(l->id, _guioptionsString)) - _langPopUp->appendEntry(l->description, l->id); - } - - // Platform popup - if (g_system->getOverlayWidth() > 320) - _platformPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.PlatformPopupDesc", _("Platform:"), _("Platform the game was originally designed for")); - else - _platformPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.PlatformPopupDesc", _c("Platform:", "lowres"), _("Platform the game was originally designed for")); - _platformPopUp = new PopUpWidget(tab, "GameOptions_Game.PlatformPopup", _("Platform the game was originally designed for")); - _platformPopUp->appendEntry(_("<default>")); - _platformPopUp->appendEntry(""); - const Common::PlatformDescription *p = Common::g_platforms; - for (; p->code; ++p) { - _platformPopUp->appendEntry(p->description, p->id); - } - - // - // 2) The engine tab (shown only if there are custom engine options) - // - if (_engineOptions.size() > 0) { - tab->addTab(_("Engine")); - - addEngineControls(tab, "GameOptions_Engine.", _engineOptions); - } - - // - // 3) The graphics tab - // - _graphicsTabId = tab->addTab(g_system->getOverlayWidth() > 320 ? _("Graphics") : _("GFX")); - - if (g_system->getOverlayWidth() > 320) - _globalGraphicsOverride = new CheckboxWidget(tab, "GameOptions_Graphics.EnableTabCheckbox", _("Override global graphic settings"), 0, kCmdGlobalGraphicsOverride); - else - _globalGraphicsOverride = new CheckboxWidget(tab, "GameOptions_Graphics.EnableTabCheckbox", _c("Override global graphic settings", "lowres"), 0, kCmdGlobalGraphicsOverride); - - addGraphicControls(tab, "GameOptions_Graphics."); - - // - // 4) The audio tab - // - tab->addTab(_("Audio")); - - if (g_system->getOverlayWidth() > 320) - _globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _("Override global audio settings"), 0, kCmdGlobalAudioOverride); - else - _globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _c("Override global audio settings", "lowres"), 0, kCmdGlobalAudioOverride); - - addAudioControls(tab, "GameOptions_Audio."); - addSubtitleControls(tab, "GameOptions_Audio."); - - // - // 5) The volume tab - // - if (g_system->getOverlayWidth() > 320) - tab->addTab(_("Volume")); - else - tab->addTab(_c("Volume", "lowres")); - - if (g_system->getOverlayWidth() > 320) - _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _("Override global volume settings"), 0, kCmdGlobalVolumeOverride); - else - _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _c("Override global volume settings", "lowres"), 0, kCmdGlobalVolumeOverride); - - addVolumeControls(tab, "GameOptions_Volume."); - - // - // 6) The MIDI tab - // - _globalMIDIOverride = NULL; - if (!_guioptions.contains(GUIO_NOMIDI)) { - tab->addTab(_("MIDI")); - - if (g_system->getOverlayWidth() > 320) - _globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _("Override global MIDI settings"), 0, kCmdGlobalMIDIOverride); - else - _globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _c("Override global MIDI settings", "lowres"), 0, kCmdGlobalMIDIOverride); - - addMIDIControls(tab, "GameOptions_MIDI."); - } - - // - // 7) The MT-32 tab - // - _globalMT32Override = NULL; - if (!_guioptions.contains(GUIO_NOMIDI)) { - tab->addTab(_("MT-32")); - - if (g_system->getOverlayWidth() > 320) - _globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _("Override global MT-32 settings"), 0, kCmdGlobalMT32Override); - else - _globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _c("Override global MT-32 settings", "lowres"), 0, kCmdGlobalMT32Override); - - addMT32Controls(tab, "GameOptions_MT32."); - } - - // - // 8) The Paths tab - // - if (g_system->getOverlayWidth() > 320) - tab->addTab(_("Paths")); - else - tab->addTab(_c("Paths", "lowres")); - - // These buttons have to be extra wide, or the text will be truncated - // in the small version of the GUI. - - // GUI: Button + Label for the game path - if (g_system->getOverlayWidth() > 320) - new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _("Game Path:"), 0, kCmdGameBrowser); - else - new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _c("Game Path:", "lowres"), 0, kCmdGameBrowser); - _gamePathWidget = new StaticTextWidget(tab, "GameOptions_Paths.GamepathText", gamePath); - - // GUI: Button + Label for the additional path - if (g_system->getOverlayWidth() > 320) - new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _("Extra Path:"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser); - else - new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _c("Extra Path:", "lowres"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser); - _extraPathWidget = new StaticTextWidget(tab, "GameOptions_Paths.ExtrapathText", extraPath, _("Specifies path to additional data used by the game")); - - _extraPathClearButton = addClearButton(tab, "GameOptions_Paths.ExtraPathClearButton", kCmdExtraPathClear); - - // GUI: Button + Label for the save path - if (g_system->getOverlayWidth() > 320) - new ButtonWidget(tab, "GameOptions_Paths.Savepath", _("Save Path:"), _("Specifies where your saved games are put"), kCmdSaveBrowser); - else - new ButtonWidget(tab, "GameOptions_Paths.Savepath", _c("Save Path:", "lowres"), _("Specifies where your saved games are put"), kCmdSaveBrowser); - _savePathWidget = new StaticTextWidget(tab, "GameOptions_Paths.SavepathText", savePath, _("Specifies where your saved games are put")); - - _savePathClearButton = addClearButton(tab, "GameOptions_Paths.SavePathClearButton", kCmdSavePathClear); - - // Activate the first tab - tab->setActiveTab(0); - _tabWidget = tab; - - // Add OK & Cancel buttons - new ButtonWidget(this, "GameOptions.Cancel", _("Cancel"), 0, kCloseCmd); - new ButtonWidget(this, "GameOptions.Ok", _("OK"), 0, kOKCmd); -} - -void EditGameDialog::open() { - OptionsDialog::open(); - - String extraPath(ConfMan.get("extrapath", _domain)); - if (extraPath.empty() || !ConfMan.hasKey("extrapath", _domain)) { - _extraPathWidget->setLabel(_c("None", "path")); - } - - String savePath(ConfMan.get("savepath", _domain)); - if (savePath.empty() || !ConfMan.hasKey("savepath", _domain)) { - _savePathWidget->setLabel(_("Default")); - } - - int sel, i; - bool e; - - // En-/disable dialog items depending on whether overrides are active or not. - - e = ConfMan.hasKey("gfx_mode", _domain) || - ConfMan.hasKey("render_mode", _domain) || - ConfMan.hasKey("fullscreen", _domain) || - ConfMan.hasKey("aspect_ratio", _domain); - _globalGraphicsOverride->setState(e); - - e = ConfMan.hasKey("music_driver", _domain) || - ConfMan.hasKey("output_rate", _domain) || - ConfMan.hasKey("opl_driver", _domain) || - ConfMan.hasKey("subtitles", _domain) || - ConfMan.hasKey("talkspeed", _domain); - _globalAudioOverride->setState(e); - - e = ConfMan.hasKey("music_volume", _domain) || - ConfMan.hasKey("sfx_volume", _domain) || - ConfMan.hasKey("speech_volume", _domain); - _globalVolumeOverride->setState(e); - - if (!_guioptions.contains(GUIO_NOMIDI)) { - e = ConfMan.hasKey("soundfont", _domain) || - ConfMan.hasKey("multi_midi", _domain) || - ConfMan.hasKey("midi_gain", _domain); - _globalMIDIOverride->setState(e); - } - - if (!_guioptions.contains(GUIO_NOMIDI)) { - e = ConfMan.hasKey("native_mt32", _domain) || - ConfMan.hasKey("enable_gs", _domain); - _globalMT32Override->setState(e); - } - - // TODO: game path - - const Common::Language lang = Common::parseLanguage(ConfMan.get("language", _domain)); - - if (ConfMan.hasKey("language", _domain)) { - _langPopUp->setSelectedTag(lang); - } else { - _langPopUp->setSelectedTag((uint32)Common::UNK_LANG); - } - - if (_langPopUp->numEntries() <= 3) { // If only one language is avaliable - _langPopUpDesc->setEnabled(false); - _langPopUp->setEnabled(false); - } - - // Set the state of engine-specific checkboxes - for (uint j = 0; j < _engineOptions.size(); ++j) { - // The default values for engine-specific checkboxes are not set when - // ScummVM starts, as this would require us to load and poll all of the - // engine plugins on startup. Thus, we set the state of each custom - // option checkbox to what is specified by the engine plugin, and - // update it only if a value has been set in the configuration of the - // currently selected game. - bool isChecked = _engineOptions[j].defaultState; - if (ConfMan.hasKey(_engineOptions[j].configOption, _domain)) - isChecked = ConfMan.getBool(_engineOptions[j].configOption, _domain); - _engineCheckboxes[j]->setState(isChecked); - } - - const Common::PlatformDescription *p = Common::g_platforms; - const Common::Platform platform = Common::parsePlatform(ConfMan.get("platform", _domain)); - sel = 0; - for (i = 0; p->code; ++p, ++i) { - if (platform == p->id) - sel = i + 2; - } - _platformPopUp->setSelected(sel); -} - - -void EditGameDialog::close() { - if (getResult()) { - ConfMan.set("description", _descriptionWidget->getEditString(), _domain); - - Common::Language lang = (Common::Language)_langPopUp->getSelectedTag(); - if (lang < 0) - ConfMan.removeKey("language", _domain); - else - ConfMan.set("language", Common::getLanguageCode(lang), _domain); - - String gamePath(_gamePathWidget->getLabel()); - if (!gamePath.empty()) - ConfMan.set("path", gamePath, _domain); - - String extraPath(_extraPathWidget->getLabel()); - if (!extraPath.empty() && (extraPath != _c("None", "path"))) - ConfMan.set("extrapath", extraPath, _domain); - else - ConfMan.removeKey("extrapath", _domain); - - String savePath(_savePathWidget->getLabel()); - if (!savePath.empty() && (savePath != _("Default"))) - ConfMan.set("savepath", savePath, _domain); - else - ConfMan.removeKey("savepath", _domain); - - Common::Platform platform = (Common::Platform)_platformPopUp->getSelectedTag(); - if (platform < 0) - ConfMan.removeKey("platform", _domain); - else - ConfMan.set("platform", Common::getPlatformCode(platform), _domain); - - // Set the state of engine-specific checkboxes - for (uint i = 0; i < _engineOptions.size(); i++) { - ConfMan.setBool(_engineOptions[i].configOption, _engineCheckboxes[i]->getState(), _domain); - } - } - OptionsDialog::close(); -} - -void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { - switch (cmd) { - case kCmdGlobalGraphicsOverride: - setGraphicSettingsState(data != 0); - draw(); - break; - case kCmdGlobalAudioOverride: - setAudioSettingsState(data != 0); - setSubtitleSettingsState(data != 0); - if (_globalVolumeOverride == NULL) - setVolumeSettingsState(data != 0); - draw(); - break; - case kCmdGlobalMIDIOverride: - setMIDISettingsState(data != 0); - draw(); - break; - case kCmdGlobalMT32Override: - setMT32SettingsState(data != 0); - draw(); - break; - case kCmdGlobalVolumeOverride: - setVolumeSettingsState(data != 0); - draw(); - break; - case kCmdChooseSoundFontCmd: { - BrowserDialog browser(_("Select SoundFont"), false); - - if (browser.runModal() > 0) { - // User made this choice... - Common::FSNode file(browser.getResult()); - _soundFont->setLabel(file.getPath()); - - if (!file.getPath().empty() && (file.getPath() != _c("None", "path"))) - _soundFontClearButton->setEnabled(true); - else - _soundFontClearButton->setEnabled(false); - - draw(); - } - break; - } - - // Change path for the game - case kCmdGameBrowser: { - BrowserDialog browser(_("Select directory with game data"), true); - if (browser.runModal() > 0) { - // User made his choice... - Common::FSNode dir(browser.getResult()); - - // TODO: Verify the game can be found in the new directory... Best - // done with optional specific gameid to pluginmgr detectgames? - // FSList files = dir.listDir(FSNode::kListFilesOnly); - - _gamePathWidget->setLabel(dir.getPath()); - draw(); - } - draw(); - break; - } - - // Change path for extra game data (eg, using sword cutscenes when playing via CD) - case kCmdExtraBrowser: { - BrowserDialog browser(_("Select additional game directory"), true); - if (browser.runModal() > 0) { - // User made his choice... - Common::FSNode dir(browser.getResult()); - _extraPathWidget->setLabel(dir.getPath()); - draw(); - } - draw(); - break; - } - // Change path for stored save game (perm and temp) data - case kCmdSaveBrowser: { - BrowserDialog browser(_("Select directory for saved games"), true); - if (browser.runModal() > 0) { - // User made his choice... - Common::FSNode dir(browser.getResult()); - _savePathWidget->setLabel(dir.getPath()); - draw(); - } - draw(); - break; - } - - case kCmdExtraPathClear: - _extraPathWidget->setLabel(_c("None", "path")); - break; - - case kCmdSavePathClear: - _savePathWidget->setLabel(_("Default")); - break; - - case kOKCmd: { - // Write back changes made to config object - String newDomain(_domainWidget->getEditString()); - if (newDomain != _domain) { - if (newDomain.empty() - || newDomain.hasPrefix("_") - || newDomain == ConfigManager::kApplicationDomain - || ConfMan.hasGameDomain(newDomain)) { - MessageDialog alert(_("This game ID is already taken. Please choose another one.")); - alert.runModal(); - return; - } - ConfMan.renameGameDomain(_domain, newDomain); - _domain = newDomain; - } - } - // FALL THROUGH to default case - default: - OptionsDialog::handleCommand(sender, cmd, data); - } -} - #pragma mark - LauncherDialog::LauncherDialog() @@ -839,65 +328,25 @@ void LauncherDialog::addGame() { if (_browser->runModal() > 0) { // User made his choice... - Common::FSNode dir(_browser->getResult()); - Common::FSList files; - if (!dir.getChildren(files, Common::FSNode::kListAll)) { - MessageDialog alert(_("ScummVM couldn't open the specified directory!")); - alert.runModal(); - return; - } - - // ...so let's determine a list of candidates, games that - // could be contained in the specified directory. - GameList candidates(EngineMan.detectGames(files)); - - int idx; - if (candidates.empty()) { - // No game was found in the specified directory - MessageDialog alert(_("ScummVM could not find any game in the specified directory!")); - alert.runModal(); - idx = -1; - - looping = true; - } else if (candidates.size() == 1) { - // Exact match - idx = 0; - } else { - // Display the candidates to the user and let her/him pick one - StringArray list; - for (idx = 0; idx < (int)candidates.size(); idx++) - list.push_back(candidates[idx].description()); - - ChooserDialog dialog(_("Pick the game:")); - dialog.setList(list); - idx = dialog.runModal(); - } - if (0 <= idx && idx < (int)candidates.size()) { - GameDescriptor result = candidates[idx]; - - // TODO: Change the detectors to set "path" ! - result["path"] = dir.getPath(); - - Common::String domain = addGameToConf(result); - - // Display edit dialog for the new entry - EditGameDialog editDialog(domain, result.description()); - if (editDialog.runModal() > 0) { - // User pressed OK, so make changes permanent - - // Write config to disk - ConfMan.flushToDisk(); - - // Update the ListWidget, select the new item, and force a redraw - updateListing(); - selectTarget(editDialog.getDomain()); - draw(); +#ifdef USE_LIBCURL + String selectedDirectory = _browser->getResult().getPath(); + String bannedDirectory = CloudMan.getDownloadLocalDirectory(); + if (selectedDirectory.size() && selectedDirectory.lastChar() != '/' && selectedDirectory.lastChar() != '\\') + selectedDirectory += '/'; + if (bannedDirectory.size() && bannedDirectory.lastChar() != '/' && bannedDirectory.lastChar() != '\\') { + if (selectedDirectory.size()) { + bannedDirectory += selectedDirectory.lastChar(); } else { - // User aborted, remove the the new domain again - ConfMan.removeGameDomain(domain); + bannedDirectory += '/'; } - } + if (selectedDirectory.equalsIgnoreCase(bannedDirectory)) { + MessageDialog alert(_("This directory cannot be used yet, it is being downloaded into!")); + alert.runModal(); + return; + } +#endif + looping = !doGameDetection(_browser->getResult().getPath()); } } while (looping); } @@ -1076,6 +525,81 @@ void LauncherDialog::handleKeyUp(Common::KeyState state) { updateButtons(); } +bool LauncherDialog::doGameDetection(const Common::String &path) { + // Allow user to add a new game to the list. + // 2) try to auto detect which game is in the directory, if we cannot + // determine it uniquely present a list of candidates to the user + // to pick from + // 3) Display the 'Edit' dialog for that item, letting the user specify + // an alternate description (to distinguish multiple versions of the + // game, e.g. 'Monkey German' and 'Monkey English') and set default + // options for that game + // 4) If no game is found in the specified directory, return to the + // dialog. + + // User made his choice... + Common::FSNode dir(path); + Common::FSList files; + if (!dir.getChildren(files, Common::FSNode::kListAll)) { + MessageDialog alert(_("ScummVM couldn't open the specified directory!")); + alert.runModal(); + return true; + } + + // ...so let's determine a list of candidates, games that + // could be contained in the specified directory. + GameList candidates(EngineMan.detectGames(files)); + + int idx; + if (candidates.empty()) { + // No game was found in the specified directory + MessageDialog alert(_("ScummVM could not find any game in the specified directory!")); + alert.runModal(); + idx = -1; + return false; + } else if (candidates.size() == 1) { + // Exact match + idx = 0; + } else { + // Display the candidates to the user and let her/him pick one + StringArray list; + for (idx = 0; idx < (int)candidates.size(); idx++) + list.push_back(candidates[idx].description()); + + ChooserDialog dialog(_("Pick the game:")); + dialog.setList(list); + idx = dialog.runModal(); + } + if (0 <= idx && idx < (int)candidates.size()) { + GameDescriptor result = candidates[idx]; + + // TODO: Change the detectors to set "path" ! + result["path"] = dir.getPath(); + + Common::String domain = addGameToConf(result); + + // Display edit dialog for the new entry + EditGameDialog editDialog(domain, result.description()); + if (editDialog.runModal() > 0) { + // User pressed OK, so make changes permanent + + // Write config to disk + ConfMan.flushToDisk(); + + // Update the ListWidget, select the new item, and force a redraw + updateListing(); + selectTarget(editDialog.getDomain()); + draw(); + } else { + // User aborted, remove the the new domain again + ConfMan.removeGameDomain(domain); + } + + } + + return true; +} + void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { int item = _list->getSelected(); @@ -1093,7 +617,7 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat loadGameButtonPressed(item); break; case kOptionsCmd: { - GlobalOptionsDialog options; + GlobalOptionsDialog options(this); options.runModal(); } break; diff --git a/gui/launcher.h b/gui/launcher.h index e9c76a5320..58f1c930ed 100644 --- a/gui/launcher.h +++ b/gui/launcher.h @@ -51,7 +51,7 @@ public: virtual void handleKeyDown(Common::KeyState state); virtual void handleKeyUp(Common::KeyState state); - + bool doGameDetection(const Common::String &path); protected: EditTextWidget *_searchWidget; ListWidget *_list; diff --git a/gui/module.mk b/gui/module.mk index 6cbc63d24d..54818c264e 100644 --- a/gui/module.mk +++ b/gui/module.mk @@ -6,6 +6,7 @@ MODULE_OBJS := \ console.o \ debugger.o \ dialog.o \ + editgamedialog.o \ error.o \ EventRecorder.o \ filebrowser-dialog.o \ @@ -24,6 +25,9 @@ MODULE_OBJS := \ ThemeLayout.o \ ThemeParser.o \ Tooltip.o \ + animation/Animation.o \ + animation/RepeatAnimationWrapper.o \ + animation/SequenceAnimationComposite.o \ widget.o \ widgets/editable.o \ widgets/edittext.o \ @@ -54,6 +58,13 @@ MODULE_OBJS += \ endif endif +ifdef USE_LIBCURL +MODULE_OBJS += \ + downloaddialog.o \ + remotebrowser.o \ + storagewizarddialog.o +endif + ifdef ENABLE_EVENTRECORDER MODULE_OBJS += \ editrecorddialog.o \ diff --git a/gui/object.h b/gui/object.h index 219bf77f69..1541c35aa8 100644 --- a/gui/object.h +++ b/gui/object.h @@ -76,6 +76,8 @@ public: virtual void setTextDrawableArea(const Common::Rect &r) { _textDrawableArea = r; } + virtual int16 getRelX() const { return _x; } + virtual int16 getRelY() const { return _y; } virtual int16 getAbsX() const { return _x; } virtual int16 getAbsY() const { return _y; } virtual int16 getChildX() const { return getAbsX(); } @@ -91,6 +93,10 @@ public: virtual void removeWidget(Widget *widget); + virtual bool isPointIn(int x, int y) { + return (x >= _x && x < (_x + _w) && (y >= _y) && (y < _y + _h)); + } + protected: virtual void releaseFocus() = 0; }; diff --git a/gui/options.cpp b/gui/options.cpp index e410971818..3f2674b016 100644 --- a/gui/options.cpp +++ b/gui/options.cpp @@ -42,6 +42,18 @@ #include "audio/musicplugin.h" #include "audio/mixer.h" #include "audio/fmopl.h" +#include "widgets/scrollcontainer.h" +#include "widgets/edittext.h" + +#ifdef USE_LIBCURL +#include "backends/cloud/cloudmanager.h" +#include "gui/downloaddialog.h" +#include "gui/storagewizarddialog.h" +#endif + +#ifdef USE_SDL_NET +#include "backends/networking/sdl_net/localwebserver.h" +#endif namespace GUI { @@ -84,6 +96,19 @@ enum { }; #endif +#ifdef USE_CLOUD +enum { + kConfigureStorageCmd = 'cfst', + kRefreshStorageCmd = 'rfst', + kDownloadStorageCmd = 'dlst', + kRunServerCmd = 'rnsv', + kCloudTabContainerReflowCmd = 'ctcr', + kServerPortClearCmd = 'spcl', + kChooseRootDirCmd = 'chrp', + kRootPathClearCmd = 'clrp' +}; +#endif + static const char *savePeriodLabels[] = { _s("Never"), _s("every 5 mins"), _s("every 10 mins"), _s("every 15 mins"), _s("every 30 mins"), 0 }; static const int savePeriodValues[] = { 0, 5 * 60, 10 * 60, 15 * 60, 30 * 60, -1 }; static const char *outputRateLabels[] = { _s("<default>"), _s("8 kHz"), _s("11 kHz"), _s("22 kHz"), _s("44 kHz"), _s("48 kHz"), 0 }; @@ -1078,8 +1103,8 @@ void OptionsDialog::reflowLayout() { #pragma mark - -GlobalOptionsDialog::GlobalOptionsDialog() - : OptionsDialog(Common::ConfigManager::kApplicationDomain, "GlobalOptions") { +GlobalOptionsDialog::GlobalOptionsDialog(LauncherDialog *launcher) + : OptionsDialog(Common::ConfigManager::kApplicationDomain, "GlobalOptions"), _launcher(launcher) { // The tab widget TabWidget *tab = new TabWidget(this, "GlobalOptions.TabWidget"); @@ -1251,6 +1276,76 @@ GlobalOptionsDialog::GlobalOptionsDialog() new ButtonWidget(tab, "GlobalOptions_Misc.UpdatesCheckManuallyButton", _("Check now"), 0, kUpdatesCheckCmd); #endif +#ifdef USE_CLOUD + // + // 7) The cloud tab + // + if (g_system->getOverlayWidth() > 320) + tab->addTab(_("Cloud")); + else + tab->addTab(_c("Cloud", "lowres")); + + ScrollContainerWidget *container = new ScrollContainerWidget(tab, "GlobalOptions_Cloud.Container", kCloudTabContainerReflowCmd); + container->setTarget(this); + +#ifdef USE_LIBCURL + _selectedStorageIndex = CloudMan.getStorageIndex(); +#else + _selectedStorageIndex = 0; +#endif + + _storagePopUpDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StoragePopupDesc", _("Storage:"), _("Active cloud storage")); + _storagePopUp = new PopUpWidget(container, "GlobalOptions_Cloud_Container.StoragePopup"); +#ifdef USE_LIBCURL + Common::StringArray list = CloudMan.listStorages(); + for (uint32 i = 0; i < list.size(); ++i) + _storagePopUp->appendEntry(list[i], i); +#else + _storagePopUp->appendEntry(_("<none>"), 0); +#endif + _storagePopUp->setSelected(_selectedStorageIndex); + + _storageUsernameDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageUsernameDesc", _("Username:"), _("Username used by this storage")); + _storageUsername = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageUsernameLabel", "<none>"); + + _storageUsedSpaceDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageUsedSpaceDesc", _("Used space:"), _("Space used by ScummVM's saves on this storage")); + _storageUsedSpace = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageUsedSpaceLabel", "0 bytes"); + + _storageLastSyncDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageLastSyncDesc", _("Last sync time:"), _("When this storage did saves sync last time")); + _storageLastSync = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageLastSyncLabel", "<never>"); + + _storageConnectButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.ConnectButton", _("Connect"), _("Open wizard dialog to connect your cloud storage account"), kConfigureStorageCmd); + _storageRefreshButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.RefreshButton", _("Refresh"), _("Refresh current cloud storage information (username and usage)"), kRefreshStorageCmd); + _storageDownloadButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.DownloadButton", _("Downloads"), _("Open downloads manager dialog"), kDownloadStorageCmd); + + _runServerButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.RunServerButton", _("Run server"), _("Run local webserver"), kRunServerCmd); + _serverInfoLabel = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.ServerInfoLabel", _("Not running")); + + // Root path + if (g_system->getOverlayWidth() > 320) + _rootPathButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.RootPathButton", _("/root/ Path:"), _("Specifies where Files Manager can access to"), kChooseRootDirCmd); + else + _rootPathButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.RootPathButton", _c("/root/ Path:", "lowres"), _("Specifies where Files Manager can access to"), kChooseRootDirCmd); + _rootPath = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.RootPath", "/foo/bar", _("Specifies where Files Manager can access to")); + + _rootPathClearButton = addClearButton(container, "GlobalOptions_Cloud_Container.RootPathClearButton", kRootPathClearCmd); + +#ifdef USE_SDL_NET + uint32 port = Networking::LocalWebserver::getPort(); +#else + uint32 port = 0; // the following widgets are hidden anyway +#endif + _serverPortDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.ServerPortDesc", _("Server's port:"), _("Which port is used by server\nAuth with server is not available with non-default port")); + _serverPort = new EditTextWidget(container, "GlobalOptions_Cloud_Container.ServerPortEditText", Common::String::format("%u", port), 0); + _serverPortClearButton = addClearButton(container, "GlobalOptions_Cloud_Container.ServerPortClearButton", kServerPortClearCmd); + + setupCloudTab(); + _redrawCloudTab = false; +#ifdef USE_SDL_NET + _serverWasRunning = false; +#endif +#endif + // Activate the first tab tab->setActiveTab(0); _tabWidget = tab; @@ -1327,6 +1422,15 @@ void GlobalOptionsDialog::open() { if (mode == ThemeEngine::kGfxDisabled) mode = ThemeEngine::_defaultRendererMode; _rendererPopUp->setSelectedTag(mode); + +#ifdef USE_CLOUD + Common::String rootPath(ConfMan.get("rootpath", "cloud")); + if (rootPath.empty() || !ConfMan.hasKey("rootpath", "cloud")) { + _rootPath->setLabel(_c("None", "path")); + } else { + _rootPath->setLabel(rootPath); + } +#endif } void GlobalOptionsDialog::close() { @@ -1357,6 +1461,14 @@ void GlobalOptionsDialog::close() { ConfMan.removeKey("pluginspath", _domain); #endif +#ifdef USE_CLOUD + Common::String rootPath(_rootPath->getLabel()); + if (!rootPath.empty() && (rootPath != _c("None", "path"))) + ConfMan.set("rootpath", rootPath, "cloud"); + else + ConfMan.removeKey("rootpath", "cloud"); +#endif + ConfMan.setInt("autosave_period", _autosavePeriodPopUp->getSelectedTag(), _domain); GUI::ThemeEngine::GraphicsMode selected = (GUI::ThemeEngine::GraphicsMode)_rendererPopUp->getSelectedTag(); @@ -1402,7 +1514,38 @@ void GlobalOptionsDialog::close() { } #endif +#ifdef USE_LIBCURL + if (CloudMan.getStorageIndex() != _selectedStorageIndex) { + if (!CloudMan.switchStorage(_selectedStorageIndex)) { + bool anotherStorageIsWorking = CloudMan.isWorking(); + Common::String message = _("Failed to change cloud storage!"); + if (anotherStorageIsWorking) { + message += "\n"; + message += _("Current cloud storage is working at the moment."); + } + MessageDialog dialog(message); + dialog.runModal(); + } + } +#endif +#ifdef USE_SDL_NET +#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE + // save server's port + uint32 port = Networking::LocalWebserver::getPort(); + if (_serverPort) { + uint64 contents = _serverPort->getEditString().asUint64(); + if (contents != 0) + port = contents; + } + ConfMan.setInt("local_server_port", port); +#endif +#endif } +#ifdef USE_SDL_NET + if (LocalServer.isRunning()) { + LocalServer.stop(); + } +#endif OptionsDialog::close(); } @@ -1456,6 +1599,21 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3 break; } #endif +#ifdef USE_CLOUD + case kChooseRootDirCmd: { + BrowserDialog browser(_("Select directory for Files Manager /root/"), true); + if (browser.runModal() > 0) { + // User made his choice... + Common::FSNode dir(browser.getResult()); + Common::String path = dir.getPath(); + if (path.empty()) + path = "/"; // absolute root + _rootPath->setLabel(path); + draw(); + } + break; + } +#endif case kThemePathClearCmd: _themePath->setLabel(_c("None", "path")); break; @@ -1465,6 +1623,11 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3 case kSavePathClearCmd: _savePath->setLabel(_("Default")); break; +#ifdef USE_CLOUD + case kRootPathClearCmd: + _rootPath->setLabel(_c("None", "path")); + break; +#endif case kChooseSoundFontCmd: { BrowserDialog browser(_("Select SoundFont"), false); if (browser.runModal() > 0) { @@ -1481,7 +1644,8 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3 } break; } - case kChooseThemeCmd: { + case kChooseThemeCmd: + { ThemeBrowser browser; if (browser.runModal() > 0) { // User made his choice... @@ -1513,6 +1677,91 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3 } break; } +#ifdef USE_CLOUD + case kCloudTabContainerReflowCmd: + setupCloudTab(); + break; +#endif +#ifdef USE_LIBCURL + case kPopUpItemSelectedCmd: + { + //update container's scrollbar + reflowLayout(); + break; + } + case kConfigureStorageCmd: + { +#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE + // save server's port + uint32 port = Networking::LocalWebserver::getPort(); + if (_serverPort) { + uint64 contents = _serverPort->getEditString().asUint64(); + if (contents != 0) + port = contents; + } + ConfMan.setInt("local_server_port", port); + ConfMan.flushToDisk(); +#endif + StorageWizardDialog dialog(_selectedStorageIndex); + dialog.runModal(); + //update container's scrollbar + reflowLayout(); + break; + } + case kRefreshStorageCmd: + { + CloudMan.info( + new Common::Callback<GlobalOptionsDialog, Cloud::Storage::StorageInfoResponse>(this, &GlobalOptionsDialog::storageInfoCallback), + new Common::Callback<GlobalOptionsDialog, Networking::ErrorResponse>(this, &GlobalOptionsDialog::storageErrorCallback) + ); + Common::String dir = CloudMan.savesDirectoryPath(); + if (dir.lastChar() == '/') + dir.deleteLastChar(); + CloudMan.listDirectory( + dir, + new Common::Callback<GlobalOptionsDialog, Cloud::Storage::ListDirectoryResponse>(this, &GlobalOptionsDialog::storageListDirectoryCallback), + new Common::Callback<GlobalOptionsDialog, Networking::ErrorResponse>(this, &GlobalOptionsDialog::storageErrorCallback) + ); + break; + } + case kDownloadStorageCmd: + { + DownloadDialog dialog(_selectedStorageIndex, _launcher); + dialog.runModal(); + break; + } +#endif +#ifdef USE_SDL_NET + case kRunServerCmd: + { +#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE + // save server's port + uint32 port = Networking::LocalWebserver::getPort(); + if (_serverPort) { + uint64 contents = _serverPort->getEditString().asUint64(); + if (contents != 0) + port = contents; + } + ConfMan.setInt("local_server_port", port); + ConfMan.flushToDisk(); +#endif + + if (LocalServer.isRunning()) + LocalServer.stopOnIdle(); + else + LocalServer.start(); + + break; + } + + case kServerPortClearCmd: { + if (_serverPort) { + _serverPort->setEditString(Common::String::format("%u", Networking::LocalWebserver::DEFAULT_SERVER_PORT)); + } + draw(); + break; + } +#endif #ifdef GUI_ENABLE_KEYSDIALOG case kChooseKeyMappingCmd: _keysDialog->runModal(); @@ -1534,6 +1783,23 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3 } } +void GlobalOptionsDialog::handleTickle() { + OptionsDialog::handleTickle(); +#ifdef USE_CLOUD +#ifdef USE_SDL_NET + if (LocalServer.isRunning() != _serverWasRunning) { + _serverWasRunning = !_serverWasRunning; + _redrawCloudTab = true; + } +#endif + if (_redrawCloudTab) { + setupCloudTab(); + draw(); + _redrawCloudTab = false; + } +#endif +} + void GlobalOptionsDialog::reflowLayout() { int activeTab = _tabWidget->getActiveTab(); @@ -1567,6 +1833,218 @@ void GlobalOptionsDialog::reflowLayout() { _tabWidget->setActiveTab(activeTab); OptionsDialog::reflowLayout(); +#ifdef USE_CLOUD + setupCloudTab(); +#endif } +#ifdef USE_CLOUD +void GlobalOptionsDialog::setupCloudTab() { + int serverLabelPosition = -1; //no override +#ifdef USE_LIBCURL + _selectedStorageIndex = _storagePopUp->getSelectedTag(); + + if (_storagePopUpDesc) _storagePopUpDesc->setVisible(true); + if (_storagePopUp) _storagePopUp->setVisible(true); + + bool shown = (_selectedStorageIndex != Cloud::kStorageNoneId); + if (_storageUsernameDesc) _storageUsernameDesc->setVisible(shown); + if (_storageUsername) { + Common::String username = CloudMan.getStorageUsername(_selectedStorageIndex); + if (username == "") + username = _("<none>"); + _storageUsername->setLabel(username); + _storageUsername->setVisible(shown); + } + if (_storageUsedSpaceDesc) _storageUsedSpaceDesc->setVisible(shown); + if (_storageUsedSpace) { + uint64 usedSpace = CloudMan.getStorageUsedSpace(_selectedStorageIndex); + _storageUsedSpace->setLabel(Common::String::format(_("%llu bytes"), usedSpace)); + _storageUsedSpace->setVisible(shown); + } + if (_storageLastSyncDesc) _storageLastSyncDesc->setVisible(shown); + if (_storageLastSync) { + Common::String sync = CloudMan.getStorageLastSync(_selectedStorageIndex); + if (sync == "") { + if (_selectedStorageIndex == CloudMan.getStorageIndex() && CloudMan.isSyncing()) + sync = _("<right now>"); + else + sync = _("<never>"); + } + _storageLastSync->setLabel(sync); + _storageLastSync->setVisible(shown); + } + if (_storageConnectButton) + _storageConnectButton->setVisible(shown); + if (_storageRefreshButton) + _storageRefreshButton->setVisible(shown && _selectedStorageIndex == CloudMan.getStorageIndex()); + if (_storageDownloadButton) + _storageDownloadButton->setVisible(shown && _selectedStorageIndex == CloudMan.getStorageIndex()); + if (!shown) + serverLabelPosition = (_storageUsernameDesc ? _storageUsernameDesc->getRelY() : 0); +#else + _selectedStorageIndex = 0; + + if (_storagePopUpDesc) + _storagePopUpDesc->setVisible(false); + if (_storagePopUp) + _storagePopUp->setVisible(false); + if (_storageUsernameDesc) + _storageUsernameDesc->setVisible(false); + if (_storageUsernameDesc) + _storageUsernameDesc->setVisible(false); + if (_storageUsername) + _storageUsername->setVisible(false); + if (_storageUsedSpaceDesc) + _storageUsedSpaceDesc->setVisible(false); + if (_storageUsedSpace) + _storageUsedSpace->setVisible(false); + if (_storageLastSyncDesc) + _storageLastSyncDesc->setVisible(false); + if (_storageLastSync) + _storageLastSync->setVisible(false); + if (_storageConnectButton) + _storageConnectButton->setVisible(false); + if (_storageRefreshButton) + _storageRefreshButton->setVisible(false); + if (_storageDownloadButton) + _storageDownloadButton->setVisible(false); + + serverLabelPosition = (_storagePopUpDesc ? _storagePopUpDesc->getRelY() : 0); +#endif +#ifdef USE_SDL_NET + //determine original widget's positions + int16 x, y; + uint16 w, h; + int serverButtonY, serverInfoY; + int serverRootButtonY, serverRootY, serverRootClearButtonY; + int serverPortDescY, serverPortY, serverPortClearButtonY; + if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.RunServerButton", x, y, w, h)) + warning("GlobalOptions_Cloud_Container.RunServerButton's position is undefined"); + serverButtonY = y; + if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.ServerInfoLabel", x, y, w, h)) + warning("GlobalOptions_Cloud_Container.ServerInfoLabel's position is undefined"); + serverInfoY = y; + + if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.RootPathButton", x, y, w, h)) + warning("GlobalOptions_Cloud_Container.RootPathButton's position is undefined"); + serverRootButtonY = y; + if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.RootPath", x, y, w, h)) + warning("GlobalOptions_Cloud_Container.RootPath's position is undefined"); + serverRootY = y; + if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.RootPathClearButton", x, y, w, h)) + warning("GlobalOptions_Cloud_Container.RootPathClearButton's position is undefined"); + serverRootClearButtonY = y; + + if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.ServerPortDesc", x, y, w, h)) + warning("GlobalOptions_Cloud_Container.ServerPortDesc's position is undefined"); + serverPortDescY = y; + if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.ServerPortEditText", x, y, w, h)) + warning("GlobalOptions_Cloud_Container.ServerPortEditText's position is undefined"); + serverPortY = y; + if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.ServerPortClearButton", x, y, w, h)) + warning("GlobalOptions_Cloud_Container.ServerPortClearButton's position is undefined"); + serverPortClearButtonY = y; + + bool serverIsRunning = LocalServer.isRunning(); + + if (serverLabelPosition < 0) + serverLabelPosition = serverInfoY; + if (_runServerButton) { + _runServerButton->setVisible(true); + _runServerButton->setPos(_runServerButton->getRelX(), serverLabelPosition + serverButtonY - serverInfoY); + _runServerButton->setLabel(_(serverIsRunning ? "Stop server" : "Run server")); + _runServerButton->setTooltip(_(serverIsRunning ? "Stop local webserver" : "Run local webserver")); + } + if (_serverInfoLabel) { + _serverInfoLabel->setVisible(true); + _serverInfoLabel->setPos(_serverInfoLabel->getRelX(), serverLabelPosition); + if (serverIsRunning) + _serverInfoLabel->setLabel(LocalServer.getAddress()); + else + _serverInfoLabel->setLabel(_("Not running")); + } + if (_rootPathButton) { + _rootPathButton->setVisible(true); + _rootPathButton->setPos(_rootPathButton->getRelX(), serverLabelPosition + serverRootButtonY - serverInfoY); + } + if (_rootPath) { + _rootPath->setVisible(true); + _rootPath->setPos(_rootPath->getRelX(), serverLabelPosition + serverRootY - serverInfoY); + } + if (_rootPathClearButton) { + _rootPathClearButton->setVisible(true); + _rootPathClearButton->setPos(_rootPathClearButton->getRelX(), serverLabelPosition + serverRootClearButtonY - serverInfoY); + } +#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE + if (_serverPortDesc) { + _serverPortDesc->setVisible(true); + _serverPortDesc->setPos(_serverPortDesc->getRelX(), serverLabelPosition + serverPortDescY - serverInfoY); + _serverPortDesc->setEnabled(!serverIsRunning); + } + if (_serverPort) { + _serverPort->setVisible(true); + _serverPort->setPos(_serverPort->getRelX(), serverLabelPosition + serverPortY - serverInfoY); + _serverPort->setEnabled(!serverIsRunning); + } + if (_serverPortClearButton) { + _serverPortClearButton->setVisible(true); + _serverPortClearButton->setPos(_serverPortClearButton->getRelX(), serverLabelPosition + serverPortClearButtonY - serverInfoY); + _serverPortClearButton->setEnabled(!serverIsRunning); + } +#else + if (_serverPortDesc) + _serverPortDesc->setVisible(false); + if (_serverPort) + _serverPort->setVisible(false); + if (_serverPortClearButton) + _serverPortClearButton->setVisible(false); +#endif +#else + if (_runServerButton) + _runServerButton->setVisible(false); + if (_serverInfoLabel) + _serverInfoLabel->setVisible(false); + if (_rootPathButton) + _rootPathButton->setVisible(false); + if (_rootPath) + _rootPath->setVisible(false); + if (_rootPathClearButton) + _rootPathClearButton->setVisible(false); + if (_serverPortDesc) + _serverPortDesc->setVisible(false); + if (_serverPort) + _serverPort->setVisible(false); + if (_serverPortClearButton) + _serverPortClearButton->setVisible(false); +#endif +} +#endif +#ifdef USE_LIBCURL +void GlobalOptionsDialog::storageInfoCallback(Cloud::Storage::StorageInfoResponse response) { + //we could've used response.value.email() + //but Storage already notified CloudMan + //so we just set the flag to redraw our cloud tab + _redrawCloudTab = true; +} + +void GlobalOptionsDialog::storageListDirectoryCallback(Cloud::Storage::ListDirectoryResponse response) { + Common::Array<Cloud::StorageFile> &files = response.value; + uint64 totalSize = 0; + for (uint32 i = 0; i < files.size(); ++i) + if (!files[i].isDirectory()) + totalSize += files[i].size(); + CloudMan.setStorageUsedSpace(CloudMan.getStorageIndex(), totalSize); + _redrawCloudTab = true; +} + +void GlobalOptionsDialog::storageErrorCallback(Networking::ErrorResponse response) { + debug(9, "GlobalOptionsDialog: error response (%s, %ld):", (response.failed ? "failed" : "interrupted"), response.httpResponseCode); + debug(9, "%s", response.response.c_str()); + + if (!response.interrupted) + g_system->displayMessageOnOSD(_("Request failed.\nCheck your Internet connection.")); +} +#endif + } // End of namespace GUI diff --git a/gui/options.h b/gui/options.h index 294b41794b..03dbdac492 100644 --- a/gui/options.h +++ b/gui/options.h @@ -37,9 +37,15 @@ #include "gui/fluidsynth-dialog.h" #endif +#ifdef USE_LIBCURL +#include "backends/cloud/storage.h" +#endif + namespace GUI { +class LauncherDialog; class CheckboxWidget; +class EditTextWidget; class PopUpWidget; class SliderWidget; class StaticTextWidget; @@ -200,16 +206,18 @@ protected: class GlobalOptionsDialog : public OptionsDialog { public: - GlobalOptionsDialog(); + GlobalOptionsDialog(LauncherDialog *launcher); ~GlobalOptionsDialog(); void open(); void close(); void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); + void handleTickle(); virtual void reflowLayout(); protected: + LauncherDialog *_launcher; #ifdef GUI_ENABLE_KEYSDIALOG KeysDialog *_keysDialog; #endif @@ -241,6 +249,43 @@ protected: StaticTextWidget *_updatesPopUpDesc; PopUpWidget *_updatesPopUp; #endif + +#ifdef USE_CLOUD + // + // Cloud controls + // + uint32 _selectedStorageIndex; + StaticTextWidget *_storagePopUpDesc; + PopUpWidget *_storagePopUp; + StaticTextWidget *_storageUsernameDesc; + StaticTextWidget *_storageUsername; + StaticTextWidget *_storageUsedSpaceDesc; + StaticTextWidget *_storageUsedSpace; + StaticTextWidget *_storageLastSyncDesc; + StaticTextWidget *_storageLastSync; + ButtonWidget *_storageConnectButton; + ButtonWidget *_storageRefreshButton; + ButtonWidget *_storageDownloadButton; + ButtonWidget *_runServerButton; + StaticTextWidget *_serverInfoLabel; + ButtonWidget *_rootPathButton; + StaticTextWidget *_rootPath; + ButtonWidget *_rootPathClearButton; + StaticTextWidget *_serverPortDesc; + EditTextWidget *_serverPort; + ButtonWidget *_serverPortClearButton; + bool _redrawCloudTab; +#ifdef USE_SDL_NET + bool _serverWasRunning; +#endif + + void setupCloudTab(); +#endif +#ifdef USE_LIBCURL + void storageInfoCallback(Cloud::Storage::StorageInfoResponse response); + void storageListDirectoryCallback(Cloud::Storage::ListDirectoryResponse response); + void storageErrorCallback(Networking::ErrorResponse response); +#endif }; } // End of namespace GUI diff --git a/gui/remotebrowser.cpp b/gui/remotebrowser.cpp new file mode 100644 index 0000000000..b87fc60f7e --- /dev/null +++ b/gui/remotebrowser.cpp @@ -0,0 +1,232 @@ +/* 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 "gui/remotebrowser.h" +#include "gui/widgets/list.h" + +#include "common/config-manager.h" +#include "common/system.h" +#include "common/algorithm.h" + +#include "common/translation.h" +#include <backends/networking/curl/request.h> +#include <backends/cloud/storage.h> +#include <backends/cloud/cloudmanager.h> +#include "message.h" + +namespace GUI { + +enum { + kChooseCmd = 'Chos', + kGoUpCmd = 'GoUp' +}; + +RemoteBrowserDialog::RemoteBrowserDialog(const char *title): + Dialog("Browser"), _navigationLocked(false), _updateList(false), _showError(false), + _workingRequest(nullptr), _ignoreCallback(false) { + _backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain; + + new StaticTextWidget(this, "Browser.Headline", title); + _currentPath = new StaticTextWidget(this, "Browser.Path", "DUMMY"); + + _fileList = new ListWidget(this, "Browser.List"); + _fileList->setNumberingMode(kListNumberingOff); + _fileList->setEditable(false); + + if (g_system->getOverlayWidth() > 320) + new ButtonWidget(this, "Browser.Up", _("Go up"), _("Go to previous directory level"), kGoUpCmd); + else + new ButtonWidget(this, "Browser.Up", _c("Go up", "lowres"), _("Go to previous directory level"), kGoUpCmd); + new ButtonWidget(this, "Browser.Cancel", _("Cancel"), 0, kCloseCmd); + new ButtonWidget(this, "Browser.Choose", _("Choose"), 0, kChooseCmd); +} + +RemoteBrowserDialog::~RemoteBrowserDialog() { + if (_workingRequest) { + _ignoreCallback = true; + _workingRequest->finish(); + } +} + +void RemoteBrowserDialog::open() { + Dialog::open(); + listDirectory(Cloud::StorageFile()); +} + +void RemoteBrowserDialog::close() { + Dialog::close(); + if (_workingRequest) { + _ignoreCallback = true; + _workingRequest->finish(); + _ignoreCallback = false; + } +} + +void RemoteBrowserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { + switch (cmd) { + case kChooseCmd: { + // If nothing is selected in the list widget, choose the current dir. + // Else, choose the dir that is selected. + int selection = _fileList->getSelected(); + if (selection >= 0) + _choice = _nodeContent[selection]; + else + _choice = _node; + setResult(1); + close(); + break; + } + case kGoUpCmd: + goUp(); + break; + case kListItemActivatedCmd: + case kListItemDoubleClickedCmd: + if (_nodeContent[data].isDirectory()) { + _rememberedNodeContents[_node.path()] = _nodeContent; + listDirectory(_nodeContent[data]); + } + break; + case kListSelectionChangedCmd: + // We do not allow selecting directories, + // thus we will invalidate the selection + // when the user selects a directory over here. + if (data != (uint32)-1 && !_nodeContent[data].isDirectory()) + _fileList->setSelected(-1); + break; + default: + Dialog::handleCommand(sender, cmd, data); + } +} + +void RemoteBrowserDialog::handleTickle() { + if (_updateList) { + updateListing(); + _updateList = false; + } + + if (_showError) { + _showError = false; + MessageDialog alert(_("ScummVM couldn't list the directory!")); + alert.runModal(); + } + + Dialog::handleTickle(); +} + +void RemoteBrowserDialog::updateListing() { + // Update the path display + Common::String path = _node.path(); + if (path.empty()) + path = "/"; //root + if (_navigationLocked) + path = "Loading... " + path; + _currentPath->setLabel(path); + + if (!_navigationLocked) { + // Populate the ListWidget + ListWidget::StringArray list; + ListWidget::ColorList colors; + for (Common::Array<Cloud::StorageFile>::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) { + if (i->isDirectory()) { + list.push_back(i->name() + "/"); + colors.push_back(ThemeEngine::kFontColorNormal); + } else { + list.push_back(i->name()); + colors.push_back(ThemeEngine::kFontColorAlternate); + } + } + + _fileList->setList(list, &colors); + _fileList->scrollTo(0); + } + + _fileList->setEnabled(!_navigationLocked); + + // Finally, redraw + draw(); +} + +void RemoteBrowserDialog::goUp() { + if (_rememberedNodeContents.contains(_node.path())) + _rememberedNodeContents.erase(_node.path()); + + Common::String path = _node.path(); + if (path.size() && (path.lastChar() == '/' || path.lastChar() == '\\')) + path.deleteLastChar(); + if (path.empty()) { + _rememberedNodeContents.erase(""); + } else { + for (int i = path.size() - 1; i >= 0; --i) + if (i == 0 || path[i] == '/' || path[i] == '\\') { + path.erase(i); + break; + } + } + + listDirectory(Cloud::StorageFile(path, 0, 0, true)); +} + +void RemoteBrowserDialog::listDirectory(Cloud::StorageFile node) { + if (_navigationLocked || _workingRequest) + return; + + if (_rememberedNodeContents.contains(node.path())) { + _nodeContent = _rememberedNodeContents[node.path()]; + } else { + _navigationLocked = true; + + _workingRequest = CloudMan.listDirectory( + node.path(), + new Common::Callback<RemoteBrowserDialog, Cloud::Storage::ListDirectoryResponse>(this, &RemoteBrowserDialog::directoryListedCallback), + new Common::Callback<RemoteBrowserDialog, Networking::ErrorResponse>(this, &RemoteBrowserDialog::directoryListedErrorCallback), + false + ); + } + + _backupNode = _node; + _node = node; + updateListing(); +} + +void RemoteBrowserDialog::directoryListedCallback(Cloud::Storage::ListDirectoryResponse response) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + _navigationLocked = false; + _nodeContent = response.value; + Common::sort(_nodeContent.begin(), _nodeContent.end(), FileListOrder()); + _updateList = true; +} + +void RemoteBrowserDialog::directoryListedErrorCallback(Networking::ErrorResponse error) { + _workingRequest = nullptr; + if (_ignoreCallback) + return; + + _navigationLocked = false; + _node = _backupNode; + _updateList = true; + _showError = true; +} + +} // End of namespace GUI diff --git a/gui/remotebrowser.h b/gui/remotebrowser.h new file mode 100644 index 0000000000..190d8c6895 --- /dev/null +++ b/gui/remotebrowser.h @@ -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 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. + * + */ + +#ifndef GUI_REMOTEBROWSER_DIALOG_H +#define GUI_REMOTEBROWSER_DIALOG_H + +#include "gui/dialog.h" +#include "common/fs.h" +#include <backends/cloud/storagefile.h> +#include <backends/networking/curl/request.h> +#include <backends/cloud/storage.h> + +namespace GUI { + +class ListWidget; +class StaticTextWidget; +class CheckboxWidget; +class CommandSender; + +class RemoteBrowserDialog : public Dialog { +public: + RemoteBrowserDialog(const char *title); + virtual ~RemoteBrowserDialog(); + + virtual void open(); + virtual void close(); + virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); + virtual void handleTickle(); + + const Cloud::StorageFile &getResult() { return _choice; } + +protected: + ListWidget *_fileList; + StaticTextWidget *_currentPath; + Cloud::StorageFile _node, _backupNode; + Common::Array<Cloud::StorageFile> _nodeContent; + Common::HashMap<Common::String, Common::Array<Cloud::StorageFile> > _rememberedNodeContents; + Cloud::StorageFile _choice; + bool _navigationLocked; + bool _updateList; + bool _showError; + + Networking::Request *_workingRequest; + bool _ignoreCallback; + + void updateListing(); + void goUp(); + void listDirectory(Cloud::StorageFile node); + void directoryListedCallback(Cloud::Storage::ListDirectoryResponse response); + void directoryListedErrorCallback(Networking::ErrorResponse error); + + struct FileListOrder : public Common::BinaryFunction<Cloud::StorageFile, Cloud::StorageFile, bool> { + bool operator()(const Cloud::StorageFile &x, const Cloud::StorageFile &y) const { + if (x.isDirectory() != y.isDirectory()) { + return x.isDirectory(); //x < y (directory < not directory) or x > y (not directory > directory) + } + + return x.name() < y.name(); + } + }; +}; + +} // End of namespace GUI + +#endif diff --git a/gui/saveload-dialog.cpp b/gui/saveload-dialog.cpp index 3d4adfff2b..ae3612f778 100644 --- a/gui/saveload-dialog.cpp +++ b/gui/saveload-dialog.cpp @@ -21,6 +21,13 @@ */ #include "gui/saveload-dialog.h" + +#ifdef USE_LIBCURL +#include "backends/cloud/cloudmanager.h" +#include "backends/cloud/savessyncrequest.h" +#include "backends/networking/curl/connectionmanager.h" +#endif + #include "common/translation.h" #include "common/config-manager.h" @@ -30,9 +37,68 @@ #include "gui/widgets/edittext.h" #include "graphics/scaler.h" +#include <common/savefile.h> namespace GUI { +#ifdef USE_LIBCURL + +enum { + kCancelSyncCmd = 'PDCS', + kBackgroundSyncCmd = 'PDBS' +}; + +SaveLoadCloudSyncProgressDialog::SaveLoadCloudSyncProgressDialog(bool canRunInBackground): Dialog("SaveLoadCloudSyncProgress"), _close(false) { + _label = new StaticTextWidget(this, "SaveLoadCloudSyncProgress.TitleText", "Downloading saves..."); + uint32 progress = (uint32)(100 * CloudMan.getSyncDownloadingProgress()); + _progressBar = new SliderWidget(this, "SaveLoadCloudSyncProgress.ProgressBar"); + _progressBar->setMinValue(0); + _progressBar->setMaxValue(100); + _progressBar->setValue(progress); + _progressBar->setEnabled(false); + _percentLabel = new StaticTextWidget(this, "SaveLoadCloudSyncProgress.PercentText", Common::String::format("%u %%", progress)); + new ButtonWidget(this, "SaveLoadCloudSyncProgress.Cancel", "Cancel", 0, kCancelSyncCmd, Common::ASCII_ESCAPE); // Cancel dialog + ButtonWidget *backgroundButton = new ButtonWidget(this, "SaveLoadCloudSyncProgress.Background", "Run in background", 0, kBackgroundSyncCmd, Common::ASCII_RETURN); // Confirm dialog + backgroundButton->setEnabled(canRunInBackground); + draw(); +} + +SaveLoadCloudSyncProgressDialog::~SaveLoadCloudSyncProgressDialog() { + CloudMan.setSyncTarget(nullptr); //not that dialog, at least +} + +void SaveLoadCloudSyncProgressDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { + switch(cmd) { + case kSavesSyncProgressCmd: + _percentLabel->setLabel(Common::String::format("%u%%", data)); + _progressBar->setValue(data); + _progressBar->draw(); + break; + + case kCancelSyncCmd: + setResult(kCancelSyncCmd); + close(); + break; + + case kSavesSyncEndedCmd: + case kBackgroundSyncCmd: + _close = true; + break; + } + + Dialog::handleCommand(sender, cmd, data); +} + +void SaveLoadCloudSyncProgressDialog::handleTickle() { + if (_close) { + setResult(kBackgroundSyncCmd); + close(); + } + + Dialog::handleTickle(); +} +#endif + #ifndef DISABLE_SAVELOADCHOOSER_GRID SaveLoadChooserType getRequestedSaveLoadDialog(const MetaEngine &metaEngine) { const Common::String &userConfig = ConfMan.get("gui_saveload_chooser", Common::ConfigManager::kApplicationDomain); @@ -45,9 +111,9 @@ SaveLoadChooserType getRequestedSaveLoadDialog(const MetaEngine &metaEngine) { g_gui.checkScreenChange(); if (g_gui.getWidth() >= 640 && g_gui.getHeight() >= 400 - && metaEngine.hasFeature(MetaEngine::kSavesSupportMetaInfo) - && metaEngine.hasFeature(MetaEngine::kSavesSupportThumbnail) - && userConfig.equalsIgnoreCase("grid")) { + && metaEngine.hasFeature(MetaEngine::kSavesSupportMetaInfo) + && metaEngine.hasFeature(MetaEngine::kSavesSupportThumbnail) + && userConfig.equalsIgnoreCase("grid")) { // In case we are 640x400 or higher, this dialog is not in save mode, // the user requested the grid dialog and the engines supports it we // try to set it up. @@ -66,7 +132,8 @@ enum { SaveLoadChooserDialog::SaveLoadChooserDialog(const Common::String &dialogName, const bool saveMode) : Dialog(dialogName), _metaEngine(0), _delSupport(false), _metaInfoSupport(false), - _thumbnailSupport(false), _saveDateSupport(false), _playTimeSupport(false), _saveMode(saveMode) + _thumbnailSupport(false), _saveDateSupport(false), _playTimeSupport(false), _saveMode(saveMode), + _dialogWasShown(false) #ifndef DISABLE_SAVELOADCHOOSER_GRID , _listButton(0), _gridButton(0) #endif // !DISABLE_SAVELOADCHOOSER_GRID @@ -78,7 +145,8 @@ SaveLoadChooserDialog::SaveLoadChooserDialog(const Common::String &dialogName, c SaveLoadChooserDialog::SaveLoadChooserDialog(int x, int y, int w, int h, const bool saveMode) : Dialog(x, y, w, h), _metaEngine(0), _delSupport(false), _metaInfoSupport(false), - _thumbnailSupport(false), _saveDateSupport(false), _playTimeSupport(false), _saveMode(saveMode) + _thumbnailSupport(false), _saveDateSupport(false), _playTimeSupport(false), _saveMode(saveMode), + _dialogWasShown(false) #ifndef DISABLE_SAVELOADCHOOSER_GRID , _listButton(0), _gridButton(0) #endif // !DISABLE_SAVELOADCHOOSER_GRID @@ -88,12 +156,27 @@ SaveLoadChooserDialog::SaveLoadChooserDialog(int x, int y, int w, int h, const b #endif // !DISABLE_SAVELOADCHOOSER_GRID } +SaveLoadChooserDialog::~SaveLoadChooserDialog() { +#ifdef USE_LIBCURL + CloudMan.setSyncTarget(nullptr); //not that dialog, at least +#endif +} + void SaveLoadChooserDialog::open() { Dialog::open(); // So that quitting ScummVM will not cause the dialog result to say a // saved game was selected. setResult(-1); + + _dialogWasShown = false; +} + +void SaveLoadChooserDialog::close() { +#ifdef USE_LIBCURL + CloudMan.setSyncTarget(nullptr); //not that dialog, at least +#endif + Dialog::close(); } int SaveLoadChooserDialog::run(const Common::String &target, const MetaEngine *metaEngine) { @@ -132,9 +215,53 @@ void SaveLoadChooserDialog::handleCommand(CommandSender *sender, uint32 cmd, uin } #endif // !DISABLE_SAVELOADCHOOSER_GRID +#ifdef USE_LIBCURL + if (cmd == kSavesSyncProgressCmd || cmd == kSavesSyncEndedCmd) { + //this dialog only gets these commands if the progress dialog was shown and user clicked "run in background" + return updateSaveList(); + } +#endif + return Dialog::handleCommand(sender, cmd, data); } +#ifdef USE_LIBCURL +void SaveLoadChooserDialog::runSaveSync(bool hasSavepathOverride) { + if (!CloudMan.isSyncing()) { + if (hasSavepathOverride) { + ConnMan.showCloudDisabledIcon(); + } else { + Cloud::SavesSyncRequest *request = CloudMan.syncSaves(); + if (request) + request->setTarget(this); + } + } +} +#endif + +void SaveLoadChooserDialog::handleTickle() { +#ifdef USE_LIBCURL + if (!_dialogWasShown && CloudMan.isSyncing()) { + Common::Array<Common::String> files = CloudMan.getSyncingFiles(); + if (!files.empty()) { + { + SaveLoadCloudSyncProgressDialog dialog(_metaEngine ? _metaEngine->hasFeature(MetaEngine::kSimpleSavesNames) : false); + CloudMan.setSyncTarget(&dialog); + int result = dialog.runModal(); + if (result == kCancelSyncCmd) { + CloudMan.cancelSync(); + } + } + //dialog changes syncTarget to nullptr after that } + CloudMan.setSyncTarget(this); + _dialogWasShown = true; + updateSaveList(); + } + } +#endif + Dialog::handleTickle(); +} + void SaveLoadChooserDialog::reflowLayout() { #ifndef DISABLE_SAVELOADCHOOSER_GRID addChooserButtons(); @@ -152,6 +279,46 @@ void SaveLoadChooserDialog::reflowLayout() { Dialog::reflowLayout(); } +void SaveLoadChooserDialog::updateSaveList() { +#ifdef USE_LIBCURL + Common::Array<Common::String> files = CloudMan.getSyncingFiles(); //returns empty array if not syncing + g_system->getSavefileManager()->updateSavefilesList(files); +#endif + listSaves(); +} + +void SaveLoadChooserDialog::listSaves() { + if (!_metaEngine) return; //very strange + _saveList = _metaEngine->listSaves(_target.c_str()); + +#ifdef USE_LIBCURL + //if there is Cloud support, add currently synced files as "locked" saves in the list + if (_metaEngine->hasFeature(MetaEngine::kSimpleSavesNames)) { + Common::String pattern = _target + ".###"; + Common::Array<Common::String> files = CloudMan.getSyncingFiles(); //returns empty array if not syncing + for (uint32 i = 0; i < files.size(); ++i) { + if (!files[i].matchString(pattern, true)) + continue; + + //make up some slot number + int slotNum = 0; + for (uint32 j = (files[i].size() > 3 ? files[i].size() - 3 : 0); j < files[i].size(); ++j) { //3 last chars + char c = files[i][j]; + if (c < '0' || c > '9') + continue; + slotNum = slotNum * 10 + (c - '0'); + } + + SaveStateDescriptor slot(slotNum, files[i]); + slot.setLocked(true); + _saveList.push_back(slot); + } + + Common::sort(_saveList.begin(), _saveList.end(), SaveStateDescriptorSlotComparator()); + } +#endif +} + #ifndef DISABLE_SAVELOADCHOOSER_GRID void SaveLoadChooserDialog::addChooserButtons() { if (_listButton) { @@ -353,6 +520,7 @@ void SaveLoadChooserSimple::updateSelection(bool redraw) { bool isDeletable = _delSupport; bool isWriteProtected = false; bool startEditMode = _list->isEditable(); + bool isLocked = false; // We used to support letting the themes specify the fill color with our // initial theme based GUI. But this support was dropped. @@ -362,10 +530,11 @@ void SaveLoadChooserSimple::updateSelection(bool redraw) { _playtime->setLabel(_("No playtime saved")); if (selItem >= 0 && _metaInfoSupport) { - SaveStateDescriptor desc = _metaEngine->querySaveMetaInfos(_target.c_str(), _saveList[selItem].getSaveSlot()); + SaveStateDescriptor desc = (_saveList[selItem].getLocked() ? _saveList[selItem] : _metaEngine->querySaveMetaInfos(_target.c_str(), _saveList[selItem].getSaveSlot())); isDeletable = desc.getDeletableFlag() && _delSupport; isWriteProtected = desc.getWriteProtectedFlag(); + isLocked = desc.getLocked(); // Don't allow the user to change the description of write protected games if (isWriteProtected) @@ -398,9 +567,9 @@ void SaveLoadChooserSimple::updateSelection(bool redraw) { if (_list->isEditable()) { - // Disable the save button if nothing is selected, or if the selected - // game is write protected - _chooseButton->setEnabled(selItem >= 0 && !isWriteProtected); + // Disable the save button if slot is locked, nothing is selected, + // or if the selected game is write protected + _chooseButton->setEnabled(!isLocked && selItem >= 0 && !isWriteProtected); if (startEditMode) { _list->startEditMode(); @@ -412,13 +581,13 @@ void SaveLoadChooserSimple::updateSelection(bool redraw) { } } } else { - // Disable the load button if nothing is selected, or if an empty - // list item is selected. - _chooseButton->setEnabled(selItem >= 0 && !_list->getSelectedString().empty()); + // Disable the load button if slot is locked, nothing is selected, + // or if an empty list item is selected. + _chooseButton->setEnabled(!isLocked && selItem >= 0 && !_list->getSelectedString().empty()); } // Delete will always be disabled if the engine doesn't support it. - _deleteButton->setEnabled(isDeletable && (selItem >= 0) && (!_list->getSelectedString().empty())); + _deleteButton->setEnabled(isDeletable && !isLocked && (selItem >= 0) && (!_list->getSelectedString().empty())); if (redraw) { _gfxWidget->draw(); @@ -463,7 +632,7 @@ void SaveLoadChooserSimple::close() { } void SaveLoadChooserSimple::updateSaveList() { - _saveList = _metaEngine->listSaves(_target.c_str()); + SaveLoadChooserDialog::updateSaveList(); int curSlot = 0; int saveSlot = 0; @@ -496,7 +665,7 @@ void SaveLoadChooserSimple::updateSaveList() { description = _("Untitled savestate"); colors.push_back(ThemeEngine::kFontColorAlternate); } else { - colors.push_back(ThemeEngine::kFontColorNormal); + colors.push_back((x->getLocked() ? ThemeEngine::kFontColorAlternate : ThemeEngine::kFontColorNormal)); } saveNames.push_back(description); @@ -524,6 +693,7 @@ void SaveLoadChooserSimple::updateSaveList() { } _list->setList(saveNames, &colors); + draw(); } // SaveLoadChooserGrid implementation @@ -619,10 +789,16 @@ void SaveLoadChooserGrid::handleMouseWheel(int x, int y, int direction) { } } +void SaveLoadChooserGrid::updateSaveList() { + SaveLoadChooserDialog::updateSaveList(); + updateSaves(); + draw(); +} + void SaveLoadChooserGrid::open() { SaveLoadChooserDialog::open(); - _saveList = _metaEngine->listSaves(_target.c_str()); + listSaves(); _resultString.clear(); // Load information to restore the last page the user had open. @@ -863,7 +1039,7 @@ void SaveLoadChooserGrid::updateSaves() { for (uint i = _curPage * _entriesPerPage, curNum = 0; i < _saveList.size() && curNum < _entriesPerPage; ++i, ++curNum) { const uint saveSlot = _saveList[i].getSaveSlot(); - SaveStateDescriptor desc = _metaEngine->querySaveMetaInfos(_target.c_str(), saveSlot); + SaveStateDescriptor desc = (_saveList[i].getLocked() ? _saveList[i] : _metaEngine->querySaveMetaInfos(_target.c_str(), saveSlot)); SlotButton &curButton = _buttons[curNum]; curButton.setVisible(true); const Graphics::Surface *thumbnail = desc.getThumbnail(); @@ -908,6 +1084,10 @@ void SaveLoadChooserGrid::updateSaves() { } else { curButton.button->setEnabled(true); } + + //that would make it look "disabled" if slot is locked + curButton.button->setEnabled(!desc.getLocked()); + curButton.description->setEnabled(!desc.getLocked()); } const uint numPages = (_entriesPerPage != 0 && !_saveList.empty()) ? ((_saveList.size() + _entriesPerPage - 1) / _entriesPerPage) : 1; diff --git a/gui/saveload-dialog.h b/gui/saveload-dialog.h index 31f28f6452..fb2833355f 100644 --- a/gui/saveload-dialog.h +++ b/gui/saveload-dialog.h @@ -30,6 +30,25 @@ namespace GUI { +#ifdef USE_LIBCURL +enum SaveLoadCloudSyncProgress { + kSavesSyncProgressCmd = 'SSPR', + kSavesSyncEndedCmd = 'SSEN' +}; + +class SaveLoadCloudSyncProgressDialog : public Dialog { //protected? + StaticTextWidget *_label, *_percentLabel; + SliderWidget *_progressBar; + bool _close; +public: + SaveLoadCloudSyncProgressDialog(bool canRunInBackground); + virtual ~SaveLoadCloudSyncProgressDialog(); + + virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); + virtual void handleTickle(); +}; +#endif + #define kSwitchSaveLoadDialog -2 // TODO: We might want to disable the grid based save/load chooser for more @@ -53,13 +72,21 @@ class SaveLoadChooserDialog : protected Dialog { public: SaveLoadChooserDialog(const Common::String &dialogName, const bool saveMode); SaveLoadChooserDialog(int x, int y, int w, int h, const bool saveMode); + virtual ~SaveLoadChooserDialog(); virtual void open(); + virtual void close(); virtual void reflowLayout(); virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); +#ifdef USE_LIBCURL + virtual void runSaveSync(bool hasSavepathOverride); +#endif + + virtual void handleTickle(); + #ifndef DISABLE_SAVELOADCHOOSER_GRID virtual SaveLoadChooserType getType() const = 0; #endif // !DISABLE_SAVELOADCHOOSER_GRID @@ -70,6 +97,19 @@ public: protected: virtual int runIntern() = 0; + /** Common function to refresh the list on the screen. */ + virtual void updateSaveList(); + + /** + * Common function to get saves list from MetaEngine. + * + * It also checks whether there are some locked saves + * because of saves sync and adds such saves as locked + * slots. User sees these slots, but is unable to save + * or load from these. + */ + virtual void listSaves(); + const bool _saveMode; const MetaEngine *_metaEngine; bool _delSupport; @@ -78,6 +118,8 @@ protected: bool _saveDateSupport; bool _playTimeSupport; Common::String _target; + bool _dialogWasShown; + SaveStateList _saveList; #ifndef DISABLE_SAVELOADCHOOSER_GRID ButtonWidget *_listButton; @@ -106,6 +148,8 @@ public: virtual void open(); virtual void close(); +protected: + virtual void updateSaveList(); private: virtual int runIntern(); @@ -118,10 +162,8 @@ private: StaticTextWidget *_time; StaticTextWidget *_playtime; - SaveStateList _saveList; String _resultString; - void updateSaveList(); void updateSelection(bool redraw); }; @@ -164,13 +206,13 @@ public: protected: virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); virtual void handleMouseWheel(int x, int y, int direction); + virtual void updateSaveList(); private: virtual int runIntern(); uint _columns, _lines; uint _entriesPerPage; uint _curPage; - SaveStateList _saveList; ButtonWidget *_nextButton; ButtonWidget *_prevButton; diff --git a/gui/saveload.cpp b/gui/saveload.cpp index b94d30289b..3072aa6082 100644 --- a/gui/saveload.cpp +++ b/gui/saveload.cpp @@ -87,6 +87,10 @@ int SaveLoadChooser::runModalWithPluginAndTarget(const EnginePlugin *plugin, con if (!_impl) return -1; +#ifdef USE_LIBCURL + _impl->runSaveSync(ConfMan.hasKey("savepath", target)); +#endif + // Set up the game domain as newly active domain, so // target specific savepath will be checked String oldDomain = ConfMan.getActiveDomainName(); diff --git a/gui/storagewizarddialog.cpp b/gui/storagewizarddialog.cpp new file mode 100644 index 0000000000..ad00365813 --- /dev/null +++ b/gui/storagewizarddialog.cpp @@ -0,0 +1,361 @@ +/* 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 "gui/storagewizarddialog.h" +#include "gui/gui-manager.h" +#include "gui/message.h" +#include "gui/widget.h" +#include "gui/widgets/edittext.h" +#include "gui/widgets/scrollcontainer.h" +#include "backends/cloud/cloudmanager.h" +#ifdef USE_SDL_NET +#include "backends/networking/sdl_net/localwebserver.h" +#endif +#include "backends/networking/browser/openurl.h" +#include "common/translation.h" + +namespace GUI { + +enum { + kConnectCmd = 'Cnnt', + kCodeBoxCmd = 'CdBx', + kOpenUrlCmd = 'OpUr', + kPasteCodeCmd = 'PsCd', + kStorageWizardContainerReflowCmd = 'SWCr' +}; + +StorageWizardDialog::StorageWizardDialog(uint32 storageId): + Dialog("GlobalOptions_Cloud_ConnectionWizard"), _storageId(storageId), _close(false) { +#ifdef USE_SDL_NET + _stopServerOnClose = false; +#endif + _backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain; + + ScrollContainerWidget *container = new ScrollContainerWidget(this, "GlobalOptions_Cloud_ConnectionWizard.Container", kStorageWizardContainerReflowCmd); + container->setTarget(this); + + Common::String headline = Common::String::format(_("%s Storage Connection Wizard"), CloudMan.listStorages()[_storageId].c_str()); + _headlineWidget = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.Headline", headline); + + _navigateLineWidget = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.NavigateLine", _s("Navigate to the following URL:")); + _urlLineWidget = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.URLLine", getUrl()); + + _returnLine1 = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.ReturnLine1", _s("Obtain the code from the storage, enter it")); + _returnLine2 = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.ReturnLine2", _s("in the following field and press 'Connect':")); + for (uint32 i = 0; i < CODE_FIELDS; ++i) + _codeWidget[i] = new EditTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.CodeBox" + Common::String::format("%d", i+1), "", 0, kCodeBoxCmd); + _messageWidget = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.MessageLine", ""); + + // Buttons + _cancelWidget = new ButtonWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.CancelButton", _("Cancel"), 0, kCloseCmd); + _openUrlWidget = new ButtonWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.OpenUrlButton", _("Open URL"), 0, kOpenUrlCmd); + _pasteCodeWidget = new ButtonWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.PasteCodeButton", _("Paste"), _("Pastes clipboard contents into fields"), kPasteCodeCmd); + _connectWidget = new ButtonWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.ConnectButton", _("Connect"), 0, kConnectCmd); + + // Initialy the code is empty, so disable the connect button + _connectWidget->setEnabled(false); + + if (Cloud::CloudManager::couldUseLocalServer()) { + // hide fields and even the button if local webserver is on + _returnLine1->setLabel(_s("You would be navigated to ScummVM's page")); + _returnLine2->setLabel(_s("when you'd allow it to use your storage.")); + } + + _picture = new GraphicsWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.Picture"); +#ifndef DISABLE_FANCY_THEMES + if (g_gui.theme()->supportsImages()) { + _picture->useThemeTransparency(true); + switch (_storageId) { + case Cloud::kStorageDropboxId: + _picture->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageDropboxLogo)); + break; + case Cloud::kStorageOneDriveId: + _picture->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageOneDriveLogo)); + break; + case Cloud::kStorageGoogleDriveId: + _picture->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageGoogleDriveLogo)); + break; + case Cloud::kStorageBoxId: + _picture->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageBoxLogo)); + break; + } + } +#endif + + containerWidgetsReflow(); +} + +void StorageWizardDialog::open() { + Dialog::open(); + + if (CloudMan.isWorking()) { + bool doClose = true; + + MessageDialog alert(_("The other Storage is working. Do you want to interrupt it?"), _("Yes"), _("No")); + if (alert.runModal() == GUI::kMessageOK) { + if (CloudMan.isDownloading()) + CloudMan.cancelDownload(); + if (CloudMan.isSyncing()) + CloudMan.cancelSync(); + + // I believe it still would return `true` here, but just in case + if (CloudMan.isWorking()) { + MessageDialog alert2(_("Wait until current Storage finishes up and try again.")); + alert2.runModal(); + } else { + doClose = false; + } + } + + if (doClose) { + close(); + return; + } + } + +#ifdef USE_SDL_NET + if (Cloud::CloudManager::couldUseLocalServer()) { + _stopServerOnClose = !LocalServer.isRunning(); + LocalServer.start(true); // using "minimal mode" (no "/files", "/download", etc available) + LocalServer.indexPageHandler().setTarget(this); + } +#endif +} + +void StorageWizardDialog::close() { +#ifdef USE_SDL_NET + if (Cloud::CloudManager::couldUseLocalServer()) { + if (_stopServerOnClose) + LocalServer.stopOnIdle(); + LocalServer.indexPageHandler().setTarget(nullptr); + } +#endif + Dialog::close(); +} + +void StorageWizardDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { + switch (cmd) { + case kCodeBoxCmd: { + Common::String code, message; + uint32 correctFields = 0; + for (uint32 i = 0; i < CODE_FIELDS; ++i) { + Common::String subcode = _codeWidget[i]->getEditString(); + if (subcode.size() == 0) { + ++correctFields; + continue; + } + bool correct = correctChecksum(subcode); + if (correct) { + code += subcode; + code.deleteLastChar(); + ++correctFields; + } else { + if (i == correctFields) { //first incorrect field + message += Common::String::format("#%d", i + 1); + } else { + message += Common::String::format(", #%d", i + 1); + } + } + } + + if (message.size() > 0) { + Common::String messageTemplate; + if (CODE_FIELDS - correctFields == 1) + messageTemplate = _("Field %s has a mistake in it."); + else + messageTemplate = _("Fields %s have mistakes in them."); + message = Common::String::format(messageTemplate.c_str(), message.c_str()); + } + + bool ok = false; + if (correctFields == CODE_FIELDS && code.size() > 0) { + //the last 3 chars must be an encoded crc16 + if (code.size() > 3) { + uint32 size = code.size(); + uint32 gotcrc = decodeHashchar(code[size - 3]) | (decodeHashchar(code[size - 2]) << 6) | (decodeHashchar(code[size - 1]) << 12); + code.erase(size - 3); + uint32 crc = crc16(code); + ok = (crc == gotcrc); + } + if (ok) + message = _("All OK!"); + else + message = _("Invalid code"); + } + _connectWidget->setEnabled(ok); + _messageWidget->setLabel(message); + break; + } + case kOpenUrlCmd: { + if (!Networking::Browser::openUrl(getUrl())) { + MessageDialog alert(_("Failed to open URL!\nYou should navigate there manually then.")); + alert.runModal(); + } + break; + } + case kPasteCodeCmd: { + if (g_system->hasTextInClipboard()) { + Common::String message = g_system->getTextFromClipboard(); + for (uint32 i = 0; i < CODE_FIELDS; ++i) { + if (message.empty()) break; + Common::String subcode = ""; + for (uint32 j = 0; j < message.size(); ++j) { + if (message[j] == ' ') { + message.erase(0, j+1); + break; + } + subcode += message[j]; + if (j+1 == message.size()) { + message = ""; + break; + } + } + _codeWidget[i]->setEditString(subcode); + } + handleCommand(sender, kCodeBoxCmd, data); + draw(); + } + break; + } + case kConnectCmd: { + Common::String code; + for (uint32 i = 0; i < CODE_FIELDS; ++i) { + Common::String subcode = _codeWidget[i]->getEditString(); + if (subcode.size() == 0) + continue; + code += subcode; + code.deleteLastChar(); + } + if (code.size() > 3) { + code.erase(code.size() - 3); + CloudMan.connectStorage(_storageId, code); + setResult(1); + close(); + } + break; + } +#ifdef USE_SDL_NET + case kStorageCodePassedCmd: + CloudMan.connectStorage(_storageId, LocalServer.indexPageHandler().code()); + _close = true; + break; +#endif + case kStorageWizardContainerReflowCmd: + containerWidgetsReflow(); + break; + default: + Dialog::handleCommand(sender, cmd, data); + } +} + +void StorageWizardDialog::handleTickle() { + if (_close) { + setResult(1); + close(); + } + + Dialog::handleTickle(); +} + +void StorageWizardDialog::containerWidgetsReflow() { + // contents + if (_headlineWidget) _headlineWidget->setVisible(true); + if (_navigateLineWidget) _navigateLineWidget->setVisible(true); + if (_urlLineWidget) _urlLineWidget->setVisible(true); + if (_returnLine1) _returnLine1->setVisible(true); + if (_returnLine2) _returnLine2->setVisible(true); + + bool showFields = (!Cloud::CloudManager::couldUseLocalServer()); + for (uint32 i = 0; i < CODE_FIELDS; ++i) + _codeWidget[i]->setVisible(showFields); + _messageWidget->setVisible(showFields); + + // left column / first bottom row + if (_picture) { + _picture->setVisible(g_system->getOverlayWidth() > 320); + } + if (_openUrlWidget) _openUrlWidget->setVisible(true); + if (_pasteCodeWidget) { + bool visible = showFields && g_system->hasFeature(OSystem::kFeatureClipboardSupport); + _pasteCodeWidget->setVisible(visible); + } + + // bottom row + if (_cancelWidget) _cancelWidget->setVisible(true); + if (_connectWidget) { + _connectWidget->setVisible(showFields); + } +} + +Common::String StorageWizardDialog::getUrl() const { + Common::String url = "https://www.scummvm.org/c/"; + switch (_storageId) { + case Cloud::kStorageDropboxId: + url += "db"; + break; + case Cloud::kStorageOneDriveId: + url += "od"; + break; + case Cloud::kStorageGoogleDriveId: + url += "gd"; + break; + case Cloud::kStorageBoxId: + url += "bx"; + break; + } + + if (Cloud::CloudManager::couldUseLocalServer()) + url += "s"; + + return url; +} + +int StorageWizardDialog::decodeHashchar(char c) { + const char HASHCHARS[65] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ?!"; + for (uint32 i = 0; i < 64; ++i) + if (c == HASHCHARS[i]) + return i; + return -1; +} + +bool StorageWizardDialog::correctChecksum(Common::String s) { + if (s.size() == 0) + return false; //no last char + int providedChecksum = decodeHashchar(s.lastChar()); + int calculatedChecksum = 0x2A; //any initial value would do, but it must equal to the one used on the page where these checksums were generated + for (uint32 i = 0; i < s.size()-1; ++i) { + calculatedChecksum = calculatedChecksum ^ s[i]; + } + return providedChecksum == (calculatedChecksum % 64); +} + +uint32 StorageWizardDialog::crc16(Common::String s) { //"CRC16_CCITT_FALSE" + uint32 crc = 0xFFFF, x; + for (uint32 i = 0; i < s.size(); ++i) { + x = ((crc >> 8) ^ s[i]) & 0xFF; + x ^= x >> 4; + crc = ((crc << 8) ^ (x << 12) ^ (x << 5) ^ x) & 0xFFFF; + } + return crc; +} + +} // End of namespace GUI diff --git a/gui/storagewizarddialog.h b/gui/storagewizarddialog.h new file mode 100644 index 0000000000..61bc8ac873 --- /dev/null +++ b/gui/storagewizarddialog.h @@ -0,0 +1,107 @@ +/* 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. + * + */ + +#ifndef GUI_STORAGEWIZARDDIALOG_H +#define GUI_STORAGEWIZARDDIALOG_H + +#include "gui/dialog.h" +#include "common/str.h" + +namespace GUI { + +class CommandSender; +class EditTextWidget; +class StaticTextWidget; +class ButtonWidget; +class GraphicsWidget; + +#ifdef USE_SDL_NET +enum StorageWizardDialogCommands { + kStorageCodePassedCmd = 'SWDC' +}; +#endif + +class StorageWizardDialog : public Dialog { + static const uint32 CODE_FIELDS = 8; + uint32 _storageId; + + StaticTextWidget *_headlineWidget; + StaticTextWidget *_navigateLineWidget; + StaticTextWidget *_urlLineWidget; + StaticTextWidget *_returnLine1; + StaticTextWidget *_returnLine2; + EditTextWidget *_codeWidget[CODE_FIELDS]; + StaticTextWidget *_messageWidget; + + GraphicsWidget *_picture; + ButtonWidget *_openUrlWidget; + ButtonWidget *_pasteCodeWidget; + + ButtonWidget *_cancelWidget; + ButtonWidget *_connectWidget; + + bool _close; +#ifdef USE_SDL_NET + bool _stopServerOnClose; +#endif + + /** Hides/shows widgets for Container to work with them correctly. */ + void containerWidgetsReflow(); + + /** Return short scummvm.org URL for user to navigate to. */ + Common::String getUrl() const; + + /** + * Return the value corresponding to the given character. + * + * There is a value corresponding to each of 64 selected + * printable characters (0-9, A-Z, a-z, ? and !). + * + * When given another character, -1 is returned. + */ + int decodeHashchar(char c); + + /** + * Return whether checksum is correct. + * + * The last character of the string is treated as + * the checksum of all the others (decoded with + * decodeHashchar()). + * + * Checksum = (c[0] ^ c[1] ^ ...) % 64 + */ + bool correctChecksum(Common::String s); + + /** The "CRC16_CCITT_FALSE" CRC-16 algorithm. */ + uint32 crc16(Common::String s); +public: + StorageWizardDialog(uint32 storageId); + + virtual void open(); + virtual void close(); + virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data); + virtual void handleTickle(); +}; + +} // End of namespace GUI + +#endif diff --git a/gui/themes/default.inc b/gui/themes/default.inc index c0ea733de8..d46a603830 100644 --- a/gui/themes/default.inc +++ b/gui/themes/default.inc @@ -1092,6 +1092,192 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "/>" "</layout>" "</dialog>" +"<dialog name='GlobalOptions_Cloud' overlays='Dialog.GlobalOptions.TabWidget'>" +"<layout type='vertical' padding='0,0,0,0'>" +"<widget name='Container'/>" +"</layout>" +"</dialog>" +"<dialog name='GlobalOptions_Cloud_Container' overlays='GlobalOptions_Cloud.Container'>" +"<layout type='vertical' padding='16,16,16,16' spacing='8'>" +"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>" +"<widget name='StoragePopupDesc' " +"type='OptionsLabel' " +"/>" +"<widget name='StoragePopup' " +"type='PopUp' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>" +"<widget name='StorageUsernameDesc' " +"type='OptionsLabel' " +"/>" +"<widget name='StorageUsernameLabel' " +"height='Globals.Line.Height' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>" +"<widget name='StorageUsedSpaceDesc' " +"type='OptionsLabel' " +"/>" +"<widget name='StorageUsedSpaceLabel' " +"height='Globals.Line.Height' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>" +"<widget name='StorageLastSyncDesc' " +"type='OptionsLabel' " +"/>" +"<widget name='StorageLastSyncLabel' " +"height='Globals.Line.Height' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>" +"<widget name='ConnectButton' " +"type='Button' " +"/>" +"<widget name='RefreshButton' " +"type='Button' " +"/>" +"<widget name='DownloadButton' " +"type='Button' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>" +"<widget name='RunServerButton' " +"type='Button' " +"/>" +"<widget name='ServerInfoLabel' " +"height='Globals.Line.Height' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>" +"<widget name='ServerPortDesc' " +"type='OptionsLabel' " +"/>" +"<widget name='ServerPortEditText' " +"width='70' " +"height='Globals.Line.Height' " +"/>" +"<widget name='ServerPortClearButton' " +"height='Globals.Line.Height' " +"width='Globals.Line.Height' " +"/>" +"</layout>" +"</layout>" +"</dialog>" +"<dialog name='GlobalOptions_Cloud_DownloadDialog' overlays='Dialog.GlobalOptions'>" +"<layout type='vertical' padding='16,16,16,8' spacing='8'>" +"<widget name='RemoteDirectory' " +"height='Globals.Line.Height' " +"/>" +"<widget name='LocalDirectory' " +"height='Globals.Line.Height' " +"/>" +"<widget name='ProgressBar' " +"height='Globals.Button.Height' " +"/>" +"<space size='1'/>" +"<widget name='PercentText' " +"height='Globals.Line.Height' " +"textalign='center' " +"/>" +"<widget name='DownloadSize' " +"height='Globals.Line.Height' " +"/>" +"<widget name='DownloadSpeed' " +"height='Globals.Line.Height' " +"/>" +"<space/>" +"<layout type='horizontal' padding='0,0,0,0' spacing='10'>" +"<widget name='MainButton' " +"type='Button' " +"/>" +"<space/>" +"<widget name='CloseButton' " +"type='Button' " +"/>" +"</layout>" +"</layout>" +"</dialog>" +"<dialog name='GlobalOptions_Cloud_ConnectionWizard' overlays='Dialog.GlobalOptions'>" +"<layout type='vertical' padding='16,16,16,16' spacing='8'>" +"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>" +"<widget name='Picture' " +"type='OptionsLabel' " +"/>" +"<layout type='vertical' padding='0,0,0,0' spacing='6'>" +"<widget name='Headline' " +"height='Globals.Line.Height' " +"/>" +"<space size='4' />" +"<widget name='NavigateLine' " +"height='Globals.Line.Height' " +"/>" +"<widget name='URLLine' " +"height='Globals.Line.Height' " +"/>" +"<space size='4' />" +"<widget name='ReturnLine1' " +"height='Globals.Line.Height' " +"/>" +"<widget name='ReturnLine2' " +"height='Globals.Line.Height' " +"/>" +"<layout type='horizontal' padding='0,0,0,0' spacing='4' center='true'>" +"<widget name='CodeBox1' " +"width='70' " +"height='Globals.Line.Height' " +"/>" +"<widget name='CodeBox2' " +"width='70' " +"height='Globals.Line.Height' " +"/>" +"<widget name='CodeBox3' " +"width='70' " +"height='Globals.Line.Height' " +"/>" +"<widget name='CodeBox4' " +"width='70' " +"height='Globals.Line.Height' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='4' center='true'>" +"<widget name='CodeBox5' " +"width='70' " +"height='Globals.Line.Height' " +"/>" +"<widget name='CodeBox6' " +"width='70' " +"height='Globals.Line.Height' " +"/>" +"<widget name='CodeBox7' " +"width='70' " +"height='Globals.Line.Height' " +"/>" +"<widget name='CodeBox8' " +"width='70' " +"height='Globals.Line.Height' " +"/>" +"</layout>" +"<widget name='MessageLine' " +"height='Globals.Line.Height' " +"/>" +"<space size='6' />" +"</layout>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>" +"<widget name='CancelButton' " +"type='Button' " +"/>" +"<widget name='OpenUrlButton' " +"type='Button' " +"/>" +"<widget name='ConnectButton' " +"type='Button' " +"/>" +"</layout>" +"</layout>" +"</dialog>" "<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>" "<layout type='vertical' padding='8,8,8,8' center='true'>" "<widget name='Action' " @@ -1592,6 +1778,37 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "</layout>" "</layout>" "</dialog>" +"<dialog name='SaveLoadCloudSyncProgress' overlays='screen_center' inset='8' shading='dim'>" +"<layout type='vertical' padding='8,8,8,8' center='true'>" +"<widget name='TitleText' " +"width='496' " +"height='Globals.Line.Height' " +"textalign='center' " +"/>" +"<space size='1'/>" +"<widget name='ProgressBar' " +"width='496' " +"height='Globals.Button.Height' " +"/>" +"<space size='1'/>" +"<widget name='PercentText' " +"width='496' " +"height='Globals.Line.Height' " +"textalign='center' " +"/>" +"<space size='1'/>" +"<layout type='horizontal' padding='0,0,0,0' center='true' spacing='10'>" +"<widget name='Cancel' " +"width='150' " +"height='Globals.Button.Height' " +"/>" +"<widget name='Background' " +"width='150' " +"height='Globals.Button.Height' " +"/>" +"</layout>" +"</layout>" +"</dialog>" "<dialog name='SavenameDialog' overlays='screen_center'>" "<layout type='vertical' padding='8,8,8,8'>" "<widget name='DescriptionText' " @@ -2394,6 +2611,197 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "/>" "</layout>" "</dialog>" +"<dialog name='GlobalOptions_Cloud' overlays='Dialog.GlobalOptions.TabWidget'>" +"<layout type='vertical' padding='0,0,0,0'>" +"<widget name='Container'/>" +"</layout>" +"</dialog>" +"<dialog name='GlobalOptions_Cloud_Container' overlays='GlobalOptions_Cloud.Container'>" +"<layout type='vertical' padding='16,16,16,16' spacing='8'>" +"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>" +"<widget name='StoragePopupDesc' " +"width='80' " +"height='Globals.Line.Height' " +"textalign='right' " +"/>" +"<widget name='StoragePopup' " +"type='PopUp' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>" +"<widget name='StorageUsernameDesc' " +"width='80' " +"height='Globals.Line.Height' " +"textalign='right' " +"/>" +"<widget name='StorageUsernameLabel' " +"height='Globals.Line.Height' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>" +"<widget name='StorageUsedSpaceDesc' " +"width='80' " +"height='Globals.Line.Height' " +"textalign='right' " +"/>" +"<widget name='StorageUsedSpaceLabel' " +"height='Globals.Line.Height' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>" +"<widget name='StorageLastSyncDesc' " +"width='80' " +"height='Globals.Line.Height' " +"textalign='right' " +"/>" +"<widget name='StorageLastSyncLabel' " +"height='Globals.Line.Height' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>" +"<widget name='ConnectButton' " +"type='Button' " +"/>" +"<widget name='RefreshButton' " +"type='Button' " +"/>" +"<widget name='DownloadButton' " +"type='Button' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>" +"<widget name='RunServerButton' " +"type='Button' " +"/>" +"<widget name='ServerInfoLabel' " +"height='Globals.Line.Height' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>" +"<widget name='ServerPortDesc' " +"width='80' " +"height='Globals.Line.Height' " +"textalign='right' " +"/>" +"<widget name='ServerPortEditText' " +"width='60' " +"height='16' " +"/>" +"<widget name='ServerPortClearButton' " +"height='Globals.Line.Height' " +"width='Globals.Line.Height' " +"/>" +"</layout>" +"</layout>" +"</dialog>" +"<dialog name='GlobalOptions_Cloud_DownloadDialog' overlays='Dialog.GlobalOptions'>" +"<layout type='vertical' padding='8,8,8,4' spacing='8'>" +"<widget name='RemoteDirectory' " +"height='Globals.Line.Height' " +"/>" +"<widget name='LocalDirectory' " +"height='Globals.Line.Height' " +"/>" +"<widget name='ProgressBar' " +"height='Globals.Button.Height' " +"/>" +"<space size='1'/>" +"<widget name='PercentText' " +"height='Globals.Line.Height' " +"textalign='center' " +"/>" +"<widget name='DownloadSize' " +"height='Globals.Line.Height' " +"/>" +"<widget name='DownloadSpeed' " +"height='Globals.Line.Height' " +"/>" +"<space/>" +"<layout type='horizontal' padding='0,0,0,0' spacing='6'>" +"<widget name='MainButton' " +"type='Button' " +"/>" +"<space/>" +"<widget name='CloseButton' " +"type='Button' " +"/>" +"</layout>" +"</layout>" +"</dialog>" +"<dialog name='GlobalOptions_Cloud_ConnectionWizard' overlays='Dialog.GlobalOptions'>" +"<layout type='vertical' padding='16,16,16,16' spacing='8'>" +"<layout type='vertical' padding='0,0,0,0' spacing='4'>" +"<widget name='Headline' " +"height='Globals.Line.Height' " +"/>" +"<space size='2' />" +"<widget name='NavigateLine' " +"height='Globals.Line.Height' " +"/>" +"<widget name='URLLine' " +"height='Globals.Line.Height' " +"/>" +"<space size='2' />" +"<widget name='ReturnLine1' " +"height='Globals.Line.Height' " +"/>" +"<widget name='ReturnLine2' " +"height='Globals.Line.Height' " +"/>" +"<layout type='horizontal' padding='0,0,0,0' spacing='4' center='true'>" +"<widget name='CodeBox1' " +"width='60' " +"height='16' " +"/>" +"<widget name='CodeBox2' " +"width='60' " +"height='16' " +"/>" +"<widget name='CodeBox3' " +"width='60' " +"height='16' " +"/>" +"<widget name='CodeBox4' " +"width='60' " +"height='16' " +"/>" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='4' center='true'>" +"<widget name='CodeBox5' " +"width='60' " +"height='16' " +"/>" +"<widget name='CodeBox6' " +"width='60' " +"height='16' " +"/>" +"<widget name='CodeBox7' " +"width='60' " +"height='16' " +"/>" +"<widget name='CodeBox8' " +"width='60' " +"height='16' " +"/>" +"</layout>" +"<widget name='MessageLine' " +"height='Globals.Line.Height' " +"/>" +"<space size='4' />" +"</layout>" +"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>" +"<widget name='CancelButton' " +"type='Button' " +"/>" +"<widget name='OpenUrlButton' " +"type='Button' " +"/>" +"<widget name='ConnectButton' " +"type='Button' " +"/>" +"</layout>" +"</layout>" +"</dialog>" "<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>" "<layout type='vertical' padding='8,8,8,8' center='true'>" "<widget name='Action' " @@ -2888,6 +3296,37 @@ const char *defaultXML1 = "<?xml version = '1.0'?>" "</layout>" "</layout>" "</dialog>" +"<dialog name='SaveLoadCloudSyncProgress' overlays='screen_center' inset='8' shading='dim'>" +"<layout type='vertical' padding='8,8,8,8' center='true'>" +"<widget name='TitleText' " +"width='240' " +"height='Globals.Line.Height' " +"textalign='center' " +"/>" +"<space size='1'/>" +"<widget name='ProgressBar' " +"width='240' " +"height='Globals.Button.Height' " +"/>" +"<space size='1'/>" +"<widget name='PercentText' " +"width='240' " +"height='Globals.Line.Height' " +"textalign='center' " +"/>" +"<space size='1'/>" +"<layout type='horizontal' padding='0,0,0,0' center='true' spacing='10'>" +"<widget name='Cancel' " +"width='100' " +"height='Globals.Button.Height' " +"/>" +"<widget name='Background' " +"width='100' " +"height='Globals.Button.Height' " +"/>" +"</layout>" +"</layout>" +"</dialog>" "<dialog name='SavenameDialog' overlays='screen_center'>" "<layout type='vertical' padding='8,8,8,8'>" "<widget name='DescriptionText' " diff --git a/gui/themes/scummclassic.zip b/gui/themes/scummclassic.zip Binary files differindex 561f2a5dd3..e574fe5039 100644 --- a/gui/themes/scummclassic.zip +++ b/gui/themes/scummclassic.zip diff --git a/gui/themes/scummclassic/classic_gfx.stx b/gui/themes/scummclassic/classic_gfx.stx index 49ecea2e11..1311345621 100644 --- a/gui/themes/scummclassic/classic_gfx.stx +++ b/gui/themes/scummclassic/classic_gfx.stx @@ -192,7 +192,7 @@ orientation = 'top' /> </drawdata> - + <drawdata id = 'scrollbar_button_idle' cache = 'false' resolution = 'y<400'> <drawstep func = 'bevelsq' bevel = '2' @@ -226,7 +226,7 @@ orientation = 'top' /> </drawdata> - + <drawdata id = 'scrollbar_button_hover' cache = 'false' resolution = 'y<400'> <drawstep func = 'bevelsq' bevel = '2' @@ -314,7 +314,7 @@ bevel = '2' fill = 'none' /> - + <drawstep func = 'triangle' fg_color = 'green' fill = 'foreground' @@ -325,7 +325,7 @@ padding = '0, 0, 7, 0' orientation = 'bottom' /> - + <drawstep func = 'triangle' fg_color = 'green' fill = 'foreground' @@ -336,20 +336,20 @@ padding = '0, 0, 7, 0' orientation = 'top' /> - + <text font = 'text_default' text_color = 'color_normal' vertical_align = 'center' horizontal_align = 'left' /> </drawdata> - + <drawdata id = 'popup_idle' cache = 'false' resolution = 'y<400'> <drawstep func = 'bevelsq' bevel = '2' fill = 'none' /> - + <drawstep func = 'triangle' fg_color = 'green' fill = 'foreground' @@ -360,7 +360,7 @@ padding = '0, 0, 3, 0' orientation = 'bottom' /> - + <drawstep func = 'triangle' fg_color = 'green' fill = 'foreground' @@ -371,7 +371,7 @@ padding = '0, 0, 3, 0' orientation = 'top' /> - + <text font = 'text_default' text_color = 'color_normal' vertical_align = 'center' @@ -394,7 +394,7 @@ padding = '0, 0, 7, 0' orientation = 'bottom' /> - + <drawstep func = 'triangle' fg_color = 'green' fill = 'foreground' @@ -411,13 +411,13 @@ horizontal_align = 'left' /> </drawdata> - + <drawdata id = 'popup_disabled' cache = 'false' resolution = 'y<400'> <drawstep func = 'bevelsq' bevel = '2' fill = 'none' /> - + <drawstep func = 'triangle' fg_color = 'green' fill = 'foreground' @@ -428,7 +428,7 @@ padding = '0, 0, 3, 0' orientation = 'bottom' /> - + <drawstep func = 'triangle' fg_color = 'green' fill = 'foreground' @@ -439,7 +439,7 @@ padding = '0, 0, 3, 0' orientation = 'top' /> - + <text font = 'text_default' text_color = 'color_normal' vertical_align = 'center' @@ -462,7 +462,7 @@ padding = '0, 0, 7, 0' orientation = 'bottom' /> - + <drawstep func = 'triangle' fg_color = 'green' fill = 'foreground' @@ -479,13 +479,13 @@ horizontal_align = 'left' /> </drawdata> - + <drawdata id = 'popup_hover' cache = 'false' resolution = 'y<400'> <drawstep func = 'bevelsq' bevel = '2' fill = 'none' /> - + <drawstep func = 'triangle' fg_color = 'green' fill = 'foreground' @@ -496,7 +496,7 @@ padding = '0, 0, 3, 0' orientation = 'bottom' /> - + <drawstep func = 'triangle' fg_color = 'green' fill = 'foreground' @@ -507,7 +507,7 @@ padding = '0, 0, 3, 0' orientation = 'top' /> - + <text font = 'text_default' text_color = 'color_normal' vertical_align = 'center' @@ -552,7 +552,7 @@ fill = 'foreground' fg_color = 'green' /> - </drawdata> + </drawdata> <drawdata id = 'button_idle' cache = 'false'> <text font = 'text_button' diff --git a/gui/themes/scummclassic/classic_layout.stx b/gui/themes/scummclassic/classic_layout.stx index 5172326859..c67631ad22 100644 --- a/gui/themes/scummclassic/classic_layout.stx +++ b/gui/themes/scummclassic/classic_layout.stx @@ -524,6 +524,221 @@ </layout> </dialog> + <dialog name = 'GlobalOptions_Cloud' overlays = 'Dialog.GlobalOptions.TabWidget'> + <layout type = 'vertical' padding = '0, 0, 0, 0'> + <widget name = 'Container'/> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_Container' overlays = 'GlobalOptions_Cloud.Container'> + <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'StoragePopupDesc' + type = 'OptionsLabel' + /> + <widget name = 'StoragePopup' + type = 'PopUp' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'StorageUsernameDesc' + type = 'OptionsLabel' + /> + <widget name = 'StorageUsernameLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'StorageUsedSpaceDesc' + type = 'OptionsLabel' + /> + <widget name = 'StorageUsedSpaceLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'StorageLastSyncDesc' + type = 'OptionsLabel' + /> + <widget name = 'StorageLastSyncLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'ConnectButton' + type = 'Button' + /> + <widget name = 'RefreshButton' + type = 'Button' + /> + <widget name = 'DownloadButton' + type = 'Button' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'RunServerButton' + type = 'Button' + /> + <widget name = 'ServerInfoLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'RootPathButton' + type = 'Button' + /> + <widget name = 'RootPath' + height = 'Globals.Line.Height' + /> + <widget name = 'RootPathClearButton' + height = 'Globals.Line.Height' + width = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'ServerPortDesc' + type = 'OptionsLabel' + /> + <widget name = 'ServerPortEditText' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'ServerPortClearButton' + height = 'Globals.Line.Height' + width = 'Globals.Line.Height' + /> + </layout> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_DownloadDialog' overlays = 'Dialog.GlobalOptions'> + <layout type = 'vertical' padding = '16, 16, 16, 8' spacing = '8'> + <widget name = 'RemoteDirectory' + height = 'Globals.Line.Height' + /> + <widget name = 'LocalDirectory' + height = 'Globals.Line.Height' + /> + <widget name = 'ProgressBar' + height = 'Globals.Button.Height' + /> + <space size = '1'/> + <widget name = 'PercentText' + height = 'Globals.Line.Height' + textalign = 'center' + /> + <widget name = 'DownloadSize' + height = 'Globals.Line.Height' + /> + <widget name = 'DownloadSpeed' + height = 'Globals.Line.Height' + /> + <space/> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10'> + <widget name = 'MainButton' + type = 'Button' + /> + <space/> + <widget name = 'CloseButton' + type = 'Button' + /> + </layout> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'> + <layout type = 'vertical' padding = '0, 0, 0, 0'> + <widget name = 'Container'/> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'> + <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '0'> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'> + <widget name = 'Picture' + width = '109' + height = '109' + /> + <widget name = 'OpenUrlButton' + type = 'Button' + /> + <widget name = 'PasteCodeButton' + type = 'Button' + /> + </layout> + <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'> + <widget name = 'Headline' + height = 'Globals.Line.Height' + /> + <space size = '4' /> + <widget name = 'NavigateLine' + height = 'Globals.Line.Height' + /> + <widget name = 'URLLine' + height = 'Globals.Line.Height' + /> + <space size = '4' /> + <widget name = 'ReturnLine1' + height = 'Globals.Line.Height' + /> + <widget name = 'ReturnLine2' + height = 'Globals.Line.Height' + /> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'> + <widget name = 'CodeBox1' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'CodeBox2' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'CodeBox3' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'CodeBox4' + width = '70' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'> + <widget name = 'CodeBox5' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'CodeBox6' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'CodeBox7' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'CodeBox8' + width = '70' + height = 'Globals.Line.Height' + /> + </layout> + <widget name = 'MessageLine' + height = 'Globals.Line.Height' + /> + <space size = '6' /> + </layout> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'CancelButton' + type = 'Button' + /> + <space /> + <widget name = 'ConnectButton' + type = 'Button' + /> + </layout> + </layout> + </dialog> + <dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'> <layout type='vertical' padding='8,8,8,8' center='true'> <widget name='Action' @@ -1042,6 +1257,38 @@ </layout> </dialog> + <dialog name = 'SaveLoadCloudSyncProgress' overlays = 'screen_center' inset = '8' shading = 'dim'> + <layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'> + <widget name = 'TitleText' + width = '496' + height = 'Globals.Line.Height' + textalign = 'center' + /> + <space size = '1'/> + <widget name = 'ProgressBar' + width = '496' + height = 'Globals.Button.Height' + /> + <space size = '1'/> + <widget name = 'PercentText' + width = '496' + height = 'Globals.Line.Height' + textalign = 'center' + /> + <space size = '1'/> + <layout type = 'horizontal' padding = '0, 0, 0, 0' center = 'true' spacing = '10'> + <widget name = 'Cancel' + width = '150' + height = 'Globals.Button.Height' + /> + <widget name = 'Background' + width = '150' + height = 'Globals.Button.Height' + /> + </layout> + </layout> + </dialog> + <dialog name = 'SavenameDialog' overlays = 'screen_center'> <layout type = 'vertical' padding = '8, 8, 8, 8'> <widget name = 'DescriptionText' diff --git a/gui/themes/scummclassic/classic_layout_lowres.stx b/gui/themes/scummclassic/classic_layout_lowres.stx index 0013b91ee2..e19256695c 100644 --- a/gui/themes/scummclassic/classic_layout_lowres.stx +++ b/gui/themes/scummclassic/classic_layout_lowres.stx @@ -529,6 +529,226 @@ </layout> </dialog> + <dialog name = 'GlobalOptions_Cloud' overlays = 'Dialog.GlobalOptions.TabWidget'> + <layout type = 'vertical' padding = '0, 0, 0, 0'> + <widget name = 'Container'/> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_Container' overlays = 'GlobalOptions_Cloud.Container'> + <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'StoragePopupDesc' + width = '80' + height = 'Globals.Line.Height' + textalign = 'right' + /> + <widget name = 'StoragePopup' + type = 'PopUp' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'StorageUsernameDesc' + width = '80' + height = 'Globals.Line.Height' + textalign = 'right' + /> + <widget name = 'StorageUsernameLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'StorageUsedSpaceDesc' + width = '80' + height = 'Globals.Line.Height' + textalign = 'right' + /> + <widget name = 'StorageUsedSpaceLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'StorageLastSyncDesc' + width = '80' + height = 'Globals.Line.Height' + textalign = 'right' + /> + <widget name = 'StorageLastSyncLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'ConnectButton' + type = 'Button' + /> + <widget name = 'RefreshButton' + type = 'Button' + /> + <widget name = 'DownloadButton' + type = 'Button' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'RunServerButton' + type = 'Button' + /> + <widget name = 'ServerInfoLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '16' center = 'true'> + <widget name = 'RootPathButton' + type = 'Button' + /> + <widget name = 'RootPathPath' + height = 'Globals.Line.Height' + /> + <widget name = 'RootPathClearButton' + height = 'Globals.Line.Height' + width = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'ServerPortDesc' + width = '80' + height = 'Globals.Line.Height' + textalign = 'right' + /> + <widget name = 'ServerPortEditText' + width = '60' + height = '16' + /> + <widget name = 'ServerPortClearButton' + height = 'Globals.Line.Height' + width = 'Globals.Line.Height' + /> + </layout> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_DownloadDialog' overlays = 'Dialog.GlobalOptions'> + <layout type = 'vertical' padding = '8, 8, 8, 4' spacing = '8'> + <widget name = 'RemoteDirectory' + height = 'Globals.Line.Height' + /> + <widget name = 'LocalDirectory' + height = 'Globals.Line.Height' + /> + <widget name = 'ProgressBar' + height = 'Globals.Button.Height' + /> + <space size = '1'/> + <widget name = 'PercentText' + height = 'Globals.Line.Height' + textalign = 'center' + /> + <widget name = 'DownloadSize' + height = 'Globals.Line.Height' + /> + <widget name = 'DownloadSpeed' + height = 'Globals.Line.Height' + /> + <space/> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6'> + <widget name = 'MainButton' + type = 'Button' + /> + <space/> + <widget name = 'CloseButton' + type = 'Button' + /> + </layout> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'> + <layout type = 'vertical' padding = '0, 0, 0, 0'> + <widget name = 'Container'/> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'> + <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'> + <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '4'> + <widget name = 'Headline' + height = 'Globals.Line.Height' + /> + <space size = '2' /> + <widget name = 'NavigateLine' + height = 'Globals.Line.Height' + /> + <widget name = 'URLLine' + height = 'Globals.Line.Height' + /> + <space size = '2' /> + <widget name = 'ReturnLine1' + height = 'Globals.Line.Height' + /> + <widget name = 'ReturnLine2' + height = 'Globals.Line.Height' + /> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'> + <widget name = 'CodeBox1' + width = '60' + height = '16' + /> + <widget name = 'CodeBox2' + width = '60' + height = '16' + /> + <widget name = 'CodeBox3' + width = '60' + height = '16' + /> + <widget name = 'CodeBox4' + width = '60' + height = '16' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'> + <widget name = 'CodeBox5' + width = '60' + height = '16' + /> + <widget name = 'CodeBox6' + width = '60' + height = '16' + /> + <widget name = 'CodeBox7' + width = '60' + height = '16' + /> + <widget name = 'CodeBox8' + width = '60' + height = '16' + /> + </layout> + <widget name = 'MessageLine' + height = 'Globals.Line.Height' + /> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'> + <widget name = 'OpenUrlButton' + type = 'Button' + /> + <widget name = 'PasteCodeButton' + type = 'Button' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'> + <widget name = 'CancelButton' + type = 'Button' + /> + <space /> + <widget name = 'ConnectButton' + type = 'Button' + /> + </layout> + <space size = '6' /> + <widget name = 'Picture' width = '1' height = '1' /> + </layout> + </layout> + </dialog> + <dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'> <layout type='vertical' padding='8,8,8,8' center='true'> <widget name='Action' @@ -1040,6 +1260,38 @@ </layout> </dialog> + <dialog name = 'SaveLoadCloudSyncProgress' overlays = 'screen_center' inset = '8' shading = 'dim'> + <layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'> + <widget name = 'TitleText' + width = '240' + height = 'Globals.Line.Height' + textalign = 'center' + /> + <space size = '1'/> + <widget name = 'ProgressBar' + width = '240' + height = 'Globals.Button.Height' + /> + <space size = '1'/> + <widget name = 'PercentText' + width = '240' + height = 'Globals.Line.Height' + textalign = 'center' + /> + <space size = '1'/> + <layout type = 'horizontal' padding = '0, 0, 0, 0' center = 'true' spacing = '10'> + <widget name = 'Cancel' + width = '100' + height = 'Globals.Button.Height' + /> + <widget name = 'Background' + width = '100' + height = 'Globals.Button.Height' + /> + </layout> + </layout> + </dialog> + <dialog name = 'SavenameDialog' overlays = 'screen_center'> <layout type = 'vertical' padding = '8, 8, 8, 8'> <widget name = 'DescriptionText' diff --git a/gui/themes/scummmodern.zip b/gui/themes/scummmodern.zip Binary files differindex d80c481ffc..78b62d4286 100644 --- a/gui/themes/scummmodern.zip +++ b/gui/themes/scummmodern.zip diff --git a/gui/themes/scummmodern/box.bmp b/gui/themes/scummmodern/box.bmp Binary files differnew file mode 100644 index 0000000000..21fb650f02 --- /dev/null +++ b/gui/themes/scummmodern/box.bmp diff --git a/gui/themes/scummmodern/dropbox.bmp b/gui/themes/scummmodern/dropbox.bmp Binary files differnew file mode 100644 index 0000000000..4ed95f0009 --- /dev/null +++ b/gui/themes/scummmodern/dropbox.bmp diff --git a/gui/themes/scummmodern/googledrive.bmp b/gui/themes/scummmodern/googledrive.bmp Binary files differnew file mode 100644 index 0000000000..30377a5f74 --- /dev/null +++ b/gui/themes/scummmodern/googledrive.bmp diff --git a/gui/themes/scummmodern/onedrive.bmp b/gui/themes/scummmodern/onedrive.bmp Binary files differnew file mode 100644 index 0000000000..cd26d71d3c --- /dev/null +++ b/gui/themes/scummmodern/onedrive.bmp diff --git a/gui/themes/scummmodern/scummmodern_gfx.stx b/gui/themes/scummmodern/scummmodern_gfx.stx index 3a1ec5a5f0..e3d2152e4b 100644 --- a/gui/themes/scummmodern/scummmodern_gfx.stx +++ b/gui/themes/scummmodern/scummmodern_gfx.stx @@ -119,6 +119,10 @@ <bitmap filename = 'editbtn_small.bmp'/> <bitmap filename = 'switchbtn_small.bmp'/> <bitmap filename = 'fastreplay_small.bmp'/> + <bitmap filename = 'dropbox.bmp'/> + <bitmap filename = 'onedrive.bmp'/> + <bitmap filename = 'googledrive.bmp'/> + <bitmap filename = 'box.bmp'/> </bitmaps> <fonts> @@ -181,7 +185,7 @@ <text_color id = 'color_button_hover' color = 'white' /> - + <text_color id = 'color_alternative_inverted' color = 'white' /> @@ -327,7 +331,7 @@ orientation = 'top' /> </drawdata> - + <drawdata id = 'scrollbar_button_hover' cache = 'false' resolution = 'y>399'> <drawstep func = 'roundedsq' radius = '10' @@ -350,7 +354,7 @@ orientation = 'top' /> </drawdata> - + <drawdata id = 'scrollbar_button_hover' cache = 'false' resolution = 'y<400'> <drawstep func = 'roundedsq' radius = '10' @@ -476,7 +480,7 @@ bg_color = 'xtrabrightred' shadow = '1' /> - + <drawstep func = 'triangle' bg_color = 'shadowcolor' fill = 'background' @@ -487,7 +491,7 @@ padding = '0, 0, 6, 0' orientation = 'bottom' /> - + <drawstep func = 'triangle' bg_color = 'shadowcolor' fill = 'background' @@ -498,14 +502,14 @@ padding = '0, 0, 6, 0' orientation = 'top' /> - + <text font = 'text_default' text_color = 'color_normal' vertical_align = 'center' horizontal_align = 'left' /> </drawdata> - + <drawdata id = 'popup_idle' cache = 'false' resolution ='y<400'> <drawstep func = 'roundedsq' radius = '5' @@ -515,7 +519,7 @@ bg_color = 'xtrabrightred' shadow = '1' /> - + <drawstep func = 'triangle' bg_color = 'shadowcolor' fill = 'background' @@ -526,7 +530,7 @@ padding = '0, 0, 3, 0' orientation = 'bottom' /> - + <drawstep func = 'triangle' bg_color = 'shadowcolor' fill = 'background' @@ -537,7 +541,7 @@ padding = '0, 0, 3, 0' orientation = 'top' /> - + <text font = 'text_default' text_color = 'color_normal' vertical_align = 'center' @@ -566,7 +570,7 @@ padding = '0, 0, 6, 0' orientation = 'bottom' /> - + <drawstep func = 'triangle' bg_color = 'shadowcolor' fill = 'background' @@ -577,14 +581,14 @@ padding = '0, 0, 6, 0' orientation = 'top' /> - + <text font = 'text_default' text_color = 'color_normal_hover' vertical_align = 'center' horizontal_align = 'left' /> </drawdata> - + <drawdata id = 'popup_disabled' cache = 'false' resolution = 'y<400'> <drawstep func = 'roundedsq' radius = '5' @@ -594,7 +598,7 @@ bg_color = 'xtrabrightred' shadow = '2' /> - + <drawstep func = 'triangle' bg_color = 'shadowcolor' fill = 'background' @@ -605,7 +609,7 @@ padding = '0, 0, 3, 0' orientation = 'bottom' /> - + <drawstep func = 'triangle' bg_color = 'shadowcolor' fill = 'background' @@ -616,7 +620,7 @@ padding = '0, 0, 3, 0' orientation = 'top' /> - + <text font = 'text_default' text_color = 'color_normal' vertical_align = 'center' @@ -645,7 +649,7 @@ padding = '0, 0, 6, 0' orientation = 'bottom' /> - + <drawstep func = 'triangle' bg_color = 'shadowcolor' fill = 'background' @@ -656,14 +660,14 @@ padding = '0, 0, 6, 0' orientation = 'top' /> - + <text font = 'text_default' text_color = 'color_normal_hover' vertical_align = 'center' horizontal_align = 'left' /> </drawdata> - + <drawdata id = 'popup_hover' cache = 'false' resolution = 'y<400'> <drawstep func = 'roundedsq' radius = '5' @@ -673,7 +677,7 @@ bg_color = 'xtrabrightred' shadow = '1' /> - + <drawstep func = 'triangle' bg_color = 'shadowcolor' fill = 'background' @@ -684,7 +688,7 @@ padding = '0, 0, 3, 0' orientation = 'bottom' /> - + <drawstep func = 'triangle' bg_color = 'shadowcolor' fill = 'background' @@ -695,7 +699,7 @@ padding = '0, 0, 3, 0' orientation = 'top' /> - + <text font = 'text_default' text_color = 'color_normal' vertical_align = 'center' @@ -777,7 +781,7 @@ bevel = '1' bevel_color = 'black' /> - </drawdata> + </drawdata> <!-- Idle button --> <drawdata id = 'button_idle' cache = 'false'> diff --git a/gui/themes/scummmodern/scummmodern_layout.stx b/gui/themes/scummmodern/scummmodern_layout.stx index 026fa7bc64..bb182c9dbb 100644 --- a/gui/themes/scummmodern/scummmodern_layout.stx +++ b/gui/themes/scummmodern/scummmodern_layout.stx @@ -538,6 +538,221 @@ </layout> </dialog> + <dialog name = 'GlobalOptions_Cloud' overlays = 'Dialog.GlobalOptions.TabWidget'> + <layout type = 'vertical' padding = '0, 0, 0, 0'> + <widget name = 'Container'/> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_Container' overlays = 'GlobalOptions_Cloud.Container'> + <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'StoragePopupDesc' + type = 'OptionsLabel' + /> + <widget name = 'StoragePopup' + type = 'PopUp' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'StorageUsernameDesc' + type = 'OptionsLabel' + /> + <widget name = 'StorageUsernameLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'StorageUsedSpaceDesc' + type = 'OptionsLabel' + /> + <widget name = 'StorageUsedSpaceLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'StorageLastSyncDesc' + type = 'OptionsLabel' + /> + <widget name = 'StorageLastSyncLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'ConnectButton' + type = 'Button' + /> + <widget name = 'RefreshButton' + type = 'Button' + /> + <widget name = 'DownloadButton' + type = 'Button' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'RunServerButton' + type = 'Button' + /> + <widget name = 'ServerInfoLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'RootPathButton' + type = 'Button' + /> + <widget name = 'RootPath' + height = 'Globals.Line.Height' + /> + <widget name = 'RootPathClearButton' + height = 'Globals.Line.Height' + width = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'ServerPortDesc' + type = 'OptionsLabel' + /> + <widget name = 'ServerPortEditText' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'ServerPortClearButton' + height = 'Globals.Line.Height' + width = 'Globals.Line.Height' + /> + </layout> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_DownloadDialog' overlays = 'Dialog.GlobalOptions'> + <layout type = 'vertical' padding = '16, 16, 16, 8' spacing = '8'> + <widget name = 'RemoteDirectory' + height = 'Globals.Line.Height' + /> + <widget name = 'LocalDirectory' + height = 'Globals.Line.Height' + /> + <widget name = 'ProgressBar' + height = 'Globals.Button.Height' + /> + <space size = '1'/> + <widget name = 'PercentText' + height = 'Globals.Line.Height' + textalign = 'center' + /> + <widget name = 'DownloadSize' + height = 'Globals.Line.Height' + /> + <widget name = 'DownloadSpeed' + height = 'Globals.Line.Height' + /> + <space/> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10'> + <widget name = 'MainButton' + type = 'Button' + /> + <space/> + <widget name = 'CloseButton' + type = 'Button' + /> + </layout> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'> + <layout type = 'vertical' padding = '0, 0, 0, 0'> + <widget name = 'Container'/> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'> + <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '0'> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'> + <widget name = 'Picture' + width = '109' + height = '109' + /> + <widget name = 'OpenUrlButton' + type = 'Button' + /> + <widget name = 'PasteCodeButton' + type = 'Button' + /> + </layout> + <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'> + <widget name = 'Headline' + height = 'Globals.Line.Height' + /> + <space size = '4' /> + <widget name = 'NavigateLine' + height = 'Globals.Line.Height' + /> + <widget name = 'URLLine' + height = 'Globals.Line.Height' + /> + <space size = '4' /> + <widget name = 'ReturnLine1' + height = 'Globals.Line.Height' + /> + <widget name = 'ReturnLine2' + height = 'Globals.Line.Height' + /> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'> + <widget name = 'CodeBox1' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'CodeBox2' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'CodeBox3' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'CodeBox4' + width = '70' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'> + <widget name = 'CodeBox5' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'CodeBox6' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'CodeBox7' + width = '70' + height = 'Globals.Line.Height' + /> + <widget name = 'CodeBox8' + width = '70' + height = 'Globals.Line.Height' + /> + </layout> + <widget name = 'MessageLine' + height = 'Globals.Line.Height' + /> + <space size = '6' /> + </layout> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'> + <widget name = 'CancelButton' + type = 'Button' + /> + <space /> + <widget name = 'ConnectButton' + type = 'Button' + /> + </layout> + </layout> + </dialog> + <dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'> <layout type='vertical' padding='8,8,8,8' center='true'> <widget name='Action' @@ -1056,6 +1271,38 @@ </layout> </dialog> + <dialog name = 'SaveLoadCloudSyncProgress' overlays = 'screen_center' inset = '8' shading = 'dim'> + <layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'> + <widget name = 'TitleText' + width = '496' + height = 'Globals.Line.Height' + textalign = 'center' + /> + <space size = '1'/> + <widget name = 'ProgressBar' + width = '496' + height = 'Globals.Button.Height' + /> + <space size = '1'/> + <widget name = 'PercentText' + width = '496' + height = 'Globals.Line.Height' + textalign = 'center' + /> + <space size = '1'/> + <layout type = 'horizontal' padding = '0, 0, 0, 0' center = 'true' spacing = '10'> + <widget name = 'Cancel' + width = '150' + height = 'Globals.Button.Height' + /> + <widget name = 'Background' + width = '150' + height = 'Globals.Button.Height' + /> + </layout> + </layout> + </dialog> + <dialog name = 'SavenameDialog' overlays = 'screen_center'> <layout type = 'vertical' padding = '8, 8, 8, 8'> <widget name = 'DescriptionText' diff --git a/gui/themes/scummmodern/scummmodern_layout_lowres.stx b/gui/themes/scummmodern/scummmodern_layout_lowres.stx index 169e61a9bb..3416fbeb5e 100644 --- a/gui/themes/scummmodern/scummmodern_layout_lowres.stx +++ b/gui/themes/scummmodern/scummmodern_layout_lowres.stx @@ -527,6 +527,226 @@ </layout> </dialog> + <dialog name = 'GlobalOptions_Cloud' overlays = 'Dialog.GlobalOptions.TabWidget'> + <layout type = 'vertical' padding = '0, 0, 0, 0'> + <widget name = 'Container'/> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_Container' overlays = 'GlobalOptions_Cloud.Container'> + <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'StoragePopupDesc' + width = '80' + height = 'Globals.Line.Height' + textalign = 'right' + /> + <widget name = 'StoragePopup' + type = 'PopUp' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'StorageUsernameDesc' + width = '80' + height = 'Globals.Line.Height' + textalign = 'right' + /> + <widget name = 'StorageUsernameLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'StorageUsedSpaceDesc' + width = '80' + height = 'Globals.Line.Height' + textalign = 'right' + /> + <widget name = 'StorageUsedSpaceLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'StorageLastSyncDesc' + width = '80' + height = 'Globals.Line.Height' + textalign = 'right' + /> + <widget name = 'StorageLastSyncLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'ConnectButton' + type = 'Button' + /> + <widget name = 'RefreshButton' + type = 'Button' + /> + <widget name = 'DownloadButton' + type = 'Button' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'RunServerButton' + type = 'Button' + /> + <widget name = 'ServerInfoLabel' + height = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '16' center = 'true'> + <widget name = 'RootPathButton' + type = 'Button' + /> + <widget name = 'RootPathPath' + height = 'Globals.Line.Height' + /> + <widget name = 'RootPathClearButton' + height = 'Globals.Line.Height' + width = 'Globals.Line.Height' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'> + <widget name = 'ServerPortDesc' + width = '80' + height = 'Globals.Line.Height' + textalign = 'right' + /> + <widget name = 'ServerPortEditText' + width = '60' + height = '16' + /> + <widget name = 'ServerPortClearButton' + height = 'Globals.Line.Height' + width = 'Globals.Line.Height' + /> + </layout> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_DownloadDialog' overlays = 'Dialog.GlobalOptions'> + <layout type = 'vertical' padding = '8, 8, 8, 4' spacing = '8'> + <widget name = 'RemoteDirectory' + height = 'Globals.Line.Height' + /> + <widget name = 'LocalDirectory' + height = 'Globals.Line.Height' + /> + <widget name = 'ProgressBar' + height = 'Globals.Button.Height' + /> + <space size = '1'/> + <widget name = 'PercentText' + height = 'Globals.Line.Height' + textalign = 'center' + /> + <widget name = 'DownloadSize' + height = 'Globals.Line.Height' + /> + <widget name = 'DownloadSpeed' + height = 'Globals.Line.Height' + /> + <space/> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6'> + <widget name = 'MainButton' + type = 'Button' + /> + <space/> + <widget name = 'CloseButton' + type = 'Button' + /> + </layout> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'> + <layout type = 'vertical' padding = '0, 0, 0, 0'> + <widget name = 'Container'/> + </layout> + </dialog> + + <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'> + <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'> + <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '4'> + <widget name = 'Headline' + height = 'Globals.Line.Height' + /> + <space size = '2' /> + <widget name = 'NavigateLine' + height = 'Globals.Line.Height' + /> + <widget name = 'URLLine' + height = 'Globals.Line.Height' + /> + <space size = '2' /> + <widget name = 'ReturnLine1' + height = 'Globals.Line.Height' + /> + <widget name = 'ReturnLine2' + height = 'Globals.Line.Height' + /> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'> + <widget name = 'CodeBox1' + width = '60' + height = '16' + /> + <widget name = 'CodeBox2' + width = '60' + height = '16' + /> + <widget name = 'CodeBox3' + width = '60' + height = '16' + /> + <widget name = 'CodeBox4' + width = '60' + height = '16' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'> + <widget name = 'CodeBox5' + width = '60' + height = '16' + /> + <widget name = 'CodeBox6' + width = '60' + height = '16' + /> + <widget name = 'CodeBox7' + width = '60' + height = '16' + /> + <widget name = 'CodeBox8' + width = '60' + height = '16' + /> + </layout> + <widget name = 'MessageLine' + height = 'Globals.Line.Height' + /> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'> + <widget name = 'OpenUrlButton' + type = 'Button' + /> + <widget name = 'PasteCodeButton' + type = 'Button' + /> + </layout> + <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'> + <widget name = 'CancelButton' + type = 'Button' + /> + <space /> + <widget name = 'ConnectButton' + type = 'Button' + /> + </layout> + <space size = '6' /> + <widget name = 'Picture' width = '1' height = '1' /> + </layout> + </layout> + </dialog> + <dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'> <layout type='vertical' padding='8,8,8,8' center='true'> <widget name='Action' @@ -1038,6 +1258,38 @@ </layout> </dialog> + <dialog name = 'SaveLoadCloudSyncProgress' overlays = 'screen_center' inset = '8' shading = 'dim'> + <layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'> + <widget name = 'TitleText' + width = '240' + height = 'Globals.Line.Height' + textalign = 'center' + /> + <space size = '1'/> + <widget name = 'ProgressBar' + width = '240' + height = 'Globals.Button.Height' + /> + <space size = '1'/> + <widget name = 'PercentText' + width = '240' + height = 'Globals.Line.Height' + textalign = 'center' + /> + <space size = '1'/> + <layout type = 'horizontal' padding = '0, 0, 0, 0' center = 'true' spacing = '10'> + <widget name = 'Cancel' + width = '100' + height = 'Globals.Button.Height' + /> + <widget name = 'Background' + width = '100' + height = 'Globals.Button.Height' + /> + </layout> + </layout> + </dialog> + <dialog name = 'SavenameDialog' overlays = 'screen_center'> <layout type = 'vertical' padding = '8, 8, 8, 8'> <widget name = 'DescriptionText' diff --git a/gui/themes/scummtheme.py b/gui/themes/scummtheme.py index d5fa4dfca7..365787275b 100755 --- a/gui/themes/scummtheme.py +++ b/gui/themes/scummtheme.py @@ -5,7 +5,7 @@ import re import os import zipfile -THEME_FILE_EXTENSIONS = ('.stx', '.bmp', '.fcc', '.ttf') +THEME_FILE_EXTENSIONS = ('.stx', '.bmp', '.fcc', '.ttf', '.png') def buildTheme(themeName): if not os.path.isdir(themeName) or not os.path.isfile(os.path.join(themeName, "THEMERC")): diff --git a/gui/widget.cpp b/gui/widget.cpp index f2a29c3100..f6e6d09a8a 100644 --- a/gui/widget.cpp +++ b/gui/widget.cpp @@ -398,25 +398,28 @@ void ButtonWidget::setUnpressedState() { PicButtonWidget::PicButtonWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip, uint32 cmd, uint8 hotkey) : ButtonWidget(boss, x, y, w, h, "", tooltip, cmd, hotkey), - _gfx(), _alpha(256), _transparency(false) { + _alpha(255), _transparency(false), _showButton(true) { setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG); _type = kButtonWidget; + _mode = ThemeEngine::kAutoScaleNone; } PicButtonWidget::PicButtonWidget(GuiObject *boss, const Common::String &name, const char *tooltip, uint32 cmd, uint8 hotkey) : ButtonWidget(boss, name, "", tooltip, cmd, hotkey), - _gfx(), _alpha(256), _transparency(false) { + _alpha(255), _transparency(false), _showButton(true), _isAlpha(false) { setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG); _type = kButtonWidget; + _mode = ThemeEngine::kAutoScaleNone; } PicButtonWidget::~PicButtonWidget() { - _gfx.free(); + for (int i = 0; i < kPicButtonStateMax + 1; i++) + _gfx[i].free(); } -void PicButtonWidget::setGfx(const Graphics::Surface *gfx) { - _gfx.free(); +void PicButtonWidget::setGfx(const Graphics::Surface *gfx, int statenum) { + _gfx[statenum].free(); if (!gfx || !gfx->getPixels()) return; @@ -432,10 +435,27 @@ void PicButtonWidget::setGfx(const Graphics::Surface *gfx) { return; } - _gfx.copyFrom(*gfx); + _gfx[statenum].copyFrom(*gfx); +} + +void PicButtonWidget::setAGfx(const Graphics::TransparentSurface *gfx, int statenum, ThemeEngine::AutoScaleMode mode) { + _agfx[statenum].free(); + + if (!gfx || !gfx->getPixels()) + return; + + if (gfx->format.bytesPerPixel == 1) { + warning("PicButtonWidget::setGfx got paletted surface passed"); + return; + } + + _agfx[statenum].copyFrom(*gfx); + + _isAlpha = true; + _mode = mode; } -void PicButtonWidget::setGfx(int w, int h, int r, int g, int b) { +void PicButtonWidget::setGfx(int w, int h, int r, int g, int b, int statenum) { if (w == -1) w = _w; if (h == -1) @@ -443,26 +463,69 @@ void PicButtonWidget::setGfx(int w, int h, int r, int g, int b) { const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat(); - _gfx.free(); - _gfx.create(w, h, requiredFormat); - _gfx.fillRect(Common::Rect(0, 0, w, h), _gfx.format.RGBToColor(r, g, b)); + _gfx[statenum].free(); + _gfx[statenum].create(w, h, requiredFormat); + _gfx[statenum].fillRect(Common::Rect(0, 0, w, h), _gfx[statenum].format.RGBToColor(r, g, b)); } void PicButtonWidget::drawWidget() { - g_gui.theme()->drawButtonClip(Common::Rect(_x, _y, _x + _w, _y + _h), getBossClipRect(), "", _state, getFlags()); + if (_showButton) + g_gui.theme()->drawButtonClip(Common::Rect(_x, _y, _x + _w, _y + _h), getBossClipRect(), "", _state, getFlags()); + + if (!_isAlpha) { + Graphics::Surface *gfx; + + if (_state == ThemeEngine::kStateHighlight) + gfx = &_gfx[kPicButtonHighlight]; + else if (_state == ThemeEngine::kStateDisabled) + gfx = &_gfx[kPicButtonStateDisabled]; + else if (_state == ThemeEngine::kStatePressed) + gfx = &_gfx[kPicButtonStatePressed]; + else + gfx = &_gfx[kPicButtonStateEnabled]; - if (_gfx.getPixels()) { + if (!gfx->getPixels()) + gfx = &_gfx[kPicButtonStateEnabled]; + + if (gfx->getPixels()) { // Check whether the set up surface needs to be converted to the GUI // color format. - const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat(); - if (_gfx.format != requiredFormat) { - _gfx.convertToInPlace(requiredFormat); + const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat(); + if (gfx->format != requiredFormat) { + gfx->convertToInPlace(requiredFormat); + } + + const int x = _x + (_w - gfx->w) / 2; + const int y = _y + (_h - gfx->h) / 2; + + g_gui.theme()->drawSurfaceClip(Common::Rect(x, y, x + gfx->w, y + gfx->h), getBossClipRect(), *gfx, _state, _alpha, _transparency); } + } else { + Graphics::TransparentSurface *gfx; + + if (_state == ThemeEngine::kStateHighlight) + gfx = &_agfx[kPicButtonHighlight]; + else if (_state == ThemeEngine::kStateDisabled) + gfx = &_agfx[kPicButtonStateDisabled]; + else if (_state == ThemeEngine::kStatePressed) + gfx = &_agfx[kPicButtonStatePressed]; + else + gfx = &_agfx[kPicButtonStateEnabled]; - const int x = _x + (_w - _gfx.w) / 2; - const int y = _y + (_h - _gfx.h) / 2; + if (!gfx->getPixels()) + gfx = &_agfx[kPicButtonStateEnabled]; - g_gui.theme()->drawSurfaceClip(Common::Rect(x, y, x + _gfx.w, y + _gfx.h), getBossClipRect(), _gfx, _state, _alpha, _transparency); + if (gfx->getPixels()) { + if (_mode == GUI::ThemeEngine::kAutoScaleNone) { + const int x = _x + (_w - gfx->w) / 2; + const int y = _y + (_h - gfx->h) / 2; + + g_gui.theme()->drawASurface(Common::Rect(x, y, x + gfx->w, y + gfx->h), *gfx, _mode, _alpha); + + } else { + g_gui.theme()->drawASurface(Common::Rect(_x, _y, _x + _w, _y + _h), *gfx, _mode, _alpha); + } + } } } @@ -652,13 +715,13 @@ int SliderWidget::posToValue(int pos) { #pragma mark - GraphicsWidget::GraphicsWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip) - : Widget(boss, x, y, w, h, tooltip), _gfx(), _alpha(256), _transparency(false) { + : Widget(boss, x, y, w, h, tooltip), _gfx(), _alpha(255), _transparency(false) { setFlags(WIDGET_ENABLED | WIDGET_CLEARBG); _type = kGraphicsWidget; } GraphicsWidget::GraphicsWidget(GuiObject *boss, const Common::String &name, const char *tooltip) - : Widget(boss, name, tooltip), _gfx(), _alpha(256), _transparency(false) { + : Widget(boss, name, tooltip), _gfx(), _alpha(255), _transparency(false) { setFlags(WIDGET_ENABLED | WIDGET_CLEARBG); _type = kGraphicsWidget; } @@ -686,6 +749,26 @@ void GraphicsWidget::setGfx(const Graphics::Surface *gfx) { _gfx.copyFrom(*gfx); } +void GraphicsWidget::setAGfx(const Graphics::TransparentSurface *gfx, ThemeEngine::AutoScaleMode mode) { + _agfx.free(); + + if (!gfx || !gfx->getPixels()) + return; + + if (gfx->format.bytesPerPixel == 1) { + warning("GraphicsWidget::setGfx got paletted surface passed"); + return; + } + + if ((gfx->w > _w || gfx->h > _h) && mode == ThemeEngine::kAutoScaleNone) { + warning("GraphicsWidget has size %dx%d, but a surface with %dx%d is to be set", _w, _h, gfx->w, gfx->h); + return; + } + + _agfx.copyFrom(*gfx); + _mode = mode; +} + void GraphicsWidget::setGfx(int w, int h, int r, int g, int b) { if (w == -1) w = _w; @@ -712,6 +795,23 @@ void GraphicsWidget::drawWidget() { const int y = _y + (_h - _gfx.h) / 2; g_gui.theme()->drawSurfaceClip(Common::Rect(x, y, x + _gfx.w, y + _gfx.h), getBossClipRect(), _gfx, _state, _alpha, _transparency); + } else if (_agfx.getPixels()) { + // Check whether the set up surface needs to be converted to the GUI + // color format. + const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat(); + if (_agfx.format != requiredFormat) { + _agfx.convertToInPlace(requiredFormat); + } + + if (_mode == GUI::ThemeEngine::kAutoScaleNone) { + const int x = _x + (_w - _agfx.w) / 2; + const int y = _y + (_h - _agfx.h) / 2; + + g_gui.theme()->drawASurface(Common::Rect(x, y, x + _agfx.w, y + _agfx.h), _agfx, _mode, _alpha); + + } else { + g_gui.theme()->drawASurface(Common::Rect(_x, _y, _x + _w, _y + _h), _agfx, _mode, _alpha); + } } } diff --git a/gui/widget.h b/gui/widget.h index 0f4b300233..e9343f264c 100644 --- a/gui/widget.h +++ b/gui/widget.h @@ -80,6 +80,15 @@ enum { kPressedButtonTime = 200 }; +enum { + kPicButtonStateEnabled = 0, + kPicButtonHighlight = 1, + kPicButtonStateDisabled = 2, + kPicButtonStatePressed = 3, + + kPicButtonStateMax = 3 +}; + /* Widget */ class Widget : public GuiObject { friend class Dialog; @@ -221,18 +230,24 @@ public: PicButtonWidget(GuiObject *boss, const Common::String &name, const char *tooltip = 0, uint32 cmd = 0, uint8 hotkey = 0); ~PicButtonWidget(); - void setGfx(const Graphics::Surface *gfx); - void setGfx(int w, int h, int r, int g, int b); + void setGfx(const Graphics::Surface *gfx, int statenum = kPicButtonStateEnabled); + void setAGfx(const Graphics::TransparentSurface *gfx, int statenum = kPicButtonStateEnabled, ThemeEngine::AutoScaleMode mode = ThemeEngine::kAutoScaleNone); + void setGfx(int w, int h, int r, int g, int b, int statenum = kPicButtonStateEnabled); void useAlpha(int alpha) { _alpha = alpha; } void useThemeTransparency(bool enable) { _transparency = enable; } + void setButtonDisplay(bool enable) {_showButton = enable; } protected: void drawWidget(); - Graphics::Surface _gfx; + Graphics::Surface _gfx[kPicButtonStateMax + 1]; + Graphics::TransparentSurface _agfx[kPicButtonStateMax + 1]; int _alpha; bool _transparency; + bool _showButton; + bool _isAlpha; + ThemeEngine::AutoScaleMode _mode; }; /* CheckboxWidget */ @@ -351,6 +366,7 @@ public: void setGfx(const Graphics::Surface *gfx); void setGfx(int w, int h, int r, int g, int b); + void setAGfx(const Graphics::TransparentSurface *gfx, ThemeEngine::AutoScaleMode mode = ThemeEngine::kAutoScaleNone); void useAlpha(int alpha) { _alpha = alpha; } void useThemeTransparency(bool enable) { _transparency = enable; } @@ -359,8 +375,10 @@ protected: void drawWidget(); Graphics::Surface _gfx; + Graphics::TransparentSurface _agfx; int _alpha; bool _transparency; + ThemeEngine::AutoScaleMode _mode; }; /* ContainerWidget */ diff --git a/gui/widgets/editable.cpp b/gui/widgets/editable.cpp index 4f7e584c14..02defe9a56 100644 --- a/gui/widgets/editable.cpp +++ b/gui/widgets/editable.cpp @@ -185,6 +185,21 @@ bool EditableWidget::handleKeyDown(Common::KeyState state) { forcecaret = true; break; + case Common::KEYCODE_v: + if (g_system->hasFeature(OSystem::kFeatureClipboardSupport) && state.flags & Common::KBD_CTRL) { + if (g_system->hasTextInClipboard()) { + String text = g_system->getTextFromClipboard(); + for (uint32 i = 0; i < text.size(); ++i) { + if (tryInsertChar(text[i], _caretPos)) + ++_caretPos; + } + dirty = true; + } + } else { + defaultKeyDownHandler(state, dirty, forcecaret, handled); + } + break; + #ifdef MACOSX // Let ctrl-a / ctrl-e move the caret to the start / end of the line. // diff --git a/gui/widgets/scrollcontainer.cpp b/gui/widgets/scrollcontainer.cpp index 1b38478c11..9a7792730d 100644 --- a/gui/widgets/scrollcontainer.cpp +++ b/gui/widgets/scrollcontainer.cpp @@ -28,13 +28,13 @@ namespace GUI { -ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h) - : Widget(boss, x, y, w, h) { +ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h, uint32 reflowCmd) + : Widget(boss, x, y, w, h), CommandSender(nullptr), _reflowCmd(reflowCmd) { init(); } -ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, const Common::String &name) - : Widget(boss, name) { +ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, const Common::String &name, uint32 reflowCmd) + : Widget(boss, name), CommandSender(nullptr), _reflowCmd(reflowCmd) { init(); } @@ -52,14 +52,14 @@ void ScrollContainerWidget::init() { void ScrollContainerWidget::recalc() { int scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0); _limitH = _h; - + //calculate virtual height const int spacing = g_gui.xmlEval()->getVar("Global.Font.Height", 16); //on the bottom int h = 0; int min = spacing, max = 0; Widget *ptr = _firstWidget; while (ptr) { - if (ptr != _verticalScroll) { + if (ptr != _verticalScroll && ptr->isVisible()) { int y = ptr->getAbsY() - getChildY(); min = MIN(min, y - spacing); max = MAX(max, y + ptr->getHeight() + spacing); @@ -68,6 +68,8 @@ void ScrollContainerWidget::recalc() { } h = max - min; + if (h <= _limitH) _scrolledY = 0; + _verticalScroll->_numEntries = h; _verticalScroll->_currentPos = _scrolledY; _verticalScroll->_entriesPerPage = _limitH; @@ -115,7 +117,10 @@ void ScrollContainerWidget::reflowLayout() { ptr->reflowLayout(); ptr = ptr->next(); } - + + //hide and move widgets, if needed + sendCommand(_reflowCmd, 0); + //recalculate height recalc(); @@ -124,7 +129,7 @@ void ScrollContainerWidget::reflowLayout() { while (ptr) { int y = ptr->getAbsY() - getChildY(); int h = ptr->getHeight(); - bool visible = true; + bool visible = ptr->isVisible(); if (y + h - _scrolledY < 0) visible = false; if (y - _scrolledY > _limitH) visible = false; ptr->setVisible(visible); @@ -132,6 +137,7 @@ void ScrollContainerWidget::reflowLayout() { } _verticalScroll->setVisible(_verticalScroll->_numEntries > _limitH); //show when there is something to scroll + _verticalScroll->recalc(); } void ScrollContainerWidget::drawWidget() { diff --git a/gui/widgets/scrollcontainer.h b/gui/widgets/scrollcontainer.h index 692c7e3507..c2d47370ee 100644 --- a/gui/widgets/scrollcontainer.h +++ b/gui/widgets/scrollcontainer.h @@ -29,16 +29,17 @@ namespace GUI { -class ScrollContainerWidget: public Widget { +class ScrollContainerWidget: public Widget, public CommandSender { ScrollBarWidget *_verticalScroll; int16 _scrolledX, _scrolledY; uint16 _limitH; + uint32 _reflowCmd; void recalc(); public: - ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h); - ScrollContainerWidget(GuiObject *boss, const Common::String &name); + ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h, uint32 reflowCmd = 0); + ScrollContainerWidget(GuiObject *boss, const Common::String &name, uint32 reflowCmd = 0); ~ScrollContainerWidget(); void init(); diff --git a/image/codecs/msrle.cpp b/image/codecs/msrle.cpp index 89fe869a9e..bb1125e0af 100644 --- a/image/codecs/msrle.cpp +++ b/image/codecs/msrle.cpp @@ -101,7 +101,10 @@ void MSRLEDecoder::decode8(Common::SeekableReadStream &stream) { // Copy data if (output + value > output_end) { - stream.skip(value); + if (stream.pos() + value >= stream.size()) + break; + else + stream.skip(value); continue; } @@ -461,7 +461,7 @@ win32dist: $(EXECUTABLE) cp $(srcdir)/doc/de/LIESMICH $(WIN32PATH)/doc/de/LIESMICH.txt cp $(srcdir)/doc/se/LasMig $(WIN32PATH)/doc/se/LasMig.txt cp /usr/local/README-SDL.txt $(WIN32PATH) - cp /usr/local/bin/SDL.dll $(WIN32PATH) + cp /usr/local/bin/SDL2.dll $(WIN32PATH) cp $(srcdir)/dists/win32/graphics/left.bmp $(WIN32PATH)/graphics cp $(srcdir)/dists/win32/graphics/scummvm-install.ico $(WIN32PATH)/graphics cp $(srcdir)/dists/win32/migration.bat $(WIN32PATH) diff --git a/snapcraft.yaml b/snapcraft.yaml new file mode 100644 index 0000000000..178a323414 --- /dev/null +++ b/snapcraft.yaml @@ -0,0 +1,63 @@ +name: scummvm +version: "1.9.0git" +summary: ScummVM +description: | + ScummVM is a program which allows you to run certain classic graphical + point-and-click adventure games, provided you already have their data + files. The clever part about this: ScummVM just replaces the executables + shipped with the game, allowing you to play them on systems for which + they were never designed! +confinement: strict + +apps: + scummvm: + command: scummvm + plugs: [x11, home, pulseaudio, unity7, opengl] + +parts: + scummvm: + source: . + plugin: autotools +# Quick test build +# configflags: +# - --disable-all-engines +# - --enable-engine=scumm + build-packages: + - g++ + - make + - libsdl2-dev + - libjpeg62-dev + - libmpeg2-4-dev + - libogg-dev + - libvorbis-dev + - libflac-dev + - libmad0-dev + - libpng12-dev + - libtheora-dev + - libfaad-dev + - libfluidsynth-dev + - libfreetype6-dev + - zlib1g-dev + - libunity-dev + stage-packages: + - libicu55 + - libasound2 + - libc6 + - libfaad2 + - libflac8 + - libfluidsynth1 + - libgl1-mesa-dri + - libgl1-mesa-glx + - libjpeg62 + - libjpeg8 + - libmad0 + - libmpeg2-4 + - libogg0 + - libpng12-0 + - libsdl2-2.0-0 + - libsndio6.1 + - libstdc++6 + - libtheora0 + - libvorbis0a + - libvorbisfile3 + - zlib1g |