diff options
106 files changed, 44720 insertions, 2 deletions
diff --git a/devtools/create_neverhood/create_neverhood.cpp b/devtools/create_neverhood/create_neverhood.cpp new file mode 100644 index 0000000000..323066d8b1 --- /dev/null +++ b/devtools/create_neverhood/create_neverhood.cpp @@ -0,0 +1,537 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 + + +// HACK to allow building with the SDL backend on MinGW +// see bug #1800764 "TOOLS: MinGW tools building broken" +#ifdef main +#undef main +#endif // main + +#include <vector> +#include "create_neverhood.h" +#include "tables.h" + +const int DAT_VERSION = 0; + +uint32 dataSize; +byte *data; +uint32 dataStart = 0x004AE000; +uint32 fileStart = 0x000AC600; + +class HitRectList; +class RectList; +class MessageList; +class NavigationList; + +void addMessageList(uint32 messageListCount, uint32 messageListOffset); + +void loadExe(const char *filename) { + FILE *exe = fopen(filename, "rb"); + dataSize = fileSize(exe); + data = new byte[dataSize]; + fread(data, dataSize, 1, exe); + fclose(exe); +} + +byte *getData(uint32 offset) { + return data + offset - dataStart + fileStart; +} + +const char *getStringP(uint32 offset) { + return offset != 0 ? (const char*)getData(offset) : NULL; +} + +uint32 calcHash(const char *value) { + if (!value) + return 0; + uint32 hash = 0, shiftValue = 0; + while (*value != 0) { + char ch = *value++; + if (ch >= 'a' && ch <= 'z') + ch -= 32; + else if (ch >= '0' && ch <= '9') + ch += 22; + shiftValue += ch - 64; + if (shiftValue >= 32) + shiftValue -= 32; + hash ^= 1 << shiftValue; + } + return hash; +} + +struct HitRect { + int16 x1, y1, x2, y2; + uint16 messageNum; + + void load(uint32 offset) { + byte *item = getData(offset); + x1 = READ_LE_UINT16(item + 0); + y1 = READ_LE_UINT16(item + 2); + x2 = READ_LE_UINT16(item + 4); + y2 = READ_LE_UINT16(item + 6); + messageNum = READ_LE_UINT16(item + 8); + } + + void save(FILE *fd) { + writeUint16LE(fd, x1); + writeUint16LE(fd, y1); + writeUint16LE(fd, x2); + writeUint16LE(fd, y2); + writeUint16LE(fd, messageNum); + } + + int getItemSize() const { + return 10; + } + +}; + +struct MessageItem { + uint16 messageNum; + uint32 messageParam; + MessageItem() {} + MessageItem(uint16 msgNum, uint32 msgParam) : messageNum(msgNum), messageParam(msgParam) {} + + void load(uint32 offset) { + byte *item = getData(offset); + messageNum = READ_LE_UINT16(item + 0); + messageParam = READ_LE_UINT32(item + 4); + } + + void save(FILE *fd) { + writeUint16LE(fd, messageNum); + writeUint32LE(fd, messageParam); + } + + int getItemSize() const { + return 8; + } + +}; + +struct SubRectItem { + int16 x1, y1, x2, y2; + uint32 messageListCount; + uint32 messageListOffset; + + void load(uint32 offset) { + byte *item = getData(offset); + x1 = READ_LE_UINT16(item + 0); + y1 = READ_LE_UINT16(item + 2); + x2 = READ_LE_UINT16(item + 4); + y2 = READ_LE_UINT16(item + 6); + messageListCount = READ_LE_UINT32(item + 8); + messageListOffset = READ_LE_UINT32(item + 12); + // Add the message to the message list + addMessageList(messageListCount, messageListOffset); + } + + void save(FILE *fd) { + writeUint16LE(fd, x1); + writeUint16LE(fd, y1); + writeUint16LE(fd, x2); + writeUint16LE(fd, y2); + writeUint32LE(fd, messageListOffset); + } + + int getItemSize() const { + return 16; + } + +}; + +struct RectItem { + int16 x1, y1, x2, y2; + uint32 subRectListCount; + uint32 subRectListOffset; + std::vector<SubRectItem> subRectItems; + + void load(uint32 offset) { + byte *item = getData(offset); + uint32 subItemOffset; + x1 = READ_LE_UINT16(item + 0); + y1 = READ_LE_UINT16(item + 2); + x2 = READ_LE_UINT16(item + 4); + y2 = READ_LE_UINT16(item + 6); + subRectListCount = READ_LE_UINT32(item + 8); + subRectListOffset = READ_LE_UINT32(item + 12); + subItemOffset = subRectListOffset; + for (uint32 j = 0; j < subRectListCount; j++) { + SubRectItem subRectItem; + subRectItem.load(subItemOffset); + subItemOffset += 16; + subRectItems.push_back(subRectItem); + } + } + + void save(FILE *fd) { + writeUint16LE(fd, x1); + writeUint16LE(fd, y1); + writeUint16LE(fd, x2); + writeUint16LE(fd, y2); + writeUint32LE(fd, subRectItems.size()); + for (uint32 j = 0; j < subRectItems.size(); j++) + subRectItems[j].save(fd); + } + + int getItemSize() const { + return 16; + } + +}; + +struct NavigationItem { + uint32 fileHash; + uint32 leftSmackerFileHash; + uint32 rightSmackerFileHash; + uint32 middleSmackerFileHash; + byte interactive; + byte middleFlag; + uint32 mouseCursorFileHash; + + void load(uint32 offset) { + byte *item = getData(offset); + fileHash = READ_LE_UINT32(item + 0); + leftSmackerFileHash = READ_LE_UINT32(item + 4); + rightSmackerFileHash = READ_LE_UINT32(item + 8); + middleSmackerFileHash = READ_LE_UINT32(item + 12); + interactive = item[16]; + middleFlag = item[17]; + mouseCursorFileHash = READ_LE_UINT32(item + 20); + } + + void save(FILE *fd) { + writeUint32LE(fd, fileHash); + writeUint32LE(fd, leftSmackerFileHash); + writeUint32LE(fd, rightSmackerFileHash); + writeUint32LE(fd, middleSmackerFileHash); + writeByte(fd, interactive); + writeByte(fd, middleFlag); + writeUint32LE(fd, mouseCursorFileHash); + } + + int getItemSize() const { + return 24; + } + +}; + +struct SceneInfo140Item { + uint32 id; + uint32 bgFilename1; + uint32 bgFilename2; + uint32 txFilename; + uint32 bgFilename3; + byte xPosIndex; + byte count; + + void load(uint32 offset) { + byte *item = getData(offset); + id = offset; + // Only save the hashes instead of the full names + bgFilename1 = calcHash(getStringP(READ_LE_UINT32(item + 0))); + bgFilename2 = calcHash(getStringP(READ_LE_UINT32(item + 4))); + txFilename = calcHash(getStringP(READ_LE_UINT32(item + 8))); + bgFilename3 = calcHash(getStringP(READ_LE_UINT32(item + 12))); + xPosIndex = item[16]; + count = item[17]; + } + + void save(FILE *fd) { + writeUint32LE(fd, id); + writeUint32LE(fd, bgFilename1); + writeUint32LE(fd, bgFilename2); + writeUint32LE(fd, txFilename); + writeUint32LE(fd, bgFilename3); + writeByte(fd, xPosIndex); + writeByte(fd, count); + } + +}; + +struct SceneInfo2700Item { + uint32 id; + uint32 bgFilename; + uint32 class437Filename; + uint32 dataResourceFilename; + uint32 pointListName; + uint32 rectListName; + uint32 exPaletteFilename2; + uint32 exPaletteFilename1; + uint32 mouseCursorFilename; + int16 which1; + int16 which2; + + void load(uint32 offset) { + byte *item = getData(offset); + id = offset; + // Only save the hashes instead of the full names + bgFilename = calcHash(getStringP(READ_LE_UINT32(item + 0))); + class437Filename = calcHash(getStringP(READ_LE_UINT32(item + 4))); + dataResourceFilename = calcHash(getStringP(READ_LE_UINT32(item + 8))); + pointListName = calcHash(getStringP(READ_LE_UINT32(item + 12))); + rectListName = calcHash(getStringP(READ_LE_UINT32(item + 16))); + exPaletteFilename2 = calcHash(getStringP(READ_LE_UINT32(item + 20))); + exPaletteFilename1 = calcHash(getStringP(READ_LE_UINT32(item + 24))); + mouseCursorFilename = calcHash(getStringP(READ_LE_UINT32(item + 28))); + which1 = READ_LE_UINT16(item + 32); + which2 = READ_LE_UINT16(item + 34); + } + + void save(FILE *fd) { + writeUint32LE(fd, id); + writeUint32LE(fd, bgFilename); + writeUint32LE(fd, class437Filename); + writeUint32LE(fd, dataResourceFilename); + writeUint32LE(fd, pointListName); + writeUint32LE(fd, rectListName); + writeUint32LE(fd, exPaletteFilename2); + writeUint32LE(fd, exPaletteFilename1); + writeUint32LE(fd, mouseCursorFilename); + writeUint16LE(fd, which1); + writeUint16LE(fd, which2); + } + +}; + +template<class ITEMCLASS> +class StaticDataList { +public: + uint32 id; + std::vector<ITEMCLASS> items; + + virtual ~StaticDataList() { + } + + void add(ITEMCLASS item) { + items.push_back(item); + } + + int getCount() const { + return items.size(); + } + + ITEMCLASS *getListItem(int index) { + return &items[index]; + } + + virtual bool specialLoadList(uint32 count, uint32 offset) { + return false; + } + + void loadList(uint32 count, uint32 offset) { + id = offset; + if (!specialLoadList(count, offset)) { + for (uint32 i = 0; i < count; i++) { + ITEMCLASS listItem; + listItem.load(offset); + offset += listItem.getItemSize(); + add(listItem); + } + } + } + + void saveList(FILE *fd) { + writeUint32LE(fd, id); + writeUint32LE(fd, getCount()); + for (int i = 0; i < getCount(); i++) { + items[i].save(fd); + } + } + +}; + +class HitRectList : public StaticDataList<HitRect> { +}; + +class RectList : public StaticDataList<RectItem> { +}; + +class MessageList : public StaticDataList<MessageItem> { +public: + + virtual bool specialLoadList(uint32 count, uint32 offset) { + // Special code for message lists which are set at runtime (but otherwise constant) + switch (offset) { + // Scene 1002 rings + case 0x004B4200: + add(MessageItem(0x4800, 258)); + add(MessageItem(0x100D, 0x4A845A00)); + add(MessageItem(0x4805, 1)); + return true; + case 0x004B4218: + add(MessageItem(0x4800, 297)); + add(MessageItem(0x100D, 0x43807801)); + add(MessageItem(0x4805, 2)); + return true; + case 0x004B4230: + add(MessageItem(0x4800, 370)); + add(MessageItem(0x100D, 0x46C26A01)); + return true; + case 0x004B4240: + add(MessageItem(0x4800, 334)); + add(MessageItem(0x100D, 0x468C7B11)); + add(MessageItem(0x4805, 1)); + return true; + case 0x004B4258: + add(MessageItem(0x4800, 425)); + add(MessageItem(0x100D, 0x42845B19)); + add(MessageItem(0x4805, 1)); + return true; + // Scene 1302 rings + case 0x004B0888: + add(MessageItem(0x4800, 218)); + add(MessageItem(0x100D, 0x4A845A00)); + add(MessageItem(0x4805, 1)); + return true; + case 0x004B08A0: + add(MessageItem(0x4800, 218 + 32)); + add(MessageItem(0x100D, 0x43807801)); + return true; + case 0x004B08B0: + add(MessageItem(0x4800, 218 + 32 + 32)); + add(MessageItem(0x100D, 0x46C26A01)); + add(MessageItem(0x4805, 1)); + return true; + case 0x004B08C8: + add(MessageItem(0x4800, 218 + 32 + 32 + 32)); + add(MessageItem(0x100D, 0x468C7B11)); + return true; + case 0x004B08D8: + add(MessageItem(0x4800, 218 + 32 + 32 + 32 + 32)); + add(MessageItem(0x100D, 0x42845B19)); + add(MessageItem(0x4805, 4)); + return true; + } + return false; + } + +}; + +class NavigationList : public StaticDataList<NavigationItem> { +}; + +template<class LISTCLASS> +class StaticDataListVector { +public: + std::vector<LISTCLASS*> lists; + + void add(LISTCLASS *list) { + lists.push_back(list); + } + + void loadListVector(const uint32 *offsets) { + for (int i = 0; offsets[i] != 0; i += 2) { + LISTCLASS *list = new LISTCLASS(); + list->loadList(offsets[i], offsets[i + 1]); + bool doAppend = true; + // Bad + for (typename std::vector<LISTCLASS*>::iterator it = lists.begin(); it != lists.end(); it++) { + if ((*it)->id == list->id) { + doAppend = false; + break; + } + } + if (doAppend) + lists.push_back(list); + } + } + + void saveListVector(FILE *fd) { + writeUint32LE(fd, lists.size()); + for (typename std::vector<LISTCLASS*>::iterator it = lists.begin(); it != lists.end(); it++) { + (*it)->saveList(fd); + } + } + +}; + +template<class ITEMCLASS> +class StaticDataVector { +public: + std::vector<ITEMCLASS> items; + + void loadVector(const uint32 *offsets) { + for (int i = 0; offsets[i] != 0; i++) { + ITEMCLASS item; + item.load(offsets[i]); + items.push_back(item); + } + } + + void saveVector(FILE *fd) { + writeUint32LE(fd, items.size()); + for (typename std::vector<ITEMCLASS>::iterator it = items.begin(); it != items.end(); it++) { + (*it).save(fd); + } + } + +}; + +StaticDataListVector<HitRectList> hitRectLists; +StaticDataListVector<RectList> rectLists; +StaticDataListVector<MessageList> messageLists; +StaticDataListVector<NavigationList> navigationLists; +StaticDataVector<SceneInfo140Item> sceneInfo140Items; +StaticDataVector<SceneInfo2700Item> sceneInfo2700Items; + +void addMessageList(uint32 messageListCount, uint32 messageListOffset) { + MessageList *messageList = new MessageList(); + messageList->loadList(messageListCount, messageListOffset); + messageLists.add(messageList); +} + +int main(int argc, char *argv[]) { + + FILE *datFile; + + loadExe("nhc.exe"); + + hitRectLists.loadListVector(hitRectListOffsets); + rectLists.loadListVector(rectListOffsets); + messageLists.loadListVector(messageListOffsets); + navigationLists.loadListVector(navigationListOffsets); + sceneInfo140Items.loadVector(sceneInfo140Offsets); + sceneInfo2700Items.loadVector(sceneInfo2700Offsets); + + datFile = fopen("neverhood.dat", "wb"); + + writeUint32LE(datFile, 0x11223344); // Some magic + writeUint32LE(datFile, DAT_VERSION); + + messageLists.saveListVector(datFile); + rectLists.saveListVector(datFile); + hitRectLists.saveListVector(datFile); + navigationLists.saveListVector(datFile); + sceneInfo140Items.saveVector(datFile); + sceneInfo2700Items.saveVector(datFile); + + fclose(datFile); + + printf("Done.\n"); + + return 0; +} diff --git a/devtools/create_neverhood/create_neverhood.h b/devtools/create_neverhood/create_neverhood.h new file mode 100644 index 0000000000..6382c87375 --- /dev/null +++ b/devtools/create_neverhood/create_neverhood.h @@ -0,0 +1,28 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 CREATE_NEVERHOOD_H +#define CREATE_NEVERHOOD_H + +#include "util.h" + +#endif diff --git a/devtools/create_neverhood/module.mk b/devtools/create_neverhood/module.mk new file mode 100644 index 0000000000..284e19105d --- /dev/null +++ b/devtools/create_neverhood/module.mk @@ -0,0 +1,12 @@ + +MODULE := devtools/create_neverhood + +MODULE_OBJS := \ + create_neverhood.o \ + util.o + +# Set the name of the executable +TOOL_EXECUTABLE := create_neverhood + +# Include common rules +include $(srcdir)/rules.mk diff --git a/devtools/create_neverhood/tables.h b/devtools/create_neverhood/tables.h new file mode 100644 index 0000000000..ea39aa807d --- /dev/null +++ b/devtools/create_neverhood/tables.h @@ -0,0 +1,718 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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. + * + */ + +static const uint32 hitRectListOffsets[] = { + // Scene1001 + 1, 0x004B4860, + // Scene1002 + 1, 0x004B4138, + // Scene1201 + 4, 0x004AEBD0, + // Scene1302 + 1, 0x004B0858, + // Scene1705 + 1, 0x004B69D8, + // Scene2203 + 1, 0x004B8320, + // Scene2205 + 1, 0x004B0620, + 0, 0 +}; + +static const uint32 rectListOffsets[] = { + // Scene1001 + 1, 0x004B49F0, + 1, 0x004B4A00, + // Scene1002 + 3, 0x004B43A0, + 1, 0x004B4418, + 3, 0x004B43A0, + // Scene1004 + 1, 0x004B7C70, + // Scene1109 + 1, 0x004B63A8, + 1, 0x004B6398, + // Scene1201 + 1, 0x004AEE58, + 1, 0x004AEDC8, + 1, 0x004AEE18, + 1, 0x004AED88, + // Scene1302 + 2, 0x004B0A38, + // Scene1303 + 1, 0x004AF9E8, + // Scene1304 + 1, 0x004B91A8, + // Scene1305 + 1, 0x004B6E98, + // Scene1306 + 1, 0x004AFD28, + 1, 0x004AFD18, + // Scene1308 + 1, 0x004B5990, + 1, 0x004B5980, + 1, 0x004B59A0, + // Scene1401 + 1, 0x004B6758, + // Scene1402 + 1, 0x004B0C48, + 1, 0x004B0C98, + // Scene1403 + 1, 0x004B1FF8, + 1, 0x004B2008, + // Scene1404 + 1, 0x004B8D80, + // Scene1608 + 1, 0x004B47D0, + 1, 0x004B4810, + // Scene1705 + 1, 0x004B6B40, + 1, 0x004B6B30, + // Scene1901 + 1, 0x004B34C8, + // Scene2001 + 1, 0x004B3680, + 1, 0x004B3670, + // Scene2101 + 1, 0x004B9008, + 1, 0x004B8FF8, + // Scene2203 + 1, 0x004B8420, + // Scene2206 + 1, 0x004B8AF8, + 1, 0x004B8B58, + // Scene2207 + 3, 0x004B38B8, + 1, 0x004B3948, + // Scene2242 + 1, 0x004B3DC8, + 1, 0x004B3E18, + // HallOfRecordsScene + 1, 0x004B2BF8, + 1, 0x004B2BB8, + // Scene2247 + 1, 0x004B5588, + 1, 0x004B55C8, + // Scene2401 + 1, 0x004B3140, + // Scene2402 + 1, 0x004AF900, + // Scene2403 + 1, 0x004B5E18, + 1, 0x004B5E28, + // Scene2406 + 1, 0x004B78C8, + 1, 0x004B78D8, + // Scene2501 + 1, 0x004B2608, + // Scene2732 + 1, 0x004AE360, + // Scene2801 + 1, 0x004B6CE0, + 1, 0x004B6CD0, + 1, 0x004B6CF0, + // Scene2805 + 1, 0x004AE318, + 1, 0x004AE308, + // Scene2810 + 1, 0x004AE810, + 1, 0x004AE800, + // Scene2812 + 1, 0x004AF700, + 1, 0x004AF710, + 0, 0 +}; + +static const uint32 messageListOffsets[] = { + // Scene1001 + 1, 0x004B4888, + 2, 0x004B4898, + 1, 0x004B4970, + 1, 0x004B4890, + 3, 0x004B4910, + 5, 0x004B4938, + 1, 0x004B4960, + 4, 0x004B48A8, + 3, 0x004B48C8, + // Scene1002 + 1, 0x004B4270, + 1, 0x004B4478, + 3, 0x004B4298, + 1, 0x004B4470, + 4, 0x004B4428, + 5, 0x004B4448, + 1, 0x004B44B8, + 2, 0x004B44A8, + 1, 0x004B44A0, + 2, 0x004B43D0, + 4, 0x004B4480, + 2, 0x004B41E0, + 5, 0x004B4148, + // Scene1004 + 3, 0x004B7BF0, + 2, 0x004B7C08, + 1, 0x004B7C18, + 2, 0x004B7C20, + // Scene1109 + 1, 0x004B6260, + 2, 0x004B6268, + 4, 0x004B6318, + 2, 0x004B6278, + 1, 0x004B6258, + // Scene1201 + 1, 0x004AEC08, + 2, 0x004AEC10, + 2, 0x004AEC20, + 2, 0x004AEC30, + 4, 0x004AEC90, + 2, 0x004AECB0, + 2, 0x004AECC0, + 5, 0x004AECF0, + 2, 0x004AECD0, + 2, 0x004AECE0, + 2, 0x004AED38, + // Scene1302 + 4, 0x004B08F0, + 3, 0x004B0920, + 1, 0x004B0950, + 2, 0x004B0940, + 1, 0x004B0938, + 2, 0x004B0910, + 1, 0x004B0968, + 2, 0x004B0878, + 4, 0x004B0978, + 1, 0x004B0870, + 1, 0x004B0868, + // Scene1303 + 1, 0x004AF9A0, + 2, 0x004AF9B8, + // Scene1304 + 1, 0x004B90E8, + 1, 0x004B90F0, + 2, 0x004B9158, + 2, 0x004B9130, + 2, 0x004B9140, + // Scene1305 + 1, 0x004B6E40, + 1, 0x004B6E48, + // Scene1306 + 1, 0x004AFAD0, + 2, 0x004AFAF0, + 1, 0x004AFBC8, + 1, 0x004AFC30, + 4, 0x004AFC38, + 2, 0x004AFB00, + 1, 0x004AFBD0, + 4, 0x004AFBD8, + 2, 0x004AFAE0, + 1, 0x004AFAD8, + 2, 0x004AFC58, + 2, 0x004AFC68, + // Scene1308 + 1, 0x004B57C0, + 1, 0x004B57C8, + 1, 0x004B58B0, + 3, 0x004B57D0, + 3, 0x004B57E8, + 2, 0x004B5868, + 4, 0x004B5848, + 3, 0x004B5830, + 2, 0x004B5800, + 2, 0x004B5868, + 2, 0x004B58E0, + // Scene1401 + 1, 0x004B65C8, + 1, 0x004B65D0, + 1, 0x004B65D8, + 1, 0x004B65E8, + 3, 0x004B6670, + 4, 0x004B6690, + 1, 0x004B66B0, + 3, 0x004B6658, + 2, 0x004B65F0, + // Scene1402 + 1, 0x004B0B48, + 1, 0x004B0B50, + 1, 0x004B0B58, + 1, 0x004B0B60, + 2, 0x004B0B68, + 3, 0x004B0BB8, + 3, 0x004B0BD0, + // Scene1403 + 1, 0x004B1F18, + 1, 0x004B1F20, + 3, 0x004B1F70, + 2, 0x004B1FA8, + 4, 0x004B1F88, + 3, 0x004B1F58, + 2, 0x004B1F28, + 2, 0x004B1FB8, + // Scene1404 + 1, 0x004B8C28, + 1, 0x004B8C30, + 1, 0x004B8C38, + 1, 0x004B8D28, + 3, 0x004B8CB8, + 2, 0x004B8C40, + 6, 0x004B8CE8, + 3, 0x004B8CA0, + 2, 0x004B8CD0, + 2, 0x004B8D18, + // Scene1608 + 1, 0x004B46A8, + 1, 0x004B46B0, + 1, 0x004B47A8, + 3, 0x004B4748, + 2, 0x004B4770, + 2, 0x004B46C8, + 2, 0x004B4760, + // Scene1705 + 1, 0x004B69E8, + 2, 0x004B6A08, + 4, 0x004B6AA0, + 2, 0x004B6A18, + 1, 0x004B69F0, + 2, 0x004B6AC0, + // Scene1901 + 1, 0x004B3408, + 1, 0x004B3410, + 1, 0x004B3400, + // Scene2001 + 1, 0x004B3538, + 2, 0x004B3540, + 4, 0x004B35F0, + 2, 0x004B3550, + 1, 0x004B3530, + // Scene2101 + 1, 0x004B8E48, + 3, 0x004B8E50, + 4, 0x004B8F58, + 2, 0x004B8EB0, + 2, 0x004B8EA0, + 1, 0x004B8F50, + 1, 0x004B8F48, + 4, 0x004B8E80, + 1, 0x004B8EC8, + 2, 0x004B8F78, + 3, 0x004B8F00, + // Scene2201 + 1, 0x004B8118, + 1, 0x004B8130, + 1, 0x004B8178, + 2, 0x004B8120, + 3, 0x004B81A0, + 1, 0x004B81B8, + 2, 0x004B8108, + 5, 0x004B8150, + 4, 0x004B8180, + 3, 0x004B8138, + 2, 0x004B8108, + 2, 0x004B81C8, + // Scene2203 + 1, 0x004B8340, + 1, 0x004B8350, + 1, 0x004B8358, + 1, 0x004B8348, + 3, 0x004B83B0, + 3, 0x004B83C8, + 2, 0x004B8370, + 2, 0x004B8360, + 2, 0x004B83E0, + 2, 0x004B83F0, + // Scene2205 + 1, 0x004B0658, + 2, 0x004B0648, + 1, 0x004B0640, + 4, 0x004B0690, + 2, 0x004B0630, + // Scene2206 + 1, 0x004B88A8, + 2, 0x004B88B8, + 1, 0x004B88C8, + 1, 0x004B8A70, + 1, 0x004B88B0, + 5, 0x004B8948, + 2, 0x004B8970, + 2, 0x004B8988, + 4, 0x004B8998, + 4, 0x004B89B8, + 4, 0x004B89D8, + 5, 0x004B89F8, + 5, 0x004B8A20, + 5, 0x004B8A48, + // Scene2207 + 1, 0x004B38E8, + 4, 0x004B38F0, + 2, 0x004B37D8, + 2, 0x004B3958, + 3, 0x004B3920, + // Scene2242 + 1, 0x004B3C18, + 1, 0x004B3D60, + 1, 0x004B3D48, + 1, 0x004B3C20, + 2, 0x004B3D50, + 5, 0x004B3CF8, + 5, 0x004B3D20, + 4, 0x004B3CB8, + 4, 0x004B3CD8, + // HallOfRecordsScene + 1, 0x004B2900, + 2, 0x004B2910, + 1, 0x004B2B70, + 1, 0x004B2908, + 2, 0x004B2920, + 4, 0x004B2978, + 4, 0x004B2998, + 4, 0x004B29B8, + 4, 0x004B29D8, + 4, 0x004B29F8, + 4, 0x004B2A18, + 4, 0x004B2A38, + 5, 0x004B2A58, + 5, 0x004B2A80, + 5, 0x004B2AA8, + 5, 0x004B2AD0, + 5, 0x004B2AF8, + 5, 0x004B2B20, + 5, 0x004B2B48, + // Scene2247 + 1, 0x004B5428, + 2, 0x004B5438, + 1, 0x004B5530, + 1, 0x004B5430, + 4, 0x004B54A0, + 4, 0x004B54C0, + 5, 0x004B54E0, + 5, 0x004B5508, + // Scene2401 + 1, 0x004B2F70, + 1, 0x004B2F80, + 1, 0x004B2F78, + 4, 0x004B3090, + 2, 0x004B30B0, + 6, 0x004B3020, + 2, 0x004B3050, + 4, 0x004B2FA8, + 4, 0x004B2FC8, + // Scene2402 + 1, 0x004AF7C8, + 2, 0x004AF7D8, + 1, 0x004AF888, + 1, 0x004AF7D0, + 3, 0x004AF800, + 1, 0x004AF818, + 2, 0x004AF890, + // Scene2403 + 1, 0x004B5C98, + 1, 0x004B5D70, + 4, 0x004B5CA0, + 2, 0x004B5D98, + // Scene2406 + 1, 0x004B76C8, + 3, 0x004B76D8, + 1, 0x004B77C0, + 1, 0x004B7810, + 1, 0x004B76D0, + 2, 0x004B77C8, + 2, 0x004B77D8, + 2, 0x004B7758, + 4, 0x004B7738, + // Scene2501 + 7, 0x004B2538, + 6, 0x004B2570, + // Scene2732 + 1, 0x004AE328, + // Scene2801 + 1, 0x004B6BB8, + 1, 0x004B6BC0, + 1, 0x004B6C10, + 1, 0x004B6BB0, + 2, 0x004B6C40, + // Scene2803b + 1, 0x004B60D8, + 1, 0x004B6100, + 1, 0x004B60F8, + 1, 0x004B6100, + 3, 0x004B6138, + 3, 0x004B60E0, + 3, 0x004B6180, + 1, 0x004B6198, + 6, 0x004B6108, + 3, 0x004B6150, + 3, 0x004B6168, + 1, 0x004B61A0, + 5, 0x004B61A8, + // Scene2803 + 1, 0x004B79F0, + 5, 0x004B79C8, + 1, 0x004B7A00, + 2, 0x004B7A78, + 1, 0x004B79F8, + 1, 0x004B79C0, + 1, 0x004B7A50, + 2, 0x004B7A58, + 5, 0x004B7A08, + 4, 0x004B7A30, + 2, 0x004B7A68, + 7, 0x004B7A88, + // Scene2805 + 1, 0x004AE1C8, + 2, 0x004AE1D0, + 4, 0x004AE288, + 2, 0x004AE1E0, + 1, 0x004AE1C0, + // Scene2806 + 1, 0x004AF098, + 1, 0x004AF098, + 3, 0x004AF0C8, + 5, 0x004AF0A0, + 1, 0x004AF090, + 2, 0x004AF0E0, + // Scene2809 + 1, 0x004B5B90, + 3, 0x004B5BD0, + 5, 0x004B5BA8, + 1, 0x004B5B88, + 2, 0x004B5B98, + 1, 0x004AE438, + 3, 0x004AE440, + 3, 0x004AE738, + 1, 0x004AE6D8, + 2, 0x004AE6E8, + 1, 0x004AE6E0, + 2, 0x004AE428, + 2, 0x004AE418, + 1, 0x004AE410, + 4, 0x004AE458, + 5, 0x004AE4A8, + 5, 0x004AE4D0, + 5, 0x004AE4F8, + 5, 0x004AE520, + 5, 0x004AE548, + 5, 0x004AE570, + 5, 0x004AE598, + 5, 0x004AE5C0, + 5, 0x004AE5E8, + 5, 0x004AE610, + 5, 0x004AE638, + 5, 0x004AE660, + 5, 0x004AE688, + 2, 0x004AE750, + // Scene2812 + 1, 0x004AF560, + 1, 0x004AF588, + 1, 0x004AF5F0, + 4, 0x004AF568, + 2, 0x004AF658, + 2, 0x004AF668, + 0, 0 +}; + +static const uint32 navigationListOffsets[] = { + // Module1100 + 2, 0x004B8430, + 2, 0x004B8460, + 4, 0x004B84F0, + 4, 0x004B8490, + 2, 0x004B8580, + 2, 0x004B8550, + // Module1300 + 6, 0x004B2718, + 2, 0x004B27A8, + 2, 0x004B27D8, + 2, 0x004B2808, + 2, 0x004B2838, + // Module1600 + 4, 0x004B39D0, + 2, 0x004B3A30, + 2, 0x004B3A60, + 6, 0x004B3A90, + 2, 0x004B3B20, + 2, 0x004B3B50, + 2, 0x004B3B80, + // Module1700 + 2, 0x004AE8B8, + 3, 0x004AE8E8, + // Module1800 + 4, 0x004AFD38, + 1, 0x004AFD98, + 2, 0x004AFDB0, + 4, 0x004AFDE0, + 2, 0x004AFE40, + // Module2300 + 2, 0x004B67B8, + 6, 0x004B67E8, + 2, 0x004B6878, + 3, 0x004B68F0, + 3, 0x004B68A8, + // Module2000 + 3, 0x004B7B48, + 3, 0x004B7B00, + // Module2600 + 2, 0x004B8608, + 4, 0x004B8638, + 2, 0x004B8698, + 2, 0x004B86C8, + 4, 0x004B8758, + 4, 0x004B86F8, + 2, 0x004B87B8, + // Module3000 + 2, 0x004B7C80, + 2, 0x004B7CE0, + 2, 0x004B7CB0, + 3, 0x004B7D58, + 3, 0x004B7D10, + 4, 0x004B7E60, + 4, 0x004B7DA0, + 4, 0x004B7E00, + 4, 0x004B7F20, + 4, 0x004B7EC0, + 2, 0x004B7F80, + 1, 0x004B7FB0, + 0, 0 +}; + +// Hall of Records scene definitions + +static const uint32 sceneInfo140Offsets[] = { + 0x004B7180, + 0x004B7198, + 0x004B71B0, + 0x004B71C8, + 0x004B71E0, + 0x004B71F8, + 0x004B7210, + 0x004B7228, + 0x004B7240, + 0x004B7258, + 0x004B7270, + 0x004B7288, + 0x004B72A0, + 0x004B72B8, + 0x004B72D0, + 0x004B72E8, + 0x004B7300, + 0x004B7318, + 0x004B7330, + 0x004B7348, + 0x004B7360, + 0x004B7378, + 0x004B7390, + 0x004B73A8, + 0x004B73C0, + 0x004B73D8, + 0x004B73F0, + 0x004B7408, + 0x004B7420, + 0x004B7438, + 0x004B7450, + 0x004B7468, + 0x004B7480, + 0x004B7498, + 0x004B74B0, + 0x004B74C8, + 0 +}; + +static const uint32 sceneInfo2700Offsets[] = { + // + 0x004B1710, + 0x004B1738, + 0x004B1760, + 0x004B1788, + 0x004B17B0, + 0x004B17D8, + 0x004B1800, + 0x004B1828, + 0x004B1850, + 0x004B1878, + 0x004B18A0, + 0x004B18C8, + 0x004B18F0, + 0x004B1918, + // + 0x004B19E0, + 0x004B1A08, + 0x004B1A30, + 0x004B1A58, + 0x004B1A80, + 0x004B1AA8, + 0x004B1AD0, + 0x004B1AF8, + 0x004B1B20, + 0x004B1B48, + 0x004B1B70, + 0x004B1B98, + 0x004B1BC0, + 0x004B1BE8, + 0x004B1C10, + 0x004B1C38, + 0x004B1C60, + 0x004B1C88, + 0x004B1CB0, + 0x004B1CD8, + 0x004B1D00, + 0x004B1D28, + 0x004B1D50, + 0x004B1D78, + // + 0x004B1DB0, + // + 0x004B1DE8, + 0x004B1E10, + 0x004B1E38, + 0x004B1E60, + // + 0x004B1950, + // + 0x004B2240, + // + 0x004B5F68, + 0x004B5F8C, + 0x004B5FB0, + 0x004B5FD8, + 0x004B5FFC, + 0x004B6020, + // Scene2501 + 0x004B2628, + 0x004B264C, + 0x004B2670, + // Scene2502 + 0x004B01B8, + // Scene2503 + 0x004B01E0, + 0x004B0208, + // Scene2505 + 0x004B0230, + // Scene2506 + 0x004B0268, + // Scene2507 + 0x004B02A0, + // Scene2508 + 0x004B02C8, + // Scene2706 + 0x004B22A0, + 0x004B22C4, + 0x004B22E8, + 0 +}; diff --git a/devtools/create_neverhood/util.cpp b/devtools/create_neverhood/util.cpp new file mode 100644 index 0000000000..5ce8237b85 --- /dev/null +++ b/devtools/create_neverhood/util.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. + * + */ + +// Disable symbol overrides so that we can use system headers. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + +#include "util.h" +#include <stdarg.h> + +#ifdef _MSC_VER + #define vsnprintf _vsnprintf +#endif + +void error(const char *s, ...) { + char buf[1024]; + va_list va; + + va_start(va, s); + vsnprintf(buf, 1024, s, va); + va_end(va); + + fprintf(stderr, "ERROR: %s!\n", buf); + + exit(1); +} + +void warning(const char *s, ...) { + char buf[1024]; + va_list va; + + va_start(va, s); + vsnprintf(buf, 1024, s, va); + va_end(va); + + fprintf(stderr, "WARNING: %s!\n", buf); +} + +int scumm_stricmp(const char *s1, const char *s2) { + byte l1, l2; + do { + // Don't use ++ inside tolower, in case the macro uses its + // arguments more than once. + l1 = (byte)*s1++; + l1 = tolower(l1); + l2 = (byte)*s2++; + l2 = tolower(l2); + } while (l1 == l2 && l1 != 0); + return l1 - l2; +} + +void debug(int level, const char *s, ...) { + char buf[1024]; + va_list va; + + va_start(va, s); + vsnprintf(buf, 1024, s, va); + va_end(va); + + fprintf(stderr, "DEBUG: %s!\n", buf); +} + +uint8 readByte(FILE *fp) { + return fgetc(fp); +} + +uint16 readUint16BE(FILE *fp) { + uint16 ret = 0; + ret |= fgetc(fp) << 8; + ret |= fgetc(fp); + return ret; +} + +uint16 readUint16LE(FILE *fp) { + uint16 ret = 0; + ret |= fgetc(fp); + ret |= fgetc(fp) << 8; + return ret; +} + +uint32 readUint32BE(FILE *fp) { + uint32 ret = 0; + ret |= fgetc(fp) << 24; + ret |= fgetc(fp) << 16; + ret |= fgetc(fp) << 8; + ret |= fgetc(fp); + return ret; +} + +uint32 readUint32LE(FILE *fp) { + uint32 ret = 0; + ret |= fgetc(fp); + ret |= fgetc(fp) << 8; + ret |= fgetc(fp) << 16; + ret |= fgetc(fp) << 24; + return ret; +} + +void writeByte(FILE *fp, uint8 b) { + fwrite(&b, 1, 1, fp); +} + +void writeUint16BE(FILE *fp, uint16 value) { + writeByte(fp, (uint8)(value >> 8)); + writeByte(fp, (uint8)(value)); +} + +void writeUint16LE(FILE *fp, uint16 value) { + writeByte(fp, (uint8)(value)); + writeByte(fp, (uint8)(value >> 8)); +} + +void writeUint32BE(FILE *fp, uint32 value) { + writeByte(fp, (uint8)(value >> 24)); + writeByte(fp, (uint8)(value >> 16)); + writeByte(fp, (uint8)(value >> 8)); + writeByte(fp, (uint8)(value)); +} + +void writeUint32LE(FILE *fp, uint32 value) { + writeByte(fp, (uint8)(value)); + writeByte(fp, (uint8)(value >> 8)); + writeByte(fp, (uint8)(value >> 16)); + writeByte(fp, (uint8)(value >> 24)); +} + +uint32 fileSize(FILE *fp) { + uint32 sz; + uint32 pos = ftell(fp); + fseek(fp, 0, SEEK_END); + sz = ftell(fp); + fseek(fp, pos, SEEK_SET); + return sz; +} diff --git a/devtools/create_neverhood/util.h b/devtools/create_neverhood/util.h new file mode 100644 index 0000000000..a2783cca71 --- /dev/null +++ b/devtools/create_neverhood/util.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 UTIL_H +#define UTIL_H + +#include "common/scummsys.h" +#include "common/endian.h" +#include "common/util.h" + +#ifdef WIN32 +#include <io.h> +#include <process.h> +#endif + + +/* File I/O */ +uint8 readByte(FILE *fp); +uint16 readUint16BE(FILE *fp); +uint16 readUint16LE(FILE *fp); +uint32 readUint32BE(FILE *fp); +uint32 readUint32LE(FILE *fp); +void writeByte(FILE *fp, uint8 b); +void writeUint16BE(FILE *fp, uint16 value); +void writeUint16LE(FILE *fp, uint16 value); +void writeUint32BE(FILE *fp, uint32 value); +void writeUint32LE(FILE *fp, uint32 value); +uint32 fileSize(FILE *fp); + +/* Misc stuff */ +void NORETURN_PRE error(const char *s, ...) NORETURN_POST; +void warning(const char *s, ...); +void debug(int level, const char *s, ...); +int scumm_stricmp(const char *s1, const char *s2); + +using namespace Common; + +#endif diff --git a/dists/engine-data/neverhood.dat b/dists/engine-data/neverhood.dat Binary files differnew file mode 100644 index 0000000000..dc95c00965 --- /dev/null +++ b/dists/engine-data/neverhood.dat diff --git a/engines/configure.engines b/engines/configure.engines index a52276130a..15aa11d0ef 100644 --- a/engines/configure.engines +++ b/engines/configure.engines @@ -28,6 +28,7 @@ add_engine mohawk "Mohawk" yes "cstime myst riven" "Living Books" add_engine cstime "Where in Time is Carmen Sandiego?" no add_engine riven "Riven: The Sequel to Myst" no "" "" "16bit" add_engine myst "Myst" no "" "" "16bit" +add_engine neverhood "Neverhood" no add_engine parallaction "Parallaction" yes add_engine pegasus "The Journeyman Project: Pegasus Prime" yes "" "" "16bit" add_engine queen "Flight of the Amazon Queen" yes diff --git a/engines/engines.mk b/engines/engines.mk index bcf97df991..b905a288c9 100644 --- a/engines/engines.mk +++ b/engines/engines.mk @@ -130,6 +130,11 @@ DEFINES += -DENABLE_RIVEN endif endif +ifdef ENABLE_NEVERHOOD +DEFINES += -DENABLE_NEVERHOOD=$(ENABLE_NEVERHOOD) +MODULES += engines/neverhood +endif + ifdef ENABLE_PARALLACTION DEFINES += -DENABLE_PARALLACTION=$(ENABLE_PARALLACTION) MODULES += engines/parallaction diff --git a/engines/neverhood/background.cpp b/engines/neverhood/background.cpp new file mode 100644 index 0000000000..0a80bd8390 --- /dev/null +++ b/engines/neverhood/background.cpp @@ -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. + * + */ + +#include "neverhood/background.h" + +namespace Neverhood { + +// Background + +Background::Background(NeverhoodEngine *vm, int objectPriority) + : Entity(vm, objectPriority), _surface(NULL), _spriteResource(vm) { + // Empty +} + +Background::Background(NeverhoodEngine *vm, uint32 fileHash, int objectPriority, int surfacePriority) + : Entity(vm, objectPriority), _surface(NULL), _spriteResource(vm) { + + _spriteResource.load(fileHash); + createSurface(surfacePriority, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height); + _surface->drawSpriteResource(_spriteResource); + +} + +Background::~Background() { + delete _surface; +} + +void Background::createSurface(int surfacePriority, int16 width, int16 height) { + _surface = new BaseSurface(_vm, surfacePriority, width, height); + _surface->setTransparent(false); + _spriteResource.getPosition().x = width; + _spriteResource.getPosition().y = height; +} + +void Background::load(uint32 fileHash) { + _spriteResource.load(fileHash); + if (_surface) + _surface->drawSpriteResource(_spriteResource); +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/background.h b/engines/neverhood/background.h new file mode 100644 index 0000000000..ef88be21c0 --- /dev/null +++ b/engines/neverhood/background.h @@ -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. + * + */ + +#ifndef NEVERHOOD_BACKGROUND_H +#define NEVERHOOD_BACKGROUND_H + +#include "neverhood/neverhood.h" +#include "neverhood/entity.h" +#include "neverhood/graphics.h" +#include "neverhood/resource.h" + +namespace Neverhood { + +class Background : public Entity { +public: + Background(NeverhoodEngine *vm, int objectPriority); + Background(NeverhoodEngine *vm, uint32 fileHash, int objectPriority, int surfacePriority); + virtual ~Background(); + BaseSurface *getSurface() { return _surface; } + void createSurface(int surfacePriority, int16 width, int16 height); + void load(uint32 fileHash); + SpriteResource& getSpriteResource() { return _spriteResource; } +protected: + BaseSurface *_surface; + SpriteResource _spriteResource; +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_BACKGROUND_H */ diff --git a/engines/neverhood/blbarchive.cpp b/engines/neverhood/blbarchive.cpp new file mode 100644 index 0000000000..9f5f46487c --- /dev/null +++ b/engines/neverhood/blbarchive.cpp @@ -0,0 +1,159 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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/dcl.h" +#include "neverhood/blbarchive.h" + +namespace Neverhood { + +/** + * A special variant of SafeSeekableSubReadStream which locks a mutex during each read. + * This is neccessary because the music is streamed from disk and it could happen + * that a sound effect or another music track is played from the same read stream + * while the first music track is updated/read. + */ + +class SafeMutexedSeekableSubReadStream : public Common::SafeSeekableSubReadStream { +public: + SafeMutexedSeekableSubReadStream(SeekableReadStream *parentStream, uint32 begin, uint32 end, DisposeAfterUse::Flag disposeParentStream, + Common::Mutex &mutex) + : SafeSeekableSubReadStream(parentStream, begin, end, disposeParentStream), _mutex(mutex) { + } + virtual uint32 read(void *dataPtr, uint32 dataSize); +protected: + Common::Mutex &_mutex; +}; + +uint32 SafeMutexedSeekableSubReadStream::read(void *dataPtr, uint32 dataSize) { + Common::StackLock lock(_mutex); + return Common::SafeSeekableSubReadStream::read(dataPtr, dataSize); +} + +BlbArchive::BlbArchive() : _extData(NULL) { +} + +BlbArchive::~BlbArchive() { + delete[] _extData; +} + +void BlbArchive::open(const Common::String &filename) { + BlbHeader header; + uint16 *extDataOffsets; + + _entries.clear(); + + if (!_fd.open(filename)) + error("BlbArchive::open() Could not open %s", filename.c_str()); + + header.id1 = _fd.readUint32LE(); + header.id2 = _fd.readUint16LE(); + header.extDataSize = _fd.readUint16LE(); + header.fileSize = _fd.readUint32LE(); + header.fileCount = _fd.readUint32LE(); + + if (header.id1 != 0x2004940 || header.id2 != 7 || header.fileSize != _fd.size()) + error("BlbArchive::open() %s seems to be corrupt", filename.c_str()); + + debug(4, "%s: fileCount = %d", filename.c_str(), header.fileCount); + + _entries.reserve(header.fileCount); + + // Load file hashes + for (uint i = 0; i < header.fileCount; i++) { + BlbArchiveEntry entry; + entry.fileHash = _fd.readUint32LE(); + _entries.push_back(entry); + } + + extDataOffsets = new uint16[header.fileCount]; + + // Load file records + for (uint i = 0; i < header.fileCount; i++) { + BlbArchiveEntry &entry = _entries[i]; + entry.type = _fd.readByte(); + entry.comprType = _fd.readByte(); + entry.extData = NULL; + extDataOffsets[i] = _fd.readUint16LE(); + entry.timeStamp = _fd.readUint32LE(); + entry.offset = _fd.readUint32LE(); + entry.diskSize = _fd.readUint32LE(); + entry.size = _fd.readUint32LE(); + debug(4, "%08X: %03d, %02X, %04X, %08X, %08X, %08X, %08X", + entry.fileHash, entry.type, entry.comprType, extDataOffsets[i], entry.timeStamp, + entry.offset, entry.diskSize, entry.size); + } + + // Load ext data + if (header.extDataSize > 0) { + _extData = new byte[header.extDataSize]; + _fd.read(_extData, header.extDataSize); + for (uint i = 0; i < header.fileCount; i++) + _entries[i].extData = extDataOffsets[i] > 0 ? _extData + extDataOffsets[i] - 1 : NULL; + } + + delete[] extDataOffsets; + +} + +void BlbArchive::load(uint index, byte *buffer, uint32 size) { + load(&_entries[index], buffer, size); +} + +void BlbArchive::load(BlbArchiveEntry *entry, byte *buffer, uint32 size) { + Common::StackLock lock(_mutex); + + _fd.seek(entry->offset); + + switch (entry->comprType) { + case 1: // Uncompressed + if (size == 0) + size = entry->diskSize; + _fd.read(buffer, size); + break; + case 3: // DCL-compressed + if (!Common::decompressDCL(&_fd, buffer, entry->diskSize, entry->size)) + error("BlbArchive::load() Error during decompression of %08X", entry->fileHash); + break; + default: + error("BlbArchive::load() Unknown compression type %d", entry->comprType); + } + +} + +byte *BlbArchive::getEntryExtData(uint index) { + return getEntryExtData(&_entries[index]); +} + +byte *BlbArchive::getEntryExtData(BlbArchiveEntry *entry) { + return entry->extData; +} + +Common::SeekableReadStream *BlbArchive::createStream(uint index) { + return createStream(&_entries[index]); +} + +Common::SeekableReadStream *BlbArchive::createStream(BlbArchiveEntry *entry) { + return new SafeMutexedSeekableSubReadStream(&_fd, entry->offset, entry->offset + entry->diskSize, + DisposeAfterUse::NO, _mutex); +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/blbarchive.h b/engines/neverhood/blbarchive.h new file mode 100644 index 0000000000..620b12b8ac --- /dev/null +++ b/engines/neverhood/blbarchive.h @@ -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. + * + */ + +#ifndef NEVERHOOD_BLBARCHIVE_H +#define NEVERHOOD_BLBARCHIVE_H + +#include "common/array.h" +#include "common/file.h" +#include "common/mutex.h" +#include "common/stream.h" +#include "common/substream.h" +#include "neverhood/neverhood.h" + +namespace Neverhood { + +struct BlbHeader { + uint32 id1; + uint16 id2; + uint16 extDataSize; + int32 fileSize; + uint32 fileCount; +}; + +struct BlbArchiveEntry { + uint32 fileHash; + byte type; + byte comprType; + byte *extData; + uint32 timeStamp; + uint32 offset; + uint32 diskSize; + uint32 size; +}; + +class BlbArchive { +public: + BlbArchive(); + ~BlbArchive(); + void open(const Common::String &filename); + void load(uint index, byte *buffer, uint32 size); + void load(BlbArchiveEntry *entry, byte *buffer, uint32 size); + byte *getEntryExtData(uint index); + byte *getEntryExtData(BlbArchiveEntry *entry); + uint32 getSize(uint index) { return _entries[index].size; } + BlbArchiveEntry *getEntry(uint index) { return &_entries[index]; } + uint getCount() { return _entries.size(); } + Common::SeekableReadStream *createStream(uint index); + Common::SeekableReadStream *createStream(BlbArchiveEntry *entry); +private: + Common::File _fd; + Common::Mutex _mutex; + Common::Array<BlbArchiveEntry> _entries; + byte *_extData; +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_BLBARCHIVE_H */ diff --git a/engines/neverhood/detection.cpp b/engines/neverhood/detection.cpp new file mode 100644 index 0000000000..4f70c63299 --- /dev/null +++ b/engines/neverhood/detection.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 "base/plugins.h" + +#include "engines/advancedDetector.h" +#include "common/file.h" + +#include "neverhood/neverhood.h" + + +namespace Neverhood { + +struct NeverhoodGameDescription { + ADGameDescription desc; + + int gameID; + int gameType; + uint32 features; + uint16 version; +}; + +const char *NeverhoodEngine::getGameId() const { + return _gameDescription->desc.gameid; +} + +uint32 NeverhoodEngine::getFeatures() const { + return _gameDescription->features; +} + +Common::Platform NeverhoodEngine::getPlatform() const { + return _gameDescription->desc.platform; +} + +uint16 NeverhoodEngine::getVersion() const { + return _gameDescription->version; +} + +bool NeverhoodEngine::isDemo() const { + return _gameDescription->desc.flags & ADGF_DEMO; +} + +} + +static const PlainGameDescriptor neverhoodGames[] = { + {"neverhood", "The Neverhood Chronicles"}, + {0, 0} +}; + +namespace Neverhood { + +static const NeverhoodGameDescription gameDescriptions[] = { + + { + // Neverhood English version + { + "neverhood", + 0, + AD_ENTRY1s("hd.blb", "22958d968458c9ff221aee38577bb2b2", 4279716), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + 0, + 0, + 0, + 0, + }, + + { + // Neverhood English demo version + { + "neverhood", + "Demo", + AD_ENTRY1s("nevdemo.blb", "05b735cfb1086892bec79b54dca5545b", 22564568), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_DEMO, + GUIO1(GUIO_NONE) + }, + 0, + 0, + 0, + 0, + }, + + { AD_TABLE_END_MARKER, 0, 0, 0, 0 } +}; + +} // End of namespace Neverhood + +class NeverhoodMetaEngine : public AdvancedMetaEngine { +public: + NeverhoodMetaEngine() : AdvancedMetaEngine(Neverhood::gameDescriptions, sizeof(Neverhood::NeverhoodGameDescription), neverhoodGames) { + _singleid = "neverhood"; + _guioptions = GUIO2(GUIO_NOSUBTITLES, GUIO_NOMIDI); + } + + virtual const char *getName() const { + return "Neverhood Engine"; + } + + virtual const char *getOriginalCopyright() const { + return "Neverhood (C) The Neverhood, Inc."; + } + + virtual bool hasFeature(MetaEngineFeature f) const; + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + + SaveStateList listSaves(const char *target) const; + virtual int getMaximumSaveSlot() const; + void removeSaveState(const char *target, int slot) const; + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; + +}; + +bool NeverhoodMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup) || + (f == kSupportsDeleteSave) || + (f == kSavesSupportMetaInfo) || + (f == kSavesSupportThumbnail) || + (f == kSavesSupportCreationDate) || + (f == kSavesSupportPlayTime); +} + +bool Neverhood::NeverhoodEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +} + +bool NeverhoodMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + const Neverhood::NeverhoodGameDescription *gd = (const Neverhood::NeverhoodGameDescription *)desc; + if (gd) { + *engine = new Neverhood::NeverhoodEngine(syst, gd); + } + return gd != 0; +} + +SaveStateList NeverhoodMetaEngine::listSaves(const char *target) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Neverhood::NeverhoodEngine::SaveHeader header; + Common::String pattern = target; + pattern += ".???"; + + Common::StringArray filenames; + filenames = saveFileMan->listSavefiles(pattern.c_str()); + Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) + + SaveStateList saveList; + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); file++) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + int slotNum = atoi(file->c_str() + file->size() - 3); + if (slotNum >= 0 && slotNum <= 999) { + Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str()); + if (in) { + if (Neverhood::NeverhoodEngine::readSaveHeader(in, false, header) == Neverhood::NeverhoodEngine::kRSHENoError) { + saveList.push_back(SaveStateDescriptor(slotNum, header.description)); + } + delete in; + } + } + } + + return saveList; +} + +int NeverhoodMetaEngine::getMaximumSaveSlot() const { + return 999; +} + +void NeverhoodMetaEngine::removeSaveState(const char *target, int slot) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::String filename = Neverhood::NeverhoodEngine::getSavegameFilename(target, slot); + saveFileMan->removeSavefile(filename.c_str()); +} + +SaveStateDescriptor NeverhoodMetaEngine::querySaveMetaInfos(const char *target, int slot) const { + Common::String filename = Neverhood::NeverhoodEngine::getSavegameFilename(target, slot); + Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(filename.c_str()); + + if (in) { + Neverhood::NeverhoodEngine::SaveHeader header; + Neverhood::NeverhoodEngine::kReadSaveHeaderError error; + + error = Neverhood::NeverhoodEngine::readSaveHeader(in, true, header); + delete in; + + if (error == Neverhood::NeverhoodEngine::kRSHENoError) { + SaveStateDescriptor desc(slot, header.description); + + desc.setDeletableFlag(false); + desc.setWriteProtectedFlag(false); + desc.setThumbnail(header.thumbnail); + int day = (header.saveDate >> 24) & 0xFF; + int month = (header.saveDate >> 16) & 0xFF; + int year = header.saveDate & 0xFFFF; + desc.setSaveDate(year, month, day); + int hour = (header.saveTime >> 16) & 0xFF; + int minutes = (header.saveTime >> 8) & 0xFF; + desc.setSaveTime(hour, minutes); + desc.setPlayTime(header.playTime * 1000); + return desc; + } + } + + return SaveStateDescriptor(); +} + +#if PLUGIN_ENABLED_DYNAMIC(NEVERHOOD) + REGISTER_PLUGIN_DYNAMIC(NEVERHOOD, PLUGIN_TYPE_ENGINE, NeverhoodMetaEngine); +#else + REGISTER_PLUGIN_STATIC(NEVERHOOD, PLUGIN_TYPE_ENGINE, NeverhoodMetaEngine); +#endif diff --git a/engines/neverhood/diskplayerscene.cpp b/engines/neverhood/diskplayerscene.cpp new file mode 100644 index 0000000000..d972943759 --- /dev/null +++ b/engines/neverhood/diskplayerscene.cpp @@ -0,0 +1,511 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/diskplayerscene.h" +#include "neverhood/mouse.h" + +namespace Neverhood { + +static const uint32 kDiskplayerPaletteFileHashes[] = { + 0x03B78240, + 0x34B32B08, + 0x4F2569D4, + 0x07620590, + 0x38422401 +}; + +static const byte kDiskplayerInitArray[] = { + 2, 1, 4, 5, 3, 11, 8, 6, 7, 9, 10, 17, 16, 18, 19, 20, 15, 14, 13, 12 +}; + +static const uint32 kDiskplayerSmackerFileHashes[] = { + 0x010A2810, + 0x020A2810, + 0x040A2810, + 0x080A2810, + 0x100A2810, + 0x200A2810, + 0x400A2810, + 0x800A2810, + 0x000A2811, + 0x010C2810, + 0x020C2810, + 0x040C2810, + 0x080C2810, + 0x100C2810, + 0x200C2810, + 0x400C2810, + 0x800C2810, + 0x000C2811, + 0x000C2812, + 0x02002810, + 0x04002810 +}; + +static const uint32 kDiskplayerSlotFileHashes1[] = { + 0x81312280, + 0x01312281, + 0x01312282, + 0x01312284, + 0x01312288, + 0x01312290, + 0x013122A0, + 0x013122C0, + 0x01312200, + 0x82312280, + 0x02312281, + 0x02312282, + 0x02312284, + 0x02312288, + 0x02312290, + 0x023122A0, + 0x023122C0, + 0x02312200, + 0x02312380, + 0x04312281 +}; + +static const uint32 kDiskplayerSlotFileHashes2[] = { + 0x90443A00, + 0x90443A18, + 0x90443A28, + 0x90443A48, + 0x90443A88, + 0x90443B08, + 0x90443808, + 0x90443E08, + 0x90443208, + 0xA0443A00, + 0xA0443A18, + 0xA0443A28, + 0xA0443A48, + 0xA0443A88, + 0xA0443B08, + 0xA0443808, + 0xA0443E08, + 0xA0443208, + 0xA0442A08, + 0xC0443A18 +}; + +static const uint32 kDiskplayerSlotFileHashes3[] = { + 0x10357320, + 0x10557320, + 0x10957320, + 0x11157320, + 0x12157320, + 0x14157320, + 0x18157320, + 0x00157320, + 0x30157320, + 0x1035B320, + 0x1055B320, + 0x1095B320, + 0x1115B320, + 0x1215B320, + 0x1415B320, + 0x1815B320, + 0x0015B320, + 0x3015B320, + 0x5015B320, + 0x10543320 +}; + +static const uint32 kDiskplayerSlotFileHashes4[] = { + 0xDC8020E4, + 0xDC802164, + 0xDC802264, + 0xDC802464, + 0xDC802864, + 0xDC803064, + 0xDC800064, + 0xDC806064, + 0xDC80A064, + 0xDC8020E7, + 0xDC802167, + 0xDC802267, + 0xDC802467, + 0xDC802867, + 0xDC803067, + 0xDC800067, + 0xDC806067, + 0xDC80A067, + 0xDC812067, + 0xDC802161 +}; + +AsDiskplayerSceneKey::AsDiskplayerSceneKey(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1100) { + + createSurface1(0x100B90B4, 1200); + _x = 211; + _y = 195; + startAnimation(0x100B90B4, 0, -1); + _newStickFrameIndex = 0; + _needRefresh = true; + updatePosition(); + setVisible(false); +} + +uint32 AsDiskplayerSceneKey::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsDiskplayerSceneKey::stDropKey() { + startAnimation(0x100B90B4, 0, -1); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsDiskplayerSceneKey::handleMessage); + NextState(&AsDiskplayerSceneKey::stDropKeyDone); + setVisible(true); +} + +void AsDiskplayerSceneKey::stDropKeyDone() { + stopAnimation(); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&Sprite::handleMessage); + setVisible(false); +} + +DiskplayerPlayButton::DiskplayerPlayButton(NeverhoodEngine *vm, DiskplayerScene *diskplayerScene) + : StaticSprite(vm, 1400), _diskplayerScene(diskplayerScene), _isPlaying(false) { + + loadSprite(0x24A4A664, kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset, 400); + setVisible(false); + loadSound(0, 0x44043000); + loadSound(1, 0x44045000); + SetMessageHandler(&DiskplayerPlayButton::handleMessage); +} + +uint32 DiskplayerPlayButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = 0; + Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (!_diskplayerScene->getDropKey()) { + if (_isPlaying) { + sendMessage(_diskplayerScene, 0x2001, 0); + release(); + } else { + sendMessage(_diskplayerScene, 0x2000, 0); + press(); + } + } + updatePosition(); + messageResult = 1; + break; + } + return messageResult; +} + +void DiskplayerPlayButton::press() { + if (!_isPlaying) { + setVisible(true); + updatePosition(); + playSound(0); + _isPlaying = true; + } +} + +void DiskplayerPlayButton::release() { + if (_isPlaying) { + setVisible(false); + updatePosition(); + playSound(1); + _isPlaying = false; + } +} + +DiskplayerSlot::DiskplayerSlot(NeverhoodEngine *vm, DiskplayerScene *diskplayerScene, int slotIndex, bool isAvailable) + : Entity(vm, 0), _diskplayerScene(diskplayerScene), _isLocked(false), _isBlinking(false), + _blinkCountdown(0), _initialBlinkCountdown(2), _inactiveSlot(NULL), _appearSlot(NULL), _activeSlot(NULL) { + + if (isAvailable && slotIndex < 20) { + _inactiveSlot = _diskplayerScene->addSprite(new StaticSprite(_vm, kDiskplayerSlotFileHashes1[slotIndex], 1100)); + _appearSlot = _diskplayerScene->addSprite(new StaticSprite(_vm, kDiskplayerSlotFileHashes2[slotIndex], 1000)); + _activeSlot = _diskplayerScene->addSprite(new StaticSprite(_vm, kDiskplayerSlotFileHashes3[slotIndex], 1100)); + _inactiveSlot->setVisible(false); + _appearSlot->setVisible(false); + _activeSlot->setVisible(false); + loadSound(0, 0x46210074); + setSoundPan(0, slotIndex * 100 / 19); + } else if (slotIndex != 20) { + _activeSlot = _diskplayerScene->addSprite(new StaticSprite(_vm, kDiskplayerSlotFileHashes4[slotIndex], 1100)); + _activeSlot->setVisible(false); + } + SetUpdateHandler(&DiskplayerSlot::update); +} + +void DiskplayerSlot::update() { + if (_blinkCountdown != 0 && (--_blinkCountdown == 0)) { + if (_isBlinking) { + if (_inactiveSlot) + _inactiveSlot->setVisible(true); + if (_activeSlot) + _activeSlot->setVisible(false); + _blinkCountdown = _initialBlinkCountdown / 2; + } else { + if (_inactiveSlot) + _inactiveSlot->setVisible(false); + if (_activeSlot) + _activeSlot->setVisible(true); + _blinkCountdown = _initialBlinkCountdown; + } + _isBlinking = !_isBlinking; + } +} + +void DiskplayerSlot::appear() { + if (_inactiveSlot) + _inactiveSlot->setVisible(true); + if (_appearSlot) + _appearSlot->setVisible(true); + if (_inactiveSlot) + playSound(0); +} + +void DiskplayerSlot::play() { + if (!_isLocked) { + if (_inactiveSlot) + _inactiveSlot->setVisible(false); + if (_activeSlot) + _activeSlot->setVisible(true); + _isBlinking = true; + _blinkCountdown = 0; + } +} + +void DiskplayerSlot::activate() { + if (!_isLocked) + _blinkCountdown = _initialBlinkCountdown; +} + +void DiskplayerSlot::stop() { + if (!_isLocked) { + if (_inactiveSlot) + _inactiveSlot->setVisible(true); + if (_activeSlot) + _activeSlot->setVisible(false); + _isBlinking = false; + _blinkCountdown = 0; + } +} + +DiskplayerScene::DiskplayerScene(NeverhoodEngine *vm, Module *parentModule, int paletteIndex) + : Scene(vm, parentModule), _diskIndex(0), _appearCountdown(0), _tuneInCountdown(0), + _hasAllDisks(false), _dropKey(false), _inputDisabled(true), _updateStatus(kUSStopped) { + + int availableDisksCount = 0; + + setBackground(0x8A000044); + setPalette(kDiskplayerPaletteFileHashes[paletteIndex]); + + _ssPlayButton = insertSprite<DiskplayerPlayButton>(this); + addCollisionSprite(_ssPlayButton); + + _asKey = insertSprite<AsDiskplayerSceneKey>(); + + for (int i = 0; i < 20; i++) { + _diskAvailable[i] = false; + if (getSubVar(VA_IS_TAPE_INSERTED, i)) + availableDisksCount++; + } + + for (int i = 0; i < availableDisksCount; i++) + _diskAvailable[kDiskplayerInitArray[i] - 1] = true; + + for (int slotIndex = 0; slotIndex < 20; slotIndex++) { + _diskSlots[slotIndex] = new DiskplayerSlot(_vm, this, slotIndex, _diskAvailable[slotIndex]); + addEntity(_diskSlots[slotIndex]); + } + + _hasAllDisks = availableDisksCount == 20; + + if (_hasAllDisks && !getGlobalVar(V_HAS_FINAL_KEY)) + _dropKey = true; + + _finalDiskSlot = new DiskplayerSlot(_vm, this, 20, false); + addEntity(_finalDiskSlot); + + insertPuzzleMouse(0x000408A8, 20, 620); + showMouse(false); + + _diskSmackerPlayer = new SmackerPlayer(_vm, this, 0x08288103, false, true); + addEntity(_diskSmackerPlayer); + addSurface(_diskSmackerPlayer->getSurface()); + _diskSmackerPlayer->setDrawPos(154, 86); + _vm->_screen->setSmackerDecoder(_diskSmackerPlayer->getSmackerDecoder()); + + _palette->usePalette(); + + SetMessageHandler(&DiskplayerScene::handleMessage); + SetUpdateHandler(&DiskplayerScene::update); + _appearCountdown = 6; + +} + +void DiskplayerScene::update() { + Scene::update(); + + if (_updateStatus == kUSTuningIn && _diskSmackerPlayer->isDone()) { + if (_diskAvailable[_diskIndex]) + playDisk(); + else + playStatic(); + } else if (_updateStatus == kUSPlaying && _diskSmackerPlayer->isDone()) { + _diskSlots[_diskIndex]->stop(); + _diskIndex++; + if (_hasAllDisks) { + if (_diskIndex != 20) { + playDisk(); + } else if (_dropKey) { + playDisk(); + _updateStatus = kUSPlayingFinal; + } else { + _diskIndex = 0; + stop(); + } + } else if (_diskIndex != 20) { + tuneIn(); + } else { + _diskIndex = 0; + stop(); + } + } else if (_updateStatus == kUSPlayingFinal) { + if (_diskSmackerPlayer->getFrameNumber() == 133) { + _asKey->stDropKey(); + setGlobalVar(V_HAS_FINAL_KEY, 1); + } else if (_diskSmackerPlayer->isDone()) { + for (int i = 0; i < 20; i++) { + _diskSlots[i]->setLocked(false); + _diskSlots[i]->stop(); + } + _diskIndex = 0; + stop(); + showMouse(true); + _dropKey = false; + } + } + + if (_appearCountdown != 0 && (--_appearCountdown == 0)) { + _diskSlots[_diskIndex]->appear(); + if (_dropKey) { + _diskSlots[_diskIndex]->activate(); + _diskSlots[_diskIndex]->setLocked(true); + } + _diskIndex++; + while (!_diskAvailable[_diskIndex] && _diskIndex < 19) + _diskIndex++; + if (_diskIndex < 20) { + _appearCountdown = 1; + } else { + _diskIndex = 0; + _inputDisabled = false; + if (_dropKey) { + _ssPlayButton->press(); + _tuneInCountdown = 2; + } else { + showMouse(true); + _diskSlots[_diskIndex]->activate(); + } + } + } + + if (_tuneInCountdown != 0 && (--_tuneInCountdown == 0)) + playDisk(); + +} + +uint32 DiskplayerScene::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + if (!_inputDisabled) { + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) { + sendMessage(_parentModule, 0x1009, 0); + } else if (!_dropKey && + param.asPoint().x > 38 && param.asPoint().x < 598 && + param.asPoint().y > 400 && param.asPoint().y < 460) { + + _diskSlots[_diskIndex]->stop(); + _diskIndex = (param.asPoint().x - 38) / 28; + _diskSlots[_diskIndex]->activate(); + if (_updateStatus == kUSPlaying) { + if (_diskAvailable[_diskIndex]) + playDisk(); + else + playStatic(); + } + } + break; + case 0x2000: + tuneIn(); + break; + case 0x2001: + stop(); + break; + } + } + return 0; +} + +void DiskplayerScene::stop() { + _diskSmackerPlayer->open(0x08288103, true); + _vm->_screen->setSmackerDecoder(_diskSmackerPlayer->getSmackerDecoder()); + _palette->usePalette(); + _ssPlayButton->release(); + _updateStatus = kUSStopped; + _diskSlots[_diskIndex]->activate(); +} + +void DiskplayerScene::tuneIn() { + _diskSmackerPlayer->open(0x900001C1, false); + _vm->_screen->setSmackerDecoder(_diskSmackerPlayer->getSmackerDecoder()); + _palette->usePalette(); + _ssPlayButton->release(); + _updateStatus = kUSTuningIn; + _diskSlots[_diskIndex]->activate(); +} + +void DiskplayerScene::playDisk() { + _diskSmackerPlayer->open(kDiskplayerSmackerFileHashes[_diskIndex], false); + _vm->_screen->setSmackerDecoder(_diskSmackerPlayer->getSmackerDecoder()); + _palette->usePalette(); + _updateStatus = kUSPlaying; + _diskSlots[_diskIndex]->play(); +} + +void DiskplayerScene::playStatic() { + _diskSmackerPlayer->open(0x90000101, false); + _vm->_screen->setSmackerDecoder(_diskSmackerPlayer->getSmackerDecoder()); + _palette->usePalette(); + _ssPlayButton->release(); + _updateStatus = kUSPlaying; + _diskSlots[_diskIndex]->activate(); +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/diskplayerscene.h b/engines/neverhood/diskplayerscene.h new file mode 100644 index 0000000000..f3fd9ea874 --- /dev/null +++ b/engines/neverhood/diskplayerscene.h @@ -0,0 +1,110 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_DISKPLAYERSCENE_H +#define NEVERHOOD_DISKPLAYERSCENE_H + +#include "neverhood/neverhood.h" +#include "neverhood/resourceman.h" +#include "neverhood/scene.h" +#include "neverhood/smackerplayer.h" + +namespace Neverhood { + +class DiskplayerScene; + +class AsDiskplayerSceneKey : public AnimatedSprite { +public: + AsDiskplayerSceneKey(NeverhoodEngine *vm); + void stDropKey(); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stDropKeyDone(); +}; + +class DiskplayerPlayButton : public StaticSprite { +public: + DiskplayerPlayButton(NeverhoodEngine *vm, DiskplayerScene *diskplayerScene); + void press(); + void release(); +protected: + DiskplayerScene *_diskplayerScene; + bool _isPlaying; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class DiskplayerSlot : public Entity { +public: + DiskplayerSlot(NeverhoodEngine *vm, DiskplayerScene *diskplayerScene, int slotIndex, bool isAvailable); + void activate(); + void stop(); + void appear(); + void play(); + void setLocked(bool isLocked) { _isLocked = isLocked; } +protected: + DiskplayerScene *_diskplayerScene; + Sprite *_inactiveSlot; + Sprite *_appearSlot; + Sprite *_activeSlot; + int _initialBlinkCountdown; + int _blinkCountdown; + bool _isLocked; + bool _isBlinking; + void update(); +}; + +enum { + kUSStopped = 0, + kUSTuningIn = 1, + kUSPlaying = 2, + kUSPlayingFinal = 3 +}; + +class DiskplayerScene : public Scene { +public: + DiskplayerScene(NeverhoodEngine *vm, Module *parentModule, int paletteIndex); + bool getDropKey() const { return _dropKey; } +protected: + SmackerPlayer *_diskSmackerPlayer; + DiskplayerPlayButton *_ssPlayButton; + AsDiskplayerSceneKey *_asKey; + DiskplayerSlot *_diskSlots[20]; + DiskplayerSlot *_finalDiskSlot; + int _updateStatus; + bool _diskAvailable[20]; + int _diskIndex; + int _appearCountdown; + int _tuneInCountdown; + bool _hasAllDisks; + bool _inputDisabled; + bool _dropKey; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stop(); + void tuneIn(); + void playDisk(); + void playStatic(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_DISKPLAYERSCENE_H */ diff --git a/engines/neverhood/entity.cpp b/engines/neverhood/entity.cpp new file mode 100644 index 0000000000..8b1298916c --- /dev/null +++ b/engines/neverhood/entity.cpp @@ -0,0 +1,159 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/entity.h" +#include "neverhood/sound.h" + +namespace Neverhood { + +uint32 MessageParam::asInteger() const { + assert(_type == mptInteger); + return _integer; +} + +NPoint MessageParam::asPoint() const { + assert(_type == mptInteger || _type == mptPoint); + if (_type == mptInteger) { + NPoint pt; + pt.x = _integer & 0xFFFF; + pt.y = (_integer >> 16) & 0xFFFF; + return pt; + } + return _point; +} + +Entity *MessageParam::asEntity() const { + assert(_type == mptEntity); + return _entity; +} + +Entity::Entity(NeverhoodEngine *vm, int priority) + : _vm(vm), _updateHandlerCb(NULL), _messageHandlerCb(NULL), _priority(priority), _soundResources(NULL) { +} + +Entity::~Entity() { + deleteSoundResources(); +} + +void Entity::draw() { + // Empty +} + +void Entity::handleUpdate() { + debug(5, "handleUpdate() -> [%s]", _updateHandlerCbName.c_str()); + if (_updateHandlerCb) + (this->*_updateHandlerCb)(); +} + +uint32 Entity::receiveMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + debug(5, "receiveMessage(%04X) -> [%s]", messageNum, _messageHandlerCbName.c_str()); + return _messageHandlerCb ? (this->*_messageHandlerCb)(messageNum, param, sender) : 0; +} + +uint32 Entity::sendMessage(Entity *receiver, int messageNum, const MessageParam ¶m) { + return receiver ? receiver->receiveMessage(messageNum, param, this) : 0; +} + +uint32 Entity::sendMessage(Entity *receiver, int messageNum, uint32 param) { + return sendMessage(receiver, messageNum, MessageParam(param)); +} + +uint32 Entity::sendPointMessage(Entity *receiver, int messageNum, const NPoint ¶m) { + return sendMessage(receiver, messageNum, MessageParam(param)); +} + +uint32 Entity::sendEntityMessage(Entity *receiver, int messageNum, Entity *param) { + return sendMessage(receiver, messageNum, MessageParam((Entity*)param)); +} + +uint32 Entity::getGlobalVar(uint32 nameHash) { + return _vm->_gameVars->getGlobalVar(nameHash); +} + +void Entity::setGlobalVar(uint32 nameHash, uint32 value) { + _vm->_gameVars->setGlobalVar(nameHash, value); +} + +uint32 Entity::getSubVar(uint32 nameHash, uint32 subNameHash) { + return _vm->_gameVars->getSubVar(nameHash, subNameHash); +} + +void Entity::setSubVar(uint32 nameHash, uint32 subNameHash, uint32 value) { + _vm->_gameVars->setSubVar(nameHash, subNameHash, value); +} + +void Entity::incGlobalVar(uint32 nameHash, int incrValue) { + setGlobalVar(nameHash, getGlobalVar(nameHash) + incrValue); +} + +void Entity::incSubVar(uint32 nameHash, uint32 subNameHash, int incrValue) { + setSubVar(nameHash, subNameHash, getSubVar(nameHash, subNameHash) + incrValue); +} + +SoundResource *Entity::getSoundResource(uint index) { + assert(index < kMaxSoundResources); + if (!_soundResources) { + _soundResources = new SoundResource*[kMaxSoundResources]; + for (uint i = 0; i < kMaxSoundResources; ++i) + _soundResources[i] = NULL; + } + if (!_soundResources[index]) + _soundResources[index] = new SoundResource(_vm); + return _soundResources[index]; +} + +void Entity::loadSound(uint index, uint32 fileHash) { + getSoundResource(index)->load(fileHash); +} + +void Entity::playSound(uint index, uint32 fileHash) { + if (fileHash) + getSoundResource(index)->play(fileHash); + else + getSoundResource(index)->play(); +} + +void Entity::stopSound(uint index) { + getSoundResource(index)->stop(); +} + +bool Entity::isSoundPlaying(uint index) { + return getSoundResource(index)->isPlaying(); +} + +void Entity::setSoundVolume(uint index, int volume) { + getSoundResource(index)->setVolume(volume); +} + +void Entity::setSoundPan(uint index, int pan) { + getSoundResource(index)->setPan(pan); +} + +void Entity::deleteSoundResources() { + if (_soundResources) { + for (uint i = 0; i < kMaxSoundResources; ++i) + delete _soundResources[i]; + delete[] _soundResources; + } +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/entity.h b/engines/neverhood/entity.h new file mode 100644 index 0000000000..fb8941ae43 --- /dev/null +++ b/engines/neverhood/entity.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 NEVERHOOD_ENTITY_H +#define NEVERHOOD_ENTITY_H + +#include "common/str.h" +#include "neverhood/neverhood.h" +#include "neverhood/gamevars.h" +#include "neverhood/graphics.h" +#include "neverhood/sound.h" + +namespace Neverhood { + +class Entity; +class SoundResource; + +enum MessageParamType { + mptInteger, + mptPoint, + mptEntity +}; + +struct MessageParam { +public: + MessageParam(uint32 value) : _type(mptInteger), _integer(value) {} + MessageParam(NPoint value) : _type(mptPoint), _point(value) {} + MessageParam(Entity *entity) : _type(mptEntity), _entity(entity) {} + uint32 asInteger() const; + NPoint asPoint() const; + Entity *asEntity() const; +protected: + union { + uint32 _integer; + NPoint _point; + Entity *_entity; + }; + MessageParamType _type; +}; + +// TODO: Disable heavy debug stuff in release mode + +#define SetUpdateHandler(handler) \ + _updateHandlerCb = static_cast <void (Entity::*)(void)> (handler); \ + debug(5, "SetUpdateHandler(" #handler ")"); \ + _updateHandlerCbName = #handler + +#define SetMessageHandler(handler) \ + _messageHandlerCb = static_cast <uint32 (Entity::*)(int messageNum, const MessageParam ¶m, Entity *sender)> (handler); \ + debug(5, "SetMessageHandler(" #handler ")"); \ + _messageHandlerCbName = #handler + +const uint kMaxSoundResources = 16; + +class Entity { +public: + Common::String _updateHandlerCbName; + Common::String _messageHandlerCbName; + Entity(NeverhoodEngine *vm, int priority); + virtual ~Entity(); + virtual void draw(); + void handleUpdate(); + uint32 receiveMessage(int messageNum, const MessageParam ¶m, Entity *sender); + // NOTE: These were overloaded before for the various message parameter types + // it caused some problems so each type gets its own sendMessage variant now + uint32 sendMessage(Entity *receiver, int messageNum, const MessageParam ¶m); + uint32 sendMessage(Entity *receiver, int messageNum, uint32 param); + uint32 sendPointMessage(Entity *receiver, int messageNum, const NPoint ¶m); + uint32 sendEntityMessage(Entity *receiver, int messageNum, Entity *param); + // Shortcuts for game variable access + uint32 getGlobalVar(uint32 nameHash); + void setGlobalVar(uint32 nameHash, uint32 value); + uint32 getSubVar(uint32 nameHash, uint32 subNameHash); + void setSubVar(uint32 nameHash, uint32 subNameHash, uint32 value); + void incGlobalVar(uint32 nameHash, int incrValue); + void incSubVar(uint32 nameHash, uint32 subNameHash, int incrValue); + int getPriority() const { return _priority; } + bool hasMessageHandler() const { return _messageHandlerCb != NULL; } +protected: + void (Entity::*_updateHandlerCb)(); + uint32 (Entity::*_messageHandlerCb)(int messageNum, const MessageParam ¶m, Entity *sender); + NeverhoodEngine *_vm; + int _priority; + SoundResource **_soundResources; + SoundResource *getSoundResource(uint index); + void loadSound(uint index, uint32 fileHash); + void playSound(uint index, uint32 fileHash = 0); + void stopSound(uint index); + bool isSoundPlaying(uint index); + void setSoundVolume(uint index, int volume); + void setSoundPan(uint index, int pan); + void deleteSoundResources(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_ENTITY_H */ diff --git a/engines/neverhood/gamemodule.cpp b/engines/neverhood/gamemodule.cpp new file mode 100644 index 0000000000..49682b0d29 --- /dev/null +++ b/engines/neverhood/gamemodule.cpp @@ -0,0 +1,849 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/gamemodule.h" + +#include "neverhood/graphics.h" +#include "neverhood/menumodule.h" +#include "neverhood/modules/module1000.h" +#include "neverhood/modules/module1100.h" +#include "neverhood/modules/module1200.h" +#include "neverhood/modules/module1300.h" +#include "neverhood/modules/module1400.h" +#include "neverhood/modules/module1500.h" +#include "neverhood/modules/module1600.h" +#include "neverhood/modules/module1700.h" +#include "neverhood/modules/module1800.h" +#include "neverhood/modules/module1900.h" +#include "neverhood/modules/module2000.h" +#include "neverhood/modules/module2100.h" +#include "neverhood/modules/module2200.h" +#include "neverhood/modules/module2300.h" +#include "neverhood/modules/module2400.h" +#include "neverhood/modules/module2500.h" +#include "neverhood/modules/module2600.h" +#include "neverhood/modules/module2700.h" +#include "neverhood/modules/module2800.h" +#include "neverhood/modules/module2900.h" +#include "neverhood/modules/module3000.h" + +namespace Neverhood { + +static const uint32 kRadioMusicFileHashes[] = { + 0x82B22000, + 0x02B22004, + 0x42B22000, + 0x03322008, + 0x02B22001, + 0x02B22008, + 0x02B22020, + 0x03322001, + 0x03322002, + 0x03322004, + 0x03322040, + 0x02B22002, + 0x02B22010, + 0x03322010, + 0x02B22040, + 0x43322000, + 0x83322000, + 0x03322020 +}; + +enum { + MENU_MODULE = 9999 +}; + +GameModule::GameModule(NeverhoodEngine *vm) + : Module(vm, NULL), _moduleNum(-1), _prevChildObject(NULL), _prevModuleNum(-1), + _restoreGameRequested(false), _restartGameRequested(false), _canRequestMainMenu(true), + _mainMenuRequested(false) { + + // Other initializations moved to actual engine class + _vm->_soundMan->playSoundThree(0x002D0031, 0x8861079); + SetMessageHandler(&GameModule::handleMessage); +} + +GameModule::~GameModule() { + _vm->_soundMan->deleteSoundGroup(0x002D0031); + delete _childObject; + _childObject = NULL; +} + +void GameModule::handleMouseMove(int16 x, int16 y) { + if (_childObject) { + NPoint mousePos; + mousePos.x = x; + mousePos.y = y; + debug(2, "GameModule::handleMouseMove(%d, %d)", x, y); + sendPointMessage(_childObject, 0, mousePos); + } +} + +void GameModule::handleMouseDown(int16 x, int16 y) { + if (_childObject) { + NPoint mousePos; + mousePos.x = x; + mousePos.y = y; + debug(2, "GameModule::handleMouseDown(%d, %d)", x, y); + sendPointMessage(_childObject, 0x0001, mousePos); + } +} + +void GameModule::handleMouseUp(int16 x, int16 y) { + if (_childObject) { + NPoint mousePos; + mousePos.x = x; + mousePos.y = y; + debug(2, "GameModule::handleMouseUp(%d, %d)", x, y); + sendPointMessage(_childObject, 0x0002, mousePos); + } +} + +void GameModule::handleSpaceKey() { + if (_childObject) { + debug(2, "GameModule::handleSpaceKey()"); + sendMessage(_childObject, 0x0009, 0); + } +} + +void GameModule::handleAsciiKey(char key) { + if (_childObject) { + debug(2, "GameModule::handleAsciiKey()"); + sendMessage(_childObject, 0x000A, (uint32)key); + } +} + +void GameModule::handleKeyDown(Common::KeyCode keyCode) { + if (_childObject) { + if (keyCode == Common::KEYCODE_ESCAPE) + handleEscapeKey(); + else if (keyCode == Common::KEYCODE_SPACE) + handleSpaceKey(); + debug(2, "GameModule::handleKeyDown()"); + sendMessage(_childObject, 0x000B, keyCode); + } +} + +void GameModule::handleEscapeKey() { + if (_vm->isDemo()) + _vm->quitGame(); + else if (!_prevChildObject && _canRequestMainMenu) + _mainMenuRequested = true; + else if (_childObject) + sendMessage(_childObject, 0x000C, 0); +} + +void GameModule::initKeySlotsPuzzle() { + if (!getSubVar(VA_IS_PUZZLE_INIT, 0x25400B10)) { + NonRepeatingRandomNumbers keySlots(_vm->_rnd, 16); + for (uint i = 0; i < 3; i++) { + setSubVar(VA_GOOD_KEY_SLOT_NUMBERS, i, keySlots.getNumber()); + setSubVar(VA_CURR_KEY_SLOT_NUMBERS, i, keySlots.getNumber()); + } + setSubVar(VA_IS_PUZZLE_INIT, 0x25400B10, 1); + } +} + +void GameModule::initMemoryPuzzle() { + if (!getSubVar(VA_IS_PUZZLE_INIT, 0xC8606803)) { + NonRepeatingRandomNumbers diceIndices(_vm->_rnd, 3); + NonRepeatingRandomNumbers availableTiles(_vm->_rnd, 48); + NonRepeatingRandomNumbers tileSymbols(_vm->_rnd, 10); + for (uint32 i = 0; i < 3; i++) + setSubVar(VA_CURR_DICE_NUMBERS, i, 1); + // Set special symbols + // Symbol 5 is always one of the three special symbols + setSubVar(VA_DICE_MEMORY_SYMBOLS, diceIndices.getNumber(), 5); + tileSymbols.removeNumber(5); + for (int i = 0; i < 2; i++) + setSubVar(VA_DICE_MEMORY_SYMBOLS, diceIndices.getNumber(), tileSymbols.getNumber()); + // Insert special symbols tiles + for (uint32 i = 0; i < 3; ++i) { + int tileSymbolOccurence = _vm->_rnd->getRandomNumber(4 - 1) * 2 + 2; + setSubVar(VA_GOOD_DICE_NUMBERS, i, tileSymbolOccurence); + while (tileSymbolOccurence--) + setSubVar(VA_TILE_SYMBOLS, availableTiles.getNumber(), getSubVar(VA_DICE_MEMORY_SYMBOLS, i)); + } + // Fill the remaining tiles + uint32 tileSymbolIndex = 0; + while (!availableTiles.empty()) { + setSubVar(VA_TILE_SYMBOLS, availableTiles.getNumber(), tileSymbols[tileSymbolIndex]); + setSubVar(VA_TILE_SYMBOLS, availableTiles.getNumber(), tileSymbols[tileSymbolIndex]); + tileSymbolIndex++; + if (tileSymbolIndex >= tileSymbols.size()) + tileSymbolIndex = 0; + } + setSubVar(VA_IS_PUZZLE_INIT, 0xC8606803, 1); + + // DEBUG Enable to autosolve all tiles and leave only two matching tiles open +#if 0 + for (int i = 0; i < 48; i++) + setSubVar(VA_IS_TILE_MATCH, i, 1); + int debugIndex = 0; + setSubVar(VA_IS_TILE_MATCH, debugIndex, 0); + for (int i = 0; i < 48; i++) { + if (i != debugIndex && getSubVar(VA_TILE_SYMBOLS, i) == getSubVar(VA_TILE_SYMBOLS, debugIndex)) { + setSubVar(VA_IS_TILE_MATCH, i, 0); + break; + } + } +#endif + + } +} + +void GameModule::initWaterPipesPuzzle() { + if (!getSubVar(VA_IS_PUZZLE_INIT, 0x40520234)) { + setSubVar(VA_GOOD_WATER_PIPES_LEVEL, 0, 3); + setSubVar(VA_GOOD_WATER_PIPES_LEVEL, 1, 1); + setSubVar(VA_GOOD_WATER_PIPES_LEVEL, 2, 2); + setSubVar(VA_GOOD_WATER_PIPES_LEVEL, 3, 0); + setSubVar(VA_GOOD_WATER_PIPES_LEVEL, 4, 4); + setSubVar(VA_IS_PUZZLE_INIT, 0x40520234, 1); + } +} + +void GameModule::initRadioPuzzle() { + if (!getSubVar(VA_IS_PUZZLE_INIT, 0x08C80800)) { + int currMusicIndex = _vm->_rnd->getRandomNumber(5 - 1) + 3; + setGlobalVar(V_GOOD_RADIO_MUSIC_INDEX, 5 * currMusicIndex); + setGlobalVar(V_GOOD_RADIO_MUSIC_NAME, kRadioMusicFileHashes[currMusicIndex]); + setGlobalVar(V_RADIO_ROOM_LEFT_DOOR, 1); + setGlobalVar(V_RADIO_ROOM_RIGHT_DOOR, 0); + setSubVar(VA_IS_PUZZLE_INIT, 0x08C80800, 1); + } +} + +void GameModule::initTestTubes1Puzzle() { + if (!getSubVar(VA_IS_PUZZLE_INIT, 0x20479010)) { + for (uint i = 0; i < 3; i++) + setSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, i, _vm->_rnd->getRandomNumber(3 - 1) + 1); + setSubVar(VA_IS_PUZZLE_INIT, 0x20479010, 1); + } +} + +void GameModule::initTestTubes2Puzzle() { + if (!getSubVar(VA_IS_PUZZLE_INIT, 0x66059818)) { + for (uint i = 0; i < 3; i++) + setSubVar(VA_GOOD_TEST_TUBES_LEVEL_2, i, _vm->_rnd->getRandomNumber(6 - 1) + 1); + setSubVar(VA_IS_PUZZLE_INIT, 0x66059818, 1); + } +} + +void GameModule::initCannonSymbolsPuzzle() { + if (!getSubVar(VA_IS_PUZZLE_INIT, 0x8C9819C2)) { + for (int i = 0; i < 3; i++) { + setSubVar(VA_GOOD_CANNON_SYMBOLS_1, i, _vm->_rnd->getRandomNumber(12 - 1)); + setSubVar(VA_GOOD_CANNON_SYMBOLS_2, i, _vm->_rnd->getRandomNumber(12 - 1)); + } + setSubVar(VA_IS_PUZZLE_INIT, 0x8C9819C2, 1); + } +} + +void GameModule::initCodeSymbolsPuzzle() { + if (!getSubVar(VA_IS_PUZZLE_INIT, 0x0CD09B50)) { + for (int i = 0; i < 12; ++i) + setSubVar(VA_CODE_SYMBOLS, i, i); + for (int i = 0; i < 12; ++i) { + uint32 index1 = _vm->_rnd->getRandomNumber(12 - 1); + uint32 index2 = _vm->_rnd->getRandomNumber(12 - 1); + uint32 temp = getSubVar(VA_CODE_SYMBOLS, index1); + setSubVar(VA_CODE_SYMBOLS, index1, getSubVar(VA_CODE_SYMBOLS, index2)); + setSubVar(VA_CODE_SYMBOLS, index2, temp); + } + setGlobalVar(V_NOISY_SYMBOL_INDEX, _vm->_rnd->getRandomNumber(11 - 1) + 1); + setSubVar(VA_IS_PUZZLE_INIT, 0x0CD09B50, 1); + } +} + +void GameModule::initCubeSymbolsPuzzle() { + if (!getSubVar(VA_IS_PUZZLE_INIT, 0x60400854)) { + NonRepeatingRandomNumbers cubeSymbols(_vm->_rnd, 9); + for (uint32 cubePosition = 0; cubePosition < 9; ++cubePosition) + setSubVar(VA_CUBE_POSITIONS, cubePosition, (uint32)(cubeSymbols.getNumber() - 1)); + setSubVar(VA_IS_PUZZLE_INIT, 0x60400854, 1); + } +} + +void GameModule::initCrystalColorsPuzzle() { + if (!getGlobalVar(V_CRYSTAL_COLORS_INIT)) { + TextResource textResource(_vm); + const char *textStart, *textEnd; + textResource.load(0x46691611); + textStart = textResource.getString(0, textEnd); + for (uint index = 0; index < 5; index++) { + char colorLetter = (byte)textStart[index]; + byte correctColorNum = 0, misalignedColorNum; + switch (colorLetter) { + case 'B': + correctColorNum = 4; + break; + case 'G': + correctColorNum = 3; + break; + case 'O': + correctColorNum = 1; + break; + case 'R': + correctColorNum = 0; + break; + case 'V': + correctColorNum = 5; + break; + case 'Y': + correctColorNum = 2; + break; + } + do { + misalignedColorNum = _vm->_rnd->getRandomNumber(6 - 1); + } while (misalignedColorNum == correctColorNum); + setSubVar(VA_GOOD_CRYSTAL_COLORS, index, correctColorNum); + setSubVar(VA_CURR_CRYSTAL_COLORS, index, misalignedColorNum); + } + setGlobalVar(V_CRYSTAL_COLORS_INIT, 1); + } +} + +uint32 GameModule::getCurrRadioMusicFileHash() { + uint musicIndex = getGlobalVar(V_CURR_RADIO_MUSIC_INDEX); + return (musicIndex % 5 != 0) ? 0 : kRadioMusicFileHashes[CLIP<uint>(musicIndex / 5, 0, 17)]; +} + + +uint32 GameModule::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Module::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0800: + _canRequestMainMenu = true; + break; + case 0x1009: + _moduleResult = param.asInteger(); + _done = true; + break; + } + return messageResult; +} + +void GameModule::startup() { +#if 1 + // Logos and intro video // Real game start + createModule(1500, 0); +#else + // DEBUG>>> + /* + setGlobalVar(V_SEEN_MUSIC_BOX, 1); + setGlobalVar(V_CREATURE_EXPLODED, 0); + setGlobalVar(V_MATCH_STATUS, 0); + setGlobalVar(V_PROJECTOR_LOCATION, 2); + */ + //setGlobalVar(V_ENTRANCE_OPEN, 0); + //setGlobalVar(V_DOOR_SPIKES_OPEN, 1); + //setGlobalVar(V_CREATURE_ANGRY, 1); + setGlobalVar(V_RADIO_ENABLED, 1); + //setGlobalVar(V_TNT_DUMMY_BUILT, 1); + setGlobalVar(V_FLYTRAP_RING_DOOR, 1); + setGlobalVar(V_TV_JOKE_TOLD, 1); + /* + // Give all disks + for (int i = 0; i < 20; i++) + setSubVar(VA_IS_TAPE_INSERTED, i, 1); + */ + setSubVar(VA_IS_KEY_INSERTED, 0, 1); + setSubVar(VA_IS_KEY_INSERTED, 1, 1); + setSubVar(VA_IS_KEY_INSERTED, 2, 1); + for (uint32 index = 0; index < 9; index++) + setSubVar(VA_CUBE_POSITIONS, index, 7 - index); + setGlobalVar(V_WALL_BROKEN, 0); + setGlobalVar(V_WORLDS_JOINED, 1); + setGlobalVar(V_RADIO_MOVE_DISH_VIDEO, 0); + // Enable all locations + for (int i = 0; i < 6; i++) + setSubVar(V_TELEPORTER_DEST_AVAILABLE, i, 1); + //setGlobalVar(V_PROJECTOR_LOCATION, 4); + setGlobalVar(V_KEYDOOR_UNLOCKED, 1); + setGlobalVar(V_LIGHTS_ON, 1); + setGlobalVar(V_WATER_RUNNING, 1); + setGlobalVar(V_HAS_TEST_TUBE, 1); + setSubVar(VA_CURR_WATER_PIPES_LEVEL, 0, 3); + setSubVar(VA_CURR_WATER_PIPES_LEVEL, 1, 1); + setSubVar(VA_CURR_WATER_PIPES_LEVEL, 2, 2); + setSubVar(VA_CURR_WATER_PIPES_LEVEL, 3, 0); + setSubVar(VA_CURR_WATER_PIPES_LEVEL, 4, 4); + setGlobalVar(V_KLAYMEN_SMALL, 1); + setGlobalVar(V_SHRINK_LIGHTS_ON, 0); + // <<<DEBUG + +#if 1 + _vm->gameState().which = 0; + _vm->gameState().sceneNum = 0; + createModule(2400, 0); +#endif + +#endif +} + +void GameModule::requestRestoreGame() { + _restoreGameRequested = true; +} + +void GameModule::requestRestartGame(bool requestMainMenu) { + _restartGameRequested = true; + _mainMenuRequested = requestMainMenu; +} + +void GameModule::redrawPrevChildObject() { + if (_prevChildObject) { + _prevChildObject->draw(); + _vm->_screen->update(); + } +} + +void GameModule::checkRequests() { + if (_restartGameRequested) { + _restartGameRequested = false; + _vm->_gameVars->clear(); + requestRestoreGame(); + } + if (_restoreGameRequested) { + _restoreGameRequested = false; + delete _childObject; + delete _prevChildObject; + _childObject = NULL; + _prevChildObject = NULL; + _prevModuleNum = 0; + createModuleByHash(getGlobalVar(V_MODULE_NAME)); + } + if (_mainMenuRequested) + openMainMenu(); +} + +void GameModule::createModule(int moduleNum, int which) { + debug("GameModule::createModule(%d, %d)", moduleNum, which); + _moduleNum = moduleNum; + switch (_moduleNum) { + case 1000: + setGlobalVar(V_MODULE_NAME, 0x03294419); + _childObject = new Module1000(_vm, this, which); + break; + case 1100: + setGlobalVar(V_MODULE_NAME, 0x0002C818); + _childObject = new Module1100(_vm, this, which); + break; + case 1200: + setGlobalVar(V_MODULE_NAME, 0x00478311); + _childObject = new Module1200(_vm, this, which); + break; + case 1300: + setGlobalVar(V_MODULE_NAME, 0x0061C090); + _childObject = new Module1300(_vm, this, which); + break; + case 1400: + setGlobalVar(V_MODULE_NAME, 0x00AD0012); + _childObject = new Module1400(_vm, this, which); + break; + case 1500: + _canRequestMainMenu = false; + setGlobalVar(V_MODULE_NAME, 0x00F10114); + _childObject = new Module1500(_vm, this, which); + break; + case 1600: + setGlobalVar(V_MODULE_NAME, 0x01A008D8); + _childObject = new Module1600(_vm, this, which); + break; + case 1700: + setGlobalVar(V_MODULE_NAME, 0x04212331); + _childObject = new Module1700(_vm, this, which); + break; + case 1800: + setGlobalVar(V_MODULE_NAME, 0x04A14718); + _childObject = new Module1800(_vm, this, which); + break; + case 1900: + setGlobalVar(V_MODULE_NAME, 0x04E1C09C); + _childObject = new Module1900(_vm, this, which); + break; + case 2000: + setGlobalVar(V_MODULE_NAME, 0x08250000); + _childObject = new Module2000(_vm, this, which); + break; + case 2100: + setGlobalVar(V_MODULE_NAME, 0x10A10C14); + _childObject = new Module2100(_vm, this, which); + break; + case 2200: + setGlobalVar(V_MODULE_NAME, 0x11391412); + _childObject = new Module2200(_vm, this, which); + break; + case 2300: + setGlobalVar(V_MODULE_NAME, 0x1A214010); + _childObject = new Module2300(_vm, this, which); + break; + case 2400: + setGlobalVar(V_MODULE_NAME, 0x202D1010); + _childObject = new Module2400(_vm, this, which); + break; + case 2500: + setGlobalVar(V_MODULE_NAME, 0x29220120); + _childObject = new Module2500(_vm, this, which); + break; + case 2600: + setGlobalVar(V_MODULE_NAME, 0x40271018); + _childObject = new Module2600(_vm, this, which); + break; + case 2700: + setGlobalVar(V_MODULE_NAME, 0x42212411); + _childObject = new Module2700(_vm, this, which); + break; + case 2800: + setGlobalVar(V_MODULE_NAME, 0x64210814); + _childObject = new Module2800(_vm, this, which); + break; + case 2900: + setGlobalVar(V_MODULE_NAME, 0x81100020); + if (which >= 0) + setGlobalVar(V_TELEPORTER_CURR_LOCATION, which); + _childObject = new Module2900(_vm, this, which); + break; + case 3000: + setGlobalVar(V_MODULE_NAME, 0x81293110); + _childObject = new Module3000(_vm, this, which); + break; + case 9999: + createDemoScene(); + break; + default: + error("GameModule::createModule() Could not create module %d", moduleNum); + } + SetUpdateHandler(&GameModule::updateModule); + _childObject->handleUpdate(); +} + +void GameModule::createModuleByHash(uint32 nameHash) { + debug("GameModule::createModuleByHash(%08X)", nameHash); + switch (nameHash) { + case 0x03294419: + createModule(1000, -1); + break; + case 0x0002C818: + createModule(1100, -1); + break; + case 0x00478311: + createModule(1200, -1); + break; + case 0x0061C090: + createModule(1300, -1); + break; + case 0x00AD0012: + createModule(1400, -1); + break; + case 0x00F10114: + createModule(1500, -1); + break; + case 0x01A008D8: + createModule(1600, -1); + break; + case 0x04212331: + createModule(1700, -1); + break; + case 0x04A14718: + createModule(1800, -1); + break; + case 0x04E1C09C: + createModule(1900, -1); + break; + case 0x08250000: + createModule(2000, -1); + break; + case 0x10A10C14: + createModule(2100, -1); + break; + case 0x11391412: + createModule(2200, -1); + break; + case 0x1A214010: + createModule(2300, -1); + break; + case 0x202D1010: + createModule(2400, -1); + break; + case 0x29220120: + createModule(2500, -1); + break; + case 0x40271018: + createModule(2600, -1); + break; + case 0x42212411: + createModule(2700, -1); + break; + case 0x64210814: + createModule(2800, -1); + break; + case 0x81100020: + createModule(2900, -1); + break; + case 0x81293110: + createModule(3000, -1); + break; + default: + createModule(1000, 0); + break; + } +} + +void GameModule::updateModule() { + if (!updateChild()) { + switch (_moduleNum) { + case 1000: + createModule(2300, 0); + break; + case 1200: + if (_moduleResult == 1) + createModule(2600, 0); + else + createModule(2300, 2); + break; + case 1100: + if (_moduleResult == 0) + createModule(2900, 2); + else { + setGlobalVar(V_ENTRANCE_OPEN, 1); + createModule(1300, 0); + } + break; + case 1300: + if (_moduleResult == 1) { + // The game was successfully finished + requestRestartGame(true); + } else + createModule(2900, 0); + break; + case 1400: + createModule(1600, 1); + break; + case 1500: + createModule(1000, 0); + break; + case 1600: + if (_moduleResult == 1) + createModule(1400, 0); + else if (_moduleResult == 2) + createModule(1700, 0); + else + createModule(2100, 0); + break; + case 1700: + if (_moduleResult == 1) + createModule(2900, 3); + else + createModule(1600, 2); + break; + case 1800: + if (_moduleResult == 1) { + // Game over, Klaymen jumped into the hole + requestRestartGame(true); + } else if (_moduleResult == 2) + createModule(2700, 0); + else if (_moduleResult == 3) + createModule(3000, 3); + else + createModule(2800, 0); + break; + case 1900: + createModule(3000, 1); + break; + case 2000: + createModule(2900, 4); + break; + case 2100: + if (_moduleResult == 1) + createModule(2900, 1); + else + createModule(1600, 0); + break; + case 2200: + createModule(2300, 1); + break; + case 2300: + debug("module 23000 _moduleResult : %d", _moduleResult); + if (_moduleResult == 2) + createModule(1200, 0); + else if (_moduleResult == 0) + createModule(1000, 1); + else if (_vm->isDemo()) + createModule(9999, -1); + else if (_moduleResult == 1) + createModule(2200, 0); + else if (_moduleResult == 3) + createModule(2400, 0); + else if (_moduleResult == 4) + createModule(3000, 0); + break; + case 2400: + createModule(2300, 3); + break; + case 2500: + createModule(2600, 1); + break; + case 2600: + if (_moduleResult == 1) + createModule(2500, 0); + else + createModule(1200, 1); + break; + case 2700: + createModule(1800, 2); + break; + case 2800: + if (_moduleResult == 1) + createModule(2900, 5); + else + createModule(1800, 0); + break; + case 2900: + if (_moduleResult != (uint32)-1) { + switch (_moduleResult) { + case 0: + createModule(1300, 5); + break; + case 1: + createModule(2100, 1); + break; + case 2: + createModule(1100, 1); + break; + case 3: + setSubVar(V_TELEPORTER_DEST_AVAILABLE, 2, 1); + createModule(1700, 1); + break; + case 4: + createModule(2000, 0); + break; + case 5: + default: + createModule(2800, 1); + break; + } + } else { + switch (getGlobalVar(V_TELEPORTER_CURR_LOCATION)) { + case 0: + createModule(1300, 6); + break; + case 1: + createModule(2100, 2); + break; + case 2: + createModule(1100, 2); + break; + case 3: + createModule(1700, 2); + break; + case 4: + createModule(2000, 1); + break; + case 5: + default: + createModule(2800, 2); + break; + } + } + setGlobalVar(V_TELEPORTER_CURR_LOCATION, 0); + break; + case 3000: + // NOTE _moduleResult 2 never used + // NOTE Check if _moduleResult 4 is used + if (_moduleResult == 1) + createModule(1900, 0); + else if (_moduleResult == 3) + createModule(1800, 3); + else if (_moduleResult == 4) + createModule(3000, 0); + else + createModule(2300, 4); + break; + case 9999: + createModuleByHash(getGlobalVar(V_MODULE_NAME)); + break; + } + } +} + +void GameModule::openMainMenu() { + if (_childObject) { + sendMessage(_childObject, 0x101D, 0); + _childObject->draw(); + } else { + // If there's no module, create one so there's something to return to + createModule(1000, 0); + } + _vm->_screen->saveParams(); + _mainMenuRequested = false; + createMenuModule(); +} + +void GameModule::createMenuModule() { + if (!_prevChildObject) { + _prevChildObject = _childObject; + _prevModuleNum = _moduleNum; + _childObject = new MenuModule(_vm, this, 0); + _childObject->handleUpdate(); + SetUpdateHandler(&GameModule::updateMenuModule); + } +} + +void GameModule::updateMenuModule() { + if (!updateChild()) { + _vm->_screen->restoreParams(); + _childObject = _prevChildObject; + sendMessage(_childObject, 0x101E, 0); + _prevChildObject = NULL; + _moduleNum = _prevModuleNum; + SetUpdateHandler(&GameModule::updateModule); + } +} + +NonRepeatingRandomNumbers::NonRepeatingRandomNumbers(Common::RandomSource *rnd, int count) + : _rnd(rnd) { + for (int i = 0; i < count; i++) + push_back(i); +} + +int NonRepeatingRandomNumbers::getNumber() { + int number; + if (!empty()) { + uint index = _rnd->getRandomNumber(size() - 1); + number = (*this)[index]; + remove_at(index); + } else + number = 0; + return number; +} + +void NonRepeatingRandomNumbers::removeNumber(int number) { + for (uint i = 0; i < size(); ++i) + if ((*this)[i] == number) { + remove_at(i); + break; + } +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/gamemodule.h b/engines/neverhood/gamemodule.h new file mode 100644 index 0000000000..8101d38009 --- /dev/null +++ b/engines/neverhood/gamemodule.h @@ -0,0 +1,86 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_GAMEMODULE_H +#define NEVERHOOD_GAMEMODULE_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" + +namespace Neverhood { + +class GameModule : public Module { +public: + GameModule(NeverhoodEngine *vm); + virtual ~GameModule(); + void startup(); + void requestRestoreGame(); + void requestRestartGame(bool requestMainMenu); + void redrawPrevChildObject(); + void checkRequests(); + void handleMouseMove(int16 x, int16 y); + void handleMouseDown(int16 x, int16 y); + void handleMouseUp(int16 x, int16 y); + void handleSpaceKey(); + void handleAsciiKey(char key); + void handleKeyDown(Common::KeyCode keyCode); + void handleEscapeKey(); + void initKeySlotsPuzzle(); + void initMemoryPuzzle(); + void initWaterPipesPuzzle(); + void initRadioPuzzle(); + void initTestTubes1Puzzle(); + void initTestTubes2Puzzle(); + void initCannonSymbolsPuzzle(); + void initCodeSymbolsPuzzle(); + void initCubeSymbolsPuzzle(); + void initCrystalColorsPuzzle(); + uint32 getCurrRadioMusicFileHash(); +protected: + int _moduleNum; + Entity *_prevChildObject; + int _prevModuleNum; + bool _restoreGameRequested; + bool _restartGameRequested; + bool _canRequestMainMenu; + bool _mainMenuRequested; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void createModule(int moduleNum, int which); + void createModuleByHash(uint32 nameHash); + void updateModule(); + void openMainMenu(); + void createMenuModule(); + void updateMenuModule(); +}; + +class NonRepeatingRandomNumbers : public Common::Array<int> { +public: + NonRepeatingRandomNumbers(Common::RandomSource *rnd, int count); + int getNumber(); + void removeNumber(int number); +protected: + Common::RandomSource *_rnd; +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULE_H */ diff --git a/engines/neverhood/gamevars.cpp b/engines/neverhood/gamevars.cpp new file mode 100644 index 0000000000..87f5fe6dd9 --- /dev/null +++ b/engines/neverhood/gamevars.cpp @@ -0,0 +1,133 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/gamevars.h" + +namespace Neverhood { + +GameVars::GameVars() { + addVar(0, 0); +} + +void GameVars::clear() { + _vars.clear(); + addVar(0, 0); +} + +void GameVars::loadState(Common::InSaveFile *in) { + uint varCount; + _vars.clear(); + varCount = in->readUint32LE(); + for (uint i = 0; i < varCount; ++i) { + GameVar var; + var.nameHash = in->readUint32LE(); + var.value = in->readUint32LE(); + var.firstIndex = in->readUint16LE(); + var.nextIndex = in->readUint16LE(); + _vars.push_back(var); + } +} + +void GameVars::saveState(Common::OutSaveFile *out) { + out->writeUint32LE(_vars.size()); + for (uint i = 0; i < _vars.size(); ++i) { + GameVar &var = _vars[i]; + out->writeUint32LE(var.nameHash); + out->writeUint32LE(var.value); + out->writeUint16LE(var.firstIndex); + out->writeUint16LE(var.nextIndex); + } +} + +uint32 GameVars::getGlobalVar(uint32 nameHash) { + int16 varIndex = findSubVarIndex(0, nameHash); + return varIndex != -1 ? _vars[varIndex].value : 0; +} + +void GameVars::setGlobalVar(uint32 nameHash, uint32 value) { + _vars[getSubVarIndex(0, nameHash)].value = value; +} + +uint32 GameVars::getSubVar(uint32 nameHash, uint32 subNameHash) { + uint32 value = 0; + int16 varIndex = findSubVarIndex(0, nameHash); + if (varIndex != -1) { + int16 subVarIndex = findSubVarIndex(varIndex, subNameHash); + if (subVarIndex != -1) + value = _vars[subVarIndex].value; + } + return value; +} + +void GameVars::setSubVar(uint32 nameHash, uint32 subNameHash, uint32 value) { + int16 varIndex = getSubVarIndex(0, nameHash); + int16 subVarIndex = getSubVarIndex(varIndex, subNameHash); + _vars[subVarIndex].value = value; +} + +int16 GameVars::addVar(uint32 nameHash, uint32 value) { + GameVar gameVar; + gameVar.nameHash = nameHash; + gameVar.value = value; + gameVar.firstIndex = -1; + gameVar.nextIndex = -1; + _vars.push_back(gameVar); + return _vars.size() - 1; +} + +int16 GameVars::findSubVarIndex(int16 varIndex, uint32 subNameHash) { + for (int16 nextIndex = _vars[varIndex].firstIndex; nextIndex != -1; nextIndex = _vars[nextIndex].nextIndex) + if (_vars[nextIndex].nameHash == subNameHash) + return nextIndex; + return -1; +} + +int16 GameVars::addSubVar(int16 varIndex, uint32 subNameHash, uint32 value) { + int16 nextIndex = _vars[varIndex].firstIndex; + int16 subVarIndex; + if (nextIndex == -1) { + subVarIndex = addVar(subNameHash, value); + _vars[varIndex].firstIndex = subVarIndex; + } else { + while (_vars[nextIndex].nextIndex != -1) + nextIndex = _vars[nextIndex].nextIndex; + subVarIndex = addVar(subNameHash, value); + _vars[nextIndex].nextIndex = subVarIndex; + } + return subVarIndex; +} + +int16 GameVars::getSubVarIndex(int16 varIndex, uint32 subNameHash) { + int16 subVarIndex = findSubVarIndex(varIndex, subNameHash); + if (subVarIndex == -1) + subVarIndex = addSubVar(varIndex, subNameHash, 0); + return subVarIndex; +} + +void GameVars::dumpVars() { + for (Common::Array<GameVar>::iterator it = _vars.begin(); it != _vars.end(); ++it) { + GameVar gameVar = *it; + debug("%08X %08X %3d %3d", gameVar.nameHash, gameVar.value, gameVar.firstIndex, gameVar.nextIndex); + } +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/gamevars.h b/engines/neverhood/gamevars.h new file mode 100644 index 0000000000..5337c13394 --- /dev/null +++ b/engines/neverhood/gamevars.h @@ -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. + * + */ + +#ifndef NEVERHOOD_GAMEVARS_H +#define NEVERHOOD_GAMEVARS_H + +#include "common/array.h" +#include "common/savefile.h" +#include "neverhood/neverhood.h" + +namespace Neverhood { + +enum { + // Misc + V_MODULE_NAME = 0x91080831, // Currently active module name hash + V_CURRENT_SCENE = 0x108A4870, // Current scene in the current module + V_CURRENT_SCENE_WHICH = 0x82C80875, + V_DEBUG = 0xA4014072, // Original debug-flag, can probably be removed + V_SMACKER_CAN_ABORT = 0x06C02850, // Not set anywhere (yet), seems like a debug flag + V_KEY3_LOCATION = 0x13382860, // Location of the third key + V_TEXT_FLAG1 = 0x8440001F, + V_TEXT_INDEX = 0x01830201, + V_TEXT_COUNTING_INDEX1 = 0x29408F00, + V_TEXT_COUNTING_INDEX2 = 0x8A140C21, + V_TALK_COUNTING_INDEX = 0xA0808898, + V_FRUIT_COUNTING_INDEX = 0x40040831, + V_NOISY_SYMBOL_INDEX = 0x2414C2F2, + V_COLUMN_BACK_NAME = 0x4CE79018, + V_COLUMN_TEXT_NAME = 0xC8C28808, + V_CLICKED_COLUMN_INDEX = 0x48A68852, + V_CLICKED_COLUMN_ROW = 0x49C40058, + // Klaymen + V_KLAYMEN_SMALL = 0x1860C990, // Is Klaymen small? + V_KLAYMEN_FRAMEINDEX = 0x18288913, + V_KLAYMEN_IS_DELTA_X = 0xC0418A02, + V_KLAYMEN_SAVED_X = 0x00D30138, + V_CAR_DELTA_X = 0x21E60190, + // Flags + V_CRYSTAL_COLORS_INIT = 0xDE2EC914, + V_TV_JOKE_TOLD = 0x92603A79, + V_NOTES_DOOR_UNLOCKED = 0x0045D021, + V_WATER_RUNNING = 0x4E0BE910, + V_CREATURE_ANGRY = 0x0A310817, // After having played with the music box + V_BEEN_SHRINKING_ROOM = 0x1C1B8A9A, + V_BEEN_STATUE_ROOM = 0xCB45DE03, + V_MOUSE_PUZZLE_SOLVED = 0x70A1189C, + V_NOTES_PUZZLE_SOLVED = 0x86615030, + V_TILE_PUZZLE_SOLVED = 0x404290D5, + V_STAIRS_PUZZLE_SOLVED = 0xA9035F60, + V_CODE_SYMBOLS_SOLVED = 0x2C531AF8, + V_SPIKES_RETRACTED = 0x18890C91, + V_LARGE_DOOR_NUMBER = 0x9A500914, // Number of the currently "large" door + V_LIGHTS_ON = 0x4D080E54, + V_SHRINK_LIGHTS_ON = 0x190A1D18, // Lights on in the room with the shrinking device + V_STAIRS_DOWN_ONCE = 0x2050861A, // Stairs have been down once before + V_STAIRS_DOWN = 0x09221A62, + V_LADDER_DOWN = 0x0018CA22, // Is the ladder in the statue room down? + V_LADDER_DOWN_ACTION = 0x00188211, + V_WALL_BROKEN = 0x10938830, + V_BOLT_DOOR_OPEN = 0x01BA1A52, + V_BOLT_DOOR_UNLOCKED = 0x00040153, + V_SEEN_SYMBOLS_NO_LIGHT = 0x81890D14, + V_FELL_DOWN_HOLE = 0xE7498218, + V_DOOR_PASSED = 0x2090590C, // Auto-closing door was passed + V_ENTRANCE_OPEN = 0xD0A14D10, // Is the entrance to Module1300 open (after the robot got his teddy) + V_WINDOW_OPEN = 0x03C698DA, + V_DOOR_STATUS = 0x52371C95, + V_DOOR_BUSTED = 0xD217189D, + V_WORLDS_JOINED = 0x98109F12, // Are the worlds joined? + V_KEYDOOR_UNLOCKED = 0x80455A41, // Is the keyboard-door unlocked? + V_MOUSE_SUCKED_IN = 0x01023818, // Are mouse/cheese in Scene1308? + V_BALLOON_POPPED = 0xAC00C0D0, // Has the balloon with the key been popped? + V_TNT_DUMMY_BUILT = 0x000CF819, // Are all TNT parts on the dummy? + V_TNT_DUMMY_FUSE_LIT = 0x20A0C516, + V_RADIO_ENABLED = 0x4DE80AC0, + V_SEEN_CREATURE_EXPLODE_VID = 0x2A02C07B, + V_CREATURE_EXPLODED = 0x0A18CA33, + V_UNUSED = 0x89C669AA, // Seems to be unused, confirmed by checking the exe for this constant value (still left in atm) + // Radio + V_RADIO_ROOM_LEFT_DOOR = 0x09880D40, + V_RADIO_ROOM_RIGHT_DOOR = 0x08180ABC, + V_CURR_RADIO_MUSIC_INDEX = 0x08CC0828, + V_GOOD_RADIO_MUSIC_INDEX = 0x88880915, + V_GOOD_RADIO_MUSIC_NAME = 0x89A82A15, + V_RADIO_MOVE_DISH_VIDEO = 0x28D8C940, + // Match + V_MATCH_STATUS = 0x0112090A, + // Venus fly trap + V_FLYTRAP_RING_EATEN = 0x2B514304, + V_FLYTRAP_RING_DOOR = 0x8306F218, + V_FLYTRAP_RING_FENCE = 0x80101B1E, + V_FLYTRAP_RING_BRIDGE = 0x13206309, + V_FLYTRAP_POSITION_1 = 0x1B144052, + V_FLYTRAP_POSITION_2 = 0x86341E88, + // Navigation + V_NAVIGATION_INDEX = 0x4200189E, // Navigation scene: Current navigation index + // Cannon + V_CANNON_RAISED = 0x000809C2, // Is the cannon raised? + V_CANNON_TURNED = 0x9040018A, // Is the cannon turned? + V_ROBOT_HIT = 0x0C0288F4, // Was the robot hit by the cannon? + V_ROBOT_TARGET = 0x610210B7, // Is the robot at the cannon target position? (teddy) + V_CANNON_SMACKER_NAME = 0xF0402B0A, + V_CANNON_TARGET_STATUS = 0x20580A86, + // Projector + V_PROJECTOR_SLOT = 0x04A10F33, // Projector x slot index + V_PROJECTOR_LOCATION = 0x04A105B3, // Projector scene location + V_PROJECTOR_ACTIVE = 0x12A10DB3, // Is the projecor projecting? + // Teleporter + V_TELEPORTER_CURR_LOCATION = 0x0152899A, + V_TELEPORTER_WHICH = 0x60826830, + V_TELEPORTER_DEST_AVAILABLE = 0x2C145A98, + // Inventory + V_HAS_NEEDLE = 0x31C63C51, // Has Klaymen the needle? + V_HAS_FINAL_KEY = 0xC0780812, // Has Klaymen the key from the diskplayer? + V_HAS_TEST_TUBE = 0x45080C38, + // Arrays + // NOTE "GOOD" means the solution, "CURR" is the current setup of the puzzle variables + VA_IS_PUZZLE_INIT = 0x40050052, + VA_SMACKER_PLAYED = 0x00800410, + VA_CURR_CRYSTAL_COLORS = 0xE11A1929, + VA_GOOD_CRYSTAL_COLORS = 0xD4B2089C, + VA_GOOD_TEST_TUBES_LEVEL_1 = 0x0C601058, + VA_GOOD_TEST_TUBES_LEVEL_2 = 0x40005834, + VA_CURR_CANNON_SYMBOLS = 0x00000914, + VA_GOOD_CANNON_SYMBOLS_1 = 0x00504B86, + VA_GOOD_CANNON_SYMBOLS_2 = 0x0A4C0A9A, + VA_CURR_WATER_PIPES_LEVEL = 0x0800547C, + VA_GOOD_WATER_PIPES_LEVEL = 0x90405038, + VA_CURR_DICE_NUMBERS = 0x61084036, + VA_GOOD_DICE_NUMBERS = 0x7500993A, + VA_CURR_KEY_SLOT_NUMBERS = 0xA010B810, + VA_GOOD_KEY_SLOT_NUMBERS = 0x0C10A000, + VA_CUBE_POSITIONS = 0x484498D0, + VA_CODE_SYMBOLS = 0x04909A50, + VA_TILE_SYMBOLS = 0x0C65F80B, + VA_IS_TILE_MATCH = 0xCCE0280F, + VA_TNT_POSITIONS = 0x10055D14, + VA_DICE_MEMORY_SYMBOLS = 0x13100631, + VA_HAS_TAPE = 0x02038314, + VA_IS_TAPE_INSERTED = 0x02720344, + VA_HAS_KEY = 0x0090EA95, + VA_IS_KEY_INSERTED = 0x08D0AB11, + VA_LOCKS_DISABLED = 0x14800353, + V_END_ +}; + +struct GameVar { + uint32 nameHash; + uint32 value; + int16 firstIndex, nextIndex; +}; + +class GameVars { +public: + GameVars(); + void clear(); + void loadState(Common::InSaveFile *in); + void saveState(Common::OutSaveFile *out); + uint32 getGlobalVar(uint32 nameHash); + void setGlobalVar(uint32 nameHash, uint32 value); + uint32 getSubVar(uint32 nameHash, uint32 subNameHash); + void setSubVar(uint32 nameHash, uint32 subNameHash, uint32 value); + void dumpVars(); +protected: + Common::Array<GameVar> _vars; + int16 addVar(uint32 nameHash, uint32 value); + int16 findSubVarIndex(int16 varIndex, uint32 subNameHash); + int16 addSubVar(int16 varIndex, uint32 subNameHash, uint32 value); + int16 getSubVarIndex(int16 varIndex, uint32 subNameHash); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_GAMEVARS_H */ diff --git a/engines/neverhood/graphics.cpp b/engines/neverhood/graphics.cpp new file mode 100644 index 0000000000..5099c7a00e --- /dev/null +++ b/engines/neverhood/graphics.cpp @@ -0,0 +1,349 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/graphics.h" +#include "neverhood/resource.h" +#include "neverhood/screen.h" + +namespace Neverhood { + +BaseSurface::BaseSurface(NeverhoodEngine *vm, int priority, int16 width, int16 height) + : _vm(vm), _priority(priority), _visible(true), _transparent(true), + _clipRects(NULL), _clipRectsCount(0), _version(0) { + + _drawRect.x = 0; + _drawRect.y = 0; + _drawRect.width = width; + _drawRect.height = height; + _sysRect.x = 0; + _sysRect.y = 0; + _sysRect.width = (width + 3) & 0xFFFC; // align by 4 bytes + _sysRect.height = height; + _clipRect.x1 = 0; + _clipRect.y1 = 0; + _clipRect.x2 = 640; + _clipRect.y2 = 480; + _surface = new Graphics::Surface(); + _surface->create(_sysRect.width, _sysRect.height, Graphics::PixelFormat::createFormatCLUT8()); +} + +BaseSurface::~BaseSurface() { + delete _surface; +} + +void BaseSurface::draw() { + if (_surface && _visible && _drawRect.width > 0 && _drawRect.height > 0) { + if (_clipRects && _clipRectsCount) { + _vm->_screen->drawSurfaceClipRects(_surface, _drawRect, _clipRects, _clipRectsCount, _transparent, _version); + } else if (_sysRect.x == 0 && _sysRect.y == 0) { + _vm->_screen->drawSurface2(_surface, _drawRect, _clipRect, _transparent, _version); + } else { + _vm->_screen->drawUnk(_surface, _drawRect, _sysRect, _clipRect, _transparent, _version); + } + } +} + +void BaseSurface::clear() { + _surface->fillRect(Common::Rect(0, 0, _surface->w, _surface->h), 0); + ++_version; +} + +void BaseSurface::drawSpriteResource(SpriteResource &spriteResource) { + if (spriteResource.getDimensions().width <= _drawRect.width && + spriteResource.getDimensions().height <= _drawRect.height) { + clear(); + spriteResource.draw(_surface, false, false); + ++_version; + } +} + +void BaseSurface::drawSpriteResourceEx(SpriteResource &spriteResource, bool flipX, bool flipY, int16 width, int16 height) { + if (spriteResource.getDimensions().width <= _sysRect.width && + spriteResource.getDimensions().height <= _sysRect.height) { + if (width > 0 && width <= _sysRect.width) + _drawRect.width = width; + if (height > 0 && height <= _sysRect.height) + _drawRect.height = height; + if (_surface) { + clear(); + spriteResource.draw(_surface, flipX, flipY); + ++_version; + } + } +} + +void BaseSurface::drawAnimResource(AnimResource &animResource, uint frameIndex, bool flipX, bool flipY, int16 width, int16 height) { + if (width > 0 && width <= _sysRect.width) + _drawRect.width = width; + if (height > 0 && height <= _sysRect.height) + _drawRect.height = height; + if (_surface) { + clear(); + if (frameIndex < animResource.getFrameCount()) { + animResource.draw(frameIndex, _surface, flipX, flipY); + ++_version; + } + } +} + +void BaseSurface::drawMouseCursorResource(MouseCursorResource &mouseCursorResource, int frameNum) { + if (frameNum < 3) { + mouseCursorResource.draw(frameNum, _surface); + ++_version; + } +} + +void BaseSurface::copyFrom(Graphics::Surface *sourceSurface, int16 x, int16 y, NDrawRect &sourceRect) { + // Copy a rectangle from sourceSurface, no clipping is performed, 0 is the transparent color + byte *source = (byte*)sourceSurface->getBasePtr(sourceRect.x, sourceRect.y); + byte *dest = (byte*)_surface->getBasePtr(x, y); + int height = sourceRect.height; + while (height--) { + for (int xc = 0; xc < sourceRect.width; xc++) + if (source[xc] != 0) + dest[xc] = source[xc]; + source += sourceSurface->pitch; + dest += _surface->pitch; + } + ++_version; +} + +// ShadowSurface + +ShadowSurface::ShadowSurface(NeverhoodEngine *vm, int priority, int16 width, int16 height, BaseSurface *shadowSurface) + : BaseSurface(vm, priority, width, height), _shadowSurface(shadowSurface) { + // Empty +} + +void ShadowSurface::draw() { + if (_surface && _visible && _drawRect.width > 0 && _drawRect.height > 0) { + _vm->_screen->drawSurface2(_surface, _drawRect, _clipRect, _transparent, _version, _shadowSurface->getSurface()); + } +} + +// FontSurface + +FontSurface::FontSurface(NeverhoodEngine *vm, NPointArray *tracking, uint charsPerRow, uint16 numRows, byte firstChar, uint16 charWidth, uint16 charHeight) + : BaseSurface(vm, 0, charWidth * charsPerRow, charHeight * numRows), _charsPerRow(charsPerRow), _numRows(numRows), + _firstChar(firstChar), _charWidth(charWidth), _charHeight(charHeight), _tracking(NULL) { + + _tracking = new NPointArray(); + *_tracking = *tracking; + +} + +FontSurface::FontSurface(NeverhoodEngine *vm, uint32 fileHash, uint charsPerRow, uint16 numRows, byte firstChar, uint16 charWidth, uint16 charHeight) + : BaseSurface(vm, 0, charWidth * charsPerRow, charHeight * numRows), _charsPerRow(charsPerRow), _numRows(numRows), + _firstChar(firstChar), _charWidth(charWidth), _charHeight(charHeight), _tracking(NULL) { + + SpriteResource fontSpriteResource(_vm); + fontSpriteResource.load(fileHash, true); + drawSpriteResourceEx(fontSpriteResource, false, false, 0, 0); +} + +FontSurface::~FontSurface() { + delete _tracking; +} + +void FontSurface::drawChar(BaseSurface *destSurface, int16 x, int16 y, byte chr) { + NDrawRect sourceRect; + chr -= _firstChar; + sourceRect.x = (chr % _charsPerRow) * _charWidth; + sourceRect.y = (chr / _charsPerRow) * _charHeight; + sourceRect.width = _charWidth; + sourceRect.height = _charHeight; + destSurface->copyFrom(_surface, x, y, sourceRect); +} + +void FontSurface::drawString(BaseSurface *destSurface, int16 x, int16 y, const byte *string, int stringLen) { + + if (stringLen < 0) + stringLen = strlen((const char*)string); + + for (; stringLen > 0; --stringLen, ++string) { + drawChar(destSurface, x, y, *string); + x += _tracking ? (*_tracking)[*string - _firstChar].x : _charWidth; + } + +} + +int16 FontSurface::getStringWidth(const byte *string, int stringLen) { + return string ? stringLen * _charWidth : 0; +} + +FontSurface *FontSurface::createFontSurface(NeverhoodEngine *vm, uint32 fileHash) { + FontSurface *fontSurface; + DataResource fontData(vm); + SpriteResource fontSprite(vm); + fontData.load(calcHash("asRecFont")); + uint16 numRows = fontData.getPoint(calcHash("meNumRows")).x; + uint16 firstChar = fontData.getPoint(calcHash("meFirstChar")).x; + uint16 charWidth = fontData.getPoint(calcHash("meCharWidth")).x; + uint16 charHeight = fontData.getPoint(calcHash("meCharHeight")).x; + NPointArray *tracking = fontData.getPointArray(calcHash("meTracking")); + fontSprite.load(fileHash, true); + fontSurface = new FontSurface(vm, tracking, 16, numRows, firstChar, charWidth, charHeight); + fontSurface->drawSpriteResourceEx(fontSprite, false, false, 0, 0); + return fontSurface; +} + +// Misc + +enum BitmapFlags { + BF_RLE = 1, + BF_HAS_DIMENSIONS = 2, + BF_HAS_POSITION = 4, + BF_HAS_PALETTE = 8, + BF_HAS_IMAGE = 16 +}; + +void parseBitmapResource(const byte *sprite, bool *rle, NDimensions *dimensions, NPoint *position, const byte **palette, const byte **pixels) { + + uint16 flags; + + flags = READ_LE_UINT16(sprite); + sprite += 2; + + if (rle) + *rle = flags & BF_RLE; + + if (flags & BF_HAS_DIMENSIONS) { + if (dimensions) { + dimensions->width = READ_LE_UINT16(sprite); + dimensions->height = READ_LE_UINT16(sprite + 2); + } + sprite += 4; + } else if (dimensions) { + dimensions->width = 1; + dimensions->height = 1; + } + + if (flags & BF_HAS_POSITION) { + if (position) { + position->x = READ_LE_UINT16(sprite); + position->y = READ_LE_UINT16(sprite + 2); + } + sprite += 4; + } else if (position) { + position->x = 0; + position->y = 0; + } + + if (flags & BF_HAS_PALETTE) { + if (palette) + *palette = sprite; + sprite += 1024; + } else if (palette) + *palette = NULL; + + if (flags & BF_HAS_IMAGE) { + if (pixels) + *pixels = sprite; + } else if (pixels) + *pixels = NULL; + +} + +void unpackSpriteRle(const byte *source, int width, int height, byte *dest, int destPitch, bool flipX, bool flipY, byte oldColor, byte newColor) { + + const bool replaceColors = oldColor != newColor; + + int16 rows, chunks; + int16 skip, copy; + + if (flipY) { + dest += destPitch * (height - 1); + destPitch = -destPitch; + } + + rows = READ_LE_UINT16(source); + chunks = READ_LE_UINT16(source + 2); + source += 4; + + do { + if (chunks == 0) { + dest += rows * destPitch; + } else { + while (rows-- > 0) { + uint16 rowChunks = chunks; + while (rowChunks-- > 0) { + skip = READ_LE_UINT16(source); + copy = READ_LE_UINT16(source + 2); + source += 4; + if (!flipX) { + memcpy(dest + skip, source, copy); + } else { + byte *flipDest = dest + width - skip - 1; + for (int xc = 0; xc < copy; xc++) { + *flipDest-- = source[xc]; + } + } + source += copy; + } + dest += destPitch; + if (replaceColors) + for (int xc = 0; xc < width; xc++) + if (dest[xc] == oldColor) + dest[xc] = newColor; + } + } + rows = READ_LE_UINT16(source); + chunks = READ_LE_UINT16(source + 2); + source += 4; + } while (rows > 0); + +} + +void unpackSpriteNormal(const byte *source, int width, int height, byte *dest, int destPitch, bool flipX, bool flipY) { + + const int sourcePitch = (width + 3) & 0xFFFC; + + if (flipY) { + dest += destPitch * (height - 1); + destPitch = -destPitch; + } + + if (!flipX) { + while (height-- > 0) { + memcpy(dest, source, width); + source += sourcePitch; + dest += destPitch; + } + } else { + while (height-- > 0) { + dest += width - 1; + for (int xc = 0; xc < width; xc++) + *dest-- = source[xc]; + source += sourcePitch; + dest += destPitch; + } + } + +} + +int calcDistance(int16 x1, int16 y1, int16 x2, int16 y2) { + const int16 deltaX = ABS(x1 - x2); + const int16 deltaY = ABS(y1 - y2); + return sqrt((double)(deltaX * deltaX + deltaY * deltaY)); +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/graphics.h b/engines/neverhood/graphics.h new file mode 100644 index 0000000000..a0ac1f09d5 --- /dev/null +++ b/engines/neverhood/graphics.h @@ -0,0 +1,159 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_GRAPHICS_H +#define NEVERHOOD_GRAPHICS_H + +#include "common/array.h" +#include "common/file.h" +#include "graphics/surface.h" +#include "neverhood/neverhood.h" + +namespace Neverhood { + +struct NPoint { + int16 x, y; +}; + +typedef Common::Array<NPoint> NPointArray; + +struct NDimensions { + int16 width, height; +}; + +struct NRect { + int16 x1, y1, x2, y2; + + NRect() : x1(0), y1(0), x2(0), y2(0) {} + + NRect(int16 x01, int16 y01, int16 x02, int16 y02) : x1(x01), y1(y01), x2(x02), y2(y02) {} + + void set(int16 x01, int16 y01, int16 x02, int16 y02) { + x1 = x01; + y1 = y01; + x2 = x02; + y2 = y02; + } + + bool contains(int16 x, int16 y) const { + return x >= x1 && x <= x2 && y >= y1 && y <= y2; + } + +}; + +typedef Common::Array<NRect> NRectArray; + +// TODO: Use Common::Rect +struct NDrawRect { + int16 x, y, width, height; + NDrawRect() : x(0), y(0), width(0), height(0) {} + NDrawRect(int16 x0, int16 y0, int16 width0, int16 height0) : x(x0), y(y0), width(width0), height(height0) {} + int16 x2() { return x + width; } + int16 y2() { return y + height; } + void set(int16 x0, int16 y0, int16 width0, int16 height0) { + x = x0; + y = y0; + width = width0; + height = height0; + } +}; + +class AnimResource; +class SpriteResource; +class MouseCursorResource; + +class BaseSurface { +public: + BaseSurface(NeverhoodEngine *vm, int priority, int16 width, int16 height); + virtual ~BaseSurface(); + virtual void draw(); + void clear(); + void drawSpriteResource(SpriteResource &spriteResource); + void drawSpriteResourceEx(SpriteResource &spriteResource, bool flipX, bool flipY, int16 width, int16 height); + void drawAnimResource(AnimResource &animResource, uint frameIndex, bool flipX, bool flipY, int16 width, int16 height); + void drawMouseCursorResource(MouseCursorResource &mouseCursorResource, int frameNum); + void copyFrom(Graphics::Surface *sourceSurface, int16 x, int16 y, NDrawRect &sourceRect); + int getPriority() const { return _priority; } + void setPriority(int priority) { _priority = priority; } + NDrawRect& getDrawRect() { return _drawRect; } + NDrawRect& getSysRect() { return _sysRect; } + NRect& getClipRect() { return _clipRect; } + void setClipRect(NRect clipRect) { _clipRect = clipRect; } + void setClipRects(NRect *clipRects, uint clipRectsCount) { _clipRects = clipRects; _clipRectsCount = clipRectsCount; } + void clearClipRects() { _clipRects = NULL; _clipRectsCount = 0; } + bool getVisible() const { return _visible; } + void setVisible(bool value) { _visible = value; } + void setTransparent(bool value) { _transparent = value; } + Graphics::Surface *getSurface() { return _surface; } +protected: + NeverhoodEngine *_vm; + int _priority; + bool _visible; + Graphics::Surface *_surface; + NDrawRect _drawRect; + NDrawRect _sysRect; + NRect _clipRect; + NRect *_clipRects; + uint _clipRectsCount; + bool _transparent; + // Version changes each time the pixels are touched in any way + byte _version; +}; + +class ShadowSurface : public BaseSurface { +public: + ShadowSurface(NeverhoodEngine *vm, int priority, int16 width, int16 height, BaseSurface *shadowSurface); + virtual void draw(); +protected: + BaseSurface *_shadowSurface; +}; + +class FontSurface : public BaseSurface { +public: + FontSurface(NeverhoodEngine *vm, NPointArray *tracking, uint charsPerRow, uint16 numRows, byte firstChar, uint16 charWidth, uint16 charHeight); + FontSurface(NeverhoodEngine *vm, uint32 fileHash, uint charsPerRow, uint16 numRows, byte firstChar, uint16 charWidth, uint16 charHeight); + virtual ~FontSurface(); + void drawChar(BaseSurface *destSurface, int16 x, int16 y, byte chr); + void drawString(BaseSurface *destSurface, int16 x, int16 y, const byte *string, int stringLen = -1); + int16 getStringWidth(const byte *string, int stringLen); + uint16 getCharWidth() const { return _charWidth; } + uint16 getCharHeight() const { return _charHeight; } + static FontSurface *createFontSurface(NeverhoodEngine *vm, uint32 fileHash); +protected: + uint _charsPerRow; + uint16 _numRows; + byte _firstChar; + uint16 _charWidth; + uint16 _charHeight; + NPointArray *_tracking; +}; + +// Misc + +void parseBitmapResource(const byte *sprite, bool *rle, NDimensions *dimensions, NPoint *position, const byte **palette, const byte **pixels); +void unpackSpriteRle(const byte *source, int width, int height, byte *dest, int destPitch, bool flipX, bool flipY, byte oldColor = 0, byte newColor = 0); +void unpackSpriteNormal(const byte *source, int width, int height, byte *dest, int destPitch, bool flipX, bool flipY); +int calcDistance(int16 x1, int16 y1, int16 x2, int16 y2); + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_GRAPHICS_H */ diff --git a/engines/neverhood/klaymen.cpp b/engines/neverhood/klaymen.cpp new file mode 100644 index 0000000000..06d606e18d --- /dev/null +++ b/engines/neverhood/klaymen.cpp @@ -0,0 +1,6141 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/klaymen.h" +#include "neverhood/resourceman.h" +#include "neverhood/scene.h" +#include "neverhood/staticdata.h" + +namespace Neverhood { + +static const KlaymenIdleTableItem klaymenIdleTable1[] = { + {1, kIdlePickEar}, + {1, kIdleSpinHead}, + {1, kIdleArms}, + {1, kIdleChest}, + {1, kIdleHeadOff} +}; + +static const KlaymenIdleTableItem klaymenIdleTable2[] = { + {1, kIdlePickEar}, + {1, kIdleSpinHead}, + {1, kIdleChest}, + {1, kIdleHeadOff} +}; + +static const KlaymenIdleTableItem klaymenIdleTable3[] = { + {1, kIdleTeleporterHands}, + {1, kIdleTeleporterHands2} +}; + +static const KlaymenIdleTableItem klaymenIdleTable4[] = { + {1, kIdleSpinHead}, + {1, kIdleChest}, + {1, kIdleHeadOff}, +}; + +static const KlaymenIdleTableItem klaymenIdleTable1002[] = { + {1, kIdlePickEar}, + {2, kIdleWonderAbout} +}; + +// Klaymen + +Klaymen::Klaymen(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, NRectArray *clipRects) + : AnimatedSprite(vm, 1000), _idleCounterMax(0), _idleCounter(0), _isMoveObjectRequested(false), _blinkCounterMax(0), + _isWalkingOpenDoorNotified(false), _spitOutCountdown(0), _tapesToInsert(0), _keysToInsert(0), _busyStatus(0), _acceptInput(true), + _attachedSprite(NULL), _isWalking(false), _actionStatus(1), _parentScene(parentScene), _isSneaking(false), _isLargeStep(false), + _doYHitIncr(false), _isLeverDown(false), _isSittingInTeleporter(false), _actionStatusChanged(false), _ladderStatus(0), _pathPoints(NULL), _soundFlag(false), + _idleTableNum(0), _otherSprite(NULL), _moveObjectCountdown(0), _readyToSpit(false), _walkResumeFrameIncr(0) { + + createSurface(1000, 320, 200); + _x = x; + _y = y; + _destX = x; + _destY = y; + _flags = 2; + setKlaymenIdleTable1(); + stTryStandIdle(); + SetUpdateHandler(&Klaymen::update); +} + +void Klaymen::xUpdate() { + // Empty +} + +uint32 Klaymen::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4818: + startWalkToX(_dataResource.getPoint(param.asInteger()).x, false); + break; + } + return 0; +} + +void Klaymen::update() { + AnimatedSprite::update(); + xUpdate(); +} + +void Klaymen::setKlaymenIdleTable(const KlaymenIdleTableItem *table, uint tableCount) { + _idleTable = table; + _idleTableCount = tableCount; + _idleTableTotalWeight = 0; + for (uint i = 0; i < tableCount; i++) + _idleTableTotalWeight += table[i].weight; +} + +void Klaymen::setKlaymenIdleTable1() { + setKlaymenIdleTable(klaymenIdleTable1, ARRAYSIZE(klaymenIdleTable1)); +} + +void Klaymen::setKlaymenIdleTable2() { + setKlaymenIdleTable(klaymenIdleTable2, ARRAYSIZE(klaymenIdleTable2)); +} + +void Klaymen::setKlaymenIdleTable3() { + setKlaymenIdleTable(klaymenIdleTable3, ARRAYSIZE(klaymenIdleTable3)); +} + +void Klaymen::stIdlePickEar() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0x5B20C814, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmIdlePickEar); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stStandAround); + FinalizeState(&Klaymen::evIdlePickEarDone); +} + +uint32 Klaymen::hmIdlePickEar(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x04DBC02C) { + playSound(0, 0x44528AA1); + } + break; + } + return messageResult; +} + +void Klaymen::evIdlePickEarDone() { + stopSound(0); +} + +void Klaymen::stIdleSpinHead() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0xD122C137, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmIdleSpinHead); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stStandAround); +} + +uint32 Klaymen::hmIdleSpinHead(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x808A0008) { + playSound(0, 0xD948A340); + } + break; + } + return messageResult; +} + +void Klaymen::stIdleArms() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0x543CD054, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmIdleArms); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stStandAround); + FinalizeState(&Klaymen::evIdleArmsDone); +} + +void Klaymen::evIdleArmsDone() { + stopSound(0); +} + +uint32 Klaymen::hmIdleArms(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x5A0F0104) { + playSound(0, 0x7970A100); + } else if (param.asInteger() == 0x9A9A0109) { + playSound(0, 0xD170CF04); + } else if (param.asInteger() == 0x989A2169) { + playSound(0, 0xD073CF14); + } + break; + } + return messageResult; +} + +void Klaymen::stIdleChest() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0x40A0C034, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmIdleChest); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stStandAround); +} + +uint32 Klaymen::hmIdleChest(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x0D2A0288) { + playSound(0, 0xD192A368); + } + break; + } + return messageResult; +} + +void Klaymen::stIdleHeadOff() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0x5120E137, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmIdleHeadOff); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stStandAround); +} + +uint32 Klaymen::hmIdleHeadOff(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0xC006000C) { + playSound(0, 0x9D406340); + } else if (param.asInteger() == 0x2E4A2940) { + playSound(0, 0x53A4A1D4); + } else if (param.asInteger() == 0xAA0A0860) { + playSound(0, 0x5BE0A3C6); + } else if (param.asInteger() == 0xC0180260) { + playSound(0, 0x5D418366); + } + break; + } + return messageResult; +} + +void Klaymen::stIdleWonderAbout() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0xD820A114, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stStandAround); +} + +void Klaymen::stSitIdleTeleporter() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x582EC138, 0, -1); + SetUpdateHandler(&Klaymen::upSitIdleTeleporter); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(NULL); + _idleCounter = 0; + _blinkCounter = 0; + _idleCounterMax = 8; + _blinkCounterMax = _vm->_rnd->getRandomNumber(64 - 1) + 24; +} + +void Klaymen::upSitIdleTeleporter() { + update(); + if (++_idleCounter >= _idleCounterMax) { + _idleCounter = 0; + if (_idleTable) { + int idleWeight = _vm->_rnd->getRandomNumber(_idleTableTotalWeight - 1); + for (uint i = 0; i < _idleTableCount; i++) { + if (idleWeight < _idleTable[i].weight) { + enterIdleAnimation(_idleTable[i].idleAnimation); + _idleCounterMax = _vm->_rnd->getRandomNumber(128 - 1) + 24; + break; + } + idleWeight -= _idleTable[i].weight; + } + } + } else if (++_blinkCounter >= _blinkCounterMax) { + _blinkCounter = 0; + _blinkCounterMax = _vm->_rnd->getRandomNumber(64 - 1) + 24; + stSitIdleTeleporterBlink(); + } +} + +void Klaymen::stSitIdleTeleporterBlink() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x5C24C018, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stSitIdleTeleporterBlinkSecond); +} + +void Klaymen::stSitIdleTeleporterBlinkSecond() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x5C24C018, 0, -1); + SetUpdateHandler(&Klaymen::upSitIdleTeleporter); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(NULL); +} + +void Klaymen::stPickUpNeedle() { + setDoDeltaX(_attachedSprite->getX() < _x ? 1 : 0); + if (!stStartAction(AnimationCallback(&Klaymen::stPickUpNeedle))) { + _busyStatus = 1; + _acceptInput = false; + startAnimation(0x1449C169, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmPickUpObject); + SetSpriteUpdate(NULL); + } +} + +void Klaymen::stPickUpTube() { + setDoDeltaX(_attachedSprite->getX() < _x ? 1 : 0); + if (!stStartAction(AnimationCallback(&Klaymen::stPickUpTube))) { + _busyStatus = 1; + _acceptInput = false; + startAnimation(0x0018C032, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmPickUpTube); + SetSpriteUpdate(NULL); + } +} + +uint32 Klaymen::hmPickUpTube(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0xC1380080) { + sendMessage(_attachedSprite, 0x4806, 0); + playSound(0, 0xC8004340); + } else if (param.asInteger() == 0x02B20220) { + playSound(0, 0xC5408620); + } else if (param.asInteger() == 0x03020231) { + playSound(0, 0xD4C08010); + } else if (param.asInteger() == 0x67221A03) { + playSound(0, 0x44051000); + } else if (param.asInteger() == 0x925A0C1E) { + playSound(0, 0x40E5884D); + } + break; + } + return messageResult; +} + +void Klaymen::stTurnToUseInTeleporter() { + _busyStatus = 0; + _acceptInput = false; + startAnimation(0xD229823D, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); +} + +void Klaymen::stReturnFromUseInTeleporter() { + _busyStatus = 0; + _acceptInput = false; + startAnimation(0x9A2801E0, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); +} + +void Klaymen::stStepOver() { + if (!stStartAction(AnimationCallback(&Klaymen::stStepOver))) { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x004AA310, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmStartWalking); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } +} + +void Klaymen::stSitInTeleporter() { + if (!stStartAction(AnimationCallback(&Klaymen::stSitInTeleporter))) { + _busyStatus = 0; + _acceptInput = false; + startAnimation(0x392A0330, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmSitInTeleporter); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } +} + +uint32 Klaymen::hmSitInTeleporter(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x001A2832) { + playSound(0, 0xC0E4884C); + } + break; + } + return messageResult; +} + +void Klaymen::stGetUpFromTeleporter() { + _busyStatus = 0; + _acceptInput = false; + startAnimation(0x913AB120, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(&Klaymen::suUpdateDestX); +} + +///////////////////////////////////////////////////////////////// + +void Klaymen::stopWalking() { + _destX = _x; + if (!_isWalking && !_isSneaking && !_isLargeStep) { + gotoState(NULL); + gotoNextStateExt(); + } +} + +void Klaymen::startIdleAnimation(uint32 fileHash, AnimationCb callback) { + debug("startIdleAnimation(%08X)", fileHash); + NextState(callback); + SetUpdateHandler(&Klaymen::upIdleAnimation); +} + +void Klaymen::upIdleAnimation() { + gotoNextStateExt(); + update(); +} + +bool Klaymen::stStartActionFromIdle(AnimationCb callback) { + if (_busyStatus == 2) { + _busyStatus = 1; + _acceptInput = false; + startAnimation(0x9A7020B8, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmStartAction); + SetSpriteUpdate(NULL); + NextState(callback); + return true; + } + return false; +} + +void Klaymen::gotoNextStateExt() { + if (_finalizeStateCb) { + AnimationCb cb = _finalizeStateCb; + _finalizeStateCb = NULL; + (this->*cb)(); + } + if (_nextStateCb) { + AnimationCb cb = _nextStateCb; + _nextStateCb = NULL; + (this->*cb)(); + } else { + // Inform the scene that the current Klaymen animation sequence has finished + sendMessage(_parentScene, 0x1006, 0); + } +} + +void Klaymen::beginAction() { + _actionStatusChanged = false; + _actionStatus = 1; +} + +void Klaymen::endAction() { + if (_actionStatusChanged) + _actionStatus = 0; +} + +void Klaymen::stTryStandIdle() { + if (!stStartActionFromIdle(AnimationCallback(&Klaymen::stTryStandIdle))) { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0x5420E254, 0, -1); + SetUpdateHandler(&Klaymen::upStandIdle); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(NULL); + _idleCounter = 0; + _blinkCounter = 0; + _blinkCounterMax = _vm->_rnd->getRandomNumber(64) + 24; + } +} + +void Klaymen::upStandIdle() { + update(); + if (++_idleCounter >= 720) { + _idleCounter = 0; + if (_idleTable) { + int idleWeight = _vm->_rnd->getRandomNumber(_idleTableTotalWeight - 1); + for (uint i = 0; i < _idleTableCount; i++) { + if (idleWeight < _idleTable[i].weight) { + enterIdleAnimation(_idleTable[i].idleAnimation); + break; + } + idleWeight -= _idleTable[i].weight; + } + } + } else if (++_blinkCounter >= _blinkCounterMax) { + _blinkCounter = 0; + _blinkCounterMax = _vm->_rnd->getRandomNumber(64 - 1) + 24; + stIdleBlink(); + } +} + +uint32 Klaymen::hmLowLevel(int messageNum, const MessageParam ¶m, Entity *sender) { + Sprite::handleMessage(messageNum, param, sender); + uint32 messageResult = xHandleMessage(messageNum, param); + switch (messageNum) { + case 0x1008: + messageResult = _acceptInput; + break; + case 0x1014: + _attachedSprite = (Sprite*)(param.asEntity()); + break; + case 0x1019: + gotoNextStateExt(); + break; + case 0x101C: + beginAction(); + break; + case 0x1021: + endAction(); + break; + case 0x481C: + _actionStatus = param.asInteger(); + _actionStatusChanged = true; + messageResult = 1; + break; + case 0x482C: + if (param.asInteger() != 0) { + _pathPoints = _dataResource.getPointArray(param.asInteger()); + } else { + _pathPoints = NULL; + } + break; + } + return messageResult; +} + +void Klaymen::stIdleBlink() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0x5900C41E, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stStandAround); +} + +uint32 Klaymen::hmLowLevelAnimation(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevel(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextStateExt(); + break; + } + return messageResult; +} + +void Klaymen::stStandAround() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0x5420E254, 0, -1); + SetUpdateHandler(&Klaymen::upStandIdle); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(NULL); +} + +uint32 Klaymen::hmStartAction(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x271AA210) { + playSound(0, 0x4924AAC4); + } else if (param.asInteger() == 0x2B22AA81) { + playSound(0, 0x0A2AA8E0); + } + break; + } + return messageResult; +} + + +void Klaymen::startWalkToX(int16 x, bool walkExt) { + int16 xdiff = ABS(x - _x); + if (x == _x) { + _destX = x; + if (!_isWalking && !_isSneaking && !_isLargeStep) { + gotoState(NULL); + gotoNextStateExt(); + } + } else if (xdiff <= 36 && !_isWalking && !_isSneaking && !_isLargeStep) { + _destX = x; + gotoState(NULL); + gotoNextStateExt(); + } else if (xdiff <= 42 && _actionStatus != 3) { + if (_isSneaking && ((!_doDeltaX && x - _x > 0) || (_doDeltaX && x - _x < 0)) && ABS(_destX - _x) > xdiff) { + _destX = x; + } else { + _destX = x; + GotoState(&Klaymen::stSneak); + } + } else if (_isWalking && ((!_doDeltaX && x - _x > 0) || (_doDeltaX && x - _x < 0))) { + _destX = x; + } else if (walkExt) { + _destX = x; + GotoState(&Klaymen::stStartWalkingExt); + } else { + _destX = x; + GotoState(&Klaymen::stStartWalking); + } +} + +void Klaymen::stWakeUp() { + _busyStatus = 1; + _acceptInput = false; + startAnimation(0x527AC970, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); +} + +void Klaymen::stSleeping() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x5A38C110, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmSleeping); + SetSpriteUpdate(NULL); +} + +uint32 Klaymen::hmSleeping(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevel(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x03060012) { + playSound(0, 0xC0238244); + } + break; + } + return messageResult; +} + +bool Klaymen::stStartAction(AnimationCb callback3) { + if (_busyStatus == 1) { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x5C7080D4, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmStartAction); + SetSpriteUpdate(&Klaymen::suAction); + NextState(callback3); + return true; + } else { + _x = _destX; + return false; + } +} + +void Klaymen::suAction() { + + int16 xdiff = _destX - _x; + + if (_doDeltaX) { + _x -= _deltaX; + } else { + _x += _deltaX; + } + _deltaX = 0; + + if (_doDeltaY) { + _y -= _deltaY; + } else { + _y += _deltaY; + } + _deltaY = 0; + + if (_frameChanged) { + if (xdiff > 6) + _x += 6; + else if (xdiff < -6) + _x -= 6; + else + _x = _destX; + } + + updateBounds(); + +} + +void Klaymen::suSneaking() { + + int16 xdiff = _destX - _x; + + if (_currFrameIndex == 9) { + if (xdiff > 26) + _deltaX += xdiff - 26; + else if (xdiff < -26) + _deltaX -= xdiff + 26; + } + + if (xdiff > _deltaX) + xdiff = _deltaX; + else if (xdiff < -_deltaX) + xdiff = -_deltaX; + _deltaX = 0; + + if (_destX != _x) { + HitRect *hitRectPrev = _parentScene->findHitRectAtPos(_x, _y); + _x += xdiff; + if (_pathPoints) { + walkAlongPathPoints(); + } else { + HitRect *hitRectNext = _parentScene->findHitRectAtPos(_x, _y); + if (hitRectNext->type == 0x5002) { + _y = MAX<int16>(hitRectNext->rect.y1, hitRectNext->rect.y2 - (hitRectNext->rect.x2 - _x) / 2); + } else if (hitRectNext->type == 0x5003) { + _y = MAX<int16>(hitRectNext->rect.y1, hitRectNext->rect.y2 - (_x - hitRectNext->rect.x1) / 2); + } else if (hitRectPrev->type == 0x5002) { + if (xdiff > 0) { + _y = hitRectPrev->rect.y2; + } else { + _y = hitRectPrev->rect.y1; + } + } else if (hitRectPrev->type == 0x5003) { + if (xdiff < 0) { + _y = hitRectPrev->rect.y2; + } else { + _y = hitRectPrev->rect.y1; + } + } + } + updateBounds(); + } + +} + +void Klaymen::stSneak() { + _busyStatus = 1; + _isSneaking = true; + _acceptInput = true; + setDoDeltaX(_destX < _x ? 1 : 0); + startAnimation(0x5C48C506, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmSneaking); + SetSpriteUpdate(&Klaymen::suSneaking); + FinalizeState(&Klaymen::evSneakingDone); +} + +void Klaymen::evSneakingDone() { + _isSneaking = false; +} + +uint32 Klaymen::hmSneaking(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevel(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x32180101) { + playSound(0, 0x4924AAC4); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, 0x0A2AA8E0); + } else if (param.asInteger() == 0x32188010) { + playSound(0, _soundFlag ? 0x48498E46 : 0x405002D8); + } else if (param.asInteger() == 0x02A2909C) { + playSound(0, _soundFlag ? 0x50399F64 : 0x0460E2FA); + } + break; + case 0x3002: + _x = _destX; + gotoNextStateExt(); + break; + } + return messageResult; +} + +void Klaymen::stStartWalking() { + if (!stStartActionFromIdle(AnimationCallback(&Klaymen::stStartWalking))) { + _busyStatus = 0; + _isWalking = true; + _acceptInput = true; + setDoDeltaX(_destX < _x ? 1 : 0); + startAnimation(0x242C0198, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmStartWalking); + SetSpriteUpdate(&Klaymen::suWalkingTestExit); + NextState(&Klaymen::stWalkingFirst); + FinalizeState(&Klaymen::evStartWalkingDone); + } +} + +void Klaymen::evStartWalkingDone() { + _isWalking = false; +} + +uint32 Klaymen::hmStartWalking(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x32180101) { + playSound(0, _soundFlag ? 0x48498E46 : 0x405002D8); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, _soundFlag ? 0x50399F64 : 0x0460E2FA); + } + break; + } + return messageResult; +} + +void Klaymen::stWalkingFirst() { + _busyStatus = 0; + _isWalking = true; + _acceptInput = true; + startAnimation(0x1A249001, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalking); + SetSpriteUpdate(&Klaymen::suWalkingFirst); + NextState(&Klaymen::stUpdateWalkingFirst); + FinalizeState(&Klaymen::evStartWalkingDone); +} + +void Klaymen::suWalkingFirst() { + SetSpriteUpdate(&Klaymen::suWalkingTestExit); + _deltaX = 0; +} + +uint32 Klaymen::hmWalking(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevel(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x32180101) { + playSound(0, _soundFlag ? 0x48498E46 : 0x405002D8); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, _soundFlag ? 0x50399F64 : 0x0460E2FA); + } + break; + } + return messageResult; +} + +void Klaymen::stUpdateWalkingFirst() { + if (_actionStatus == 2) { + gotoNextStateExt(); + } else if (_actionStatus == 3) { + stWalkingOpenDoor(); + } else { + _isSneaking = true; + _acceptInput = true; + if (ABS(_destX - _x) <= 42 && _currFrameIndex >= 5 && _currFrameIndex <= 11) { + if (_actionStatus == 0) { + _busyStatus = 1; + startAnimation(0xF234EE31, 0, -1); + } else { + _busyStatus = 2; + startAnimation(0xF135CC21, 0, -1); + } + } else if (ABS(_destX - _x) <= 10 && (_currFrameIndex >= 12 || _currFrameIndex <= 4)) { + if (_actionStatus == 0) { + _busyStatus = 1; + startAnimation(0x8604A152, 0, -1); + } else { + _busyStatus = 2; + startAnimation(0xA246A132, 0, -1); + } + } + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmSneaking); + SetSpriteUpdate(&Klaymen::suSneaking); + FinalizeState(&Klaymen::evSneakingDone); + } +} + +void Klaymen::suWalkingTestExit() { + int16 xdiff = ABS(_destX - _x); + int16 xdelta = _destX - _x; + + if (xdelta > _deltaX) + xdelta = _deltaX; + else if (xdelta < -_deltaX) + xdelta = -_deltaX; + + _deltaX = 0; + + if (xdiff == 0 || + (_actionStatus != 2 && _actionStatus != 3 && xdiff <= 42 && _currFrameIndex >= 5 && _currFrameIndex <= 11) || + (_actionStatus != 2 && _actionStatus != 3 && xdiff <= 10 && (_currFrameIndex >= 12 || _currFrameIndex <= 4)) || + (_actionStatus == 3 && xdiff < 30) || + (_actionStatus == 3 && xdiff < 150 && _currFrameIndex >= 6)) { + sendMessage(this, 0x1019, 0); + } else { + HitRect *hitRectPrev = _parentScene->findHitRectAtPos(_x, _y); + _x += xdelta; + if (_pathPoints) { + walkAlongPathPoints(); + } else { + HitRect *hitRectNext = _parentScene->findHitRectAtPos(_x, _y); + if (hitRectNext->type == 0x5002) { + _y = MAX<int16>(hitRectNext->rect.y1, hitRectNext->rect.y2 - (hitRectNext->rect.x2 - _x) / 2); + } else if (hitRectNext->type == 0x5003) { + _y = MAX<int16>(hitRectNext->rect.y1, hitRectNext->rect.y2 - (_x - hitRectNext->rect.x1) / 2); + } else if (hitRectPrev->type == 0x5002) { + _y = xdelta > 0 ? hitRectPrev->rect.y2 : hitRectPrev->rect.y1; + } else if (hitRectPrev->type == 0x5003) { + _y = xdelta < 0 ? hitRectPrev->rect.y2 : hitRectPrev->rect.y1; + } else if (_doYHitIncr && xdelta != 0) { + if (hitRectNext->type == 0x5000) { + _y++; + } else if (hitRectNext->type == 0x5001 && _y > hitRectNext->rect.y1) { + _y--; + } + } + } + updateBounds(); + } + +} + +uint32 Klaymen::hmLever(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x4AB28209) { + sendMessage(_attachedSprite, 0x482A, 0); + } else if (param.asInteger() == 0x88001184) { + sendMessage(_attachedSprite, 0x482B, 0); + } + break; + } + return messageResult; +} + +void Klaymen::stPickUpGeneric() { + setDoDeltaX(_attachedSprite->getX() < _x ? 1 : 0); + if (!stStartAction(AnimationCallback(&Klaymen::stPickUpGeneric))) { + _busyStatus = 1; + _acceptInput = false; + startAnimation(0x1C28C178, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmPickUpObject); + SetSpriteUpdate(NULL); + } +} + +uint32 Klaymen::hmPickUpObject(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0xC1380080) { + sendMessage(_attachedSprite, 0x4806, 0); + playSound(0, 0x40208200); + } else if (param.asInteger() == 0x02B20220) { + playSound(0, 0xC5408620); + } else if (param.asInteger() == 0x03020231) { + playSound(0, 0xD4C08010); + } else if (param.asInteger() == 0x67221A03) { + playSound(0, 0x44051000); + } else if (param.asInteger() == 0x2EAE0303) { + playSound(0, 0x03630300); + } else if (param.asInteger() == 0x61CE4467) { + playSound(0, 0x03630300); + } + break; + } + return messageResult; + +} + +void Klaymen::stPressButton() { + if (!stStartAction(AnimationCallback(&Klaymen::stPressButton))) { + _busyStatus = 2; + _acceptInput = true; + startAnimation(0x1C02B03D, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmPressButton); + SetSpriteUpdate(NULL); + } +} + +uint32 Klaymen::hmPressButton(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x0D01B294) { + sendMessage(_attachedSprite, 0x480B, 0); + } else if (param.asInteger() == 0x32180101) { + playSound(0, 0x4924AAC4); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, 0x0A2AA8E0); + } + break; + } + return messageResult; +} + +void Klaymen::stPressFloorButton() { + if (!stStartAction(AnimationCallback(&Klaymen::stPressFloorButton))) { + _busyStatus = 2; + _acceptInput = true; + startAnimation(0x1C16B033, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmPressButton); + SetSpriteUpdate(NULL); + } +} + +void Klaymen::stPressButtonSide() { + if (!stStartActionFromIdle(AnimationCallback(&Klaymen::stPressButtonSide))) { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0x1CD89029, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmPressButton); + SetSpriteUpdate(&Klaymen::suAction); + } +} + +void Klaymen::startSpecialWalkRight(int16 x) { + if (_x == x) { + _destX = x; + gotoState(NULL); + gotoNextStateExt(); + } else if (_x < x) { + startWalkToX(x, false); + } else if (_x - x <= 105) { + startWalkToXExt(x); + } else { + startWalkToX(x, false); + } +} + +void Klaymen::startSpecialWalkLeft(int16 x) { + if (x == _x) { + _destX = x; + gotoState(NULL); + gotoNextStateExt(); + } else if (x < _x) { + startWalkToX(x, false); + } else if (x - _x <= 105) { + startWalkToXExt(x); + } else { + startWalkToX(x, false); + } +} + +void Klaymen::startWalkToXSmall(int16 x) { + _actionStatus = 2; + if (_x == x) { + _destX = x; + if (_isWalking) { + GotoState(NULL); + gotoNextStateExt(); + } + } else if (_isWalking && ((!_doDeltaX && x - _x > 0) || (_doDeltaX && x - _x < 0))) { + _destX = x; + } else { + _destX = x; + GotoState(&Klaymen::stStartWalkingSmall); + } +} + +void Klaymen::stStartWalkingSmall() { + _isWalking = true; + _acceptInput = true; + _actionStatus = 2; + setDoDeltaX(_destX < _x ? 1 : 0); + startAnimation(0x3A4CD934, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalkingSmall); + SetSpriteUpdate(&Klaymen::suWalkingTestExit); + FinalizeState(&Klaymen::evStartWalkingDone); +} + +uint32 Klaymen::hmWalkingSmall(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevel(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x32180101) + playSound(0, 0x4924AAC4); + else if (param.asInteger() == 0x0A2A9098) + playSound(0, 0x0A2AA8E0); + } + return messageResult; +} + +void Klaymen::stStandIdleSmall() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x90D0D1D0, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(NULL); +} + +void Klaymen::stWonderAboutAfterSmall() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x11C8D156, 30, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); +} + +void Klaymen::stWonderAboutHalfSmall() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x11C8D156, 0, 10); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); +} + +void Klaymen::stWonderAboutSmall() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x11C8D156, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); +} + +void Klaymen::stWalkToFrontNoStepSmall() { + _busyStatus = 0; + _acceptInput = false; + startAnimationByHash(0x3F9CC394, 0x14884392, 0); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalkFrontBackSmall); + SetSpriteUpdate(&Klaymen::suUpdateDestX); +} + +uint32 Klaymen::hmWalkFrontBackSmall(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x80C110B5) + sendMessage(_parentScene, 0x482A, 0); + else if (param.asInteger() == 0x110010D1) + sendMessage(_parentScene, 0x482B, 0); + else if (param.asInteger() == 0x32180101) + playSound(0, 0x4924AAC4); + else if (param.asInteger() == 0x0A2A9098) + playSound(0, 0x0A2AA8E0); + break; + } + return messageResult; +} + +void Klaymen::stWalkToFront2Small() { + _busyStatus = 0; + _acceptInput = false; + startAnimation(0x2F1C4694, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalkFrontBackSmall); + SetSpriteUpdate(&Klaymen::suUpdateDestX); +} + +void Klaymen::stWalkToFrontSmall() { + _busyStatus = 0; + _acceptInput = false; + startAnimation(0x3F9CC394, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalkFrontBackSmall); + SetSpriteUpdate(&Klaymen::suUpdateDestX); +} + +void Klaymen::stTurnToBackHalfSmall() { + _busyStatus = 0; + _acceptInput = false; + startAnimationByHash(0x37ECD436, 0, 0x8520108C); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalkFrontBackSmall); + SetSpriteUpdate(&Klaymen::suUpdateDestX); +} + +void Klaymen::stTurnToBackWalkSmall() { + _busyStatus = 0; + _acceptInput = false; + startAnimation(0x16EDDE36, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalkFrontBackSmall); + SetSpriteUpdate(&Klaymen::suUpdateDestX); +} + +void Klaymen::stTurnToBackSmall() { + _busyStatus = 0; + _acceptInput = false; + startAnimation(0x37ECD436, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalkFrontBackSmall); + SetSpriteUpdate(&Klaymen::suUpdateDestX); +} + +void Klaymen::stPullCord() { + if (!stStartAction(AnimationCallback(&Klaymen::stPullCord))) { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x3F28E094, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmPullReleaseCord); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stReleaseCord); + } +} + +void Klaymen::stReleaseCord() { + _acceptInput = false; + startAnimation(0x3A28C094, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmPullReleaseCord); + SetSpriteUpdate(NULL); +} + +uint32 Klaymen::hmPullReleaseCord(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x4AB28209) { + sendMessage(_attachedSprite, 0x482A, 0); + sendMessage(_attachedSprite, 0x480F, 0); + } else if (param.asInteger() == 0x88001184) { + sendMessage(_attachedSprite, 0x482B, 0); + } + break; + } + return messageResult; +} + +void Klaymen::stUseTube() { + if (!stStartAction(AnimationCallback(&Klaymen::stUseTube))) { + _busyStatus = 1; + _acceptInput = false; + startAnimation(0x1A38A814, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmUseTube); + SetSpriteUpdate(NULL); + } +} + +uint32 Klaymen::hmUseTube(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x02B20220) + playSound(0, 0xC5408620); + else if (param.asInteger() == 0x0A720138) + playSound(0, 0xD4C08010); + else if (param.asInteger() == 0x03020231) + playSound(0, 0xD4C08010); + else if (param.asInteger() == 0xB613A180) + playSound(0, 0x44051000); + else if (param.asInteger() == 0x67221A03) + playSound(0, 0x44051000); + else if (param.asInteger() == 0x038A010B) + playSound(0, 0x00018040); + else if (param.asInteger() == 0x422B0280) + playSound(0, 0x166FC6E0); + else if (param.asInteger() == 0x925A0C1E) + playSound(0, 0x40E5884D); + break; + } + return messageResult; +} + +void Klaymen::stWalkingFirstExt() { + _busyStatus = 0; + _isWalking = true; + _acceptInput = true; + startAnimation(0x5A2CBC00, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalking); + SetSpriteUpdate(&Klaymen::suWalkingFirst); + NextState(&Klaymen::stUpdateWalkingFirst); + FinalizeState(&Klaymen::evStartWalkingDone); +} + +void Klaymen::stStartWalkingExt() { + if (!stStartActionFromIdle(AnimationCallback(&Klaymen::stStartWalkingExt))) { + _busyStatus = 0; + _isWalking = true; + _acceptInput = true; + setDoDeltaX(_destX < _x ? 1 : 0); + startAnimation(0x272C1199, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmStartWalking); + SetSpriteUpdate(&Klaymen::suWalkingTestExit); + NextState(&Klaymen::stWalkingFirstExt); + FinalizeState(&Klaymen::evStartWalkingDone); + } +} + +void Klaymen::startWalkToXDistance(int16 destX, int16 distance) { + if (_x > destX) { + if (_x == destX + distance) { + _destX = destX + distance; + gotoState(NULL); + gotoNextStateExt(); + } else if (_x < destX + distance) { + startWalkToXExt(destX + distance); + } else { + startWalkToX(destX + distance, false); + } + } else { + if (_x == destX - distance) { + _destX = destX - distance; + gotoState(NULL); + gotoNextStateExt(); + } else if (_x > destX - distance) { + startWalkToXExt(destX - distance); + } else { + startWalkToX(destX - distance, false); + } + } +} + +void Klaymen::startWalkToXExt(int16 x) { + int16 xdiff = ABS(x - _x); + if (x == _x) { + _destX = x; + if (!_isWalking && !_isSneaking && !_isLargeStep) { + gotoState(NULL); + gotoNextStateExt(); + } + } else if (xdiff <= 36 && !_isWalking && !_isSneaking && !_isLargeStep) { + _destX = x; + gotoState(NULL); + gotoNextStateExt(); + } else if (xdiff <= 42 && _actionStatus != 3) { + if (_isSneaking && ((!_doDeltaX && x - _x > 0) || (_doDeltaX && x - _x < 0)) && ABS(_destX - _x) > xdiff) { + _destX = x; + } else { + _destX = x; + GotoState(&Klaymen::stSneak); + } + } else if (_isLargeStep && ((!_doDeltaX && x - _x > 0) || (_doDeltaX && x - _x < 0))) { + _destX = x; + } else { + _destX = x; + GotoState(&Klaymen::stLargeStep); + } +} + +void Klaymen::stLargeStep() { + _busyStatus = 2; + _isLargeStep = true; + _acceptInput = true; + setDoDeltaX(_destX >= _x ? 1 : 0); + startAnimation(0x08B28116, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLargeStep); + SetSpriteUpdate(&Klaymen::suLargeStep); + FinalizeState(&Klaymen::evLargeStepDone); +} + +void Klaymen::evLargeStepDone() { + _isLargeStep = false; +} + +void Klaymen::suLargeStep() { + int16 xdiff = _destX - _x; + + if (_doDeltaX) { + _deltaX = -_deltaX; + } + + if (_currFrameIndex == 7) { + _deltaX = xdiff; + } + + if ((xdiff > 0 && xdiff > _deltaX) || (xdiff < 0 && xdiff < _deltaX)) + xdiff = _deltaX; + + _deltaX = 0; + + if (_x != _destX) { + HitRect *hitRectPrev = _parentScene->findHitRectAtPos(_x, _y); + _x += xdiff; + if (_pathPoints) { + walkAlongPathPoints(); + } else { + HitRect *hitRectNext = _parentScene->findHitRectAtPos(_x, _y); + if (hitRectNext->type == 0x5002) { + _y = MAX<int16>(hitRectNext->rect.y1, hitRectNext->rect.y2 - (hitRectNext->rect.x2 - _x) / 2); + } else if (hitRectNext->type == 0x5003) { + _y = MAX<int16>(hitRectNext->rect.y1, hitRectNext->rect.y2 - (_x - hitRectNext->rect.x1) / 2); + } else if (hitRectPrev->type == 0x5002) { + _y = xdiff > 0 ? hitRectPrev->rect.y2 : hitRectPrev->rect.y1; + } else if (hitRectPrev->type == 0x5003) { + _y = xdiff < 0 ? hitRectPrev->rect.y2 : hitRectPrev->rect.y1; + } + } + updateBounds(); + } +} + +uint32 Klaymen::hmLargeStep(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevel(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x32180101) { + playSound(0, 0x4924AAC4); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, 0x0A2AA8E0); + } + break; + case 0x3002: + _x = _destX; + gotoNextStateExt(); + break; + } + return messageResult; +} + +void Klaymen::stWonderAboutHalf() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0xD820A114, 0, 10); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); +} + +void Klaymen::stWonderAboutAfter() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0xD820A114, 30, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); +} + +void Klaymen::stTurnToUseHalf() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x9B250AD2, 0, 7); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmTurnToUse); + SetSpriteUpdate(NULL); +} + +uint32 Klaymen::hmTurnToUse(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x32180101) { + playSound(0, 0x4924AAC4); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, 0x0A2AA8E0); + } + break; + } + return messageResult; +} + +void Klaymen::stTurnAwayFromUse() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0x98F88391, 4, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmTurnToUse); + SetSpriteUpdate(NULL); +} + +void Klaymen::stWonderAbout() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0xD820A114, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); +} + +void Klaymen::stPeekWall() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0xAC20C012, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmPeekWall); + SetSpriteUpdate(NULL); +} + +uint32 Klaymen::hmPeekWall(int messageNum, const MessageParam ¶m, Entity *sender) { + int16 speedUpFrameIndex; + switch (messageNum) { + case 0x1008: + speedUpFrameIndex = getFrameIndex(kKlaymenSpeedUpHash); + if (_currFrameIndex < speedUpFrameIndex) + startAnimation(0xAC20C012, speedUpFrameIndex, -1); + return 0; + case 0x100D: + if (param.asInteger() == 0x32180101) { + playSound(0, 0x405002D8); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, 0x0460E2FA); + } + break; + } + return hmLowLevelAnimation(messageNum, param, sender); +} + +void Klaymen::stJumpToRing1() { + if (!stStartAction(AnimationCallback(&Klaymen::stJumpToRing1))) { + _busyStatus = 0; + startAnimation(0xD82890BA, 0, -1); + setupJumpToRing(); + } +} + +void Klaymen::setupJumpToRing() { + _acceptInput = false; + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmJumpToRing); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + NextState(&Klaymen::stHangOnRing); + sendMessage(_attachedSprite, 0x482B, 0); +} + +uint32 Klaymen::hmJumpToRing(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x168050A0) { + sendMessage(_attachedSprite, 0x4806, 0); + _acceptInput = true; + } else if (param.asInteger() == 0x320AC306) { + playSound(0, 0x5860C640); + } else if (param.asInteger() == 0x4AB28209) { + sendMessage(_attachedSprite, 0x482A, 0); + } else if (param.asInteger() == 0x88001184) { + sendMessage(_attachedSprite, 0x482B, 0); + } + break; + } + return messageResult; +} + +void Klaymen::suUpdateDestX() { + AnimatedSprite::updateDeltaXY(); + _destX = _x; +} + +void Klaymen::stHangOnRing() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x4829E0B8, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(NULL); +} + +void Klaymen::stJumpToRing2() { + if (!stStartAction(AnimationCallback(&Klaymen::stJumpToRing2))) { + _busyStatus = 0; + startAnimation(0x900980B2, 0, -1); + setupJumpToRing(); + } +} + +void Klaymen::stJumpToRing3() { + if (!stStartAction(AnimationCallback(&Klaymen::stJumpToRing3))) { + _busyStatus = 0; + _acceptInput = false; + startAnimation(0xBA1910B2, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + SetMessageHandler(&Klaymen::hmJumpToRing3); + NextState(&Klaymen::stHoldRing3); + sendMessage(_attachedSprite, 0x482B, 0); + } +} + +uint32 Klaymen::hmJumpToRing3(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x168050A0) { + sendMessage(_attachedSprite, 0x4806, 0); + } else if (param.asInteger() == 0x320AC306) { + playSound(0, 0x5860C640); + } else if (param.asInteger() == 0x4AB28209) { + sendMessage(_attachedSprite, 0x482A, 0); + } else if (param.asInteger() == 0x88001184) { + sendMessage(_attachedSprite, 0x482B, 0); + } + break; + } + return messageResult; +} + +void Klaymen::stHoldRing3() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x4A293FB0, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmHoldRing3); + SetSpriteUpdate(NULL); +} + +uint32 Klaymen::hmHoldRing3(int messageNum, const MessageParam ¶m, Entity *sender) { + if (messageNum == 0x1008) { + stReleaseRing(); + return 0; + } + return hmLowLevel(messageNum, param, sender); +} + +void Klaymen::stReleaseRing() { + _busyStatus = 1; + _acceptInput = false; + sendMessage(_attachedSprite, 0x4807, 0); + _attachedSprite = NULL; + startAnimation(0xB869A4B9, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); +} + +void Klaymen::stJumpToRing4() { + if (!stStartAction(AnimationCallback(&Klaymen::stJumpToRing4))) { + _busyStatus = 0; + startAnimation(0xB8699832, 0, -1); + setupJumpToRing(); + } +} + +void Klaymen::startWalkToAttachedSpriteXDistance(int16 distance) { + startWalkToXDistance(_attachedSprite->getX(), distance); +} + +void Klaymen::stContinueClimbLadderUp() { + _busyStatus = 0; + _acceptInput = true; + _ladderStatus = 3; + startAnimationByHash(0x3A292504, 0x01084280, 0); + _newStickFrameHash = 0x01084280; + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(NULL); + gotoNextStateExt(); +} + +void Klaymen::stStartClimbLadderDown() { + if (!stStartAction(AnimationCallback(&Klaymen::stStartClimbLadderDown))) { + _busyStatus = 0; + if (_destY < _y) { + if (_ladderStatus == 1) { + _ladderStatus = 2; + stClimbLadderHalf(); + } else { + gotoNextStateExt(); + } + } else if (_ladderStatus == 0) { + _ladderStatus = 2; + _acceptInput = false; + startAnimation(0x122D1505, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmClimbLadderUpDown); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } else if (_ladderStatus == 3) { + _ladderStatus = 2; + _acceptInput = true; + startAnimationByHash(0x122D1505, 0x01084280, 0); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmClimbLadderUpDown); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } else if (_ladderStatus == 1) { + _ladderStatus = 2; + _acceptInput = true; + startAnimation(0x122D1505, 29 - _currFrameIndex, -1); + } + } +} + +void Klaymen::stClimbLadderHalf() { + _busyStatus = 2; + if (_ladderStatus == 1) { + _ladderStatus = 0; + _acceptInput = false; + startAnimationByHash(0x3A292504, 0x02421405, 0); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmClimbLadderHalf); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } else if (_ladderStatus == 2) { + _ladderStatus = 0; + _acceptInput = false; + startAnimationByHash(0x122D1505, 0x02421405, 0); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmClimbLadderHalf); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } else { + gotoNextStateExt(); + } +} + +uint32 Klaymen::hmClimbLadderHalf(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x489B025C) { + playSound(0, 0x52C4C2D7); + } else if (param.asInteger() == 0x400A0E64) { + playSound(0, 0x50E081D9); + } else if (param.asInteger() == 0x32180101) { + playSound(0, 0x405002D8); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, 0x0460E2FA); + } + break; + } + return messageResult; +} + +uint32 Klaymen::hmClimbLadderUpDown(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevel(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x01084280) { + _acceptInput = true; + } else if (param.asInteger() == 0x489B025C) { + playSound(0, 0x52C4C2D7); + } else if (param.asInteger() == 0x400A0E64) { + playSound(0, 0x50E081D9); + } else if (param.asInteger() == 0x02421405) { + if (_ladderStatus == 1) { + startAnimationByHash(0x3A292504, 0x01084280, 0); + if (_destY >= _y - 30) + sendMessage(this, 0x1019, 0); + } else { + startAnimationByHash(0x122D1505, 0x01084280, 0); + if (_destY <= _y) + sendMessage(this, 0x1019, 0); + } + } + break; + } + return messageResult; +} + +void Klaymen::stStartClimbLadderUp() { + if (!stStartAction(AnimationCallback(&Klaymen::stStartClimbLadderUp))) { + _busyStatus = 0; + if (_destY >= _y - 30) { + gotoNextStateExt(); + } else if (_ladderStatus == 0) { + _ladderStatus = 1; + _acceptInput = false; + startAnimation(0x3A292504, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmClimbLadderUpDown); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } else if (_ladderStatus == 3) { + _ladderStatus = 1; + _acceptInput = true; + startAnimationByHash(0x3A292504, 0x01084280, 0); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmClimbLadderUpDown); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } else if (_ladderStatus == 2) { + _ladderStatus = 1; + _acceptInput = true; + startAnimation(0x3A292504, 29 - _currFrameIndex, -1); + } + } +} + +void Klaymen::stWalkToFrontNoStep() { + _busyStatus = 2; + _acceptInput = false; + startAnimationByHash(0xF229C003, 0x14884392, 0); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalkToFront); + SetSpriteUpdate(&Klaymen::suUpdateDestX); +} + +uint32 Klaymen::hmWalkToFront(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x80C110B5) { + sendMessage(_parentScene, 0x482A, 0); + } else if (param.asInteger() == 0x110010D1) { + sendMessage(_parentScene, 0x482B, 0); + } else if (param.asInteger() == 0x32180101) { + playSound(0, _soundFlag ? 0x48498E46 : 0x405002D8); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, _soundFlag ? 0x50399F64 : 0x0460E2FA); + } + break; + } + return messageResult; +} + +void Klaymen::stWalkToFront() { + if (!stStartAction(AnimationCallback(&Klaymen::stWalkToFront))) { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0xF229C003, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalkToFront); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } +} + +void Klaymen::stTurnToFront() { + if (!stStartAction(AnimationCallback(&Klaymen::stTurnToFront))) { + _busyStatus = 0; + _acceptInput = false; + startAnimationByHash(0xCA221107, 0, 0x8520108C); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalkToFront); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } +} + +void Klaymen::stTurnToBack() { + if (!stStartAction(AnimationCallback(&Klaymen::stTurnToBack))) { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0xCA221107, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalkToFront); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } +} + +void Klaymen::stLandOnFeet() { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0x18118554, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLandOnFeet); + SetSpriteUpdate(NULL); +} + +uint32 Klaymen::hmLandOnFeet(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x320AC306) { + playSound(0, 0x5860C640); + } + break; + } + return messageResult; +} + +void Klaymen::stTurnToBackToUse() { + if (!stStartAction(AnimationCallback(&Klaymen::stTurnToBackToUse))) { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x91540140, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmTurnToBackToUse); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } +} + +uint32 Klaymen::hmTurnToBackToUse(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0xC61A0119) { + playSound(0, 0x402338C2); + } else if (param.asInteger() == 0x32180101) { + playSound(0, 0x4924AAC4); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, 0x0A2AA8E0); + } + break; + } + return messageResult; +} + +void Klaymen::stClayDoorOpen() { + if (!stStartAction(AnimationCallback(&Klaymen::stClayDoorOpen))) { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x5CCCB330, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmClayDoorOpen); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } +} + +uint32 Klaymen::hmClayDoorOpen(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x040D4186) { + sendMessage(_attachedSprite, 0x4808, 0); + } + break; + } + return messageResult; +} + +void Klaymen::stTurnToUse() { + if (!stStartAction(AnimationCallback(&Klaymen::stTurnToUse))) { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x9B250AD2, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmTurnToUse); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } +} + +void Klaymen::stReturnFromUse() { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x98F88391, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmTurnToUse); + SetSpriteUpdate(&Klaymen::suUpdateDestX); +} + +void Klaymen::stWalkingOpenDoor() { + _isWalkingOpenDoorNotified = false; + _acceptInput = false; + startAnimation(0x11A8E012, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmStartWalking); + SetSpriteUpdate(&Klaymen::suWalkingOpenDoor); +} + +void Klaymen::suWalkingOpenDoor() { + if (!_isWalkingOpenDoorNotified && ABS(_destX - _x) < 80) { + sendMessage(_parentScene, 0x4829, 0); + _isWalkingOpenDoorNotified = true; + } + AnimatedSprite::updateDeltaXY(); +} + +void Klaymen::stMoveObjectSkipTurnFaceObject() { + setDoDeltaX(_attachedSprite->getX() < _x ? 1 : 0); + _isMoveObjectRequested = false; + _acceptInput = true; + startAnimationByHash(0x0C1CA072, 0x01084280, 0); + SetUpdateHandler(&Klaymen::update); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + SetMessageHandler(&Klaymen::hmMoveObjectTurn); +} + +void Klaymen::evMoveObjectTurnDone() { + sendMessage(_attachedSprite, 0x4807, 0); +} + +uint32 Klaymen::hmMoveObjectTurn(int messageNum, const MessageParam ¶m, Entity *sender) { + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x01084280) { + sendMessage(_attachedSprite, 0x480B, _doDeltaX ? 1 : 0); + } else if (param.asInteger() == 0x02421405) { + if (_isMoveObjectRequested && sendMessage(_attachedSprite, 0x480C, _doDeltaX ? 1 : 0) != 0) { + stMoveObjectSkipTurn(); + } else { + FinalizeState(&Klaymen::evMoveObjectTurnDone); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + } + } else if (param.asInteger() == 0x32180101) { + playSound(0, 0x405002D8); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, 0x0460E2FA); + } + break; + case 0x480A: + _isMoveObjectRequested = true; + return 0; + } + return hmLowLevelAnimation(messageNum, param, sender); +} + +void Klaymen::stMoveObjectSkipTurn() { + _isMoveObjectRequested = false; + _acceptInput = true; + startAnimationByHash(0x0C1CA072, 0x01084280, 0); + SetUpdateHandler(&Klaymen::update); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + SetMessageHandler(&Klaymen::hmMoveObjectTurn); +} + +void Klaymen::stMoveObjectFaceObject() { + setDoDeltaX(_attachedSprite->getX() < _x ? 1 : 0); + if (!stStartAction(AnimationCallback(&Klaymen::stMoveObjectFaceObject))) { + _busyStatus = 2; + _isMoveObjectRequested = false; + _acceptInput = true; + startAnimation(0x0C1CA072, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmMoveObjectTurn); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } +} + +void Klaymen::stUseLever() { + if (!stStartAction(AnimationCallback(&Klaymen::stUseLever))) { + _busyStatus = 0; + if (_isLeverDown) { + stUseLeverRelease(); + } else { + sendMessage(_attachedSprite, 0x482B, 0); + startAnimation(0x0C303040, 0, -1); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + SetMessageHandler(&Klaymen::hmLever); + SetUpdateHandler(&Klaymen::update); + NextState(&Klaymen::stPullLeverDown); + _acceptInput = false; + } + } +} + +// Exactly the same code as sub420DA0 which was removed +void Klaymen::stPullLeverDown() { + startAnimation(0x0D318140, 0, -1); + sendMessage(_attachedSprite, 0x480F, 0); + NextState(&Klaymen::stHoldLeverDown); +} + +void Klaymen::stHoldLeverDown() { + startAnimation(0x4464A440, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + _isLeverDown = true; + _acceptInput = true; +} + +void Klaymen::stUseLeverRelease() { + startAnimation(0x09018068, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLever); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + sendMessage(_attachedSprite, 0x4807, 0); + NextState(&Klaymen::stPullLeverDown); + _acceptInput = false; +} + +void Klaymen::stReleaseLever() { + if (_isLeverDown) { + _busyStatus = 2; + startAnimation(0x09018068, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLever); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + sendMessage(_attachedSprite, 0x4807, 0); + NextState(&Klaymen::stLetGoOfLever); + _acceptInput = false; + _isLeverDown = false; + } else { + gotoNextStateExt(); + } +} + +void Klaymen::stLetGoOfLever() { + startAnimation(0x0928C048, 0, -1); + FinalizeState(&Klaymen::evLeverReleasedEvent); +} + +void Klaymen::evLeverReleasedEvent() { + sendMessage(_attachedSprite, 0x482A, 0); +} + +void Klaymen::stInsertDisk() { + if (!stStartActionFromIdle(AnimationCallback(&Klaymen::stInsertDisk))) { + _busyStatus = 2; + _tapesToInsert = 0; + for (uint32 i = 0; i < 20; i++) { + if (getSubVar(VA_HAS_TAPE, i)) { + setSubVar(VA_IS_TAPE_INSERTED, i, 1); + setSubVar(VA_HAS_TAPE, i, 0); + _tapesToInsert++; + } + } + if (_tapesToInsert == 0) { + GotoState(NULL); + gotoNextStateExt(); + } else { + startAnimation(0xD8C8D100, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmInsertDisk); + SetSpriteUpdate(&Klaymen::suAction); + _acceptInput = false; + _tapesToInsert--; + } + } +} + +uint32 Klaymen::hmInsertDisk(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Klaymen::hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (_tapesToInsert == 0 && param.asInteger() == 0x06040580) { + nextAnimationByHash(0xD8C8D100, calcHash("GoToStartLoop/Finish"), 0); + } else if (_tapesToInsert != 0 && param.asInteger() == calcHash("GoToStartLoop/Finish")) { + _tapesToInsert--; + startAnimationByHash(0xD8C8D100, 0x01084280, 0); + } else if (param.asInteger() == 0x062A1510) { + playSound(0, 0x41688704); + } else if (param.asInteger() == 0x02B20220) { + playSound(0, 0xC5408620); + } else if (param.asInteger() == 0x0A720138) { + playSound(0, 0xD4C08010); + } else if (param.asInteger() == 0xB613A180) { + playSound(0, 0x44051000); + } else if (param.asInteger() == 0x0E040501) { + playSound(1, 0xC6A129C1); + } + } + return messageResult; +} + +void Klaymen::walkAlongPathPoints() { + if (_x <= (*_pathPoints)[0].x) + _y = (*_pathPoints)[0].y; + else if (_x >= (*_pathPoints)[_pathPoints->size() - 1].x) + _y = (*_pathPoints)[_pathPoints->size() - 1].y; + else { + int16 deltaX = _x - (*_pathPoints)[0].x, deltaXIncr = 0; + uint index = 0; + while (deltaX > 0) { + NPoint pt2 = (*_pathPoints)[index]; + NPoint pt1 = index + 1 >= _pathPoints->size() ? (*_pathPoints)[0] : (*_pathPoints)[index + 1]; + int16 xd = ABS(pt1.x - pt2.x); + int16 yd = ABS(pt1.y - pt2.y); + if (deltaX + deltaXIncr >= xd) { + deltaX -= xd; + deltaX += deltaXIncr; + ++index; + if (index >= _pathPoints->size()) + index = 0; + _y = (*_pathPoints)[index].y; + } else { + deltaXIncr += deltaX; + if (pt1.y >= pt2.y) { + _y = pt2.y + (yd * deltaXIncr) / xd; + } else { + _y = pt2.y - (yd * deltaXIncr) / xd; + } + deltaX = 0; + } + } + } +} + +void Klaymen::enterIdleAnimation(uint idleAnimation) { + switch (idleAnimation) { + case kIdlePickEar: + startIdleAnimation(0x5B20C814, AnimationCallback(&Klaymen::stIdlePickEar)); + break; + case kIdleSpinHead: + startIdleAnimation(0xD122C137, AnimationCallback(&Klaymen::stIdleSpinHead)); + break; + case kIdleArms: + startIdleAnimation(0x543CD054, AnimationCallback(&Klaymen::stIdleArms)); + break; + case kIdleChest: + startIdleAnimation(0x40A0C034, AnimationCallback(&Klaymen::stIdleChest)); + break; + case kIdleHeadOff: + startIdleAnimation(0x5120E137, AnimationCallback(&Klaymen::stIdleHeadOff)); + break; + case kIdleTeleporterHands: + startIdleAnimation(0x90EF8D38, AnimationCallback(&Klaymen::stIdleTeleporterHands)); + break; + case kIdleTeleporterHands2: + startIdleAnimation(0x900F0930, AnimationCallback(&Klaymen::stIdleTeleporterHands2)); + break; + case kIdleWonderAbout: + stIdleWonderAbout(); + break; + } +} + +void Klaymen::stJumpToGrab() { + _busyStatus = 0; + _acceptInput = false; + startAnimationByHash(0x00AB8C10, 0x01084280, 0); + SetUpdateHandler(&Klaymen::update); + SetSpriteUpdate(&Klaymen::suJumpToGrab); + SetMessageHandler(&Klaymen::hmJumpToGrab); +} + +void Klaymen::suJumpToGrab() { + updateDeltaXY(); + if (_y >= _destY) { + _y = _destY; + updateBounds(); + gotoNextStateExt(); + } +} + +uint32 Klaymen::hmJumpToGrab(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevel(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x168050A0) + sendMessage(_attachedSprite, 0x4806, 0); + else if (param.asInteger() == 0x320AC306) + startAnimationByHash(0x00AB8C10, 0x01084280, 0); + else if (param.asInteger() == 0x4AB28209) + sendMessage(_attachedSprite, 0x482A, 0); + else if (param.asInteger() == 0x88001184) + sendMessage(_attachedSprite, 0x482B, 0); + break; + } + return messageResult; +} + +void Klaymen::stFinishGrow() { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x38445000, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetSpriteUpdate(NULL); + SetMessageHandler(&Klaymen::hmFinishGrow); +} + +uint32 Klaymen::hmFinishGrow(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x040C4C01) + playSound(0, 0x01E11140); + break; + } + return messageResult; +} + +void Klaymen::stTurnToUseExt() { + if (!stStartAction(AnimationCallback(&Klaymen::stTurnToUseExt))) { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x1B3D8216, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmTurnToUse); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + } +} + +void Klaymen::stJumpToGrabFall() { + if (!stStartAction(AnimationCallback(&Klaymen::stJumpToGrabFall))) { + _busyStatus = 0; + _acceptInput = false; + startAnimation(0x00AB8C10, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmJumpToGrab); + SetSpriteUpdate(&Klaymen::suJumpToGrab); + sendMessage(_attachedSprite, 0x482B, 0); + } +} + +void Klaymen::stJumpToGrabRelease() { + _busyStatus = 1; + _acceptInput = false; + startAnimationByHash(0x00AB8C10, 0x320AC306, 0); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmJumpToGrabRelease); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stReleaseRing); +} + +uint32 Klaymen::hmJumpToGrabRelease(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x320AC306) + playSound(0, 0x5860C640); + break; + } + return messageResult; +} + +void Klaymen::stIdleTeleporterHands() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x90EF8D38, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stSitIdleTeleporterBlinkSecond); +} + +void Klaymen::stIdleTeleporterHands2() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0x900F0930, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stSitIdleTeleporterBlinkSecond); +} + +void Klaymen::teleporterAppear(uint32 fileHash) { + _busyStatus = 0; + _acceptInput = false; + startAnimation(fileHash, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmTeleporterAppearDisappear); + SetSpriteUpdate(NULL); +} + +void Klaymen::teleporterDisappear(uint32 fileHash) { + _busyStatus = 0; + _acceptInput = false; + startAnimation(fileHash, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmTeleporterAppearDisappear); + SetSpriteUpdate(NULL); +} + +uint32 Klaymen::hmTeleporterAppearDisappear(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x4E0A2C24) { + playSound(0, 0x85B10BB8); + } else if (param.asInteger() == 0x4E6A0CA0) { + playSound(0, 0xC5B709B0); + } + break; + } + return messageResult; +} + +uint32 Klaymen::hmShrink(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x80C110B5) + sendMessage(_parentScene, 0x482A, 0); + else if (param.asInteger() == 0x33288344) + playSound(2, 0x10688664); + break; + } + return messageResult; +} + +void Klaymen::stShrink() { + _busyStatus = 0; + _acceptInput = false; + playSound(0, 0x4C69EA53); + startAnimation(0x1AE88904, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmShrink); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); +} + +void Klaymen::stStandWonderAbout() { + if (_x > 260) + setDoDeltaX(1); + _busyStatus = 0; + _acceptInput = true; + startAnimation(0xD820A114, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(NULL); + _newStickFrameIndex = 10; +} + +uint32 Klaymen::hmDrinkPotion(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x1008: + if (_potionFlag1) { + startAnimationByHash(0x1C388C04, 0x004A2148, 0); + messageResult = 0; + } else + _potionFlag2 = true; + break; + case 0x100D: + if (param.asInteger() == 0x0002418E) + sendMessage(_parentScene, 0x2000, 0); + else if (param.asInteger() == 0x924090C2) { + _potionFlag1 = true; + if (_potionFlag2) { + startAnimationByHash(0x1C388C04, 0x004A2148, 0); + messageResult = 0; + } + } else if (param.asInteger() == 0x004A2148) + _potionFlag1 = false; + else if (param.asInteger() == 0x02B20220) + playSound(0, 0xC5408620); + else if (param.asInteger() == 0x0A720138) + playSound(0, 0xD4C08010); + else if (param.asInteger() == 0x03020231) + playSound(0, 0xD4C08010); + else if (param.asInteger() == 0xB613A180) + playSound(0, 0x44051000); + else if (param.asInteger() == 0x67221A03) + playSound(0, 0x44051000); + else if (param.asInteger() == 0x038A010B) + playSound(0, 0x00018040); + else if (param.asInteger() == 0x422B0280) + playSound(0, 0x166FC6E0); + else if (param.asInteger() == 0x925A0C1E) + playSound(0, 0x40E5884D); + else if (param.asInteger() == 0x000F0082) + playSound(0, 0x546CDCC1); + else if (param.asInteger() == 0x00020814) + playSound(0, 0x786CC6D0); + else if (param.asInteger() == 0x06020500) + playSound(0, 0x1069C0E1); + else if (param.asInteger() == 0x02128C00) + playSound(0, 0x5068C4C3); + else if (param.asInteger() == 0x82022030) + playSound(0, 0x5C48C0E8); + break; + } + return messageResult; +} + +uint32 Klaymen::hmGrow(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x32180101) + playSound(0, 0x405002D8); + else if (param.asInteger() == 0x0A2A9098) + playSound(0, 0x0460E2FA); + else if (param.asInteger() == 0xD00A0C0C) + playSound(3); + else if (param.asInteger() == 0x04121920) + playSound(4); + else if (param.asInteger() == 0x030B4480) + playSound(5); + else if (param.asInteger() == 0x422B0280) + playSound(6); + else if (param.asInteger() == 0x038A010B) + playSound(7); + else if (param.asInteger() == 0x67221A03) + playSound(0, 0x44051000); + else if (param.asInteger() == 0x02B20220) + playSound(0, 0xC5408620); + else if (param.asInteger() == 0x925A0C1E) + playSound(0, 0x40E5884D); + else if (param.asInteger() == 0x03020231) + playSound(0, 0xD4C08010); + else if (param.asInteger() == 0x08040840) + setDoDeltaX(2); + break; + } + return messageResult; +} + +void Klaymen::stGrow() { + _busyStatus = 0; + _acceptInput = false; + startAnimation(0x2838C010, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmGrow); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); +} + +void Klaymen::stDrinkPotion() { + _busyStatus = 1; + _acceptInput = false; + _potionFlag1 = false; + _potionFlag2 = false; + startAnimation(0x1C388C04, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmDrinkPotion); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); +} + +uint32 Klaymen::hmInsertKey(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Klaymen::hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (_keysToInsert == 0 && param.asInteger() == 0x06040580) { + nextAnimationByHash(0xDC409440, 0x46431401, 0); + } else if (_keysToInsert != 0 && param.asInteger() == 0x46431401) { + _keysToInsert--; + startAnimationByHash(0xDC409440, 0x01084280, 0); + } else if (param.asInteger() == 0x062A1510) { + playSound(0, 0x41688704); + } else if (param.asInteger() == 0x02B20220) { + playSound(0, 0xC5408620); + } else if (param.asInteger() == 0x0A720138) { + playSound(0, 0xD4C08010); + } else if (param.asInteger() == 0xB613A180) { + playSound(0, 0x44051000); + } else if (param.asInteger() == 0x0E4C8141) { + playSound(0, 0xDC4A1280); + } + break; + } + return messageResult; +} + +void Klaymen::stInsertKey() { + if (!stStartActionFromIdle(AnimationCallback(&Klaymen::stInsertKey))) { + _busyStatus = 2; + _keysToInsert = 0; + for (uint32 i = 0; i < 3; i++) { + if (getSubVar(VA_HAS_KEY, i)) { + bool more; + setSubVar(VA_IS_KEY_INSERTED, i, 1); + setSubVar(VA_HAS_KEY, i, 0); + do { + more = false; + setSubVar(VA_CURR_KEY_SLOT_NUMBERS, i, _vm->_rnd->getRandomNumber(16 - 1)); + for (uint j = 0; j < i && !more; j++) { + if (getSubVar(VA_IS_KEY_INSERTED, j) && getSubVar(VA_CURR_KEY_SLOT_NUMBERS, j) == getSubVar(VA_CURR_KEY_SLOT_NUMBERS, i)) + more = true; + } + if (getSubVar(VA_CURR_KEY_SLOT_NUMBERS, i) == getSubVar(VA_GOOD_KEY_SLOT_NUMBERS, i)) + more = true; + } while (more); + _keysToInsert++; + } + } + if (_keysToInsert == 0) { + GotoState(NULL); + gotoNextStateExt(); + } else { + _acceptInput = false; + startAnimation(0xDC409440, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmInsertKey); + SetSpriteUpdate(&Klaymen::suAction); + _keysToInsert--; + } + } +} + +uint32 Klaymen::hmReadNote(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x04684052) { + _acceptInput = true; + sendMessage(_parentScene, 0x2002, 0); + } + break; + } + return messageResult; +} + +void Klaymen::stReadNote() { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x123E9C9F, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmReadNote); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); +} + +uint32 Klaymen::hmHitByDoor(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + int16 speedUpFrameIndex; + switch (messageNum) { + case 0x1008: + speedUpFrameIndex = getFrameIndex(kKlaymenSpeedUpHash); + if (_currFrameIndex < speedUpFrameIndex) { + startAnimation(0x35AA8059, speedUpFrameIndex, -1); + _y = 438; + } + messageResult = 0; + break; + case 0x100D: + if (param.asInteger() == 0x1A1A0785) { + playSound(0, 0x40F0A342); + } else if (param.asInteger() == 0x60428026) { + playSound(0, 0x40608A59); + } + break; + } + return messageResult; +} + +void Klaymen::stHitByDoor() { + _busyStatus = 1; + _acceptInput = false; + startAnimation(0x35AA8059, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmHitByDoor); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + playSound(0, 0x402E82D4); +} + +uint32 Klaymen::hmPeekWallReturn(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == calcHash("PopBalloon")) { + sendMessage(_parentScene, 0x2000, 0); + } else if (param.asInteger() == 0x02B20220) { + playSound(0, 0xC5408620); + } else if (param.asInteger() == 0x0A720138) { + playSound(0, 0xD4C08010); + } else if (param.asInteger() == 0xB613A180) { + playSound(0, 0x44051000); + } + break; + } + return messageResult; +} + +void Klaymen::upPeekWallBlink() { + Klaymen::update(); + _blinkCounter++; + if (_blinkCounter >= _blinkCounterMax) + stPeekWallBlink(); +} + +void Klaymen::stPeekWall1() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0xAC20C012, 8, 37); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stPeekWallBlink); +} + +void Klaymen::stPeekWall2() { + _busyStatus = 1; + _acceptInput = false; + startAnimation(0xAC20C012, 43, 49); + SetUpdateHandler(&Klaymen::update); + SetSpriteUpdate(NULL); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); +} + +void Klaymen::stPeekWallBlink() { + _blinkCounter = 0; + _busyStatus = 0; + _acceptInput = true; + _blinkCounterMax = _vm->_rnd->getRandomNumber(64) + 24; + startAnimation(0xAC20C012, 38, 42); + SetUpdateHandler(&Klaymen::upPeekWallBlink); + SetSpriteUpdate(NULL); + SetMessageHandler(&Klaymen::hmLowLevel); + _newStickFrameIndex = 42; +} + +void Klaymen::stPeekWallReturn() { + _busyStatus = 0; + _acceptInput = false; + startAnimation(0x2426932E, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmPeekWallReturn); + SetSpriteUpdate(NULL); +} + +void Klaymen::stPullHammerLever() { + if (!stStartAction(AnimationCallback(&Klaymen::stPullHammerLever))) { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x00648953, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmPullHammerLever); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + } +} + +uint32 Klaymen::hmPullHammerLever(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Klaymen::hmLever(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x4AB28209) + sendMessage(_attachedSprite, 0x480F, 0); + break; + } + return messageResult; +} + +void Klaymen::suRidePlatformDown() { + _platformDeltaY++; + _y += _platformDeltaY; + if (_y > 600) + sendMessage(this, 0x1019, 0); +} + +void Klaymen::stRidePlatformDown() { + if (!stStartActionFromIdle(AnimationCallback(&Klaymen::stRidePlatformDown))) { + _busyStatus = 1; + sendMessage(_parentScene, 0x4803, 0); + _acceptInput = false; + _platformDeltaY = 0; + startAnimation(0x5420E254, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(&Klaymen::suRidePlatformDown); + _vm->_soundMan->playSoundLooping(0xD3B02847); + } +} + +void Klaymen::stCrashDown() { + playSound(0, 0x41648271); + _busyStatus = 1; + _acceptInput = false; + startAnimationByHash(0x000BAB02, 0x88003000, 0); + SetUpdateHandler(&Klaymen::update); + SetSpriteUpdate(NULL); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + NextState(&Klaymen::stCrashDownFinished); +} + +void Klaymen::stCrashDownFinished() { + setDoDeltaX(2); + stTryStandIdle(); +} + +void Klaymen::upSpitOutFall() { + Klaymen::update(); + if (_spitOutCountdown != 0 && (--_spitOutCountdown == 0)) { + _surface->setVisible(true); + SetUpdateHandler(&Klaymen::update); + } +} + +uint32 Klaymen::hmJumpToRingVenusFlyTrap(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x168050A0) { + sendMessage(_attachedSprite, 0x480F, 0); + } else if (param.asInteger() == 0x586B0300) { + sendMessage(_otherSprite, 0x480E, 1); + } else if (param.asInteger() == 0x4AB28209) { + sendMessage(_attachedSprite, 0x482A, 0); + } else if (param.asInteger() == 0x88001184) { + sendMessage(_attachedSprite, 0x482B, 0); + } + break; + } + return messageResult; +} + +uint32 Klaymen::hmStandIdleSpecial(int messageNum, const MessageParam ¶m, Entity *sender) { + switch (messageNum) { + case 0x4811: + playSound(0, 0x5252A0E4); + setDoDeltaX(((Sprite*)sender)->isDoDeltaX() ? 1 : 0); + if (_doDeltaX) { + _x = ((Sprite*)sender)->getX() - 75; + } else { + _x = ((Sprite*)sender)->getX() + 75; + } + _y = ((Sprite*)sender)->getY() - 200; + if (param.asInteger() == 0) { + stSpitOutFall0(); + } else if (param.asInteger() == 1) { + // NOTE This is never used and the code was removed + // Also the animations used here in the original don't exist... + } else if (param.asInteger() == 2) { + stSpitOutFall2(); + } + break; + } + return 0; +} + +uint32 Klaymen::hmPressDoorButton(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x942D2081) { + _acceptInput = false; + sendMessage(_attachedSprite, 0x2003, 0); + } else if (param.asInteger() == 0xDA600012) { + stHitByBoxingGlove(); + } else if (param.asInteger() == 0x0D01B294) { + _acceptInput = false; + sendMessage(_attachedSprite, 0x480B, 0); + } + break; + } + return messageResult; +} + +uint32 Klaymen::hmMoveVenusFlyTrap(int messageNum, const MessageParam ¶m, Entity *sender) { + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x01084280) { + sendMessage(_attachedSprite, 0x480B, (uint32)_doDeltaX); + } else if (param.asInteger() == 0x02421405) { + if (_isMoveObjectRequested) { + if (sendMessage(_attachedSprite, 0x480C, (uint32)_doDeltaX) != 0) + stContinueMovingVenusFlyTrap(); + } else { + SetMessageHandler(&Klaymen::hmFirstMoveVenusFlyTrap); + } + } else if (param.asInteger() == 0x4AB28209) { + sendMessage(_attachedSprite, 0x482A, 0); + } else if (param.asInteger() == 0x88001184) { + sendMessage(_attachedSprite, 0x482B, 0); + } else if (param.asInteger() == 0x32180101) { + playSound(0, 0x405002D8); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, 0x0460E2FA); + } + break; + case 0x480A: + _isMoveObjectRequested = true; + return 0; + } + return hmLowLevelAnimation(messageNum, param, sender); +} + +uint32 Klaymen::hmFirstMoveVenusFlyTrap(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x4AB28209) { + sendMessage(_attachedSprite, 0x482A, 0); + } else if (param.asInteger() == 0x88001184) { + sendMessage(_attachedSprite, 0x482B, 0); + } else if (param.asInteger() == 0x32180101) { + playSound(0, 0x405002D8); + } else if (param.asInteger() == 0x0A2A9098) { + playSound(0, 0x0460E2FA); + } + break; + } + return messageResult; +} + +uint32 Klaymen::hmHitByBoxingGlove(int messageNum, const MessageParam ¶m, Entity *sender) { + int16 speedUpFrameIndex; + uint32 messageResult = hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x1008: + speedUpFrameIndex = getFrameIndex(kKlaymenSpeedUpHash); + if (_currFrameIndex < speedUpFrameIndex) { + startAnimation(0x35AA8059, speedUpFrameIndex, -1); + _y = 435; + } + messageResult = 0; + break; + case 0x100D: + if (param.asInteger() == 0x1A1A0785) { + playSound(0, 0x40F0A342); + } else if (param.asInteger() == 0x60428026) { + playSound(0, 0x40608A59); + } + break; + } + return messageResult; +} + +uint32 Klaymen::hmJumpAndFall(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmLowLevel(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x1307050A) { + playSound(0, 0x40428A09); + } + break; + } + return messageResult; +} + +void Klaymen::suFallDown() { + AnimatedSprite::updateDeltaXY(); + HitRect *hitRect = _parentScene->findHitRectAtPos(_x, _y + 10); + if (hitRect->type == 0x5001) { + _y = hitRect->rect.y1; + updateBounds(); + sendMessage(this, 0x1019, 0); + } + _parentScene->checkCollision(this, 0xFFFF, 0x4810, 0); +} + +void Klaymen::stJumpToRingVenusFlyTrap() { + if (!stStartAction(AnimationCallback(&Klaymen::stJumpToRingVenusFlyTrap))) { + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x584984B4, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmJumpToRingVenusFlyTrap); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + NextState(&Klaymen::stLandOnFeet); + sendMessage(_attachedSprite, 0x482B, 0); + } +} + +void Klaymen::stStandIdleSpecial() { + playSound(0, 0x56548280); + _busyStatus = 0; + _acceptInput = false; + _surface->setVisible(false); + startAnimation(0x5420E254, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmStandIdleSpecial); + SetSpriteUpdate(NULL); +} + +void Klaymen::stSpitOutFall0() { + _spitOutCountdown = 1; + _busyStatus = 0; + _acceptInput = false; + startAnimation(0x000BAB02, 0, -1); + SetUpdateHandler(&Klaymen::upSpitOutFall); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(&Klaymen::suFallDown); + NextState(&Klaymen::stFalling); + sendMessage(_parentScene, 0x8000, 0); +} + +void Klaymen::stSpitOutFall2() { + _spitOutCountdown = 1; + _busyStatus = 0; + _acceptInput = false; + startAnimation(0x9308C132, 0, -1); + SetUpdateHandler(&Klaymen::upSpitOutFall); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(&Klaymen::suFallDown); + NextState(&Klaymen::stFalling); + sendMessage(_parentScene, 0x8000, 0); +} + +void Klaymen::stFalling() { + sendMessage(_parentScene, 0x1024, 1); + playSound(0, 0x41648271); + _busyStatus = 1; + _acceptInput = false; + _isWalking = false; + startAnimationByHash(0x000BAB02, 0x88003000, 0); + SetUpdateHandler(&Klaymen::update); + SetSpriteUpdate(NULL); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + NextState(&Klaymen::stFallTouchdown); + sendMessage(_parentScene, 0x2002, 0); + _attachedSprite = NULL; + sendMessage(_parentScene, 0x8001, 0); +} + +void Klaymen::stFallTouchdown() { + setDoDeltaX(2); + stTryStandIdle(); +} + +void Klaymen::stJumpAndFall() { + if (!stStartAction(AnimationCallback(&Klaymen::stJumpAndFall))) { + sendMessage(_parentScene, 0x1024, 3); + _busyStatus = 2; + _acceptInput = false; + startAnimation(0xB93AB151, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmJumpAndFall); + SetSpriteUpdate(&Klaymen::suFallDown); + NextState(&Klaymen::stLandOnFeet); + } +} + +void Klaymen::stDropFromRing() { + if (_attachedSprite) { + _x = _attachedSprite->getX(); + sendMessage(_attachedSprite, 0x4807, 0); + _attachedSprite = NULL; + } + _busyStatus = 2; + _acceptInput = false; + startAnimation(0x586984B1, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(&Klaymen::suFallDown); + NextState(&Klaymen::stLandOnFeet); +} + +void Klaymen::stPressDoorButton() { + _busyStatus = 2; + _acceptInput = true; + setDoDeltaX(0); + startAnimation(0x1CD89029, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmPressDoorButton); + SetSpriteUpdate(&Klaymen::suAction); +} + +void Klaymen::stHitByBoxingGlove() { + _busyStatus = 1; + _acceptInput = false; + startAnimation(0x35AA8059, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmHitByBoxingGlove); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + FinalizeState(&Klaymen::evHitByBoxingGloveDone); +} + +void Klaymen::evHitByBoxingGloveDone() { + sendMessage(_parentScene, 0x1024, 1); +} + +void Klaymen::stMoveVenusFlyTrap() { + if (!stStartAction(AnimationCallback(&Klaymen::stMoveVenusFlyTrap))) { + _busyStatus = 2; + _isMoveObjectRequested = false; + _acceptInput = true; + setDoDeltaX(_attachedSprite->getX() < _x ? 1 : 0); + startAnimation(0x5C01A870, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmMoveVenusFlyTrap); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + FinalizeState(&Klaymen::evMoveVenusFlyTrapDone); + } +} + +void Klaymen::stContinueMovingVenusFlyTrap() { + _isMoveObjectRequested = false; + _acceptInput = true; + startAnimationByHash(0x5C01A870, 0x01084280, 0); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmMoveVenusFlyTrap); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + FinalizeState(&Klaymen::evMoveVenusFlyTrapDone); +} + +void Klaymen::evMoveVenusFlyTrapDone() { + sendMessage(_attachedSprite, 0x482A, 0); +} + +void Klaymen::suFallSkipJump() { + updateDeltaXY(); + HitRect *hitRect = _parentScene->findHitRectAtPos(_x, _y + 10); + if (hitRect->type == 0x5001) { + _y = hitRect->rect.y1; + updateBounds(); + sendMessage(this, 0x1019, 0); + } +} + +void Klaymen::stFallSkipJump() { + _busyStatus = 2; + _acceptInput = false; + startAnimationByHash(0xB93AB151, 0x40A100F8, 0); + SetUpdateHandler(&Klaymen::update); + SetSpriteUpdate(&Klaymen::suFallSkipJump); + SetMessageHandler(&Klaymen::hmLowLevel); + NextState(&Klaymen::stLandOnFeet); +} + +void Klaymen::upMoveObject() { + if (_x >= 380) + gotoNextStateExt(); + Klaymen::update(); +} + +uint32 Klaymen::hmMatch(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Klaymen::hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x51281850) { + setGlobalVar(V_TNT_DUMMY_FUSE_LIT, 1); + } else if (param.asInteger() == 0x43000538) { + playSound(0, 0x21043059); + } else if (param.asInteger() == 0x02B20220) { + playSound(0, 0xC5408620); + } else if (param.asInteger() == 0x0A720138) { + playSound(0, 0xD4C08010); + } else if (param.asInteger() == 0xB613A180) { + playSound(0, 0x44051000); + } + break; + } + return messageResult; +} + +void Klaymen::stFetchMatch() { + if (!stStartAction(AnimationCallback(&Klaymen::stFetchMatch))) { + _busyStatus = 0; + _acceptInput = false; + setDoDeltaX(_attachedSprite->getX() < _x ? 1 : 0); + startAnimation(0x9CAA0218, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmMatch); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stLightMatch); + } +} + +void Klaymen::stLightMatch() { + _busyStatus = 1; + _acceptInput = false; + setDoDeltaX(_attachedSprite->getX() < _x ? 1 : 0); + startAnimation(0x1222A513, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmMatch); + SetSpriteUpdate(NULL); +} + +uint32 Klaymen::hmMoveObject(int messageNum, const MessageParam ¶m, Entity *sender) { + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x01084280) { + playSound(0, 0x405002D8); + sendMessage(_attachedSprite, 0x480B, 0); + } else if (param.asInteger() == 0x02421405) { + if (_moveObjectCountdown != 0) { + _moveObjectCountdown--; + stContinueMoveObject(); + } else { + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + } + } + break; + } + return Klaymen::hmLowLevelAnimation(messageNum, param, sender); +} + +uint32 Klaymen::hmTumbleHeadless(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Klaymen::hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x000F0082) { + playSound(0, 0x74E2810F); + } + break; + } + return messageResult; +} + +void Klaymen::stMoveObject() { + if (!stStartAction(AnimationCallback(&Klaymen::stMoveObject))) { + _busyStatus = 2; + _acceptInput = false; + _moveObjectCountdown = 8; + setDoDeltaX(0); + startAnimation(0x0C1CA072, 0, -1); + SetUpdateHandler(&Klaymen::upMoveObject); + SetMessageHandler(&Klaymen::hmMoveObject); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + } +} + +void Klaymen::stContinueMoveObject() { + _acceptInput = false; + startAnimationByHash(0x0C1CA072, 0x01084280, 0); + SetUpdateHandler(&Klaymen::upMoveObject); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + SetMessageHandler(&Klaymen::hmMoveObject); +} + +void Klaymen::stTumbleHeadless() { + if (!stStartActionFromIdle(AnimationCallback(&Klaymen::stTumbleHeadless))) { + _busyStatus = 1; + _acceptInput = false; + setDoDeltaX(0); + startAnimation(0x2821C590, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmTumbleHeadless); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + NextState(&Klaymen::stTryStandIdle); + sendMessage(_parentScene, 0x8000, 0); + playSound(0, 0x62E0A356); + } +} + +void Klaymen::stCloseEyes() { + if (!stStartActionFromIdle(AnimationCallback(&Klaymen::stCloseEyes))) { + _busyStatus = 1; + _acceptInput = false; + startAnimation(0x5420E254, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(NULL); + } +} + +uint32 Klaymen::hmSpit(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Klaymen::hmLowLevelAnimation(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x16401CA6) { + _canSpitPipe = true; + if (_contSpitPipe) + spitIntoPipe(); + } else if (param.asInteger() == 0xC11C0008) { + _canSpitPipe = false; + _acceptInput = false; + _readyToSpit = false; + } else if (param.asInteger() == 0x018A0001) { + sendMessage(_parentScene, 0x2001, _spitDestPipeIndex); + } + break; + } + return messageResult; +} + +void Klaymen::stTrySpitIntoPipe() { + if (_readyToSpit) { + _contSpitPipe = true; + _spitContDestPipeIndex = _spitPipeIndex; + if (_canSpitPipe) + spitIntoPipe(); + } else if (!stStartAction(AnimationCallback(&Klaymen::stTrySpitIntoPipe))) { + _busyStatus = 2; + _acceptInput = true; + _spitDestPipeIndex = _spitPipeIndex; + _readyToSpit = true; + _canSpitPipe = false; + _contSpitPipe = false; + startAnimation(0x1808B150, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmSpit); + SetSpriteUpdate(NULL); + } +} + +void Klaymen::spitIntoPipe() { + _contSpitPipe = false; + _spitDestPipeIndex = _spitContDestPipeIndex; + _canSpitPipe = false; + _acceptInput = false; + startAnimation(0x1B08B553, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmSpit); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stContSpitIntoPipe); +} + +void Klaymen::stContSpitIntoPipe() { + _canSpitPipe = true; + _acceptInput = true; + startAnimationByHash(0x1808B150, 0x16401CA6, 0); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmSpit); + SetSpriteUpdate(NULL); +} + +void Klaymen::suRidePlatform() { + _x = _attachedSprite->getX() - 20; + _y = _attachedSprite->getY() + 46; + updateBounds(); +} + +void Klaymen::stRidePlatform() { + if (!stStartActionFromIdle(AnimationCallback(&Klaymen::stRidePlatform))) { + _busyStatus = 1; + _acceptInput = true; + startAnimation(0x5420E254, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(&Klaymen::suRidePlatform); + } +} + +void Klaymen::stInteractLever() { + if (!stStartAction(AnimationCallback(&Klaymen::stInteractLever))) { + _busyStatus = 0; + if (_isLeverDown) { + stUseLeverRelease(); + } else { + _acceptInput = false; + startAnimation(0x0C303040, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLever); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + NextState(&Klaymen::stPullLever); + } + } +} + +void Klaymen::stPullLever() { + startAnimation(0x0D318140, 0, -1); + NextState(&Klaymen::stLookLeverDown); + sendMessage(_attachedSprite, 0x480F, 0); +} + +void Klaymen::stLookLeverDown() { + _acceptInput = true; + _isLeverDown = true; + startAnimation(0x1564A2C0, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetSpriteUpdate(&Klaymen::suUpdateDestX); + NextState(&Klaymen::stWaitLeverDown); +} + +void Klaymen::stWaitLeverDown() { + _acceptInput = true; + _isLeverDown = true; + startAnimation(0x4464A440, 0, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(&Klaymen::suUpdateDestX); +} + +void Klaymen::stStartWalkingResume() { + int16 frameIndex = getGlobalVar(V_KLAYMEN_FRAMEINDEX) + _walkResumeFrameIncr; + if (frameIndex < 0 || frameIndex > 13) + frameIndex = 0; + _busyStatus = 0; + _isWalking = true; + _acceptInput = true; + startAnimation(0x1A249001, frameIndex, -1); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmWalking); + SetSpriteUpdate(&Klaymen::suWalkingFirst); + NextState(&Klaymen::stUpdateWalkingFirst); + FinalizeState(&Klaymen::evStartWalkingDone); +} + +void Klaymen::upPeekInsideBlink() { + update(); + ++_blinkCounter; + if (_blinkCounter >= _blinkCounterMax) + stPeekInsideBlink(); +} + +void Klaymen::stPeekInside() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0xAC20C012, 8, 37); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); + NextState(&Klaymen::stPeekInsideBlink); +} + +void Klaymen::stPeekInsideReturn() { + _busyStatus = 1; + _acceptInput = false; + startAnimation(0xAC20C012, 43, 49); + SetUpdateHandler(&Klaymen::update); + SetMessageHandler(&Klaymen::hmLowLevelAnimation); + SetSpriteUpdate(NULL); +} + +void Klaymen::stPeekInsideBlink() { + _busyStatus = 0; + _acceptInput = true; + startAnimation(0xAC20C012, 38, 42); + _newStickFrameIndex = 42; + SetUpdateHandler(&Klaymen::upPeekInsideBlink); + SetMessageHandler(&Klaymen::hmLowLevel); + SetSpriteUpdate(NULL); + _blinkCounter = 0; + _blinkCounterMax = _vm->_rnd->getRandomNumber(64 - 1) + 24; +} + +// KmScene1001 + +KmScene1001::KmScene1001(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { +} + +uint32 KmScene1001::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4804: + if (param.asInteger() == 2) + GotoState(&Klaymen::stSleeping); + break; + case 0x480D: + GotoState(&Klaymen::stPullHammerLever); + break; + case 0x4812: + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4816: + if (param.asInteger() == 1) + GotoState(&Klaymen::stPressButton); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stPressFloorButton); + else + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481F: + if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x4836: + if (param.asInteger() == 1) { + sendMessage(_parentScene, 0x2002, 0); + GotoState(&Klaymen::stWakeUp); + } + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +// KmScene1002 + +KmScene1002::KmScene1002(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + setKlaymenIdleTable1(); +} + +void KmScene1002::xUpdate() { + if (_x >= 250 && _x <= 435 && _y >= 420) { + if (_idleTableNum == 0) { + setKlaymenIdleTable(klaymenIdleTable1002, ARRAYSIZE(klaymenIdleTable1002)); + _idleTableNum = 1; + } + } else if (_idleTableNum == 1) { + setKlaymenIdleTable1(); + _idleTableNum = 0; + } +} + +uint32 KmScene1002::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x2001: + GotoState(&Klaymen::stStandIdleSpecial); + break; + case 0x2007: + _otherSprite = (Sprite*)param.asEntity(); + break; + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4803: + if (param.asInteger() == 1) + GotoState(&Klaymen::stJumpAndFall); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stDropFromRing); + break; + case 0x4804: + GotoState(&Klaymen::stPeekWall); + break; + case 0x4805: + switch (param.asInteger()) { + case 1: + GotoState(&Klaymen::stJumpToRing1); + break; + case 2: + GotoState(&Klaymen::stJumpToRing2); + break; + case 3: + GotoState(&Klaymen::stJumpToRing3); + break; + case 4: + GotoState(&Klaymen::stJumpToRing4); + break; + } + break; + case 0x480A: + GotoState(&Klaymen::stMoveVenusFlyTrap); + break; + case 0x480D: + GotoState(&Klaymen::stJumpToRingVenusFlyTrap); + break; + case 0x4816: + if (param.asInteger() == 0) + GotoState(&Klaymen::stPressDoorButton); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + startWalkToAttachedSpriteXDistance(param.asInteger()); + break; + case 0x4820: + sendMessage(_parentScene, 0x2005, 0); + GotoState(&Klaymen::stContinueClimbLadderUp); + break; + case 0x4821: + sendMessage(_parentScene, 0x2005, 0); + _destY = param.asInteger(); + GotoState(&Klaymen::stStartClimbLadderDown); + break; + case 0x4822: + sendMessage(_parentScene, 0x2005, 0); + _destY = param.asInteger(); + GotoState(&Klaymen::stStartClimbLadderUp); + break; + case 0x4823: + sendMessage(_parentScene, 0x2006, 0); + GotoState(&Klaymen::stClimbLadderHalf); + break; + case 0x482E: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWalkToFrontNoStep); + else + GotoState(&Klaymen::stWalkToFront); + break; + case 0x482F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnToFront); + else + GotoState(&Klaymen::stTurnToBack); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +// KmScene1004 + +KmScene1004::KmScene1004(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + _dataResource.load(0x01900A04); +} + +uint32 KmScene1004::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x4818: + startWalkToX(_dataResource.getPoint(param.asInteger()).x, false); + break; + case 0x481E: + GotoState(&Klaymen::stReadNote); + break; + case 0x4820: + sendMessage(_parentScene, 0x2000, 0); + GotoState(&Klaymen::stContinueClimbLadderUp); + break; + case 0x4821: + sendMessage(_parentScene, 0x2000, 0); + _destY = param.asInteger(); + GotoState(&Klaymen::stStartClimbLadderDown); + break; + case 0x4822: + sendMessage(_parentScene, 0x2000, 0); + _destY = param.asInteger(); + GotoState(&Klaymen::stStartClimbLadderUp); + break; + case 0x4823: + sendMessage(_parentScene, 0x2001, 0); + GotoState(&Klaymen::stClimbLadderHalf); + break; + case 0x4824: + sendMessage(_parentScene, 0x2000, 0); + _destY = _dataResource.getPoint(param.asInteger()).y; + GotoState(&Klaymen::stStartClimbLadderDown); + break; + case 0x4825: + sendMessage(_parentScene, 0x2000, 0); + _destY = _dataResource.getPoint(param.asInteger()).y; + GotoState(&Klaymen::stStartClimbLadderUp); + break; + case 0x4828: + GotoState(&Klaymen::stTurnToBackToUse); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +KmScene1109::KmScene1109(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene1109::xHandleMessage(int messageNum, const MessageParam ¶m) { + uint32 messageResult = 0; + switch (messageNum) { + case 0x2000: + _isSittingInTeleporter = param.asInteger() != 0; + messageResult = 1; + break; + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stSitIdleTeleporter); + else + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4804: + if (param.asInteger() != 0) { + _destX = param.asInteger(); + GotoState(&Klaymen::stWalkingFirst); + } else + GotoState(&Klaymen::stPeekWall); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481D: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stTurnToUseInTeleporter); + break; + case 0x481E: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stReturnFromUseInTeleporter); + break; + case 0x4834: + GotoState(&Klaymen::stStepOver); + break; + case 0x4835: + sendMessage(_parentScene, 0x2000, 1); + _isSittingInTeleporter = true; + GotoState(&Klaymen::stSitInTeleporter); + break; + case 0x4836: + sendMessage(_parentScene, 0x2000, 0); + _isSittingInTeleporter = false; + GotoState(&Klaymen::stGetUpFromTeleporter); + break; + case 0x483D: + teleporterAppear(0x2C2A4A1C); + break; + case 0x483E: + teleporterDisappear(0x3C2E4245); + break; + } + return messageResult; +} + +// KmScene1201 + +KmScene1201::KmScene1201(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + setKlaymenIdleTable(klaymenIdleTable4, ARRAYSIZE(klaymenIdleTable4)); + _doYHitIncr = true; +} + +uint32 KmScene1201::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x480A: + GotoState(&Klaymen::stMoveObject); + break; + case 0x4812: + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4813: + GotoState(&Klaymen::stFetchMatch); + break; + case 0x4814: + GotoState(&Klaymen::stTumbleHeadless); + break; + case 0x4815: + GotoState(&Klaymen::stCloseEyes); + break; + case 0x4816: + if (param.asInteger() == 0) + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + GotoState(&Klaymen::stTurnToUse); + break; + case 0x481E: + GotoState(&Klaymen::stReturnFromUse); + break; + case 0x481F: + GotoState(&Klaymen::stWonderAbout); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +KmScene1303::KmScene1303(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene1303::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4804: + GotoState(&Klaymen::stPeekWall1); + break; + case 0x483B: + GotoState(&Klaymen::stPeekWallReturn); + break; + case 0x483C: + GotoState(&Klaymen::stPeekWall2); + break; + } + return 0; +} + +KmScene1304::KmScene1304(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene1304::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4812: + if (param.asInteger() == 2) + GotoState(&Klaymen::stPickUpNeedle); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnAwayFromUse); + else if (param.asInteger() == 0) + GotoState(&Klaymen::stTurnToUseHalf); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +KmScene1305::KmScene1305(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene1305::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4804: + GotoState(&Klaymen::stCrashDown); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + } + return 0; +} + +KmScene1306::KmScene1306(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene1306::xHandleMessage(int messageNum, const MessageParam ¶m) { + uint32 messageResult = 0; + switch (messageNum) { + case 0x2000: + _isSittingInTeleporter = param.asInteger() != 0; + messageResult = 1; + break; + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stSitIdleTeleporter); + else + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4812: + if (param.asInteger() == 2) + GotoState(&Klaymen::stPickUpNeedle); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4816: + if (param.asInteger() == 1) + GotoState(&Klaymen::stPressButton); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stPressFloorButton); + else + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481A: + GotoState(&Klaymen::stInsertDisk); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stTurnToUseInTeleporter); + else + GotoState(&Klaymen::stTurnToUse); + break; + case 0x481E: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stReturnFromUseInTeleporter); + else + GotoState(&Klaymen::stReturnFromUse); + break; + case 0x481F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x482E: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWalkToFrontNoStep); + else + GotoState(&Klaymen::stWalkToFront); + break; + case 0x482F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnToFront); + else + GotoState(&Klaymen::stTurnToBack); + break; + case 0x4834: + GotoState(&Klaymen::stStepOver); + break; + case 0x4835: + sendMessage(_parentScene, 0x2000, 1); + _isSittingInTeleporter = true; + GotoState(&Klaymen::stSitInTeleporter); + break; + case 0x4836: + sendMessage(_parentScene, 0x2000, 0); + _isSittingInTeleporter = false; + GotoState(&Klaymen::stGetUpFromTeleporter); + break; + case 0x483D: + teleporterAppear(0xEE084A04); + break; + case 0x483E: + teleporterDisappear(0xB86A4274); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return messageResult; +} + +KmScene1308::KmScene1308(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene1308::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x480A: + if (param.asInteger() == 1) + GotoState(&Klaymen::stMoveObjectSkipTurnFaceObject); + else + GotoState(&Klaymen::stMoveObjectFaceObject); + break; + case 0x480D: + GotoState(&Klaymen::stUseLever); + break; + case 0x4812: + if (param.asInteger() == 2) + GotoState(&Klaymen::stPickUpNeedle); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481A: + if (param.asInteger() == 1) + GotoState(&Klaymen::stInsertKey); + else + GotoState(&Klaymen::stInsertDisk); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + GotoState(&Klaymen::stTurnToUse); + break; + case 0x481E: + GotoState(&Klaymen::stReturnFromUse); + break; + case 0x4827: + GotoState(&Klaymen::stReleaseLever); + break; + case 0x4834: + GotoState(&Klaymen::stStepOver); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +// KmScene1401 + +KmScene1401::KmScene1401(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene1401::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x480A: + if (param.asInteger() == 1) + GotoState(&Klaymen::stMoveObjectSkipTurnFaceObject); + else + GotoState(&Klaymen::stMoveObjectFaceObject); + break; + case 0x4816: + if (param.asInteger() == 1) + GotoState(&Klaymen::stPressButton); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stPressFloorButton); + else + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnAwayFromUse); + else if (param.asInteger() == 0) + GotoState(&Klaymen::stTurnToUseHalf); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x482E: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWalkToFrontNoStep); + else + GotoState(&Klaymen::stWalkToFront); + break; + case 0x482F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnToFront); + else + GotoState(&Klaymen::stTurnToBack); + break; + } + return 0; +} + +// KmScene1402 + +KmScene1402::KmScene1402(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + SetFilterY(&Sprite::defFilterY); +} + +uint32 KmScene1402::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x480A: + if (param.asInteger() == 1) + GotoState(&Klaymen::stMoveObjectSkipTurnFaceObject); + else + GotoState(&Klaymen::stMoveObjectFaceObject); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + GotoState(&Klaymen::stTurnToUse); + break; + case 0x481E: + GotoState(&Klaymen::stReturnFromUse); + break; + } + return 0; +} + +// KmScene1403 + +KmScene1403::KmScene1403(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + setKlaymenIdleTable(klaymenIdleTable4, ARRAYSIZE(klaymenIdleTable4)); +} + +uint32 KmScene1403::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x480A: + if (param.asInteger() == 1) + GotoState(&Klaymen::stMoveObjectSkipTurnFaceObject); + else + GotoState(&Klaymen::stMoveObjectFaceObject); + break; + case 0x480D: + GotoState(&Klaymen::stUseLever); + break; + case 0x4812: + if (param.asInteger() == 2) + GotoState(&Klaymen::stPickUpNeedle); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x4827: + GotoState(&Klaymen::stReleaseLever); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +// KmScene1404 + +KmScene1404::KmScene1404(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene1404::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x480A: + if (param.asInteger() == 1) + GotoState(&Klaymen::stMoveObjectSkipTurnFaceObject); + else + GotoState(&Klaymen::stMoveObjectFaceObject); + break; + case 0x4812: + if (param.asInteger() == 2) + GotoState(&Klaymen::stPickUpNeedle); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481A: + GotoState(&Klaymen::stInsertDisk); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + GotoState(&Klaymen::stTurnToUse); + break; + case 0x481E: + GotoState(&Klaymen::stReturnFromUse); + break; + case 0x481F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +KmScene1608::KmScene1608(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene1608::xHandleMessage(int messageNum, const MessageParam ¶m) { + uint32 messageResult = 0; + switch (messageNum) { + case 0x2032: + _isSittingInTeleporter = param.asInteger() != 0; + messageResult = 1; + break; + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stSitIdleTeleporter); + else + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4812: + if (param.asInteger() == 2) + GotoState(&Klaymen::stPickUpNeedle); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stTurnToUseInTeleporter); + break; + case 0x481E: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stReturnFromUseInTeleporter); + break; + case 0x481F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x4834: + GotoState(&Klaymen::stStepOver); + break; + case 0x4835: + sendMessage(_parentScene, 0x2032, 1); + _isSittingInTeleporter = true; + GotoState(&Klaymen::stSitInTeleporter); + break; + case 0x4836: + sendMessage(_parentScene, 0x2032, 0); + _isSittingInTeleporter = false; + GotoState(&Klaymen::stGetUpFromTeleporter); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return messageResult; +} + +// KmScene1705 + +KmScene1705::KmScene1705(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene1705::xHandleMessage(int messageNum, const MessageParam ¶m) { + uint32 messageResult = 0; + switch (messageNum) { + case 0x2000: + _isSittingInTeleporter = param.asInteger() != 0; + messageResult = 1; + break; + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stSitIdleTeleporter); + else + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4803: + GotoState(&Klaymen::stFallSkipJump); + break; + case 0x4812: + if (param.asInteger() == 2) + GotoState(&Klaymen::stPickUpNeedle); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + if (_isSittingInTeleporter) { + GotoState(&Klaymen::stTurnToUseInTeleporter); + } + break; + case 0x481E: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stReturnFromUseInTeleporter); + break; + case 0x481F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x4834: + GotoState(&Klaymen::stStepOver); + break; + case 0x4835: + sendMessage(_parentScene, 0x2000, 1); + _isSittingInTeleporter = true; + GotoState(&Klaymen::stSitInTeleporter); + break; + case 0x4836: + sendMessage(_parentScene, 0x2000, 0); + _isSittingInTeleporter = false; + GotoState(&Klaymen::stGetUpFromTeleporter); + break; + case 0x483D: + teleporterAppear(0x5E0A4905); + break; + case 0x483E: + teleporterDisappear(0xD86E4477); + break; + } + return messageResult; +} + +KmScene1901::KmScene1901(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene1901::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481D: + GotoState(&Klaymen::stTurnToUse); + break; + case 0x481E: + GotoState(&Klaymen::stReturnFromUse); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +KmScene2001::KmScene2001(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2001::xHandleMessage(int messageNum, const MessageParam ¶m) { + uint32 messageResult = 0; + switch (messageNum) { + case 0x2000: + _isSittingInTeleporter = param.asInteger() != 0; + messageResult = 1; + break; + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stSitIdleTeleporter); + else + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4804: + if (param.asInteger() != 0) { + _destX = param.asInteger(); + GotoState(&Klaymen::stWalkingFirst); + } else + GotoState(&Klaymen::stPeekWall); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481D: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stTurnToUseInTeleporter); + break; + case 0x481E: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stReturnFromUseInTeleporter); + break; + case 0x4834: + GotoState(&Klaymen::stStepOver); + break; + case 0x4835: + sendMessage(_parentScene, 0x2000, 1); + _isSittingInTeleporter = true; + GotoState(&Klaymen::stSitInTeleporter); + break; + case 0x4836: + sendMessage(_parentScene, 0x2000, 0); + _isSittingInTeleporter = false; + GotoState(&Klaymen::stGetUpFromTeleporter); + break; + case 0x483D: + teleporterAppear(0xBE68CC54); + break; + case 0x483E: + teleporterDisappear(0x18AB4ED4); + break; + } + return messageResult; +} + +KmScene2101::KmScene2101(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2101::xHandleMessage(int messageNum, const MessageParam ¶m) { + uint32 messageResult = 0; + switch (messageNum) { + case 0x2000: + _isSittingInTeleporter = param.asInteger() != 0; + messageResult = 1; + break; + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stSitIdleTeleporter); + else + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4811: + GotoState(&Klaymen::stHitByDoor); + break; + case 0x4812: + if (param.asInteger() == 2) + GotoState(&Klaymen::stPickUpNeedle); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4816: + if (param.asInteger() == 1) + GotoState(&Klaymen::stPressButton); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stPressFloorButton); + else + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stTurnToUseInTeleporter); + break; + case 0x481E: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stReturnFromUseInTeleporter); + break; + case 0x4834: + GotoState(&Klaymen::stStepOver); + break; + case 0x4835: + sendMessage(_parentScene, 0x2000, 1); + _isSittingInTeleporter = true; + GotoState(&Klaymen::stSitInTeleporter); + break; + case 0x4836: + sendMessage(_parentScene, 0x2000, 0); + _isSittingInTeleporter = false; + GotoState(&Klaymen::stGetUpFromTeleporter); + break; + case 0x483D: + teleporterAppear(0xFF290E30); + break; + case 0x483E: + teleporterDisappear(0x9A28CA1C); + break; + } + return messageResult; +} + +KmScene2201::KmScene2201(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, NRect *clipRects, int clipRectsCount) + : Klaymen(vm, parentScene, x, y) { + + _surface->setClipRects(clipRects, clipRectsCount); + _dataResource.load(0x04104242); +} + +uint32 KmScene2201::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4812: + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4816: + if (param.asInteger() == 0) + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x4818: + startWalkToX(_dataResource.getPoint(param.asInteger()).x, false); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + GotoState(&Klaymen::stTurnToUse); + break; + case 0x481E: + GotoState(&Klaymen::stReturnFromUse); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x482E: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWalkToFrontNoStep); + else + GotoState(&Klaymen::stWalkToFront); + break; + case 0x482F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnToFront); + else + GotoState(&Klaymen::stTurnToBack); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +KmScene2203::KmScene2203(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2203::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4812: + if (param.asInteger() == 2) + GotoState(&Klaymen::stPickUpNeedle); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4816: + if (param.asInteger() == 1) + GotoState(&Klaymen::stPressButton); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stPressFloorButton); + else + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x4818: + startWalkToX(_dataResource.getPoint(param.asInteger()).x, false); + break; + case 0x4819: + GotoState(&Klaymen::stClayDoorOpen); + break; + case 0x481A: + GotoState(&Klaymen::stInsertDisk); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + GotoState(&Klaymen::stTurnToUse); + break; + case 0x481E: + GotoState(&Klaymen::stReturnFromUse); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +KmScene2205::KmScene2205(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +void KmScene2205::xUpdate() { + setGlobalVar(V_KLAYMEN_FRAMEINDEX, _currFrameIndex); +} + +uint32 KmScene2205::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4804: + if (param.asInteger() != 0) { + _destX = param.asInteger(); + GotoState(&Klaymen::stStartWalkingResume); + } else + GotoState(&Klaymen::stPeekWall); + break; + case 0x4816: + if (param.asInteger() == 0) + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x4818: + startWalkToX(_dataResource.getPoint(param.asInteger()).x, false); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +KmScene2206::KmScene2206(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + _walkResumeFrameIncr = 1; + _vm->_soundMan->addSound(0x80101800, 0xD3B02847); +} + +KmScene2206::~KmScene2206() { + _vm->_soundMan->deleteSoundGroup(0x80101800); +} + +void KmScene2206::xUpdate() { + setGlobalVar(V_KLAYMEN_FRAMEINDEX, _currFrameIndex); +} + +uint32 KmScene2206::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4803: + GotoState(&Klaymen::stRidePlatformDown); + break; + case 0x4804: + if (param.asInteger() != 0) { + _destX = param.asInteger(); + GotoState(&Klaymen::stStartWalkingResume); + } else + GotoState(&Klaymen::stPeekWall); + break; + case 0x4812: + if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4816: + if (param.asInteger() == 1) + GotoState(&Klaymen::stPressButton); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stPressFloorButton); + else + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481F: + if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x482E: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWalkToFrontNoStep); + else + GotoState(&Klaymen::stWalkToFront); + break; + case 0x482F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnToFront); + else + GotoState(&Klaymen::stTurnToBack); + break; + case 0x4837: + stopWalking(); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +KmScene2207::KmScene2207(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2207::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x2001: + GotoState(&Klaymen::stRidePlatform); + break; + case 0x2005: + suRidePlatform(); + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x480D: + GotoState(&Klaymen::stInteractLever); + break; + case 0x4812: + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4816: + if (param.asInteger() == 1) + GotoState(&Klaymen::stPressButton); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stPressFloorButton); + else + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x4827: + GotoState(&Klaymen::stReleaseLever); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +KmScene2242::KmScene2242(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +void KmScene2242::xUpdate() { + setGlobalVar(V_KLAYMEN_FRAMEINDEX, _currFrameIndex); +} + +uint32 KmScene2242::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4804: + if (param.asInteger() != 0) { + _destX = param.asInteger(); + GotoState(&Klaymen::stStartWalkingResume); + } else + GotoState(&Klaymen::stPeekWall); + break; + case 0x4812: + if (param.asInteger() == 2) + GotoState(&Klaymen::stPickUpNeedle); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481F: + if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x4837: + stopWalking(); + break; + } + return 0; +} + +KmHallOfRecords::KmHallOfRecords(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + // Empty +} + +void KmHallOfRecords::xUpdate() { + setGlobalVar(V_KLAYMEN_FRAMEINDEX, _currFrameIndex); +} + +uint32 KmHallOfRecords::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4804: + if (param.asInteger() != 0) { + _destX = param.asInteger(); + GotoState(&Klaymen::stStartWalkingResume); + } else + GotoState(&Klaymen::stPeekWall); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481F: + if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x4837: + stopWalking(); + break; + } + return 0; +} + +KmScene2247::KmScene2247(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +void KmScene2247::xUpdate() { + setGlobalVar(V_KLAYMEN_FRAMEINDEX, _currFrameIndex); +} + +uint32 KmScene2247::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4804: + if (param.asInteger() != 0) { + _destX = param.asInteger(); + GotoState(&Klaymen::stStartWalkingResume); + } else + GotoState(&Klaymen::stPeekWall); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481F: + if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x4837: + stopWalking(); + break; + } + return 0; +} + +KmScene2401::KmScene2401(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2401::xHandleMessage(int messageNum, const MessageParam ¶m) { + uint32 messageResult = 0; + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4816: + if (param.asInteger() == 1) + GotoState(&Klaymen::stPressButton); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stPressFloorButton); + else + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnAwayFromUse); + else if (param.asInteger() == 0) + GotoState(&Klaymen::stTurnToUseHalf); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x482E: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWalkToFrontNoStep); + else + GotoState(&Klaymen::stWalkToFront); + break; + case 0x482F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnToFront); + else + GotoState(&Klaymen::stTurnToBack); + break; + case 0x4832: + GotoState(&Klaymen::stUseTube); + break; + case 0x4833: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAbout); + else { + _spitPipeIndex = sendMessage(_parentScene, 0x2000, 0); + GotoState(&Klaymen::stTrySpitIntoPipe); + } + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return messageResult; +} + +KmScene2402::KmScene2402(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2402::xHandleMessage(int messageNum, const MessageParam ¶m) { + uint32 messageResult = 0; + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + if (!getGlobalVar(V_TV_JOKE_TOLD)) + GotoState(&Klaymen::stStandWonderAbout); + else + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4804: + if (param.asInteger() != 0) { + _destX = param.asInteger(); + GotoState(&Klaymen::stWalkingFirst); + } else + GotoState(&Klaymen::stPeekWall); + break; + case 0x4812: + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4816: + if (param.asInteger() == 1) + GotoState(&Klaymen::stPressButton); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stPressFloorButton); + else + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481F: + if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return messageResult; +} + +KmScene2403::KmScene2403(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2403::xHandleMessage(int messageNum, const MessageParam ¶m) { + uint32 messageResult = 0; + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x480D: + GotoState(&Klaymen::stPullCord); + break; + case 0x4812: + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4816: + if (param.asInteger() == 1) + GotoState(&Klaymen::stPressButton); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stPressFloorButton); + else + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481F: + if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x4820: + sendMessage(_parentScene, 0x2000, 0); + GotoState(&Klaymen::stContinueClimbLadderUp); + break; + case 0x4821: + sendMessage(_parentScene, 0x2000, 0); + _destY = param.asInteger(); + GotoState(&Klaymen::stStartClimbLadderDown); + break; + case 0x4822: + sendMessage(_parentScene, 0x2000, 0); + _destY = param.asInteger(); + GotoState(&Klaymen::stStartClimbLadderUp); + break; + case 0x4823: + sendMessage(_parentScene, 0x2001, 0); + GotoState(&Klaymen::stClimbLadderHalf); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return messageResult; +} + +KmScene2406::KmScene2406(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, NRect *clipRects, int clipRectsCount) + : Klaymen(vm, parentScene, x, y) { + + _surface->setClipRects(clipRects, clipRectsCount); +} + +uint32 KmScene2406::xHandleMessage(int messageNum, const MessageParam ¶m) { + uint32 messageResult = 0; + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4804: + if (param.asInteger() != 0) { + _destX = param.asInteger(); + GotoState(&Klaymen::stWalkingFirst); + } else + GotoState(&Klaymen::stPeekWall); + break; + case 0x4812: + if (param.asInteger() == 2) + GotoState(&Klaymen::stPickUpNeedle); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481A: + GotoState(&Klaymen::stInsertDisk); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + GotoState(&Klaymen::stTurnToUse); + break; + case 0x481E: + GotoState(&Klaymen::stReturnFromUse); + break; + case 0x481F: + if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x4820: + sendMessage(_parentScene, 0x2000, 0); + GotoState(&Klaymen::stContinueClimbLadderUp); + break; + case 0x4821: + sendMessage(_parentScene, 0x2000, 0); + _destY = param.asInteger(); + GotoState(&Klaymen::stStartClimbLadderDown); + break; + case 0x4822: + sendMessage(_parentScene, 0x2000, 0); + _destY = param.asInteger(); + GotoState(&Klaymen::stStartClimbLadderUp); + break; + case 0x4823: + sendMessage(_parentScene, 0x2001, 0); + GotoState(&Klaymen::stClimbLadderHalf); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return messageResult; +} + +KmScene2501::KmScene2501(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2501::xHandleMessage(int messageNum, const MessageParam ¶m) { + uint32 messageResult = 0; + switch (messageNum) { + case 0x2000: + _isSittingInTeleporter = param.asInteger() != 0; + messageResult = 1; + break; + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stSitIdleTeleporter); + else + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481D: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stTurnToUseInTeleporter); + break; + case 0x481E: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stReturnFromUseInTeleporter); + break; + case 0x4834: + GotoState(&Klaymen::stStepOver); + break; + case 0x4835: + sendMessage(_parentScene, 0x2000, 1); + _isSittingInTeleporter = true; + GotoState(&Klaymen::stSitInTeleporter); + break; + case 0x4836: + sendMessage(_parentScene, 0x2000, 0); + _isSittingInTeleporter = false; + GotoState(&Klaymen::stGetUpFromTeleporter); + break; + } + return messageResult; +} + +KmScene2732::KmScene2732(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2732::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4804: + GotoState(&Klaymen::stPeekInside); + break; + case 0x483C: + GotoState(&Klaymen::stPeekInsideReturn); + break; + } + return 0; +} + +KmScene2801::KmScene2801(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2801::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4812: + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + GotoState(&Klaymen::stTurnToUse); + break; + case 0x481E: + GotoState(&Klaymen::stReturnFromUse); + break; + case 0x481F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x482E: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWalkToFrontNoStep); + else + GotoState(&Klaymen::stWalkToFront); + break; + case 0x482F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnToFront); + else + GotoState(&Klaymen::stTurnToBack); + break; + case 0x4837: + stopWalking(); + break; + } + return 0; +} + +KmScene2803::KmScene2803(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, NRect *clipRects, int clipRectsCount) + : Klaymen(vm, parentScene, x, y) { + + _surface->setClipRects(clipRects, clipRectsCount); + _dataResource.load(0x00900849); +} + +uint32 KmScene2803::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4803: + _destY = param.asInteger(); + GotoState(&Klaymen::stJumpToGrab); + break; + case 0x4804: + if (param.asInteger() == 3) + GotoState(&Klaymen::stFinishGrow); + break; + case 0x480D: + GotoState(&Klaymen::stPullCord); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x4818: + startWalkToX(_dataResource.getPoint(param.asInteger()).x, false); + break; + case 0x481D: + GotoState(&Klaymen::stTurnToUse); + break; + case 0x481E: + GotoState(&Klaymen::stReturnFromUse); + break; + case 0x481F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else + GotoState(&Klaymen::stWonderAboutHalf); + break; + case 0x482E: + GotoState(&Klaymen::stWalkToFront); + break; + case 0x482F: + GotoState(&Klaymen::stTurnToBack); + break; + case 0x4834: + GotoState(&Klaymen::stStepOver); + break; + case 0x4838: + GotoState(&Klaymen::stJumpToGrabRelease); + break; + } + return 0; +} + +KmScene2803Small::KmScene2803Small(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + _dataResource.load(0x81120132); +} + +uint32 KmScene2803Small::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToXSmall(param.asPoint().x); + break; + case 0x4004: + GotoState(&Klaymen::stStandIdleSmall); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x4818: + startWalkToXSmall(_dataResource.getPoint(param.asInteger()).x); + break; + case 0x481F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfterSmall); + else if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalfSmall); + else + GotoState(&Klaymen::stWonderAboutSmall); + break; + case 0x482E: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWalkToFrontNoStepSmall); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stWalkToFront2Small); + else + GotoState(&Klaymen::stWalkToFrontSmall); + break; + case 0x482F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnToBackHalfSmall); + else if (param.asInteger() == 2) + GotoState(&Klaymen::stTurnToBackWalkSmall); + else + GotoState(&Klaymen::stTurnToBackSmall); + break; + case 0x4830: + GotoState(&Klaymen::stShrink); + break; + } + return 0; +} + +KmScene2805::KmScene2805(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2805::xHandleMessage(int messageNum, const MessageParam ¶m) { + uint32 messageResult = 0; + switch (messageNum) { + case 0x2000: + _isSittingInTeleporter = param.asInteger() != 0; + messageResult = 1; + break; + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stSitIdleTeleporter); + else + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481D: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stTurnToUseInTeleporter); + break; + case 0x481E: + if (_isSittingInTeleporter) + GotoState(&Klaymen::stReturnFromUseInTeleporter); + break; + case 0x4834: + GotoState(&Klaymen::stStepOver); + break; + case 0x4835: + sendMessage(_parentScene, 0x2000, 1); + _isSittingInTeleporter = true; + GotoState(&Klaymen::stSitInTeleporter); + break; + case 0x4836: + sendMessage(_parentScene, 0x2000, 0); + _isSittingInTeleporter = false; + GotoState(&Klaymen::stGetUpFromTeleporter); + break; + case 0x483D: + teleporterAppear(0xDE284B74); + break; + case 0x483E: + teleporterDisappear(0xD82A4094); + break; + } + return messageResult; +} + +KmScene2806::KmScene2806(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, + bool needsLargeSurface, NRect *clipRects, uint clipRectsCount) + : Klaymen(vm, parentScene, x, y) { + + if (needsLargeSurface) { + NDimensions dimensions = _animResource.loadSpriteDimensions(0x2838C010); + delete _surface; + createSurface(1000, dimensions.width, dimensions.height); + loadSound(3, 0x58E0C341); + loadSound(4, 0x40A00342); + loadSound(5, 0xD0A1C348); + loadSound(6, 0x166FC6E0); + loadSound(7, 0x00018040); + } + + _dataResource.load(0x98182003); + _surface->setClipRects(clipRects, clipRectsCount); +} + +uint32 KmScene2806::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4804: + startWalkToX(440, true); + break; + case 0x480D: + GotoState(&Klaymen::stPullCord); + break; + case 0x4816: + if (param.asInteger() == 0) + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x4818: + startWalkToX(_dataResource.getPoint(param.asInteger()).x, false); + break; + case 0x4831: + GotoState(&Klaymen::stGrow); + break; + case 0x4832: + if (param.asInteger() == 1) + GotoState(&Klaymen::stDrinkPotion); + else + GotoState(&Klaymen::stUseTube); + break; + } + return 0; +} + +KmScene2809::KmScene2809(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, + bool needsLargeSurface, NRect *clipRects, uint clipRectsCount) + : Klaymen(vm, parentScene, x, y) { + + if (needsLargeSurface) { + NDimensions dimensions = _animResource.loadSpriteDimensions(0x2838C010); + delete _surface; + createSurface(1000, dimensions.width, dimensions.height); + loadSound(3, 0x58E0C341); + loadSound(4, 0x40A00342); + loadSound(5, 0xD0A1C348); + loadSound(6, 0x166FC6E0); + loadSound(7, 0x00018040); + } + + _dataResource.load(0x1830009A); + _surface->setClipRects(clipRects, clipRectsCount); +} + +uint32 KmScene2809::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4804: + startWalkToX(226, true); + break; + case 0x480D: + GotoState(&Klaymen::stPullCord); + break; + case 0x4816: + if (param.asInteger() == 0) + GotoState(&Klaymen::stPressButtonSide); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x4818: + startWalkToX(_dataResource.getPoint(param.asInteger()).x, false); + break; + case 0x4831: + GotoState(&Klaymen::stGrow); + break; + case 0x4832: + if (param.asInteger() == 1) + GotoState(&Klaymen::stDrinkPotion); + else + GotoState(&Klaymen::stUseTube); + break; + } + return 0; +} + +KmScene2810Small::KmScene2810Small(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2810Small::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToXSmall(param.asPoint().x); + break; + case 0x4004: + GotoState(&Klaymen::stStandIdleSmall); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x4818: + startWalkToXSmall(_dataResource.getPoint(param.asInteger()).x); + break; + case 0x481F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfterSmall); + else if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalfSmall); + else + GotoState(&Klaymen::stWonderAboutSmall); + break; + case 0x482E: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWalkToFrontNoStepSmall); + else + GotoState(&Klaymen::stWalkToFrontSmall); + break; + case 0x482F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnToBackHalfSmall); + else + GotoState(&Klaymen::stTurnToBackSmall); + break; + case 0x4837: + stopWalking(); + break; + } + return 0; +} + +KmScene2810::KmScene2810(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, NRect *clipRects, uint clipRectsCount) + : Klaymen(vm, parentScene, x, y) { + + _surface->setClipRects(clipRects, clipRectsCount); +} + +uint32 KmScene2810::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4803: + _destY = param.asInteger(); + GotoState(&Klaymen::stJumpToGrab); + break; + case 0x4804: + if (param.asInteger() == 3) + GotoState(&Klaymen::stFinishGrow); + break; + case 0x4812: + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x4818: + startWalkToX(_dataResource.getPoint(param.asInteger()).x, false); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481F: + if (param.asInteger() == 0) + GotoState(&Klaymen::stWonderAboutHalf); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stWonderAboutAfter); + else if (param.asInteger() == 3) + GotoState(&Klaymen::stTurnToUseHalf); + else if (param.asInteger() == 4) + GotoState(&Klaymen::stTurnAwayFromUse); + else if (param.asInteger() == 5) + GotoState(&Klaymen::stTurnToUseExt); + else + GotoState(&Klaymen::stWonderAbout); + break; + case 0x4820: + sendMessage(_parentScene, 0x2000, 0); + GotoState(&Klaymen::stContinueClimbLadderUp); + break; + case 0x4821: + sendMessage(_parentScene, 0x2000, 0); + _destY = param.asInteger(); + GotoState(&Klaymen::stStartClimbLadderDown); + break; + case 0x4822: + sendMessage(_parentScene, 0x2000, 0); + _destY = param.asInteger(); + GotoState(&Klaymen::stStartClimbLadderUp); + break; + case 0x4823: + sendMessage(_parentScene, 0x2001, 0); + GotoState(&Klaymen::stClimbLadderHalf); + break; + case 0x4824: + sendMessage(_parentScene, 0x2000, 0); + _destY = _dataResource.getPoint(param.asInteger()).y; + GotoState(&Klaymen::stStartClimbLadderDown); + break; + case 0x4825: + sendMessage(_parentScene, 0x2000, 0); + _destY = _dataResource.getPoint(param.asInteger()).y; + GotoState(&Klaymen::stStartClimbLadderUp); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x4837: + stopWalking(); + break; + } + return 0; +} + +KmScene2812::KmScene2812(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : Klaymen(vm, parentScene, x, y) { + + // Empty +} + +uint32 KmScene2812::xHandleMessage(int messageNum, const MessageParam ¶m) { + switch (messageNum) { + case 0x4001: + case 0x4800: + startWalkToX(param.asPoint().x, false); + break; + case 0x4004: + GotoState(&Klaymen::stTryStandIdle); + break; + case 0x4805: + _destY = param.asInteger(); + GotoState(&Klaymen::stJumpToGrabFall); + break; + case 0x4812: + if (param.asInteger() == 2) + GotoState(&Klaymen::stPickUpNeedle); + else if (param.asInteger() == 1) + GotoState(&Klaymen::stPickUpTube); + else + GotoState(&Klaymen::stPickUpGeneric); + break; + case 0x4817: + setDoDeltaX(param.asInteger()); + gotoNextStateExt(); + break; + case 0x481A: + GotoState(&Klaymen::stInsertDisk); + break; + case 0x481B: + if (param.asPoint().y != 0) + startWalkToXDistance(param.asPoint().y, param.asPoint().x); + else + startWalkToAttachedSpriteXDistance(param.asPoint().x); + break; + case 0x481D: + GotoState(&Klaymen::stTurnToUse); + break; + case 0x481E: + GotoState(&Klaymen::stReturnFromUse); + break; + case 0x4820: + sendMessage(_parentScene, 0x2001, 0); + GotoState(&Klaymen::stContinueClimbLadderUp); + break; + case 0x4821: + sendMessage(_parentScene, 0x2001, 0); + _destY = param.asInteger(); + GotoState(&Klaymen::stStartClimbLadderDown); + break; + case 0x4822: + sendMessage(_parentScene, 0x2001, 0); + _destY = param.asInteger(); + GotoState(&Klaymen::stStartClimbLadderUp); + break; + case 0x4823: + sendMessage(_parentScene, 0x2002, 0); + GotoState(&Klaymen::stClimbLadderHalf); + break; + case 0x482D: + setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0); + gotoNextStateExt(); + break; + case 0x482E: + if (param.asInteger() == 1) + GotoState(&Klaymen::stWalkToFrontNoStep); + else + GotoState(&Klaymen::stWalkToFront); + break; + case 0x482F: + if (param.asInteger() == 1) + GotoState(&Klaymen::stTurnToFront); + else + GotoState(&Klaymen::stTurnToBack); + break; + case 0x483F: + startSpecialWalkRight(param.asInteger()); + break; + case 0x4840: + startSpecialWalkLeft(param.asInteger()); + break; + } + return 0; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/klaymen.h b/engines/neverhood/klaymen.h new file mode 100644 index 0000000000..25443b5a35 --- /dev/null +++ b/engines/neverhood/klaymen.h @@ -0,0 +1,771 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_KLAYMEN_H +#define NEVERHOOD_KLAYMEN_H + +#include "neverhood/neverhood.h" +#include "neverhood/sprite.h" +#include "neverhood/graphics.h" +#include "neverhood/resource.h" + +namespace Neverhood { + +// TODO This code is horrible and weird and a lot of stuff needs renaming once a better name is found +// TODO Also the methods should probably rearranged and be grouped together more consistently + +class Klaymen; +class Scene; + +const uint32 kKlaymenSpeedUpHash = 0x004A2148; + +#include "common/pack-start.h" // START STRUCT PACKING + +struct KlaymenIdleTableItem { + int weight; + uint idleAnimation; +}; + +#include "common/pack-end.h" // END STRUCT PACKING + +enum { + kIdlePickEar, + kIdleSpinHead, + kIdleArms, + kIdleChest, + kIdleHeadOff, + kIdleTeleporterHands, + kIdleTeleporterHands2, + kIdleWonderAbout +}; + +class Klaymen : public AnimatedSprite { +public: + Klaymen(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, NRectArray *clipRects = NULL); + + void update(); + + void startIdleAnimation(uint32 fileHash, AnimationCb callback); + void upIdleAnimation(); + + void stIdlePickEar(); + void evIdlePickEarDone(); + uint32 hmIdlePickEar(int messageNum, const MessageParam ¶m, Entity *sender); + + void stIdleSpinHead(); + uint32 hmIdleSpinHead(int messageNum, const MessageParam ¶m, Entity *sender); + + void stIdleArms(); + void evIdleArmsDone(); + uint32 hmIdleArms(int messageNum, const MessageParam ¶m, Entity *sender); + + void stIdleChest(); + uint32 hmIdleChest(int messageNum, const MessageParam ¶m, Entity *sender); + + void stIdleHeadOff(); + uint32 hmIdleHeadOff(int messageNum, const MessageParam ¶m, Entity *sender); + + void stIdleWonderAbout(); + + void stIdleTeleporterHands(); + + void stIdleTeleporterHands2(); + + void stTryStandIdle(); + void stStandAround(); + void upStandIdle(); + void stIdleBlink(); + + bool stStartAction(AnimationCb callback3); + bool stStartActionFromIdle(AnimationCb callback); + uint32 hmStartAction(int messageNum, const MessageParam ¶m, Entity *sender); + + void stSneak(); + uint32 hmSneaking(int messageNum, const MessageParam ¶m, Entity *sender); + void suSneaking(); + void evSneakingDone(); + + void stStartWalking(); + void stStartWalkingExt(); + void stWalkingOpenDoor(); + void suWalkingOpenDoor(); + void stStepOver(); + uint32 hmStartWalking(int messageNum, const MessageParam ¶m, Entity *sender); + void evStartWalkingDone(); + + void stWalkingFirst(); + void stWalkingFirstExt(); + void stStartWalkingResume(); + void stUpdateWalkingFirst(); + uint32 hmWalking(int messageNum, const MessageParam ¶m, Entity *sender); + void suWalkingFirst(); + + void stWalkToFrontNoStep(); + void stWalkToFront(); + void stTurnToFront(); + void stTurnToBack(); + uint32 hmWalkToFront(int messageNum, const MessageParam ¶m, Entity *sender); + + void stTurnToBackToUse(); + uint32 hmTurnToBackToUse(int messageNum, const MessageParam ¶m, Entity *sender); + + void stPickUpGeneric(); + void stPickUpNeedle(); + uint32 hmPickUpObject(int messageNum, const MessageParam ¶m, Entity *sender); + + void stPickUpTube(); + uint32 hmPickUpTube(int messageNum, const MessageParam ¶m, Entity *sender); + + void stTurnToUse(); + void stTurnToUseHalf(); + void stTurnAwayFromUse(); + void stReturnFromUse(); + void stTurnToUseExt(); + uint32 hmTurnToUse(int messageNum, const MessageParam ¶m, Entity *sender); + + void stLargeStep(); + uint32 hmLargeStep(int messageNum, const MessageParam ¶m, Entity *sender); + void suLargeStep(); + void evLargeStepDone(); + + void stInsertKey(); + uint32 hmInsertKey(int messageNum, const MessageParam ¶m, Entity *sender); + + void stReadNote(); + uint32 hmReadNote(int messageNum, const MessageParam ¶m, Entity *sender); + + void stHitByDoor(); + uint32 hmHitByDoor(int messageNum, const MessageParam ¶m, Entity *sender); + + void stPeekWall(); + uint32 hmPeekWall(int messageNum, const MessageParam ¶m, Entity *sender); + + void stPeekWallReturn(); + uint32 hmPeekWallReturn(int messageNum, const MessageParam ¶m, Entity *sender); + + void stPeekWallBlink(); + void upPeekWallBlink(); + + void stPeekWall1(); + + void stPeekWall2(); + + void stPullHammerLever(); + uint32 hmPullHammerLever(int messageNum, const MessageParam ¶m, Entity *sender); + + void stRidePlatformDown(); + void suRidePlatformDown(); + + void stCrashDown(); + void stCrashDownFinished(); + + void stShrink(); + uint32 hmShrink(int messageNum, const MessageParam ¶m, Entity *sender); + + void stGrow(); + uint32 hmGrow(int messageNum, const MessageParam ¶m, Entity *sender); + + void stDrinkPotion(); + uint32 hmDrinkPotion(int messageNum, const MessageParam ¶m, Entity *sender); + + void stPullCord(); + void stReleaseCord(); + uint32 hmPullReleaseCord(int messageNum, const MessageParam ¶m, Entity *sender); + + void stUseTube(); + uint32 hmUseTube(int messageNum, const MessageParam ¶m, Entity *sender); + + void stUseLever(); + void stUseLeverRelease(); + void stReleaseLever(); + void stInteractLever(); + uint32 hmLever(int messageNum, const MessageParam ¶m, Entity *sender); + + void stLetGoOfLever(); + void evLeverReleasedEvent(); + + void stWakeUp(); + + void stSleeping(); + uint32 hmSleeping(int messageNum, const MessageParam ¶m, Entity *sender); + + void stPressButton(); + void stPressFloorButton(); + void stPressButtonSide(); + uint32 hmPressButton(int messageNum, const MessageParam ¶m, Entity *sender); + + void stWonderAbout(); + void stWonderAboutHalf(); + void stWonderAboutAfter(); + + void stStandWonderAbout(); + + void stStartClimbLadderUp(); + void stStartClimbLadderDown(); + uint32 hmClimbLadderUpDown(int messageNum, const MessageParam ¶m, Entity *sender); + + void stContinueClimbLadderUp(); + + void stClimbLadderHalf(); + uint32 hmClimbLadderHalf(int messageNum, const MessageParam ¶m, Entity *sender); + + void setupJumpToRing(); + void stJumpToRing1(); + void stJumpToRing2(); + void stJumpToRing4(); + uint32 hmJumpToRing(int messageNum, const MessageParam ¶m, Entity *sender); + + void stHangOnRing(); + + void stJumpToRing3(); + uint32 hmJumpToRing3(int messageNum, const MessageParam ¶m, Entity *sender); + + void stHoldRing3(); + uint32 hmHoldRing3(int messageNum, const MessageParam ¶m, Entity *sender); + + void stReleaseRing(); + + void stLandOnFeet(); + uint32 hmLandOnFeet(int messageNum, const MessageParam ¶m, Entity *sender); + + void stPullLeverDown(); + void stHoldLeverDown(); + + void stInsertDisk(); + uint32 hmInsertDisk(int messageNum, const MessageParam ¶m, Entity *sender); + + void stMoveObjectSkipTurnFaceObject(); + void stMoveObjectSkipTurn(); + void stMoveObjectFaceObject(); + uint32 hmMoveObjectTurn(int messageNum, const MessageParam ¶m, Entity *sender); + void evMoveObjectTurnDone(); + + void stJumpToGrab(); + void stJumpToGrabFall(); + uint32 hmJumpToGrab(int messageNum, const MessageParam ¶m, Entity *sender); + void suJumpToGrab(); + + void stJumpToGrabRelease(); + uint32 hmJumpToGrabRelease(int messageNum, const MessageParam ¶m, Entity *sender); + + void stSitInTeleporter(); + uint32 hmSitInTeleporter(int messageNum, const MessageParam ¶m, Entity *sender); + + void stSitIdleTeleporter(); + void upSitIdleTeleporter(); + + void stSitIdleTeleporterBlink(); + + void stSitIdleTeleporterBlinkSecond(); + + void stTurnToUseInTeleporter(); + + void stReturnFromUseInTeleporter(); + + void stGetUpFromTeleporter(); + + void teleporterAppear(uint32 fileHash); + void teleporterDisappear(uint32 fileHash); + uint32 hmTeleporterAppearDisappear(int messageNum, const MessageParam ¶m, Entity *sender); + + void stClayDoorOpen(); + uint32 hmClayDoorOpen(int messageNum, const MessageParam ¶m, Entity *sender); + + void stFallSkipJump(); + void suFallSkipJump(); + + void stMoveObject(); + void stContinueMoveObject(); + uint32 hmMoveObject(int messageNum, const MessageParam ¶m, Entity *sender); + void upMoveObject(); + + void stCloseEyes(); + + void stTumbleHeadless(); + uint32 hmTumbleHeadless(int messageNum, const MessageParam ¶m, Entity *sender); + + void stFetchMatch(); + void stLightMatch(); + uint32 hmMatch(int messageNum, const MessageParam ¶m, Entity *sender); + + void stHitByBoxingGlove(); + uint32 hmHitByBoxingGlove(int messageNum, const MessageParam ¶m, Entity *sender); + void evHitByBoxingGloveDone(); + + void stStandIdleSmall(); + void stWonderAboutSmall(); + void stWonderAboutHalfSmall(); + void stWonderAboutAfterSmall(); + + void stWalkToFrontNoStepSmall(); + void stWalkToFrontSmall(); + void stWalkToFront2Small(); + void stTurnToBackHalfSmall(); + void stTurnToBackWalkSmall(); + void stTurnToBackSmall(); + uint32 hmWalkFrontBackSmall(int messageNum, const MessageParam ¶m, Entity *sender); + + void stFinishGrow(); + uint32 hmFinishGrow(int messageNum, const MessageParam ¶m, Entity *sender); + + void stJumpToRingVenusFlyTrap(); + uint32 hmJumpToRingVenusFlyTrap(int messageNum, const MessageParam ¶m, Entity *sender); + + void stDropFromRing(); + + void stStandIdleSpecial(); + uint32 hmStandIdleSpecial(int messageNum, const MessageParam ¶m, Entity *sender); + + void stPressDoorButton(); + uint32 hmPressDoorButton(int messageNum, const MessageParam ¶m, Entity *sender); + + void stSpitOutFall0(); + void stSpitOutFall2(); + void suFallDown(); + void upSpitOutFall(); + + void stJumpAndFall(); + uint32 hmJumpAndFall(int messageNum, const MessageParam ¶m, Entity *sender); + + void stFalling(); + void stFallTouchdown(); + + void stMoveVenusFlyTrap(); + void stContinueMovingVenusFlyTrap(); + uint32 hmMoveVenusFlyTrap(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmFirstMoveVenusFlyTrap(int messageNum, const MessageParam ¶m, Entity *sender); + void evMoveVenusFlyTrapDone(); + + void stPeekInside(); + void stPeekInsideReturn(); + void stPeekInsideBlink(); + void upPeekInsideBlink(); + + //////////////////////////////////////////////////////////////////////////// + + void stopWalking(); + + void suAction(); + void suUpdateDestX(); + void suWalkingTestExit(); + + uint32 hmLowLevel(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmLowLevelAnimation(int messageNum, const MessageParam ¶m, Entity *sender); + + void setKlaymenIdleTable(const KlaymenIdleTableItem *table, uint tableCount); + void setKlaymenIdleTable1(); + void setKlaymenIdleTable2(); + void setKlaymenIdleTable3(); + + void setSoundFlag(bool value) { _soundFlag = value; } + + void spitIntoPipe(); + void stTrySpitIntoPipe(); + void stContSpitIntoPipe(); + uint32 hmSpit(int messageNum, const MessageParam ¶m, Entity *sender); + + void stRidePlatform(); + void suRidePlatform(); + void stPullLever(); + void stLookLeverDown(); + void stWaitLeverDown(); + +protected: + Scene *_parentScene; + Sprite *_attachedSprite; + int _ladderStatus; + bool _isWalking; + bool _isSneaking; + bool _isLargeStep; + bool _isMoveObjectRequested; + bool _acceptInput; + int16 _destX, _destY; + int16 _idleCounter, _idleCounterMax; + int16 _blinkCounter, _blinkCounterMax; + int16 _tapesToInsert, _keysToInsert; + bool _doYHitIncr; + bool _isLeverDown; + bool _isWalkingOpenDoorNotified; + int _busyStatus; + bool _actionStatusChanged; + int _actionStatus; + + const KlaymenIdleTableItem *_idleTable; + uint _idleTableCount; + int _idleTableTotalWeight; + int _idleTableNum; + + NPointArray *_pathPoints; + bool _soundFlag; + + int16 _spitOutCountdown; + + bool _isSittingInTeleporter; + + bool _potionFlag1; + bool _potionFlag2; + + int16 _platformDeltaY; + + Sprite *_otherSprite; + + int16 _walkResumeFrameIncr; + + int _moveObjectCountdown; + + bool _canSpitPipe; + bool _contSpitPipe; + bool _readyToSpit; + uint32 _spitPipeIndex; + uint32 _spitDestPipeIndex; + uint32 _spitContDestPipeIndex; + + virtual void xUpdate(); + virtual uint32 xHandleMessage(int messageNum, const MessageParam ¶m); + + void startWalkToX(int16 x, bool walkExt); + void startWalkToXExt(int16 x); + void startWalkToXSmall(int16 x); + void startSpecialWalkLeft(int16 x); + void startSpecialWalkRight(int16 x); + void startWalkToXDistance(int16 destX, int16 distance); + void startWalkToAttachedSpriteXDistance(int16 distance); + + void gotoNextStateExt(); + void beginAction(); + void endAction(); + + void stStartWalkingSmall(); + uint32 hmWalkingSmall(int messageNum, const MessageParam ¶m, Entity *sender); + + void enterIdleAnimation(uint idleAnimation); + void walkAlongPathPoints(); + +}; + +class KmScene1001 : public Klaymen { +public: + KmScene1001(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1002 : public Klaymen { +public: + KmScene1002(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + void xUpdate(); + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1004 : public Klaymen { +public: + KmScene1004(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1109 : public Klaymen { +public: + KmScene1109(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1201 : public Klaymen { +public: + KmScene1201(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1303 : public Klaymen { +public: + KmScene1303(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1304 : public Klaymen { +public: + KmScene1304(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1305 : public Klaymen { +public: + KmScene1305(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1306 : public Klaymen { +public: + KmScene1306(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1308 : public Klaymen { +public: + KmScene1308(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1401 : public Klaymen { +public: + KmScene1401(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1402 : public Klaymen { +public: + KmScene1402(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1403 : public Klaymen { +public: + KmScene1403(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1404 : public Klaymen { +public: + KmScene1404(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1608 : public Klaymen { +public: + KmScene1608(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1705 : public Klaymen { +public: + KmScene1705(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene1901 : public Klaymen { +public: + KmScene1901(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2001 : public Klaymen { +public: + KmScene2001(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2101 : public Klaymen { +public: + KmScene2101(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2201 : public Klaymen { +public: + KmScene2201(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, NRect *clipRects, int clipRectsCount); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2203 : public Klaymen { +public: + KmScene2203(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2205 : public Klaymen { +public: + KmScene2205(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + void xUpdate(); + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2206 : public Klaymen { +public: + KmScene2206(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); + ~KmScene2206(); +protected: + void xUpdate(); + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2207 : public Klaymen { +public: + KmScene2207(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2242 : public Klaymen { +public: + KmScene2242(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + void xUpdate(); + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmHallOfRecords : public Klaymen { +public: + KmHallOfRecords(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + void xUpdate(); + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2247 : public Klaymen { +public: + KmScene2247(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + void xUpdate(); + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2401 : public Klaymen { +public: + KmScene2401(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2402 : public Klaymen { +public: + KmScene2402(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2403 : public Klaymen { +public: + KmScene2403(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2406 : public Klaymen { +public: + KmScene2406(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, NRect *clipRects, int clipRectsCount); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2501 : public Klaymen { +public: + KmScene2501(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2732 : public Klaymen { +public: + KmScene2732(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2801 : public Klaymen { +public: + KmScene2801(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2803 : public Klaymen { +public: + KmScene2803(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, NRect *clipRects, int clipRectsCount); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2803Small : public Klaymen { +public: + KmScene2803Small(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2805 : public Klaymen { +public: + KmScene2805(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2806 : public Klaymen { +public: + KmScene2806(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, + bool needsLargeSurface, NRect *clipRects, uint clipRectsCount); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2809 : public Klaymen { +public: + KmScene2809(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, + bool needsLargeSurface, NRect *clipRects, uint clipRectsCount); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2810Small : public Klaymen { +public: + KmScene2810Small(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2810 : public Klaymen { +public: + KmScene2810(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, + NRect *clipRects, uint clipRectsCount); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +class KmScene2812 : public Klaymen { +public: + KmScene2812(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); +protected: + uint32 xHandleMessage(int messageNum, const MessageParam ¶m); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_KLAYMEN_H */ diff --git a/engines/neverhood/menumodule.cpp b/engines/neverhood/menumodule.cpp new file mode 100644 index 0000000000..a8631cb0d6 --- /dev/null +++ b/engines/neverhood/menumodule.cpp @@ -0,0 +1,1116 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/menumodule.h" +#include "neverhood/gamemodule.h" + +#include "engines/savestate.h" + +namespace Neverhood { + +enum { + MAIN_MENU = 0, + CREDITS_SCENE = 1, + MAKING_OF = 2, + LOAD_GAME_MENU = 3, + SAVE_GAME_MENU = 4, + DELETE_GAME_MENU = 5, + QUERY_OVR_MENU = 6 +}; + +enum { + kMainMenuRestartGame = 0, + kMainMenuLoadGame = 1, + kMainMenuSaveGame = 2, + kMainMenuResumeGame = 3, + kMainMenuQuitGame = 4, + kMainMenuCredits = 5, + kMainMenuMakingOf = 6, + kMainMenuToggleMusic = 7, + kMainMenuDeleteGame = 8 +}; + +static const uint32 kMakingOfSmackerFileHashList[] = { + 0x21082409, + 0x21082809, + 0x21083009, + 0x21080009, + 0x21086009, + 0x2108A009, + 0x21092009, + 0x210A2009, + 0x210C2009, + 0x21082411, + 0x21082811, + 0x21083011, + 0x21080011, + 0 +}; + +MenuModule::MenuModule(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule), _savegameList(NULL) { + + SetMessageHandler(&MenuModule::handleMessage); + + _savedPaletteData = _vm->_screen->getPaletteData(); + _vm->_mixer->pauseAll(true); + + createScene(MAIN_MENU, -1); +} + +MenuModule::~MenuModule() { + _vm->_mixer->pauseAll(false); + _vm->_screen->setPaletteData(_savedPaletteData); +} + +void MenuModule::setLoadgameInfo(uint index) { + _savegameSlot = (*_savegameList)[index].slotNum; +} + +void MenuModule::setSavegameInfo(const Common::String &description, uint index, bool newSavegame) { + _savegameDescription = description; + _savegameSlot = newSavegame ? -1 : (*_savegameList)[index].slotNum; +} + +void MenuModule::setDeletegameInfo(uint index) { + _savegameSlot = (*_savegameList)[index].slotNum; +} + +void MenuModule::createScene(int sceneNum, int which) { + _sceneNum = sceneNum; + switch (_sceneNum) { + case MAIN_MENU: + _childObject = new MainMenu(_vm, this); + break; + case CREDITS_SCENE: + _childObject = new CreditsScene(_vm, this, true); + break; + case MAKING_OF: + createSmackerScene(kMakingOfSmackerFileHashList, false, true, true); + break; + case LOAD_GAME_MENU: + createLoadGameMenu(); + break; + case SAVE_GAME_MENU: + createSaveGameMenu(); + break; + case DELETE_GAME_MENU: + createDeleteGameMenu(); + break; + case QUERY_OVR_MENU: + _childObject = new QueryOverwriteMenu(_vm, this, _savegameDescription); + break; + } + SetUpdateHandler(&MenuModule::updateScene); + _childObject->handleUpdate(); +} + +void MenuModule::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case MAIN_MENU: + switch (_moduleResult) { + case kMainMenuRestartGame: + _vm->_gameModule->requestRestartGame(false); + leaveModule(0); + break; + case kMainMenuLoadGame: + createScene(LOAD_GAME_MENU, -1); + break; + case kMainMenuSaveGame: + createScene(SAVE_GAME_MENU, -1); + break; + case kMainMenuResumeGame: + leaveModule(0); + break; + case kMainMenuQuitGame: + leaveModule(0); + _vm->quitGame(); + break; + case kMainMenuCredits: + createScene(CREDITS_SCENE, -1); + break; + case kMainMenuMakingOf: + createScene(MAKING_OF, -1); + break; + case kMainMenuToggleMusic: + // TODO Toggle music 0048A367 + createScene(MAIN_MENU, -1); + break; + case kMainMenuDeleteGame: + createScene(DELETE_GAME_MENU, -1); + break; + default: + createScene(MAIN_MENU, -1); + break; + } + break; + case CREDITS_SCENE: + case MAKING_OF: + createScene(MAIN_MENU, -1); + break; + case LOAD_GAME_MENU: + handleLoadGameMenuAction(_moduleResult != 1); + break; + case SAVE_GAME_MENU: + handleSaveGameMenuAction(_moduleResult != 1, true); + break; + case DELETE_GAME_MENU: + handleDeleteGameMenuAction(_moduleResult != 1); + break; + case QUERY_OVR_MENU: + handleSaveGameMenuAction(_moduleResult != 1, false); + break; + default: + break; + } + } +} + +uint32 MenuModule::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + return Module::handleMessage(messageNum, param, sender);; +} + +void MenuModule::createLoadGameMenu() { + _savegameSlot = -1; + _savegameList = new SavegameList(); + loadSavegameList(); + _childObject = new LoadGameMenu(_vm, this, _savegameList); +} + +void MenuModule::createSaveGameMenu() { + _savegameSlot = -1; + _savegameList = new SavegameList(); + loadSavegameList(); + _childObject = new SaveGameMenu(_vm, this, _savegameList); +} + +void MenuModule::createDeleteGameMenu() { + _savegameSlot = -1; + _savegameList = new SavegameList(); + loadSavegameList(); + _childObject = new DeleteGameMenu(_vm, this, _savegameList); +} + +void MenuModule::handleLoadGameMenuAction(bool doLoad) { + createScene(MAIN_MENU, -1); + if (doLoad && _savegameSlot >= 0) { + _vm->loadGameState(_savegameSlot); + leaveModule(0); + } + delete _savegameList; + _savegameList = NULL; +} + +void MenuModule::handleSaveGameMenuAction(bool doSave, bool doQuery) { + if (doSave && doQuery && _savegameSlot >= 0) { + createScene(QUERY_OVR_MENU, -1); + } else if (doSave) { + // Get a new slot number if it's a new savegame + if (_savegameSlot < 0) + _savegameSlot = _savegameList->size() > 0 ? _savegameList->back().slotNum + 1 : 0; + // Restore the scene palette and background so that the correct thumbnail is saved + byte *menuPaletteData = _vm->_screen->getPaletteData(); + _vm->_screen->setPaletteData(_savedPaletteData); + _vm->_gameModule->redrawPrevChildObject(); + _vm->saveGameState(_savegameSlot, _savegameDescription); + _vm->_screen->setPaletteData(menuPaletteData); + createScene(MAIN_MENU, -1); + } else { + createScene(MAIN_MENU, -1); + } + delete _savegameList; + _savegameList = NULL; +} + +void MenuModule::handleDeleteGameMenuAction(bool doDelete) { + createScene(MAIN_MENU, -1); + if (doDelete && _savegameSlot >= 0) { + _vm->removeGameState(_savegameSlot); + } + delete _savegameList; + _savegameList = NULL; +} + +void MenuModule::loadSavegameList() { + + Common::SaveFileManager *saveFileMan = _vm->_system->getSavefileManager(); + Neverhood::NeverhoodEngine::SaveHeader header; + Common::String pattern = _vm->getTargetName(); + pattern += ".???"; + + Common::StringArray filenames; + filenames = saveFileMan->listSavefiles(pattern.c_str()); + Common::sort(filenames.begin(), filenames.end()); + + SaveStateList saveList; + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); file++) { + int slotNum = atoi(file->c_str() + file->size() - 3); + if (slotNum >= 0 && slotNum <= 999) { + Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str()); + if (in) { + if (Neverhood::NeverhoodEngine::readSaveHeader(in, false, header) == Neverhood::NeverhoodEngine::kRSHENoError) { + SavegameItem savegameItem; + savegameItem.slotNum = slotNum; + savegameItem.description = header.description; + _savegameList->push_back(savegameItem); + } + delete in; + } + } + } + +} + +MenuButton::MenuButton(NeverhoodEngine *vm, Scene *parentScene, uint buttonIndex, uint32 fileHash, const NRect &collisionBounds) + : StaticSprite(vm, 900), _parentScene(parentScene), _buttonIndex(buttonIndex), _countdown(0) { + + loadSprite(fileHash, kSLFDefDrawOffset | kSLFDefPosition, 100); + _collisionBounds = collisionBounds; + setVisible(false); + SetUpdateHandler(&MenuButton::update); + SetMessageHandler(&MenuButton::handleMessage); +} + +void MenuButton::update() { + updatePosition(); + if (_countdown != 0 && (--_countdown) == 0) { + setVisible(false); + sendMessage(_parentScene, 0x2000, _buttonIndex); + } +} + +uint32 MenuButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_countdown == 0) { + setVisible(true); + _countdown = 4; + } + messageResult = 1; + break; + } + return messageResult; +} + +MainMenu::MainMenu(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule) { + + static const uint32 kMenuButtonFileHashes[] = { + 0x36C62120, + 0x56C62120, + 0x96C62120, + 0x16C62121, + 0x16C62122, + 0x16C62124, + 0x16C62128, + 0x16C62130, + 0x16C62100 + }; + + static const NRect kMenuButtonCollisionBounds[] = { + NRect(52, 121, 110, 156), + NRect(52, 192, 109, 222), + NRect(60, 257, 119, 286), + NRect(67, 326, 120, 354), + NRect(70, 389, 128, 416), + NRect(523, 113, 580, 144), + NRect(525, 176, 577, 206), + NRect(527, 384, 580, 412), + NRect(522, 255, 580, 289) + }; + + setBackground(0x08C0020C); + setPalette(0x08C0020C); + insertScreenMouse(0x00208084); + + insertStaticSprite(0x41137051, 100); + insertStaticSprite(0xC10B2015, 100); + + // TODO Only if music is enabled + _musicOnButton = insertStaticSprite(0x0C24C0EE, 100); + + for (uint buttonIndex = 0; buttonIndex < 9; ++buttonIndex) { + Sprite *menuButton = insertSprite<MenuButton>(this, buttonIndex, + kMenuButtonFileHashes[buttonIndex], kMenuButtonCollisionBounds[buttonIndex]); + addCollisionSprite(menuButton); + } + + SetUpdateHandler(&Scene::update); + SetMessageHandler(&MainMenu::handleMessage); + +} + +uint32 MainMenu::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + leaveScene(param.asInteger()); + break; + } + return 0; +} + +static const uint32 kCreditsSceneFileHashes[] = { + 0x6081128C, 0x608112BC, 0x608112DC, + 0x6081121C, 0x6081139C, 0x6081109C, + 0x6081169C, 0x60811A9C, 0x6081029C, + 0x0081128C, 0x008112BC, 0x008012BC, + 0x008112DC, 0x0081121C, 0x0081139C, + 0x0081109C, 0x0081169C, 0x00811A9C, + 0x0081029C, 0x0081329C, 0xC08112BC, + 0xC08112DC, 0xC081121C, 0xC081139C, + 0 +}; + +CreditsScene::CreditsScene(NeverhoodEngine *vm, Module *parentModule, bool canAbort) + : Scene(vm, parentModule), _canAbort(canAbort), _screenIndex(0), _ticksDuration(0), + _countdown(216) { + + SetUpdateHandler(&CreditsScene::update); + SetMessageHandler(&CreditsScene::handleMessage); + + setBackground(0x6081128C); + setPalette(0x6081128C); + + _ticksTime = _vm->_system->getMillis() + 202100; + + _musicResource = new MusicResource(_vm); + _musicResource->load(0x30812225); + _musicResource->play(0); + +} + +CreditsScene::~CreditsScene() { + _musicResource->unload(); + delete _musicResource; +} + +void CreditsScene::update() { + Scene::update(); + if (_countdown != 0) { + if (_screenIndex == 23 && _vm->_system->getMillis() > _ticksTime) + leaveScene(0); + else if ((--_countdown) == 0) { + ++_screenIndex; + if (kCreditsSceneFileHashes[_screenIndex] == 0) + leaveScene(0); + else { + _background->load(kCreditsSceneFileHashes[_screenIndex]); + _palette->addPalette(kCreditsSceneFileHashes[_screenIndex], 0, 256, 0); + if (_screenIndex < 5) + _countdown = 192; + else if (_screenIndex < 15) + _countdown = 144; + else if (_screenIndex < 16) + _countdown = 216; + else if (_screenIndex < 23) + _countdown = 144; + else + _countdown = 1224; + } + } + } +} + +uint32 CreditsScene::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0009: + leaveScene(0); + break; + case 0x000B: + if (param.asInteger() == Common::KEYCODE_ESCAPE && _canAbort) + leaveScene(0); + break; + case 0x101D: + _ticksDuration = _ticksTime - _vm->_system->getMillis(); + break; + case 0x101E: + _ticksTime = _ticksDuration + _vm->_system->getMillis(); + break; + } + return 0; +} + +Widget::Widget(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene, + int baseObjectPriority, int baseSurfacePriority) + : StaticSprite(vm, baseObjectPriority), _parentScene(parentScene), + _baseObjectPriority(baseObjectPriority), _baseSurfacePriority(baseSurfacePriority) { + + SetUpdateHandler(&Widget::update); + SetMessageHandler(&Widget::handleMessage); + + setPosition(x, y); +} + +void Widget::onClick() { + _parentScene->setCurrWidget(this); +} + +void Widget::setPosition(int16 x, int16 y) { + _x = x; + _y = y; + updateBounds(); +} + +void Widget::refreshPosition() { + _needRefresh = true; + StaticSprite::updatePosition(); + _collisionBoundsOffset.set(0, 0, + _spriteResource.getDimensions().width, _spriteResource.getDimensions().height); + updateBounds(); +} + +void Widget::initialize() { + // Empty +} + +int16 Widget::getWidth() { + return _spriteResource.getDimensions().width; +} + +int16 Widget::getHeight() { + return _spriteResource.getDimensions().height; +} + +void Widget::enterWidget() { + // Empty +} + +void Widget::exitWidget() { + // Empty +} + +void Widget::update() { + handleSpriteUpdate(); + StaticSprite::updatePosition(); +} + +uint32 Widget::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + onClick(); + messageResult = 1; + break; + } + return messageResult; +} + +TextLabelWidget::TextLabelWidget(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene, + int baseObjectPriority, int baseSurfacePriority, + const byte *string, int stringLen, BaseSurface *drawSurface, int16 tx, int16 ty, FontSurface *fontSurface) + : Widget(vm, x, y, parentScene, baseObjectPriority, baseSurfacePriority), + _string(string), _stringLen(stringLen), _drawSurface(drawSurface), _tx(tx), _ty(ty), _fontSurface(fontSurface) { + +} + +void TextLabelWidget::initialize() { + _parentScene->addSprite(this); + _parentScene->addCollisionSprite(this); +} + +int16 TextLabelWidget::getWidth() { + return _fontSurface->getStringWidth(_string, _stringLen); +} + +int16 TextLabelWidget::getHeight() { + return _fontSurface->getCharHeight(); +} + +void TextLabelWidget::drawString(int maxStringLength) { + _fontSurface->drawString(_drawSurface, _x, _y, _string, MIN(_stringLen, maxStringLength)); + _collisionBoundsOffset.set(_tx, _ty, getWidth(), getHeight()); + updateBounds(); +} + +void TextLabelWidget::clear() { + _collisionBoundsOffset.set(0, 0, 0, 0); + updateBounds(); +} + +void TextLabelWidget::setString(const byte *string, int stringLen) { + _string = string; + _stringLen = stringLen; +} + +TextEditWidget::TextEditWidget(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene, + int maxStringLength, FontSurface *fontSurface, uint32 fileHash, const NRect &rect) + : Widget(vm, x, y, parentScene, 1000, 1000), + _maxStringLength(maxStringLength), _fontSurface(fontSurface), _fileHash(fileHash), _rect(rect), + _cursorSurface(NULL), _cursorTicks(0), _cursorPos(0), _cursorFileHash(0), _cursorWidth(0), _cursorHeight(0), + _modified(false), _readOnly(false) { + + _maxVisibleChars = (_rect.x2 - _rect.x1) / _fontSurface->getCharWidth(); + _cursorPos = 0; + + SetUpdateHandler(&TextEditWidget::update); + SetMessageHandler(&TextEditWidget::handleMessage); +} + +TextEditWidget::~TextEditWidget() { + delete _cursorSurface; +} + +void TextEditWidget::onClick() { + NPoint mousePos = _parentScene->getMousePos(); + mousePos.x -= _x + _rect.x1; + mousePos.y -= _y + _rect.y1; + if (mousePos.x >= 0 && mousePos.x <= _rect.x2 - _rect.x1 && + mousePos.y >= 0 && mousePos.y <= _rect.y2 - _rect.y1) { + if (_entryString.size() == 1) + _cursorPos = 0; + else { + int newCursorPos = mousePos.x / _fontSurface->getCharWidth(); + if (mousePos.x % _fontSurface->getCharWidth() > _fontSurface->getCharWidth() / 2 && newCursorPos <= (int)_entryString.size()) + ++newCursorPos; + _cursorPos = MIN((int)_entryString.size(), newCursorPos); + } + _cursorSurface->setVisible(true); + refresh(); + } + Widget::onClick(); +} + +void TextEditWidget::initialize() { + SpriteResource cursorSpriteResource(_vm); + + _spriteResource.load(_fileHash, true); + createSurface(_baseSurfacePriority, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height); + refreshPosition(); + _parentScene->addSprite(this); + _parentScene->addCollisionSprite(this); + _surface->setVisible(true); + _textLabelWidget = new TextLabelWidget(_vm, _rect.x1, _rect.y1 + (_rect.y2 - _rect.y1 + 1 - _fontSurface->getCharHeight()) / 2, + _parentScene, _baseObjectPriority + 1, _baseSurfacePriority + 1, + (const byte*)_entryString.c_str(), _entryString.size(), _surface, _x, _y, _fontSurface); + _textLabelWidget->initialize(); + if (_cursorFileHash != 0) { + cursorSpriteResource.load(_cursorFileHash, true); + _cursorSurface = new BaseSurface(_vm, 0, cursorSpriteResource.getDimensions().width, cursorSpriteResource.getDimensions().height); + _cursorSurface->drawSpriteResourceEx(cursorSpriteResource, false, false, cursorSpriteResource.getDimensions().width, cursorSpriteResource.getDimensions().height); + _cursorSurface->setVisible(!_readOnly); + } + refresh(); +} + +void TextEditWidget::enterWidget() { + if (!_readOnly) { + _cursorSurface->setVisible(true); + _vm->_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true); + } + refresh(); +} + +void TextEditWidget::exitWidget() { + if (!_readOnly) { + _cursorSurface->setVisible(false); + _vm->_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false); + } + refresh(); +} + +void TextEditWidget::setCursor(uint32 cursorFileHash, int16 cursorWidth, int16 cursorHeight) { + _cursorFileHash = cursorFileHash; + _cursorWidth = cursorWidth; + _cursorHeight = cursorHeight; +} + +void TextEditWidget::drawCursor() { + if (_cursorSurface->getVisible() && _cursorPos >= 0 && _cursorPos <= _maxVisibleChars) { + NDrawRect sourceRect(0, 0, _cursorWidth, _cursorHeight); + _surface->copyFrom(_cursorSurface->getSurface(), _rect.x1 + _cursorPos * _fontSurface->getCharWidth(), + _rect.y1 + (_rect.y2 - _cursorHeight - _rect.y1 + 1) / 2, sourceRect); + } else if (!_readOnly) + _cursorSurface->setVisible(false); +} + +void TextEditWidget::updateString() { + _textLabelWidget->setString((const byte *)_entryString.c_str(), _entryString.size()); + _textLabelWidget->drawString(_maxVisibleChars); +} + +Common::String& TextEditWidget::getString() { + return _entryString; +} + +void TextEditWidget::setString(const Common::String &string) { + _entryString = string; + _cursorPos = _entryString.size(); + _modified = false; + refresh(); +} + +void TextEditWidget::handleAsciiKey(char ch) { + if ((int)_entryString.size() < _maxStringLength && + ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == ' ')) { + _entryString.insertChar(ch, _cursorPos); + ++_cursorPos; + _modified = true; + refresh(); + } +} + +void TextEditWidget::handleKeyDown(Common::KeyCode keyCode) { + bool doRefresh = true; + switch (keyCode) { + case Common::KEYCODE_HOME: + _cursorPos = 0; + break; + case Common::KEYCODE_END: + _cursorPos = _entryString.size(); + break; + case Common::KEYCODE_LEFT: + if (_entryString.size() > 0 && _cursorPos > 0) + --_cursorPos; + break; + case Common::KEYCODE_RIGHT: + if (_cursorPos < (int)_entryString.size()) + ++_cursorPos; + break; + case Common::KEYCODE_DELETE: + if (_entryString.size() > 0 && _cursorPos < (int)_entryString.size()) { + _entryString.deleteChar(_cursorPos); + _modified = true; + } + break; + case Common::KEYCODE_BACKSPACE: + if (_entryString.size() > 0 && _cursorPos > 0) { + _entryString.deleteChar(--_cursorPos); + _modified = true; + } + break; + default: + doRefresh = false; + break; + } + if (doRefresh) { + _cursorSurface->setVisible(!_readOnly); + _cursorTicks = 0; + refresh(); + } +} + +void TextEditWidget::refresh() { + refreshPosition(); + updateString(); + if (_cursorFileHash != 0) + drawCursor(); +} + +void TextEditWidget::update() { + Widget::update(); + if (!_readOnly && _parentScene->getCurrWidget() == this && _cursorTicks++ == 10) { + _cursorSurface->setVisible(!_cursorSurface->getVisible()); + refresh(); + _cursorTicks = 0; + } +} + +uint32 TextEditWidget::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Widget::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x000A: + handleAsciiKey(param.asInteger()); + break; + case 0x000B: + handleKeyDown((Common::KeyCode)param.asInteger()); + break; + } + return messageResult; +} + +SavegameListBox::SavegameListBox(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene, + SavegameList *savegameList, FontSurface *fontSurface, uint32 bgFileHash, const NRect &rect) + : Widget(vm, x, y, parentScene, 1000, 1000), + _savegameList(savegameList), _fontSurface(fontSurface), _bgFileHash(bgFileHash), _rect(rect), + _maxStringLength(0), _firstVisibleItem(0), _lastVisibleItem(0), _currIndex(0) { + + _maxVisibleItemsCount = (_rect.y2 - _rect.y1) / _fontSurface->getCharHeight(); + _maxStringLength = (_rect.x2 - _rect.x1) / _fontSurface->getCharWidth(); +} + +void SavegameListBox::onClick() { + NPoint mousePos = _parentScene->getMousePos(); + mousePos.x -= _x + _rect.x1; + mousePos.y -= _y + _rect.y1; + if (mousePos.x >= 0 && mousePos.x <= _rect.x2 - _rect.x1 && + mousePos.y >= 0 && mousePos.y <= _rect.y2 - _rect.y1) { + // We add 1 to the char height to ensure that the correct entry is chosen if the + // user clicks at the bottom the text entry + int newIndex = _firstVisibleItem + mousePos.y / (_fontSurface->getCharHeight() + 1); + if (newIndex <= _lastVisibleItem) { + _currIndex = newIndex; + refresh(); + _parentScene->setCurrWidget(this); + _parentScene->refreshDescriptionEdit(); + } + } +} + +void SavegameListBox::initialize() { + _spriteResource.load(_bgFileHash, true); + createSurface(_baseSurfacePriority, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height); + refreshPosition(); + _parentScene->addSprite(this); + _parentScene->addCollisionSprite(this); + _surface->setVisible(true); + buildItems(); + _firstVisibleItem = 0; + _lastVisibleItem = MIN(_maxVisibleItemsCount, (int)_textLabelItems.size()) - 1; + refresh(); +} + +void SavegameListBox::buildItems() { + SavegameList &savegameList = *_savegameList; + int16 itemX = _rect.x1, itemY = 0; + for (uint i = 0; i < savegameList.size(); ++i) { + const byte *string = (const byte*)savegameList[i].description.c_str(); + int stringLen = (int)savegameList[i].description.size(); + TextLabelWidget *label = new TextLabelWidget(_vm, itemX, itemY, _parentScene, _baseObjectPriority + 1, + _baseSurfacePriority + 1, string, MIN(stringLen, _maxStringLength), _surface, _x, _y, _fontSurface); + label->initialize(); + _textLabelItems.push_back(label); + } +} + +void SavegameListBox::drawItems() { + for (int i = 0; i < (int)_textLabelItems.size(); ++i) { + TextLabelWidget *label = _textLabelItems[i]; + if (i >= _firstVisibleItem && i <= _lastVisibleItem) { + label->setY(_rect.y1 + (i - _firstVisibleItem) * _fontSurface->getCharHeight()); + label->updateBounds(); + label->drawString(_maxStringLength); + } else + label->clear(); + } +} + +void SavegameListBox::refresh() { + refreshPosition(); + drawItems(); +} + +void SavegameListBox::scrollUp() { + if (_firstVisibleItem > 0) { + --_firstVisibleItem; + --_lastVisibleItem; + refresh(); + } +} + +void SavegameListBox::scrollDown() { + if (_lastVisibleItem < (int)_textLabelItems.size()) { + ++_firstVisibleItem; + ++_lastVisibleItem; + refresh(); + } +} + +void SavegameListBox::pageUp() { + int amount = MIN(_firstVisibleItem, _maxVisibleItemsCount); + if (amount > 0) { + _firstVisibleItem -= amount; + _lastVisibleItem -= amount; + refresh(); + } +} + +void SavegameListBox::pageDown() { + int amount = MIN((int)_textLabelItems.size() - _lastVisibleItem, _maxVisibleItemsCount); + if (amount > 0) { + _firstVisibleItem += amount; + _lastVisibleItem += amount; + refresh(); + } +} + +GameStateMenu::GameStateMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList, + const uint32 *buttonFileHashes, const NRect *buttonCollisionBounds, + uint32 backgroundFileHash, uint32 fontFileHash, + uint32 mouseFileHash, const NRect *mouseRect, + uint32 listBoxBackgroundFileHash, int16 listBoxX, int16 listBoxY, const NRect &listBoxRect, + uint32 textEditBackgroundFileHash, uint32 textEditCursorFileHash, int16 textEditX, int16 textEditY, const NRect &textEditRect, + uint32 textFileHash1, uint32 textFileHash2) + : Scene(vm, parentModule), _currWidget(NULL), _savegameList(savegameList) { + + _fontSurface = new FontSurface(_vm, fontFileHash, 32, 7, 32, 11, 17); + + setBackground(backgroundFileHash); + setPalette(backgroundFileHash); + insertScreenMouse(mouseFileHash, mouseRect); + insertStaticSprite(textFileHash1, 200); + insertStaticSprite(textFileHash2, 200); + + _listBox = new SavegameListBox(_vm, listBoxX, listBoxY, this, + _savegameList, _fontSurface, listBoxBackgroundFileHash, listBoxRect); + _listBox->initialize(); + + _textEditWidget = new TextEditWidget(_vm, textEditX, textEditY, this, 29, + _fontSurface, textEditBackgroundFileHash, textEditRect); + if (textEditCursorFileHash != 0) + _textEditWidget->setCursor(textEditCursorFileHash, 2, 13); + else + _textEditWidget->setReadOnly(true); + _textEditWidget->initialize(); + setCurrWidget(_textEditWidget); + + for (uint buttonIndex = 0; buttonIndex < 6; ++buttonIndex) { + Sprite *menuButton = insertSprite<MenuButton>(this, buttonIndex, + buttonFileHashes[buttonIndex], buttonCollisionBounds[buttonIndex]); + addCollisionSprite(menuButton); + } + + SetUpdateHandler(&Scene::update); + SetMessageHandler(&GameStateMenu::handleMessage); + +} + +GameStateMenu::~GameStateMenu() { + delete _fontSurface; +} + +NPoint GameStateMenu::getMousePos() { + NPoint pt; + pt.x = _mouseCursor->getX(); + pt.y = _mouseCursor->getY(); + return pt; +} + +void GameStateMenu::setCurrWidget(Widget *newWidget) { + if (newWidget && newWidget != _currWidget) { + if (_currWidget) + _currWidget->exitWidget(); + newWidget->enterWidget(); + _currWidget = newWidget; + } +} + +void GameStateMenu::refreshDescriptionEdit() { + uint currIndex = _listBox->getCurrIndex(); + _textEditWidget->setString((*_savegameList)[currIndex].description); + setCurrWidget(_textEditWidget); +} + +void GameStateMenu::performAction() { + // Empty +} + +uint32 GameStateMenu::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x000A: + if (!_textEditWidget->isReadOnly()) { + sendMessage(_textEditWidget, 0x000A, param.asInteger()); + setCurrWidget(_textEditWidget); + } + break; + case 0x000B: + if (param.asInteger() == Common::KEYCODE_RETURN) + performAction(); + else if (param.asInteger() == Common::KEYCODE_ESCAPE) + leaveScene(1); + else if (!_textEditWidget->isReadOnly()) { + sendMessage(_textEditWidget, 0x000B, param.asInteger()); + setCurrWidget(_textEditWidget); + } + break; + case 0x2000: + // Handle menu button click + switch (param.asInteger()) { + case 0: + performAction(); + break; + case 1: + leaveScene(1); + break; + case 2: + _listBox->pageUp(); + break; + case 3: + _listBox->scrollUp(); + break; + case 4: + _listBox->scrollDown(); + break; + case 5: + _listBox->pageDown(); + break; + } + break; + } + return 0; +} + +static const uint32 kSaveGameMenuButtonFileHashes[] = { + 0x8359A824, 0x0690E260, 0x0352B050, + 0x1392A223, 0x13802260, 0x0B32B200 +}; + +static const NRect kSaveGameMenuButtonCollisionBounds[] = { + NRect(518, 106, 602, 160), + NRect(516, 378, 596, 434), + NRect(394, 108, 458, 206), + NRect(400, 204, 458, 276), + NRect(398, 292, 456, 352), + NRect(396, 352, 460, 444) +}; + +static const NRect kSaveGameMenuListBoxRect(0, 0, 320, 272); +static const NRect kSaveGameMenuTextEditRect(0, 0, 377, 17); +static const NRect kSaveGameMenuMouseRect(50, 47, 427, 64); + +SaveGameMenu::SaveGameMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList) + : GameStateMenu(vm, parentModule, savegameList, kSaveGameMenuButtonFileHashes, kSaveGameMenuButtonCollisionBounds, + 0x30084E25, 0x2328121A, + 0x84E21308, &kSaveGameMenuMouseRect, + 0x1115A223, 60, 142, kSaveGameMenuListBoxRect, + 0x3510A868, 0x8290AC20, 50, 47, kSaveGameMenuTextEditRect, + 0x1340A5C2, 0x1301A7EA) { + +} + +void SaveGameMenu::performAction() { + ((MenuModule*)_parentModule)->setSavegameInfo(_textEditWidget->getString(), + _listBox->getCurrIndex(), _textEditWidget->isModified()); + leaveScene(0); +} + +static const uint32 kLoadGameMenuButtonFileHashes[] = { + 0x100B2091, 0x84822B03, 0x20E22087, + 0x04040107, 0x04820122, 0x24423047 +}; + +static const NRect kLoadGameMenuButtonCollisionBounds[] = { + NRect( 44, 115, 108, 147), + NRect( 52, 396, 112, 426), + NRect(188, 116, 245, 196), + NRect(189, 209, 239, 269), + NRect(187, 301, 233, 349), + NRect(182, 358, 241, 433) +}; + +static const NRect kLoadGameMenuListBoxRect(0, 0, 320, 271); +static const NRect kLoadGameMenuTextEditRect(0, 0, 320, 17); +static const NRect kLoadGameMenuMouseRect(263, 48, 583, 65); + +LoadGameMenu::LoadGameMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList) + : GameStateMenu(vm, parentModule, savegameList, kLoadGameMenuButtonFileHashes, kLoadGameMenuButtonCollisionBounds, + 0x98620234, 0x201C2474, + 0x2023098E, &kLoadGameMenuMouseRect, + 0x04040409, 263, 142, kLoadGameMenuListBoxRect, + 0x10924C03, 0, 263, 48, kLoadGameMenuTextEditRect, + 0x0BC600A3, 0x0F960021) { + +} + +void LoadGameMenu::performAction() { + ((MenuModule*)_parentModule)->setLoadgameInfo(_listBox->getCurrIndex()); + leaveScene(0); +} + +static const uint32 kDeleteGameMenuButtonFileHashes[] = { + 0x8198E268, 0xDD0C4620, 0x81296520, + 0x8D284211, 0x8C004621, 0x07294020 +}; + +static const NRect kDeleteGameMenuButtonCollisionBounds[] = { + NRect(518, 46, 595, 91), + NRect(524, 322, 599, 369), + NRect(395, 40, 462, 127), + NRect(405, 126, 460, 185), + NRect(397, 205, 456, 273), + NRect(395, 278, 452, 372) +}; + +static const NRect kDeleteGameMenuListBoxRect(0, 0, 320, 271); +static const NRect kDeleteGameMenuTextEditRect(0, 0, 320, 17); + +DeleteGameMenu::DeleteGameMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList) + : GameStateMenu(vm, parentModule, savegameList, kDeleteGameMenuButtonFileHashes, kDeleteGameMenuButtonCollisionBounds, + 0x4080E01C, 0x728523ED, + 0x0E018400, NULL, + 0xA5584211, 61, 64, kDeleteGameMenuListBoxRect, + 0x250A3060, 0, 49, 414, kDeleteGameMenuTextEditRect, + 0x80083C01, 0x84181E81) { + +} + +void DeleteGameMenu::performAction() { + ((MenuModule*)_parentModule)->setDeletegameInfo(_listBox->getCurrIndex()); + leaveScene(0); +} + +QueryOverwriteMenu::QueryOverwriteMenu(NeverhoodEngine *vm, Module *parentModule, const Common::String &description) + : Scene(vm, parentModule) { + + static const uint32 kQueryOverwriteMenuButtonFileHashes[] = { + 0x90312400, + 0x94C22A22 + }; + + static const NRect kQueryOverwriteMenuCollisionBounds[] = { + NRect(145, 334, 260, 385), + NRect(365, 340, 477, 388) + }; + + setBackground(0x043692C4); + setPalette(0x043692C4); + insertScreenMouse(0x692C004B); + insertStaticSprite(0x08C0AC24, 200); + + for (uint buttonIndex = 0; buttonIndex < 2; ++buttonIndex) { + Sprite *menuButton = insertSprite<MenuButton>(this, buttonIndex, + kQueryOverwriteMenuButtonFileHashes[buttonIndex], kQueryOverwriteMenuCollisionBounds[buttonIndex]); + addCollisionSprite(menuButton); + } + + // Draw the query text to the background, each text line is centered + // NOTE The original had this text in its own class + FontSurface *fontSurface = new FontSurface(_vm, 0x94188D4D, 32, 7, 32, 11, 17); + Common::StringArray textLines; + textLines.push_back(description); + textLines.push_back("Game exists."); + textLines.push_back("Overwrite it?"); + for (uint i = 0; i < textLines.size(); ++i) + fontSurface->drawString(_background->getSurface(), 106 + (423 - textLines[i].size() * 11) / 2, + 127 + 31 + i * 17, (const byte*)textLines[i].c_str()); + delete fontSurface; + + SetUpdateHandler(&Scene::update); + SetMessageHandler(&QueryOverwriteMenu::handleMessage); +} + +uint32 QueryOverwriteMenu::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + // Handle menu button click + leaveScene(param.asInteger()); + break; + } + return 0; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/menumodule.h b/engines/neverhood/menumodule.h new file mode 100644 index 0000000000..08858ad204 --- /dev/null +++ b/engines/neverhood/menumodule.h @@ -0,0 +1,265 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_MENUMODULE_H +#define NEVERHOOD_MENUMODULE_H + +#include "common/str.h" +#include "common/str-array.h" +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +struct SavegameItem { + int slotNum; + Common::String description; +}; + +typedef Common::Array<SavegameItem> SavegameList; + +class MenuModule : public Module { +public: + MenuModule(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~MenuModule(); + void setLoadgameInfo(uint index); + void setSavegameInfo(const Common::String &description, uint index, bool newSavegame); + void setDeletegameInfo(uint index); +protected: + int _sceneNum; + byte *_savedPaletteData; + SavegameList *_savegameList; + Common::String _savegameDescription; + int _savegameSlot; + void createScene(int sceneNum, int which); + void updateScene(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void createLoadGameMenu(); + void createSaveGameMenu(); + void createDeleteGameMenu(); + void handleLoadGameMenuAction(bool doLoad); + void handleSaveGameMenuAction(bool doSave, bool doQuery); + void handleDeleteGameMenuAction(bool doDelete); + void loadSavegameList(); +}; + +class MenuButton : public StaticSprite { +public: + MenuButton(NeverhoodEngine *vm, Scene *parentScene, uint buttonIndex, uint32 fileHash, const NRect &collisionBounds); +protected: + Scene *_parentScene; + int _countdown; + uint _buttonIndex; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class MainMenu : public Scene { +public: + MainMenu(NeverhoodEngine *vm, Module *parentModule); +protected: + Sprite *_musicOnButton; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class CreditsScene : public Scene { +public: + CreditsScene(NeverhoodEngine *vm, Module *parentModule, bool canAbort); + virtual ~CreditsScene(); +protected: + int _screenIndex; + int _countdown; + MusicResource *_musicResource; + uint32 _ticksTime; + uint32 _ticksDuration; + bool _canAbort; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Widget; +class GameStateMenu; + +class Widget : public StaticSprite { +public: + Widget(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene, + int baseObjectPriority, int baseSurfacePriority); + virtual void onClick(); + virtual void setPosition(int16 x, int16 y); + virtual void refreshPosition(); + virtual void initialize(); + virtual int16 getWidth(); + virtual int16 getHeight(); + virtual void enterWidget(); + virtual void exitWidget(); +protected: + GameStateMenu *_parentScene; + int _baseObjectPriority; + int _baseSurfacePriority; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class TextLabelWidget : public Widget { +public: + TextLabelWidget(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene, + int baseObjectPriority, int baseSurfacePriority, + const byte *string, int stringLen, BaseSurface *drawSurface, int16 tx, int16 ty, FontSurface *fontSurface); + virtual void initialize(); + virtual int16 getWidth(); + virtual int16 getHeight(); + void drawString(int maxStringLength); + void clear(); + void setString(const byte *string, int stringLen); + FontSurface *getFontSurface() const { return _fontSurface; } +protected: + BaseSurface *_drawSurface; + int16 _tx, _ty; + FontSurface *_fontSurface; + const byte *_string; + int _stringLen; +}; + +class TextEditWidget : public Widget { +public: + TextEditWidget(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene, + int maxStringLength, FontSurface *fontSurface, uint32 fileHash, const NRect &rect); + ~TextEditWidget(); + virtual void onClick(); + virtual void initialize(); + virtual void enterWidget(); + virtual void exitWidget(); + void setCursor(uint32 cursorFileHash, int16 cursorWidth, int16 cursorHeight); + void drawCursor(); + void updateString(); + Common::String& getString(); + void setString(const Common::String &string); + void handleAsciiKey(char ch); + void handleKeyDown(Common::KeyCode keyCode); + void refresh(); + void setReadOnly(bool value) { _readOnly = value; } + bool isReadOnly() const { return _readOnly; } + bool isModified() const { return _modified; } +protected: + NRect _rect; + uint32 _fileHash; + int _maxVisibleChars; + int _maxStringLength; + int _cursorPos; + int _cursorTicks; + Common::String _entryString; + FontSurface *_fontSurface; + TextLabelWidget *_textLabelWidget; + BaseSurface *_cursorSurface; + uint32 _cursorFileHash; + int16 _cursorWidth, _cursorHeight; + bool _modified; + bool _readOnly; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SavegameListBox : public Widget { +public: + SavegameListBox(NeverhoodEngine *vm, int16 x, int16 y, GameStateMenu *parentScene, + SavegameList *savegameList, FontSurface *fontSurface, uint32 bgFileHash, const NRect &rect); + virtual void onClick(); + virtual void initialize(); + void buildItems(); + void drawItems(); + void refresh(); + void scrollUp(); + void scrollDown(); + void pageUp(); + void pageDown(); + uint getCurrIndex() const { return _currIndex; } +protected: + const NRect _rect; + uint32 _bgFileHash; + int _maxStringLength; + Common::Array<TextLabelWidget*> _textLabelItems; + int _firstVisibleItem; + int _lastVisibleItem; + SavegameList *_savegameList; + FontSurface *_fontSurface; + uint _currIndex; + int _maxVisibleItemsCount; +}; + +class GameStateMenu : public Scene { +public: + GameStateMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList, + const uint32 *buttonFileHashes, const NRect *buttonCollisionBounds, + uint32 backgroundFileHash, uint32 fontFileHash, + uint32 mouseFileHash, const NRect *mouseRect, + uint32 listBoxBackgroundFileHash, int16 listBoxX, int16 listBoxY, const NRect &listBoxRect, + uint32 textEditBackgroundFileHash, uint32 textEditCursorFileHash, int16 textEditX, int16 textEditY, const NRect &textEditRect, + uint32 textFileHash1, uint32 textFileHash2); + virtual ~GameStateMenu(); + NPoint getMousePos(); + virtual void setCurrWidget(Widget *newWidget); + virtual Widget *getCurrWidget() { return _currWidget; } + virtual void refreshDescriptionEdit(); +protected: + Widget *_currWidget; + SavegameList *_savegameList; + FontSurface *_fontSurface; + SavegameListBox *_listBox; + TextEditWidget *_textEditWidget; + Common::String _savegameDescription; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + virtual void performAction(); +}; + +class SaveGameMenu : public GameStateMenu { +public: + SaveGameMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList); +protected: + virtual void performAction(); +}; + +class LoadGameMenu : public GameStateMenu { +public: + LoadGameMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList); +protected: + virtual void performAction(); +}; + +class DeleteGameMenu : public GameStateMenu { +public: + DeleteGameMenu(NeverhoodEngine *vm, Module *parentModule, SavegameList *savegameList); +protected: + virtual void performAction(); +}; + +class QueryOverwriteMenu : public Scene { +public: + QueryOverwriteMenu(NeverhoodEngine *vm, Module *parentModule, const Common::String &description); +protected: + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MENUMODULE_H */ diff --git a/engines/neverhood/messages.h b/engines/neverhood/messages.h new file mode 100644 index 0000000000..78c66868d5 --- /dev/null +++ b/engines/neverhood/messages.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 NEVERHOOD_MESSAGES_H +#define NEVERHOOD_MESSAGES_H + +namespace Neverhood { + +enum NeverhoodMessage { + NM_KLAYMEN_PICKUP = 0x4812, + NM_KLAYMEN_PRESS_BUTTON = 0x4816, + NM_KLAYMEN_INSERT_DISK = 0x481A, + NM_KLAYMEN_RELEASE_LEVER = 0x4827 +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MESSAGES_H */ diff --git a/engines/neverhood/microtiles.cpp b/engines/neverhood/microtiles.cpp new file mode 100644 index 0000000000..e19be52b5a --- /dev/null +++ b/engines/neverhood/microtiles.cpp @@ -0,0 +1,164 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/microtiles.h" + +namespace Neverhood { + +MicroTileArray::MicroTileArray(int16 width, int16 height) { + _tilesW = (width / TileSize) + ((width % TileSize) > 0 ? 1 : 0); + _tilesH = (height / TileSize) + ((height % TileSize) > 0 ? 1 : 0); + _tiles = new BoundingBox[_tilesW * _tilesH]; + clear(); +} + +MicroTileArray::~MicroTileArray() { + delete[] _tiles; +} + +void MicroTileArray::addRect(Common::Rect r) { + + int ux0, uy0, ux1, uy1; + int tx0, ty0, tx1, ty1; + int ix0, iy0, ix1, iy1; + + r.clip(Common::Rect(0, 0, 639, 479)); + + ux0 = r.left / TileSize; + uy0 = r.top / TileSize; + ux1 = r.right / TileSize; + uy1 = r.bottom / TileSize; + + tx0 = r.left % TileSize; + ty0 = r.top % TileSize; + tx1 = r.right % TileSize; + ty1 = r.bottom % TileSize; + + for (int yc = uy0; yc <= uy1; yc++) { + for (int xc = ux0; xc <= ux1; xc++) { + ix0 = (xc == ux0) ? tx0 : 0; + ix1 = (xc == ux1) ? tx1 : TileSize - 1; + iy0 = (yc == uy0) ? ty0 : 0; + iy1 = (yc == uy1) ? ty1 : TileSize - 1; + updateBoundingBox(_tiles[xc + yc * _tilesW], ix0, iy0, ix1, iy1); + } + } + +} + +void MicroTileArray::clear() { + memset(_tiles, 0, _tilesW * _tilesH * sizeof(BoundingBox)); +} + +byte MicroTileArray::TileX0(const BoundingBox &boundingBox) { + return (boundingBox >> 24) & 0xFF; +} + +byte MicroTileArray::TileY0(const BoundingBox &boundingBox) { + return (boundingBox >> 16) & 0xFF; +} + +byte MicroTileArray::TileX1(const BoundingBox &boundingBox) { + return (boundingBox >> 8) & 0xFF; +} + +byte MicroTileArray::TileY1(const BoundingBox &boundingBox) { + return boundingBox & 0xFF; +} + +bool MicroTileArray::isBoundingBoxEmpty(const BoundingBox &boundingBox) { + return boundingBox == EmptyBoundingBox; +} + +bool MicroTileArray::isBoundingBoxFull(const BoundingBox &boundingBox) { + return boundingBox == FullBoundingBox; +} + +void MicroTileArray::setBoundingBox(BoundingBox &boundingBox, byte x0, byte y0, byte x1, byte y1) { + boundingBox = (x0 << 24) | (y0 << 16) | (x1 << 8) | y1; +} + +void MicroTileArray::updateBoundingBox(BoundingBox &boundingBox, byte x0, byte y0, byte x1, byte y1) { + if (!isBoundingBoxEmpty(boundingBox)) { + x0 = MIN(TileX0(boundingBox), x0); + y0 = MIN(TileY0(boundingBox), y0); + x1 = MAX(TileX1(boundingBox), x1); + y1 = MAX(TileY1(boundingBox), y1); + } + setBoundingBox(boundingBox, x0, y0, x1, y1); +} + +RectangleList *MicroTileArray::getRectangles() { + + RectangleList *rects = new RectangleList(); + + int x, y; + int x0, y0, x1, y1; + int i = 0; + + for (y = 0; y < _tilesH; ++y) { + for (x = 0; x < _tilesW; ++x) { + + int start; + int finish = 0; + BoundingBox boundingBox = _tiles[i]; + + if (isBoundingBoxEmpty(boundingBox)) { + ++i; + continue; + } + + x0 = (x * TileSize) + TileX0(boundingBox); + y0 = (y * TileSize) + TileY0(boundingBox); + y1 = (y * TileSize) + TileY1(boundingBox); + + start = i; + + if (TileX1(boundingBox) == TileSize - 1 && x != _tilesW - 1) { // check if the tile continues + while (!finish) { + ++x; + ++i; + if (x == _tilesW || i >= _tilesW * _tilesH || + TileY0(_tiles[i]) != TileY0(boundingBox) || + TileY1(_tiles[i]) != TileY1(boundingBox) || + TileX0(_tiles[i]) != 0) + { + --x; + --i; + finish = 1; + } + } + } + + x1 = (x * TileSize) + TileX1(_tiles[i]); + + rects->push_back(Common::Rect(x0, y0, x1 + 1, y1 + 1)); + + ++i; + } + } + + return rects; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/microtiles.h b/engines/neverhood/microtiles.h new file mode 100644 index 0000000000..29af3d956a --- /dev/null +++ b/engines/neverhood/microtiles.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 NEVERHOOD_MICROTILES_H +#define NEVERHOOD_MICROTILES_H + +#include "common/scummsys.h" +#include "common/list.h" +#include "common/util.h" +#include "common/rect.h" + +namespace Neverhood { + +typedef uint32 BoundingBox; + +const BoundingBox FullBoundingBox = 0x00001F1F; +const BoundingBox EmptyBoundingBox = 0x00000000; +const int TileSize = 32; + +typedef Common::List<Common::Rect> RectangleList; + +class MicroTileArray { +public: + MicroTileArray(int16 width, int16 height); + ~MicroTileArray(); + void addRect(Common::Rect r); + void clear(); + RectangleList *getRectangles(); +protected: + BoundingBox *_tiles; + int16 _tilesW, _tilesH; + byte TileX0(const BoundingBox &boundingBox); + byte TileY0(const BoundingBox &boundingBox); + byte TileX1(const BoundingBox &boundingBox); + byte TileY1(const BoundingBox &boundingBox); + bool isBoundingBoxEmpty(const BoundingBox &boundingBox); + bool isBoundingBoxFull(const BoundingBox &boundingBox); + void setBoundingBox(BoundingBox &boundingBox, byte x0, byte y0, byte x1, byte y1); + void updateBoundingBox(BoundingBox &boundingBox, byte x0, byte y0, byte x1, byte y1); +}; + +} // namespace Neverhood + +#endif // NEVERHOOD_MICROTILES_H diff --git a/engines/neverhood/module.cpp b/engines/neverhood/module.cpp new file mode 100644 index 0000000000..e384b5a4d2 --- /dev/null +++ b/engines/neverhood/module.cpp @@ -0,0 +1,123 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 "neverhood/module.h" +#include "neverhood/navigationscene.h" +#include "neverhood/smackerscene.h" +#include "neverhood/modules/module1000.h" +#include "neverhood/modules/module1500.h" + +namespace Neverhood { + +Module::Module(NeverhoodEngine *vm, Module *parentModule) + : Entity(vm, 0), _parentModule(parentModule), _childObject(NULL), + _done(false), _sceneType(kSceneTypeNormal) { + + SetMessageHandler(&Module::handleMessage); + +} + +Module::~Module() { + delete _childObject; +} + +void Module::draw() { + if (_childObject) + _childObject->draw(); +} + +uint32 Module::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + switch (messageNum) { + case 0x0008: + sendMessage(_parentModule, 8, 0); + return 0; + case 0x1009: + _moduleResult = param.asInteger(); + _done = true; + return 0; + case 0x100A: + case 0x1023: + case 0x1024: + // Unused resource preloading messages + return 0; + default: + if (_childObject && sender == _parentModule) + return sender->sendMessage(_childObject, messageNum, param); + } + return 0; +} + +NavigationScene *Module::navigationScene() { + return (NavigationScene*)_childObject; +} + +void Module::createNavigationScene(uint32 navigationListId, int navigationIndex, const byte *itemsTypes) { + _sceneType = kSceneTypeNavigation; + _childObject = new NavigationScene(_vm, this, navigationListId, navigationIndex, itemsTypes); +} + +void Module::createSmackerScene(uint32 fileHash, bool doubleSurface, bool canSkip, bool canAbort) { + _sceneType = kSceneTypeSmacker; + SmackerScene *smackerScene = new SmackerScene(_vm, this, doubleSurface, canSkip, canAbort); + smackerScene->setFileHash(fileHash); + smackerScene->nextVideo(); + _childObject = smackerScene; +} + +void Module::createSmackerScene(const uint32 *fileHashList, bool doubleSurface, bool canSkip, bool canAbort) { + _sceneType = kSceneTypeSmacker; + SmackerScene *smackerScene = new SmackerScene(_vm, this, doubleSurface, canSkip, canAbort); + smackerScene->setFileHashList(fileHashList); + smackerScene->nextVideo(); + _childObject = smackerScene; +} + +void Module::createStaticScene(uint32 backgroundFileHash, uint32 cursorFileHash) { + _childObject = new StaticScene(_vm, this, backgroundFileHash, cursorFileHash); +} + +void Module::createDemoScene() { + _childObject = new Scene1501(_vm, this, 0x0009B624, 0, 288, 0); +} + +bool Module::updateChild() { + if (_childObject) { + _childObject->handleUpdate(); + if (_done) { + _done = false; + // Save the last area type if it's a NavigationScene for further processing + if (_sceneType == kSceneTypeNavigation) + _navigationAreaType = navigationScene()->getNavigationAreaType(); + delete _childObject; + _childObject = NULL; + _sceneType = kSceneTypeNormal; + return false; + } + } + return true; +} + +void Module::leaveModule(uint32 result) { + sendMessage(_parentModule, 0x1009, result); +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/module.h b/engines/neverhood/module.h new file mode 100644 index 0000000000..e98012cbea --- /dev/null +++ b/engines/neverhood/module.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. + * + */ + +// TODO: I couldn't come up with a better name than 'Module' so far + +#ifndef NEVERHOOD_MODULE_H +#define NEVERHOOD_MODULE_H + +#include "neverhood/neverhood.h" +#include "neverhood/background.h" +#include "neverhood/entity.h" +#include "neverhood/graphics.h" +#include "neverhood/mouse.h" +#include "neverhood/palette.h" +#include "neverhood/screen.h" + +namespace Neverhood { + +class NavigationScene; + +enum SceneType { + kSceneTypeNormal, + kSceneTypeSmacker, + kSceneTypeNavigation +}; + +class Module : public Entity { +public: + Module(NeverhoodEngine *vm, Module *parentModule); + virtual ~Module(); + virtual void draw(); +protected: + Module *_parentModule; + Entity *_childObject; + bool _done; + uint32 _moduleResult; + SceneType _sceneType; + int _navigationAreaType; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + NavigationScene *navigationScene(); + void createNavigationScene(uint32 navigationListId, int navigationIndex, const byte *itemsTypes = NULL); + void createSmackerScene(uint32 fileHash, bool doubleSurface, bool canSkip, bool canAbort); + void createSmackerScene(const uint32 *fileHashList, bool doubleSurface, bool canSkip, bool canAbort); + void createStaticScene(uint32 backgroundFileHash, uint32 cursorFileHash); + void createDemoScene(); + bool updateChild(); + void leaveModule(uint32 result); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULE_H */ diff --git a/engines/neverhood/module.mk b/engines/neverhood/module.mk new file mode 100644 index 0000000000..c7bfbb6460 --- /dev/null +++ b/engines/neverhood/module.mk @@ -0,0 +1,58 @@ +MODULE := engines/neverhood + +MODULE_OBJS = \ + background.o \ + blbarchive.o \ + detection.o \ + diskplayerscene.o \ + entity.o \ + gamemodule.o \ + gamevars.o \ + graphics.o \ + klaymen.o \ + menumodule.o \ + microtiles.o \ + module.o \ + modules/module1000.o \ + modules/module1100.o \ + modules/module1200.o \ + modules/module1300.o \ + modules/module1400.o \ + modules/module1500.o \ + modules/module1600.o \ + modules/module1700.o \ + modules/module1800.o \ + modules/module1900.o \ + modules/module2000.o \ + modules/module2100.o \ + modules/module2200.o \ + modules/module2300.o \ + modules/module2400.o \ + modules/module2500.o \ + modules/module2600.o \ + modules/module2700.o \ + modules/module2800.o \ + modules/module2900.o \ + modules/module3000.o \ + mouse.o \ + navigationscene.o \ + neverhood.o \ + palette.o \ + resource.o \ + resourceman.o \ + saveload.o \ + scene.o \ + screen.o \ + smackerscene.o \ + smackerplayer.o \ + sound.o \ + sprite.o \ + staticdata.o + +# This module can be built as a plugin +ifdef BUILD_PLUGINS +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/neverhood/modules/module1000.cpp b/engines/neverhood/modules/module1000.cpp new file mode 100644 index 0000000000..ef2872ba2e --- /dev/null +++ b/engines/neverhood/modules/module1000.cpp @@ -0,0 +1,1699 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module1000.h" + +namespace Neverhood { + +Module1000::Module1000(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + debug("Create Module1000(%d)", which); + + _musicFileHash = getGlobalVar(V_ENTRANCE_OPEN) ? 0x81106480 : 0x00103144; + + _vm->_soundMan->addMusic(0x03294419, 0x061880C6); + _vm->_soundMan->addMusic(0x03294419, _musicFileHash); + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else if (which == 0) + createScene(0, 0); + else if (which == 1) + createScene(1, 1); + +} + +Module1000::~Module1000() { + _vm->_soundMan->deleteMusicGroup(0x03294419); +} + +void Module1000::createScene(int sceneNum, int which) { + debug("Module1000::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _vm->_soundMan->startMusic(0x061880C6, 0, 0); + _childObject = new Scene1001(_vm, this, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + _vm->_soundMan->startMusic(0x061880C6, 0, 0); + _childObject = new Scene1002(_vm, this, which); + break; + case 2: + _vm->gameState().sceneNum = 2; + _vm->_soundMan->startMusic(0x061880C6, 0, 0); + createStaticScene(0xC084110C, 0x41108C00); + break; + case 3: + _vm->gameState().sceneNum = 3; + _vm->_soundMan->stopMusic(0x061880C6, 0, 2); + _childObject = new Scene1004(_vm, this, which); + break; + case 4: + _vm->gameState().sceneNum = 4; + _vm->_soundMan->stopMusic(0x061880C6, 0, 0); + _vm->_soundMan->startMusic(_musicFileHash, 0, 0); + _childObject = new Scene1005(_vm, this, which); + break; + } + SetUpdateHandler(&Module1000::updateScene); + _childObject->handleUpdate(); +} + +void Module1000::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 2) + createScene(2, 0); + else + createScene(1, 0); + break; + case 1: + if (_moduleResult == 1) + leaveModule(0); + else if (_moduleResult == 2) { + if (_vm->isDemo()) + // Demo version returns to the same scene + createScene(1, 2); + else + createScene(3, 0); + } else + createScene(0, 1); + break; + case 2: + createScene(0, 2); + break; + case 3: + if (_moduleResult == 1) + createScene(4, 0); + else + createScene(1, 2); + break; + case 4: + _vm->_soundMan->stopMusic(_musicFileHash, 0, 1); + createScene(3, 1); + break; + } + } +} + +// Scene1001 + +AsScene1001Door::AsScene1001Door(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1100) { + + createSurface(800, 137, 242); + _x = 726; + _y = 440; + stShowIdleDoor(); + loadSound(1, 0xED403E03); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1001Door::handleMessage); +} + +uint32 AsScene1001Door::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + hammerHitsDoor(); + break; + case 0x3002: + gotoNextState(); + break; + } + return 0; +} + +void AsScene1001Door::hammerHitsDoor() { + switch (getGlobalVar(V_DOOR_STATUS)) { + case 0: + case 1: + playSound(0, 0x65482F03); + startAnimation(0x624C0498, 1, 3); + NextState(&AsScene1001Door::stShowIdleDoor); + break; + case 2: + playSound(1); + startAnimation(0x624C0498, 6, 6); + NextState(&AsScene1001Door::stBustedDoorMove); + break; + default: + // Nothing + break; + } + incGlobalVar(V_DOOR_STATUS, 1); +} + +void AsScene1001Door::stShowIdleDoor() { + switch (getGlobalVar(V_DOOR_STATUS)) { + case 1: + startAnimation(0x624C0498, 4, -1); + _newStickFrameIndex = 4; + break; + case 2: + startAnimation(0x624C0498, 1, -1); + _newStickFrameIndex = 1; + break; + case 3: + stopAnimation(); + setVisible(false); + break; + default: + startAnimation(0x624C0498, 0, -1); + _newStickFrameIndex = 0; + break; + } +} + +void AsScene1001Door::stBustedDoorMove() { + setGlobalVar(V_DOOR_BUSTED, 1); + startAnimation(0x624C0498, 6, 6); + NextState(&AsScene1001Door::stBustedDoorGone); + _x = 30; +} + +void AsScene1001Door::stBustedDoorGone() { + playSound(0); + stopAnimation(); + setVisible(false); +} + +AsScene1001Hammer::AsScene1001Hammer(NeverhoodEngine *vm, Sprite *asDoor) + : AnimatedSprite(vm, 1100), _asDoor(asDoor) { + + _x = 547; + _y = 206; + createSurface(900, 177, 192); + startAnimation(0x022C90D4, -1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1001Hammer::handleMessage); +} + +uint32 AsScene1001Hammer::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x00352100) + sendMessage(_asDoor, 0x2000, 0); + else if (param.asInteger() == 0x0A1A0109) + playSound(0, 0x66410886); + break; + case 0x2000: + startAnimation(0x022C90D4, 1, -1); + playSound(0, 0xE741020A); + _newStickFrameIndex = STICK_LAST_FRAME; + break; + } + return 0; +} + +AsScene1001Window::AsScene1001Window(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1200) { + + _x = 320; + _y = 240; + createSurface(100, 66, 129); + startAnimation(0xC68C2299, 0, -1); + _newStickFrameIndex = 0; + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1001Window::handleMessage); +} + +uint32 AsScene1001Window::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x0E0A1410) + playSound(0, 0x60803F10); + break; + case 0x2001: + startAnimation(0xC68C2299, 0, -1); + break; + case 0x3002: + SetMessageHandler(NULL); + setGlobalVar(V_WINDOW_OPEN, 1); + setVisible(false); + break; + } + return 0; +} + +AsScene1001Lever::AsScene1001Lever(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, int deltaXType) + : AnimatedSprite(vm, 1100), _parentScene(parentScene) { + + createSurface(1010, 71, 73); + setDoDeltaX(deltaXType); + startAnimation(0x04A98C36, 0, -1); + _newStickFrameIndex = 0; + _x = x; + _y = y; + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1001Lever::handleMessage); +} + +uint32 AsScene1001Lever::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x00C0C444) + sendMessage(_parentScene, 0x480F, 0); + else if (param.asInteger() == 0xC41A02C0) + playSound(0, 0x40581882); + break; + case 0x1011: + sendMessage(_parentScene, 0x4826, 0); + messageResult = 1; + break; + case 0x3002: + startAnimation(0x04A98C36, 0, -1); + _newStickFrameIndex = 0; + break; + case 0x480F: + startAnimation(0x04A98C36, 0, -1); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +SsCommonButtonSprite::SsCommonButtonSprite(NeverhoodEngine *vm, Scene *parentScene, uint32 fileHash, int surfacePriority, uint32 soundFileHash) + : StaticSprite(vm, fileHash, surfacePriority), _parentScene(parentScene), _countdown(0) { + + _priority = 1100; + _soundFileHash = soundFileHash ? soundFileHash : 0x44141000; + setVisible(false); + SetUpdateHandler(&SsCommonButtonSprite::update); + SetMessageHandler(&SsCommonButtonSprite::handleMessage); +} + +void SsCommonButtonSprite::update() { + if (_countdown != 0 && (--_countdown) == 0) + setVisible(false); +} + +uint32 SsCommonButtonSprite::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x480B: + sendMessage(_parentScene, 0x480B, 0); + setVisible(true); + _countdown = 8; + playSound(0, _soundFileHash); + break; + } + return messageResult; +} + +Scene1001::Scene1001(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _asDoor(NULL), _asWindow(NULL) { + + Sprite *tempSprite; + + SetMessageHandler(&Scene1001::handleMessage); + + setHitRects(0x004B4860); + setBackground(0x4086520E); + setPalette(0x4086520E); + insertScreenMouse(0x6520A400); + + if (which < 0) { + // Restoring game + setRectList(0x004B49F0); + insertKlaymen<KmScene1001>(200, 433); + setMessageList(0x004B4888); + } else if (which == 1) { + // Klaymen entering from the right + setRectList(0x004B49F0); + insertKlaymen<KmScene1001>(640, 433); + setMessageList(0x004B4898); + } else if (which == 2) { + // Klaymen returning from looking through the window + setRectList(0x004B49F0); + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) { + insertKlaymen<KmScene1001>(390, 433); + _klaymen->setDoDeltaX(1); + } else { + insertKlaymen<KmScene1001>(300, 433); + } + setMessageList(0x004B4970); + } else { + // Klaymen sleeping + setRectList(0x004B4A00); + insertKlaymen<KmScene1001>(200, 433); + setMessageList(0x004B4890); + } + + tempSprite = insertStaticSprite(0x2080A3A8, 1300); + + _klaymen->setClipRect(0, 0, tempSprite->getDrawRect().x2(), 480); + + if (!getGlobalVar(V_DOOR_BUSTED)) { + _asDoor = insertSprite<AsScene1001Door>(); + _asDoor->setClipRect(0, 0, tempSprite->getDrawRect().x2(), 480); + } + + _asLever = insertSprite<AsScene1001Lever>(this, 150, 433, 1); + + insertStaticSprite(0x809861A6, 950); + insertStaticSprite(0x89C03848, 1100); + + _ssButton = insertSprite<SsCommonButtonSprite>(this, 0x15288120, 100, 0); + + if (!getGlobalVar(V_WINDOW_OPEN)) { + tempSprite = insertStaticSprite(0x8C066150, 200); + _asWindow = insertSprite<AsScene1001Window>(); + _asWindow->setClipRect(tempSprite->getDrawRect()); + } + + _asHammer = insertSprite<AsScene1001Hammer>(_asDoor); + +} + +Scene1001::~Scene1001() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX()); +} + +uint32 Scene1001::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = 0; + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x00342624) { + sendEntityMessage(_klaymen, 0x1014, _asLever); + setMessageList2(0x004B4910); + messageResult = 1; + } else if (param.asInteger() == 0x21E64A00) { + if (getGlobalVar(V_DOOR_BUSTED)) { + setMessageList(0x004B48A8); + } else { + setMessageList(0x004B48C8); + } + messageResult = 1; + } else if (param.asInteger() == 0x040424D0) { + sendEntityMessage(_klaymen, 0x1014, _ssButton); + } else if (param.asInteger() == 0x80006358) { + if (getGlobalVar(V_WINDOW_OPEN)) { + setMessageList(0x004B4938); + } else { + setMessageList(0x004B4960); + } + } + break; + case 0x2002: + setRectList(0x004B49F0); + break; + case 0x480B: + sendMessage(_asWindow, 0x2001, 0); + break; + case 0x480F: + sendMessage(_asHammer, 0x2000, 0); + break; + } + return messageResult; +} + +// Scene1002 + +AsScene1002Ring::AsScene1002Ring(NeverhoodEngine *vm, Scene *parentScene, bool isSpecial, int16 x, int16 y, int16 clipY1, bool isRingLow) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _isSpecial(isSpecial) { + + SetUpdateHandler(&AsScene1002Ring::update); + + if (_isSpecial) { + createSurface(990, 68, 314); + if (isRingLow) { + startAnimation(0x04103090, 0, -1); + SetMessageHandler(&AsScene1002Ring::hmRingHangingLow); + } else { + startAnimation(0xA85C4011, _vm->_rnd->getRandomNumber(15), -1); + SetMessageHandler(&AsScene1002Ring::hmRingIdle); + } + } else { + createSurface(990, 68, 138); + startAnimation(0xA85C4011, _vm->_rnd->getRandomNumber(15), -1); + SetMessageHandler(&AsScene1002Ring::hmRingIdle); + } + + setClipRect(0, clipY1, 640, 480); + + _x = x; + _y = y; + + setDoDeltaX(_vm->_rnd->getRandomNumber(1)); + +} + +void AsScene1002Ring::update() { + updateAnim(); + updatePosition(); +} + +uint32 AsScene1002Ring::hmRingIdle(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x4806: + setDoDeltaX(((Sprite*)sender)->isDoDeltaX() ? 1 : 0); + sendMessage(_parentScene, 0x4806, 0); + SetMessageHandler(&AsScene1002Ring::hmRingPulled1); + startAnimation(_isSpecial ? 0x87502558 : 0x80DD4010, 0, -1); + break; + case 0x480F: + setDoDeltaX(((Sprite*)sender)->isDoDeltaX() ? 1 : 0); + sendMessage(_parentScene, 0x480F, 0); + SetMessageHandler(&AsScene1002Ring::hmRingPulled2); + startAnimation(0x861A2020, 0, -1); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +uint32 AsScene1002Ring::hmRingPulled1(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + startAnimation(_isSpecial ? 0x78D0A812 : 0xB85D2A10, 0, -1); + SetMessageHandler(&AsScene1002Ring::hmRingHangingLow); + break; + case 0x4807: + sendMessage(_parentScene, 0x4807, 0); + setDoDeltaX(_vm->_rnd->getRandomNumber(1)); + startAnimation(0x8258A030, 0, -1); + SetMessageHandler(&AsScene1002Ring::hmRingReleased); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +uint32 AsScene1002Ring::hmRingPulled2(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + startAnimation(0x04103090, 0, -1); + SetMessageHandler(&AsScene1002Ring::hmRingHangingLow); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +uint32 AsScene1002Ring::hmRingHangingLow(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x4807: + sendMessage(_parentScene, 0x4807, 0); + setDoDeltaX(_vm->_rnd->getRandomNumber(1)); + startAnimation(0x8258A030, 0, -1); + SetMessageHandler(&AsScene1002Ring::hmRingReleased); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +uint32 AsScene1002Ring::hmRingReleased(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmRingIdle(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x05410F72) + playSound(0, 0x21EE40A9); + break; + case 0x3002: + startAnimation(0xA85C4011, 0, -1); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +AsScene1002Door::AsScene1002Door(NeverhoodEngine *vm, NRect &clipRect) + : StaticSprite(vm, 1200) { + + loadSprite(0x1052370F, kSLFDefDrawOffset | kSLFSetPosition, 800, 526, getGlobalVar(V_FLYTRAP_RING_DOOR) ? 49 : 239); + setClipRect(clipRect); + SetUpdateHandler(&AsScene1002Door::update); + SetMessageHandler(&AsScene1002Door::handleMessage); + SetSpriteUpdate(NULL); +} + +void AsScene1002Door::update() { + handleSpriteUpdate(); + updatePosition(); +} + +uint32 AsScene1002Door::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x4808: + setGlobalVar(V_FLYTRAP_RING_DOOR, 1); + SetSpriteUpdate(&AsScene1002Door::suOpenDoor); + break; + case 0x4809: + setGlobalVar(V_FLYTRAP_RING_DOOR, 0); + SetSpriteUpdate(&AsScene1002Door::suCloseDoor); + break; + } + return messageResult; +} + +void AsScene1002Door::suOpenDoor() { + if (_y > 49) { + _y -= 8; + if (_y < 49) { + SetSpriteUpdate(NULL); + _y = 49; + } + _needRefresh = true; + } +} + +void AsScene1002Door::suCloseDoor() { + if (_y < 239) { + _y += 8; + if (_y > 239) { + SetSpriteUpdate(NULL); + _y = 239; + } + _needRefresh = true; + } +} + +AsScene1002BoxingGloveHitEffect::AsScene1002BoxingGloveHitEffect(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1400) { + + createSurface(1025, 88, 165); + setVisible(false); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1002BoxingGloveHitEffect::handleMessage); +} + +uint32 AsScene1002BoxingGloveHitEffect::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2004: + _x = ((Sprite*)sender)->getX() - 98; + _y = ((Sprite*)sender)->getY() - 111; + startAnimation(0x0422255A, 0, -1); + setVisible(true); + break; + case 0x3002: + stopAnimation(); + setVisible(false); + break; + } + return messageResult; +} + +AsScene1002DoorSpy::AsScene1002DoorSpy(NeverhoodEngine *vm, NRect &clipRect, Scene *parentScene, Sprite *asDoor, Sprite *asScene1002BoxingGloveHitEffect) + : AnimatedSprite(vm, 1300), _clipRect(clipRect), _parentScene(parentScene), _asDoor(asDoor), _asBoxingGloveHitEffect(asScene1002BoxingGloveHitEffect) { + + createSurface(800, 136, 147); + setClipRect(clipRect); + suDoorSpy(); + loadSound(0, 0xC0C40298); + startAnimation(0x586C1D48, 0, 0); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1002DoorSpy::handleMessage); + SetSpriteUpdate(&AsScene1002DoorSpy::suDoorSpy); +} + +uint32 AsScene1002DoorSpy::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0xA61CA1C2) + sendMessage(_asBoxingGloveHitEffect, 0x2004, 0); + else if (param.asInteger() == 0x14CE0620) + playSound(0); + break; + case 0x2003: + stDoorSpyBoxingGlove(); + break; + } + return messageResult; +} + +uint32 AsScene1002DoorSpy::hmDoorSpyAnimation(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene1002DoorSpy::suDoorSpy() { + _x = _asDoor->getX() + 34; + _y = _asDoor->getY() + 175; +} + +void AsScene1002DoorSpy::stDoorSpyIdle() { + setClipRect(_clipRect); + _parentScene->setSurfacePriority(getSurface(), 800); + startAnimation(0x586C1D48, 0, 0); + SetMessageHandler(&AsScene1002DoorSpy::handleMessage); +} + +void AsScene1002DoorSpy::stDoorSpyBoxingGlove() { + setClipRect(0, 0, 640, 480); + _parentScene->setSurfacePriority(getSurface(), 1200); + startAnimation(0x586C1D48, 1, -1); + SetMessageHandler(&AsScene1002DoorSpy::hmDoorSpyAnimation); + NextState(&AsScene1002DoorSpy::stDoorSpyIdle); +} + +SsCommonPressButton::SsCommonPressButton(NeverhoodEngine *vm, Scene *parentScene, uint32 fileHash1, uint32 fileHash2, int surfacePriority, uint32 soundFileHash) + : StaticSprite(vm, 1100), _parentScene(parentScene), _status(0) { + + _soundFileHash = soundFileHash != 0 ? soundFileHash : 0x44141000; + _fileHashes[0] = fileHash1; + _fileHashes[1] = fileHash2; + createSurface(surfacePriority, 40, 40); + loadSprite(fileHash1, kSLFDefDrawOffset | kSLFDefPosition); + setVisible(false); + SetUpdateHandler(&SsCommonPressButton::update); + SetMessageHandler(&SsCommonPressButton::handleMessage); +} + +void SsCommonPressButton::setFileHashes(uint32 fileHash1, uint32 fileHash2) { + _fileHashes[0] = fileHash1; + _fileHashes[1] = fileHash2; + loadSprite(_status == 2 ? fileHash2 : fileHash1, kSLFDefDrawOffset | kSLFDefPosition); +} + +void SsCommonPressButton::update() { + if (_countdown != 0 && (--_countdown) == 0) { + if (_status == 1) { + _status = 2; + loadSprite(_fileHashes[1], kSLFDefDrawOffset | kSLFDefPosition); + _countdown = 4; + } else if (_status == 2) { + _status = 3; + loadSprite(_fileHashes[0], kSLFDefDrawOffset | kSLFDefPosition); + _countdown = 4; + } else if (_status == 3) { + _status = 0; + setVisible(false); + } + } +} + +uint32 SsCommonPressButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x480B: + sendMessage(_parentScene, 0x480B, 0); + _status = 1; + _countdown = 4; + setVisible(true); + playSound(0, _soundFileHash); + break; + } + return messageResult; +} + +AsScene1002VenusFlyTrap::AsScene1002VenusFlyTrap(NeverhoodEngine *vm, Scene *parentScene, Sprite *klaymen, bool isSecond) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _klaymen(klaymen), _isSecond(isSecond), _countdown(0) { + + createSurface(995, 175, 195); + if (!_isSecond) { + if (getGlobalVar(V_FLYTRAP_RING_DOOR)) { + setDoDeltaX(1); + _x = 366; + _y = 435; + stRingGrabbed(); + } else { + _x = 174 + getGlobalVar(V_FLYTRAP_POSITION_1) * 32; + _y = 435; + stIdle(); + } + } else { + _x = 186 + getGlobalVar(V_FLYTRAP_POSITION_2) * 32; + _y = 364; + if (getGlobalVar(V_FLYTRAP_RING_BRIDGE) || getGlobalVar(V_FLYTRAP_RING_FENCE)) { + stRingGrabbed(); + } else { + stIdle(); + } + } + _flags = 4; + SetUpdateHandler(&AsScene1002VenusFlyTrap::update); + SetMessageHandler(&AsScene1002VenusFlyTrap::handleMessage); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); +} + +void AsScene1002VenusFlyTrap::update() { + if (_countdown != 0 && (--_countdown == 0)) + gotoNextState(); + AnimatedSprite::update(); +} + +void AsScene1002VenusFlyTrap::upIdle() { + if (_countdown == 0 && _klaymen->getX() - 20 > _x) + setDoDeltaX(1); + else if (_klaymen->getX() + 20 < _x) + setDoDeltaX(0); + update(); +} + +uint32 AsScene1002VenusFlyTrap::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x000890C4) + playSound(0, 0xC21190D8); + else if (param.asInteger() == 0x522200A0) + playSound(0, 0x931080C8); + break; + case 0x1011: + if (_isSecond) { + if (_x >= 154 && _x <= 346) { + sendMessage(_parentScene, 0x2000, 0); + messageResult = 1; + } + } else { + if (_x >= 174 && _x <= 430) { + sendMessage(_parentScene, 0x2000, 0); + messageResult = 1; + } + } + break; + case 0x480B: + setDoDeltaX(param.asInteger() != 0 ? 1 : 0); + if (!_isSecond) { + if (getGlobalVar(V_FLYTRAP_RING_DOOR)) + stRelease(); + else + stWalk(); + } else { + if (getGlobalVar(V_FLYTRAP_RING_BRIDGE) || getGlobalVar(V_FLYTRAP_RING_FENCE)) + stRelease(); + else + stWalk(); + } + break; + case 0x480C: + if (_isSecond) { + if (_x >= 154 && _x <= 346) + messageResult = 1; + else + messageResult = 0; + } else { + if (_x >= 174 && _x <= 430) + messageResult = 1; + else + messageResult = 0; + } + break; + case 0x480E: + if (param.asInteger() == 1) + stGrabRing(); + break; + case 0x4810: + swallowKlaymen(); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 995); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1015); + break; + } + return messageResult; +} + +uint32 AsScene1002VenusFlyTrap::hmAnimationSimple(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +uint32 AsScene1002VenusFlyTrap::hmAnimationExt(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x000890C4) + playSound(0, 0xC21190D8); + else if (param.asInteger() == 0x41881801) { + if (_isSecond) { + if (_x > 330) + sendMessage(_klaymen, 0x4811, 2); + else + sendMessage(_klaymen, 0x4811, 0); + } else { + sendMessage(_klaymen, 0x4811, 0); + } + } else if (param.asInteger() == 0x522200A0) + playSound(0, 0x931080C8); + break; + case 0x3002: + gotoNextState(); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 995); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1015); + break; + } + return messageResult; +} + +void AsScene1002VenusFlyTrap::stWalkBack() { + setDoDeltaX(2); + startAnimation(0xC4080034, 0, -1); + SetUpdateHandler(&AsScene1002VenusFlyTrap::update); + SetMessageHandler(&AsScene1002VenusFlyTrap::hmAnimationExt); + NextState(&AsScene1002VenusFlyTrap::stIdle); +} + +void AsScene1002VenusFlyTrap::stWalk() { + startAnimation(0xC4080034, 0, -1); + SetUpdateHandler(&AsScene1002VenusFlyTrap::update); + SetMessageHandler(&AsScene1002VenusFlyTrap::hmAnimationSimple); + NextState(&AsScene1002VenusFlyTrap::stIdle); +} + +void AsScene1002VenusFlyTrap::stRelease() { + sendMessage(_parentScene, 0x4807, 0); + startAnimation(0x82292851, 0, -1); + SetUpdateHandler(&AsScene1002VenusFlyTrap::update); + SetMessageHandler(&AsScene1002VenusFlyTrap::hmAnimationSimple); + NextState(&AsScene1002VenusFlyTrap::stIdle); +} + +void AsScene1002VenusFlyTrap::stGrabRing() { + setDoDeltaX(1); + startAnimation(0x86A82A11, 0, -1); + SetUpdateHandler(&AsScene1002VenusFlyTrap::update); + SetMessageHandler(&AsScene1002VenusFlyTrap::hmAnimationSimple); + NextState(&AsScene1002VenusFlyTrap::stRingGrabbed); +} + +void AsScene1002VenusFlyTrap::stRingGrabbed() { + startAnimation(0xB5A86034, 0, -1); + SetUpdateHandler(&AsScene1002VenusFlyTrap::update); + SetMessageHandler(&AsScene1002VenusFlyTrap::handleMessage); +} + +void AsScene1002VenusFlyTrap::stKlaymenInside() { + startAnimation(0x31303094, 0, -1); + SetUpdateHandler(&AsScene1002VenusFlyTrap::update); + SetMessageHandler(NULL); + NextState(&AsScene1002VenusFlyTrap::stKlaymenInsideMoving); + _countdown = 24; +} + +void AsScene1002VenusFlyTrap::stIdle() { + startAnimation(0xC8204250, 0, -1); + SetUpdateHandler(&AsScene1002VenusFlyTrap::upIdle); + SetMessageHandler(&AsScene1002VenusFlyTrap::handleMessage); + if (_isSecond) { + if (_x >= 154 && _x <= 346) + setGlobalVar(V_FLYTRAP_POSITION_2, (_x - 186) / 32); + else { + NextState(&AsScene1002VenusFlyTrap::stWalkBack); + _countdown = 12; + } + } else { + if (_x >= 174 && _x <= 430) + setGlobalVar(V_FLYTRAP_POSITION_1, (_x - 174) / 32); + else { + NextState(&AsScene1002VenusFlyTrap::stWalkBack); + _countdown = 12; + } + } +} + +void AsScene1002VenusFlyTrap::stKlaymenInsideMoving() { + startAnimation(0x152920C4, 0, -1); + SetUpdateHandler(&AsScene1002VenusFlyTrap::update); + SetMessageHandler(&AsScene1002VenusFlyTrap::hmAnimationExt); + NextState(&AsScene1002VenusFlyTrap::stSpitOutKlaymen); +} + +void AsScene1002VenusFlyTrap::stSpitOutKlaymen() { + startAnimation(0x84001117, 0, -1); + SetUpdateHandler(&AsScene1002VenusFlyTrap::update); + SetMessageHandler(&AsScene1002VenusFlyTrap::hmAnimationExt); + NextState(&AsScene1002VenusFlyTrap::stIdle); +} + +void AsScene1002VenusFlyTrap::swallowKlaymen() { + if (_x - 15 < _klaymen->getX() && _x + 15 > _klaymen->getX()) { + if (_isSecond) + setDoDeltaX(_x > 265 && _x < 330 ? 1 : 0); + else + setDoDeltaX(_x > 320 ? 1 : 0); + sendMessage(_klaymen, 0x2001, 0); + startAnimation(0x8C2C80D4, 0, -1); + SetUpdateHandler(&AsScene1002VenusFlyTrap::update); + SetMessageHandler(&AsScene1002VenusFlyTrap::hmAnimationExt); + NextState(&AsScene1002VenusFlyTrap::stKlaymenInside); + } +} + +AsScene1002OutsideDoorBackground::AsScene1002OutsideDoorBackground(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1200), _countdown(0) { + + createSurface(850, 186, 212); + _x = 320; + _y = 240; + if (getGlobalVar(V_FLYTRAP_RING_DOOR)) { + startAnimation(0x004A4495, -1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + } else + setVisible(false); + SetUpdateHandler(&AsScene1002OutsideDoorBackground::update); + SetMessageHandler(&AsScene1002OutsideDoorBackground::handleMessage); +} + +void AsScene1002OutsideDoorBackground::update() { + if (_countdown != 0 && (--_countdown == 0)) { + if (_isDoorClosed) + stCloseDoor(); + else + stOpenDoor(); + } + AnimatedSprite::update(); +} + +uint32 AsScene1002OutsideDoorBackground::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageResult) { + case 0x4808: + _isDoorClosed = false; + _countdown = 2; + break; + case 0x4809: + _isDoorClosed = true; + _countdown = 2; + break; + } + return messageResult; +} + +uint32 AsScene1002OutsideDoorBackground::hmAnimation(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = handleMessage(messageNum, param, sender); + switch (messageResult) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene1002OutsideDoorBackground::stOpenDoor() { + startAnimation(0x004A4495, 0, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + setVisible(true); + SetMessageHandler(&AsScene1002OutsideDoorBackground::handleMessage); +} + +void AsScene1002OutsideDoorBackground::stCloseDoor() { + startAnimation(0x004A4495, -1, -1); + _playBackwards = true; + setVisible(true); + SetMessageHandler(&AsScene1002OutsideDoorBackground::hmAnimation); + NextState(&AsScene1002OutsideDoorBackground::stDoorClosed); +} + +void AsScene1002OutsideDoorBackground::stDoorClosed() { + setVisible(false); + stopAnimation(); +} + +AsScene1002KlaymenLadderHands::AsScene1002KlaymenLadderHands(NeverhoodEngine *vm, Klaymen *klaymen) + : AnimatedSprite(vm, 1200), _klaymen(klaymen) { + + createSurface(1200, 40, 163); + setVisible(false); + SetUpdateHandler(&AsScene1002KlaymenLadderHands::update); + SetMessageHandler(&Sprite::handleMessage); +} + +void AsScene1002KlaymenLadderHands::update() { + if (_klaymen->getCurrAnimFileHash() == 0x3A292504) { + startAnimation(0xBA280522, _klaymen->getFrameIndex(), -1); + _newStickFrameIndex = _klaymen->getFrameIndex(); + setVisible(true); + _x = _klaymen->getX(); + _y = _klaymen->getY(); + setDoDeltaX(_klaymen->isDoDeltaX() ? 1 : 0); + } else if (_klaymen->getCurrAnimFileHash() == 0x122D1505) { + startAnimation(0x1319150C, _klaymen->getFrameIndex(), -1); + _newStickFrameIndex = _klaymen->getFrameIndex(); + setVisible(true); + _x = _klaymen->getX(); + _y = _klaymen->getY(); + setDoDeltaX(_klaymen->isDoDeltaX() ? 1 : 0); + } else + setVisible(false); + AnimatedSprite::update(); +} + +AsScene1002KlaymenPeekHand::AsScene1002KlaymenPeekHand(NeverhoodEngine *vm, Scene *parentScene, Klaymen *klaymen) + : AnimatedSprite(vm, 1200), _parentScene(parentScene), _klaymen(klaymen), + _isClipRectSaved(false) { + + createSurface(1000, 33, 41); + setVisible(false); + SetUpdateHandler(&AsScene1002KlaymenPeekHand::update); + SetMessageHandler(&AsScene1002KlaymenPeekHand::handleMessage); +} + +void AsScene1002KlaymenPeekHand::update() { + if (_klaymen->getCurrAnimFileHash() == 0xAC20C012 && _klaymen->getFrameIndex() < 50) { + startAnimation(0x9820C913, _klaymen->getFrameIndex(), -1); + _newStickFrameIndex = _klaymen->getFrameIndex(); + setVisible(true); + _x = _klaymen->getX(); + _y = _klaymen->getY(); + setDoDeltaX(_klaymen->isDoDeltaX() ? 1 : 0); + } else + setVisible(false); + AnimatedSprite::update(); +} + +uint32 AsScene1002KlaymenPeekHand::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x4AB28209) { + sendMessage(_parentScene, 0x1022, 1200); + _isClipRectSaved = true; + _savedClipRect = _surface->getClipRect(); + setClipRect(0, 0, 640, 480); + } else if (param.asInteger() == 0x88001184) { + sendMessage(_parentScene, 0x1022, 1000); + if (_isClipRectSaved) + setClipRect(_savedClipRect); + } + break; + } + return messageResult; +} + +Scene1002::Scene1002(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _isKlaymenFloor(false), _isClimbingLadder(false) { + + NRect tempClipRect; + Sprite *tempSprite; + + SetUpdateHandler(&Scene1002::update); + SetMessageHandler(&Scene1002::handleMessage); + + setHitRects(0x004B4138); + setBackground(0x12C23307); + setPalette(0x12C23307); + + insertStaticSprite(0x06149428, 1100); + insertStaticSprite(0x312C8774, 1100); + + _ssLadderArch = insertStaticSprite(0x152C1313, 1015); + _ssLadderArchPart1 = insertStaticSprite(0x060000A0, 1200); + _ssLadderArchPart2 = insertStaticSprite(0xB2A423B0, 1100); + _ssLadderArchPart3 = insertStaticSprite(0x316E0772, 1100); + + _ssCeiling = insertStaticSprite(0x316C4BB4, 1015); + + if (which < 0) { + // Restoring game + if (_vm->_gameState.which == 0) { + // Klaymen on top + insertKlaymen<KmScene1002>(90, 226); + _asKlaymenLadderHands = insertSprite<AsScene1002KlaymenLadderHands>(_klaymen); + setMessageList(0x004B4270); + _klaymen->setClipRect(31, 0, _ssLadderArchPart2->getDrawRect().x2(), _ssLadderArchPart3->getDrawRect().y2()); + _asKlaymenLadderHands->getSurface()->getClipRect() = _klaymen->getSurface()->getClipRect(); + _klaymen->setRepl(64, 0); + } else { + // Klaymen on the floor + insertKlaymen<KmScene1002>(379, 435); + _asKlaymenLadderHands = insertSprite<AsScene1002KlaymenLadderHands>(_klaymen); + setMessageList(0x004B4270); + _klaymen->setClipRect(_ssLadderArch->getDrawRect().x, 0, _ssLadderArchPart2->getDrawRect().x2(), _ssLadderArchPart1->getDrawRect().y2()); + _asKlaymenLadderHands->setClipRect(_klaymen->getClipRect()); + } + } else if (which == 1) { + // Klaymen entering from the right + insertKlaymen<KmScene1002>(650, 435); + _asKlaymenLadderHands = insertSprite<AsScene1002KlaymenLadderHands>(_klaymen); + setMessageList(0x004B4478); + _klaymen->setClipRect(_ssLadderArch->getDrawRect().x, 0, _ssLadderArchPart2->getDrawRect().x2(), _ssLadderArchPart1->getDrawRect().y2()); + _asKlaymenLadderHands->setClipRect(_klaymen->getClipRect()); + _vm->_gameState.which = 1; + } else if (which == 2) { + // Klaymen coming up the ladder + insertKlaymen<KmScene1002>(68, 645); + _asKlaymenLadderHands = insertSprite<AsScene1002KlaymenLadderHands>(_klaymen); + setMessageList(0x004B4298); + _klaymen->setClipRect(_ssLadderArch->getDrawRect().x, 0, _ssLadderArchPart2->getDrawRect().x2(), _ssLadderArchPart1->getDrawRect().y2()); + _asKlaymenLadderHands->setClipRect(_klaymen->getClipRect()); + _vm->_gameState.which = 1; + sendMessage(_klaymen, 0x4820, 0); + } else { + // Klaymen entering from the left, peeking + insertKlaymen<KmScene1002>(90, 226); + _asKlaymenLadderHands = insertSprite<AsScene1002KlaymenLadderHands>(_klaymen); + setMessageList(0x004B4470); + _klaymen->setClipRect(31, 0, _ssLadderArchPart2->getDrawRect().x2(), _ssLadderArchPart3->getDrawRect().y2()); + _asKlaymenLadderHands->setClipRect(_klaymen->getClipRect()); + _asKlaymenPeekHand = insertSprite<AsScene1002KlaymenPeekHand>(this, _klaymen); + _asKlaymenPeekHand->setClipRect(_klaymen->getClipRect()); + _klaymen->setRepl(64, 0); + _vm->_gameState.which = 0; + } + + insertScreenMouse(0x23303124); + + tempSprite = insertStaticSprite(0xB3242310, 825); + tempClipRect.set(tempSprite->getDrawRect().x, tempSprite->getDrawRect().y, + _ssLadderArchPart2->getDrawRect().x2(), _ssLadderArchPart2->getDrawRect().y2()); + + _asRing1 = insertSprite<AsScene1002Ring>(this, false, 258, 191, _ssCeiling->getDrawRect().y, false); + _asRing2 = insertSprite<AsScene1002Ring>(this, false, 297, 189, _ssCeiling->getDrawRect().y, false); + _asRing3 = insertSprite<AsScene1002Ring>(this, true, 370, 201, _ssCeiling->getDrawRect().y, getGlobalVar(V_FLYTRAP_RING_DOOR)); + _asRing4 = insertSprite<AsScene1002Ring>(this, false, 334, 191, _ssCeiling->getDrawRect().y, false); + _asRing5 = insertSprite<AsScene1002Ring>(this, false, 425, 184, _ssCeiling->getDrawRect().y, false); + + _asDoor = insertSprite<AsScene1002Door>(tempClipRect); + tempSprite = insertSprite<AsScene1002BoxingGloveHitEffect>(); + _asDoorSpy = insertSprite<AsScene1002DoorSpy>(tempClipRect, this, _asDoor, tempSprite); + _ssPressButton = insertSprite<SsCommonPressButton>(this, 0x00412692, 0x140B60BE, 800, 0); + _asVenusFlyTrap = insertSprite<AsScene1002VenusFlyTrap>(this, _klaymen, false); + addCollisionSprite(_asVenusFlyTrap); + + sendEntityMessage(_klaymen, 0x2007, _asVenusFlyTrap); + + _asOutsideDoorBackground = insertSprite<AsScene1002OutsideDoorBackground>(); + + setRectList(0x004B43A0); + + loadSound(1, 0x60755842); + loadSound(2, 0x616D5821); + +} + +Scene1002::~Scene1002() { +} + +void Scene1002::update() { + Scene::update(); + if (!_isKlaymenFloor && _klaymen->getY() > 230) { + _klaymen->setClipRect(_ssLadderArch->getDrawRect().x, 0, _ssLadderArchPart2->getDrawRect().x2(), _ssLadderArchPart1->getDrawRect().y2()); + _asKlaymenLadderHands->setClipRect(_klaymen->getClipRect()); + deleteSprite(&_ssLadderArchPart3); + _klaymen->clearRepl(); + _isKlaymenFloor = true; + _vm->_gameState.which = 1; + } +} + +uint32 Scene1002::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = 0; + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0xE6EE60E1) { + if (getGlobalVar(V_FLYTRAP_RING_DOOR)) + setMessageList(0x004B4428); + else + setMessageList(0x004B4448); + messageResult = 1; + } else if (param.asInteger() == 0x4A845A00) + sendEntityMessage(_klaymen, 0x1014, _asRing1); + else if (param.asInteger() == 0x43807801) + sendEntityMessage(_klaymen, 0x1014, _asRing2); + else if (param.asInteger() == 0x46C26A01) { + if (getGlobalVar(V_FLYTRAP_RING_DOOR)) { + setMessageList(0x004B44B8); + } else { + sendEntityMessage(_klaymen, 0x1014, _asRing3); + if (_asVenusFlyTrap->getX() - 10 < 366 && _asVenusFlyTrap->getX() + 10 > 366) { + setGlobalVar(V_FLYTRAP_RING_EATEN, 1); + setMessageList(0x004B44A8); + } else { + setMessageList(0x004B44A0); + } + } + messageResult = 1; + } else if (param.asInteger() == 0x468C7B11) + sendEntityMessage(_klaymen, 0x1014, _asRing4); + else if (param.asInteger() == 0x42845B19) + sendEntityMessage(_klaymen, 0x1014, _asRing5); + else if (param.asInteger() == 0xC0A07458) + sendEntityMessage(_klaymen, 0x1014, _ssPressButton); + break; + case 0x1024: + sendMessage(_parentModule, 0x1024, param.asInteger()); + break; + case 0x2000: + if (_isClimbingLadder) { + setMessageList2(0x004B43D0); + } else { + if (_klaymen->getY() > 420) { + sendEntityMessage(_klaymen, 0x1014, _asVenusFlyTrap); + setMessageList2(0x004B4480); + } else if (_klaymen->getY() > 227) { + setMessageList2(0x004B41E0); + } else { + setMessageList2(0x004B4148); + } + } + break; + case 0x2002: + _messageList = NULL; + break; + case 0x2005: + _isClimbingLadder = true; + setRectList(0x004B4418); + break; + case 0x2006: + _isClimbingLadder = false; + setRectList(0x004B43A0); + break; + case 0x4806: + if (sender == _asRing1) { + setGlobalVar(V_RADIO_ENABLED, 0); + playSound(0, 0x665198C0); + } else if (sender == _asRing2) { + setGlobalVar(V_RADIO_ENABLED, 0); + playSound(0, 0xE2D389C0); + } else if (sender == _asRing3) { + setGlobalVar(V_RADIO_ENABLED, 0); + playSound(1); + sendMessage(_asDoor, 0x4808, 0); + sendMessage(_asOutsideDoorBackground, 0x4808, 0); + } else if (sender == _asRing4) { + setGlobalVar(V_RADIO_ENABLED, 0); + playSound(0, 0xE0558848); + } else if (sender == _asRing5) { + setGlobalVar(V_RADIO_ENABLED, 1); + playSound(0, 0x44014282); + } + break; + case 0x4807: + if (sender == _asRing3) { + playSound(2); + sendMessage(_asDoor, 0x4809, 0); + sendMessage(_asOutsideDoorBackground, 0x4809, 0); + } else if (sender == _asVenusFlyTrap) { + if (getGlobalVar(V_FLYTRAP_RING_DOOR)) { + sendMessage(_asRing3, 0x4807, 0); + } + } + break; + case 0x480B: + sendEntityMessage(_klaymen, 0x1014, _asDoorSpy); + break; + case 0x480F: + setGlobalVar(V_RADIO_ENABLED, 0); + playSound(1); + sendMessage(_asDoor, 0x4808, 0); + sendMessage(_asOutsideDoorBackground, 0x4808, 0); + break; + case 0x8000: + setSpriteSurfacePriority(_ssCeiling, 995); + setSpriteSurfacePriority(_ssLadderArch, 995); + break; + case 0x8001: + setSpriteSurfacePriority(_ssCeiling, 1015); + setSpriteSurfacePriority(_ssLadderArch, 1015); + break; + } + return messageResult; +} + +// StaticScene + +StaticScene::StaticScene(NeverhoodEngine *vm, Module *parentModule, uint32 backgroundFileHash, uint32 cursorFileHash) + : Scene(vm, parentModule) { + + SetMessageHandler(&StaticScene::handleMessage); + + setBackground(backgroundFileHash); + setPalette(backgroundFileHash); + insertPuzzleMouse(cursorFileHash, 20, 620); +} + +uint32 StaticScene::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) + leaveScene(0); + break; + } + return 0; +} + +// Scene1004 + +AsScene1004TrashCan::AsScene1004TrashCan(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1100) { + + _x = 330; + _y = 327; + createSurface(800, 56, 50); + setVisible(false); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1004TrashCan::handleMessage); +} + +uint32 AsScene1004TrashCan::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x225A8587) + playSound(0, 0x109AFC4C); + break; + case 0x2002: + startAnimation(0xEB312C11, 0, -1); + setVisible(true); + break; + case 0x3002: + stopAnimation(); + setVisible(false); + break; + } + return 0; +} + +Scene1004::Scene1004(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _paletteAreaStatus(-1) { + + Sprite *tempSprite; + + SetUpdateHandler(&Scene1004::update); + SetMessageHandler(&Scene1004::handleMessage); + + setBackground(0x50C03005); + + if (getGlobalVar(V_ENTRANCE_OPEN)) { + setPalette(0xA30BA329); + _palette->addBasePalette(0xA30BA329, 0, 256, 0); + } else { + setPalette(0x50C03005); + _palette->addBasePalette(0x50C03005, 0, 256, 0); + } + addEntity(_palette); + + insertScreenMouse(0x03001504); + + if (which < 0) { + // Restoring game + setRectList(0x004B7C70); + insertKlaymen<KmScene1004>(330, 327); + setMessageList(0x004B7C18); + } else if (which == 1) { + // Klaymen returning from reading a note + setRectList(0x004B7C70); + insertKlaymen<KmScene1004>(330, 327); + setMessageList(0x004B7C08); + } else { + // Klaymen coming down the ladder + loadDataResource(0x01900A04); + insertKlaymen<KmScene1004>(_dataResource.getPoint(0x80052A29).x, 27); + setMessageList(0x004B7BF0); + } + + updatePaletteArea(); + + _asKlaymenLadderHands = insertSprite<AsScene1002KlaymenLadderHands>(_klaymen); + + insertStaticSprite(0x800034A0, 1100); + insertStaticSprite(0x64402020, 1100); + insertStaticSprite(0x3060222E, 1300); + tempSprite = insertStaticSprite(0x0E002004, 1300); + + _klaymen->setClipRect(0, tempSprite->getDrawRect().y, 640, 480); + _asKlaymenLadderHands->setClipRect(_klaymen->getClipRect()); + + _asTrashCan = insertSprite<AsScene1004TrashCan>(); + +} + +void Scene1004::update() { + Scene::update(); + updatePaletteArea(); +} + +uint32 Scene1004::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = 0; + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x926500A1) { + setMessageList(0x004B7C20); + messageResult = 1; + } + break; + case 0x2000: + loadDataResource(0x01900A04); + break; + case 0x2001: + setRectList(0x004B7C70); + break; + case 0x2002: + sendMessage(_asTrashCan, 0x2002, 0); + break; + } + return messageResult; +} + +void Scene1004::updatePaletteArea() { + if (_klaymen->getY() < 150) { + if (_paletteAreaStatus != 0) { + _paletteAreaStatus = 0; + _palette->addBasePalette(0x406B0D10, 0, 64, 0); + _palette->startFadeToPalette(12); + } + } else { + if (_paletteAreaStatus != 1) { + _paletteAreaStatus = 1; + _palette->addBasePalette(0x24332243, 0, 64, 0); + _palette->startFadeToPalette(12); + } + } +} + +// Scene1005 + +Scene1005::Scene1005(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + SetMessageHandler(&Scene1005::handleMessage); + + if (getGlobalVar(V_ENTRANCE_OPEN)) { + setBackground(0x2800E011); + setPalette(0x2800E011); + insertStaticSprite(0x492D5AD7, 100); + insertPuzzleMouse(0x0E015288, 20, 620); + } else { + setBackground(0x8870A546); + setPalette(0x8870A546); + insertStaticSprite(0x40D1E0A9, 100); + insertStaticSprite(0x149C00A6, 100); + insertPuzzleMouse(0x0A54288F, 20, 620); + } + + drawTextToBackground(); + +} + +uint32 Scene1005::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) + leaveScene(0); + break; + } + return 0; +} + +void Scene1005::drawTextToBackground() { + TextResource textResource(_vm); + const char *textStart, *textEnd; + int16 y = 36; + uint32 textIndex = getTextIndex(); + FontSurface *fontSurface = FontSurface::createFontSurface(_vm, getGlobalVar(V_ENTRANCE_OPEN) ? 0x283CE401 : 0xC6604282); + textResource.load(0x80283101); + textStart = textResource.getString(textIndex, textEnd); + while (textStart < textEnd) { + fontSurface->drawString(_background->getSurface(), 188, y, (const byte*)textStart); + y += 36; + textStart += strlen(textStart) + 1; + } + delete fontSurface; +} + +uint32 Scene1005::getTextIndex() { + uint32 textIndex; + textIndex = getTextIndex1(); + if (getGlobalVar(V_ENTRANCE_OPEN)) { + textIndex = getTextIndex2(); + } + if (getGlobalVar(V_TEXT_FLAG1) && getGlobalVar(V_TEXT_INDEX) == textIndex) { + textIndex = getTextIndex3(); + } else { + setGlobalVar(V_TEXT_FLAG1, 1); + setGlobalVar(V_TEXT_INDEX, textIndex); + } + return textIndex; +} + +uint32 Scene1005::getTextIndex1() { + uint32 textIndex; + if (getGlobalVar(V_WORLDS_JOINED)) { + if (!getGlobalVar(V_DOOR_PASSED)) + textIndex = 18; + else if (!getGlobalVar(V_ROBOT_TARGET)) + textIndex = 19; + else if (getGlobalVar(V_ROBOT_HIT)) { + if (!getGlobalVar(V_ENTRANCE_OPEN)) + textIndex = 23; + else if (!getSubVar(VA_HAS_KEY, 0) && !getSubVar(VA_IS_KEY_INSERTED, 0)) + textIndex = 24; + else if (!getGlobalVar(V_HAS_FINAL_KEY)) + textIndex = 26; + else if (!getSubVar(VA_HAS_KEY, 1) && !getSubVar(VA_IS_KEY_INSERTED, 1)) + textIndex = 27; + else if (!getGlobalVar(V_HAS_FINAL_KEY)) + textIndex = 28; + else + textIndex = 29; + } else if (!getGlobalVar(V_FELL_DOWN_HOLE)) + textIndex = 20; + else if (!getGlobalVar(V_SEEN_SYMBOLS_NO_LIGHT)) + textIndex = 21; + else + textIndex = 22; + } else if (getGlobalVar(V_BOLT_DOOR_UNLOCKED)) { + if (!getGlobalVar(V_WALL_BROKEN)) + textIndex = 12; + else if (!getGlobalVar(V_STAIRS_DOWN_ONCE)) + textIndex = 13; + else if (!getGlobalVar(V_RADIO_ENABLED)) + textIndex = 50; + else if (!getGlobalVar(V_UNUSED)) + textIndex = 14; + else if (!getGlobalVar(V_BEEN_SHRINKING_ROOM)) + textIndex = 15; + else if (!getGlobalVar(V_BEEN_STATUE_ROOM)) + textIndex = 16; + else + textIndex = 17; + } else if (!getGlobalVar(V_FLYTRAP_RING_EATEN)) { + textIndex = 0; + } else if (getGlobalVar(V_CREATURE_EXPLODED)) { + if (!getGlobalVar(V_TILE_PUZZLE_SOLVED)) + textIndex = 4; + else if (!getGlobalVar(V_HAS_TEST_TUBE)) + textIndex = 5; + else if (!getSubVar(VA_LOCKS_DISABLED, 0x40119852)) + textIndex = 6; + else if (!getGlobalVar(V_WATER_RUNNING)) + textIndex = 7; + else if (!getGlobalVar(V_NOTES_PUZZLE_SOLVED)) + textIndex = 8; + else if (!getSubVar(VA_LOCKS_DISABLED, 0x304008D2)) + textIndex = 9; + else if (!getSubVar(VA_LOCKS_DISABLED, 0x01180951)) + textIndex = 10; + else + textIndex = 11; + } else if (!getGlobalVar(V_CREATURE_ANGRY)) { + textIndex = 1; + } else if (getGlobalVar(V_TNT_DUMMY_BUILT)) { + textIndex = 3; + } else { + textIndex = 2; + } + return textIndex; +} + +uint32 Scene1005::getTextIndex2() { + uint32 textIndex = getGlobalVar(V_TEXT_COUNTING_INDEX1); + if (textIndex + 1 >= 10) { + setGlobalVar(V_TEXT_COUNTING_INDEX1, 0); + textIndex = 0; + } else { + setGlobalVar(V_TEXT_COUNTING_INDEX1, textIndex + 1); + } + return textIndex + 40; +} + +uint32 Scene1005::getTextIndex3() { + uint32 textIndex = getGlobalVar(V_TEXT_COUNTING_INDEX2); + if (textIndex + 1 >= 10) { + setGlobalVar(V_TEXT_COUNTING_INDEX2, 0); + textIndex = 0; + } else { + setGlobalVar(V_TEXT_COUNTING_INDEX2, textIndex + 1); + } + return textIndex + 30; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module1000.h b/engines/neverhood/modules/module1000.h new file mode 100644 index 0000000000..9977590a6a --- /dev/null +++ b/engines/neverhood/modules/module1000.h @@ -0,0 +1,300 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_MODULES_MODULE1000_H +#define NEVERHOOD_MODULES_MODULE1000_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +// Module1000 + +class Module1000 : public Module { +public: + Module1000(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module1000(); +protected: + int _sceneNum; + uint32 _musicFileHash; + void createScene(int sceneNum, int which); + void updateScene(); +}; + +// Scene1001 + +class AsScene1001Door : public AnimatedSprite { +public: + AsScene1001Door(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void hammerHitsDoor(); + void stShowIdleDoor(); + void stBustedDoorMove(); + void stBustedDoorGone(); +}; + +class AsScene1001Hammer : public AnimatedSprite { +public: + AsScene1001Hammer(NeverhoodEngine *vm, Sprite *asDoor); +protected: + Sprite *_asDoor; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1001Window : public AnimatedSprite { +public: + AsScene1001Window(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1001Lever : public AnimatedSprite { +public: + AsScene1001Lever(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, int deltaXType); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsCommonButtonSprite : public StaticSprite { +public: + SsCommonButtonSprite(NeverhoodEngine *vm, Scene *parentScene, uint32 fileHash, int surfacePriority, uint32 soundFileHash); +protected: + Scene *_parentScene; + uint32 _soundFileHash; + int16 _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene1001 : public Scene { +public: + Scene1001(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Scene1001(); +protected: + Sprite *_asHammer; + Sprite *_asDoor; + Sprite *_asWindow; + Sprite *_asLever; + Sprite *_ssButton; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +// TODO: Move this to some common file since it's used several times + +class StaticScene : public Scene { +public: + StaticScene(NeverhoodEngine *vm, Module *parentModule, uint32 backgroundFileHash, uint32 cursorFileHash); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +// Scene1002 + +class AsScene1002Ring : public AnimatedSprite { +public: + AsScene1002Ring(NeverhoodEngine *vm, Scene *parentScene, bool isSpecial, int16 x, int16 y, int16 clipY1, bool isRingLow); +protected: + Scene *_parentScene; + bool _isSpecial; + void update(); + uint32 hmRingIdle(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmRingPulled1(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmRingPulled2(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmRingHangingLow(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmRingReleased(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1002Door : public StaticSprite { +public: + AsScene1002Door(NeverhoodEngine *vm, NRect &clipRect); +protected: + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void suOpenDoor(); + void suCloseDoor(); +}; + +class AsScene1002BoxingGloveHitEffect : public AnimatedSprite { +public: + AsScene1002BoxingGloveHitEffect(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1002DoorSpy : public AnimatedSprite { +public: + AsScene1002DoorSpy(NeverhoodEngine *vm, NRect &clipRect, Scene *parentScene, Sprite *asDoor, Sprite *asScene1002BoxingGloveHitEffect); +protected: + Scene *_parentScene; + Sprite *_asDoor; + Sprite *_asBoxingGloveHitEffect; + NRect _clipRect; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmDoorSpyAnimation(int messageNum, const MessageParam ¶m, Entity *sender); + void suDoorSpy(); + void stDoorSpyIdle(); + void stDoorSpyBoxingGlove(); +}; + +class SsCommonPressButton : public StaticSprite { +public: + SsCommonPressButton(NeverhoodEngine *vm, Scene *parentScene, uint32 fileHash1, uint32 fileHash2, int surfacePriority, uint32 soundFileHash); + void setFileHashes(uint32 fileHash1, uint32 fileHash2); +protected: + Scene *_parentScene; + uint32 _soundFileHash; + uint32 _fileHashes[2]; + int _status; + int _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1002VenusFlyTrap : public AnimatedSprite { +public: + AsScene1002VenusFlyTrap(NeverhoodEngine *vm, Scene *parentScene, Sprite *klaymen, bool isSecond); +protected: + Scene *_parentScene; + Sprite *_klaymen; + int _countdown; + bool _isSecond; + void update(); + void upIdle(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmAnimationSimple(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmAnimationExt(int messageNum, const MessageParam ¶m, Entity *sender); + void stWalkBack(); + void stWalk(); + void stRelease(); + void stGrabRing(); + void stRingGrabbed(); + void stKlaymenInside(); + void stIdle(); + void stKlaymenInsideMoving(); + void stSpitOutKlaymen(); + void swallowKlaymen(); +}; + +class AsScene1002OutsideDoorBackground : public AnimatedSprite { +public: + AsScene1002OutsideDoorBackground(NeverhoodEngine *vm); +protected: + int _countdown; + bool _isDoorClosed; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmAnimation(int messageNum, const MessageParam ¶m, Entity *sender); + void stOpenDoor(); + void stCloseDoor(); + void stDoorClosed(); +}; + +class AsScene1002KlaymenLadderHands : public AnimatedSprite { +public: + AsScene1002KlaymenLadderHands(NeverhoodEngine *vm, Klaymen *klaymen); +protected: + Klaymen *_klaymen; + void update(); +}; + +class AsScene1002KlaymenPeekHand : public AnimatedSprite { +public: + AsScene1002KlaymenPeekHand(NeverhoodEngine *vm, Scene *parentScene, Klaymen *klaymen); +protected: + Scene *_parentScene; + Klaymen *_klaymen; + bool _isClipRectSaved; + NRect _savedClipRect; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene1002 : public Scene { +public: + Scene1002(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Scene1002(); +protected: + Sprite *_asRing1; + Sprite *_asRing2; + Sprite *_asRing3; + Sprite *_asRing4; + Sprite *_asRing5; + Sprite *_asDoor; + Sprite *_asDoorSpy; + Sprite *_asVenusFlyTrap; + Sprite *_ssLadderArch; + Sprite *_ssLadderArchPart1; + Sprite *_ssLadderArchPart2; + Sprite *_ssLadderArchPart3; + Sprite *_ssCeiling; + Sprite *_asKlaymenLadderHands; + Sprite *_asKlaymenPeekHand; + Sprite *_asOutsideDoorBackground; + Sprite *_ssPressButton; + bool _isKlaymenFloor; + bool _isClimbingLadder; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +// Scene1004 + +class AsScene1004TrashCan : public AnimatedSprite { +public: + AsScene1004TrashCan(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene1004 : public Scene { +public: + Scene1004(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_asKlaymenLadderHands; + Sprite *_asTrashCan; + int _paletteAreaStatus; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void updatePaletteArea(); +}; + +// Scene1005 + +class Scene1005 : public Scene { +public: + Scene1005(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void drawTextToBackground(); + uint32 getTextIndex(); + uint32 getTextIndex1(); + uint32 getTextIndex2(); + uint32 getTextIndex3(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE1000_H */ diff --git a/engines/neverhood/modules/module1100.cpp b/engines/neverhood/modules/module1100.cpp new file mode 100644 index 0000000000..5a5e52e5b0 --- /dev/null +++ b/engines/neverhood/modules/module1100.cpp @@ -0,0 +1,707 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module1100.h" +#include "neverhood/gamemodule.h" +#include "neverhood/navigationscene.h" + +namespace Neverhood { + +static const uint32 kModule1100SoundList[] = { + 0x90805C50, + 0xB288D450, + 0x98C05840, + 0x98A01500, + 0xB4005E50, + 0x92025040, + 0x90035066, + 0x74E01054, + 0 +}; + +Module1100::Module1100(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + if (which < 0) { + createScene(_vm->gameState().sceneNum, -1); + } else if (which == 1) { + createScene(8, 1); + } else { + createScene(8, 3); + } + + _vm->_soundMan->addSoundList(0x0002C818, kModule1100SoundList); + _vm->_soundMan->setSoundListParams(kModule1100SoundList, true, 50, 600, 20, 250); + _vm->_soundMan->setSoundParams(0x74E01054, false, 100, 200, 10, 20); + _vm->_soundMan->setSoundVolume(0x74E01054, 60); + _vm->_soundMan->playTwoSounds(0x0002C818, 0x41861371, 0x43A2507F, 0); + +} + +Module1100::~Module1100() { + _vm->_soundMan->deleteGroup(0x0002C818); +} + +void Module1100::createScene(int sceneNum, int which) { + static const uint32 kSmackerFileHashList06[] = {0x10880805, 0x1088081D, 0}; + static const uint32 kSmackerFileHashList07[] = {0x00290321, 0x01881000, 0}; + debug("Module1100::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _countdown = 65; + createNavigationScene(0x004B8430, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + _countdown = 50; + createNavigationScene(0x004B8460, which); + break; + case 2: + _vm->gameState().sceneNum = 2; + if (getGlobalVar(V_ROBOT_TARGET)) { + createNavigationScene(0x004B84F0, which); + } else { + createNavigationScene(0x004B8490, which); + } + break; + case 3: + _vm->gameState().sceneNum = 3; + if (getGlobalVar(V_ROBOT_TARGET)) { + createNavigationScene(0x004B8580, which); + } else { + createNavigationScene(0x004B8550, which); + } + break; + case 4: + _vm->gameState().sceneNum = 4; + _childObject = new Scene1105(_vm, this); + break; + case 5: + _vm->gameState().sceneNum = 5; + if (getGlobalVar(V_ROBOT_TARGET)) + createSmackerScene(0x04180001, true, false, false); + else + createSmackerScene(0x04180007, true, false, false); + break; + case 6: + _vm->gameState().sceneNum = 6; + _vm->_soundMan->deleteSoundGroup(0x0002C818); + createSmackerScene(kSmackerFileHashList06, true, true, false); + break; + case 7: + _vm->gameState().sceneNum = 7; + _vm->_soundMan->setSoundParams(0x74E01054, false, 0, 0, 0, 0); + createSmackerScene(kSmackerFileHashList07, true, true, false); + break; + case 8: + _vm->gameState().sceneNum = 8; + _childObject = new Scene1109(_vm, this, which); + break; + case 1002: + _vm->gameState().sceneNum = 2; + _countdown = 40; + _vm->_soundMan->setTwoSoundsPlayFlag(true); + createSmackerScene(0x00012211, true, true, false); + break; + } + SetUpdateHandler(&Module1100::updateScene); + _childObject->handleUpdate(); +} + +void Module1100::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + _countdown = 0; + _vm->_soundMan->playTwoSounds(0x0002C818, 0x48498E46, 0x50399F64, 0); + _vm->_soundMan->setSoundVolume(0x48498E46, 65); + _vm->_soundMan->setSoundVolume(0x50399F64, 65); + if (_moduleResult == 0) + createScene(1, 0); + else if (_moduleResult == 1) + createScene(8, 0); + break; + case 1: + _vm->_soundMan->playTwoSounds(0x0002C818, 0x41861371, 0x43A2507F, 0); + if (getGlobalVar(V_ROBOT_HIT)) { + if (_moduleResult == 0) + createScene(6, -1); + else if (_moduleResult == 1) + createScene(0, 1); + } else { + if (_moduleResult == 0) + createScene(2, 0); + else if (_moduleResult == 1) + createScene(0, 1); + } + break; + case 2: + _vm->_soundMan->setSoundParams(0x74E01054, false, 0, 0, 0, 0); + if (_navigationAreaType == 3) + createScene(7, -1); + else if (_moduleResult == 1) + createScene(3, 0); + else if (_moduleResult == 2) + createScene(1002, -1); + break; + case 3: + if (_moduleResult == 0) + createScene(4, 0); + else if (_moduleResult == 1) + createScene(2, 3); + break; + case 4: + if (_moduleResult == 0) + createScene(3, 0); + else if (_moduleResult == 1) + createScene(5, -1); + break; + case 5: + _vm->_soundMan->setTwoSoundsPlayFlag(false); + if (getGlobalVar(V_ROBOT_TARGET)) + createScene(3, 0); + else + createScene(4, 0); + break; + case 6: + _vm->_soundMan->setTwoSoundsPlayFlag(false); + leaveModule(1); + break; + case 7: + _vm->_soundMan->setTwoSoundsPlayFlag(false); + createScene(2, 2); + break; + case 8: + if (_moduleResult == 0) + createScene(0, 0); + else if (_moduleResult == 1) + leaveModule(0); + break; + case 1002: + _vm->_soundMan->setTwoSoundsPlayFlag(false); + _countdown = 0; + _vm->_soundMan->playTwoSounds(0x0002C818, 0x48498E46, 0x50399F64, 0); + createScene(1, 1); + break; + } + } else { + switch (_sceneNum) { + case 0: + if (navigationScene()->isWalkingForward() && _countdown != 0 && (--_countdown == 0)) { + _vm->_soundMan->playTwoSounds(0x0002C818, 0x48498E46, 0x50399F64, 0); + _vm->_soundMan->setSoundVolume(0x48498E46, 65); + _vm->_soundMan->setSoundVolume(0x50399F64, 65); + } + break; + case 1: + if (navigationScene()->isWalkingForward() && _countdown != 0 && (--_countdown == 0)) + _vm->_soundMan->playTwoSounds(0x0002C818, 0x41861371, 0x43A2507F, 0); + break; + case 2: + _vm->_soundMan->setSoundParams(0x74E01054, !navigationScene()->isWalkingForward(), 0, 0, 0, 0); + break; + case 5: + case 6: + case 7: + case 1002: + if (_countdown != 0 && (--_countdown == 0)) { + _vm->_soundMan->playTwoSounds(0x0002C818, 0x48498E46, 0x50399F64, 0); + _vm->_soundMan->setSoundVolume(0x48498E46, 65); + _vm->_soundMan->setSoundVolume(0x50399F64, 65); + } + break; + } + } +} + +static const uint32 kScene1105FileHashes[] = { + 0x00028006, + 0x0100A425, + 0x63090415, + 0x082100C4, + 0x0068C607, + 0x00018344, + 0x442090E4, + 0x0400E004, + 0x5020A054, + 0xB14A891E +}; + +static const uint32 kScene1105BackgroundFileHashes[] = { + 0x20018662, + 0x20014202, + 0x20012202, + 0x20010002 // CHECKME: This used ?? +}; + +static const uint32 kSsScene1105SymbolDieFileHashes[] = { + 0, + 0x90898414, + 0x91098414, + 0x92098414, + 0x94098414, + 0x98098414, + 0x80098414, + 0xB0098414, + 0xD0098414, + 0x10098414 +}; + +SsScene1105Button::SsScene1105Button(NeverhoodEngine *vm, Scene *parentScene, uint32 fileHash, NRect &collisionBounds) + : StaticSprite(vm, fileHash, 200), _parentScene(parentScene), _countdown(0) { + + _collisionBounds = collisionBounds; + SetMessageHandler(&SsScene1105Button::handleMessage); + SetUpdateHandler(&SsScene1105Button::update); + setVisible(false); +} + +void SsScene1105Button::update() { + if (_countdown != 0 && (--_countdown == 0)) { + sendMessage(_parentScene, 0x4807, 0); + setVisible(false); + } +} + +uint32 SsScene1105Button::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_countdown == 0) { + sendMessage(_parentScene, 0x4826, 0); + messageResult = 1; + } + break; + case 0x480B: + _countdown = 8; + setVisible(true); + playSound(0, 0x44141000); + break; + } + return messageResult; +} + +SsScene1105Symbol::SsScene1105Symbol(NeverhoodEngine *vm, uint32 fileHash, int16 x, int16 y) + : StaticSprite(vm, 0) { + + loadSprite(fileHash, kSLFCenteredDrawOffset | kSLFSetPosition, 200, x, y); +} + +void SsScene1105Symbol::hide() { + setVisible(false); + _needRefresh = true; + updatePosition(); +} + +SsScene1105SymbolDie::SsScene1105SymbolDie(NeverhoodEngine *vm, uint dieIndex, int16 x, int16 y) + : StaticSprite(vm, 1100), _dieIndex(dieIndex) { + + _x = x; + _y = y; + createSurface(200, 50, 50); + loadSymbolSprite(); + SetMessageHandler(&SsScene1105SymbolDie::handleMessage); +} + +uint32 SsScene1105SymbolDie::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + loadSymbolSprite(); + break; + } + return messageResult; +} + +void SsScene1105SymbolDie::loadSymbolSprite() { + loadSprite(kSsScene1105SymbolDieFileHashes[getSubVar(VA_CURR_DICE_NUMBERS, _dieIndex)], kSLFCenteredDrawOffset); +} + +void SsScene1105SymbolDie::hide() { + setVisible(false); + _needRefresh = true; + updatePosition(); +} + +AsScene1105TeddyBear::AsScene1105TeddyBear(NeverhoodEngine *vm, Scene *parentScene) + : AnimatedSprite(vm, 1100), _parentScene(parentScene) { + + createSurface(100, 556, 328); + _x = 320; + _y = 240; + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1105TeddyBear::handleMessage); + startAnimation(0x65084002, 0, -1); + _newStickFrameIndex = 0; + setVisible(false); + _needRefresh = true; + updatePosition(); + loadSound(0, 0xCE840261); + loadSound(1, 0xCCA41A62); +} + +uint32 AsScene1105TeddyBear::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2002: + if (getGlobalVar(V_ROBOT_TARGET)) { + startAnimation(0x6B0C0432, 0, -1); + playSound(0); + } else { + startAnimation(0x65084002, 0, -1); + playSound(1); + } + break; + case 0x3002: + sendMessage(_parentScene, 0x2003, 0); + stopAnimation(); + break; + } + return messageResult; +} + +void AsScene1105TeddyBear::show() { + setVisible(true); + _needRefresh = true; + updatePosition(); +} + +void AsScene1105TeddyBear::hide() { + setVisible(false); + _needRefresh = true; + updatePosition(); +} + +SsScene1105OpenButton::SsScene1105OpenButton(NeverhoodEngine *vm, Scene *parentScene) + : StaticSprite(vm, 900), _parentScene(parentScene), _countdown(0), _isClicked(false) { + + loadSprite(0x8228A46C, kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset, 400); + setVisible(false); + loadSound(0, 0x44045140); + SetUpdateHandler(&SsScene1105OpenButton::update); + SetMessageHandler(&SsScene1105OpenButton::handleMessage); +} + +void SsScene1105OpenButton::update() { + updatePosition(); + if (_countdown != 0 && (--_countdown == 0)) { + setVisible(false); + sendMessage(_parentScene, 0x2001, 0); + } +} + +uint32 SsScene1105OpenButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = 0; + Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_countdown == 0 && !_isClicked) { + playSound(0); + setVisible(true); + _isClicked = true; + _countdown = 4; + } + messageResult = 1; + break; + } + return messageResult; +} + +Scene1105::Scene1105(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule), _countdown(0), _isPanelOpen(false), _isActionButtonClicked(false), _doMoveTeddy(false), + _isClosePanelDone(false), _leaveResult(0), _backgroundIndex(0) { + + Sprite *ssOpenButton; + + _vm->gameModule()->initMemoryPuzzle(); + + SetUpdateHandler(&Scene1105::update); + SetMessageHandler(&Scene1105::handleMessage); + + setBackground(0x20010002); + setPalette(0x20010002); + + _asTeddyBear = insertSprite<AsScene1105TeddyBear>(this); + ssOpenButton = insertSprite<SsScene1105OpenButton>(this); + addCollisionSprite(ssOpenButton); + insertPuzzleMouse(0x10006208, 20, 620); + + loadSound(0, 0x48442057); + loadSound(1, 0xC025014F); + loadSound(2, 0x68E25540); + +} + +uint32 Scene1105::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = 0; + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) { + if (!_isActionButtonClicked && _backgroundIndex == 0) { + if (_isPanelOpen) { + _isPanelOpen = false; + _backgroundIndex = 15; + SetUpdateHandler(&Scene1105::upClosePanel); + } else + _isPanelOpen = true; + _leaveResult = 0; + } + } + break; + case 0x2001: + showMouse(false); + _backgroundIndex = 24; + SetUpdateHandler(&Scene1105::upOpenPanel); + break; + case 0x2003: + _backgroundIndex = 24; + _leaveResult = 1; + SetUpdateHandler(&Scene1105::upClosePanel); + break; + case 0x4807: + if (sender == _ssActionButton) { + if (getSubVar(VA_GOOD_DICE_NUMBERS, 0) == getSubVar(VA_CURR_DICE_NUMBERS, 0) && + getSubVar(VA_GOOD_DICE_NUMBERS, 1) == getSubVar(VA_CURR_DICE_NUMBERS, 1) && + getSubVar(VA_GOOD_DICE_NUMBERS, 2) == getSubVar(VA_CURR_DICE_NUMBERS, 2)) { + setGlobalVar(V_ROBOT_TARGET, 1); + playSound(2); + _doMoveTeddy = true; + } else { + sendMessage(_asTeddyBear, 0x2002, 0); + } + showMouse(false); + _isActionButtonClicked = true; + } + break; + case 0x4826: + if (_isPanelOpen) { + if (sender == _ssActionButton) { + sendMessage(_ssActionButton, 0x480B, 0); + _isPanelOpen = false; + } else if (!getGlobalVar(V_ROBOT_TARGET)) { + if (sender == _ssSymbol1UpButton) { + if (getSubVar(VA_CURR_DICE_NUMBERS, 0) < 9) { + incSubVar(VA_CURR_DICE_NUMBERS, 0, +1); + sendMessage(_ssSymbol1UpButton, 0x480B, 0); + sendMessage(_ssSymbolDice[0], 0x2000, 0); + } + } else if (sender == _ssSymbol1DownButton) { + if (getSubVar(VA_CURR_DICE_NUMBERS, 0) > 1) { + incSubVar(VA_CURR_DICE_NUMBERS, 0, -1); + sendMessage(_ssSymbol1DownButton, 0x480B, 0); + sendMessage(_ssSymbolDice[0], 0x2000, 0); + } + } else if (sender == _ssSymbol2UpButton) { + if (getSubVar(VA_CURR_DICE_NUMBERS, 1) < 9) { + incSubVar(VA_CURR_DICE_NUMBERS, 1, +1); + sendMessage(_ssSymbol2UpButton, 0x480B, 0); + sendMessage(_ssSymbolDice[1], 0x2000, 0); + } + } else if (sender == _ssSymbol2DownButton) { + if (getSubVar(VA_CURR_DICE_NUMBERS, 1) > 1) { + incSubVar(VA_CURR_DICE_NUMBERS, 1, -1); + sendMessage(_ssSymbol2DownButton, 0x480B, 0); + sendMessage(_ssSymbolDice[1], 0x2000, 0); + } + } else if (sender == _ssSymbol3UpButton) { + if (getSubVar(VA_CURR_DICE_NUMBERS, 2) < 9) { + incSubVar(VA_CURR_DICE_NUMBERS, 2, +1); + sendMessage(_ssSymbol3UpButton, 0x480B, 0); + sendMessage(_ssSymbolDice[2], 0x2000, 0); + } + } else if (sender == _ssSymbol3DownButton) { + if (getSubVar(VA_CURR_DICE_NUMBERS, 2) > 1) { + incSubVar(VA_CURR_DICE_NUMBERS, 2, -1); + sendMessage(_ssSymbol3DownButton, 0x480B, 0); + sendMessage(_ssSymbolDice[2], 0x2000, 0); + } + } + } + } + break; + } + return messageResult; +} + +void Scene1105::createObjects() { + _ssSymbols[0] = insertSprite<SsScene1105Symbol>(kScene1105FileHashes[getSubVar(VA_DICE_MEMORY_SYMBOLS, 0)], 161, 304); + _ssSymbols[1] = insertSprite<SsScene1105Symbol>(kScene1105FileHashes[getSubVar(VA_DICE_MEMORY_SYMBOLS, 1)], 294, 304); + _ssSymbols[2] = insertSprite<SsScene1105Symbol>(kScene1105FileHashes[getSubVar(VA_DICE_MEMORY_SYMBOLS, 2)], 440, 304); + + _ssSymbolDice[0] = insertSprite<SsScene1105SymbolDie>(0, 206, 304); + _ssSymbolDice[1] = insertSprite<SsScene1105SymbolDie>(1, 339, 304); + _ssSymbolDice[2] = insertSprite<SsScene1105SymbolDie>(2, 485, 304); + + _ssSymbol1UpButton = insertSprite<SsScene1105Button>(this, 0x08002860, NRect(146, 362, 192, 403)); + addCollisionSprite(_ssSymbol1UpButton); + _ssSymbol1DownButton = insertSprite<SsScene1105Button>(this, 0x42012460, NRect(147, 404, 191, 442)); + addCollisionSprite(_ssSymbol1DownButton); + _ssSymbol2UpButton = insertSprite<SsScene1105Button>(this, 0x100030A0, NRect(308, 361, 355, 402)); + addCollisionSprite(_ssSymbol2UpButton); + _ssSymbol2DownButton = insertSprite<SsScene1105Button>(this, 0x840228A0, NRect(306, 406, 352, 445)); + addCollisionSprite(_ssSymbol2DownButton); + _ssSymbol3UpButton = insertSprite<SsScene1105Button>(this, 0x20000120, NRect(476, 358, 509, 394)); + addCollisionSprite(_ssSymbol3UpButton); + _ssSymbol3DownButton = insertSprite<SsScene1105Button>(this, 0x08043121, NRect(463, 401, 508, 438)); + addCollisionSprite(_ssSymbol3DownButton); + _ssActionButton = insertSprite<SsScene1105Button>(this, 0x8248AD35, NRect(280, 170, 354, 245)); + addCollisionSprite(_ssActionButton); + + _isPanelOpen = true; + + _asTeddyBear->show(); + + insertPuzzleMouse(0x18666208, 20, 620); + +} + +void Scene1105::upOpenPanel() { + Scene::update(); + if (_backgroundIndex != 0) { + _backgroundIndex--; + if (_backgroundIndex < 6 && _backgroundIndex % 2 == 0) { + uint32 backgroundFileHash = kScene1105BackgroundFileHashes[_backgroundIndex / 2]; + changeBackground(backgroundFileHash); + _palette->addPalette(backgroundFileHash, 0, 256, 0); + } + if (_backgroundIndex == 10) + playSound(0); + if (_backgroundIndex == 0) { + SetUpdateHandler(&Scene1105::update); + _countdown = 2; + } + } +} + +void Scene1105::upClosePanel() { + Scene::update(); + if (_backgroundIndex != 0) { + _backgroundIndex--; + if (_backgroundIndex == 14) { + showMouse(false); + _ssSymbols[0]->hide(); + _ssSymbols[1]->hide(); + _ssSymbols[2]->hide(); + _ssSymbolDice[0]->hide(); + _ssSymbolDice[1]->hide(); + _ssSymbolDice[2]->hide(); + } + if (_backgroundIndex < 6 && _backgroundIndex % 2 == 0) { + uint32 backgroundFileHash = kScene1105BackgroundFileHashes[3 - _backgroundIndex / 2]; // CHECKME + if (_backgroundIndex == 4) { + playSound(1); + _asTeddyBear->hide(); + } + changeBackground(backgroundFileHash); + _palette->addPalette(backgroundFileHash, 0, 256, 0); + } + if (_backgroundIndex == 0) { + SetUpdateHandler(&Scene1105::update); + _isClosePanelDone = true; + } + } +} + +void Scene1105::update() { + + // DEBUG: Show the correct code + debug("(%d, %d) (%d, %d) (%d, %d)", + getSubVar(VA_GOOD_DICE_NUMBERS, 0), getSubVar(VA_CURR_DICE_NUMBERS, 0), + getSubVar(VA_GOOD_DICE_NUMBERS, 1), getSubVar(VA_CURR_DICE_NUMBERS, 1), + getSubVar(VA_GOOD_DICE_NUMBERS, 2), getSubVar(VA_CURR_DICE_NUMBERS, 2)); + + Scene::update(); + if (_countdown != 0 && (--_countdown == 0)) + createObjects(); + if (_isClosePanelDone && !isSoundPlaying(1)) + leaveScene(_leaveResult); + if (_doMoveTeddy && !isSoundPlaying(2)) { + sendMessage(_asTeddyBear, 0x2002, 0); + _doMoveTeddy = false; + } +} + +Scene1109::Scene1109(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + SetMessageHandler(&Scene1109::handleMessage); + + setBackground(0x8449E02F); + setPalette(0x8449E02F); + insertScreenMouse(0x9E02B84C); + + _sprite1 = insertStaticSprite(0x600CEF01, 1100); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene1109>(140, 436); + setMessageList(0x004B6260); + sendMessage(this, 0x2000, 0); + } else if (which == 1) { + // Klaymen teleporting in + insertKlaymen<KmScene1109>(450, 436); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004B6268, false); + sendMessage(this, 0x2000, 1); + } else if (which == 2) { + // Klaymen teleporting out + insertKlaymen<KmScene1109>(450, 436); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004B6318, false); + sendMessage(this, 0x2000, 1); + } else if (which == 3) { + // Klaymen returning from teleporter console + insertKlaymen<KmScene1109>(450, 436); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004B6278, false); + sendMessage(this, 0x2000, 1); + } else { + // Klaymen entering from the left + insertKlaymen<KmScene1109>(0, 436); + setMessageList(0x004B6258); + sendMessage(this, 0x2000, 0); + } + + _klaymen->setClipRect(0, 0, _sprite1->getDrawRect().x2(), 480); + +} + +uint32 Scene1109::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + if (param.asInteger()) { + setRectList(0x004B63A8); + _klaymen->setKlaymenIdleTable3(); + } else { + setRectList(0x004B6398); + _klaymen->setKlaymenIdleTable1(); + } + break; + } + return 0; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module1100.h b/engines/neverhood/modules/module1100.h new file mode 100644 index 0000000000..373f6b703f --- /dev/null +++ b/engines/neverhood/modules/module1100.h @@ -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. + * + */ + +#ifndef NEVERHOOD_MODULES_MODULE1100_H +#define NEVERHOOD_MODULES_MODULE1100_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +// Module1100 + +class Module1100 : public Module { +public: + Module1100(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module1100(); +protected: + int _sceneNum; + int _countdown; + void createScene(int sceneNum, int which); + void updateScene(); +}; + +class SsScene1105Button : public StaticSprite { +public: + SsScene1105Button(NeverhoodEngine *vm, Scene *parentScene, uint32 fileHash, NRect &collisionBounds); +protected: + Scene *_parentScene; + int _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsScene1105Symbol : public StaticSprite { +public: + SsScene1105Symbol(NeverhoodEngine *vm, uint32 fileHash, int16 x, int16 y); + void hide(); +}; + +class SsScene1105SymbolDie : public StaticSprite { +public: + SsScene1105SymbolDie(NeverhoodEngine *vm, uint dieIndex, int16 x, int16 y); + void hide(); +protected: + uint _dieIndex; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void loadSymbolSprite(); +}; + +class AsScene1105TeddyBear : public AnimatedSprite { +public: + AsScene1105TeddyBear(NeverhoodEngine *vm, Scene *parentScene); + void show(); + void hide(); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsScene1105OpenButton : public StaticSprite { +public: + SsScene1105OpenButton(NeverhoodEngine *vm, Scene *parentScene); +protected: + Scene *_parentScene; + int _countdown; + bool _isClicked; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene1105 : public Scene { +public: + Scene1105(NeverhoodEngine *vm, Module *parentModule); +protected: + int _countdown; + int _backgroundIndex; + bool _isPanelOpen; + bool _isActionButtonClicked; + bool _doMoveTeddy; + bool _isClosePanelDone; + int _leaveResult; + AsScene1105TeddyBear *_asTeddyBear; + SsScene1105Symbol *_ssSymbols[3]; + SsScene1105SymbolDie *_ssSymbolDice[3]; + Sprite *_ssSymbol1UpButton; + Sprite *_ssSymbol1DownButton; + Sprite *_ssSymbol2UpButton; + Sprite *_ssSymbol2DownButton; + Sprite *_ssSymbol3UpButton; + Sprite *_ssSymbol3DownButton; + Sprite *_ssActionButton; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void createObjects(); + void upOpenPanel(); + void upClosePanel(); + void update(); +}; + +class Scene1109 : public Scene { +public: + Scene1109(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_sprite1; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE1100_H */ diff --git a/engines/neverhood/modules/module1200.cpp b/engines/neverhood/modules/module1200.cpp new file mode 100644 index 0000000000..3be3635645 --- /dev/null +++ b/engines/neverhood/modules/module1200.cpp @@ -0,0 +1,1102 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module1200.h" + +namespace Neverhood { + +Module1200::Module1200(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + SetMessageHandler(&Module1200::handleMessage); + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else if (which == 1) + createScene(0, 2); + else + createScene(0, 0); + + _vm->_soundMan->addMusic(0x00478311, 0x62222CAE); + _vm->_soundMan->startMusic(0x62222CAE, 0, 0); +} + +Module1200::~Module1200() { + _vm->_soundMan->deleteMusicGroup(0x00478311); +} + +void Module1200::createScene(int sceneNum, int which) { + debug("Module1200::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _childObject = new Scene1201(_vm, this, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + _childObject = new Scene1202(_vm, this); + break; + case 2: + _vm->gameState().sceneNum = 2; + _vm->_soundMan->stopMusic(0x62222CAE, 0, 0); + createSmackerScene(0x31890001, true, true, false); + setGlobalVar(V_SEEN_CREATURE_EXPLODE_VID, 1); + break; + } + SetUpdateHandler(&Module1200::updateScene); + _childObject->handleUpdate(); +} + +void Module1200::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 1) + createScene(1, 0); + else if (_moduleResult == 2) { + if (getGlobalVar(V_CREATURE_EXPLODED) && !getGlobalVar(V_SEEN_CREATURE_EXPLODE_VID)) + createScene(2, -1); + else + leaveModule(1); + } else + leaveModule(0); + break; + case 1: + createScene(0, 1); + break; + case 2: + _vm->_soundMan->startMusic(0x62222CAE, 0, 0); + createScene(0, 3); + break; + } + } +} + +// Scene1201 + +static const uint32 kScene1201InitArray[] = { + 1, 0, 2, 4, 5, 3, 6, 7, 8, 10, 9, 11, 13, 14, 12, 16, 17, 15 +}; + +static const NPoint kScene1201PointArray[] = { + {218, 193}, {410, 225}, {368, 277}, + {194, 227}, {366, 174}, {458, 224}, + {242, 228}, {512, 228}, {458, 277}, + {217, 233}, {458, 173}, {410, 276}, + {203, 280}, {371, 226}, {508, 279}, + {230, 273}, {410, 171}, {493, 174} +}; + +static const uint32 kScene1201TntFileHashList1[] = { + 0x2098212D, 0x1600437E, 0x1600437E, + 0x00A840E3, 0x1A1830F6, 0x1A1830F6, + 0x00212062, 0x384010B6, 0x384010B6, + 0x07A01080, 0xD80C2837, 0xD80C2837, + 0x03A22092, 0xD8802CB6, 0xD8802CB6, + 0x03A93831, 0xDA460476, 0xDA460476 +}; + +static const uint32 kScene1201TntFileHashList2[] = { + 0x3040C676, 0x10914448, 0x10914448, + 0x3448A066, 0x1288C049, 0x1288C049, + 0x78C0E026, 0x3098D05A, 0x3098D05A, + 0x304890E6, 0x1284E048, 0x1284E048, + 0xB140A1E6, 0x5088A068, 0x5088A068, + 0x74C4C866, 0x3192C059, 0x3192C059 +}; + +SsScene1201Tnt::SsScene1201Tnt(NeverhoodEngine *vm, uint32 elemIndex, uint32 pointIndex, int16 clipY2) + : StaticSprite(vm, 900) { + + int16 x = kScene1201PointArray[pointIndex].x; + int16 y = kScene1201PointArray[pointIndex].y; + if (x < 300) + loadSprite(kScene1201TntFileHashList1[elemIndex], kSLFDefDrawOffset | kSLFDefPosition, 50); + else + loadSprite(kScene1201TntFileHashList2[elemIndex], kSLFCenteredDrawOffset | kSLFSetPosition, 50, x, y); + setClipRect(0, 0, 640, clipY2); +} + +AsScene1201Tape::AsScene1201Tape(NeverhoodEngine *vm, Scene *parentScene, uint32 nameHash, int surfacePriority, int16 x, int16 y, uint32 fileHash) + : AnimatedSprite(vm, fileHash, surfacePriority, x, y), _parentScene(parentScene), _nameHash(nameHash) { + + if (!getSubVar(VA_HAS_TAPE, _nameHash) && !getSubVar(VA_IS_TAPE_INSERTED, _nameHash)) { + SetMessageHandler(&AsScene1201Tape::handleMessage); + } else { + setVisible(false); + SetMessageHandler(NULL); + } +} + +uint32 AsScene1201Tape::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + sendMessage(_parentScene, 0x4826, 0); + messageResult = 1; + break; + case 0x4806: + setSubVar(VA_HAS_TAPE, _nameHash, 1); + setVisible(false); + SetMessageHandler(NULL); + break; + } + return messageResult; +} + +AsScene1201TntManRope::AsScene1201TntManRope(NeverhoodEngine *vm, bool isDummyHanging) + : AnimatedSprite(vm, 1200) { + + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1201TntManRope::handleMessage); + createSurface(10, 34, 149); + _x = 202; + _y = -32; + if (isDummyHanging) { + startAnimation(0x928F0C10, 15, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + } else { + startAnimation(0x928F0C10, 0, -1); + _newStickFrameIndex = 0; + } +} + +uint32 AsScene1201TntManRope::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x02060018) + playSound(0, 0x47900E06); + break; + case 0x2006: + startAnimation(0x928F0C10, 1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + break; + } + return messageResult; +} + +AsScene1201RightDoor::AsScene1201RightDoor(NeverhoodEngine *vm, Sprite *klaymen, bool isOpen) + : AnimatedSprite(vm, 1100), _klaymen(klaymen), _countdown(0) { + + createSurface1(0xD088AC30, 100); + _x = 320; + _y = 240; + SetUpdateHandler(&AsScene1201RightDoor::update); + SetMessageHandler(&AsScene1201RightDoor::handleMessage); + _newStickFrameIndex = STICK_LAST_FRAME; + if (isOpen) { + startAnimation(0xD088AC30, -1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + _countdown = 25; + } else { + stopAnimation(); + setVisible(false); + } +} + +void AsScene1201RightDoor::update() { + if (_countdown != 0 && (--_countdown == 0)) + stCloseDoor(); + AnimatedSprite::update(); +} + +uint32 AsScene1201RightDoor::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + case 0x4829: + stOpenDoor(); + break; + } + return messageResult; +} + +void AsScene1201RightDoor::stOpenDoor() { + startAnimation(0xD088AC30, 0, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + setVisible(true); + playSound(0, calcHash("fxDoorOpen20")); +} + +void AsScene1201RightDoor::stCloseDoor() { + startAnimation(0xD088AC30, -1, -1); + _playBackwards = true; + setVisible(true); + playSound(0, calcHash("fxDoorClose20")); + NextState(&AsScene1201RightDoor::stCloseDoorDone); +} + +void AsScene1201RightDoor::stCloseDoorDone() { + stopAnimation(); + setVisible(false); +} + +AsScene1201KlaymenHead::AsScene1201KlaymenHead(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1200) { + + createSurface(1200, 69, 98); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1201KlaymenHead::handleMessage); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + setVisible(false); +} + +uint32 AsScene1201KlaymenHead::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2006: + _x = 436; + _y = 339; + startAnimation(0xA060C599, 0, -1); + setVisible(true); + break; + case 0x3002: + stopAnimation(); + setVisible(false); + gotoNextState(); + break; + } + return messageResult; +} + +AsScene1201TntMan::AsScene1201TntMan(NeverhoodEngine *vm, Scene *parentScene, Sprite *asTntManRope, bool isComingDown) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _asTntManRope(asTntManRope), + _isMoving(false) { + + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1201TntMan::handleMessage); + createSurface(990, 106, 181); + _x = 201; + if (isComingDown) { + _y = 297; + stComingDown(); + } else { + _y = 334; + stStanding(); + } +} + +AsScene1201TntMan::~AsScene1201TntMan() { + _vm->_soundMan->deleteSoundGroup(0x01D00560); +} + +uint32 AsScene1201TntMan::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x092870C0) + sendMessage(_asTntManRope, 0x2006, 0); + else if (param.asInteger() == 0x11CA0144) + playSound(0, 0x51800A04); + break; + case 0x1011: + sendMessage(_parentScene, 0x2002, 0); + messageResult = 1; + break; + case 0x480B: + if (!_isMoving) { + _sprite = (Sprite*)sender; + stMoving(); + } + break; + } + return messageResult; + +} + +uint32 AsScene1201TntMan::hmComingDown(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = AsScene1201TntMan::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene1201TntMan::suMoving() { + _x = _sprite->getX() + 100; +} + +void AsScene1201TntMan::stStanding() { + startAnimation(0x654913D0, 0, -1); + SetMessageHandler(&AsScene1201TntMan::handleMessage); + SetSpriteUpdate(NULL); +} + +void AsScene1201TntMan::stComingDown() { + startAnimation(0x356803D0, 0, -1); + SetMessageHandler(&AsScene1201TntMan::hmComingDown); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + NextState(&AsScene1201TntMan::stStanding); +} + +void AsScene1201TntMan::stMoving() { + _vm->_soundMan->addSound(0x01D00560, 0x4B044624); + _vm->_soundMan->playSoundLooping(0x4B044624); + _isMoving = true; + startAnimation(0x85084190, 0, -1); + SetMessageHandler(&AsScene1201TntMan::handleMessage); + SetSpriteUpdate(&AsScene1201TntMan::suMoving); + _newStickFrameIndex = STICK_LAST_FRAME; +} + +AsScene1201TntManFlame::AsScene1201TntManFlame(NeverhoodEngine *vm, Sprite *asTntMan) + : AnimatedSprite(vm, 1200), _asTntMan(asTntMan) { + + createSurface1(0x828C0411, 995); + SetUpdateHandler(&AsScene1201TntManFlame::update); + SetMessageHandler(&Sprite::handleMessage); + SetSpriteUpdate(&AsScene1201TntManFlame::suUpdate); + startAnimation(0x828C0411, 0, -1); + setVisible(false); +} + +AsScene1201TntManFlame::~AsScene1201TntManFlame() { + _vm->_soundMan->deleteSoundGroup(0x041080A4); +} + +void AsScene1201TntManFlame::update() { + AnimatedSprite::update(); + if (getGlobalVar(V_TNT_DUMMY_FUSE_LIT)) { + setVisible(true); + SetUpdateHandler(&AnimatedSprite::update); + _vm->_soundMan->addSound(0x041080A4, 0x460A1050); + _vm->_soundMan->playSoundLooping(0x460A1050); + } +} + +void AsScene1201TntManFlame::suUpdate() { + _x = _asTntMan->getX() - 18; + _y = _asTntMan->getY() - 158; +} + +AsScene1201Match::AsScene1201Match(NeverhoodEngine *vm, Scene *parentScene) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _countdown(0) { + + createSurface(1100, 57, 60); + SetUpdateHandler(&AsScene1201Match::update); + SetMessageHandler(&AsScene1201Match::hmOnDoorFrameAboutToMove); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + switch (getGlobalVar(V_MATCH_STATUS)) { + case 0: + _x = 521; + _y = 112; + _status = 0; + stIdleOnDoorFrame(); + break; + case 1: + _x = 521; + _y = 112; + _status = 2; + stOnDoorFrameAboutToMove(); + loadSound(0, 0xD00230CD); + break; + case 2: + setDoDeltaX(1); + _x = 403; + _y = 337; + _status = 0; + stIdleOnFloor(); + break; + } +} + +void AsScene1201Match::update() { + if (_countdown != 0 && (--_countdown == 0)) + gotoNextState(); + updateAnim(); + handleSpriteUpdate(); + updatePosition(); +} + +uint32 AsScene1201Match::hmOnDoorFrameAboutToMove(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x86668011) + playSound(0); + break; + } + return messageResult; +} + +uint32 AsScene1201Match::hmOnDoorFrameMoving(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmOnDoorFrameAboutToMove(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +uint32 AsScene1201Match::hmIdle(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmOnDoorFrameAboutToMove(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + sendMessage(_parentScene, 0x2001, 0); + messageResult = 1; + break; + case 0x4806: + setVisible(false); + setGlobalVar(V_MATCH_STATUS, 3); + break; + } + return messageResult; +} + +void AsScene1201Match::stOnDoorFrameMoving() { + startAnimation(0x00842374, 0, -1); + SetMessageHandler(&AsScene1201Match::hmOnDoorFrameMoving); + if (_status == 0) { + NextState(&AsScene1201Match::stFallingFromDoorFrame); + } else { + NextState(&AsScene1201Match::stOnDoorFrameAboutToMove); + } +} + +void AsScene1201Match::stFallingFromDoorFrame() { + setGlobalVar(V_MATCH_STATUS, 2); + _x -= 199; + _y += 119; + startAnimation(0x018D0240, 0, -1); + SetMessageHandler(&AsScene1201Match::hmOnDoorFrameMoving); + NextState(&AsScene1201Match::stIdleOnFloor); +} + +void AsScene1201Match::stOnDoorFrameAboutToMove() { + startAnimation(0x00842374, 0, -1); + SetMessageHandler(&AsScene1201Match::hmOnDoorFrameAboutToMove); + _newStickFrameIndex = 0; + if (_status != 0) { + _countdown = 36; + _status--; + NextState(&AsScene1201Match::stOnDoorFrameMoving); + } +} + +void AsScene1201Match::stIdleOnDoorFrame() { + startAnimation(0x00842374, 0, -1); + SetMessageHandler(&AsScene1201Match::hmIdle); + _newStickFrameIndex = 0; +} + +void AsScene1201Match::stIdleOnFloor() { + setDoDeltaX(1); + _x = 403; + _y = 337; + startAnimation(0x00842374, 0, -1); + SetMessageHandler(&AsScene1201Match::hmIdle); + _newStickFrameIndex = 0; +} + +AsScene1201Creature::AsScene1201Creature(NeverhoodEngine *vm, Scene *parentScene, Sprite *klaymen) + : AnimatedSprite(vm, 900), _parentScene(parentScene), _klaymen(klaymen), _klaymenTooClose(false) { + + // NOTE: _countdown2 and _countdown3 were unused/without effect and thus removed + + createSurface(1100, 203, 199); + SetUpdateHandler(&AsScene1201Creature::update); + SetMessageHandler(&AsScene1201Creature::hmWaiting); + _x = 540; + _y = 320; + stWaiting(); +} + +void AsScene1201Creature::update() { + bool oldKlaymenTooClose = _klaymenTooClose; + _klaymenTooClose = _klaymen->getX() >= 385; + if (_klaymenTooClose != oldKlaymenTooClose) + stWaiting(); + if (_countdown != 0 && (--_countdown == 0)) + gotoNextState(); + updateAnim(); + handleSpriteUpdate(); + updatePosition(); +} + +uint32 AsScene1201Creature::hmWaiting(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x02060018) + playSound(0, 0xCD298116); + break; + case 0x2004: + GotoState(&AsScene1201Creature::stStartReachForTntDummy); + break; + case 0x2006: + GotoState(&AsScene1201Creature::stPincerSnapKlaymen); + break; + } + return messageResult; +} + +uint32 AsScene1201Creature::hmPincerSnap(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = hmWaiting(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +uint32 AsScene1201Creature::hmPincerSnapKlaymen(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x02060018) { + playSound(0, 0xCD298116); + sendMessage(_parentScene, 0x4814, 0); + sendMessage(_klaymen, 0x4814, 0); + } + break; + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene1201Creature::stWaiting() { + startAnimation(0x08081513, 0, -1); + SetMessageHandler(&AsScene1201Creature::hmWaiting); + NextState(&AsScene1201Creature::stPincerSnap); + _countdown = 36; +} + +void AsScene1201Creature::stPincerSnap() { + if (!_klaymenTooClose) { + startAnimation(0xCA287133, 0, -1); + SetMessageHandler(&AsScene1201Creature::hmPincerSnap); + NextState(&AsScene1201Creature::stWaiting); + } +} + +void AsScene1201Creature::stStartReachForTntDummy() { + startAnimation(0x08081513, 0, -1); + SetMessageHandler(&AsScene1201Creature::hmWaiting); + NextState(&AsScene1201Creature::stReachForTntDummy); + _countdown = 48; +} + +void AsScene1201Creature::stReachForTntDummy() { + startAnimation(0x5A201453, 0, -1); + SetMessageHandler(&AsScene1201Creature::hmWaiting); + _countdown = 0; +} + +void AsScene1201Creature::stPincerSnapKlaymen() { + startAnimation(0xCA287133, 0, -1); + SetMessageHandler(&AsScene1201Creature::hmPincerSnapKlaymen); + NextState(&AsScene1201Creature::stWaiting); + _countdown = 0; +} + +AsScene1201LeftDoor::AsScene1201LeftDoor(NeverhoodEngine *vm, Sprite *klaymen) + : AnimatedSprite(vm, 1100), _klaymen(klaymen) { + + _x = 320; + _y = 240; + createSurface(800, 55, 199); + if (_klaymen->getX() < 100) { + startAnimation(0x508A111B, 0, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + playSound(0, calcHash("fxDoorOpen03")); + } else { + startAnimation(0x508A111B, -1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + } + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1201LeftDoor::handleMessage); +} + +uint32 AsScene1201LeftDoor::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x4809: + stCloseDoor(); + break; + } + return messageResult; +} + +void AsScene1201LeftDoor::stCloseDoor() { + startAnimation(0x508A111B, -1, -1); + _playBackwards = true; + _newStickFrameIndex = 0; +} + +Scene1201::Scene1201(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _creatureExploded(false), _asMatch(NULL), _asTntMan(NULL), + _asCreature(NULL), _asTntManRope(NULL), _asLeftDoor(NULL), _asRightDoor(NULL), _asTape(NULL) { + + int16 topY1, topY2, topY3, topY4; + int16 x1, x2; + Sprite *tempSprite; + + SetUpdateHandler(&Scene1201::update); + SetMessageHandler(&Scene1201::handleMessage); + + setHitRects(0x004AEBD0); + + if (!getSubVar(VA_IS_PUZZLE_INIT, 0xE8058B52)) { + setSubVar(VA_IS_PUZZLE_INIT, 0xE8058B52, 1); + for (uint32 index = 0; index < 18; index++) + setSubVar(VA_TNT_POSITIONS, index, kScene1201InitArray[index]); + } + + insertScreenMouse(0x9A2C0409); + + _asTape = insertSprite<AsScene1201Tape>(this, 3, 1100, 243, 340, 0x9148A011); + addCollisionSprite(_asTape); + + tempSprite = insertStaticSprite(0x03C82530, 100); + topY1 = tempSprite->getY() + tempSprite->getDrawRect().height; + + tempSprite = insertStaticSprite(0x88182069, 200); + topY2 = tempSprite->getY() + tempSprite->getDrawRect().height; + + tempSprite = insertStaticSprite(0x476014E0, 300); + topY3 = tempSprite->getY() + tempSprite->getDrawRect().height; + + tempSprite = insertStaticSprite(0x04063110, 500); + topY4 = tempSprite->getY() + 1; + + _asTntManRope = insertSprite<AsScene1201TntManRope>(getGlobalVar(V_TNT_DUMMY_BUILT) && which != 1); + _asTntManRope->setClipRect(0, topY4, 640, 480); + + insertStaticSprite(0x400B04B0, 1200); + + tempSprite = insertStaticSprite(0x40295462, 1200); + x1 = tempSprite->getX(); + + tempSprite = insertStaticSprite(0xA29223FA, 1200); + x2 = tempSprite->getX() + tempSprite->getDrawRect().width; + + _asKlaymenHead = insertSprite<AsScene1201KlaymenHead>(); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene1201>(364, 333); + setMessageList(0x004AEC08); + } else if (which == 3) { + // Klaymen standing after the weasel exploded + insertKlaymen<KmScene1201>(400, 329); + setMessageList(0x004AEC08); + } else if (which == 2) { + // Klaymen entering from the right + if (getGlobalVar(V_CREATURE_ANGRY) && !getGlobalVar(V_CREATURE_EXPLODED)) { + insertKlaymen<KmScene1201>(374, 333); + setMessageList(0x004AEC08); + } else { + insertKlaymen<KmScene1201>(640, 329); + setMessageList(0x004AEC20); + } + } else if (which == 1) { + // Klaymen returning from the TNT console + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) { + insertKlaymen<KmScene1201>(364, 333); + _klaymen->setDoDeltaX(1); + } else { + insertKlaymen<KmScene1201>(246, 333); + } + setMessageList(0x004AEC30); + } else { + // Klaymen entering from the left + insertKlaymen<KmScene1201>(0, 336); + setMessageList(0x004AEC10); + } + + _klaymen->setClipRect(x1, 0, x2, 480); + _klaymen->setRepl(64, 0); + + if (getGlobalVar(V_CREATURE_ANGRY) && !getGlobalVar(V_CREATURE_EXPLODED)) { + setBackground(0x4019A2C4); + setPalette(0x4019A2C4); + _asRightDoor = NULL; + } else { + setBackground(0x40206EC5); + setPalette(0x40206EC5); + _asRightDoor = insertSprite<AsScene1201RightDoor>(_klaymen, which == 2); + } + + if (getGlobalVar(V_TNT_DUMMY_BUILT)) { + insertStaticSprite(0x10002ED8, 500); + if (!getGlobalVar(V_CREATURE_EXPLODED)) { + _asTntMan = insertSprite<AsScene1201TntMan>(this, _asTntManRope, which == 1); + _asTntMan->setClipRect(x1, 0, x2, 480); + _asTntMan->setRepl(64, 0); + addCollisionSprite(_asTntMan); + tempSprite = insertSprite<AsScene1201TntManFlame>(_asTntMan); + tempSprite->setClipRect(x1, 0, x2, 480); + } + + uint32 tntIndex = 1; + while (tntIndex < 18) { + uint32 elemIndex = getSubVar(VA_TNT_POSITIONS, tntIndex); + int16 clipY2; + if (kScene1201PointArray[elemIndex].y < 175) + clipY2 = topY1; + else if (kScene1201PointArray[elemIndex].y < 230) + clipY2 = topY2; + else + clipY2 = topY3; + insertSprite<SsScene1201Tnt>(tntIndex, getSubVar(VA_TNT_POSITIONS, tntIndex), clipY2); + elemIndex = getSubVar(VA_TNT_POSITIONS, tntIndex + 1); + if (kScene1201PointArray[elemIndex].y < 175) + clipY2 = topY1; + else if (kScene1201PointArray[elemIndex].y < 230) + clipY2 = topY2; + else + clipY2 = topY3; + insertSprite<SsScene1201Tnt>(tntIndex + 1, getSubVar(VA_TNT_POSITIONS, tntIndex + 1), clipY2); + tntIndex += 3; + } + + if (getGlobalVar(V_CREATURE_ANGRY) && !getGlobalVar(V_CREATURE_EXPLODED)) { + setRectList(0x004AEE58); + } else { + setRectList(0x004AEDC8); + } + + } else { + + insertStaticSprite(0x8E8A1981, 900); + + uint32 tntIndex = 0; + while (tntIndex < 18) { + uint32 elemIndex = getSubVar(VA_TNT_POSITIONS, tntIndex); + int16 clipY2; + if (kScene1201PointArray[elemIndex].x < 300) { + clipY2 = 480; + } else { + if (kScene1201PointArray[elemIndex].y < 175) + clipY2 = topY1; + else if (kScene1201PointArray[elemIndex].y < 230) + clipY2 = topY2; + else + clipY2 = topY3; + } + insertSprite<SsScene1201Tnt>(tntIndex, getSubVar(VA_TNT_POSITIONS, tntIndex), clipY2); + tntIndex++; + } + + if (getGlobalVar(V_CREATURE_ANGRY) && !getGlobalVar(V_CREATURE_EXPLODED)) + setRectList(0x004AEE18); + else + setRectList(0x004AED88); + + } + + tempSprite = insertStaticSprite(0x63D400BC, 900); + + _asLeftDoor = insertSprite<AsScene1201LeftDoor>(_klaymen); + _asLeftDoor->setClipRect(x1, tempSprite->getDrawRect().y, tempSprite->getDrawRect().x2(), 480); + + if (getGlobalVar(V_CREATURE_ANGRY) && getGlobalVar(V_MATCH_STATUS) == 0) + setGlobalVar(V_MATCH_STATUS, 1); + + _asMatch = NULL; + + if (getGlobalVar(V_MATCH_STATUS) < 3) { + _asMatch = insertSprite<AsScene1201Match>(this); + addCollisionSprite(_asMatch); + } + + if (getGlobalVar(V_CREATURE_ANGRY) && getGlobalVar(V_CREATURE_EXPLODED) == 0) { + _asCreature = insertSprite<AsScene1201Creature>(this, _klaymen); + _asCreature->setClipRect(x1, 0, x2, 480); + } + +} + +Scene1201::~Scene1201() { + if (_creatureExploded) + setGlobalVar(V_CREATURE_EXPLODED, 1); + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX() ? 1 : 0); +} + +void Scene1201::update() { + Scene::update(); + if (_asMatch && getGlobalVar(V_MATCH_STATUS) == 3) + deleteSprite(&_asMatch); +} + +uint32 Scene1201::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x07053000) { + _creatureExploded = true; + sendMessage(_asCreature, 0x2004, 0); + } else if (param.asInteger() == 0x140E5744) + sendMessage(_asCreature, 0x2005, 0); + else if (param.asInteger() == 0x40253C40) { + _canAcceptInput = false; + sendMessage(_asCreature, 0x2006, 0); + } else if (param.asInteger() == 0x090EB048) { + if (_klaymen->getX() < 572) + setMessageList2(0x004AEC90); + else + setMessageList2(0x004AEC20); + } + break; + case 0x2001: + if (getGlobalVar(V_MATCH_STATUS) == 0) + setMessageList2(0x004AECB0); + else { + sendEntityMessage(_klaymen, 0x1014, _asMatch); + setMessageList2(0x004AECC0); + } + break; + case 0x2002: + if (getGlobalVar(V_TNT_DUMMY_FUSE_LIT)) { + // Move the TNT dummy if the fuse is burning + sendEntityMessage(_klaymen, 0x1014, _asTntMan); + setMessageList2(0x004AECF0, false); + } else if (getGlobalVar(V_MATCH_STATUS) == 3) { + // Light the TNT dummy if we have the match + sendEntityMessage(_klaymen, 0x1014, _asTntMan); + if (_klaymen->getX() > _asTntMan->getX()) + setMessageList(0x004AECD0); + else + setMessageList(0x004AECE0); + } + break; + case 0x4814: + cancelMessageList(); + break; + case 0x4826: + if (sender == _asTape) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList(0x004AED38); + } + break; + case 0x4829: + sendMessage(_asRightDoor, 0x4829, 0); + break; + case 0x8000: + sendMessage(_asKlaymenHead, 0x2006, 0); + break; + } + return messageResult; +} + +// Scene1202 + +static const uint32 kScene1202Table[] = { + 1, 2, 0, 4, 5, 3, 7, 8, 6, 10, 11, 9, 13, 14, 12, 16, 17, 15 +}; + +static const NPoint kScene1202Points[] = { + {203, 140}, {316, 212}, {277, 264}, + {176, 196}, {275, 159}, {366, 212}, + {230, 195}, {412, 212}, {368, 263}, + {204, 192}, {365, 164}, {316, 262}, + {191, 255}, {280, 213}, {406, 266}, + {214, 254}, {316, 158}, {402, 161} +}; + +static const uint32 kScene1202FileHashes[] = { + 0x1AC00B8, 0x1AC14B8, 0x1AC14B8, + 0x1AC30B8, 0x1AC14B8, 0x1AC14B8, + 0x1AC00B8, 0x1AC14B8, 0x1AC14B8, + 0x1AC90B8, 0x1AC18B8, 0x1AC18B8, + 0x1AC30B8, 0x1AC14B8, 0x1AC14B8, + 0x1AC50B8, 0x1AC14B8, 0x1AC14B8 +}; + +AsScene1202TntItem::AsScene1202TntItem(NeverhoodEngine *vm, Scene *parentScene, int itemIndex) + : AnimatedSprite(vm, 900), _parentScene(parentScene), _itemIndex(itemIndex) { + + int positionIndex; + + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1202TntItem::hmShowIdle); + positionIndex = getSubVar(VA_TNT_POSITIONS, _itemIndex); + createSurface(900, 37, 67); + _x = kScene1202Points[positionIndex].x; + _y = kScene1202Points[positionIndex].y; + stShowIdle(); +} + +uint32 AsScene1202TntItem::hmShowIdle(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + sendMessage(_parentScene, 0x2000, _itemIndex); + messageResult = 1; + break; + case 0x2001: + _newPosition = (int)param.asInteger(); + stChangePositionFadeOut(); + break; + } + return messageResult; +} + +uint32 AsScene1202TntItem::hmChangePosition(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene1202TntItem::stShowIdle() { + startAnimation(kScene1202FileHashes[_itemIndex], 0, -1); + SetMessageHandler(&AsScene1202TntItem::hmShowIdle); + _newStickFrameIndex = 0; +} + +void AsScene1202TntItem::stChangePositionFadeOut() { + startAnimation(kScene1202FileHashes[_itemIndex], 0, -1); + SetMessageHandler(&AsScene1202TntItem::hmChangePosition); + NextState(&AsScene1202TntItem::stChangePositionFadeIn); +} + +void AsScene1202TntItem::stChangePositionFadeIn() { + _x = kScene1202Points[_newPosition].x; + _y = kScene1202Points[_newPosition].y; + startAnimation(kScene1202FileHashes[_itemIndex], 6, -1); + _playBackwards = true; + SetMessageHandler(&AsScene1202TntItem::hmChangePosition); + NextState(&AsScene1202TntItem::stChangePositionDone); +} + +void AsScene1202TntItem::stChangePositionDone() { + sendMessage(_parentScene, 0x2002, _itemIndex); + stShowIdle(); +} + +Scene1202::Scene1202(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule), _paletteResource(vm), + _soundToggle(true), _isPuzzleSolved(false), _counter(0), _clickedIndex(-1) { + + SetMessageHandler(&Scene1202::handleMessage); + SetUpdateHandler(&Scene1202::update); + + setBackground(0x60210ED5); + setPalette(0x60210ED5); + addEntity(_palette); + + _paletteResource.load(0x60250EB5); + _paletteResource.copyPalette(_paletteData); + + insertPuzzleMouse(0x10ED160A, 20, 620); + + for (int tntIndex = 0; tntIndex < 18; tntIndex++) { + _asTntItems[tntIndex] = insertSprite<AsScene1202TntItem>(this, tntIndex); + addCollisionSprite(_asTntItems[tntIndex]); + } + + insertStaticSprite(0x8E8419C1, 1100); + + if (getGlobalVar(V_TNT_DUMMY_BUILT)) + SetMessageHandler(&Scene1202::hmSolved); + + playSound(0, 0x40106542); + loadSound(1, 0x40005446); + loadSound(2, 0x40005446); // Same sound as slot 1 + loadSound(3, 0x68E25540); + +} + +Scene1202::~Scene1202() { + if (isSolved()) + setGlobalVar(V_TNT_DUMMY_BUILT, 1); +} + +void Scene1202::update() { + Scene::update(); + if (_isPuzzleSolved) { + if (!isSoundPlaying(3)) + leaveScene(0); + } else if (_counter == 0 && isSolved()) { + _clickedIndex = 0; + SetMessageHandler(&Scene1202::hmSolved); + setGlobalVar(V_TNT_DUMMY_BUILT, 1); + _palette->copyToBasePalette(_paletteData); + _palette->startFadeToPalette(24); + playSound(3); + _isPuzzleSolved = true; + } else if (_clickedIndex >= 0 && _counter == 0) { + // Swap TNT positions + int destIndex = kScene1202Table[_clickedIndex]; + sendMessage(_asTntItems[_clickedIndex], 0x2001, getSubVar(VA_TNT_POSITIONS, destIndex)); + sendMessage(_asTntItems[destIndex], 0x2001, getSubVar(VA_TNT_POSITIONS, _clickedIndex)); + int temp = getSubVar(VA_TNT_POSITIONS, destIndex); + setSubVar(VA_TNT_POSITIONS, destIndex, getSubVar(VA_TNT_POSITIONS, _clickedIndex)); + setSubVar(VA_TNT_POSITIONS, _clickedIndex, temp); + _counter = 2; + _clickedIndex = -1; + playSound(_soundToggle ? 1 : 2); + _soundToggle = !_soundToggle; + } +} + +uint32 Scene1202::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = 0; + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if ((param.asPoint().x <= 20 || param.asPoint().x >= 620) && !_isPuzzleSolved) + leaveScene(0); + break; + case 0x2000: + _clickedIndex = (int)param.asInteger(); + break; + case 0x2002: + _counter--; + break; + } + return messageResult; +} + +uint32 Scene1202::hmSolved(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) + leaveScene(0); + break; + } + return 0; +} + +bool Scene1202::isSolved() { + return + getSubVar(VA_TNT_POSITIONS, 0) == 0 && getSubVar(VA_TNT_POSITIONS, 3) == 3 && + getSubVar(VA_TNT_POSITIONS, 6) == 6 && getSubVar(VA_TNT_POSITIONS, 9) == 9 && + getSubVar(VA_TNT_POSITIONS, 12) == 12 && getSubVar(VA_TNT_POSITIONS, 15) == 15; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module1200.h b/engines/neverhood/modules/module1200.h new file mode 100644 index 0000000000..c97dc98986 --- /dev/null +++ b/engines/neverhood/modules/module1200.h @@ -0,0 +1,216 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 NEVERHOOD_MODULES_MODULE1200_H +#define NEVERHOOD_MODULES_MODULE1200_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +// Module1200 + +class Module1200 : public Module { +public: + Module1200(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module1200(); +protected: + int _sceneNum; + void createScene(int sceneNum, int which); + void updateScene(); +}; + +// Scene1201 + +class AsScene1201Tape : public AnimatedSprite { +public: + AsScene1201Tape(NeverhoodEngine *vm, Scene *parentScene, uint32 nameHash, int surfacePriority, int16 x, int16 y, uint32 fileHash); +protected: + Scene *_parentScene; + uint32 _nameHash; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1201TntManRope : public AnimatedSprite { +public: + AsScene1201TntManRope(NeverhoodEngine *vm, bool isDummyHanging); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1201RightDoor : public AnimatedSprite { +public: + AsScene1201RightDoor(NeverhoodEngine *vm, Sprite *klaymen, bool isOpen); +protected: + Sprite *_klaymen; + int _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stOpenDoor(); + void stCloseDoor(); + void stCloseDoorDone(); +}; + +class AsScene1201KlaymenHead : public AnimatedSprite { +public: + AsScene1201KlaymenHead(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1201TntMan : public AnimatedSprite { +public: + AsScene1201TntMan(NeverhoodEngine *vm, Scene *parentScene, Sprite *asTntManRope, bool isDown); + virtual ~AsScene1201TntMan(); +protected: + Scene *_parentScene; + Sprite *_asTntManRope; + Sprite *_sprite; + bool _isMoving; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmComingDown(int messageNum, const MessageParam ¶m, Entity *sender); + void suMoving(); + void stStanding(); + void stComingDown(); + void stMoving(); +}; + +class AsScene1201TntManFlame : public AnimatedSprite { +public: + AsScene1201TntManFlame(NeverhoodEngine *vm, Sprite *asTntMan); + ~AsScene1201TntManFlame(); +protected: + Sprite *_asTntMan; + void update(); + void suUpdate(); +}; + +class AsScene1201Match : public AnimatedSprite { +public: + AsScene1201Match(NeverhoodEngine *vm, Scene *parentScene); +protected: + Scene *_parentScene; + int _countdown; + int _status; + void update(); + uint32 hmOnDoorFrameAboutToMove(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmOnDoorFrameMoving(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmIdle(int messageNum, const MessageParam ¶m, Entity *sender); + void stOnDoorFrameMoving(); + void stFallingFromDoorFrame(); + void stOnDoorFrameAboutToMove(); + void stIdleOnDoorFrame(); + void stIdleOnFloor(); +}; + +class AsScene1201Creature : public AnimatedSprite { +public: + AsScene1201Creature(NeverhoodEngine *vm, Scene *parentScene, Sprite *klaymen); +protected: + Scene *_parentScene; + Sprite *_klaymen; + int _countdown; + bool _klaymenTooClose; + void update(); + uint32 hmWaiting(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmPincerSnap(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmPincerSnapKlaymen(int messageNum, const MessageParam ¶m, Entity *sender); + void stWaiting(); + void stPincerSnap(); + void stStartReachForTntDummy(); + void stReachForTntDummy(); + void stPincerSnapKlaymen(); +}; + +class AsScene1201LeftDoor : public AnimatedSprite { +public: + AsScene1201LeftDoor(NeverhoodEngine *vm, Sprite *klaymen); +protected: + Sprite *_klaymen; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stCloseDoor(); +}; + +class SsScene1201Tnt : public StaticSprite { +public: + SsScene1201Tnt(NeverhoodEngine *vm, uint32 elemIndex, uint32 pointIndex, int16 clipY2); +protected: + uint32 _elemIndex; +}; + +class Scene1201 : public Scene { +public: + Scene1201(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Scene1201(); +protected: + Sprite *_asMatch; + AsScene1201TntMan *_asTntMan; + Sprite *_asCreature; + Sprite *_asTntManRope; + Sprite *_asLeftDoor; + Sprite *_asRightDoor; + Sprite *_asTape; + Sprite *_asKlaymenHead; + bool _creatureExploded; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +// Scene1202 + +class AsScene1202TntItem : public AnimatedSprite { +public: + AsScene1202TntItem(NeverhoodEngine *vm, Scene *parentScene, int index); +protected: + Scene *_parentScene; + int _itemIndex, _newPosition; + uint32 hmShowIdle(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmChangePosition(int messageNum, const MessageParam ¶m, Entity *sender); + void stShowIdle(); + void stChangePositionFadeOut(); + void stChangePositionFadeIn(); + void stChangePositionDone(); +}; + +class Scene1202 : public Scene { +public: + Scene1202(NeverhoodEngine *vm, Module *parentModule); + virtual ~Scene1202(); +protected: + PaletteResource _paletteResource; + Sprite *_asTntItems[18]; + int _counter; + int _clickedIndex; + byte _paletteData[1024]; + bool _isPuzzleSolved; + bool _soundToggle; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmSolved(int messageNum, const MessageParam ¶m, Entity *sender); + bool isSolved(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE1200_H */ diff --git a/engines/neverhood/modules/module1300.cpp b/engines/neverhood/modules/module1300.cpp new file mode 100644 index 0000000000..8dbfcf616c --- /dev/null +++ b/engines/neverhood/modules/module1300.cpp @@ -0,0 +1,1835 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module1300.h" +#include "neverhood/modules/module1000.h" +#include "neverhood/modules/module1200.h" +#include "neverhood/modules/module1400.h" +#include "neverhood/modules/module2200.h" +#include "neverhood/gamemodule.h" +#include "neverhood/diskplayerscene.h" +#include "neverhood/menumodule.h" +#include "neverhood/navigationscene.h" +#include "neverhood/smackerscene.h" + +namespace Neverhood { + +static const uint32 kModule1300SoundList[] = { + 0x16805648, + 0x16805C48, + 0xB4964448, + 0x96A05481, + 0xD0E14441, + 0x90815450, + 0 +}; + +Module1300::Module1300(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + _vm->_soundMan->addMusic(0x61C090, 0x00203197); + _vm->_soundMan->addSoundList(0x61C090, kModule1300SoundList); + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 50, 600, 20, 150); + _vm->_soundMan->playTwoSounds(0x61C090, 0x48498E46, 0x50399F64, 0); + _vm->_soundMan->setSoundVolume(0x48498E46, 70); + _vm->_soundMan->setSoundVolume(0x50399F64, 70); + + if (which < 0) { + if (_vm->gameState().sceneNum >= 1 && _vm->gameState().sceneNum <= 17) + createScene(_vm->gameState().sceneNum, -1); + else + createScene(11, 0); + } else { + switch (which) { + case 0: + createScene(11, 0); + break; + case 1: + createScene(13, 0); + break; + case 2: + createScene(14, 0); + break; + case 3: + createScene(15, 0); + break; + case 4: + createScene(7, 0); + break; + case 5: + createScene(5, 1); + break; + case 6: + createScene(5, 5); + break; + case 7: + createScene(3, 0); + break; + case 8: + createScene(1, 0); + break; + case 9: + createScene(2, 0); + break; + case 10: + createScene(6, 0); + break; + case 11: + createScene(4, 0); + break; + default: + createScene(12, 0); + break; + } + } + +} + +Module1300::~Module1300() { + _vm->_soundMan->deleteGroup(0x61C090); +} + +void Module1300::createScene(int sceneNum, int which) { + debug("Module1300::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 1: + _vm->gameState().sceneNum = 1; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->startMusic(0x00203197, 0, 2); + _childObject = new Scene1302(_vm, this, which); + break; + case 2: + _vm->gameState().sceneNum = 2; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->stopMusic(0x00203197, 0, 2); + _childObject = new Scene1303(_vm, this); + break; + case 3: + _vm->gameState().sceneNum = 3; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->stopMusic(0x00203197, 0, 2); + _childObject = new Scene1304(_vm, this, which); + break; + case 4: + _vm->gameState().sceneNum = 4; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->startMusic(0x00203197, 0, 2); + _childObject = new Scene1305(_vm, this, which); + break; + case 5: + _vm->gameState().sceneNum = 5; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->startMusic(0x00203197, 0, 2); + _childObject = new Scene1306(_vm, this, which); + break; + case 6: + _vm->gameState().sceneNum = 6; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->startMusic(0x00203197, 0, 2); + _childObject = new Scene1307(_vm, this); + break; + case 7: + _vm->gameState().sceneNum = 7; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->startMusic(0x00203197, 0, 2); + _childObject = new Scene1308(_vm, this, which); + break; + case 8: + _vm->gameState().sceneNum = 8; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->stopMusic(0x00203197, 0, 2); + _childObject = new DiskplayerScene(_vm, this, 1); + break; + case 9: + _vm->gameState().sceneNum = 9; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->stopMusic(0x00203197, 0, 2); + createSmackerScene(0x20082818, true, true, false); + break; + case 10: + _vm->gameState().sceneNum = 10; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->stopMusic(0x00203197, 0, 2); + createSmackerScene(0x20082828, true, true, false); + break; + case 11: + _vm->gameState().sceneNum = 11; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, true, 0, 0, 0, 0); + _vm->_soundMan->stopMusic(0x00203197, 0, 2); + createNavigationScene(0x004B27A8, which); + break; + case 12: + _vm->gameState().sceneNum = 12; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, true, 0, 0, 0, 0); + _vm->_soundMan->stopMusic(0x00203197, 0, 2); + createNavigationScene(0x004B2718, which); + break; + case 13: + _vm->gameState().sceneNum = 13; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, true, 0, 0, 0, 0); + _vm->_soundMan->stopMusic(0x00203197, 0, 2); + createNavigationScene(0x004B27D8, which); + break; + case 14: + _vm->gameState().sceneNum = 14; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, true, 0, 0, 0, 0); + _vm->_soundMan->stopMusic(0x00203197, 0, 2); + createNavigationScene(0x004B2808, which); + break; + case 15: + _vm->gameState().sceneNum = 15; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, true, 0, 0, 0, 0); + _vm->_soundMan->stopMusic(0x00203197, 0, 2); + createNavigationScene(0x004B2838, which); + break; + case 16: + _vm->gameState().sceneNum = 16; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->stopMusic(0x00203197, 0, 2); + _childObject = new Scene1317(_vm, this); + break; + case 17: + _vm->gameState().sceneNum = 17; + _vm->_soundMan->setSoundListParams(kModule1300SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->stopMusic(0x00203197, 0, 2); + _childObject = new CreditsScene(_vm, this, false); + break; + } + SetUpdateHandler(&Module1300::updateScene); + _childObject->handleUpdate(); +} + +void Module1300::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 1: + if (_moduleResult == 1) + createScene(4, 0); + else + createScene(7, 1); + break; + case 2: + createScene(5, 3); + break; + case 3: + createScene(15, 0); + break; + case 4: + createScene(16, -1); + break; + case 5: + if (_moduleResult == 2) + createScene(8, 0); + else if (_moduleResult == 3) + createScene(2, 0); + else if (_moduleResult == 0) + leaveModule(0); + else if (_moduleResult == 1) + createScene(10, -1); + break; + case 6: + createScene(7, 2); + break; + case 7: + if (_moduleResult == 0) + createScene(13, 0); + else if (_moduleResult == 1) + createScene(1, 0); + else if (_moduleResult == 2) + createScene(6, 0); + break; + case 8: + createScene(5, 2); + break; + case 9: + createScene(5, 0); + break; + case 10: + createScene(14, 0); + break; + case 11: + if (_moduleResult == 0) + createScene(12, 0); + else if (_moduleResult == 1) + createScene(11, 1); + break; + case 12: + if (_moduleResult == 0) + createScene(14, 1); + else if (_moduleResult == 1) + createScene(15, 1); + else if (_moduleResult == 3) + createScene(11, 1); + else if (_moduleResult == 5) + createScene(13, 1); + break; + case 13: + if (_moduleResult == 0) + createScene(12, 2); + else if (_moduleResult == 1) + createScene(7, 0); + break; + case 14: + if (_moduleResult == 0) + createScene(12, 3); + else if (_moduleResult == 1) + createScene(9, -1); + break; + case 15: + if (_moduleResult == 0) + createScene(12, 4); + else if (_moduleResult == 1) + createScene(3, 0); + break; + case 16: + createScene(17, -1); + break; + case 17: + leaveModule(1); + break; + } + } +} + +AsScene1302Bridge::AsScene1302Bridge(NeverhoodEngine *vm, Scene *parentScene) + : AnimatedSprite(vm, 1100), _parentScene(parentScene) { + + _x = 320; + _y = 240; + createSurface1(0x88148150, 500); + if (!getGlobalVar(V_FLYTRAP_RING_BRIDGE)) { + startAnimation(0x88148150, 0, -1); + _newStickFrameIndex = 0; + } else { + startAnimation(0x88148150, -1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + } + loadSound(0, 0x68895082); + loadSound(1, 0x689BD0C1); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1302Bridge::handleMessage); +} + +uint32 AsScene1302Bridge::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + case 0x4808: + stLowerBridge(); + break; + case 0x4809: + stRaiseBridge(); + break; + } + return messageResult; +} + +void AsScene1302Bridge::stLowerBridge() { + startAnimation(0x88148150, 0, -1); + playSound(1); + NextState(&AsScene1302Bridge::cbLowerBridgeEvent); +} + +void AsScene1302Bridge::stRaiseBridge() { + startAnimation(0x88148150, 7, -1); + _playBackwards = true; + _newStickFrameIndex = 0; + playSound(0); +} + +void AsScene1302Bridge::cbLowerBridgeEvent() { + sendMessage(_parentScene, 0x2032, 0); + startAnimation(0x88148150, -1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; +} + +SsScene1302Fence::SsScene1302Fence(NeverhoodEngine *vm) + : StaticSprite(vm, 0x11122122, 200) { + + _firstY = _y; + if (getGlobalVar(V_FLYTRAP_RING_FENCE)) + _y += 152; + loadSound(0, 0x7A00400C); + loadSound(1, 0x78184098); + SetUpdateHandler(&SsScene1302Fence::update); + SetMessageHandler(&SsScene1302Fence::handleMessage); + SetSpriteUpdate(NULL); +} + +void SsScene1302Fence::update() { + handleSpriteUpdate(); + updatePosition(); +} + +uint32 SsScene1302Fence::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x4808: + playSound(0); + SetMessageHandler(NULL); + SetSpriteUpdate(&SsScene1302Fence::suMoveDown); + break; + case 0x4809: + playSound(1); + SetMessageHandler(NULL); + SetSpriteUpdate(&SsScene1302Fence::suMoveUp); + break; + } + return messageResult; +} + +void SsScene1302Fence::suMoveDown() { + if (_y < _firstY + 152) + _y += 8; + else { + SetMessageHandler(&SsScene1302Fence::handleMessage); + SetSpriteUpdate(NULL); + } +} + +void SsScene1302Fence::suMoveUp() { + if (_y > _firstY) + _y -= 8; + else { + SetMessageHandler(&SsScene1302Fence::handleMessage); + SetSpriteUpdate(NULL); + } +} + +Scene1302::Scene1302(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + SetMessageHandler(&Scene1302::handleMessage); + + setHitRects(0x004B0858); + setRectList(0x004B0A38); + setBackground(0x420643C4); + setPalette(0x420643C4); + insertScreenMouse(0x643C0428); + + _class595 = insertStaticSprite(0xB0420130, 1015); + _sprite1 = insertStaticSprite(0x942FC224, 300); + _sprite2 = insertStaticSprite(0x70430830, 1200); + _sprite2->setVisible(false); + _sprite3 = insertStaticSprite(0x16E01E20, 1100); + _asRing1 = insertSprite<AsScene1002Ring>(this, false, 218, 122, _class595->getDrawRect().y, false); + _asRing2 = insertSprite<AsScene1002Ring>(this, true, 218 + 32, 132, _class595->getDrawRect().y, getGlobalVar(V_FLYTRAP_RING_BRIDGE)); + _asRing3 = insertSprite<AsScene1002Ring>(this, false, 218 + 32 + 32, 122, _class595->getDrawRect().y, false); + _asRing4 = insertSprite<AsScene1002Ring>(this, true, 218 + 32 + 32 + 32, 132, _class595->getDrawRect().y, getGlobalVar(V_FLYTRAP_RING_FENCE)); + _asRing5 = insertSprite<AsScene1002Ring>(this, false, 218 + 32 + 32 + 32 + 32, 115, _class595->getDrawRect().y, false); + _asBridge = insertSprite<AsScene1302Bridge>(this); + _ssFence = insertSprite<SsScene1302Fence>(); + _ssFence->setClipRect(0, 0, 640, _sprite1->getDrawRect().y2()); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene1002>(380, 364); + setMessageList(0x004B0868); + } else { + // Klaymen entering from back + insertKlaymen<KmScene1002>(293, 330); + setMessageList(0x004B0870); + } + + _klaymen->setClipRect(0, 0, _sprite3->getDrawRect().x2(), 480); + + _asVenusFlyTrap = insertSprite<AsScene1002VenusFlyTrap>(this, _klaymen, true); + addCollisionSprite(_asVenusFlyTrap); + + sendEntityMessage(_klaymen, 0x2007, _asVenusFlyTrap); + +} + +uint32 Scene1302::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = 0; + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x4A845A00) + sendEntityMessage(_klaymen, 0x1014, _asRing1); + else if (param.asInteger() == 0x43807801) { + if (!getGlobalVar(V_FLYTRAP_RING_BRIDGE)) { + sendEntityMessage(_klaymen, 0x1014, _asRing2); + if (_asVenusFlyTrap->getX() - 10 < 218 + 32 && _asVenusFlyTrap->getX() + 10 > 218 + 32) + setMessageList(0x004B0940); + else + setMessageList(0x004B0938); + } else + setMessageList(0x004B0950); + messageResult = 1; + } else if (param.asInteger() == 0x46C26A01) + sendEntityMessage(_klaymen, 0x1014, _asRing3); + else if (param.asInteger() == 0x468C7B11) { + if (!getGlobalVar(V_FLYTRAP_RING_FENCE)) { + sendEntityMessage(_klaymen, 0x1014, _asRing4); + if (_asVenusFlyTrap->getX() - 10 < 218 + 32 + 32 + 32 && _asVenusFlyTrap->getX() + 10 > 218 + 32 + 32 + 32) + setMessageList(0x004B0940); + else + setMessageList(0x004B0938); + } else + setMessageList(0x004B0950); + messageResult = 1; + } else if (param.asInteger() == 0x42845B19) + sendEntityMessage(_klaymen, 0x1014, _asRing5); + else if (param.asInteger() == 0x430A6060) { + if (getGlobalVar(V_FLYTRAP_RING_BRIDGE)) + setMessageList2(0x004B0910); + else + cancelMessageList(); + } else if (param.asInteger() == 0x012E2070) { + if (getGlobalVar(V_FLYTRAP_RING_BRIDGE)) + setMessageList2(0x004B0968); + else + cancelMessageList(); + } else if (param.asInteger() == 0x11C40840) { + if (_asVenusFlyTrap->getX() >= 260 && _asVenusFlyTrap->getX() <= 342) + setMessageList(0x004B0878); + else + setMessageList(0x004B0978); + } + break; + case 0x2000: + if (_klaymen->getY() > 360) { + sendEntityMessage(_klaymen, 0x1014, _asVenusFlyTrap); + setMessageList2(0x004B08F0); + } else + setMessageList2(0x004B0920); + break; + case 0x2002: + if (_klaymen->getX() > 545) + leaveScene(1); + break; + case 0x2032: + _sprite2->setVisible(true); + break; + case 0x4806: + sendMessage(_parentModule, 0x1024, 2); + if (sender == _asRing1) + playSound(0, 0x665198C0); + else if (sender == _asRing2) { + sendMessage(_asBridge, 0x4808, 0); + setGlobalVar(V_FLYTRAP_RING_BRIDGE, 1); + } else if (sender == _asRing3) + playSound(0, 0xE2D389C0); + else if (sender == _asRing4) { + sendMessage(_ssFence, 0x4808, 0); + setGlobalVar(V_FLYTRAP_RING_FENCE, 1); + } else if (sender == _asRing5) + playSound(0, 0x40428A09); + break; + case 0x4807: + if (sender == _asRing2) { + sendMessage(_asBridge, 0x4809, 0); + setGlobalVar(V_FLYTRAP_RING_BRIDGE, 0); + _sprite2->setVisible(false); + } else if (sender == _asRing4) { + sendMessage(_ssFence, 0x4809, 0); + setGlobalVar(V_FLYTRAP_RING_FENCE, 0); + } else if (sender == _asVenusFlyTrap) { + if (getGlobalVar(V_FLYTRAP_RING_BRIDGE)) + sendMessage(_asRing2, 0x4807, 0); + else + sendMessage(_asRing4, 0x4807, 0); + } + break; + case 0x480F: + if (sender == _asRing2) { + playSound(0, 0x60755842); + sendMessage(_asBridge, 0x4808, 0); + setGlobalVar(V_FLYTRAP_RING_BRIDGE, 1); + } else if (sender == _asRing4) { + playSound(0, 0x60755842); + sendMessage(_ssFence, 0x4808, 0); + setGlobalVar(V_FLYTRAP_RING_FENCE, 1); + } + break; + case 0x482A: + sendMessage(_asVenusFlyTrap, 0x482B, 0); + break; + case 0x482B: + sendMessage(_asVenusFlyTrap, 0x482A, 0); + break; + case 0x8000: + setSpriteSurfacePriority(_class595, 995); + break; + case 0x8001: + setSpriteSurfacePriority(_class595, 1015); + break; + } + return messageResult; +} + +AsScene1303Balloon::AsScene1303Balloon(NeverhoodEngine *vm, Scene *parentScene) + : AnimatedSprite(vm, 1100), _parentScene(parentScene) { + + createSurface(200, 128, 315); + _x = 289; + _y = 390; + startAnimation(0x800278D2, 0, -1); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1303Balloon::handleMessage); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); +} + +uint32 AsScene1303Balloon::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + sendMessage(_parentScene, 0x4826, 0); + messageResult = 1; + break; + case 0x2000: + stPopBalloon(); + break; + } + return messageResult; +} + +uint32 AsScene1303Balloon::hmBalloonPopped(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x020B0003) + playSound(0, 0x742B0055); + break; + case 0x3002: + playSound(0, 0x470007EE); + stopAnimation(); + setVisible(false); + SetMessageHandler(NULL); + break; + } + return messageResult; +} + +void AsScene1303Balloon::stPopBalloon() { + startAnimation(0xAC004CD0, 0, -1); + SetMessageHandler(&AsScene1303Balloon::hmBalloonPopped); +} + +Scene1303::Scene1303(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule) { + + SetMessageHandler(&Scene1303::handleMessage); + + setRectList(0x004AF9E8); + setBackground(0x01581A9C); + setPalette(0x01581A9C); + insertScreenMouse(0x81A9801D); + + if (!getGlobalVar(V_BALLOON_POPPED)) { + _asBalloon = insertSprite<AsScene1303Balloon>(this); + addCollisionSprite(_asBalloon); + } + + _sprite1 = insertStaticSprite(0xA014216B, 1100); + + insertKlaymen<KmScene1303>(207, 332); + setMessageList(0x004AF9A0); + + _klaymen->setClipRect(_sprite1->getDrawRect().x, 0, 640, 480); + +} + +uint32 Scene1303::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + setGlobalVar(V_BALLOON_POPPED, 1); + sendMessage(_asBalloon, 0x2000, 0); + break; + case 0x4826: + if (sender == _asBalloon && getGlobalVar(V_HAS_NEEDLE)) + setMessageList(0x004AF9B8); + break; + } + return 0; +} + +AsScene1304Needle::AsScene1304Needle(NeverhoodEngine *vm, Scene *parentScene, int surfacePriority, int16 x, int16 y) + : AnimatedSprite(vm, 0x548E9411, surfacePriority, x, y), _parentScene(parentScene) { + + // NOTE: Skipped check if Klaymen already has the needle since that's done in the scene itself + SetMessageHandler(&AsScene1304Needle::handleMessage); +} + +uint32 AsScene1304Needle::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + sendMessage(_parentScene, 0x4826, 0); + messageResult = 1; + break; + case 0x4806: + setGlobalVar(V_HAS_NEEDLE, 1); + setVisible(false); + SetMessageHandler(NULL); + break; + } + return messageResult; +} + +Scene1304::Scene1304(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _asNeedle(NULL) { + + SetMessageHandler(&Scene1304::handleMessage); + + setRectList(0x004B91A8); + setBackground(0x062C0214); + setPalette(0x062C0214); + insertScreenMouse(0xC021006A); + + if (getGlobalVar(V_BALLOON_POPPED)) { + _asKey = insertSprite<AsCommonKey>(this, 0, 1100, 278, 347); + addCollisionSprite(_asKey); + } else { + _asKey = insertSprite<AnimatedSprite>(0x80106018, 100, 279, 48); + } + + if (!getGlobalVar(V_HAS_NEEDLE)) { + _asNeedle = insertSprite<AsScene1304Needle>(this, 1100, 278, 347); + addCollisionSprite(_asNeedle); + } + + _sprite1 = insertStaticSprite(0x0562E621, 1100); + insertStaticSprite(0x012AE033, 1100); + insertStaticSprite(0x090AF033, 1100); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene1304>(217, 347); + setMessageList(0x004B90E8); + } else { + // Klaymen entering from the left + insertKlaymen<KmScene1304>(100, 347); + setMessageList(0x004B90F0); + } + + _klaymen->setClipRect(_sprite1->getDrawRect().x, 0, 640, 480); + +} + +uint32 Scene1304::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x415634A4) { + if (getGlobalVar(V_BALLOON_POPPED)) + cancelMessageList(); + else + setMessageList(0x004B9158); + } + break; + case 0x4826: + if (sender == _asNeedle) { + sendEntityMessage(_klaymen, 0x1014, _asNeedle); + setMessageList(0x004B9130); + } else if (sender == _asKey) { + sendEntityMessage(_klaymen, 0x1014, _asKey); + setMessageList(0x004B9140); + } + break; + } + return 0; +} + +Scene1305::Scene1305(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + SetMessageHandler(&Scene1305::handleMessage); + + setRectList(0x004B6E98); + setBackground(0x28801B64); + setPalette(0x28801B64); + insertScreenMouse(0x01B60280); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene1305>(212, 441); + setMessageList(0x004B6E40); + } else { + // Klaymen enters falling + insertKlaymen<KmScene1305>(212, 441); + setMessageList(0x004B6E48); + } + +} + +uint32 Scene1305::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + return Scene::handleMessage(messageNum, param, sender); +} + +AsScene1306Elevator::AsScene1306Elevator(NeverhoodEngine *vm, Scene *parentScene, AnimatedSprite *asElevatorDoor) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _asElevatorDoor(asElevatorDoor), _isUp(false), _isDown(true), + _countdown(0) { + + _x = 320; + _y = 240; + createSurface1(0x043B0270, 100); + startAnimation(0x043B0270, 0, -1); + _newStickFrameIndex = 0; + loadSound(0, 0x1C100E83); + loadSound(1, 0x1C08CEC5); + loadSound(2, 0x5D011E87); + SetMessageHandler(&AsScene1306Elevator::handleMessage); +} + +void AsScene1306Elevator::update() { + if (_isUp && _countdown != 0 && (--_countdown == 0)) + stGoingDown(); + AnimatedSprite::update(); + if (_currFrameIndex == 7) { + playSound(1); + _asElevatorDoor->setVisible(false); + } +} + +void AsScene1306Elevator::upGoingDown() { + AnimatedSprite::update(); + if (_currFrameIndex == 5) + _asElevatorDoor->setVisible(true); +} + +uint32 AsScene1306Elevator::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2001: + if (_isUp) + _countdown = 144; + messageResult = _isUp ? 1 : 0; + break; + case 0x3002: + gotoNextState(); + break; + case 0x4808: + if (_isDown) + stGoingUp(); + break; + } + return messageResult; +} + +void AsScene1306Elevator::stGoingUp() { + setVisible(true); + _isDown = false; + startAnimation(0x043B0270, 0, -1); + playSound(0); + SetUpdateHandler(&AsScene1306Elevator::update); + NextState(&AsScene1306Elevator::cbGoingUpEvent); +} + +void AsScene1306Elevator::cbGoingUpEvent() { + sendMessage(_parentScene, 0x4808, 0); + _isUp = true; + _countdown = 144; + stopAnimation(); + setVisible(false); + SetUpdateHandler(&AsScene1306Elevator::update); +} + +void AsScene1306Elevator::stGoingDown() { + _isUp = false; + setVisible(true); + startAnimation(0x043B0270, -1, -1); + _playBackwards = true; + playSound(1); + SetUpdateHandler(&AsScene1306Elevator::upGoingDown); + NextState(&AsScene1306Elevator::cbGoingDownEvent); +} + +void AsScene1306Elevator::cbGoingDownEvent() { + _isDown = true; + sendMessage(_parentScene, 0x4809, 0); + stopAnimation(); + SetUpdateHandler(&AsScene1306Elevator::update); +} + +Scene1306::Scene1306(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + if (getGlobalVar(V_HAS_FINAL_KEY) && getGlobalVar(V_KEY3_LOCATION) == 0) + setGlobalVar(V_KEY3_LOCATION, 4); + + SetMessageHandler(&Scene1306::handleMessage); + + setBackground(0x05303114); + setPalette(0x05303114); + insertScreenMouse(0x0311005B); + + if (getGlobalVar(V_KEY3_LOCATION) == 4) { + _asKey = insertSprite<AsCommonKey>(this, 2, 1100, 435, 445); + addCollisionSprite(_asKey); + } + + _ssButton = insertSprite<SsCommonButtonSprite>(this, 0x404A36A0, 100, 0x440C1000); + _asTape = insertSprite<AsScene1201Tape>(this, 19, 1100, 359, 445, 0x9148A011); + _asElevatorDoor = insertSprite<AnimatedSprite>(0x043B0270, 90, 320, 240); + _asElevatorDoor->startAnimation(0x043B0270, 6, -1); + _asElevatorDoor->setNewHashListIndex(6); + _asElevator = insertSprite<AsScene1306Elevator>(this, _asElevatorDoor); + _sprite1 = insertStaticSprite(0x036A1EE0, 80); + insertStaticSprite(0x00042313, 1100); + + if (which < 0) { + // Resoring game + insertKlaymen<KmScene1306>(380, 440); + setMessageList(0x004AFAD0); + sendMessage(this, 0x2000, 0); + addCollisionSprite(_asTape); + } else if (which == 1) { + // Klaymen teleporting in + insertKlaymen<KmScene1306>(136, 440); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004AFAF0); + sendMessage(this, 0x2000, 1); + addCollisionSprite(_asTape); + } else if (which == 2) { + // Klaymen returning from diskplayer + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) { + insertKlaymen<KmScene1306>(515, 440); + _klaymen->setDoDeltaX(1); + } else { + insertKlaymen<KmScene1306>(355, 440); + } + setMessageList(0x004AFBC8); + sendMessage(this, 0x2000, 0); + addCollisionSprite(_asTape); + } else if (which == 3) { + // Klaymen returning from window + insertKlaymen<KmScene1306>(534, 440); + setMessageList(0x004AFC30); + sendMessage(this, 0x2000, 0); + addCollisionSprite(_asTape); + } else if (which == 4) { + // Klaymen teleporting out + insertKlaymen<KmScene1306>(136, 440); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004AFC38); + sendMessage(this, 0x2000, 1); + addCollisionSprite(_asTape); + } else if (which == 5) { + // Klaymen returning from teleporter + insertKlaymen<KmScene1306>(136, 440); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004AFB00); + sendMessage(this, 0x2000, 1); + addCollisionSprite(_asTape); + } else { + // Klaymen coming up in elevator + insertKlaymen<KmScene1306>(286, 408); + setSurfacePriority(_asElevator->getSurface(), 1100); + setSurfacePriority(_asElevatorDoor->getSurface(), 1090); + setSurfacePriority(_sprite1->getSurface(), 1080); + sendMessage(this, 0x2000, 0); + SetMessageHandler(&Scene1306::handleMessage416EB0); + clearRectList(); + sendMessage(_asElevator, 0x4808, 0); + } + +} + +Scene1306::~Scene1306() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX() ? 1 : 0); +} + +uint32 Scene1306::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x402064D8) + sendEntityMessage(_klaymen, 0x1014, _ssButton); + else if (param.asInteger() == 0x01C66840) { + if (sendMessage(_asElevator, 0x2001, 0) != 0) + setMessageList(0x004AFBD8); + else + setMessageList(0x004AFAE0); + } else if (param.asInteger() == 0x8E646E00) { + setMessageList(0x004AFAD8); + clearRectList(); + SetMessageHandler(&Scene1306::handleMessage416EB0); + } + break; + case 0x2000: + if (param.asInteger() != 0) { + setRectList(0x004AFD28); + _klaymen->setKlaymenIdleTable3(); + } else { + setRectList(0x004AFD18); + _klaymen->setKlaymenIdleTable1(); + } + break; + case 0x480B: + if (sender == _ssButton) + sendMessage(_asElevator, 0x4808, 0); + break; + case 0x4826: + if (sender == _asKey) { + if (_klaymen->getX() >= 249) { + sendEntityMessage(_klaymen, 0x1014, _asKey); + setMessageList(0x004AFC58); + } + } else if (sender == _asTape) { + if (_klaymen->getX() >= 249) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList(0x004AFC68); + } + } + break; + case 0x482A: + setSurfacePriority(_asElevator->getSurface(), 1100); + setSurfacePriority(_asElevatorDoor->getSurface(), 1090); + setSurfacePriority(_sprite1->getSurface(), 1080); + break; + case 0x482B: + setSurfacePriority(_asElevator->getSurface(), 100); + setSurfacePriority(_asElevatorDoor->getSurface(), 90); + setSurfacePriority(_sprite1->getSurface(), 80); + sendMessage(this, 0x2000, 0); + addCollisionSprite(_asTape); + break; + } + return 0; +} + +uint32 Scene1306::handleMessage416EB0(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x4808: + setMessageList(0x004AFBD0); + SetMessageHandler(&Scene1306::handleMessage); + break; + case 0x4809: + leaveScene(1); + break; + case 0x482A: + setSurfacePriority(_asElevator->getSurface(), 1100); + setSurfacePriority(_asElevatorDoor->getSurface(), 1090); + setSurfacePriority(_sprite1->getSurface(), 1080); + break; + case 0x482B: + setSurfacePriority(_asElevator->getSurface(), 100); + setSurfacePriority(_asElevatorDoor->getSurface(), 90); + setSurfacePriority(_sprite1->getSurface(), 80); + sendMessage(this, 0x2000, 0); + addCollisionSprite(_asTape); + break; + } + return 0; +} + +static const uint32 kAsScene1307KeyResourceList1[] = { + 0x0438069C, 0x45B0023C, 0x05700217 +}; + +static const uint32 kAsScene1307KeyResourceList2[] = { + 0x04441334, 0x061433F0, 0x06019390 +}; + +static const uint32 kAsScene1307KeyResourceList3[] = { + 0x11A80030, 0x178812B1, 0x1488121C +}; + +static const uint32 *kAsScene1307KeyResourceLists[] = { + kAsScene1307KeyResourceList1, + kAsScene1307KeyResourceList2, + kAsScene1307KeyResourceList3 +}; + +static const int kAsScene1307KeySurfacePriorities[] = { + 700, 500, 300, 100 +}; + +const uint kAsScene1307KeyPointsCount = 12; + +static const NPoint kAsScene1307KeyPoints[] = { + {-2, 0}, {-5, 0}, { 5, 0}, + {12, 0}, {17, 0}, {25, 0}, + {16, -2}, {10, -6}, { 0, -7}, + {-7, -3}, {-3, 4}, { 2, 2} +}; + +const uint kAsScene1307KeyFrameIndicesCount = 20; + +static const int16 kAsScene1307KeyFrameIndices[] = { + 1, 4, 8, 11, 15, 16, 17, 17, 17, 16, + 15, 14, 12, 10, 9, 7, 5, 3, 2, 1 +}; + +const int kAsScene1307KeyDivValue = 200; +const int16 kAsScene1307KeyXDelta = 70; +const int16 kAsScene1307KeyYDelta = -12; + +AsScene1307Key::AsScene1307Key(NeverhoodEngine *vm, Scene *parentScene, uint keyIndex, NRect *clipRects) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _keyIndex(keyIndex), _clipRects(clipRects), + _isClickable(true) { + + NPoint pt; + const uint32 *fileHashes = kAsScene1307KeyResourceLists[_keyIndex]; + + _dataResource.load(0x22102142); + _pointList = _dataResource.getPointArray(0xAC849240); + pt = (*_pointList)[getSubVar(VA_CURR_KEY_SLOT_NUMBERS, _keyIndex)]; + _x = pt.x; + _y = pt.y; + createSurface(kAsScene1307KeySurfacePriorities[getSubVar(VA_CURR_KEY_SLOT_NUMBERS, _keyIndex) % 4], 190, 148); + startAnimation(fileHashes[0], 0, -1); + loadSound(0, 0xDC4A1280); + loadSound(1, 0xCC021233); + loadSound(2, 0xC4C23844); + loadSound(3, 0xC4523208); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1307Key::handleMessage); +} + +uint32 AsScene1307Key::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_isClickable) { + sendMessage(_parentScene, 0x4826, 0); + stRemoveKey(); + messageResult = 1; + } + break; + case 0x2000: + _isClickable = param.asInteger() != 0; + break; + case 0x2001: + setSubVar(VA_CURR_KEY_SLOT_NUMBERS, _keyIndex, param.asInteger()); + stMoveKey(); + break; + case 0x2003: + playSound(3); + stUnlock(); + break; + case 0x2004: + playSound(2); + stInsert(); + break; + } + return messageResult; +} + +void AsScene1307Key::suRemoveKey() { + if (_pointIndex < kAsScene1307KeyPointsCount) { + _x += kAsScene1307KeyPoints[_pointIndex].x; + _y += kAsScene1307KeyPoints[_pointIndex].y; + updateBounds(); + _pointIndex++; + } else { + SetSpriteUpdate(NULL); + } +} + +void AsScene1307Key::suInsertKey() { + if (_pointIndex < kAsScene1307KeyPointsCount) { + _x -= kAsScene1307KeyPoints[kAsScene1307KeyPointsCount - _pointIndex - 1].x; + _y -= kAsScene1307KeyPoints[kAsScene1307KeyPointsCount - _pointIndex - 1].y; + updateBounds(); + _pointIndex++; + if (_pointIndex == 7) + playSound(0); + } else { + SetSpriteUpdate(NULL); + sendMessage(_parentScene, 0x2002, 0); + } +} + +void AsScene1307Key::suMoveKey() { + if (_pointIndex < kAsScene1307KeyFrameIndicesCount) { + _frameIndex += kAsScene1307KeyFrameIndices[_pointIndex]; + _x = _prevX + (_deltaX * _frameIndex) / kAsScene1307KeyDivValue; + _y = _prevY + (_deltaY * _frameIndex) / kAsScene1307KeyDivValue; + updateBounds(); + _pointIndex++; + } else { + NPoint pt = (*_pointList)[getSubVar(VA_CURR_KEY_SLOT_NUMBERS, _keyIndex)]; + _x = pt.x + kAsScene1307KeyXDelta; + _y = pt.y + kAsScene1307KeyYDelta; + stInsertKey(); + } +} + +void AsScene1307Key::stRemoveKey() { + const uint32 *fileHashes = kAsScene1307KeyResourceLists[_keyIndex]; + _pointIndex = 0; + startAnimation(fileHashes[0], 0, -1); + playSound(1); + SetSpriteUpdate(&AsScene1307Key::suRemoveKey); +} + +void AsScene1307Key::stInsertKey() { + _pointIndex = 0; + sendMessage(_parentScene, 0x1022, kAsScene1307KeySurfacePriorities[getSubVar(VA_CURR_KEY_SLOT_NUMBERS, _keyIndex) % 4]); + setClipRect(_clipRects[getSubVar(VA_CURR_KEY_SLOT_NUMBERS, _keyIndex) % 4]); + _newStickFrameIndex = STICK_LAST_FRAME; + SetSpriteUpdate(&AsScene1307Key::suInsertKey); +} + +void AsScene1307Key::stMoveKey() { + NPoint pt = (*_pointList)[getSubVar(VA_CURR_KEY_SLOT_NUMBERS, _keyIndex)]; + int16 newX = pt.x + kAsScene1307KeyXDelta; + int16 newY = pt.y + kAsScene1307KeyYDelta; + sendMessage(_parentScene, 0x1022, 1000); + setClipRect(0, 0, 640, 480); + _prevX = _x; + _prevY = _y; + if (newX == _x && newY == _y) { + stInsertKey(); + } else { + const uint32 *fileHashes = kAsScene1307KeyResourceLists[_keyIndex]; + _pointIndex = 0; + _frameIndex = 0; + _deltaX = newX - _x; + _deltaY = newY - _y; + startAnimation(fileHashes[0], 0, -1); + SetSpriteUpdate(&AsScene1307Key::suMoveKey); + } +} + +void AsScene1307Key::stUnlock() { + const uint32 *fileHashes = kAsScene1307KeyResourceLists[_keyIndex]; + startAnimation(fileHashes[1], 0, -1); + _newStickFrameIndex = STICK_LAST_FRAME; +} + +void AsScene1307Key::stInsert() { + const uint32 *fileHashes = kAsScene1307KeyResourceLists[_keyIndex]; + startAnimation(fileHashes[2], 0, -1); + _newStickFrameIndex = STICK_LAST_FRAME; +} + +Scene1307::Scene1307(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule), _countdown(0), _asCurrKey(NULL), + _isInsertingKey(false), _doLeaveScene(false), _isPuzzleSolved(false) { + + Sprite *tempSprite; + + _vm->gameModule()->initKeySlotsPuzzle(); + + _dataResource.load(0x22102142); + _keyHolePoints = _dataResource.getPointArray(0xAC849240); + + for (uint i = 0; i < 16; i++) { + NPoint pt = (*_keyHolePoints)[i]; + _keyHoleRects[i].x1 = pt.x - 15; + _keyHoleRects[i].y1 = pt.y - 15; + _keyHoleRects[i].x2 = pt.x + 15; + _keyHoleRects[i].y2 = pt.y + 15; + } + + SetMessageHandler(&Scene1307::handleMessage); + SetUpdateHandler(&Scene1307::update); + + setBackground(0xA8006200); + setPalette(0xA8006200); + addEntity(_palette); + insertPuzzleMouse(0x06204A88, 20, 620); + + tempSprite = insertStaticSprite(0x00A3621C, 800); + _clipRects[0].set(tempSprite->getDrawRect().x, 0, 640, 480); + tempSprite = insertStaticSprite(0x00A3641C, 600); + _clipRects[1].set(tempSprite->getDrawRect().x, 0, 640, 480); + tempSprite = insertStaticSprite(0x00A3681C, 400); + _clipRects[2].set(tempSprite->getDrawRect().x, 0, 640, 480); + tempSprite = insertStaticSprite(0x00A3701C, 200); + _clipRects[3].set(tempSprite->getDrawRect().x, 0, 640, 480); + + for (uint keyIndex = 0; keyIndex < 3; keyIndex++) { + if (getSubVar(VA_IS_KEY_INSERTED, keyIndex)) { + _asKeys[keyIndex] = insertSprite<AsScene1307Key>(this, keyIndex, _clipRects); + addCollisionSprite(_asKeys[keyIndex]); + } else { + _asKeys[keyIndex] = NULL; + } + } + + loadSound(0, 0x68E25540); + +} + +void Scene1307::update() { + Scene::update(); + if (_countdown && (--_countdown == 0)) + _doLeaveScene = true; + else if (_countdown == 20) + _palette->startFadeToWhite(40); + if (_doLeaveScene && !isSoundPlaying(0)) { + leaveScene(1); + setGlobalVar(V_KEYDOOR_UNLOCKED, 1); + } +} + +uint32 Scene1307::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = 0; + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (!_isPuzzleSolved) { + if (param.asPoint().x > 20 && param.asPoint().x < 620) { + if (_asCurrKey && !_isInsertingKey) { + int16 mouseX = param.asPoint().x; + int16 mouseY = param.asPoint().y; + uint clickedKeyHoleIndex; + for (clickedKeyHoleIndex = 0; clickedKeyHoleIndex < 16; clickedKeyHoleIndex++) { + if (mouseX >= _keyHoleRects[clickedKeyHoleIndex].x1 && mouseX <= _keyHoleRects[clickedKeyHoleIndex].x2 && + mouseY >= _keyHoleRects[clickedKeyHoleIndex].y1 && mouseY <= _keyHoleRects[clickedKeyHoleIndex].y2) + break; + } + if (clickedKeyHoleIndex < 16) { + // Check if the clicked keyhole is already occupied with a key + bool occupied = false; + for (uint keyIndex = 0; keyIndex < 3 && !occupied; keyIndex++) { + if (getSubVar(VA_IS_KEY_INSERTED, keyIndex) && _asKeys[keyIndex] != _asCurrKey) { + if (getSubVar(VA_CURR_KEY_SLOT_NUMBERS, keyIndex) == clickedKeyHoleIndex) + occupied = true; + } + } + if (!occupied) { + // If the keyhole is free, insert the current key + sendMessage(_asCurrKey, 0x2001, clickedKeyHoleIndex); + _isInsertingKey = true; + _mouseClicked = false; + } + } + } + } else if (_countdown == 0 && !_asCurrKey && !_isInsertingKey) + leaveScene(0); + } + break; + case 0x2002: + // Check if all keys are in the correct keyholes + if (getSubVar(VA_IS_KEY_INSERTED, 0) && getSubVar(VA_CURR_KEY_SLOT_NUMBERS, 0) == getSubVar(VA_GOOD_KEY_SLOT_NUMBERS, 0) && + getSubVar(VA_IS_KEY_INSERTED, 1) && getSubVar(VA_CURR_KEY_SLOT_NUMBERS, 1) == getSubVar(VA_GOOD_KEY_SLOT_NUMBERS, 1) && + getSubVar(VA_IS_KEY_INSERTED, 2) && getSubVar(VA_CURR_KEY_SLOT_NUMBERS, 2) == getSubVar(VA_GOOD_KEY_SLOT_NUMBERS, 2)) { + // Play unlock animations for all keys + for (uint keyIndex = 0; keyIndex < 3; keyIndex++) { + if (_asKeys[keyIndex]) + sendMessage(_asKeys[keyIndex], 0x2003, 1); + } + playSound(0); + _isPuzzleSolved = true; + _countdown = 47; + } else { + for (uint keyIndex = 0; keyIndex < 3; keyIndex++) + if (getSubVar(VA_IS_KEY_INSERTED, keyIndex) && _asKeys[keyIndex]) + sendMessage(_asKeys[keyIndex], 0x2000, 1); + sendMessage(_asCurrKey, 0x2004, 1); + } + _asCurrKey = NULL; + _isInsertingKey = false; + break; + case 0x4826: + _asCurrKey = (Sprite*)sender; + for (uint keyIndex = 0; keyIndex < 3; keyIndex++) + if (getSubVar(VA_IS_KEY_INSERTED, keyIndex) && _asKeys[keyIndex]) + sendMessage(_asKeys[keyIndex], 0x2000, 0); + break; + } + return messageResult; +} + +static const uint32 kScene1308NumberFileHashes[] = { + 0x08006320, 0x10006320, 0x20006320, + 0x40006320, 0x80006320, 0x00006321, + 0x00006322, 0x00006324, 0x00006328, + 0x08306320, 0x10306320, 0x20306320, + 0x40306320, 0x80306320, 0x00306321, + 0x00306322 +}; + +AsScene1308JaggyDoor::AsScene1308JaggyDoor(NeverhoodEngine *vm, Scene *parentScene) + : AnimatedSprite(vm, 0xBA0AE050, 1100, 320, 240), _parentScene(parentScene) { + + setVisible(false); + stopAnimation(); + SetMessageHandler(&AsScene1308JaggyDoor::handleMessage); +} + +uint32 AsScene1308JaggyDoor::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + case 0x4808: + stOpenDoor(); + break; + case 0x4809: + stCloseDoor(); + break; + } + return messageResult; +} + +void AsScene1308JaggyDoor::stOpenDoor() { + startAnimation(0xBA0AE050, 0, -1); + setVisible(true); + playSound(0, calcHash("fxDoorOpen38")); + NextState(&AsScene1308JaggyDoor::stOpenDoorDone); +} + +void AsScene1308JaggyDoor::stOpenDoorDone() { + sendMessage(_parentScene, 0x2000, 0); + stopAnimation(); + setVisible(false); +} + +void AsScene1308JaggyDoor::stCloseDoor() { + startAnimation(0xBA0AE050, -1, -1); + _playBackwards = true; + setVisible(true); + playSound(0, calcHash("fxDoorClose38")); + NextState(&AsScene1308JaggyDoor::stCloseDoorDone); +} + +void AsScene1308JaggyDoor::stCloseDoorDone() { + sendMessage(_parentScene, 0x2001, 0); + stopAnimation(); +} + +AsScene1308KeyboardDoor::AsScene1308KeyboardDoor(NeverhoodEngine *vm, Scene *parentScene) + : AnimatedSprite(vm, 0xA08A0851, 1100, 320, 240), _parentScene(parentScene) { + + playSound(0, 0x51456049); + SetMessageHandler(&AsScene1308KeyboardDoor::handleMessage); + NextState(&AsScene1308KeyboardDoor::stFallingKeys); +} + +uint32 AsScene1308KeyboardDoor::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene1308KeyboardDoor::stFallingKeys() { + startAnimation(0x6238B191, 0, -1); + _x = 580; + _y = 383; + NextState(&AsScene1308KeyboardDoor::stFallingKeysDone); +} + +void AsScene1308KeyboardDoor::stFallingKeysDone() { + sendMessage(_parentScene, 0x2004, 0); + stopAnimation(); + setVisible(false); +} + +AsScene1308LightWallSymbols::AsScene1308LightWallSymbols(NeverhoodEngine *vm, Scene *parentScene) + : AnimatedSprite(vm, 0x80180A10, 100, 320, 240), _parentScene(parentScene) { + + setVisible(false); + stopAnimation(); + Entity::_priority = 1200; + SetMessageHandler(&AsScene1308LightWallSymbols::handleMessage); +} + +uint32 AsScene1308LightWallSymbols::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2002: + stFadeIn(); + break; + case 0x2003: + stFadeOut(); + break; + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene1308LightWallSymbols::stFadeIn() { + startAnimation(0x80180A10, 0, -1); + setVisible(true); + _newStickFrameIndex = STICK_LAST_FRAME; +} + +void AsScene1308LightWallSymbols::stFadeOut() { + startAnimation(0x80180A10, -1, -1); + _playBackwards = true; + NextState(&AsScene1308LightWallSymbols::stFadeOutDone); +} + +void AsScene1308LightWallSymbols::stFadeOutDone() { + sendMessage(_parentScene, 0x2003, 0); + stopAnimation(); + setVisible(false); +} + +SsScene1308Number::SsScene1308Number(NeverhoodEngine *vm, uint32 fileHash, int index) + : StaticSprite(vm, fileHash, 100) { + + setVisible(false); + _x = _spriteResource.getPosition().x + index * 20; + updatePosition(); +} + +AsScene1308Mouse::AsScene1308Mouse(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1100) { + + _x = 286; + _y = 429; + createSurface1(0xA282C472, 100); + startAnimation(0xA282C472, 0, -1); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1308Mouse::handleMessage); +} + +uint32 AsScene1308Mouse::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x66382026) + playSound(0, 0x0CD84468); + else if (param.asInteger() == 0x6E28061C) + playSound(0, 0x78C8402C); + else if (param.asInteger() == 0x462F0410) + playSound(0, 0x60984E28); + break; + } + return messageResult; +} + +Scene1308::Scene1308(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _isProjecting(false), _asProjector(NULL) { + + _vm->gameModule()->initKeySlotsPuzzle(); + + SetMessageHandler(&Scene1308::handleMessage); + + setBackground(0x41024202); + setPalette(0x41024202); + insertScreenMouse(0x24206418); + + _asTape = insertSprite<AsScene1201Tape>(this, 17, 1100, 502, 445, 0x9148A011); + addCollisionSprite(_asTape); + + if (getGlobalVar(V_MOUSE_SUCKED_IN)) { + insertSprite<AsScene1308Mouse>(); + insertSprite<AnimatedSprite>(0x461A1490, 200, 235, 429); + } + + _sprite1 = insertStaticSprite(0x0A042060, 1100); + _asJaggyDoor = insertSprite<AsScene1308JaggyDoor>(this); + _asLightWallSymbols = insertSprite<AsScene1308LightWallSymbols>(this); + _ssNumber1 = insertSprite<SsScene1308Number>(kScene1308NumberFileHashes[getSubVar(VA_GOOD_KEY_SLOT_NUMBERS, 1)], 0); + _ssNumber2 = insertSprite<SsScene1308Number>(kScene1308NumberFileHashes[getSubVar(VA_GOOD_KEY_SLOT_NUMBERS, 0)], 1); + _ssNumber3 = insertSprite<SsScene1308Number>(kScene1308NumberFileHashes[getSubVar(VA_GOOD_KEY_SLOT_NUMBERS, 2)], 2); + _sprite2 = insertStaticSprite(0x40043120, 995); + _sprite3 = insertStaticSprite(0x43003100, 995); + _sprite4 = NULL; + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene1308>(380, 440); + setMessageList(0x004B57C0); + if (getGlobalVar(V_KEYDOOR_UNLOCKED)) { + _sprite4 = insertStaticSprite(0x0101A624, 1100); + setRectList(0x004B5990); + } else { + _sprite5 = insertStaticSprite(0x080811A0, 100); + setRectList(0x004B5980); + } + } else if (which == 1) { + // Klaymen entering from the right + insertKlaymen<KmScene1308>(640, 440); + setMessageList(0x004B57C8); + if (getGlobalVar(V_KEYDOOR_UNLOCKED)) { + _sprite4 = insertStaticSprite(0x0101A624, 1100); + setRectList(0x004B5990); + } else { + _sprite5 = insertStaticSprite(0x080811A0, 100); + setRectList(0x004B5980); + } + } else if (which == 2) { + // Klaymen returning from keyslots panel + insertKlaymen<KmScene1308>(475, 440); + setMessageList(0x004B58B0); + if (getGlobalVar(V_KEYDOOR_UNLOCKED)) { + _sprite5 = insertSprite<AsScene1308KeyboardDoor>(this); + _sprite4 = insertStaticSprite(0x0101A624, 1100); + _sprite4->setVisible(false); + } else { + _sprite5 = insertStaticSprite(0x080811A0, 100); + setRectList(0x004B5980); + } + } else { + // Klaymen entering from the left + insertKlaymen<KmScene1308>(41, 440); + setMessageList(0x004B57D0); + sendMessage(_asJaggyDoor, 0x4808, 0); + _sprite1->setVisible(false); + if (getGlobalVar(V_KEYDOOR_UNLOCKED)) { + _sprite4 = insertStaticSprite(0x0101A624, 1100); + _klaymen->setVisible(false); + } else { + _sprite5 = insertStaticSprite(0x080811A0, 100); + _klaymen->setVisible(false); + } + } + + if (_sprite4) + _klaymen->setClipRect(_sprite1->getDrawRect().x, 0, _sprite4->getDrawRect().x2(), 480); + else + _klaymen->setClipRect(_sprite1->getDrawRect().x, 0, 640, 480); + + if (getGlobalVar(V_PROJECTOR_LOCATION) == 4) { + _asProjector = insertSprite<AsCommonProjector>(this, _klaymen, (Sprite*)NULL); + addCollisionSprite(_asProjector); + _asProjector->setClipRect(0, 0, 640, _sprite2->getDrawRect().y2()); + _asProjector->setRepl(64, 0); + } + +} + +uint32 Scene1308::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x88C11390) { + setRectList(0x004B59A0); + _isProjecting = true; + } else if (param.asInteger() == 0x08821382) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + if (getGlobalVar(V_KEYDOOR_UNLOCKED)) + setRectList(0x004B5990); + else + setRectList(0x004B5980); + _isProjecting = false; + } else if (param.asInteger() == 0x4AC68808) { + clearRectList(); + sendMessage(_asJaggyDoor, 0x4809, 0); + _sprite1->setVisible(false); + _klaymen->setVisible(false); + } + break; + case 0x1022: + if (sender == _asProjector) { + if (param.asInteger() >= 1000) + setSurfacePriority(_sprite3->getSurface(), 1100); + else + setSurfacePriority(_sprite3->getSurface(), 995); + } + break; + case 0x2000: + if (getGlobalVar(V_KEYDOOR_UNLOCKED)) + setRectList(0x004B5990); + else + setRectList(0x004B5980); + setMessageList(0x004B57E8, false); + _sprite1->setVisible(true); + _klaymen->setVisible(true); + break; + case 0x2001: + leaveScene(0); + break; + case 0x2003: + _ssNumber1->setVisible(false); + _ssNumber2->setVisible(false); + _ssNumber3->setVisible(false); + break; + case 0x2004: + _sprite4->setVisible(true); + setRectList(0x004B5990); + break; + case 0x4807: + sendMessage(_asLightWallSymbols, 0x2003, 0); + break; + case 0x480F: + sendMessage(_asLightWallSymbols, 0x2002, 0); + _ssNumber1->setVisible(true); + _ssNumber2->setVisible(true); + _ssNumber3->setVisible(true); + break; + case 0x4826: + if (sender == _asProjector) { + if (_isProjecting) + setMessageList2(0x004B5868); + else { + if (param.asInteger() == 1) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + setMessageList2(0x004B5848); + } else if (sendMessage(_asProjector, 0x480C, _klaymen->getX() <= _asProjector->getX() ? 0 : 1) != 0) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + setMessageList2(0x004B5830); + } else + setMessageList2(0x004B5800); + } + } else if (sender == _asTape) { + if (_isProjecting) + setMessageList2(0x004B5868); + else if (_messageListStatus != 2) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList2(0x004B58E0); + } + } + break; + } + return 0; +} + +Scene1317::Scene1317(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule) { + + SetMessageHandler(&Scene1317::handleMessage); + _smackerPlayer = addSmackerPlayer(new SmackerPlayer(_vm, this, 0x08982841, true, false)); + _vm->_screen->setSmackerDecoder(_smackerPlayer->getSmackerDecoder()); + insertScreenMouse(0x08284011); + showMouse(false); + _smackerFileHash = 0; + _keepLastSmackerFrame = false; +} + +void Scene1317::update() { + if (_smackerFileHash) { + _smackerPlayer->open(_smackerFileHash, _keepLastSmackerFrame); + _smackerFileHash = 0; + } + Scene::update(); +} + +void Scene1317::upChooseKing() { + if (!_klaymenBlinks && _klaymenBlinkCountdown != 0 && (--_klaymenBlinkCountdown == 0)) + _klaymenBlinks = true; + + if (!_klaymenBlinks && _smackerPlayer->getFrameNumber() + 1 >= 2) { + _smackerPlayer->rewind(); + } else if (_klaymenBlinks && _smackerPlayer->getFrameNumber() + 1 >= 6) { + _smackerPlayer->rewind(); + _klaymenBlinks = false; + _klaymenBlinkCountdown = _vm->_rnd->getRandomNumber(30 - 1) + 15; + } + + if (!_klaymenBlinks && _decisionCountdown != 0 && (--_decisionCountdown == 0)) + stNoDecisionYet(); + + if (_smackerFileHash) { + _smackerPlayer->open(_smackerFileHash, _keepLastSmackerFrame); + _smackerFileHash = 0; + } + + Scene::update(); + +} + +uint32 Scene1317::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + stChooseKing(); + break; + } + return messageResult; +} + +uint32 Scene1317::hmChooseKing(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x >= 21 && param.asPoint().y >= 24 && + param.asPoint().x <= 261 && param.asPoint().y <= 280) { + stHoborgAsKing(); + } else if (param.asPoint().x >= 313 && param.asPoint().y >= 184 && + param.asPoint().x <= 399 && param.asPoint().y <= 379) { + stKlaymenAsKing(); + } else if (param.asPoint().x >= 347 && param.asPoint().y >= 380 && + param.asPoint().x <= 418 && param.asPoint().y <= 474) { + stKlaymenAsKing(); + } + break; + } + return messageResult; +} + +uint32 Scene1317::hmHoborgAsKing(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + stEndMovie(); + break; + } + return messageResult; +} + +uint32 Scene1317::hmEndMovie(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + leaveScene(0); + break; + } + return messageResult; +} + +void Scene1317::stChooseKing() { + showMouse(true); + _smackerFileHash = 0x10982841; + _keepLastSmackerFrame = true; + _decisionCountdown = 450; + _klaymenBlinks = false; + _klaymenBlinkCountdown = _vm->_rnd->getRandomNumber(30 - 1) + 15; + SetMessageHandler(&Scene1317::hmChooseKing); + SetUpdateHandler(&Scene1317::upChooseKing); +} + +void Scene1317::stNoDecisionYet() { + showMouse(false); + _smackerFileHash = 0x20982841; + _keepLastSmackerFrame = false; + SetMessageHandler(&Scene1317::handleMessage); + SetUpdateHandler(&Scene1317::update); +} + +void Scene1317::stHoborgAsKing() { + showMouse(false); + _smackerFileHash = 0x40982841; + _keepLastSmackerFrame = false; + SetMessageHandler(&Scene1317::hmHoborgAsKing); + SetUpdateHandler(&Scene1317::update); +} + +void Scene1317::stKlaymenAsKing() { + showMouse(false); + _smackerFileHash = 0x80982841; + _keepLastSmackerFrame = false; + SetMessageHandler(&Scene1317::hmEndMovie); + SetUpdateHandler(&Scene1317::update); +} + +void Scene1317::stEndMovie() { + showMouse(false); + _smackerFileHash = 0x40800711; + _keepLastSmackerFrame = false; + SetMessageHandler(&Scene1317::hmEndMovie); + SetUpdateHandler(&Scene1317::update); +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module1300.h b/engines/neverhood/modules/module1300.h new file mode 100644 index 0000000000..501f76304f --- /dev/null +++ b/engines/neverhood/modules/module1300.h @@ -0,0 +1,295 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 NEVERHOOD_MODULES_MODULE1300_H +#define NEVERHOOD_MODULES_MODULE1300_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" +#include "neverhood/smackerplayer.h" + +namespace Neverhood { + +// Module1300 + +class Module1300 : public Module { +public: + Module1300(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module1300(); +protected: + int _sceneNum; + uint32 _musicFileHash; + void createScene(int sceneNum, int which); + void updateScene(); +}; + +class AsScene1302Bridge : public AnimatedSprite { +public: + AsScene1302Bridge(NeverhoodEngine *vm, Scene *parentScene); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stLowerBridge(); + void stRaiseBridge(); + void cbLowerBridgeEvent(); +}; + +class SsScene1302Fence : public StaticSprite { +public: + SsScene1302Fence(NeverhoodEngine *vm); +protected: + int16 _firstY; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void suMoveDown(); + void suMoveUp(); +}; + +class Scene1302 : public Scene { +public: + Scene1302(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_asVenusFlyTrap; + Sprite *_asBridge; + Sprite *_ssFence; + Sprite *_asRing1; + Sprite *_asRing2; + Sprite *_asRing3; + Sprite *_asRing4; + Sprite *_asRing5; + Sprite *_class595; + Sprite *_sprite1; + Sprite *_sprite2; + Sprite *_sprite3; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1303Balloon : public AnimatedSprite { +public: + AsScene1303Balloon(NeverhoodEngine *vm, Scene *parentScene); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmBalloonPopped(int messageNum, const MessageParam ¶m, Entity *sender); + void stPopBalloon(); +}; + +class Scene1303 : public Scene { +public: + Scene1303(NeverhoodEngine *vm, Module *parentModule); +protected: + Sprite *_sprite1; + Sprite *_asBalloon; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1304Needle : public AnimatedSprite { +public: + AsScene1304Needle(NeverhoodEngine *vm, Scene *parentScene, int surfacePriority, int16 x, int16 y); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene1304 : public Scene { +public: + Scene1304(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_sprite1; + Sprite *_asKey; + Sprite *_asNeedle; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene1305 : public Scene { +public: + Scene1305(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1306Elevator : public AnimatedSprite { +public: + AsScene1306Elevator(NeverhoodEngine *vm, Scene *parentScene, AnimatedSprite *asElevatorDoor); +protected: + Scene *_parentScene; + AnimatedSprite *_asElevatorDoor; + bool _isUp; + bool _isDown; + int _countdown; + void update(); + void upGoingDown(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stGoingUp(); + void cbGoingUpEvent(); + void stGoingDown(); + void cbGoingDownEvent(); +}; + +class Scene1306 : public Scene { +public: + Scene1306(NeverhoodEngine *vm, Module *parentModule, int which); + ~Scene1306(); +protected: + Sprite *_ssButton; + Sprite *_asTape; + AnimatedSprite *_asElevatorDoor; + Sprite *_asElevator; + Sprite *_sprite1; + Sprite *_asKey; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 handleMessage416EB0(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1307Key : public AnimatedSprite { +public: + AsScene1307Key(NeverhoodEngine *vm, Scene *parentScene, uint keyIndex, NRect *clipRects); +protected: + Scene *_parentScene; + NPointArray *_pointList; + uint _pointIndex; + int _frameIndex; + uint _keyIndex; + NRect *_clipRects; + bool _isClickable; + int16 _prevX, _prevY; + int16 _deltaX, _deltaY; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void suRemoveKey(); + void suInsertKey(); + void suMoveKey(); + void stRemoveKey(); + void stInsertKey(); + void stMoveKey(); + void stUnlock(); + void stInsert(); +}; + +class Scene1307 : public Scene { +public: + Scene1307(NeverhoodEngine *vm, Module *parentModule); +protected: + NPointArray *_keyHolePoints; + NRect _keyHoleRects[16]; + NRect _clipRects[4]; + Sprite *_asKeys[3]; + int _countdown; + Sprite *_asCurrKey; + bool _isInsertingKey; + bool _doLeaveScene; + bool _isPuzzleSolved; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene1308JaggyDoor : public AnimatedSprite { +public: + AsScene1308JaggyDoor(NeverhoodEngine *vm, Scene *parentScene); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stOpenDoor(); + void stOpenDoorDone(); + void stCloseDoor(); + void stCloseDoorDone(); +}; + +class AsScene1308KeyboardDoor : public AnimatedSprite { +public: + AsScene1308KeyboardDoor(NeverhoodEngine *vm, Scene *parentScene); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stFallingKeys(); + void stFallingKeysDone(); +}; + +class AsScene1308LightWallSymbols : public AnimatedSprite { +public: + AsScene1308LightWallSymbols(NeverhoodEngine *vm, Scene *parentScene); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stFadeIn(); + void stFadeOut(); + void stFadeOutDone(); +}; + +class SsScene1308Number : public StaticSprite { +public: + SsScene1308Number(NeverhoodEngine *vm, uint32 fileHash, int index); +}; + +class AsScene1308Mouse : public AnimatedSprite { +public: + AsScene1308Mouse(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene1308 : public Scene { +public: + Scene1308(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_asTape; + Sprite *_asJaggyDoor; + Sprite *_asLightWallSymbols; + Sprite *_ssNumber1; + Sprite *_ssNumber2; + Sprite *_ssNumber3; + AnimatedSprite *_asProjector; + Sprite *_sprite1; + Sprite *_sprite2; + Sprite *_sprite3; + Sprite *_sprite4; + Sprite *_sprite5; + bool _isProjecting; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene1317 : public Scene { +public: + Scene1317(NeverhoodEngine *vm, Module *parentModule); +protected: + SmackerPlayer *_smackerPlayer; + bool _klaymenBlinks; + int _klaymenBlinkCountdown; + int _decisionCountdown; + uint32 _smackerFileHash; + bool _keepLastSmackerFrame; + void update(); + void upChooseKing(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmChooseKing(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmHoborgAsKing(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmEndMovie(int messageNum, const MessageParam ¶m, Entity *sender); + void stChooseKing(); + void stNoDecisionYet(); + void stHoborgAsKing(); + void stKlaymenAsKing(); + void stEndMovie(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE1300_H */ diff --git a/engines/neverhood/modules/module1400.cpp b/engines/neverhood/modules/module1400.cpp new file mode 100644 index 0000000000..4f69637ee0 --- /dev/null +++ b/engines/neverhood/modules/module1400.cpp @@ -0,0 +1,1621 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module1400.h" +#include "neverhood/modules/module1000.h" +#include "neverhood/modules/module2100.h" +#include "neverhood/modules/module2200.h" +#include "neverhood/diskplayerscene.h" +#include "neverhood/gamemodule.h" + +namespace Neverhood { + +Module1400::Module1400(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + _vm->_soundMan->addMusic(0x00AD0012, 0x06333232); + _vm->_soundMan->addMusic(0x00AD0012, 0x624A220E); + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else + createScene(0, 0); + +} + +Module1400::~Module1400() { + _vm->_soundMan->deleteMusicGroup(0x00AD0012); +} + +void Module1400::createScene(int sceneNum, int which) { + debug("Module1400::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _vm->_soundMan->startMusic(0x06333232, 0, 2); + _childObject = new Scene1401(_vm, this, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + _vm->_soundMan->stopMusic(0x06333232, 0, 2); + _vm->_soundMan->stopMusic(0x624A220E, 0, 2); + _childObject = new Scene1402(_vm, this, which); + break; + case 2: + _vm->gameState().sceneNum = 2; + _vm->_soundMan->stopMusic(0x06333232, 0, 2); + _vm->_soundMan->startMusic(0x624A220E, 0, 2); + _childObject = new Scene1403(_vm, this, which); + break; + case 3: + _vm->gameState().sceneNum = 3; + _vm->_soundMan->startMusic(0x06333232, 0, 2); + _childObject = new Scene1404(_vm, this, which); + break; + case 4: + _vm->gameState().sceneNum = 4; + _vm->_soundMan->startMusic(0x06333232, 0, 2); + _childObject = new Scene1405(_vm, this); + break; + case 5: + _vm->gameState().sceneNum = 5; + _vm->_soundMan->stopMusic(0x06333232, 0, 2); + _childObject = new DiskplayerScene(_vm, this, 2); + break; + case 6: + _vm->gameState().sceneNum = 6; + _vm->_soundMan->stopMusic(0x06333232, 0, 2); + _childObject = new Scene1407(_vm, this); + break; + } + SetUpdateHandler(&Module1400::updateScene); + _childObject->handleUpdate(); +} + +void Module1400::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 1) + createScene(1, 0); + else if (_moduleResult == 2) + createScene(3, 0); + else + leaveModule(0); + break; + case 1: + if (_moduleResult == 1) + createScene(2, 0); + else if (_moduleResult == 2) + createScene(6, -1); + else + createScene(0, 1); + break; + case 2: + createScene(1, 1); + break; + case 3: + if (_moduleResult == 1) + createScene(4, 0); + else if (_moduleResult == 2) + createScene(5, -1); + else + createScene(0, 2); + break; + case 4: + createScene(3, 1); + break; + case 5: + createScene(3, 2); + break; + case 6: + createScene(1, 2); + break; + } + } +} + +// Scene1401 + +AsScene1401Pipe::AsScene1401Pipe(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1100), _countdown1(0), _countdown2(0) { + + createSurface(900, 152, 147); + _x = 454; + _y = 217; + startAnimation(0x4C210500, 0, -1); + SetUpdateHandler(&AsScene1401Pipe::update); + SetMessageHandler(&AsScene1401Pipe::handleMessage); +} + +AsScene1401Pipe::~AsScene1401Pipe() { + _vm->_soundMan->deleteSoundGroup(0x01104C08); +} + +void AsScene1401Pipe::update() { + AnimatedSprite::update(); + if (_countdown1 != 0 && (--_countdown1 == 0)) + stDoneSucking(); + if (_countdown2 != 0 && (--_countdown2 == 0)) { + _vm->_soundMan->addSound(0x01104C08, 0x4A116437); + _vm->_soundMan->playSoundLooping(0x4A116437); + } +} + +void AsScene1401Pipe::upSuckInProjector() { + AnimatedSprite::update(); + if (_countdown1 != 0) + _countdown1--; +} + +uint32 AsScene1401Pipe::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x0A8A1490) + playSound(1, 0x6AB6666F); + break; + case 0x2000: + _countdown1 = 70; + _countdown2 = 8; + stStartSucking(); + break; + case 0x483A: + stSuckInProjector(); + break; + } + return messageResult; +} + +uint32 AsScene1401Pipe::hmSuckInProjector(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + if (_countdown1 != 0) + stStartSucking(); + else + stDoneSucking(); + SetMessageHandler(&AsScene1401Pipe::handleMessage); + SetUpdateHandler(&AsScene1401Pipe::update); + break; + } + return messageResult; +} + +void AsScene1401Pipe::stStartSucking() { + startAnimation(0x4C240100, 0, -1); + playSound(0, 0x4A30063F); +} + +void AsScene1401Pipe::stDoneSucking() { + _vm->_soundMan->deleteSound(0x4A116437); + playSound(0, 0x4A120435); + startAnimation(0x4C210500, 0, -1); +} + +void AsScene1401Pipe::stSuckInProjector() { + startAnimation(0x6C210810, 0, -1); + SetUpdateHandler(&AsScene1401Pipe::upSuckInProjector); + SetMessageHandler(&AsScene1401Pipe::hmSuckInProjector); +} + +AsScene1401Mouse::AsScene1401Mouse(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1100) { + + createSurface(100, 71, 41); + _x = 478; + _y = 433; + startAnimation(0xA282C472, 0, -1); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1401Mouse::handleMessage); +} + +uint32 AsScene1401Mouse::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x66382026) + playSound(0, 0x0CD84468); + else if (param.asInteger() == 0x6E28061C) + playSound(0, 0x78C8402C); + else if (param.asInteger() == 0x462F0410) + playSound(0, 0x60984E28); + break; + case 0x4839: + stSuckedIn(); + break; + } + return messageResult; +} + +void AsScene1401Mouse::suSuckedIn() { + AnimatedSprite::updateDeltaXY(); + if (_collisionBounds.y1 <= 150) { + playSound(0, 0x0E32247F); + stopAnimation(); + setVisible(false); + SetMessageHandler(NULL); + SetSpriteUpdate(NULL); + } +} + +void AsScene1401Mouse::stSuckedIn() { + startAnimation(0x34880040, 0, -1); + SetSpriteUpdate(&AsScene1401Mouse::suSuckedIn); +} + +AsScene1401Cheese::AsScene1401Cheese(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1100) { + + createSurface(200, 152, 147); + _x = 427; + _y = 433; + startAnimation(0x461A1490, 0, -1); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1401Cheese::handleMessage); +} + +uint32 AsScene1401Cheese::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x4839: + stSuckedIn(); + break; + } + return messageResult; +} + +void AsScene1401Cheese::suSuckedIn() { + AnimatedSprite::updateDeltaXY(); + if (_collisionBounds.y1 <= 150) { + playSound(0, 0x18020439); + stopAnimation(); + setVisible(false); + SetMessageHandler(NULL); + SetSpriteUpdate(NULL); + } +} + +void AsScene1401Cheese::stSuckedIn() { + startAnimation(0x103B8020, 0, -1); + SetSpriteUpdate(&AsScene1401Cheese::suSuckedIn); +} + +AsScene1401BackDoor::AsScene1401BackDoor(NeverhoodEngine *vm, Sprite *klaymen, bool isOpen) + : AnimatedSprite(vm, 1100), _klaymen(klaymen), _countdown(0), _isOpen(isOpen) { + + _x = 320; + _y = 240; + createSurface1(0x04551900, 100); + if (isOpen) { + startAnimation(0x04551900, -1, -1); + _countdown = 48; + } else { + stopAnimation(); + setVisible(false); + } + _newStickFrameIndex = STICK_LAST_FRAME; + SetUpdateHandler(&AsScene1401BackDoor::update); + SetMessageHandler(&AsScene1401BackDoor::handleMessage); +} + +void AsScene1401BackDoor::update() { + if (_countdown != 0 && (--_countdown == 0)) + stCloseDoor(); + AnimatedSprite::update(); +} + + +uint32 AsScene1401BackDoor::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2001: + if (_isOpen) + _countdown = 168; + messageResult = _isOpen ? 1 : 0; + break; + case 0x3002: + gotoNextState(); + break; + case 0x4808: + _countdown = 168; + if (!_isOpen) + stOpenDoor(); + break; + } + return messageResult; +} + +void AsScene1401BackDoor::stOpenDoor() { + _isOpen = true; + setVisible(true); + startAnimation(0x04551900, 0, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + playSound(0, calcHash("fxDoorOpen24")); +} + +void AsScene1401BackDoor::stCloseDoor() { + _isOpen = false; + setVisible(true); + startAnimation(0x04551900, -1, -1); + playSound(0, calcHash("fxDoorClose24")); + _playBackwards = true; + NextState(&AsScene1401BackDoor::stCloseDoorDone); +} + +void AsScene1401BackDoor::stCloseDoorDone() { + stopAnimation(); + setVisible(false); +} + +static const AsCommonProjectorItem kAsCommonProjectorItems[] = { + {{154, 453}, 4, 2, 0, 0, 1}, + {{104, 391}, 4, -1, -1, 1, 1}, + {{ 22, 447}, 6, -1, -1, 1, 1}, + {{112, 406}, 2, -1, -1, 1, 0}, + {{262, 433}, 1, 1, 0, 0, 0} +}; + +AsCommonProjector::AsCommonProjector(NeverhoodEngine *vm, Scene *parentScene, Sprite *klaymen, Sprite *asPipe) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _klaymen(klaymen), _asPipe(asPipe) { + + _asProjectorItem = &kAsCommonProjectorItems[getGlobalVar(V_PROJECTOR_LOCATION)]; + createSurface(990, 101, 182); + startAnimation(0x10E3042B, 0, -1); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsCommonProjector::handleMessage); + _x = getGlobalVar(V_PROJECTOR_SLOT) * 108 + _asProjectorItem->point.x; + _lockedInSlot = true; + moveProjector(); + setDoDeltaX(1); + if ((int8)getGlobalVar(V_PROJECTOR_SLOT) == _asProjectorItem->lockSlotIndex) + stStayLockedInSlot(); + loadSound(2, 0xC8C2507C); +} + +AsCommonProjector::~AsCommonProjector() { + _vm->_soundMan->deleteSoundGroup(0x05331081); +} + +uint32 AsCommonProjector::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + sendMessage(_parentScene, 0x4826, 0); + messageResult = 1; + break; + case 0x4807: + setGlobalVar(V_PROJECTOR_SLOT, (_x - _asProjectorItem->point.x) / 108); + if ((int8)getGlobalVar(V_PROJECTOR_SLOT) == _asProjectorItem->lockSlotIndex) + stStartLockedInSlot(); + else + stIdle(); + break; + case 0x480B: + if (param.asInteger() != 1) { + if ((int8)getGlobalVar(V_PROJECTOR_SLOT) < _asProjectorItem->maxSlotCount) + incGlobalVar(V_PROJECTOR_SLOT, 1); + } else if (getGlobalVar(V_PROJECTOR_SLOT) > 0) + incGlobalVar(V_PROJECTOR_SLOT, -1); + stMoving(); + break; + case 0x480C: + // Check if the projector can be moved + if (param.asInteger() != 1) + messageResult = (int8)getGlobalVar(V_PROJECTOR_SLOT) < _asProjectorItem->maxSlotCount ? 1 : 0; + else + messageResult = getGlobalVar(V_PROJECTOR_SLOT) > 0 ? 1 : 0; + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + case 0x4839: + stStartSuckedIn(); + break; + } + return messageResult; +} + +uint32 AsCommonProjector::hmLockedInSlot(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (param.asPoint().x - _x >= 17 && param.asPoint().x - _x <= 56 && + param.asPoint().y - _y >= -120 && param.asPoint().y - _y <= -82) { + sendMessage(_parentScene, 0x4826, 1); + } else + sendMessage(_parentScene, 0x4826, 0); + messageResult = 1; + break; + case 0x4807: + sendMessage(_parentScene, 0x4807, 0); + stStopProjecting(); + break; + case 0x480B: + if (param.asInteger() != 1) { + if ((int8)getGlobalVar(V_PROJECTOR_SLOT) < _asProjectorItem->maxSlotCount) + incGlobalVar(V_PROJECTOR_SLOT, 1); + } else if (getGlobalVar(V_PROJECTOR_SLOT) > 0) + incGlobalVar(V_PROJECTOR_SLOT, -1); + stTurnToFront(); + break; + case 0x480C: + // Check if the projector can be moved + if (param.asInteger() != 1) + messageResult = (int8)getGlobalVar(V_PROJECTOR_SLOT) < _asProjectorItem->maxSlotCount ? 1 : 0; + else + messageResult = getGlobalVar(V_PROJECTOR_SLOT) > 0 ? 1 : 0; + break; + case 0x480F: + stStartProjecting(); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +uint32 AsCommonProjector::hmAnimation(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsCommonProjector::suMoving() { + if (_x <= _klaymen->getX()) + _x = _klaymen->getX() - 100; + else + _x = _klaymen->getX() + 100; + moveProjector(); + if (_beforeMoveX == _x) { + if (getGlobalVar(V_PROJECTOR_SLOT) == 0 && _asProjectorItem->leftBorderLeaves != 0) { + sendMessage(_parentScene, 0x1019, 0); + incGlobalVar(V_PROJECTOR_LOCATION, -1); + setGlobalVar(V_PROJECTOR_SLOT, kAsCommonProjectorItems[getGlobalVar(V_PROJECTOR_LOCATION)].maxSlotCount); + } else if ((int8)getGlobalVar(V_PROJECTOR_SLOT) == _asProjectorItem->maxSlotCount && _asProjectorItem->rightBorderLeaves != 0) { + sendMessage(_parentScene, 0x1019, 1); + incGlobalVar(V_PROJECTOR_LOCATION, +1); + setGlobalVar(V_PROJECTOR_SLOT, 0); + } + } + Sprite::updateBounds(); +} + +void AsCommonProjector::moveProjector() { + + bool nowLockedInSlot = false; + + _y = _asProjectorItem->point.y; + + if (_asProjectorItem->index1 != -1) { + int16 elX = _asProjectorItem->index1 * 108 + _asProjectorItem->point.x; + if (elX - 20 < _x && elX + 20 > _x) { + nowLockedInSlot = true; + _y = _asProjectorItem->point.y + 10; + } + } + + if (_asProjectorItem->lockSlotIndex != -1) { + int16 elX = _asProjectorItem->lockSlotIndex * 108 + _asProjectorItem->point.x; + if (elX - 20 < _x && elX + 20 > _x) { + nowLockedInSlot = true; + _y = _asProjectorItem->point.y + 10; + } + } + + if (_lockedInSlot && !nowLockedInSlot) + _lockedInSlot = false; + else if (!_lockedInSlot && nowLockedInSlot) { + playSound(1, 0x5440E474); + _lockedInSlot = true; + } + +} + +void AsCommonProjector::stSuckedIn() { + AnimatedSprite::updateDeltaXY(); + if (_collisionBounds.y1 <= 150) { + sendMessage(_asPipe, 0x483A, 0); + stopAnimation(); + setVisible(false); + SetMessageHandler(&Sprite::handleMessage); + SetSpriteUpdate(NULL); + } +} + +void AsCommonProjector::stIdle() { + startAnimation(0x10E3042B, 0, -1); + SetMessageHandler(&AsCommonProjector::handleMessage); + SetSpriteUpdate(NULL); +} + +void AsCommonProjector::stMoving() { + _beforeMoveX = getGlobalVar(V_PROJECTOR_SLOT) * 108 + _asProjectorItem->point.x; + startAnimation(0x14A10137, 0, -1); + playSound(1, 0xEC008474); + SetMessageHandler(&AsCommonProjector::handleMessage); + SetSpriteUpdate(&AsCommonProjector::suMoving); +} + +void AsCommonProjector::stStartLockedInSlot() { + startAnimation(0x80C32213, 0, -1); + SetMessageHandler(&AsCommonProjector::hmAnimation); + SetSpriteUpdate(NULL); + NextState(&AsCommonProjector::stStayLockedInSlot); +} + +void AsCommonProjector::stStayLockedInSlot() { + startAnimation(0xD23B207F, 0, -1); + SetMessageHandler(&AsCommonProjector::hmLockedInSlot); + SetSpriteUpdate(NULL); +} + +void AsCommonProjector::stStartProjecting() { + startAnimation(0x50A80517, 0, -1); + setGlobalVar(V_PROJECTOR_ACTIVE, 1); + playSound(0, 0xCC4A8456); + _vm->_soundMan->addSound(0x05331081, 0xCE428854); + _vm->_soundMan->playSoundLooping(0xCE428854); + SetMessageHandler(&AsCommonProjector::hmAnimation); + SetSpriteUpdate(NULL); + NextState(&AsCommonProjector::stLockedInSlot); +} + +void AsCommonProjector::stLockedInSlot() { + sendMessage(_parentScene, 0x480F, 0); + startAnimation(0xD833207F, 0, -1); + SetMessageHandler(&AsCommonProjector::hmLockedInSlot); + SetSpriteUpdate(NULL); +} + +void AsCommonProjector::stStopProjecting() { + startAnimation(0x50A94417, 0, -1); + setGlobalVar(V_PROJECTOR_ACTIVE, 0); + playSound(0, 0xCC4A8456); + _vm->_soundMan->deleteSound(0xCE428854); + SetMessageHandler(&AsCommonProjector::hmAnimation); + SetSpriteUpdate(NULL); + NextState(&AsCommonProjector::stStayLockedInSlot); +} + +void AsCommonProjector::stTurnToFront() { + _beforeMoveX = getGlobalVar(V_PROJECTOR_SLOT) * 108 + _asProjectorItem->point.x; + startAnimation(0x22CB4A33, 0, -1); + SetMessageHandler(&AsCommonProjector::hmAnimation); + SetSpriteUpdate(&AsCommonProjector::suMoving); + NextState(&AsCommonProjector::stMoving); +} + +void AsCommonProjector::stStartSuckedIn() { + setGlobalVar(V_PROJECTOR_LOCATION, 4); + setGlobalVar(V_PROJECTOR_SLOT, 0); + startAnimation(0x708D4712, 0, -1); + playSound(2); + SetMessageHandler(&Sprite::handleMessage); + SetSpriteUpdate(&AsCommonProjector::stSuckedIn); +} + +Scene1401::Scene1401(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _projectorBorderFlag(false), _ssFloorButton(NULL), _asProjector(NULL), + _asPipe(NULL), _asMouse(NULL), _asCheese(NULL), _asBackDoor(NULL), + _sprite1(NULL), _sprite2(NULL), _sprite3(NULL), _ssButton(NULL) { + + SetMessageHandler(&Scene1401::handleMessage); + SetUpdateHandler(&Scene1401::update); + + setRectList(0x004B6758); + setBackground(0x08221FA5); + setPalette(0x08221FA5); + insertScreenMouse(0x21FA108A); + + _ssFloorButton = insertSprite<SsCommonFloorButton>(this, 0x980F3124, 0x12192892, 100, 0); + _asPipe = insertSprite<AsScene1401Pipe>(); + + if (!getGlobalVar(V_MOUSE_SUCKED_IN)) { + _asMouse = insertSprite<AsScene1401Mouse>(); + _asCheese = insertSprite<AsScene1401Cheese>(); + } + + _sprite3 = insertStaticSprite(0xA82BA811, 1100); + insertStaticSprite(0x0A116C60, 1100); + _ssButton = insertSprite<SsCommonButtonSprite>(this, 0xB84B1100, 100, 0); + _sprite1 = insertStaticSprite(0x38EA100C, 1005); + _sprite2 = insertStaticSprite(0x98D0223C, 1200); + _sprite2->setVisible(false); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene1401>(380, 447); + setMessageList(0x004B65C8); + _sprite1->setVisible(false); + } else if (which == 1) { + // Klaymen entering from the left + insertKlaymen<KmScene1401>(0, 447); + setMessageList(0x004B65D0); + _sprite1->setVisible(false); + } else if (which == 2) { + // Klaymen entering from the right + insertKlaymen<KmScene1401>(660, 447); + setMessageList(0x004B65D8); + _sprite1->setVisible(false); + } else { + // Klaymen entering from the back + insertKlaymen<KmScene1401>(290, 413); + setMessageList(0x004B65E8); + _sprite1->setVisible(false); + } + + if (getGlobalVar(V_PROJECTOR_LOCATION) == 2) { + _asProjector = insertSprite<AsCommonProjector>(this, _klaymen, _asPipe); + addCollisionSprite(_asProjector); + if (getGlobalVar(V_PROJECTOR_SLOT) == 6) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + _klaymen->setX(_asProjector->getX() + 100); + _klaymen->updateBounds(); + setMessageList(0x004B6670); + } else if (getGlobalVar(V_PROJECTOR_SLOT) == 0) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + _klaymen->setX(_asProjector->getX() - 100); + _klaymen->updateBounds(); + setMessageList(0x004B6670); + } + _asProjector->setClipRect(_sprite3->getDrawRect().x, _sprite2->getDrawRect().y, 640, 480); + } + + _klaymen->setClipRect(_sprite3->getDrawRect().x, 0, 640, 480); + + if (which == 0 && _asProjector) + sendMessage(_asProjector, 0x482B, 0); + + _asBackDoor = insertSprite<AsScene1401BackDoor>(_klaymen, which == 0); + +} + +void Scene1401::update() { + Scene::update(); + if (_asProjector && !_projectorBorderFlag && _asProjector->getY() < 360) { + _sprite2->setVisible(true); + _projectorBorderFlag = true; + } else + _sprite2->setVisible(false); +} + +uint32 Scene1401::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x02144CB1) + sendEntityMessage(_klaymen, 0x1014, _ssFloorButton); + else if (param.asInteger() == 0x402064D8) + sendEntityMessage(_klaymen, 0x1014, _ssButton); + else if (param.asInteger() == 0x01C66840) { + if (sendMessage(_asBackDoor, 0x2001, 0) != 0) + setMessageList(0x004B6690); + else + setMessageList(0x004B66B0); + } + break; + case 0x1019: + if (param.asInteger() != 0) + leaveScene(2); + else + leaveScene(1); + break; + case 0x480B: + if (sender == _ssFloorButton) { + sendMessage(_asPipe, 0x2000, 0); + if (!getGlobalVar(V_MOUSE_SUCKED_IN)) { + sendMessage(_asMouse, 0x4839, 0); + sendMessage(_asCheese, 0x4839, 0); + setGlobalVar(V_MOUSE_SUCKED_IN, 1); + } + if (_asProjector && _asProjector->getX() > 404 && _asProjector->getX() < 504) + sendMessage(_asProjector , 0x4839, 0); + } else if (sender == _ssButton) + sendMessage(_asBackDoor, 0x4808, 0); + break; + case 0x4826: + if (sender == _asProjector) { + if (sendMessage(_asProjector, 0x480C, _klaymen->getX() > _asProjector->getX() ? 1 : 0) != 0) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + setMessageList2(0x004B6658); + } else + setMessageList2(0x004B65F0); + } + break; + case 0x482A: + _sprite1->setVisible(true); + if (_asProjector) + sendMessage(_asProjector, 0x482B, 0); + break; + case 0x482B: + _sprite1->setVisible(false); + if (_asProjector) + sendMessage(_asProjector, 0x482A, 0); + break; + } + return 0; +} + +// Scene1402 + +SsScene1402BridgePart::SsScene1402BridgePart(NeverhoodEngine *vm, uint32 fileHash, int surfacePriority) + : StaticSprite(vm, fileHash, surfacePriority) { + + SetFilterY(&Sprite::defFilterY); + SetUpdateHandler(&StaticSprite::updatePosition); +} + +AsScene1402PuzzleBox::AsScene1402PuzzleBox(NeverhoodEngine *vm, Scene *parentScene, int status) + : AnimatedSprite(vm, 1100), _parentScene(parentScene) { + + createSurface(900, 347, 230); + + SetFilterY(&Sprite::defFilterY); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1402PuzzleBox::handleMessage); + _x = 279; + _y = 270; + if (status == 2) { + // Puzzle box after the puzzle was solved + startAnimation(0x20060259, 0, -1); + playSound(0, 0x419014AC); + loadSound(1, 0x61901C29); + NextState(&AsScene1402PuzzleBox::stMoveDownSolvedDone); + } else if (status == 1) { + // Puzzle box appears + startAnimation(0x210A0213, 0, -1); + playSound(0, 0x41809C6C); + NextState(&AsScene1402PuzzleBox::stMoveUpDone); + } else { + // Puzzle box is here + startAnimation(0x20060259, -1, -1); + loadSound(1, 0x61901C29); + _newStickFrameIndex = STICK_LAST_FRAME; + } +} + +uint32 AsScene1402PuzzleBox::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2002: + playSound(1); + startAnimation(0x20060259, -1, -1); + _playBackwards = true; + NextState(&AsScene1402PuzzleBox::stMoveDownDone); + break; + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene1402PuzzleBox::stMoveUpDone() { + sendMessage(_parentScene, 0x2000, 0); + stopAnimation(); + setVisible(false); +} + +void AsScene1402PuzzleBox::stMoveDownDone() { + sendMessage(_parentScene, 0x2001, 0); + stopAnimation(); + setVisible(false); +} + +void AsScene1402PuzzleBox::stMoveDownSolvedDone() { + sendMessage(_parentScene, 0x2003, 0); + stopAnimation(); +} + +Scene1402::Scene1402(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _isShaking(false), _asPuzzleBox(NULL), _asProjector(NULL) { + + SetMessageHandler(&Scene1402::handleMessage); + + _vm->_screen->setYOffset(0); + + setBackground(0x231482F0); + setBackgroundY(-10); + setPalette(0x231482F0); + _palette->addPalette(0x91D3A391, 0, 64, 0); + insertScreenMouse(0x482F4239); + + _ssBridgePart1 = insertSprite<SsScene1402BridgePart>(0x15402D64, 1100); + _ssBridgePart2 = insertSprite<SsScene1402BridgePart>(0x10A02120, 1100); + _ssBridgePart3 = insertSprite<SsScene1402BridgePart>(0x60882BE0, 1100); + + if (getGlobalVar(V_MOUSE_PUZZLE_SOLVED)) + setRectList(0x004B0C48); + else + setRectList(0x004B0C98); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene1402>(377, 391); + setMessageList(0x004B0B48); + if (!getGlobalVar(V_MOUSE_PUZZLE_SOLVED)) + _asPuzzleBox = insertSprite<AsScene1402PuzzleBox>(this, 0); + } else if (which == 1) { + // Klaymen entering from the left + insertKlaymen<KmScene1402>(42, 391); + setMessageList(0x004B0B50); + } else if (which == 2) { + // Klaymen returning from the puzzle box + insertKlaymen<KmScene1402>(377, 391); + setMessageList(0x004B0B60); + _klaymen->setDoDeltaX(1); + if (getGlobalVar(V_MOUSE_PUZZLE_SOLVED)) { + _asPuzzleBox = insertSprite<AsScene1402PuzzleBox>(this, 1); + clearRectList(); + showMouse(false); + startShaking(); + } else + _asPuzzleBox = insertSprite<AsScene1402PuzzleBox>(this, 0); + } else { + // Klaymen entering from the right + insertKlaymen<KmScene1402>(513, 391); + setMessageList(0x004B0B58); + if (!getGlobalVar(V_MOUSE_PUZZLE_SOLVED)) { + _asPuzzleBox = insertSprite<AsScene1402PuzzleBox>(this, 2); + startShaking(); + } + } + + if (_asPuzzleBox) + _asPuzzleBox->setClipRect(0, 0, 640, _ssBridgePart3->getDrawRect().y2()); + + if (getGlobalVar(V_PROJECTOR_LOCATION) == 1) { + _asProjector = insertSprite<AsCommonProjector>(this, _klaymen, (Sprite*)NULL); + addCollisionSprite(_asProjector); + if (getGlobalVar(V_PROJECTOR_SLOT) == 4) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + _klaymen->setX(_asProjector->getX() + 100); + _klaymen->updateBounds(); + setMessageList(0x004B0BD0); + } else if (getGlobalVar(V_PROJECTOR_SLOT) == 0) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + _klaymen->setX(_asProjector->getX() - 100); + _klaymen->updateBounds(); + setMessageList(0x004B0BD0); + } + _asProjector->setClipRect(_ssBridgePart1->getDrawRect().x, 0, _ssBridgePart2->getDrawRect().x, _ssBridgePart3->getDrawRect().y2()); + } + + _klaymen->setClipRect(_ssBridgePart1->getDrawRect().x, 0, _ssBridgePart2->getDrawRect().x2(), _ssBridgePart3->getDrawRect().y2()); + +} + +void Scene1402::upShaking() { + if (_isShaking) { + setBackgroundY(_vm->_rnd->getRandomNumber(10 - 1) - 10); + _vm->_screen->setYOffset(-10 - getBackgroundY()); + } else { + setBackgroundY(-10); + _vm->_screen->setYOffset(0); + SetUpdateHandler(&Scene::update); + } + Scene::update(); + if (_asPuzzleBox) + _asPuzzleBox->setClipRect(0, 0, 640, _ssBridgePart3->getDrawRect().y2()); + _klaymen->setClipRect(_ssBridgePart1->getDrawRect().x, 0, _ssBridgePart2->getDrawRect().x2(), _ssBridgePart3->getDrawRect().y2()); +} + +uint32 Scene1402::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x00F43389) { + if (getGlobalVar(V_MOUSE_PUZZLE_SOLVED)) + leaveScene(0); + else { + clearRectList(); + _klaymen->setVisible(false); + showMouse(false); + sendMessage(_asPuzzleBox, 0x2002, 0); + startShaking(); + } + } + break; + case 0x1019: + if (param.asInteger()) + leaveScene(0); + else + leaveScene(1); + break; + case 0x2000: + stopShaking(); + showMouse(true); + setRectList(0x004B0C48); + break; + case 0x2001: + stopShaking(); + leaveScene(0); + break; + case 0x2003: + stopShaking(); + break; + case 0x4826: + if (sender == _asProjector) { + if (sendMessage(_asProjector, 0x480C, _klaymen->getX() > _asProjector->getX() ? 1 : 0) != 0) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + setMessageList2(0x004B0BB8); + } else + setMessageList2(0x004B0B68); + } + break; + } + return 0; +} + +void Scene1402::startShaking() { + _isShaking = true; + SetUpdateHandler(&Scene1402::upShaking); +} + +void Scene1402::stopShaking() { + _isShaking = false; +} + +// Scene1407 + +static const int16 kScene1407MouseFloorY[] = { + 106, 150, 191, 230, 267, 308, 350, 395 +}; + +static const struct { + int16 x; + int16 floorIndex; + int16 sectionIndex; + int16 nextHoleIndex; +} kScene1407MouseHoles[] = { + {125, 0, 0, 7}, + {452, 7, 21, 0}, + {337, 4, 11, 4}, + {286, 6, 17, 6}, + {348, 6, 17, 39}, + {536, 6, 18, 42}, + {111, 1, 3, 18}, + {203, 1, 3, 38}, + {270, 1, 3, 9}, + {197, 5, 14, 3}, + {252, 5, 14, 35}, + {297, 5, 14, 7}, + {359, 5, 14, 8}, + {422, 4, 12, 26}, + {467, 4, 12, 2}, + {539, 4, 12, 40}, + {111, 5, 13, 17}, + {211, 0, 1, 20}, + {258, 0, 1, 11}, + {322, 0, 1, 16}, + { 99, 6, 16, 31}, + {142, 6, 16, 27}, + {194, 6, 16, 12}, + {205, 2, 6, 45}, + {264, 2, 6, 10}, + { 98, 4, 10, 2}, + {152, 4, 10, 37}, + {199, 4, 10, 13}, + {258, 4, 10, 16}, + {100, 7, 19, 43}, + {168, 7, 19, 23}, + {123, 3, 8, 14}, + {181, 3, 8, 39}, + {230, 3, 8, 28}, + {292, 3, 8, 22}, + {358, 3, 8, 36}, + {505, 3, 9, 44}, + {400, 2, 7, 34}, + {454, 2, 7, 32}, + {532, 2, 7, 46}, + {484, 5, 15, 25}, + {529, 5, 15, 30}, + {251, 7, 20, 48}, + {303, 7, 20, 21}, + {360, 7, 20, 33}, + {503, 0, 2, 5}, + {459, 1, 4, 19}, + {530, 1, 4, 42}, + {111, 2, 5, 47}, + {442, 6, 18, 1} +}; + +static const struct { + int16 x1, x2; + int16 goodHoleIndex; +} kScene1407MouseSections[] = { + {100, 149, 0}, + {182, 351, 17}, + {430, 524, 45}, + { 89, 293, 7}, + {407, 555, 47}, + { 89, 132, 48}, + {178, 303, 23}, + {367, 551, 38}, + {105, 398, 31}, + {480, 537, 36}, + { 84, 275, 27}, + {318, 359, 2}, + {402, 560, 15}, + { 91, 132, 16}, + {179, 400, 10}, + {461, 552, 41}, + { 86, 218, 21}, + {267, 376, 4}, + {420, 560, 49}, + { 77, 188, 30}, + {237, 394, 44}, + {438, 515, 5} +}; + +AsScene1407Mouse::AsScene1407Mouse(NeverhoodEngine *vm, Scene *parentScene) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _currSectionIndex(0) { + + createSurface(100, 117, 45); + _x = 108; + _y = 106; + stIdleLookAtGoodHole(); + SetUpdateHandler(&AnimatedSprite::update); +} + +void AsScene1407Mouse::suWalkTo() { + int16 xdelta = _walkDestX - _x; + if (xdelta > _deltaX) + xdelta = _deltaX; + else if (xdelta < -_deltaX) + xdelta = -_deltaX; + _deltaX = 0; + if (_walkDestX == _x) + sendMessage(this, 0x1019, 0); + else { + _x += xdelta; + updateBounds(); + } +} + +void AsScene1407Mouse::upGoThroughHole() { + if (_countdown != 0 && (--_countdown == 0)) { + SetUpdateHandler(&AnimatedSprite::update); + gotoNextState(); + } + AnimatedSprite::update(); +} + +uint32 AsScene1407Mouse::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + { + int16 mouseX = param.asPoint().x; + int16 mouseY = param.asPoint().y; + int holeIndex; + for (holeIndex = 0; holeIndex < 50; holeIndex++) { + int16 holeX = kScene1407MouseHoles[holeIndex].x; + int16 holeY = kScene1407MouseFloorY[kScene1407MouseHoles[holeIndex].floorIndex]; + if (mouseX >= holeX - 14 && mouseX <= holeX + 14 && mouseY >= holeY - 36 && mouseY <= holeY) + break; + } + if (holeIndex < 50 && kScene1407MouseHoles[holeIndex].sectionIndex == _currSectionIndex) { + _nextHoleIndex = kScene1407MouseHoles[holeIndex].nextHoleIndex; + _walkDestX = kScene1407MouseHoles[holeIndex].x; + stWalkToHole(); + } else { + if (mouseX < kScene1407MouseSections[_currSectionIndex].x1) + _walkDestX = kScene1407MouseSections[_currSectionIndex].x1; + else if (mouseX > kScene1407MouseSections[_currSectionIndex].x2) + _walkDestX = kScene1407MouseSections[_currSectionIndex].x2; + else + _walkDestX = mouseX; + stWalkToDest(); + } + } + break; + case 0x1019: + gotoNextState(); + break; + case 0x2001: + { + // Reset the position + // Find the nearest hole and go through it, and exit at the first hole + int16 distance = 640; + int matchIndex = 50; + for (int index = 0; index < 50; index++) + if (kScene1407MouseHoles[index].sectionIndex == _currSectionIndex && + ABS(kScene1407MouseHoles[index].x - _x) < distance) { + matchIndex = index; + distance = ABS(kScene1407MouseHoles[index].x - _x); + } + if (matchIndex < 50) { + _nextHoleIndex = 0; + _walkDestX = kScene1407MouseHoles[matchIndex].x; + stWalkToHole(); + } + } + break; + } + return messageResult; +} + +void AsScene1407Mouse::stIdleLookAtGoodHole() { + setDoDeltaX(kScene1407MouseHoles[kScene1407MouseSections[_currSectionIndex].goodHoleIndex].x < _x ? 1 : 0); + startAnimation(0x72215194, 0, -1); + SetMessageHandler(&AsScene1407Mouse::handleMessage); + SetSpriteUpdate(NULL); +} + +void AsScene1407Mouse::stWalkToDest() { + if (_walkDestX != _x) { + setDoDeltaX(_walkDestX < _x ? 1 : 0); + startAnimation(0x22291510, 0, -1); + SetMessageHandler(&AsScene1407Mouse::handleMessage); + SetSpriteUpdate(&AsScene1407Mouse::suWalkTo); + NextState(&AsScene1407Mouse::stIdleLookAtGoodHole); + } +} + +void AsScene1407Mouse::stWalkToHole() { + setDoDeltaX(_walkDestX < _x ? 1 : 0); + startAnimation(0x22291510, 0, -1); + SetMessageHandler(&AsScene1407Mouse::handleMessage); + SetSpriteUpdate(&AsScene1407Mouse::suWalkTo); + NextState(&AsScene1407Mouse::stGoThroughHole); +} + +void AsScene1407Mouse::stGoThroughHole() { + startAnimation(0x72215194, 0, -1); + setVisible(false); + _countdown = 12; + SetUpdateHandler(&AsScene1407Mouse::upGoThroughHole); + SetMessageHandler(NULL); + SetSpriteUpdate(NULL); + NextState(&AsScene1407Mouse::stArriveAtHole); +} + +void AsScene1407Mouse::stArriveAtHole() { + _currSectionIndex = kScene1407MouseHoles[_nextHoleIndex].sectionIndex; + _x = kScene1407MouseHoles[_nextHoleIndex].x; + _y = kScene1407MouseFloorY[kScene1407MouseHoles[_nextHoleIndex].floorIndex]; + if (_nextHoleIndex == 1) { + sendMessage(_parentScene, 0x2000, 0); + _walkDestX = 512; + stWalkToDest(); + setVisible(true); + } else { + _walkDestX = _x + 14; + stWalkToDest(); + setVisible(true); + } +} + +Scene1407::Scene1407(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule), _puzzleSolvedCountdown(0), _resetButtonCountdown(0) { + + SetMessageHandler(&Scene1407::handleMessage); + SetUpdateHandler(&Scene1407::update); + + setBackground(0x00442225); + setPalette(0x00442225); + insertPuzzleMouse(0x4222100C, 20, 620); + + _asMouse = insertSprite<AsScene1407Mouse>(this); + _ssResetButton = insertStaticSprite(0x12006600, 100); + _ssResetButton->setVisible(false); + +} + +void Scene1407::update() { + Scene::update(); + if (_puzzleSolvedCountdown != 0 && (--_puzzleSolvedCountdown == 0)) + leaveScene(1); + else if (_resetButtonCountdown != 0 && (--_resetButtonCountdown == 0)) + _ssResetButton->setVisible(false); +} + +uint32 Scene1407::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (_puzzleSolvedCountdown == 0) { + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) { + // Exit scene + leaveScene(0); + } else if (param.asPoint().x >= 75 && param.asPoint().x <= 104 && + param.asPoint().y >= 62 && param.asPoint().y <= 90) { + // The reset button was clicked + sendMessage(_asMouse, 0x2001, 0); + _ssResetButton->setVisible(true); + playSound(0, 0x44045000); + _resetButtonCountdown = 12; + } else { + // Handle the mouse + sendMessage(_asMouse, messageNum, param); + } + } + break; + case 0x2000: + // The mouse got the cheese (nomnom) + setGlobalVar(V_MOUSE_PUZZLE_SOLVED, 1); + playSound(0, 0x68E25540); + showMouse(false); + _puzzleSolvedCountdown = 72; + break; + } + return 0; +} + +// Scene1403 + +Scene1403::Scene1403(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _asProjector(NULL), _isProjecting(false) { + + SetMessageHandler(&Scene1403::handleMessage); + + setRectList(0x004B1FF8); + setBackground(0x2110A234); + setPalette(0x2110A234); + insertScreenMouse(0x0A230219); + + _sprite1 = insertStaticSprite(0x01102A33, 100); + _sprite1->setVisible(false); + _sprite2 = insertStaticSprite(0x04442520, 995); + _sprite3 = insertStaticSprite(0x08742271, 995); + _asTape1 = insertSprite<AsScene1201Tape>(this, 12, 1100, 201, 468, 0x9148A011); + addCollisionSprite(_asTape1); + _asTape1->setRepl(64, 0); + _asTape2 = insertSprite<AsScene1201Tape>(this, 16, 1100, 498, 468, 0x9048A093); + addCollisionSprite(_asTape2); + _asTape2->setRepl(64, 0); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene1403>(380, 463); + setMessageList(0x004B1F18); + } else { + // Klaymen entering from the right + insertKlaymen<KmScene1403>(640, 463); + setMessageList(0x004B1F20); + } + _klaymen->setRepl(64, 0); + + if (getGlobalVar(V_PROJECTOR_LOCATION) == 0) { + _asProjector = insertSprite<AsCommonProjector>(this, _klaymen, (Sprite*)NULL); + addCollisionSprite(_asProjector); + if (getGlobalVar(V_PROJECTOR_SLOT) == 4) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + _klaymen->setX(_asProjector->getX() + 100); + _klaymen->updateBounds(); + setMessageList(0x004B1F70); + } + _asProjector->setClipRect(0, 0, 640, _sprite2->getDrawRect().y2()); + _asProjector->setRepl(64, 0); + } + +} + +uint32 Scene1403::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x88C11390) { + setRectList(0x004B2008); + _isProjecting = true; + } else if (param.asInteger() == 0x08821382) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + setRectList(0x004B1FF8); + _isProjecting = false; + } + break; + case 0x1019: + leaveScene(0); + break; + case 0x1022: + if (sender == _asProjector) { + if (param.asInteger() >= 1000) + setSurfacePriority(_sprite3->getSurface(), 1100); + else + setSurfacePriority(_sprite3->getSurface(), 995); + } + break; + case 0x4807: + _sprite1->setVisible(false); + break; + case 0x480F: + _sprite1->setVisible(true); + break; + case 0x4826: + if (sender == _asProjector) { + if (_isProjecting) + setMessageList2(0x004B1FA8); + else if (param.asInteger() == 1) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + setMessageList2(0x004B1F88); + } else if (sendMessage(_asProjector, 0x480C, _klaymen->getX() > _asProjector->getX() ? 1 : 0) != 0) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + setMessageList2(0x004B1F58); + } else + setMessageList2(0x004B1F28); + } else if (sender == _asTape1 || sender == _asTape2) { + if (_isProjecting) + setMessageList2(0x004B1FA8); + else if (_messageListStatus != 2) { + sendEntityMessage(_klaymen, 0x1014, sender); + setMessageList2(0x004B1FB8); + } + } + break; + } + return 0; +} + +// Scene1404 + +Scene1404::Scene1404(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _asProjector(NULL), _asKey(NULL) { + + if (getGlobalVar(V_HAS_FINAL_KEY) && getGlobalVar(V_KEY3_LOCATION) == 0) + setGlobalVar(V_KEY3_LOCATION, 5); + + SetMessageHandler(&Scene1404::handleMessage); + + setRectList(0x004B8D80); + setBackground(0xAC0B006F); + setPalette(0xAC0B006F); + _palette->addPalette(0x00801510, 0, 65, 0); + insertScreenMouse(0xB006BAC8); + + if (getGlobalVar(V_KEY3_LOCATION) == 5) { + _asKey = insertSprite<AsCommonKey>(this, 2, 1100, 267, 411); + addCollisionSprite(_asKey); + } + + _sprite1 = insertStaticSprite(0x1900A1F8, 1100); + _asTape = insertSprite<AsScene1201Tape>(this, 14, 1100, 281, 411, 0x9148A011); + addCollisionSprite(_asTape); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene1404>(376, 406); + setMessageList(0x004B8C28); + } else if (which == 1) { + // Klaymen returning from the tiles puzzle + insertKlaymen<KmScene1404>(376, 406); + setMessageList(0x004B8C30); + } else if (which == 2) { + // Klaymen returning from the diskplayer + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) { + insertKlaymen<KmScene1404>(347, 406); + _klaymen->setDoDeltaX(1); + } else { + insertKlaymen<KmScene1404>(187, 406); + } + setMessageList(0x004B8D28); + } else { + // Klaymen entering from the left + insertKlaymen<KmScene1404>(30, 406); + setMessageList(0x004B8C38); + } + + if (getGlobalVar(V_PROJECTOR_LOCATION) == 3) { + _asProjector = insertSprite<AsCommonProjector>(this, _klaymen, (Sprite*)NULL); + addCollisionSprite(_asProjector); + if (getGlobalVar(V_PROJECTOR_SLOT) == 0) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + _klaymen->setX(_asProjector->getX() - 100); + _klaymen->updateBounds(); + setMessageList(0x004B8CB8); + } + _asProjector->setClipRect(_sprite1->getDrawRect().x, 0, 640, 480); + } + + _klaymen->setClipRect(_sprite1->getDrawRect().x, 0, 640, 480); + +} + +Scene1404::~Scene1404() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX() ? 1 : 0); +} + +uint32 Scene1404::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x410650C2) { + if (_asProjector && _asProjector->getX() == 220) + setMessageList(0x004B8C40); + else + setMessageList(0x004B8CE8); + } + break; + case 0x1019: + leaveScene(0); + break; + case 0x4826: + if (sender == _asProjector) { + if (sendMessage(_asProjector, 0x480C, _klaymen->getX() > _asProjector->getX() ? 1 : 0) != 0) { + sendEntityMessage(_klaymen, 0x1014, _asProjector); + setMessageList2(0x004B8CA0); + } else + setMessageList2(0x004B8C40); + } else if (sender == _asTape && _messageListStatus != 2) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList(0x004B8CD0); + } else if (sender == _asKey && _messageListStatus != 2) { + sendEntityMessage(_klaymen, 0x1014, _asKey); + setMessageList(0x004B8D18); + } + break; + } + return 0; +} + +// Scene1405 + +static const NPoint kAsScene1405TileItemPositions[] = { + {100, 80}, {162, 78}, {222, 76}, {292, 76}, + {356, 82}, {422, 84}, {488, 86}, {550, 90}, + {102, 134}, {164, 132}, {224, 136}, {294, 136}, + {360, 136}, {422, 138}, {484, 144}, {548, 146}, + { 98, 196}, {160, 200}, {228, 200}, {294, 202}, + {360, 198}, {424, 200}, {482, 202}, {548, 206}, + { 98, 260}, {160, 264}, {226, 260}, {296, 262}, + {358, 260}, {424, 262}, {486, 264}, {550, 266}, + { 94, 322}, {160, 316}, {226, 316}, {296, 320}, + {358, 322}, {422, 324}, {488, 322}, {550, 322}, + { 98, 380}, {160, 376}, {226, 376}, {294, 378}, + {356, 380}, {420, 380}, {490, 378}, {552, 376} +}; + +AsScene1405Tile::AsScene1405Tile(NeverhoodEngine *vm, Scene1405 *parentScene, uint32 tileIndex) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _tileIndex(tileIndex), _countdown(0), _isShowing(false) { + + loadSound(0, 0x05308101); + setSoundPan(0, (tileIndex % 8 * 4 + 4) * 25 / 8); + _x = kAsScene1405TileItemPositions[_tileIndex].x; + _y = kAsScene1405TileItemPositions[_tileIndex].y; + createSurface1(0x844B805C, 1100); + setVisible(false); + if (getSubVar(VA_IS_TILE_MATCH, _tileIndex)) + _countdown = _vm->_rnd->getRandomNumber(36 - 1) + 1; + startAnimation(0x844B805C, getSubVar(VA_TILE_SYMBOLS, _tileIndex), -1); + _newStickFrameIndex = (int16)getSubVar(VA_TILE_SYMBOLS, _tileIndex); + SetUpdateHandler(&AsScene1405Tile::update); + SetMessageHandler(&AsScene1405Tile::handleMessage); +} + +void AsScene1405Tile::update() { + updateAnim(); + updatePosition(); + if (_countdown != 0 && (--_countdown == 0)) + show(); +} + +uint32 AsScene1405Tile::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (getSubVar(VA_IS_TILE_MATCH, _tileIndex) == 0 && _parentScene->getCountdown() == 0) { + show(); + sendMessage(_parentScene, 0x2000, _tileIndex); + } + messageResult = 1; + break; + } + return messageResult; +} + +void AsScene1405Tile::show() { + if (!_isShowing) { + _isShowing = true; + playSound(0); + setVisible(true); + } +} + +void AsScene1405Tile::hide() { + if (_isShowing) { + _isShowing = false; + playSound(0); + setVisible(false); + } +} + +Scene1405::Scene1405(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule), _selectFirstTile(true), _tilesLeft(48), _countdown(0) { + + _vm->gameModule()->initMemoryPuzzle(); + + SetUpdateHandler(&Scene1405::update); + SetMessageHandler(&Scene1405::handleMessage); + + setBackground(0x0C0C007D); + setPalette(0x0C0C007D); + insertPuzzleMouse(0xC00790C8, 20, 620); + + for (uint32 tileIndex = 0; tileIndex < 48; tileIndex++) { + _tiles[tileIndex] = insertSprite<AsScene1405Tile>(this, tileIndex); + addCollisionSprite(_tiles[tileIndex]); + if (getSubVar(VA_IS_TILE_MATCH, tileIndex)) + _tilesLeft--; + } + + loadSound(0, 0x68E25540); +} + +void Scene1405::update() { + Scene::update(); + if (_countdown != 0 && (--_countdown == 0)) { + _tilesLeft = 48; + _tiles[_firstTileIndex]->hide(); + _tiles[_secondTileIndex]->hide(); + for (uint32 i = 0; i < 48; i++) { + if (getSubVar(VA_IS_TILE_MATCH, i)) { + _tiles[i]->hide(); + setSubVar(VA_IS_TILE_MATCH, i, 0); + } + } + } +} + +uint32 Scene1405::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) + leaveScene(0); + break; + case 0x2000: + if (_selectFirstTile) { + _firstTileIndex = param.asInteger(); + _selectFirstTile = false; + } else { + _secondTileIndex = param.asInteger(); + if (_firstTileIndex != _secondTileIndex) { + _selectFirstTile = true; + if (getSubVar(VA_TILE_SYMBOLS, _secondTileIndex) == getSubVar(VA_TILE_SYMBOLS, _firstTileIndex)) { + setSubVar(VA_IS_TILE_MATCH, _firstTileIndex, 1); + setSubVar(VA_IS_TILE_MATCH, _secondTileIndex, 1); + _tilesLeft -= 2; + if (_tilesLeft == 0) + playSound(0); + } else + _countdown = 10; + } + } + break; + } + return 0; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module1400.h b/engines/neverhood/modules/module1400.h new file mode 100644 index 0000000000..9a592c2952 --- /dev/null +++ b/engines/neverhood/modules/module1400.h @@ -0,0 +1,281 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_MODULES_MODULE1400_H +#define NEVERHOOD_MODULES_MODULE1400_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" +#include "neverhood/modules/module1200.h" + +namespace Neverhood { + +class Module1400 : public Module { +public: + Module1400(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module1400(); +protected: + int _sceneNum; + void createScene(int sceneNum, int which); + void updateScene(); +}; + +// Scene1401 + +class AsScene1401Pipe : public AnimatedSprite { +public: + AsScene1401Pipe(NeverhoodEngine *vm); + virtual ~AsScene1401Pipe(); +protected: + int _countdown1; + int _countdown2; + void update(); + void upSuckInProjector(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmSuckInProjector(int messageNum, const MessageParam ¶m, Entity *sender); + void stStartSucking(); + void stDoneSucking(); + void stSuckInProjector(); +}; + +class AsScene1401Mouse : public AnimatedSprite { +public: + AsScene1401Mouse(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void suSuckedIn(); + void stSuckedIn(); +}; + +class AsScene1401Cheese : public AnimatedSprite { +public: + AsScene1401Cheese(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void suSuckedIn(); + void stSuckedIn(); +}; + +class AsScene1401BackDoor : public AnimatedSprite { +public: + AsScene1401BackDoor(NeverhoodEngine *vm, Sprite *klaymen, bool isOpen); +protected: + Sprite *_klaymen; + int _countdown; + bool _isOpen; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stOpenDoor(); + void stCloseDoor(); + void stCloseDoorDone(); +}; + +struct AsCommonProjectorItem { + NPoint point; + int8 maxSlotCount; + int8 lockSlotIndex; + int8 index1; + int8 leftBorderLeaves; + int8 rightBorderLeaves; +}; + +class AsCommonProjector : public AnimatedSprite { +public: + AsCommonProjector(NeverhoodEngine *vm, Scene *parentScene, Sprite *klaymen, Sprite *asPipe); + virtual ~AsCommonProjector(); +protected: + Scene *_parentScene; + Sprite *_klaymen; + Sprite *_asPipe; + const AsCommonProjectorItem *_asProjectorItem; + int16 _beforeMoveX; + bool _lockedInSlot; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmLockedInSlot(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmAnimation(int messageNum, const MessageParam ¶m, Entity *sender); + void suMoving(); + void moveProjector(); + void stSuckedIn(); + void stIdle(); + void stMoving(); + void stStartLockedInSlot(); + void stStayLockedInSlot(); + void stStartProjecting(); + void stLockedInSlot(); + void stStopProjecting(); + void stTurnToFront(); + void stStartSuckedIn(); +}; + +class Scene1401 : public Scene { +public: + Scene1401(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + bool _projectorBorderFlag; + Sprite *_ssFloorButton; + AsCommonProjector *_asProjector; + Sprite *_asPipe; + Sprite *_asMouse; + Sprite *_asCheese; + Sprite *_asBackDoor; + Sprite *_sprite1; + Sprite *_sprite2; + Sprite *_sprite3; + Sprite *_ssButton; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +// Scene1402 + +class SsScene1402BridgePart : public StaticSprite { +public: + SsScene1402BridgePart(NeverhoodEngine *vm, uint32 fileHash, int surfacePriority); +}; + +class AsScene1402PuzzleBox : public AnimatedSprite { +public: + AsScene1402PuzzleBox(NeverhoodEngine *vm, Scene *parentScene, int status); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stMoveUpDone(); + void stMoveDownDone(); + void stMoveDownSolvedDone(); +}; + +class Scene1402 : public Scene { +public: + Scene1402(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_ssBridgePart1; + Sprite *_ssBridgePart2; + Sprite *_ssBridgePart3; + Sprite *_asPuzzleBox; + AsCommonProjector *_asProjector; + bool _isShaking; + void upShaking(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void startShaking(); + void stopShaking(); +}; + +// Scene1407 + +class AsScene1407Mouse : public AnimatedSprite { +public: + AsScene1407Mouse(NeverhoodEngine *vm, Scene *parentScene); +protected: + Scene *_parentScene; + int16 _walkDestX; + int16 _currSectionIndex; + int16 _nextHoleIndex; + int _countdown; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void suWalkTo(); + void upGoThroughHole(); + void stIdleLookAtGoodHole(); + void stWalkToDest(); + void stWalkToHole(); + void stGoThroughHole(); + void stArriveAtHole(); +}; + +class Scene1407 : public Scene { +public: + Scene1407(NeverhoodEngine *vm, Module *parentModule); +protected: + Sprite *_asMouse; + Sprite *_ssResetButton; + int _puzzleSolvedCountdown; + int _resetButtonCountdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +// Scene1403 + +class Scene1403 : public Scene { +public: + Scene1403(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_sprite1; + Sprite *_sprite2; + Sprite *_sprite3; + AsScene1201Tape *_asTape1; + AsScene1201Tape *_asTape2; + AsCommonProjector *_asProjector; + bool _isProjecting; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +// Scene1404 + +class Scene1404 : public Scene { +public: + Scene1404(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Scene1404(); +protected: + Sprite *_sprite1; + Sprite *_asTape; + AsCommonProjector *_asProjector; + Sprite *_asKey; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +// Scene1405 + +class Scene1405; + +class AsScene1405Tile : public AnimatedSprite { +public: + AsScene1405Tile(NeverhoodEngine *vm, Scene1405 *parentScene, uint32 tileIndex); + void show(); + void hide(); +protected: + Scene1405 *_parentScene; + bool _isShowing; + uint32 _tileIndex; + int _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene1405 : public Scene { +public: + Scene1405(NeverhoodEngine *vm, Module *parentModule); + int getCountdown() const { return _countdown; } +protected: + bool _selectFirstTile; + int _firstTileIndex; + int _secondTileIndex; + int _tilesLeft; + int _countdown; + AsScene1405Tile *_tiles[48]; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE1400_H */ diff --git a/engines/neverhood/modules/module1500.cpp b/engines/neverhood/modules/module1500.cpp new file mode 100644 index 0000000000..2a9597b1fd --- /dev/null +++ b/engines/neverhood/modules/module1500.cpp @@ -0,0 +1,134 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module1500.h" + +namespace Neverhood { + +Module1500::Module1500(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else + createScene(3, -1); + +} + +void Module1500::createScene(int sceneNum, int which) { + debug("Module1500::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _childObject = new Scene1501(_vm, this, 0x8420221D, 0xA61024C4, 150, 48); + break; + case 1: + _vm->gameState().sceneNum = 1; + _childObject = new Scene1501(_vm, this, 0x30050A0A, 0x58B45E58, 110, 48); + break; + case 2: + _vm->gameState().sceneNum = 2; + sendMessage(_parentModule, 0x0800, 0); + createSmackerScene(0x001A0005, true, true, true); + break; + case 3: + _vm->gameState().sceneNum = 3; + _childObject = new Scene1501(_vm, this, 0x0CA04202, 0, 110, 48); + break; + } + SetUpdateHandler(&Module1500::updateScene); + _childObject->handleUpdate(); +} + +void Module1500::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + createScene(1, -1); + break; + case 1: + createScene(2, -1); + break; + case 3: + createScene(0, -1); + break; + default: + leaveModule(0); + break; + } + } +} + +// Scene1501 + +Scene1501::Scene1501(NeverhoodEngine *vm, Module *parentModule, uint32 backgroundFileHash, uint32 soundFileHash, int countdown2, int countdown3) + : Scene(vm, parentModule), _countdown3(countdown3), _countdown2(countdown2), _countdown1(0), _skip(false) { + + SetUpdateHandler(&Scene1501::update); + SetMessageHandler(&Scene1501::handleMessage); + + setBackground(backgroundFileHash); + setPalette(); + addEntity(_palette); + _palette->addBasePalette(backgroundFileHash, 0, 256, 0); + _palette->startFadeToPalette(12); + + if (soundFileHash != 0) + playSound(0, soundFileHash); + +} + +void Scene1501::update() { + Scene::update(); + if (_countdown1 != 0) { + _countdown1--; + if (_countdown1 == 0) { + _vm->_screen->clear(); + leaveScene(0); + } + } else if ((_countdown2 != 0 && (--_countdown2 == 0)) || (_countdown2 == 0 && !isSoundPlaying(0))) { + _countdown1 = 12; + _palette->startFadeToBlack(11); + } + + if (_countdown3 != 0) + _countdown3--; + + if (_countdown3 == 0 && _skip && _countdown1 == 0) { + _countdown1 = 12; + _palette->startFadeToBlack(11); + } + +} + +uint32 Scene1501::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0009: + _skip = true; + break; + } + return messageResult; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module1500.h b/engines/neverhood/modules/module1500.h new file mode 100644 index 0000000000..f244948918 --- /dev/null +++ b/engines/neverhood/modules/module1500.h @@ -0,0 +1,58 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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. + * + */ + +// TODO: I couldn't come up with a better name than 'Module' so far + +#ifndef NEVERHOOD_MODULES_MODULE1500_H +#define NEVERHOOD_MODULES_MODULE1500_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" +#include "neverhood/smackerscene.h" + +namespace Neverhood { + +class Module1500 : public Module { +public: + Module1500(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + int _sceneNum; + void createScene(int sceneNum, int which); + void updateScene(); +}; + +class Scene1501 : public Scene { +public: + Scene1501(NeverhoodEngine *vm, Module *parentModule, uint32 backgroundFileHash, uint32 soundFileHash, int countdown2, int countdown3); +protected: + int _countdown1; + int _countdown2; + int _countdown3; + bool _skip; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE1500_H */ diff --git a/engines/neverhood/modules/module1600.cpp b/engines/neverhood/modules/module1600.cpp new file mode 100644 index 0000000000..f7e3c37d84 --- /dev/null +++ b/engines/neverhood/modules/module1600.cpp @@ -0,0 +1,1412 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module1600.h" +#include "neverhood/gamemodule.h" +#include "neverhood/modules/module1200.h" +#include "neverhood/modules/module2200.h" + +namespace Neverhood { + +static const uint32 kModule1600SoundList[] = { + 0x90805C50, 0x90804450, 0xB4005E60, + 0x91835066, 0x90E14440, 0 +}; + +Module1600::Module1600(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else if (which == 1) + createScene(4, 1); + else if (which == 2) + createScene(5, 0); + else if (which == 3) + createScene(6, 1); + else if (which == 4) + createScene(1, 0); + else + createScene(0, 0); + + _vm->_soundMan->addSoundList(0x1A008D8, kModule1600SoundList); + _vm->_soundMan->setSoundListParams(kModule1600SoundList, true, 50, 600, 5, 150); + _vm->_soundMan->playTwoSounds(0x1A008D8, 0x41861371, 0x43A2507F, 0); + +} + +Module1600::~Module1600() { + _vm->_soundMan->deleteGroup(0x1A008D8); +} + +void Module1600::createScene(int sceneNum, int which) { + debug("Module1600::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + createNavigationScene(0x004B39D0, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + createNavigationScene(0x004B3A30, which); + break; + case 2: + _vm->gameState().sceneNum = 2; + createNavigationScene(0x004B3A60, which); + break; + case 3: + _vm->gameState().sceneNum = 3; + createNavigationScene(0x004B3A90, which); + break; + case 4: + _vm->gameState().sceneNum = 4; + createNavigationScene(0x004B3B20, which); + break; + case 5: + _vm->gameState().sceneNum = 5; + createNavigationScene(0x004B3B50, which); + break; + case 6: + _vm->gameState().sceneNum = 6; + createNavigationScene(0x004B3B80, which); + break; + case 7: + _vm->gameState().sceneNum = 7; + _childObject = new Scene1608(_vm, this, which); + break; + case 8: + _vm->gameState().sceneNum = 8; + _childObject = new Scene1609(_vm, this); + break; + case 1001: + _vm->gameState().sceneNum = 1; + if (getGlobalVar(V_TALK_COUNTING_INDEX) == 1) + createSmackerScene(0x80050200, true, true, false); + else if (getGlobalVar(V_TALK_COUNTING_INDEX) == 2) + createSmackerScene(0x80090200, true, true, false); + else + createSmackerScene(0x80000200, true, true, false); + if (getGlobalVar(V_TALK_COUNTING_INDEX) >= 2) + setGlobalVar(V_TALK_COUNTING_INDEX, 0); + else + incGlobalVar(V_TALK_COUNTING_INDEX, +1); + break; + } + SetUpdateHandler(&Module1600::updateScene); + _childObject->handleUpdate(); +} + +void Module1600::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 0) + createScene(2, 0); + else if (_moduleResult == 1) + createScene(1, 0); + else if (_moduleResult == 2) + leaveModule(4); + break; + case 1: + if (_moduleResult == 0) + createScene(1001, -1); + else if (_moduleResult == 1) + createScene(0, 3); + break; + case 2: + if (_moduleResult == 0) + createScene(3, 0); + else if (_moduleResult == 1) + createScene(0, 2); + break; + case 3: + if (_moduleResult == 0) + createScene(5, 0); + else if (_moduleResult == 2) + createScene(6, 0); + else if (_moduleResult == 3) + createScene(2, 1); + else if (_moduleResult == 4) + createScene(4, 0); + break; + case 4: + if (_moduleResult == 0) + leaveModule(1); + else if (_moduleResult == 1) + createScene(3, 1); + break; + case 5: + if (_moduleResult == 0) + leaveModule(2); + else if (_moduleResult == 1) + createScene(3, 3); + break; + case 6: + if (_moduleResult == 0) + createScene(8, -1); + else if (_moduleResult == 1) + createScene(3, 5); + break; + case 7: + createScene(6, 1); + break; + case 8: + if (_moduleResult == 0) + createScene(6, 0); + else + createScene(7, 0); + break; + case 1001: + createScene(1, 0); + break; + } + } +} + +AsCommonCar::AsCommonCar(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y) + : AnimatedSprite(vm, 1000), _parentScene(parentScene) { + + createSurface(200, 556, 328); + _x = x; + _y = y; + + _inMainArea = false; + _exitDirection = 0; + _currPointIndex = 0; + _hasAgainDestPoint = false; + _stepError = 0; + _hasAgainDestPointIndex = false; + _steps = 0; + _isBraking = false; + _yMoveTotalSteps = 0; + _isBusy = false; + _isIdle = false; + _isMoving = true; + _rectFlag = false; + _newDeltaXType = -1; + _soundCounter = 0; + _pathPoints = NULL; + _currMoveDirection = 0; + + startAnimation(0xD4220027, 0, -1); + setDoDeltaX(getGlobalVar(V_CAR_DELTA_X)); + + SetUpdateHandler(&AsCommonCar::update); + SetMessageHandler(&AsCommonCar::handleMessage); + SetSpriteUpdate(NULL); +} + +AsCommonCar::~AsCommonCar() { + if (_finalizeStateCb == AnimationCallback(&AsCommonCar::evTurnCarDone)) + setGlobalVar(V_CAR_DELTA_X, !getGlobalVar(V_CAR_DELTA_X)); +} + +void AsCommonCar::setPathPoints(NPointArray *pathPoints) { + _pathPoints = pathPoints; +} + +void AsCommonCar::update() { + if (_newDeltaXType >= 0) { + setDoDeltaX(_newDeltaXType); + _newDeltaXType = -1; + } + AnimatedSprite::update(); + if (_hasAgainDestPoint && _yMoveTotalSteps == 0 && !_isBusy) { + _hasAgainDestPoint = false; + _hasAgainDestPointIndex = false; + sendPointMessage(this, 0x2004, _againDestPoint); + } else if (_hasAgainDestPointIndex && _yMoveTotalSteps == 0 && !_isBusy) { + _hasAgainDestPointIndex = false; + sendMessage(this, 0x2003, _againDestPointIndex); + } + updateMovement(); + updateSound(); +} + +void AsCommonCar::upIdle() { + update(); + if (++_idleCounter >= _idleCounterMax) + stIdleBlink(); + updateSound(); +} + +uint32 AsCommonCar::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1019: + SetSpriteUpdate(NULL); + break; + case 0x2002: + // Set the current position without moving + _currPointIndex = param.asInteger(); + _stepError = 0; + _x = pathPoint(_currPointIndex).x; + _y = pathPoint(_currPointIndex).y; + break; + case 0x2003: + // Move to a point by its index + { + int newPointIndex = param.asInteger(); + if (_yMoveTotalSteps <= 0 && !_isBusy) { + _destX = pathPoint(newPointIndex).x; + _destY = pathPoint(newPointIndex).y; + if (_currPointIndex < newPointIndex) { + moveToNextPoint(); + } else if (_currPointIndex == newPointIndex && _stepError == 0) { + if (_currPointIndex == 0) { + _yMoveTotalSteps = 0; + sendMessage(_parentScene, 0x2005, 0); + } else if (_currPointIndex == (int)_pathPoints->size()) { + _yMoveTotalSteps = 0; + sendMessage(_parentScene, 0x2006, 0); + } + } else { + moveToPrevPoint(); + } + } else { + _hasAgainDestPointIndex = true; + _againDestPointIndex = newPointIndex; + } + } + break; + case 0x2004: + // Move to the point closest to the parameter point + { + int minMatchIndex = -1; + int minMatchDistance, distance; + NPoint pt = param.asPoint(); + if (_yMoveTotalSteps <= 0 && !_isBusy) { + // Check if we're already exiting (or something) + if ((pt.x <= 20 && _exitDirection == 1) || + (pt.x >= 620 && _exitDirection == 3) || + (pt.y <= 20 && _exitDirection == 2) || + (pt.y >= 460 && _exitDirection == 4)) + break; + _destX = pt.x; + _destY = pt.y; + minMatchDistance = calcDistance(_destX, _destY, _x, _y) + 1; + for (int i = _currPointIndex + 1; i < (int)_pathPoints->size(); i++) { + distance = calcDistance(_destX, _destY, pathPoint(i).x, pathPoint(i).y); + if (distance >= minMatchDistance) + break; + minMatchDistance = distance; + minMatchIndex = i; + } + for (int i = _currPointIndex; i >= 0; i--) { + distance = calcDistance(_destX, _destY, pathPoint(i).x, pathPoint(i).y); + if (distance >= minMatchDistance) + break; + minMatchDistance = distance; + minMatchIndex = i; + } + if (minMatchIndex == -1) { + if (_currPointIndex == 0) + moveToPrevPoint(); + else + SetSpriteUpdate(NULL); + } else { + if (minMatchIndex > _currPointIndex) + moveToNextPoint(); + else + moveToPrevPoint(); + } + } else { + _hasAgainDestPoint = true; + _againDestPoint = pt; + } + } + break; + case 0x2007: + _yMoveTotalSteps = param.asInteger(); + _steps = 0; + _isBraking = false; + _lastDistance = 640; + SetSpriteUpdate(&AsCommonCar::suMoveToPrevPoint); + break; + case 0x2008: + _yMoveTotalSteps = param.asInteger(); + _steps = 0; + _isBraking = false; + _lastDistance = 640; + SetSpriteUpdate(&AsCommonCar::suMoveToNextPoint); + break; + case 0x2009: + stEnterCar(); + break; + case 0x200A: + stLeaveCar(); + break; + case 0x200E: + stTurnCar(); + break; + case 0x200F: + stCarAtHome(); + _newDeltaXType = param.asInteger(); + break; + } + return messageResult; +} + +uint32 AsCommonCar::hmAnimation(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = AsCommonCar::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (_isBusy && param.asInteger() == 0x025424A2) + gotoNextState(); + break; + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +uint32 AsCommonCar::hmLeaveCar(int messageNum, const MessageParam ¶m, Entity *sender) { + switch (messageNum) { + case 0x2009: + stEnterCar(); + break; + case 0x3002: + sendMessage(_parentScene, 0x200A, 0); + SetMessageHandler(&AsCommonCar::handleMessage); + break; + } + return 0; +} + +void AsCommonCar::stCarAtHome() { + bool doDeltaX = _doDeltaX; + SetSpriteUpdate(NULL); + _hasAgainDestPoint = false; + _hasAgainDestPointIndex = false; + _isBraking = false; + _isBusy = false; + _isIdle = false; + _isMoving = false; + _rectFlag = false; + NextState(&AsCommonCar::stLeanForwardIdle); + startAnimation(0x35698F78, 0, -1); + setDoDeltaX(doDeltaX ? 1 : 0); + _currMoveDirection = 0; + _newMoveDirection = 0; + _steps = 0; + _idleCounter = 0; + _idleCounterMax = _vm->_rnd->getRandomNumber(64 - 1) + 24; + SetUpdateHandler(&AsCommonCar::upIdle); + SetMessageHandler(&AsCommonCar::handleMessage); + FinalizeState(&AsCommonCar::evIdleDone); +} + +void AsCommonCar::updateTurnMovement() { + if (_turnMoveStatus == 1) { + _lastDistance = 640; + _isIdle = false; + _isBraking = false; + SetSpriteUpdate(&AsCommonCar::suMoveToNextPoint); + } else if (_turnMoveStatus == 2) { + _lastDistance = 640; + _isIdle = false; + _isBraking = false; + SetSpriteUpdate(&AsCommonCar::suMoveToPrevPoint); + } +} + +void AsCommonCar::updateMovement() { + if (_isBraking && !_isIdle && !_isBusy) { + gotoNextState(); + _isMoving = false; + _isIdle = true; + startAnimation(0x192ADD30, 0, -1); + SetUpdateHandler(&AsCommonCar::update); + SetMessageHandler(&AsCommonCar::hmAnimation); + NextState(&AsCommonCar::stLeanForwardIdle); + } else if (!_isBraking && _steps && _isIdle) { + gotoNextState(); + _isIdle = false; + startAnimation(0x9966B138, 0, -1); + SetUpdateHandler(&AsCommonCar::update); + SetMessageHandler(&AsCommonCar::hmAnimation); + NextState(&AsCommonCar::stUpdateMoveDirection); + } else if (_newMoveDirection != _currMoveDirection && _isMoving && !_isBusy) { + gotoNextState(); + _currMoveDirection = _newMoveDirection; + stUpdateMoveDirection(); + } +} + +void AsCommonCar::stEnterCar() { + startAnimation(0xA86A9538, 0, -1); + SetUpdateHandler(&AsCommonCar::update); + SetMessageHandler(&AsCommonCar::hmAnimation); + NextState(&AsCommonCar::stLeanForwardIdle); +} + +void AsCommonCar::stLeaveCar() { + startAnimation(0xA86A9538, -1, -1); + _playBackwards = true; + SetUpdateHandler(&AsCommonCar::update); + SetMessageHandler(&AsCommonCar::hmLeaveCar); +} + +void AsCommonCar::stLeanForwardIdle() { + startAnimation(0x35698F78, 0, -1); + _currMoveDirection = 0; + _newMoveDirection = 0; + _steps = 0; + _idleCounter = 0; + _idleCounterMax = _vm->_rnd->getRandomNumber(64 - 1) + 24; + SetUpdateHandler(&AsCommonCar::upIdle); + SetMessageHandler(&AsCommonCar::handleMessage); + FinalizeState(&AsCommonCar::evIdleDone); +} + +void AsCommonCar::evIdleDone() { + SetUpdateHandler(&AsCommonCar::update); +} + +void AsCommonCar::stIdleBlink() { + startAnimation(0xB579A77C, 0, -1); + _idleCounter = 0; + _idleCounterMax = _vm->_rnd->getRandomNumber(64 - 1) + 24; + SetUpdateHandler(&AsCommonCar::update); + SetMessageHandler(&AsCommonCar::hmAnimation); + NextState(&AsCommonCar::stLeanForwardIdle); +} + +void AsCommonCar::stUpdateMoveDirection() { + _isMoving = true; + if (_currMoveDirection == 1) + startAnimation(0xD4AA03A4, 0, -1); + else if (_currMoveDirection == 3) + startAnimation(0xD00A1364, 0, -1); + else if ((_currMoveDirection == 2 && _doDeltaX) || (_currMoveDirection == 4 && !_doDeltaX)) + stTurnCar(); + else + startAnimation(0xD4220027, 0, -1); + setGlobalVar(V_CAR_DELTA_X, _doDeltaX ? 1 : 0); +} + +void AsCommonCar::moveToNextPoint() { + if (_currPointIndex >= (int)_pathPoints->size() - 1) { + _yMoveTotalSteps = 0; + sendMessage(this, 0x1019, 0); + sendMessage(_parentScene, 0x2006, 0); + } else { + NPoint nextPt = pathPoint(_currPointIndex + 1); + NPoint currPt = pathPoint(_currPointIndex); + if (ABS(nextPt.y - currPt.y) <= ABS(nextPt.x - currPt.x) && + ((_currMoveDirection == 2 && nextPt.x < currPt.x) || + (_currMoveDirection == 4 && nextPt.x >= currPt.x))) { + if (_currMoveDirection == 2) + _currMoveDirection = 4; + else if (_currMoveDirection == 4) + _currMoveDirection = 2; + if (_isIdle) + stTurnCarMoveToNextPoint(); + else + stBrakeMoveToNextPoint(); + } else { + if (_steps == 0) { + gotoNextState(); + _isIdle = false; + startAnimation(0x9966B138, 0, -1); + SetMessageHandler(&AsCommonCar::hmAnimation); + SetUpdateHandler(&AsCommonCar::update); + NextState(&AsCommonCar::stUpdateMoveDirection); + } + _isBraking = false; + SetSpriteUpdate(&AsCommonCar::suMoveToNextPoint); + _lastDistance = 640; + } + } +} + +void AsCommonCar::stBrakeMoveToNextPoint() { + gotoNextState(); + _isBusy = true; + _isBraking = true; + startAnimation(0x192ADD30, 0, -1); + SetUpdateHandler(&AsCommonCar::update); + SetMessageHandler(&AsCommonCar::hmAnimation); + NextState(&AsCommonCar::stTurnCarMoveToNextPoint); +} + +void AsCommonCar::stTurnCar() { + // Turn to left/right #1 + gotoNextState(); + _isBusy = true; + startAnimation(0xF46A0324, 0, -1); + SetUpdateHandler(&AsCommonCar::update); + SetMessageHandler(&AsCommonCar::hmAnimation); + FinalizeState(&AsCommonCar::evTurnCarDone); + _turnMoveStatus = 0; + updateTurnMovement(); +} + +void AsCommonCar::stTurnCarMoveToNextPoint() { + // Turn to left/right #2 + gotoNextState(); + _isBusy = true; + startAnimation(0xF46A0324, 0, -1); + SetUpdateHandler(&AsCommonCar::update); + SetMessageHandler(&AsCommonCar::hmAnimation); + FinalizeState(&AsCommonCar::evTurnCarDone); + _turnMoveStatus = 1; + updateTurnMovement(); +} + +void AsCommonCar::stTurnCarMoveToPrevPoint() { + // Turn to left/right #3 + FinalizeState(NULL); + _isBusy = true; + startAnimation(0xF46A0324, 0, -1); + SetUpdateHandler(&AsCommonCar::update); + SetMessageHandler(&AsCommonCar::hmAnimation); + FinalizeState(&AsCommonCar::evTurnCarDone); + _turnMoveStatus = 2; + updateTurnMovement(); +} + +void AsCommonCar::moveToPrevPoint() { + if (_currPointIndex == 0 && _stepError == 0) { + _yMoveTotalSteps = 0; + sendMessage(this, 0x1019, 0); + sendMessage(_parentScene, 0x2005, 0); + } else { + NPoint prevPt; + NPoint currPt; + if (_stepError == 0) { + prevPt = pathPoint(_currPointIndex - 1); + currPt = pathPoint(_currPointIndex); + } else { + prevPt = pathPoint(_currPointIndex); + currPt = pathPoint(_currPointIndex + 1); + } + if (ABS(prevPt.y - currPt.y) <= ABS(prevPt.x - currPt.x) && + ((_currMoveDirection == 2 && prevPt.x < currPt.x) || + (_currMoveDirection == 4 && prevPt.x >= currPt.x))) { + if (_currMoveDirection == 2) + _currMoveDirection = 4; + else if (_currMoveDirection == 4) + _currMoveDirection = 2; + if (_isIdle) + stTurnCarMoveToPrevPoint(); + else + stBrakeMoveToPrevPoint(); + } else { + if (_steps == 0) { + gotoNextState(); + _isIdle = false; + startAnimation(0x9966B138, 0, -1); + SetMessageHandler(&AsCommonCar::hmAnimation); + SetUpdateHandler(&AsCommonCar::update); + NextState(&AsCommonCar::stUpdateMoveDirection); + } + _isBraking = false; + SetSpriteUpdate(&AsCommonCar::suMoveToPrevPoint); + _lastDistance = 640; + } + } +} + +void AsCommonCar::stBrakeMoveToPrevPoint() { + FinalizeState(NULL); + _isBusy = true; + _isBraking = true; + startAnimation(0x192ADD30, 0, -1); + SetUpdateHandler(&AsCommonCar::update); + SetMessageHandler(&AsCommonCar::hmAnimation); + NextState(&AsCommonCar::stTurnCarMoveToPrevPoint); +} + +void AsCommonCar::evTurnCarDone() { + _isBusy = false; + setDoDeltaX(2); + _newMoveDirection = 0; + stUpdateMoveDirection(); +} + +void AsCommonCar::suMoveToNextPoint() { + int16 newX = _x, newY = _y; + + if (_currPointIndex >= (int)_pathPoints->size()) { + _yMoveTotalSteps = 0; + sendMessage(this, 0x1019, 0); + sendMessage(_parentScene, 0x2006, 0); + return; + } + + if (_isBraking) { + if (_steps <= 0) { + sendMessage(this, 0x1019, 0); + return; + } else + _steps--; + } else if (_steps < 11) + _steps++; + + bool firstTime = true; + _ySteps = _steps; + int stepsCtr = _steps; + + while (stepsCtr > 0) { + NPoint pt1; + NPoint pt2 = pathPoint(_currPointIndex); + if (_currPointIndex + 1 >= (int)_pathPoints->size()) + pt1 = pathPoint(0); + else + pt1 = pathPoint(_currPointIndex + 1); + int16 deltaX = ABS(pt1.x - pt2.x); + int16 deltaY = ABS(pt1.y - pt2.y); + if (deltaX >= deltaY) { + _newMoveDirection = 2; + if (pt1.x < pt2.x) + _newMoveDirection = 4; + if (stepsCtr + _stepError >= deltaX) { + stepsCtr -= deltaX; + stepsCtr += _stepError; + _stepError = 0; + _currPointIndex++; + if (_currPointIndex == (int)_pathPoints->size() - 1) + stepsCtr = 0; + newX = pathPoint(_currPointIndex).x; + newY = pathPoint(_currPointIndex).y; + } else { + _stepError += stepsCtr; + if (pt1.x >= pt2.x) + newX += stepsCtr; + else + newX -= stepsCtr; + if (pt1.y >= pt2.y) + newY = pt2.y + (deltaY * _stepError) / deltaX; + else + newY = pt2.y - (deltaY * _stepError) / deltaX; + stepsCtr = 0; + } + } else { + _newMoveDirection = 3; + if (pt1.y < pt2.y) + _newMoveDirection = 1; + if (firstTime) { + if (pt1.y >= pt2.y) + stepsCtr += 7; + else { + stepsCtr -= 4; + if (stepsCtr < 0) + stepsCtr = 0; + } + _ySteps = stepsCtr; + } + if (stepsCtr + _stepError >= deltaY) { + stepsCtr -= deltaY; + stepsCtr += _stepError; + _stepError = 0; + _currPointIndex++; + if (_currPointIndex == (int)_pathPoints->size() - 1) + stepsCtr = 0; + newX = pathPoint(_currPointIndex).x; + newY = pathPoint(_currPointIndex).y; + } else { + _stepError += stepsCtr; + if (pt1.x >= pt2.x) + newX = pt2.x + (deltaX * _stepError) / deltaY; + else + newX = pt2.x - (deltaX * _stepError) / deltaY; + if (pt1.y >= pt2.y) + newY += stepsCtr; + else + newY -= stepsCtr; + stepsCtr = 0; + } + } + firstTime = false; + } + + if (_yMoveTotalSteps != 0) { + _x = newX; + _y = newY; + _yMoveTotalSteps -= _ySteps; + if (_yMoveTotalSteps <= 0) { + _isBraking = true; + _yMoveTotalSteps = 0; + } + } else { + int distance = calcDistance(_destX, _destY, _x, _y); + _x = newX; + _y = newY; + if (newX > 20 && newX < 620 && newY > 20 && newY < 460) { + _exitDirection = 0; + _inMainArea = true; + } else if (_inMainArea) { + _destX = pathPoint(_pathPoints->size() - 1).x; + _destY = pathPoint(_pathPoints->size() - 1).y; + _inMainArea = false; + if (_x <= 20) + _exitDirection = 1; + else if (_x >= 620) + _exitDirection = 3; + else if (_y <= 20) + _exitDirection = 2; + else if (_y >= 460) + _exitDirection = 4; + if (_exitDirection != 0 && _isBraking) { + _isBraking = false; + _steps = 11; + } + } + if ((distance < 20 && _exitDirection == 0 && _lastDistance < distance) || + (_exitDirection == 0 && _lastDistance + 20 < distance)) + _isBraking = true; + if (distance < _lastDistance) + _lastDistance = distance; + if (_currPointIndex == (int)_pathPoints->size() - 1) { + _isBraking = true; + _yMoveTotalSteps = 0; + sendMessage(this, 0x1019, 0); + sendMessage(_parentScene, 0x2006, 0); + } + } + +} + +void AsCommonCar::suMoveToPrevPoint() { + int16 newX = _x, newY = _y; + + if (_currPointIndex == 0 && _stepError == 0) { + _yMoveTotalSteps = 0; + sendMessage(this, 0x1019, 0); + sendMessage(_parentScene, 0x2005, 0); + return; + } + + if (_isBraking) { + if (_steps <= 0) { + sendMessage(this, 0x1019, 0); + return; + } else + _steps--; + } else if (_steps < 11) + _steps++; + + bool firstTime = true; + _ySteps = _steps; + int stepsCtr = _steps; + + while (stepsCtr > 0) { + if (_stepError == 0) + _currPointIndex--; + NPoint pt1; + NPoint pt2 = pathPoint(_currPointIndex); + if (_currPointIndex + 1 >= (int)_pathPoints->size()) + pt1 = pathPoint(0); + else + pt1 = pathPoint(_currPointIndex + 1); + int16 deltaX = ABS(pt1.x - pt2.x); + int16 deltaY = ABS(pt1.y - pt2.y); + if (deltaX >= deltaY) { + _newMoveDirection = 4; + if (pt1.x < pt2.x) + _newMoveDirection = 2; + if (_stepError == 0) + _stepError = deltaX; + if (stepsCtr > _stepError) { + stepsCtr -= _stepError; + _stepError = 0; + if (_currPointIndex == 0) + stepsCtr = 0; + newX = pathPoint(_currPointIndex).x; + newY = pathPoint(_currPointIndex).y; + } else { + _stepError -= stepsCtr; + if (pt1.x >= pt2.x) + newX -= stepsCtr; + else + newX += stepsCtr; + if (pt1.y >= pt2.y) + newY = pt2.y + (deltaY * _stepError) / deltaX; + else + newY = pt2.y - (deltaY * _stepError) / deltaX; + stepsCtr = 0; + } + } else { + _newMoveDirection = 1; + if (pt1.y < pt2.y) + _newMoveDirection = 3; + if (firstTime) { + if (pt1.y >= pt2.y) { + stepsCtr -= 4; + if (stepsCtr < 0) + stepsCtr = 0; + } else { + stepsCtr += 7; + } + _ySteps = stepsCtr; + } + if (_stepError == 0) + _stepError = deltaY; + if (stepsCtr > _stepError) { + stepsCtr -= _stepError; + _stepError = 0; + if (_currPointIndex == 0) + stepsCtr = 0; + newX = pathPoint(_currPointIndex).x; + newY = pathPoint(_currPointIndex).y; + } else { + _stepError -= stepsCtr; + if (pt1.x >= pt2.x) + newX = pt2.x + (deltaX * _stepError) / deltaY; + else + newX = pt2.x - (deltaX * _stepError) / deltaY; + if (pt1.y >= pt2.y) + newY -= stepsCtr; + else + newY += stepsCtr; + stepsCtr = 0; + } + } + firstTime = false; + } + + if (_yMoveTotalSteps != 0) { + _x = newX; + _y = newY; + _yMoveTotalSteps -= _ySteps; + if (_yMoveTotalSteps <= 0) { + _isBraking = true; + _yMoveTotalSteps = 0; + } + } else { + int distance = calcDistance(_destX, _destY, _x, _y); + _x = newX; + _y = newY; + if (newX > 20 && newX < 620 && newY > 20 && newY < 460) { + _exitDirection = 0; + _inMainArea = true; + } else if (_inMainArea) { + _destX = pathPoint(0).x; + _destY = pathPoint(0).y; + _inMainArea = false; + if (_x <= 20) + _exitDirection = 1; + else if (_x >= 620) + _exitDirection = 3; + else if (_y <= 20) + _exitDirection = 2; + else if (_y >= 460) + _exitDirection = 4; + if (_exitDirection != 0 && _isBraking) { + _isBraking = false; + _steps = 11; + } + } + if ((distance < 20 && _exitDirection == 0 && _lastDistance < distance) || + (_exitDirection == 0 && _lastDistance + 20 < distance)) + _isBraking = true; + if (distance < _lastDistance) + _lastDistance = distance; + if (_currPointIndex == 0 && _stepError == 0) { + _isBraking = true; + _yMoveTotalSteps = 0; + sendMessage(this, 0x1019, 0); + sendMessage(_parentScene, 0x2005, 0); + } + } + +} + +void AsCommonCar::updateSound() { + int maxSoundCounter = 0; + _soundCounter++; + if (_steps != 0 && !_isIdle) { + if (_currMoveDirection == 1) + maxSoundCounter = 18 - _steps; + else if (_currMoveDirection == 3) { + maxSoundCounter = 5 - _steps; + if (maxSoundCounter < 1) + maxSoundCounter = 1; + } else + maxSoundCounter = 14 - _steps; + } else + maxSoundCounter = 21; + if (_soundCounter >= maxSoundCounter) { + sendMessage(_parentScene, 0x200D, 0); + _soundCounter = 0; + } +} + +AsCommonIdleCarLower::AsCommonIdleCarLower(NeverhoodEngine *vm, int16 x, int16 y) + : AnimatedSprite(vm, 0x1209E09F, 1100, x, y) { + + setDoDeltaX(1); + startAnimation(0x1209E09F, 1, -1); + _newStickFrameIndex = 1; +} + +AsCommonIdleCarFull::AsCommonIdleCarFull(NeverhoodEngine *vm, int16 x, int16 y) + : AnimatedSprite(vm, 0x1209E09F, 100, x, y) { + + setDoDeltaX(1); + _newStickFrameIndex = 0; +} + +AsCommonCarConnector::AsCommonCarConnector(NeverhoodEngine *vm, AsCommonCar *asCar) + : AnimatedSprite(vm, 1100), _asCar(asCar) { + + createSurface1(0x60281C10, 150); + startAnimation(0x60281C10, -1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + SetUpdateHandler(&AsCommonCarConnector::update); +} + +void AsCommonCarConnector::update() { + _x = _asCar->getX(); + _y = _asCar->getY(); + AnimatedSprite::update(); +} + +void Tracks::findTrackPoint(NPoint pt, int &minMatchTrackIndex, int &minMatchDistance, + DataResource &dataResource) { + const uint trackCount = size(); + minMatchTrackIndex = -1; + minMatchDistance = 640; + for (uint trackIndex = 0; trackIndex < trackCount; trackIndex++) { + NPointArray *pointList = dataResource.getPointArray((*this)[trackIndex]->trackPointsName); + for (uint pointIndex = 0; pointIndex < pointList->size(); pointIndex++) { + NPoint testPt = (*pointList)[pointIndex]; + int distance = calcDistance(testPt.x, testPt.y, pt.x, pt.y); + if (distance < minMatchDistance) { + minMatchTrackIndex = trackIndex; + minMatchDistance = distance; + } + } + } +} + +Scene1608::Scene1608(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _asCar(NULL), _countdown1(0) { + + setGlobalVar(V_CAR_DELTA_X, 1); + + SetMessageHandler(&Scene1608::hmLowerFloor); + + _asKey = insertSprite<AsCommonKey>(this, 1, 1100, 198, 220); + addCollisionSprite(_asKey); + + if (which < 0) { + // Restoring game + if (_vm->gameState().which == 1) + // Klaymen is in the car + which = 1; + else { + // Klaymen is standing around + setRectList(0x004B47D0); + insertKlaymen<KmScene1608>(380, 438); + _kmScene1608 = _klaymen; + _klaymenInCar = false; + _sprite1 = insertStaticSprite(0x7D0404E8, 1100); + setMessageList(0x004B46A8); + setBackground(0x10080E01); + setPalette(0x10080E01); + _asTape = insertSprite<AsScene1201Tape>(this, 13, 1100, 412, 443, 0x9148A011); + addCollisionSprite(_asTape); + _klaymen->setClipRect(_sprite1->getDrawRect().x, 0, 640, 480); + SetUpdateHandler(&Scene1608::upLowerFloor); + insertScreenMouse(0x80E05108); + insertStaticSprite(0x4B18F868, 1200); + } + } else if (which == 0) { + // Klaymen entering from the left + _vm->gameState().which = 0; + setRectList(0x004B47D0); + insertKlaymen<KmScene1608>(0, 438); + _kmScene1608 = _klaymen; + _klaymenInCar = false; + setMessageList(0x004B46B0); + setBackground(0x10080E01); + setPalette(0x10080E01); + _asTape = insertSprite<AsScene1201Tape>(this, 13, 1100, 412, 443, 0x9148A011); + addCollisionSprite(_asTape); + insertScreenMouse(0x80E05108); + _sprite1 = insertStaticSprite(0x7D0404E8, 1100); + _klaymen->setClipRect(_sprite1->getDrawRect().x, 0, 640, 480); + SetUpdateHandler(&Scene1608::upLowerFloor); + insertStaticSprite(0x4B18F868, 1200); + } else if (which == 2) { + // Klaymen returning from looking through the upper window + _vm->gameState().which = 1; + _dataResource.load(0x003C0492); + _roomPathPoints = _dataResource.getPointArray(calcHash("meArchroArchRoomPath")); + setBackground(0x98001604); + setPalette(0x98001604); + _palette->addPalette("paPodRed", 65, 31, 65); + insertScreenMouse(0x01600988); + _sprite2 = insertStaticSprite(0x491F38A8, 1100); + _asCar = createSprite<AsCommonCar>(this, 375, 227); // Create but don't add to the sprite list yet + _asIdleCarLower = insertSprite<AsCommonIdleCarLower>(375, 227); + _asIdleCarFull = insertSprite<AsCommonIdleCarFull>(375, 227); + _asCar->setVisible(false); + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) { + insertKlaymen<KmScene1608>(373, 220); + _klaymen->setDoDeltaX(1); + } else + insertKlaymen<KmScene1608>(283, 220); + _kmScene1608 = _klaymen; + setMessageList(0x004B47A8); + SetMessageHandler(&Scene1608::hmUpperFloor); + SetUpdateHandler(&Scene1608::upUpperFloor); + _asCar->setPathPoints(_roomPathPoints); + sendMessage(_asCar, 0x2002, _roomPathPoints->size() - 1); + _sprite3 = insertStaticSprite(0xB47026B0, 1100); + _clipRect1.set(_sprite3->getDrawRect().x, _sprite3->getDrawRect().y, 640, _sprite2->getDrawRect().y2()); + _clipRect3.set(_sprite2->getDrawRect().x, _sprite3->getDrawRect().y, 640, _sprite2->getDrawRect().y2()); + _clipRect2 = _clipRect1; + _clipRect2.y2 = 215; + _klaymen->setClipRect(_clipRect1); + _asCar->setClipRect(_clipRect1); + _asIdleCarLower->setClipRect(_clipRect1); + _asIdleCarFull->setClipRect(_clipRect1); + _asTape = insertSprite<AsScene1201Tape>(this, 13, 1100, 412, 443, 0x9148A011); + addCollisionSprite(_asTape); + insertSprite<AsCommonCarConnector>(_asCar)->setClipRect(_clipRect1); + _klaymenInCar = false; + _carClipFlag = false; + _carStatus = 0; + setRectList(0x004B4810); + } + + // NOTE: Not in the else because 'which' is set to 1 in the true branch + if (which == 1) { + // Klaymen riding the car + _vm->gameState().which = 1; + _dataResource.load(0x003C0492); + _roomPathPoints = _dataResource.getPointArray(calcHash("meArchroArchRoomPath")); + setBackground(0x98001604); + setPalette(0x98001604); + _palette->addPalette("paPodRed", 65, 31, 65); + insertScreenMouse(0x01600988); + _asCar = insertSprite<AsCommonCar>(this, 375, 227); + _asIdleCarLower = insertSprite<AsCommonIdleCarLower>(375, 227); + _asIdleCarFull = insertSprite<AsCommonIdleCarFull>(375, 227); + _sprite2 = insertStaticSprite(0x491F38A8, 1100); + _kmScene1608 = createSprite<KmScene1608>(this, 439, 220); + sendMessage(_kmScene1608, 0x2032, 1); + _kmScene1608->setDoDeltaX(1); + SetMessageHandler(&Scene1608::hmRidingCar); + SetUpdateHandler(&Scene1608::upRidingCar); + _asIdleCarLower->setVisible(false); + _asIdleCarFull->setVisible(false); + _asCar->setPathPoints(_roomPathPoints); + sendMessage(_asCar, 0x2002, 0); + sendMessage(_asCar, 0x2008, 90); + _sprite3 = insertStaticSprite(0xB47026B0, 1100); + _clipRect1.set(_sprite3->getDrawRect().x, _sprite3->getDrawRect().y, 640, _sprite2->getDrawRect().y2()); + _clipRect3.set(_sprite2->getDrawRect().x, _sprite3->getDrawRect().y, 640, _sprite2->getDrawRect().y2()); + _clipRect2 = _clipRect1; + _clipRect2.y2 = 215; + _kmScene1608->setClipRect(_clipRect1); + _asCar->setClipRect(_clipRect1); + _asIdleCarLower->setClipRect(_clipRect1); + _asIdleCarFull->setClipRect(_clipRect1); + _asTape = insertSprite<AsScene1201Tape>(this, 13, 1100, 412, 443, 0x9148A011); + // ... addCollisionSprite(_asTape); + insertSprite<AsCommonCarConnector>(_asCar)->setClipRect(_clipRect1); + _klaymenInCar = true; + _carClipFlag = true; + _carStatus = 0; + } + + _palette->addPalette("paKlayRed", 0, 64, 0); + +} + +Scene1608::~Scene1608() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _kmScene1608->isDoDeltaX() ? 1 : 0); + if (_klaymenInCar) + delete _kmScene1608; + else + delete _asCar; +} + +void Scene1608::upLowerFloor() { + Scene::update(); + if (_countdown1 != 0 && (--_countdown1 == 0)) + leaveScene(0); +} + +void Scene1608::upUpperFloor() { + Scene::update(); + if (_carStatus == 1) { + removeSurface(_klaymen->getSurface()); + removeEntity(_klaymen); + addSprite(_asCar); + _klaymenInCar = true; + clearRectList(); + SetUpdateHandler(&Scene1608::upCarAtHome); + SetMessageHandler(&Scene1608::hmCarAtHome); + _asIdleCarLower->setVisible(false); + _asIdleCarFull->setVisible(false); + _asCar->setVisible(true); + sendMessage(_asCar, 0x2009, 0); + _asCar->handleUpdate(); + _klaymen = NULL; + _carStatus = 0; + } + updateKlaymenCliprect(); +} + +void Scene1608::upCarAtHome() { + Scene::update(); + if (_mouseClicked) { + if (_mouseClickPos.x <= 329 && _asCar->getX() == 375 && _asCar->getY() == 227) { + sendMessage(_asCar, 0x200A, 0); + SetUpdateHandler(&Scene1608::upGettingOutOfCar); + } else { + sendPointMessage(_asCar, 0x2004, _mouseClickPos); + SetMessageHandler(&Scene1608::hmRidingCar); + SetUpdateHandler(&Scene1608::upRidingCar); + } + _mouseClicked = false; + } + updateKlaymenCliprect(); +} + +void Scene1608::upGettingOutOfCar() { + Scene::update(); + if (_carStatus == 2) { + _klaymen = _kmScene1608; + removeSurface(_asCar->getSurface()); + removeEntity(_asCar); + addSprite(_klaymen); + _klaymenInCar = false; + SetMessageHandler(&Scene1608::hmUpperFloor); + SetUpdateHandler(&Scene1608::upUpperFloor); + setRectList(0x004B4810); + _asIdleCarLower->setVisible(true); + _asIdleCarFull->setVisible(true); + _asCar->setVisible(false); + setMessageList(0x004B4748); + processMessageList(); + _klaymen->handleUpdate(); + _carStatus = 0; + } + updateKlaymenCliprect(); +} + +void Scene1608::upRidingCar() { + Scene::update(); + if (_mouseClicked) { + sendPointMessage(_asCar, 0x2004, _mouseClickPos); + _mouseClicked = false; + } + if (_asCar->getX() < 300) { + if (_carClipFlag) { + _carClipFlag = false; + _asCar->setClipRect(_clipRect1); + if (!_asCar->isDoDeltaX()) + sendMessage(_asCar, 0x200E, 0); + } + } else if (!_carClipFlag) { + _carClipFlag = true; + _asCar->setClipRect(_clipRect3); + } +} + +uint32 Scene1608::hmLowerFloor(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x20250B1A) { + clearRectList(); + _klaymen->setVisible(false); + showMouse(false); + _sprite1->setVisible(false); + //sendMessage(_asDoor, 0x4809, 0); // Play sound? + _countdown1 = 28; + } + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + case 0x4826: + if (sender == _asTape) { + sendEntityMessage(_kmScene1608, 0x1014, _asTape); + setMessageList(0x004B4770); + } else if (sender == _asKey) + setMessageList(0x004B46C8); + break; + } + return 0; +} + +uint32 Scene1608::hmUpperFloor(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x60842040) + _carStatus = 1; + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + case 0x4826: + if (sender == _asKey) { + sendEntityMessage(_kmScene1608, 0x1014, _asKey); + setMessageList(0x004B4760); + } + break; + } + return 0; +} + +uint32 Scene1608::hmRidingCar(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2005: + leaveScene(1); + break; + case 0x2006: + SetMessageHandler(&Scene1608::hmCarAtHome); + SetUpdateHandler(&Scene1608::upCarAtHome); + sendMessage(_asCar, 0x200F, 1); + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + } + return 0; +} + +uint32 Scene1608::hmCarAtHome(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x200A: + _carStatus = 2; + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + } + return 0; +} + +void Scene1608::updateKlaymenCliprect() { + if (_kmScene1608->getX() <= 375) + _kmScene1608->setClipRect(_clipRect1); + else + _kmScene1608->setClipRect(_clipRect2); +} + +Scene1609::Scene1609(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule), _countdown1(1), _currentSymbolIndex(0), _symbolPosition(0), _changeCurrentSymbol(true), _isSolved(false) { + + _vm->gameModule()->initCodeSymbolsPuzzle(); + _noisySymbolIndex = getGlobalVar(V_NOISY_SYMBOL_INDEX); + + SetMessageHandler(&Scene1609::handleMessage); + SetUpdateHandler(&Scene1609::update); + + setBackground(0x92124A14); + setPalette(0x92124A14); + insertPuzzleMouse(0x24A10929, 20, 620); + + for (int symbolPosition = 0; symbolPosition < 12; symbolPosition++) + _asSymbols[symbolPosition] = insertSprite<AsScene3011Symbol>(symbolPosition, false); + + _ssButton = insertSprite<SsScene3011Button>(this, true); + addCollisionSprite(_ssButton); + loadSound(0, 0x68E25540); + +} + +void Scene1609::update() { + if (!_isSolved && _countdown1 != 0 && (--_countdown1 == 0)) { + if (_changeCurrentSymbol) { + _currentSymbolIndex++; + if (_currentSymbolIndex >= 12) + _currentSymbolIndex = 0; + _asSymbols[_symbolPosition]->change(_currentSymbolIndex + 12, _currentSymbolIndex == (int)getSubVar(VA_CODE_SYMBOLS, _noisySymbolIndex)); + _changeCurrentSymbol = false; + _countdown1 = 36; + } else { + _asSymbols[_symbolPosition]->hide(); + _changeCurrentSymbol = true; + _countdown1 = 12; + } + } + if (_isSolved && !isSoundPlaying(0)) + leaveScene(1); + Scene::update(); +} + +uint32 Scene1609::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) + leaveScene(0); + break; + case 0x2000: + if (!_isSolved) { + if (_changeCurrentSymbol) + _asSymbols[_symbolPosition]->change(_currentSymbolIndex + 12, false); + _asSymbols[_symbolPosition]->stopSymbolSound(); + _symbolPosition++; + if (_symbolPosition >= 12) { + if (testVars()) { + playSound(0); + setGlobalVar(V_CODE_SYMBOLS_SOLVED, 1); + _isSolved = true; + } else { + _symbolPosition = 0; + for (int i = 0; i < 12; i++) + _asSymbols[i]->hide(); + } + } + _changeCurrentSymbol = true; + _countdown1 = 1; + } + break; + } + return 0; +} + +bool Scene1609::testVars() { + int cmpSymbolIndex = 0; + + // Find the position of the first symbol + while ((int)getSubVar(VA_CODE_SYMBOLS, cmpSymbolIndex) != _asSymbols[0]->getSymbolIndex()) + cmpSymbolIndex++; + + // Check if the entered symbols match + for (int enteredSymbolIndex = 0; enteredSymbolIndex < 12; enteredSymbolIndex++) { + if ((int)getSubVar(VA_CODE_SYMBOLS, cmpSymbolIndex) != _asSymbols[enteredSymbolIndex]->getSymbolIndex()) + return false; + cmpSymbolIndex++; + if (cmpSymbolIndex >= 12) + cmpSymbolIndex = 0; + } + + return true; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module1600.h b/engines/neverhood/modules/module1600.h new file mode 100644 index 0000000000..0bf44ff7b8 --- /dev/null +++ b/engines/neverhood/modules/module1600.h @@ -0,0 +1,183 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_MODULES_MODULE1600_H +#define NEVERHOOD_MODULES_MODULE1600_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" +#include "neverhood/modules/module3000.h" + +namespace Neverhood { + +// Module1600 + +class Module1600 : public Module { +public: + Module1600(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module1600(); +protected: + int _sceneNum; + void createScene(int sceneNum, int which); + void updateScene(); +}; + +class AsCommonCar : public AnimatedSprite { +public: + AsCommonCar(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y); + ~AsCommonCar(); + void setPathPoints(NPointArray *pathPoints); +protected: + Scene *_parentScene; + NPointArray *_pathPoints; + int _newMoveDirection; + int _currMoveDirection; + int _exitDirection; + int _currPointIndex; + bool _hasAgainDestPoint; + NPoint _againDestPoint; + bool _hasAgainDestPointIndex; + int _againDestPointIndex; + bool _inMainArea; + bool _isBraking; + bool _isBusy; + bool _isIdle; + bool _isMoving; + bool _rectFlag; + int _idleCounter; + int _idleCounterMax; + int _steps; + int _stepError; + int _lastDistance; + int _yMoveTotalSteps; + int _ySteps; + int _newDeltaXType; + int _soundCounter; + int _turnMoveStatus; + int16 _destX, _destY; + NPoint pathPoint(uint index) { return (*_pathPoints)[index]; } + void update(); + void upIdle(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmAnimation(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmLeaveCar(int messageNum, const MessageParam ¶m, Entity *sender); + void stCarAtHome(); + void updateTurnMovement(); + void updateMovement(); + void stEnterCar(); + void stLeaveCar(); + void stLeanForwardIdle(); + void evIdleDone(); + void stIdleBlink(); + void stUpdateMoveDirection(); + void stTurnCar(); + void moveToNextPoint(); + void stBrakeMoveToNextPoint(); + void stTurnCarMoveToNextPoint(); + void moveToPrevPoint(); + void stBrakeMoveToPrevPoint(); + void stTurnCarMoveToPrevPoint(); + void evTurnCarDone(); + void suMoveToNextPoint(); + void suMoveToPrevPoint(); + void updateSound(); +}; + +class AsCommonIdleCarLower : public AnimatedSprite { +public: + AsCommonIdleCarLower(NeverhoodEngine *vm, int16 x, int16 y); +}; + +class AsCommonIdleCarFull : public AnimatedSprite { +public: + AsCommonIdleCarFull(NeverhoodEngine *vm, int16 x, int16 y); +}; + +class AsCommonCarConnector : public AnimatedSprite { +public: + AsCommonCarConnector(NeverhoodEngine *vm, AsCommonCar *asCar); +protected: + AsCommonCar *_asCar; + void update(); +}; + +class Tracks : public Common::Array<TrackInfo*> { +public: + void findTrackPoint(NPoint pt, int &minMatchTrackIndex, int &minMatchDistance, + DataResource &dataResource); +}; + +class Scene1608 : public Scene { +public: + Scene1608(NeverhoodEngine *vm, Module *parentModule, int which); + ~Scene1608(); +protected: + AsCommonCar *_asCar; + Sprite *_asKey; + Sprite *_asIdleCarLower; + Sprite *_asIdleCarFull; + Sprite *_sprite1; + Sprite *_sprite2; + Sprite *_sprite3; + Sprite *_asTape; + Klaymen *_kmScene1608; + NRect _clipRect1; + NRect _clipRect2; + NRect _clipRect3; + int _carStatus; + bool _carClipFlag; + bool _klaymenInCar; + int _countdown1; + NPointArray *_roomPathPoints; + void upLowerFloor(); + void upUpperFloor(); + void upCarAtHome(); + void upGettingOutOfCar(); + void upRidingCar(); + uint32 hmLowerFloor(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmUpperFloor(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmRidingCar(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmCarAtHome(int messageNum, const MessageParam ¶m, Entity *sender); + void updateKlaymenCliprect(); +}; + +class Scene1609 : public Scene { +public: + Scene1609(NeverhoodEngine *vm, Module *parentModule); +protected: + Sprite *_ssButton; + AsScene3011Symbol *_asSymbols[12]; + int _currentSymbolIndex; + int _noisySymbolIndex; + int _symbolPosition; + int _countdown1; + bool _changeCurrentSymbol; + bool _isSolved; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + bool testVars(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE1600_H */ diff --git a/engines/neverhood/modules/module1700.cpp b/engines/neverhood/modules/module1700.cpp new file mode 100644 index 0000000000..3a6d1f80cb --- /dev/null +++ b/engines/neverhood/modules/module1700.cpp @@ -0,0 +1,279 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module1700.h" +#include "neverhood/gamemodule.h" + +namespace Neverhood { + +static const uint32 kModule1700SoundList[] = { + 0xB288D450, + 0x90804450, + 0x99801500, + 0xB288D455, + 0x93825040, + 0 +}; + +Module1700::Module1700(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + _vm->_soundMan->addMusic(0x04212331, 0x31114225); + _vm->_soundMan->addSoundList(0x04212331, kModule1700SoundList); + _vm->_soundMan->setSoundListParams(kModule1700SoundList, true, 50, 600, 5, 150); + _vm->_soundMan->playTwoSounds(0x04212331, 0x41861371, 0x43A2507F, 0); + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else if (which == 0) + createScene(0, -1); + else if (which == 1) + createScene(4, 1); + else + createScene(4, 3); + +} + +Module1700::~Module1700() { + _vm->_soundMan->deleteGroup(0x04212331); +} + +void Module1700::createScene(int sceneNum, int which) { + debug("Module1700::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _vm->_soundMan->setSoundListParams(kModule1700SoundList, false, 0, 0, 0, 0); + createSmackerScene(0x3028A005, true, true, false); + break; + case 1: + _vm->gameState().sceneNum = 1; + createNavigationScene(0x004AE8B8, which); + break; + case 2: + _vm->gameState().sceneNum = 2; + createNavigationScene(0x004AE8E8, which); + break; + case 3: + _vm->gameState().sceneNum = 3; + _vm->_soundMan->setSoundListParams(kModule1700SoundList, false, 0, 0, 0, 0); + createSmackerScene(0x01190041, true, true, false); + break; + case 4: + _vm->gameState().sceneNum = 4; + _vm->_soundMan->setSoundListParams(kModule1700SoundList, false, 0, 0, 0, 0); + _vm->_soundMan->startMusic(0x31114225, 0, 2); + _childObject = new Scene1705(_vm, this, which); + break; + } + SetUpdateHandler(&Module1700::updateScene); + _childObject->handleUpdate(); +} + +void Module1700::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + _vm->_soundMan->setSoundListParams(kModule1700SoundList, true, 0, 0, 0, 0); + createScene(1, 0); + break; + case 1: + if (_moduleResult == 0) + createScene(2, 0); + else if (_moduleResult == 1) + createScene(1, 1); + break; + case 2: + if (_moduleResult == 0) + createScene(3, -1); + else if (_moduleResult == 1) + createScene(1, 1); + else if (_moduleResult == 2) { + if (!isSoundPlaying(0)) { + setSoundVolume(0, 60); + playSound(0, 0x58B45E58); + } + createScene(2, 2); + } + break; + case 3: + createScene(4, 0); + break; + case 4: + leaveModule(1); + break; + } + } +} + +// Scene1705 + +static const uint32 kScene1705FileHashes[] = { + 0x910EA801, 0x920EA801, 0x940EA801, + 0x980EA801, 0x800EA801, 0xB00EA801, + 0xD00EA801, 0x100EA801, 0x900EA800, + 0xD10EA801, 0x110EA801, 0x910EA800 +}; + +SsScene1705WallSymbol::SsScene1705WallSymbol(NeverhoodEngine *vm, uint32 fileHash, int symbolIndex) + : StaticSprite(vm, fileHash, 100) { + + _x = _spriteResource.getPosition().x + symbolIndex * 30; + _y = _spriteResource.getPosition().y + 160; + updatePosition(); +} + +SsScene1705Tape::SsScene1705Tape(NeverhoodEngine *vm, Scene *parentScene, uint32 tapeIndex, int surfacePriority, int16 x, int16 y, uint32 fileHash) + : StaticSprite(vm, fileHash, surfacePriority, x - 24, y - 4), _parentScene(parentScene), _tapeIndex(tapeIndex) { + + if (!getSubVar(VA_HAS_TAPE, _tapeIndex) && !getSubVar(VA_IS_TAPE_INSERTED, _tapeIndex)) { + SetMessageHandler(&SsScene1705Tape::handleMessage); + } else { + setVisible(false); + SetMessageHandler(NULL); + } + _collisionBoundsOffset = _drawOffset; + _collisionBoundsOffset.x -= 4; + _collisionBoundsOffset.y -= 8; + _collisionBoundsOffset.width += 8; + _collisionBoundsOffset.height += 16; + Sprite::updateBounds(); +} + +uint32 SsScene1705Tape::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + sendMessage(_parentScene, 0x4826, 0); + messageResult = 1; + break; + case 0x4806: + setSubVar(VA_HAS_TAPE, _tapeIndex, 1); + setVisible(false); + SetMessageHandler(NULL); + break; + } + return messageResult; +} + +Scene1705::Scene1705(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _paletteArea(1) { + + Sprite *tempSprite; + + setGlobalVar(V_FELL_DOWN_HOLE, 1); + _vm->gameModule()->initCannonSymbolsPuzzle(); + + SetMessageHandler(&Scene1705::handleMessage); + SetUpdateHandler(&Scene1705::update); + + setHitRects(0x004B69D8); + setBackground(0x03118226); + setPalette(0x03118226); + _palette->addBasePalette(0x91D3A391, 0, 64, 0); + _palette->copyBasePalette(0, 256, 0); + addEntity(_palette); + insertScreenMouse(0x18222039); + + insertSprite<SsScene1705WallSymbol>(kScene1705FileHashes[getSubVar(VA_GOOD_CANNON_SYMBOLS_2, 0)], 0); + insertSprite<SsScene1705WallSymbol>(kScene1705FileHashes[getSubVar(VA_GOOD_CANNON_SYMBOLS_2, 1)], 1); + insertSprite<SsScene1705WallSymbol>(kScene1705FileHashes[getSubVar(VA_GOOD_CANNON_SYMBOLS_2, 2)], 2); + _sprite = insertStaticSprite(0x31313A22, 1100); + _ssTape = insertSprite<SsScene1705Tape>(this, 15, 1100, 238, 439, 0x02363852); + addCollisionSprite(_ssTape); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene1705>(231, 434); + setMessageList(0x004B69E8); + sendMessage(this, 0x2000, 0); + _klaymen->setClipRect(0, 0, _sprite->getDrawRect().x2(), 480); + } else if (which == 1) { + // Klaymen teleporting in + insertKlaymen<KmScene1705>(431, 434); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004B6A08, false); + sendMessage(this, 0x2000, 1); + _klaymen->setClipRect(0, 0, _sprite->getDrawRect().x2(), 480); + } else if (which == 2) { + // Klaymen teleporting out + insertKlaymen<KmScene1705>(431, 434); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004B6AA0, false); + sendMessage(this, 0x2000, 1); + _klaymen->setClipRect(0, 0, _sprite->getDrawRect().x2(), 480); + } else if (which == 3) { + // Klaymen returning from teleporter console + insertKlaymen<KmScene1705>(431, 434); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004B6A18, false); + sendMessage(this, 0x2000, 1); + _klaymen->setClipRect(0, 0, _sprite->getDrawRect().x2(), 480); + } else { + // Klaymen falling through the hole + insertKlaymen<KmScene1705>(231, 74); + sendMessage(_klaymen, 0x2000, 0); + setMessageList(0x004B69F0); + sendMessage(this, 0x2000, 0); + tempSprite = insertStaticSprite(0x30303822, 1100); + _klaymen->setClipRect(0, tempSprite->getDrawRect().y, _sprite->getDrawRect().x2(), 480); + } + +} + +void Scene1705::update() { + Scene::update(); + if (_klaymen->getX() < 224 && _paletteArea != 0) { + _palette->addBasePalette(0xF2210C15, 0, 64, 0); + _palette->startFadeToPalette(12); + _paletteArea = 0; + } else if (_klaymen->getX() >= 224 && _paletteArea == 0) { + _palette->addBasePalette(0x91D3A391, 0, 64, 0); + _palette->startFadeToPalette(12); + _paletteArea = 1; + } +} + +uint32 Scene1705::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + if (param.asInteger()) { + setRectList(0x004B6B40); + _klaymen->setKlaymenIdleTable3(); + } else { + setRectList(0x004B6B30); + _klaymen->setKlaymenIdleTable1(); + } + break; + case 0x4826: + if (sender == _ssTape && _klaymen->getX() <= 318) { + sendEntityMessage(_klaymen, 0x1014, sender); + setMessageList(0x004B6AC0); + } + break; + } + return 0; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module1700.h b/engines/neverhood/modules/module1700.h new file mode 100644 index 0000000000..f57c411a18 --- /dev/null +++ b/engines/neverhood/modules/module1700.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. + * + */ + +#ifndef NEVERHOOD_MODULES_MODULE1700_H +#define NEVERHOOD_MODULES_MODULE1700_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" +#include "neverhood/smackerscene.h" + +namespace Neverhood { + +class Module1700 : public Module { +public: + Module1700(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module1700(); +protected: + int _sceneNum; + void createScene(int sceneNum, int which); + void updateScene(); +}; + +// Scene1705 + +class SsScene1705WallSymbol : public StaticSprite { +public: + SsScene1705WallSymbol(NeverhoodEngine *vm, uint32 fileHash, int symbolIndex); +}; + +class SsScene1705Tape : public StaticSprite { +public: + SsScene1705Tape(NeverhoodEngine *vm, Scene *parentScene, uint32 tapeIndex, int surfacePriority, int16 x, int16 y, uint32 fileHash); +protected: + Scene *_parentScene; + uint32 _tapeIndex; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene1705 : public Scene { +public: + Scene1705(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_sprite; + Sprite *_ssTape; + int _paletteArea; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE1700_H */ diff --git a/engines/neverhood/modules/module1800.cpp b/engines/neverhood/modules/module1800.cpp new file mode 100644 index 0000000000..2a6057f9c8 --- /dev/null +++ b/engines/neverhood/modules/module1800.cpp @@ -0,0 +1,181 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module1800.h" +#include "neverhood/navigationscene.h" +#include "neverhood/menumodule.h" + +namespace Neverhood { + +static const uint32 kModule1800SoundList[] = { + 0x16805548, + 0x16805048, + 0xD0E14441, + 0x90E090C2, + 0x90E1D0C2, + 0x90E2D0C2, + 0 +}; + +Module1800::Module1800(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + _vm->_soundMan->addSoundList(0x04A14718, kModule1800SoundList); + _vm->_soundMan->setSoundListParams(kModule1800SoundList, true, 50, 600, 10, 150); + _vm->_soundMan->playTwoSounds(0x04A14718, 0x8A382B55, 0x0C242F1D, 0); + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else if (which == 2) + createScene(5, 0); + else if (which == 3) + createScene(0, 0); + else + createScene(3, 1); + +} + +Module1800::~Module1800() { + _vm->_soundMan->deleteGroup(0x04A14718); +} + +void Module1800::createScene(int sceneNum, int which) { + static const byte kNavigationTypes00[] = {1, 0, 2, 0}; + static const byte kNavigationTypes01[] = {5}; + debug("Module1800::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + createNavigationScene(0x004AFD38, which, kNavigationTypes00); + break; + case 1: + _vm->gameState().sceneNum = 1; + createNavigationScene(0x004AFD98, which, kNavigationTypes01); + break; + case 2: + _vm->gameState().sceneNum = 2; + createSmackerScene(0x006C0085, true, true, false); + break; + case 3: + _vm->gameState().sceneNum = 3; + createNavigationScene(0x004AFDB0, which); + break; + case 4: + _vm->gameState().sceneNum = 4; + createNavigationScene(0x004AFDE0, which); + break; + case 5: + _vm->gameState().sceneNum = 5; + createNavigationScene(0x004AFE40, which); + break; + case 6: + _vm->gameState().sceneNum = 6; + _vm->_soundMan->deleteGroup(0x04A14718); + createSmackerScene(0x08D84010, true, true, false); + break; + case 7: + _vm->gameState().sceneNum = 7; + _vm->_soundMan->setSoundListParams(kModule1800SoundList, false, 0, 0, 0, 0); + createSmackerScene(0x0168B121, true, true, false); + break; + case 8: + _vm->gameState().sceneNum = 8; + _childObject = new CreditsScene(_vm, this, false); + break; + case 1009: + _vm->gameState().sceneNum = 3; + // NOTE: Newly introduced sceneNum 1009 (was duplicate 3 with own update handler) + createSmackerScene(0x0A840C01, true, true, false); + break; + } + SetUpdateHandler(&Module1800::updateScene); + _childObject->handleUpdate(); +} + +void Module1800::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 1) + createScene(4, 0); + else if (_moduleResult == 2) + createScene(1, -1); + else if (_moduleResult == 3) + createScene(3, 0); + break; + case 1: + if (_navigationAreaType == 3) + createScene(7, -1); + else + createScene(2, -1); + break; + case 2: + createScene(0, 2); + break; + case 3: + if (_moduleResult == 0) + createScene(1009, -1); + else if (_moduleResult == 1) + createScene(0, 1); + break; + case 4: + if (_moduleResult == 0) + createScene(6, -1); + else if (_moduleResult == 1) + createScene(5, 0); + else if (_moduleResult == 2) + createScene(0, 3); + else if (_moduleResult == 3) + createScene(4, 3); + break; + case 5: + if (_moduleResult == 0) + leaveModule(2); + else if (_moduleResult == 1) + createScene(4, 3); + break; + case 6: + createScene(8, -1); + break; + case 7: + leaveModule(3); + break; + case 8: + // NOTE: After Klaymen jumped into the hole and died... + leaveModule(1); + break; + case 1009: + leaveModule(0); + break; + } + } else { + switch (_sceneNum) { + case 0: + if (navigationScene()->isWalkingForward() && navigationScene()->getNavigationIndex() == 2) + _vm->_soundMan->setTwoSoundsPlayFlag(false); + break; + } + } +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module1800.h b/engines/neverhood/modules/module1800.h new file mode 100644 index 0000000000..d3f3a635c3 --- /dev/null +++ b/engines/neverhood/modules/module1800.h @@ -0,0 +1,46 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 NEVERHOOD_MODULES_MODULE1800_H +#define NEVERHOOD_MODULES_MODULE1800_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +// Module1800 + +class Module1800 : public Module { +public: + Module1800(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module1800(); +protected: + int _sceneNum; + void createScene(int sceneNum, int which); + void updateScene(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE1800_H */ diff --git a/engines/neverhood/modules/module1900.cpp b/engines/neverhood/modules/module1900.cpp new file mode 100644 index 0000000000..1a9ffa127b --- /dev/null +++ b/engines/neverhood/modules/module1900.cpp @@ -0,0 +1,650 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module1900.h" +#include "neverhood/gamemodule.h" + +namespace Neverhood { + +static const uint32 kModule1900SoundList[] = { + 0xB4005E60, + 0x91835066, + 0x90E14440, + 0 +}; + +Module1900::Module1900(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + // NOTE: The original has a Scene1908 here as well but it's not used here but in another module... + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else + createScene(0, 0); + + _vm->_soundMan->addSoundList(0x04E1C09C, kModule1900SoundList); + _vm->_soundMan->setSoundListParams(kModule1900SoundList, true, 50, 600, 5, 150); + +} + +Module1900::~Module1900() { + _vm->_soundMan->deleteGroup(0x04E1C09C); +} + +void Module1900::createScene(int sceneNum, int which) { + debug("Module1900::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _childObject = new Scene1901(_vm, this, which); + break; + case 6: + _vm->gameState().sceneNum = 6; + _childObject = new Scene1907(_vm, this); + break; + } + SetUpdateHandler(&Module1900::updateScene); + _childObject->handleUpdate(); +} + +void Module1900::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 1) + createScene(6, 0); + else + leaveModule(0); + break; + case 6: + createScene(0, 1); + break; + } + } +} + +// Scene1901 + +Scene1901::Scene1901(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + Sprite *tempSprite; + + setRectList(0x004B34C8); + + setBackground(0x01303227); + setPalette(0x01303227); + insertScreenMouse(0x0322301B); + + insertStaticSprite(0x42213133, 1100); + + if (!getGlobalVar(V_STAIRS_PUZZLE_SOLVED)) + insertStaticSprite(0x40A40168, 100); + else if (getGlobalVar(V_STAIRS_DOWN)) { + insertStaticSprite(0x124404C4, 100); + setGlobalVar(V_STAIRS_DOWN_ONCE, 1); + } else + insertStaticSprite(0x02840064, 100); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene1901>(120, 380); + setMessageList(0x004B3408); + } else if (which == 1) { + // Klaymen returning from the puzzle + insertKlaymen<KmScene1901>(372, 380); + setMessageList(0x004B3410); + } else { + // Klaymen entering from the left + insertKlaymen<KmScene1901>(0, 380); + setMessageList(0x004B3400); + } + + tempSprite = insertStaticSprite(0x4830A402, 1100); + _klaymen->setClipRect(tempSprite->getDrawRect().x, 0, 640, 480); + +} + +static const NPoint kAsScene1907SymbolGroundPositions[] = { + {160, 310}, { 90, 340}, {210, 335}, + {210, 380}, {310, 340}, {290, 400}, + {400, 375}, {370, 435}, {475, 415} +}; + +static const NPoint kAsScene1907SymbolPluggedInPositions[] = { + {275, 125}, {244, 125}, {238, 131}, + {221, 135}, {199, 136}, {168, 149}, + {145, 152}, {123, 154}, {103, 157} +}; + +static const NPoint kAsScene1907SymbolGroundHitPositions[] = { + {275, 299}, {244, 299}, {238, 305}, + {221, 309}, {199, 310}, {168, 323}, + {145, 326}, {123, 328}, {103, 331} +}; + +static const NPoint kAsScene1907SymbolPluggedInDownPositions[] = { + {275, 136}, {244, 156}, {238, 183}, + {221, 207}, {199, 228}, {168, 262}, + {145, 285}, {123, 307}, {103, 331} +}; + +static const uint32 kAsScene1907SymbolFileHashes[] = { + 0x006A1034, 0x006A1010, 0x006A1814, + 0x006A1016, 0x006A0014, 0x002A1014, + 0x00EA1014, 0x206A1014, 0x046A1414 +}; + +bool AsScene1907Symbol::_plugInFailed = false; +int AsScene1907Symbol::_plugInTryCount = 0; + +AsScene1907Symbol::AsScene1907Symbol(NeverhoodEngine *vm, Scene1907 *parentScene, int elementIndex, int positionIndex) + : AnimatedSprite(vm, 1000 - positionIndex), _parentScene(parentScene), _elementIndex(elementIndex), _isMoving(false) { + + _plugInFailed = false; + _plugInTryCount = 0; + + if (getGlobalVar(V_STAIRS_PUZZLE_SOLVED)) { + _isPluggedIn = true; + _currPositionIndex = elementIndex; + if (!getGlobalVar(V_STAIRS_DOWN)) { + _x = kAsScene1907SymbolPluggedInPositions[_currPositionIndex].x; + _y = kAsScene1907SymbolPluggedInPositions[_currPositionIndex].y; + } else { + _x = kAsScene1907SymbolPluggedInDownPositions[_currPositionIndex].x; + _y = kAsScene1907SymbolPluggedInDownPositions[_currPositionIndex].y; + } + createSurface1(kAsScene1907SymbolFileHashes[_elementIndex], 1000 + _currPositionIndex); + startAnimation(kAsScene1907SymbolFileHashes[_elementIndex], -1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + } else { + _isPluggedIn = false; + _currPositionIndex = positionIndex; + loadSound(0, 0x74231924); + loadSound(1, 0x36691914); + loadSound(2, 0x5421D806); + _parentScene->setPositionFree(_currPositionIndex, false); + _x = kAsScene1907SymbolGroundPositions[_currPositionIndex].x; + _y = kAsScene1907SymbolGroundPositions[_currPositionIndex].y; + createSurface1(kAsScene1907SymbolFileHashes[_elementIndex], 1000 + _currPositionIndex); + startAnimation(kAsScene1907SymbolFileHashes[_elementIndex], 0, -1); + _newStickFrameIndex = 0; + } + _collisionBoundsOffset.set(0, 0, 80, 80); + Sprite::updateBounds(); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1907Symbol::handleMessage); + +} + +void AsScene1907Symbol::update() { + updateAnim(); + handleSpriteUpdate(); + updatePosition(); + if (_plugInFailed && _plugInTryCount == 0) + _plugInFailed = false; +} + +uint32 AsScene1907Symbol::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (!_isPluggedIn && !_plugInFailed) { + tryToPlugIn(); + messageResult = 1; + } else + messageResult = 0; + break; + } + return messageResult; +} + +uint32 AsScene1907Symbol::hmTryToPlugIn(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene1907Symbol::suTryToPlugIn() { + _currStep++; + _x -= _deltaX; + _y -= _deltaY; + if (_currStep == 16) { + _x -= _smallDeltaX; + _y -= _smallDeltaY; + SetSpriteUpdate(NULL); + } +} + +void AsScene1907Symbol::suFallOff() { + if (_fallOffDelay != 0) { + _fallOffDelay--; + } else { + _y += _yAccel; + _yAccel += 8; + if (_y >= kAsScene1907SymbolGroundHitPositions[_currPositionIndex].y) { + _y = kAsScene1907SymbolGroundHitPositions[_currPositionIndex].y; + stFallOffHitGround(); + } + } +} + +void AsScene1907Symbol::suFallOffHitGround() { + + if (_x == _someX - _xBreak) + _x -= _smallDeltaX; + else + _x -= _deltaX; + + if (_y == kAsScene1907SymbolGroundHitPositions[_currPositionIndex].y) { + _y -= _someY; + } + + if (_currStep < 8) { + _y -= _yAccel; + _yAccel -= 4; + if (_yAccel < 0) + _yAccel = 0; + } else if (_currStep < 15) { + _y += _yAccel; + _yAccel += 4; + } else { + _y = kAsScene1907SymbolGroundPositions[_newPositionIndex].y; + cbFallOffHitGroundEvent(); + } + + _currStep++; +} + +void AsScene1907Symbol::suMoveDown() { + _y += _yIncr; + if (_yIncr < 11) + _yIncr++; + if (_y >= kAsScene1907SymbolPluggedInDownPositions[_elementIndex].y) { + _y = kAsScene1907SymbolPluggedInDownPositions[_elementIndex].y; + _isMoving = false; + SetSpriteUpdate(NULL); + } +} + +void AsScene1907Symbol::suMoveUp() { + _y -= _yIncr; + if (getGlobalVar(V_WALL_BROKEN)) { + if (_y - (9 + (_elementIndex > 5 ? 31 : 0)) < kAsScene1907SymbolPluggedInPositions[_elementIndex].y) + _yIncr--; + else + _yIncr++; + } else + _yIncr = 2; + if (_yIncr > 9) + _yIncr = 9; + else if (_yIncr < 1) + _yIncr = 1; + if (_y < kAsScene1907SymbolPluggedInPositions[_elementIndex].y) { + _y = kAsScene1907SymbolPluggedInPositions[_elementIndex].y; + _isMoving = false; + SetSpriteUpdate(NULL); + } +} + +void AsScene1907Symbol::tryToPlugIn() { + _isPluggedIn = true; + _plugInTryCount++; + _newPositionIndex = _parentScene->getNextPosition(); + _parentScene->setPositionFree(_currPositionIndex, true); + sendMessage(_parentScene, 0x1022, 1100 + _newPositionIndex); + startAnimation(kAsScene1907SymbolFileHashes[_elementIndex], 0, -1); + SetUpdateHandler(&AsScene1907Symbol::update); + SetMessageHandler(&AsScene1907Symbol::hmTryToPlugIn); + SetSpriteUpdate(&AsScene1907Symbol::suTryToPlugIn); + _currStep = 0; + _deltaX = (_x - kAsScene1907SymbolPluggedInPositions[_newPositionIndex].x) / 16; + _smallDeltaX = _x - _deltaX * 16 - kAsScene1907SymbolPluggedInPositions[_newPositionIndex].x; + _deltaY = (_y - kAsScene1907SymbolPluggedInPositions[_newPositionIndex].y) / 16; + _smallDeltaY = _y - _deltaY * 16 - kAsScene1907SymbolPluggedInPositions[_newPositionIndex].y; + if (_elementIndex == _newPositionIndex) { + NextState(&AsScene1907Symbol::stPlugIn); + } else { + _plugInFailed = true; + NextState(&AsScene1907Symbol::stPlugInFail); + } +} + +void AsScene1907Symbol::fallOff(int newPositionIndex, int fallOffDelay) { + _isPluggedIn = false; + _newPositionIndex = newPositionIndex; + _fallOffDelay = fallOffDelay; + _parentScene->setPositionFree(_newPositionIndex, false); + _x = kAsScene1907SymbolPluggedInPositions[_currPositionIndex].x; + _y = kAsScene1907SymbolPluggedInPositions[_currPositionIndex].y; + _someX = _x; + _someY = _y; + startAnimation(kAsScene1907SymbolFileHashes[_elementIndex], -1, 0); + _playBackwards = true; + _newStickFrameIndex = STICK_LAST_FRAME; + _currStep = 0; + _yAccel = 1; + SetUpdateHandler(&AsScene1907Symbol::update); + SetMessageHandler(&AsScene1907Symbol::handleMessage); + SetSpriteUpdate(&AsScene1907Symbol::suFallOff); +} + +void AsScene1907Symbol::stFallOffHitGround() { + playSound(1); + sendMessage(_parentScene, 0x1022, 1000 + _newPositionIndex); + Entity::_priority = 1000 - _newPositionIndex; + _parentScene->removeCollisionSprite(this); + _parentScene->addCollisionSprite(this); + SetSpriteUpdate(&AsScene1907Symbol::suFallOffHitGround); + NextState(&AsScene1907Symbol::cbFallOffHitGroundEvent); + _newStickFrameIndex = 0; + _currStep = 0; + _yAccel = 30; + _deltaX = (_x - kAsScene1907SymbolGroundPositions[_newPositionIndex].x) / 15; + _xBreak = _deltaX * 15; + _smallDeltaX = _x - kAsScene1907SymbolGroundPositions[_newPositionIndex].x - _xBreak; + _someY = 0; + if (kAsScene1907SymbolGroundHitPositions[_currPositionIndex].y > kAsScene1907SymbolGroundPositions[_newPositionIndex].y) + _someY = kAsScene1907SymbolGroundHitPositions[_currPositionIndex].y - kAsScene1907SymbolGroundPositions[_newPositionIndex].y; +} + +void AsScene1907Symbol::cbFallOffHitGroundEvent() { + _currPositionIndex = _newPositionIndex; + if (_plugInTryCount > 0) + _plugInTryCount--; + startAnimation(kAsScene1907SymbolFileHashes[_elementIndex], 0, -1); + _newStickFrameIndex = 0; + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene1907Symbol::handleMessage); + SetSpriteUpdate(NULL); + updateBounds(); + playSound(2); +} + +void AsScene1907Symbol::stPlugIn() { + playSound(0); + _currPositionIndex = _newPositionIndex; + stopAnimation(); + SetMessageHandler(&AsScene1907Symbol::handleMessage); + SetSpriteUpdate(NULL); + if (_elementIndex == 8) + sendMessage(_parentScene, 0x2001, 0); +} + +void AsScene1907Symbol::stPlugInFail() { + _currPositionIndex = _newPositionIndex; + stopAnimation(); + _parentScene->plugInFailed(); +} + +void AsScene1907Symbol::moveUp() { + startAnimation(kAsScene1907SymbolFileHashes[_elementIndex], -1, -1); + stopAnimation(); + SetMessageHandler(&AsScene1907Symbol::handleMessage); + SetSpriteUpdate(&AsScene1907Symbol::suMoveUp); + _yIncr = 1; + _isMoving = true; +} + +void AsScene1907Symbol::moveDown() { + startAnimation(kAsScene1907SymbolFileHashes[_elementIndex], -1, -1); + stopAnimation(); + SetMessageHandler(&AsScene1907Symbol::handleMessage); + SetSpriteUpdate(&AsScene1907Symbol::suMoveDown); + _yIncr = 4; + _isMoving = true; +} + +SsScene1907UpDownButton::SsScene1907UpDownButton(NeverhoodEngine *vm, Scene1907 *parentScene, AsScene1907Symbol *asScene1907Symbol) + : StaticSprite(vm, 1400), _parentScene(parentScene), _asScene1907Symbol(asScene1907Symbol), + _countdown1(0) { + + loadSprite(0x64516424, kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset, 1400); + setVisible(false); + loadSound(0, 0x44061000); + SetUpdateHandler(&SsScene1907UpDownButton::update); + SetMessageHandler(&SsScene1907UpDownButton::handleMessage); + if (getGlobalVar(V_STAIRS_PUZZLE_SOLVED)) { + if (getGlobalVar(V_STAIRS_DOWN)) + setToDownPosition(); + else + setToUpPosition(); + } +} + +void SsScene1907UpDownButton::update() { + updatePosition(); + if (_countdown1 != 0 && (--_countdown1 == 0)) { + setVisible(false); + sendMessage(_parentScene, 0x2000, 0); + } +} + +uint32 SsScene1907UpDownButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_countdown1 == 0 && !_asScene1907Symbol->isMoving() && getGlobalVar(V_STAIRS_PUZZLE_SOLVED)) { + setVisible(true); + _countdown1 = 4; + updatePosition(); + playSound(0); + } + messageResult = 1; + } + return messageResult; +} + +void SsScene1907UpDownButton::setToUpPosition() { + _y = _spriteResource.getPosition().y; + updateBounds(); + updatePosition(); +} + +void SsScene1907UpDownButton::setToDownPosition() { + _y = _spriteResource.getPosition().y + 174; + updateBounds(); + updatePosition(); +} + +AsScene1907WaterHint::AsScene1907WaterHint(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1400) { + + createSurface1(0x110A1061, 1500); + _x = 320; + _y = 240; + startAnimation(0x110A1061, 0, -1); + _newStickFrameIndex = 0; + setVisible(false); + _needRefresh = true; + AnimatedSprite::updatePosition(); + SetUpdateHandler(&AsScene1907WaterHint::update); + SetMessageHandler(&Sprite::handleMessage); +} + +void AsScene1907WaterHint::update() { + updateAnim(); + updatePosition(); +} + +uint32 AsScene1907WaterHint::hmShowing(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene1907WaterHint::show() { + setVisible(true); + startAnimation(0x110A1061, 0, -1); + SetMessageHandler(&AsScene1907WaterHint::hmShowing); + NextState(&AsScene1907WaterHint::hide); +} + +void AsScene1907WaterHint::hide() { + stopAnimation(); + setVisible(false); + SetMessageHandler(&Sprite::handleMessage); +} + +Scene1907::Scene1907(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule), _currMovingSymbolIndex(0), _pluggedInCount(0), + _moveDownCountdown(0), _moveUpCountdown(0), _countdown3(0), _hasPlugInFailed(false) { + + setBackground(0x20628E05); + setPalette(0x20628E05); + + for (int i = 0; i < 9; i++) + _positionFree[i] = true; + + for (int i = 0; i < 9; i++) { + _asSymbols[i] = insertSprite<AsScene1907Symbol>(this, i, getRandomPositionIndex()); + addCollisionSprite(_asSymbols[i]); + } + + _ssUpDownButton = insertSprite<SsScene1907UpDownButton>(this, _asSymbols[8]); + addCollisionSprite(_ssUpDownButton); + + _asWaterHint = insertSprite<AsScene1907WaterHint>(); + + insertPuzzleMouse(0x28E0120E, 20, 620); + + SetMessageHandler(&Scene1907::handleMessage); + SetUpdateHandler(&Scene1907::update); + + if (getGlobalVar(V_STAIRS_PUZZLE_SOLVED)) + _pluggedInCount = 9; + + loadSound(0, 0x72004A10); + loadSound(1, 0x22082A12); + loadSound(2, 0x21100A10); + loadSound(3, 0x68E25540); + +} + +void Scene1907::update() { + Scene::update(); + + if (_hasPlugInFailed) { + int fallOffDelay = 0; + _hasPlugInFailed = false; + for (int i = 0; i < 9; i++) { + AsScene1907Symbol *asSymbol = _asSymbols[8 - i]; + if (asSymbol->isPluggedIn()) { + asSymbol->fallOff(getRandomPositionIndex(), fallOffDelay); + fallOffDelay += _vm->_rnd->getRandomNumber(10 - 1) + 4; + } + } + } + + if (_moveDownCountdown != 0 && (--_moveDownCountdown == 0)) { + _asSymbols[_currMovingSymbolIndex]->moveDown(); + if (_currMovingSymbolIndex > 0) { + _moveDownCountdown = 2; + _currMovingSymbolIndex--; + } + } + + if (_moveUpCountdown != 0 && (--_moveUpCountdown == 0)) { + _moveDownCountdown = 0; + for (int i = 0; i < 9; i++) + _asSymbols[i]->moveUp(); + } + + if (_countdown3 != 0 && (--_countdown3 == 0)) { + _asWaterHint->show(); + _moveUpCountdown = 4; + } + +} + +uint32 Scene1907::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if ((param.asPoint().x <= 20 || param.asPoint().x >= 620) && + !_hasPlugInFailed && _moveDownCountdown == 0 && _moveUpCountdown == 0 && _countdown3 == 0) { + leaveScene(0); + } + break; + case 0x2000: + if (getGlobalVar(V_STAIRS_DOWN)) { + playSound(0); + for (int i = 0; i < 9; i++) + _asSymbols[i]->moveUp(); + _ssUpDownButton->setToUpPosition(); + setGlobalVar(V_STAIRS_DOWN, 0); + } else { + if (!getGlobalVar(V_WALL_BROKEN)) { + playSound(2); + _countdown3 = 5; + } else { + playSound(1); + _ssUpDownButton->setToDownPosition(); + setGlobalVar(V_STAIRS_DOWN, 1); + } + _moveDownCountdown = 1; + _currMovingSymbolIndex = 8; + } + break; + case 0x2001: + playSound(3); + setGlobalVar(V_STAIRS_PUZZLE_SOLVED, 1); + break; + } + return 0; +} + +void Scene1907::plugInFailed() { + _pluggedInCount = 0; + _hasPlugInFailed = true; +} + +int Scene1907::getRandomPositionIndex() { + bool found = false; + int index = 0; + // Check if any position is free + for (int i = 0; i < 9; i++) + if (_positionFree[i]) + found = true; + if (found) { + // Get a random free position + found = false; + while (!found) { + index = _vm->_rnd->getRandomNumber(9 - 1); + if (_positionFree[index]) + found = true; + } + } + return index; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module1900.h b/engines/neverhood/modules/module1900.h new file mode 100644 index 0000000000..abb5eb1d87 --- /dev/null +++ b/engines/neverhood/modules/module1900.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 NEVERHOOD_MODULES_MODULE1900_H +#define NEVERHOOD_MODULES_MODULE1900_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" +#include "neverhood/modules/module1200.h" + +namespace Neverhood { + +class Module1900 : public Module { +public: + Module1900(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module1900(); +protected: + int _sceneNum; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void createScene(int sceneNum, int which); + void updateScene(); +}; + +// Scene1901 + +class Scene1901 : public Scene { +public: + Scene1901(NeverhoodEngine *vm, Module *parentModule, int which); +}; + +// Scene1907 + +class Scene1907; + +class AsScene1907Symbol : public AnimatedSprite { +public: + AsScene1907Symbol(NeverhoodEngine *vm, Scene1907 *parentScene, int elementIndex, int positionIndex); + void moveUp(); + void moveDown(); + void fallOff(int newPositionIndex, int fallOffDelay); + bool isPluggedIn() { return _isPluggedIn; } + bool isMoving() { return _isMoving; } +protected: + Scene1907 *_parentScene; + int _elementIndex; + int _currPositionIndex; + int _newPositionIndex; + bool _isPluggedIn; + bool _isMoving; + int _someX, _someY; + int _xBreak; + int _currStep; + int _yAccel; + int _yIncr; + int _fallOffDelay; + int _deltaX, _smallDeltaX; + int _deltaY, _smallDeltaY; + // Dumb, change if possible + static bool _plugInFailed; + static int _plugInTryCount; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmTryToPlugIn(int messageNum, const MessageParam ¶m, Entity *sender); + void suTryToPlugIn(); + void suFallOff(); + void suFallOffHitGround(); + void suMoveDown(); + void suMoveUp(); + void tryToPlugIn(); + void stFallOffHitGround(); + void cbFallOffHitGroundEvent(); + void stPlugIn(); + void stPlugInFail(); +}; + +class AsScene1907WaterHint : public AnimatedSprite { +public: + AsScene1907WaterHint(NeverhoodEngine *vm); + void show(); +protected: + void update(); + uint32 hmShowing(int messageNum, const MessageParam ¶m, Entity *sender); + void hide(); +}; + +class SsScene1907UpDownButton : public StaticSprite { +public: + SsScene1907UpDownButton(NeverhoodEngine *vm, Scene1907 *parentScene, AsScene1907Symbol *asScene1907Symbol); + void setToUpPosition(); + void setToDownPosition(); +protected: + Scene1907 *_parentScene; + AsScene1907Symbol *_asScene1907Symbol; + int _countdown1; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene1907 : public Scene { +public: + Scene1907(NeverhoodEngine *vm, Module *parentModule); + void plugInFailed(); + void setPositionFree(int index, bool value) { _positionFree[index] = value; } + int getNextPosition() { return _pluggedInCount++; } +protected: + AsScene1907Symbol *_asSymbols[9]; + SsScene1907UpDownButton *_ssUpDownButton; + AsScene1907WaterHint *_asWaterHint; + int _currMovingSymbolIndex; + int _pluggedInCount; + int _moveDownCountdown; + int _moveUpCountdown; + int _countdown3; + bool _hasPlugInFailed; + bool _positionFree[9]; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + int getRandomPositionIndex(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE1900_H */ diff --git a/engines/neverhood/modules/module2000.cpp b/engines/neverhood/modules/module2000.cpp new file mode 100644 index 0000000000..5039da1b01 --- /dev/null +++ b/engines/neverhood/modules/module2000.cpp @@ -0,0 +1,160 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module2000.h" +#include "neverhood/gamemodule.h" +#include "neverhood/navigationscene.h" + +namespace Neverhood { + +Module2000::Module2000(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else if (which == 0) + createScene(0, 1); + else if (which == 1) + createScene(0, 3); + +} + +Module2000::~Module2000() { + _vm->_soundMan->deleteGroup(0x81293110); +} + +void Module2000::createScene(int sceneNum, int which) { + debug("Module2000::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _childObject = new Scene2001(_vm, this, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + createNavigationScene(getGlobalVar(V_WORLDS_JOINED) ? 0x004B7B48 : 0x004B7B00, which); + break; + case 2: + _vm->gameState().sceneNum = 2; + setGlobalVar(V_WORLDS_JOINED, 1); + setSubVar(V_TELEPORTER_DEST_AVAILABLE, 1, 1); + createSmackerScene(0x204B2031, true, true, false); + break; + } + SetUpdateHandler(&Module2000::updateScene); + _childObject->handleUpdate(); +} + +void Module2000::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 1) + leaveModule(0); + else + createScene(1, 0); + break; + case 1: + if (_moduleResult == 0) { + if (getGlobalVar(V_WORLDS_JOINED)) + createScene(1, 0); + else + createScene(2, -1); + } else if (_moduleResult == 1) + createScene(1, 1); + else if (_moduleResult == 2) + createScene(0, 0); + break; + case 2: + createScene(1, 0); + break; + } + } +} + +// Scene2001 + +Scene2001::Scene2001(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + Sprite *tempSprite; + + SetMessageHandler(&Scene2001::handleMessage); + + setBackground(0xA6417244); + setPalette(0xA6417244); + insertScreenMouse(0x17240A6C); + + tempSprite = insertStaticSprite(0x0D641724, 1100); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene2001>(300, 345); + setMessageList(0x004B3538); + sendMessage(this, 0x2000, 0); + } else if (which == 1) { + // Klaymen teleporting in + insertKlaymen<KmScene2001>(116, 345); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004B3540, false); + sendMessage(this, 0x2000, 1); + } else if (which == 2) { + // Klaymen teleporting out + insertKlaymen<KmScene2001>(116, 345); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004B35F0, false); + sendMessage(this, 0x2000, 1); + } else if (which == 3) { + // Klaymen returning from teleporter console + insertKlaymen<KmScene2001>(116, 345); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004B3550, false); + sendMessage(this, 0x2000, 1); + } else { + // Klaymen standing around + insertKlaymen<KmScene2001>(390, 345); + setMessageList(0x004B3530); + sendMessage(this, 0x2000, 0); + _klaymen->setDoDeltaX(1); + } + + _klaymen->setClipRect(tempSprite->getDrawRect().x, 0, 640, 480); + +} + +uint32 Scene2001::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + if (param.asInteger()) { + setRectList(0x004B3680); + _klaymen->setKlaymenIdleTable3(); + } else { + setRectList(0x004B3670); + _klaymen->setKlaymenIdleTable1(); + } + } + return 0; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module2000.h b/engines/neverhood/modules/module2000.h new file mode 100644 index 0000000000..fa62f9a70e --- /dev/null +++ b/engines/neverhood/modules/module2000.h @@ -0,0 +1,55 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 NEVERHOOD_MODULES_MODULE2000_H +#define NEVERHOOD_MODULES_MODULE2000_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" +#include "neverhood/modules/module1200.h" + +namespace Neverhood { + +class Module2000 : public Module { +public: + Module2000(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module2000(); +protected: + int _sceneNum; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void createScene(int sceneNum, int which); + void updateScene(); +}; + +// Scene2001 + +class Scene2001 : public Scene { +public: + Scene2001(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE2000_H */ diff --git a/engines/neverhood/modules/module2100.cpp b/engines/neverhood/modules/module2100.cpp new file mode 100644 index 0000000000..0d7f3dd22a --- /dev/null +++ b/engines/neverhood/modules/module2100.cpp @@ -0,0 +1,336 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module2100.h" +#include "neverhood/gamemodule.h" +#include "neverhood/modules/module1200.h" + +namespace Neverhood { + +Module2100::Module2100(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + _vm->_soundMan->addMusic(0x10A10C14, 0x11482B95); + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else if (which == 1) + createScene(0, 0); + else if (which == 2) + createScene(0, 3); + else + createScene(0, 1); + +} + +Module2100::~Module2100() { + _vm->_soundMan->deleteMusicGroup(0x10A10C14); +} + +void Module2100::createScene(int sceneNum, int which) { + debug("Module2100::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _vm->_soundMan->startMusic(0x11482B95, 0, 1); + _childObject = new Scene2101(_vm, this, which); + break; + } + SetUpdateHandler(&Module2100::updateScene); + _childObject->handleUpdate(); +} + +void Module2100::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 1) { + setGlobalVar(V_DOOR_PASSED, 1); + leaveModule(0); + } else + leaveModule(1); + break; + } + } +} + +// Scene2101 + +AsScene2101Door::AsScene2101Door(NeverhoodEngine *vm, bool isOpen) + : AnimatedSprite(vm, 1100) { + + createSurface(100, 328, 347); + _x = 320; + _y = 240; + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2101Door::handleMessage); + if (isOpen) { + startAnimation(0x0C202B9C, -1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + } else + setVisible(false); +} + +uint32 AsScene2101Door::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + case 0x4808: + stOpenDoor(); + break; + case 0x4809: + stCloseDoor(); + break; + } + return messageResult; +} + +void AsScene2101Door::stOpenDoor() { + startAnimation(0x0C202B9C, 0, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + setVisible(true); + playSound(0, calcHash("fxDoorOpen32")); +} + +void AsScene2101Door::stCloseDoor() { + startAnimation(0xC222A8D4, 0, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + setVisible(true); + playSound(0, calcHash("fxDoorClose32")); + NextState(&AsScene2101Door::stCloseDoorDone); +} + +void AsScene2101Door::stCloseDoorDone() { + stopAnimation(); + setVisible(false); +} + +AsScene2101HitByDoorEffect::AsScene2101HitByDoorEffect(NeverhoodEngine *vm, Sprite *klaymen) + : AnimatedSprite(vm, 1400), _klaymen(klaymen) { + + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2101HitByDoorEffect::handleMessage); + createSurface(1200, 88, 165); + setVisible(false); +} + +uint32 AsScene2101HitByDoorEffect::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2001: + _x = _klaymen->getX(); + _y = _klaymen->getY() - 132; + startAnimation(0x0422255A, 0, -1); + setVisible(true); + break; + case 0x3002: + stopAnimation(); + setVisible(false); + break; + } + return messageResult; +} + +SsCommonFloorButton::SsCommonFloorButton(NeverhoodEngine *vm, Scene *parentScene, uint32 fileHash1, uint32 fileHash2, int surfacePriority, uint32 soundFileHash) + : StaticSprite(vm, 1100), _parentScene(parentScene), _countdown(0), + _fileHash1(fileHash1), _fileHash2(fileHash2), _soundFileHash(soundFileHash) { + + SetUpdateHandler(&SsCommonFloorButton::update); + SetMessageHandler(&SsCommonFloorButton::handleMessage); + if (_soundFileHash == 0) + _soundFileHash = 0x44141000; + createSurface(1010, 61, 30); + if (_fileHash1) + loadSprite(_fileHash1, kSLFDefDrawOffset | kSLFDefPosition); + else + setVisible(false); +} + +void SsCommonFloorButton::update() { + if (_countdown != 0 && (--_countdown == 0)) { + sendMessage(_parentScene, 0x1022, 1010); + if (_fileHash1) + loadSprite(_fileHash1, kSLFDefDrawOffset | kSLFDefPosition); + else + setVisible(false); + } +} + +uint32 SsCommonFloorButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x480B: + sendMessage(_parentScene, 0x480B, 0); + setVisible(true); + sendMessage(_parentScene, 0x1022, 990); + loadSprite(_fileHash2, kSLFDefDrawOffset | kSLFDefPosition); + _countdown = 16; + playSound(0, _soundFileHash); + break; + } + return messageResult; +} + +Scene2101::Scene2101(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + Sprite *tempSprite; + + SetMessageHandler(&Scene2101::handleMessage); + SetUpdateHandler(&Scene2101::update); + + setBackground(0x44242305); + setPalette(0x44242305); + insertScreenMouse(0x4230144A); + + insertStaticSprite(0x00502330, 1100); + tempSprite = insertStaticSprite(0x78492010, 1100); + _ssFloorButton = insertSprite<SsCommonFloorButton>(this, 0x72427010, 0x32423010, 200, 0); + _asTape1 = insertSprite<AsScene1201Tape>(this, 18, 1100, 412, 443, 0x9148A011); + addCollisionSprite(_asTape1); + _asTape2 = insertSprite<AsScene1201Tape>(this, 11, 1100, 441, 443, 0x9148A011); + addCollisionSprite(_asTape2); + + if (which < 0) { + insertKlaymen<KmScene2101>(380, 438); + setMessageList(0x004B8E48); + sendMessage(this, 0x2000, 0); + _asDoor = insertSprite<AsScene2101Door>(false); + _doorStatus = 1; + _countdown1 = 0; + } else if (which == 1) { + // Klaymen entering from the right + insertKlaymen<KmScene2101>(640, 438); + setMessageList(0x004B8E50); + sendMessage(this, 0x2000, 0); + _asDoor = insertSprite<AsScene2101Door>(true); + _doorStatus = 2; + _countdown1 = 48; + } else if (which == 2) { + // Klaymen teleporting out + insertKlaymen<KmScene2101>(115, 438); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004B8F58); + sendMessage(this, 0x2000, 1); + _asDoor = insertSprite<AsScene2101Door>(false); + _doorStatus = 1; + _countdown1 = 0; + } else if (which == 3) { + // Klaymen returning from the teleporter console + insertKlaymen<KmScene2101>(115, 438); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004B8EB0); + sendMessage(this, 0x2000, 1); + _asDoor = insertSprite<AsScene2101Door>(false); + _doorStatus = 1; + _countdown1 = 0; + } else { + // Klaymen teleporting in + insertKlaymen<KmScene2101>(115, 438); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004B8EA0); + sendMessage(this, 0x2000, 1); + _asDoor = insertSprite<AsScene2101Door>(false); + _doorStatus = 1; + _countdown1 = 0; + } + + _asHitByDoorEffect = insertSprite<AsScene2101HitByDoorEffect>(_klaymen); + _klaymen->setClipRect(0, 0, tempSprite->getDrawRect().x2(), 480); + +} + +void Scene2101::update() { + if (_countdown1 != 0) { + if (_doorStatus == 2) { + if (--_countdown1 == 0) { + sendMessage(_asDoor, 0x4809, 0); + _doorStatus = 1; + } + } else { + if (_klaymen->getX() > 575) + _canAcceptInput = false; + if (--_countdown1 == 0) { + if (_klaymen->getX() < 480) { + sendMessage(_asDoor, 0x4809, 0); + _doorStatus = 1; + } else if (_klaymen->getX() >= 480 && _klaymen->getX() <= 575) { + _klaymen->setDoDeltaX(0); + setMessageList2(0x004B8F48); + sendMessage(_asDoor, 0x4809, 0); + sendMessage(_asHitByDoorEffect, 0x2001, 0); + _doorStatus = 1; + } + } + } + } else if (_doorStatus == 1 && _messageValue >= 0 && _klaymen->getX() > 470 && !isMessageList2(0x004B8F48)) + setMessageList2(0x004B8F50); + Scene::update(); +} + +uint32 Scene2101::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x02144CB1) + sendEntityMessage(_klaymen, 0x1014, _ssFloorButton); + else if (param.asInteger() == 0x21E64A00) { + if (_doorStatus == 0) + setMessageList(0x004B8E80); + else + setMessageList(0x004B8EC8); + } else if (param.asInteger() == 0x41442820) + cancelMessageList(); + break; + case 0x2000: + if (param.asInteger() != 0) { + setRectList(0x004B9008); + _klaymen->setKlaymenIdleTable3(); + } else { + setRectList(0x004B8FF8); + _klaymen->setKlaymenIdleTable1(); + } + break; + case 0x480B: + if (sender == _ssFloorButton && _doorStatus == 1) { + sendMessage(_asDoor, 0x4808, 0); + _doorStatus = 0; + _countdown1 = 90; + } + break; + case 0x4826: + if (sender == _asTape1 || sender == _asTape2) { + if (_klaymen->getX() >= 228 && _klaymen->getX() <= 500) { + sendEntityMessage(_klaymen, 0x1014, sender); + setMessageList(0x004B8F78); + } else if (_klaymen->getX() < 228) + setMessageList2(0x004B8F00); + } + break; + } + return 0; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module2100.h b/engines/neverhood/modules/module2100.h new file mode 100644 index 0000000000..369f5ac0cc --- /dev/null +++ b/engines/neverhood/modules/module2100.h @@ -0,0 +1,92 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 NEVERHOOD_MODULES_MODULE2100_H +#define NEVERHOOD_MODULES_MODULE2100_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +class Module2100 : public Module { +public: + Module2100(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module2100(); +protected: + int _sceneNum; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void createScene(int sceneNum, int which); + void updateScene(); +}; + +// Scene1901 + +class AsScene2101Door : public AnimatedSprite { +public: + AsScene2101Door(NeverhoodEngine *vm, bool isOpen); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stOpenDoor(); + void stCloseDoor(); + void stCloseDoorDone(); +}; + +class AsScene2101HitByDoorEffect : public AnimatedSprite { +public: + AsScene2101HitByDoorEffect(NeverhoodEngine *vm, Sprite *klaymen); +protected: + Sprite *_klaymen; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsCommonFloorButton : public StaticSprite { +public: + SsCommonFloorButton(NeverhoodEngine *vm, Scene *parentScene, uint32 fileHash1, uint32 fileHash2, int surfacePriority, uint32 soundFileHash); +protected: + Scene *_parentScene; + uint32 _soundFileHash; + uint32 _fileHash1, _fileHash2; + int16 _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2101 : public Scene { +public: + Scene2101(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_ssFloorButton; + Sprite *_asTape1; + Sprite *_asTape2; + Sprite *_asDoor; + Sprite *_asHitByDoorEffect; + int _countdown1; + int _doorStatus; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE2100_H */ diff --git a/engines/neverhood/modules/module2200.cpp b/engines/neverhood/modules/module2200.cpp new file mode 100644 index 0000000000..b8da0f64ff --- /dev/null +++ b/engines/neverhood/modules/module2200.cpp @@ -0,0 +1,2564 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module2200.h" +#include "neverhood/modules/module1000.h" +#include "neverhood/modules/module1200.h" +#include "neverhood/gamemodule.h" +#include "neverhood/diskplayerscene.h" + +namespace Neverhood { + +Module2200::Module2200(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + debug("Create Module2200(%d)", which); + + _vm->_soundMan->addMusic(0x11391412, 0x601C908C); + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else + createScene(0, 0); + +} + +Module2200::~Module2200() { + _vm->_soundMan->deleteGroup(0x11391412); +} + +void Module2200::createScene(int sceneNum, int which) { + debug("Module2200::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _childObject = new Scene2201(_vm, this, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + _vm->_soundMan->startMusic(0x601C908C, 0, 2); + _childObject = new Scene2202(_vm, this, which); + break; + case 2: + _vm->gameState().sceneNum = 2; + _vm->_soundMan->startMusic(0x601C908C, 0, 2); + _childObject = new Scene2203(_vm, this, which); + break; + case 3: + _vm->gameState().sceneNum = 3; + _vm->_soundMan->stopMusic(0x601C908C, 0, 2); + _childObject = new DiskplayerScene(_vm, this, 3); + break; + case 4: + _vm->gameState().sceneNum = 4; + _vm->_soundMan->stopMusic(0x601C908C, 0, 2); + _childObject = new Scene2205(_vm, this, which); + break; + case 5: + _vm->gameState().sceneNum = 5; + _vm->_soundMan->stopMusic(0x601C908C, 0, 2); + _childObject = new Scene2206(_vm, this, which); + break; + case 6: + _vm->gameState().sceneNum = 6; + _childObject = new Scene2207(_vm, this); + break; + case 7: + if (which >= 0) + _vm->gameState().which = _vm->gameState().sceneNum; + _vm->gameState().sceneNum = 7; + _childObject = new Scene2208(_vm, this, which); + break; + case 8: + _vm->gameState().sceneNum = 8; + _childObject = new Scene2208(_vm, this, which); + break; + case 9: + _vm->gameState().sceneNum = 9; + createHallOfRecordsScene(which, 0x004B7180); + break; + case 10: + _vm->gameState().sceneNum = 10; + createHallOfRecordsScene(which, 0x004B7198); + break; + case 11: + _vm->gameState().sceneNum = 11; + createHallOfRecordsScene(which, 0x004B71B0); + break; + case 12: + _vm->gameState().sceneNum = 12; + createHallOfRecordsScene(which, 0x004B71C8); + break; + case 13: + _vm->gameState().sceneNum = 13; + createHallOfRecordsScene(which, 0x004B71E0); + break; + case 14: + _vm->gameState().sceneNum = 14; + createHallOfRecordsScene(which, 0x004B71F8); + break; + case 15: + _vm->gameState().sceneNum = 15; + createHallOfRecordsScene(which, 0x004B7210); + break; + case 16: + _vm->gameState().sceneNum = 16; + createHallOfRecordsScene(which, 0x004B7228); + break; + case 17: + _vm->gameState().sceneNum = 17; + createHallOfRecordsScene(which, 0x004B7240); + break; + case 18: + _vm->gameState().sceneNum = 18; + createHallOfRecordsScene(which, 0x004B7258); + break; + case 19: + _vm->gameState().sceneNum = 19; + createHallOfRecordsScene(which, 0x004B7270); + break; + case 20: + _vm->gameState().sceneNum = 20; + createHallOfRecordsScene(which, 0x004B7288); + break; + case 21: + _vm->gameState().sceneNum = 21; + createHallOfRecordsScene(which, 0x004B72A0); + break; + case 22: + _vm->gameState().sceneNum = 22; + createHallOfRecordsScene(which, 0x004B72B8); + break; + case 23: + _vm->gameState().sceneNum = 23; + createHallOfRecordsScene(which, 0x004B72D0); + break; + case 24: + _vm->gameState().sceneNum = 24; + createHallOfRecordsScene(which, 0x004B72E8); + break; + case 25: + _vm->gameState().sceneNum = 25; + createHallOfRecordsScene(which, 0x004B7300); + break; + case 26: + _vm->gameState().sceneNum = 26; + createHallOfRecordsScene(which, 0x004B7318); + break; + case 27: + _vm->gameState().sceneNum = 27; + createHallOfRecordsScene(which, 0x004B7330); + break; + case 28: + _vm->gameState().sceneNum = 28; + createHallOfRecordsScene(which, 0x004B7348); + break; + case 29: + _vm->gameState().sceneNum = 29; + createHallOfRecordsScene(which, 0x004B7360); + break; + case 30: + _vm->gameState().sceneNum = 30; + createHallOfRecordsScene(which, 0x004B7378); + break; + case 31: + _vm->gameState().sceneNum = 31; + createHallOfRecordsScene(which, 0x004B7390); + break; + case 32: + _vm->gameState().sceneNum = 32; + createHallOfRecordsScene(which, 0x004B73A8); + break; + case 33: + _vm->gameState().sceneNum = 33; + createHallOfRecordsScene(which, 0x004B73C0); + break; + case 34: + _vm->gameState().sceneNum = 34; + createHallOfRecordsScene(which, 0x004B73D8); + break; + case 35: + _vm->gameState().sceneNum = 35; + createHallOfRecordsScene(which, 0x004B73F0); + break; + case 36: + _vm->gameState().sceneNum = 36; + createHallOfRecordsScene(which, 0x004B7408); + break; + case 37: + _vm->gameState().sceneNum = 37; + createHallOfRecordsScene(which, 0x004B7420); + break; + case 38: + _vm->gameState().sceneNum = 38; + createHallOfRecordsScene(which, 0x004B7438); + break; + case 39: + _vm->gameState().sceneNum = 39; + createHallOfRecordsScene(which, 0x004B7450); + break; + case 40: + _vm->gameState().sceneNum = 40; + createHallOfRecordsScene(which, 0x004B7468); + break; + case 41: + _vm->gameState().sceneNum = 41; + _childObject = new Scene2242(_vm, this, which); + break; + case 42: + _vm->gameState().sceneNum = 42; + createHallOfRecordsScene(which, 0x004B7480); + break; + case 43: + _vm->gameState().sceneNum = 43; + createHallOfRecordsScene(which, 0x004B7498); + break; + case 44: + _vm->gameState().sceneNum = 44; + createHallOfRecordsScene(which, 0x004B74B0); + break; + case 45: + _vm->gameState().sceneNum = 45; + createHallOfRecordsScene(which, 0x004B74C8); + break; + case 46: + _vm->gameState().sceneNum = 46; + _childObject = new Scene2247(_vm, this, which); + break; + case 47: + _vm->gameState().sceneNum = 47; + if (!getGlobalVar(V_WORLDS_JOINED)) { + if (getGlobalVar(V_LIGHTS_ON)) + createStaticScene(0x83110287, 0x10283839); + else + createStaticScene(0x83412B9D, 0x12B9983C); + } else { + if (getGlobalVar(V_LIGHTS_ON)) + createStaticScene(0x48632087, 0x3208348E); + else + createStaticScene(0x08C74886, 0x74882084); + } + break; + } + SetUpdateHandler(&Module2200::updateScene); + _childObject->handleUpdate(); +} + +#define HallOfRecordsSceneLink(nextSceneNum, prevSceneNum) \ + if (_moduleResult == 1) createScene(nextSceneNum, 0); else if (_moduleResult == 2) createScene(7, 0); else createScene(prevSceneNum, 1) + +void Module2200::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 1) + createScene(2, 0); + else if (_moduleResult == 2) + createScene(1, 0); + else + leaveModule(0); + break; + case 1: + createScene(0, 2); + break; + case 2: + if (_moduleResult == 1) + createScene(4, 0); + else if (_moduleResult == 2) + createScene(3, 0); + else + createScene(0, 1); + break; + case 3: + createScene(2, 2); + break; + case 4: + if (_moduleResult == 1) + createScene(5, 0); + else if (_moduleResult == 2) + createScene(4, 2); + else + createScene(2, 1); + break; + case 5: + if (_moduleResult == 1) + createScene(46, 0); + else if (_moduleResult == 2) + createScene(6, 0); + else if (_moduleResult == 3) + createScene(8, 0); + else + createScene(4, 1); + break; + case 6: + createScene(5, 2); + break; + case 7: + createScene(_vm->gameState().which, 2); + break; + case 8: + createScene(5, 3); + break; + case 9: + HallOfRecordsSceneLink(10, 46); + break; + case 10: + HallOfRecordsSceneLink(11, 9); + break; + case 11: + HallOfRecordsSceneLink(12, 10); + break; + case 12: + HallOfRecordsSceneLink(13, 11); + break; + case 13: + HallOfRecordsSceneLink(14, 12); + break; + case 14: + HallOfRecordsSceneLink(15, 13); + break; + case 15: + HallOfRecordsSceneLink(16, 14); + break; + case 16: + HallOfRecordsSceneLink(17, 15); + break; + case 17: + HallOfRecordsSceneLink(18, 16); + break; + case 18: + HallOfRecordsSceneLink(19, 17); + break; + case 19: + HallOfRecordsSceneLink(20, 18); + break; + case 20: + HallOfRecordsSceneLink(21, 19); + break; + case 21: + HallOfRecordsSceneLink(22, 20); + break; + case 22: + HallOfRecordsSceneLink(23, 21); + break; + case 23: + HallOfRecordsSceneLink(24, 22); + break; + case 24: + HallOfRecordsSceneLink(25, 23); + break; + case 25: + HallOfRecordsSceneLink(26, 24); + break; + case 26: + HallOfRecordsSceneLink(27, 25); + break; + case 27: + HallOfRecordsSceneLink(28, 26); + break; + case 28: + HallOfRecordsSceneLink(29, 27); + break; + case 29: + HallOfRecordsSceneLink(30, 28); + break; + case 30: + HallOfRecordsSceneLink(31, 29); + break; + case 31: + HallOfRecordsSceneLink(32, 30); + break; + case 32: + HallOfRecordsSceneLink(33, 31); + break; + case 33: + HallOfRecordsSceneLink(34, 32); + break; + case 34: + HallOfRecordsSceneLink(42, 33); + break; + case 35: + HallOfRecordsSceneLink(36, 45); + break; + case 36: + HallOfRecordsSceneLink(37, 35); + break; + case 37: + HallOfRecordsSceneLink(38, 36); + break; + case 38: + HallOfRecordsSceneLink(39, 37); + break; + case 39: + HallOfRecordsSceneLink(40, 38); + break; + case 40: + HallOfRecordsSceneLink(41, 39); + break; + case 41: + HallOfRecordsSceneLink(47, 40); + break; + case 42: + HallOfRecordsSceneLink(43, 34); + break; + case 43: + HallOfRecordsSceneLink(44, 42); + break; + case 44: + HallOfRecordsSceneLink(45, 43); + break; + case 45: + HallOfRecordsSceneLink(35, 44); + break; + case 46: + HallOfRecordsSceneLink(9, 5); + break; + case 47: + createScene(41, 1); + break; + } + } +} + +#undef HallOfRecordsSceneLink + +void Module2200::createHallOfRecordsScene(int which, uint32 hallOfRecordsInfoId) { + _childObject = new HallOfRecordsScene(_vm, this, which, hallOfRecordsInfoId); +} + +// Scene2201 + +AsScene2201CeilingFan::AsScene2201CeilingFan(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1100) { + + _x = 403; + _y = 259; + createSurface(100, 233, 96); + startAnimation(0x8600866, 0, -1); + SetUpdateHandler(&AnimatedSprite::update); +} + +AsScene2201Door::AsScene2201Door(NeverhoodEngine *vm, Klaymen *klaymen, Sprite *ssDoorLight, bool isOpen) + : AnimatedSprite(vm, 1100), _klaymen(klaymen), _ssDoorLight(ssDoorLight), _countdown(0), _isOpen(isOpen) { + + _x = 408; + _y = 290; + createSurface(900, 63, 266); + SetUpdateHandler(&AsScene2201Door::update); + SetMessageHandler(&AsScene2201Door::handleMessage); + if (_isOpen) { + startAnimation(0xE2CB0412, -1, -1); + _countdown = 48; + _newStickFrameIndex = STICK_LAST_FRAME; + } else { + startAnimation(0xE2CB0412, 0, -1); + _newStickFrameIndex = 0; + _ssDoorLight->setVisible(false); + } +} + +void AsScene2201Door::update() { + if (_countdown != 0 && _isOpen && (--_countdown == 0)) + stCloseDoor(); + AnimatedSprite::update(); +} + +uint32 AsScene2201Door::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x11001090) { + if (_isOpen) + _ssDoorLight->setVisible(true); + } else if (param.asInteger() == 0x11283090) { + if (!_isOpen) + _ssDoorLight->setVisible(false); + } + break; + case 0x2000: + if (_isOpen) + _countdown = 144; + messageResult = _isOpen ? 1 : 0; + break; + case 0x3002: + gotoNextState(); + break; + case 0x4808: + _countdown = 144; + if (!_isOpen) + stOpenDoor(); + break; + } + return messageResult; +} + +void AsScene2201Door::stOpenDoor() { + _isOpen = true; + startAnimation(0xE2CB0412, 0, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + playSound(0, calcHash("fxDoorOpen33")); +} + +void AsScene2201Door::stCloseDoor() { + _isOpen = false; + startAnimation(0xE2CB0412, -1, -1); + _playBackwards = true; + _newStickFrameIndex = 0; + playSound(0, calcHash("fxDoorClose33")); +} + +SsScene2201PuzzleCube::SsScene2201PuzzleCube(NeverhoodEngine *vm, uint32 positionIndex, uint32 cubeIndex) + : StaticSprite(vm, 900) { + + createSurface(100, 16, 16); + loadSprite(kSsScene2201PuzzleCubeFileHashes[cubeIndex], kSLFCenteredDrawOffset | kSLFSetPosition, 0, + kSsScene2201PuzzleCubePoints[positionIndex].x, kSsScene2201PuzzleCubePoints[positionIndex].y); +} + +Scene2201::Scene2201(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _isSoundPlaying(false) { + + Sprite *tempSprite; + + _vm->gameModule()->initCubeSymbolsPuzzle(); + + SetMessageHandler(&Scene2201::handleMessage); + SetUpdateHandler(&Scene2201::update); + + loadDataResource(0x04104242); + loadHitRectList(); + setBackground(0x40008208); + setPalette(0x40008208); + insertScreenMouse(0x0820C408); + + _asTape = insertSprite<AsScene1201Tape>(this, 7, 1100, 459, 432, 0x9148A011); + addCollisionSprite(_asTape); + _ssDoorButton = insertSprite<SsCommonPressButton>(this, 0xE4A43E29, 0xE4A43E29, 100, 0); + + for (uint32 cubeIndex = 0; cubeIndex < 9; cubeIndex++) + if ((int16)getSubVar(VA_CUBE_POSITIONS, cubeIndex) >= 0) + insertSprite<SsScene2201PuzzleCube>(cubeIndex, (int16)getSubVar(VA_CUBE_POSITIONS, cubeIndex)); + + _clipRects[0].y1 = 0; + _clipRects[0].x2 = 640; + _clipRects[1].x2 = 640; + _clipRects[1].y2 = 480; + + if (!getGlobalVar(V_TILE_PUZZLE_SOLVED)) + insertStaticSprite(0x00026027, 900); + + tempSprite = insertStaticSprite(0x030326A0, 1100); + _clipRects[0].x1 = tempSprite->getDrawRect().x; + insertStaticSprite(0x811DA061, 1100); + tempSprite = insertStaticSprite(0x11180022, 1100); + _clipRects[1].x1 = tempSprite->getDrawRect().x; + tempSprite = insertStaticSprite(0x0D411130, 1100); + _clipRects[0].y2 = tempSprite->getDrawRect().y2(); + _clipRects[1].y1 = tempSprite->getDrawRect().y2(); + _ssDoorLight = insertStaticSprite(0xA4062212, 900); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene2201>(300, 427, _clipRects, 2); + setMessageList(0x004B8118); + _asDoor = insertSprite<AsScene2201Door>(_klaymen, _ssDoorLight, false); + } else if (which == 1) { + // Klaymen entering from the back + insertKlaymen<KmScene2201>(412, 393, _clipRects, 2); + setMessageList(0x004B8130); + _asDoor = insertSprite<AsScene2201Door>(_klaymen, _ssDoorLight, false); + } else if (which == 2) { + // Klaymen returning from the puzzle + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) { + insertKlaymen<KmScene2201>(379, 427, _clipRects, 2); + _klaymen->setDoDeltaX(1); + } else + insertKlaymen<KmScene2201>(261, 427, _clipRects, 2); + setMessageList(0x004B8178); + _asDoor = insertSprite<AsScene2201Door>(_klaymen, _ssDoorLight, false); + } else { + // Klaymen entering from the left + NPoint pt = _dataResource.getPoint(0x0304D8DC); + insertKlaymen<KmScene2201>(pt.x, pt.y, _clipRects, 2); + setMessageList(0x004B8120); + _asDoor = insertSprite<AsScene2201Door>(_klaymen, _ssDoorLight, true); + } + + insertSprite<AsScene2201CeilingFan>(); + + _vm->_soundMan->addSound(0x04106220, 0x81212040); + +} + +Scene2201::~Scene2201() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX() ? 1 : 0); + _vm->_soundMan->deleteSoundGroup(0x04106220); +} + +void Scene2201::update() { + Scene::update(); + if (!_isSoundPlaying) { + _vm->_soundMan->playSoundLooping(0x81212040); + _isSoundPlaying = true; + } +} + +uint32 Scene2201::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x402064D8) + sendEntityMessage(_klaymen, 0x1014, _ssDoorButton); + else if (param.asInteger() == 0x35803198) { + if (sendMessage(_asDoor, 0x2000, 0)) + setMessageList(0x004B81A0); + else + setMessageList(0x004B81B8); + } else if (param.asInteger() == 0x51445010) { + if (getGlobalVar(V_TILE_PUZZLE_SOLVED)) + setMessageList(0x004B8108); + else + setMessageList(0x004B8150); + } else if (param.asInteger() == 0x1D203082) + setMessageList(0x004B8180); + else if (param.asInteger() == 0x00049091) { + if (getGlobalVar(V_TILE_PUZZLE_SOLVED)) + setMessageList(0x004B8138); + else + setMessageList(0x004B8108); + } + break; + case 0x480B: + if (sender == _ssDoorButton) + sendMessage(_asDoor, 0x4808, 0); + break; + case 0x4826: + if (sender == _asTape) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList(0x004B81C8); + } + break; + } + return 0; +} + +static const NPoint kSsScene2202PuzzleCubePoints[] = { + {196, 105}, {323, 102}, {445, 106}, + {192, 216}, {319, 220}, {446, 216}, + {188, 320}, {319, 319}, {443, 322} +}; + +static const uint32 kSsScene2202PuzzleCubeFileHashes1[] = { + 0xA500800C, 0x2182910C, 0x2323980C, + 0x23049084, 0x21008080, 0x2303900C, + 0x6120980C, 0x2504D808 +}; + +static const uint32 kSsScene2202PuzzleCubeFileHashes2[] = { + 0x0AAD8080, 0x0A290291, 0x0A2BA398, + 0x822B8490, 0x86298080, 0x0A2B8390, + 0x0A69A098, 0x0E2D84D8 +}; + +SsScene2202PuzzleCube::SsScene2202PuzzleCube(NeverhoodEngine *vm, Scene *parentScene, int16 cubePosition, int16 cubeSymbol) + : StaticSprite(vm, 900), _parentScene(parentScene), _cubeSymbol(cubeSymbol), _cubePosition(cubePosition), _isMoving(false) { + + int surfacePriority; + + SetUpdateHandler(&SsScene2202PuzzleCube::update); + SetMessageHandler(&SsScene2202PuzzleCube::handleMessage); + if (_cubePosition >= 0 && _cubePosition <= 2) + surfacePriority = 100; + else if (_cubePosition >= 3 && _cubePosition <= 5) + surfacePriority = 300; + else + surfacePriority = 500; + loadSprite(kSsScene2202PuzzleCubeFileHashes2[_cubeSymbol], kSLFCenteredDrawOffset | kSLFSetPosition | kSLFDefCollisionBoundsOffset, 0, + kSsScene2202PuzzleCubePoints[_cubePosition].x, kSsScene2202PuzzleCubePoints[_cubePosition].y); + loadSound(0, 0x40958621); + loadSound(1, 0x51108241); +} + +void SsScene2202PuzzleCube::update() { + handleSpriteUpdate(); + updatePosition(); +} + +uint32 SsScene2202PuzzleCube::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (!_isMoving && !getGlobalVar(V_TILE_PUZZLE_SOLVED)) + sendMessage(_parentScene, 0x2000, _cubePosition); + messageResult = 1; + break; + case 0x2001: + _isMoving = true; + moveCube(param.asInteger()); + break; + } + return messageResult; +} + +void SsScene2202PuzzleCube::suMoveCubeX() { + + bool done = false; + + if (_counterDirection) { + if (_counter > 2) + _counter -= 2; + } else { + if (_counter < 20) + _counter += 2; + } + + for (int16 i = 0; i < _counter; i++) { + _x += _xIncr; + _errValue += _yDelta; + if (_errValue >= _xDelta) { + _errValue -= _xDelta; + _y += _yIncr; + } + if (_x == _newX && _y == _newY) { + done = true; + break; + } + if (_x == _xFlagPos) + _counterDirection = true; + } + + if (done) + stopMoving(); + + updateBounds(); + +} + +void SsScene2202PuzzleCube::suMoveCubeY() { + + bool done = false; + + if (_counterDirection) { + if (_counter > 2) + _counter -= 2; + } else { + if (_counter < 20) + _counter += 2; + } + + for (int16 i = 0; i < _counter; i++) { + _y += _yIncr; + _errValue += _xDelta; + if (_errValue >= _yDelta) { + _errValue -= _yDelta; + _x += _xIncr; + } + if (_x == _newX && _y == _newY) { + done = true; + break; + } + if (_x == _xFlagPos) + _counterDirection = true; + } + + if (done) + stopMoving(); + + updateBounds(); + +} + +void SsScene2202PuzzleCube::moveCube(int16 newCubePosition) { + + loadSprite(kSsScene2202PuzzleCubeFileHashes1[_cubeSymbol], kSLFCenteredDrawOffset); + + setSubVar(VA_CUBE_POSITIONS, _cubePosition, (uint32)-1); + setSubVar(VA_CUBE_POSITIONS, newCubePosition, (uint32)_cubeSymbol); + + _cubePosition = newCubePosition; + _errValue = 0; + _counterDirection = false; + _counter = 0; + _newX = kSsScene2202PuzzleCubePoints[newCubePosition].x; + _newY = kSsScene2202PuzzleCubePoints[newCubePosition].y; + + if (_x == _newX && _y == _newY) + return; + + if (_x <= _newX) { + if (_y <= _newY) { + _xDelta = _newX - _x; + _yDelta = _newY - _y; + _xIncr = 1; + _yIncr = 1; + } else { + _xDelta = _newX - _x; + _yDelta = _y - _newY; + _xIncr = 1; + _yIncr = -1; + } + } else { + if (_y <= _newY) { + _xDelta = _x - _newX; + _yDelta = _newY - _y; + _xIncr = -1; + _yIncr = 1; + } else { + _xDelta = _x - _newX; + _yDelta = _y - _newY; + _xIncr = -1; + _yIncr = -1; + } + } + + if (_xDelta > _yDelta) { + SetSpriteUpdate(&SsScene2202PuzzleCube::suMoveCubeX); + if (_xIncr > 0) { + if (_newX - _x >= 180) + _xFlagPos = _newX - 90; + else + _xFlagPos = _x + _newX / 2; + } else { + if (_x - _newX >= 180) + _xFlagPos = _x + 90; + else + _xFlagPos = _x / 2 + _newX; + } + playSound(0); + } else { + SetSpriteUpdate(&SsScene2202PuzzleCube::suMoveCubeY); + if (_yIncr > 0) { + if (_newY - _y >= 180) + _xFlagPos = _newY - 90; + else + _xFlagPos = _y + _newY / 2; + } else { + if (_y - _newY >= 180) + _xFlagPos = _y + 90; + else + _xFlagPos = _y / 2 + _newY; + } + playSound(1); + } + +} + +void SsScene2202PuzzleCube::stopMoving() { + loadSprite(kSsScene2202PuzzleCubeFileHashes2[_cubeSymbol], kSLFCenteredDrawOffset); + SetSpriteUpdate(NULL); + _isMoving = false; + sendMessage(_parentScene, 0x2002, _cubePosition); +} + +Scene2202::Scene2202(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _isSolved(false), _leaveScene(false), _isCubeMoving(false), + _ssMovingCube(NULL), _ssDoneMovingCube(NULL) { + + _vm->gameModule()->initCubeSymbolsPuzzle(); + + SetMessageHandler(&Scene2202::handleMessage); + SetUpdateHandler(&Scene2202::update); + + setBackground(0x08100A0C); + setPalette(0x08100A0C); + addEntity(_palette); + insertPuzzleMouse(0x00A08089, 20, 620); + + for (uint32 cubePosition = 0; cubePosition < 9; cubePosition++) { + int16 cubeSymbol = (int16)getSubVar(VA_CUBE_POSITIONS, cubePosition); + if (cubeSymbol >= 0) { + Sprite *puzzleCubeSprite = insertSprite<SsScene2202PuzzleCube>(this, cubePosition, cubeSymbol); + addCollisionSprite(puzzleCubeSprite); + } + } + + insertStaticSprite(0x55C043B8, 200); + insertStaticSprite(0x85500158, 400); + insertStaticSprite(0x25547028, 600); + + loadSound(0, 0x68E25540); + loadSound(1, 0x40400457); + + _vm->_soundMan->addSound(0x60400854, 0x8101A241); + _vm->_soundMan->playSoundLooping(0x8101A241); + +} + +Scene2202::~Scene2202() { + _vm->_soundMan->deleteSoundGroup(0x60400854); +} + +void Scene2202::update() { + Scene::update(); + + if (_leaveScene && !isSoundPlaying(1)) + leaveScene(0); + + if (_isSolved && !isSoundPlaying(0)) { + playSound(1); + _isSolved = false; + _leaveScene = true; + } + + if (_ssMovingCube && !_isCubeMoving) { + int16 freeCubePosition = getFreeCubePosition(_movingCubePosition); + if (freeCubePosition != -1) { + setSurfacePriority(_ssMovingCube->getSurface(), 700); + sendMessage(_ssMovingCube, 0x2001, freeCubePosition); + _ssMovingCube = NULL; + _isCubeMoving = true; + } + } + + if (_ssDoneMovingCube) { + setSurfacePriority(_ssDoneMovingCube->getSurface(), _surfacePriority); + _ssDoneMovingCube = NULL; + if (testIsSolved()) { + playSound(0); + setGlobalVar(V_TILE_PUZZLE_SOLVED, 1); + _isSolved = true; + } + } + +} + +uint32 Scene2202::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) + leaveScene(0); + break; + case 0x2000: + _movingCubePosition = (int16)param.asInteger(); + _ssMovingCube = (Sprite*)sender; + break; + case 0x2002: + _isCubeMoving = false; + _ssDoneMovingCube = (Sprite*)sender; + if (param.asInteger() <= 2) + _surfacePriority = 100; + else if (param.asInteger() >= 3 && param.asInteger() <= 5) + _surfacePriority = 300; + else + _surfacePriority = 500; + break; + } + return 0; +} + +int16 Scene2202::getFreeCubePosition(int16 cubePosition) { + if (cubePosition >= 3 && (int16)getSubVar(VA_CUBE_POSITIONS, cubePosition - 3) == -1) + return cubePosition - 3; + else if (cubePosition <= 5 && (int16)getSubVar(VA_CUBE_POSITIONS, cubePosition + 3) == -1) + return cubePosition + 3; + else if (cubePosition != 0 && cubePosition != 3 && cubePosition != 6 && (int16)getSubVar(VA_CUBE_POSITIONS, cubePosition - 1) == -1) + return cubePosition - 1; + else if (cubePosition != 2 && cubePosition != 5 && cubePosition != 8 && (int16)getSubVar(VA_CUBE_POSITIONS, cubePosition + 1) == -1) + return cubePosition + 1; + else + return -1; +} + +bool Scene2202::testIsSolved() { + return + getSubVar(VA_CUBE_POSITIONS, 0) == 0 && + getSubVar(VA_CUBE_POSITIONS, 2) == 2 && + getSubVar(VA_CUBE_POSITIONS, 3) == 3 && + getSubVar(VA_CUBE_POSITIONS, 4) == 4 && + getSubVar(VA_CUBE_POSITIONS, 5) == 5 && + getSubVar(VA_CUBE_POSITIONS, 6) == 6 && + getSubVar(VA_CUBE_POSITIONS, 8) == 7; +} + +static const uint32 kAsCommonKeyFileHashes[] = { + 0x2450D850, 0x0C9CE8D0, 0x2C58A152 +}; + +AsCommonKey::AsCommonKey(NeverhoodEngine *vm, Scene *parentScene, int keyIndex, int surfacePriority, int16 x, int16 y) + : AnimatedSprite(vm, kAsCommonKeyFileHashes[keyIndex], surfacePriority, x, y), _parentScene(parentScene), _keyIndex(keyIndex) { + + if (!getSubVar(VA_HAS_KEY, _keyIndex) && !getSubVar(VA_IS_KEY_INSERTED, _keyIndex)) { + SetMessageHandler(&AsCommonKey::handleMessage); + } else { + // If Klaymen already has the key or it's already inserted then don't show it + setVisible(false); + SetMessageHandler(NULL); + } +} + +uint32 AsCommonKey::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + sendMessage(_parentScene, 0x4826, 0); + messageResult = 1; + break; + case 0x4806: + setSubVar(VA_HAS_KEY, _keyIndex, 1); + setVisible(false); + SetMessageHandler(NULL); + } + return messageResult; +} + +static const uint32 kAsScene2203DoorFileHashes[] = { + 0x7868AE10, 0x1A488110 +}; + +AsScene2203Door::AsScene2203Door(NeverhoodEngine *vm, Scene *parentScene, uint doorIndex) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _doorIndex(doorIndex) { + + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2203Door::handleMessage); + _x = 320; + _y = 240; + createSurface1(kAsScene2203DoorFileHashes[_doorIndex], 900); + if (getGlobalVar(V_LARGE_DOOR_NUMBER) == _doorIndex) { + startAnimation(kAsScene2203DoorFileHashes[_doorIndex], -1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + } else { + startAnimation(kAsScene2203DoorFileHashes[_doorIndex], 0, -1); + _newStickFrameIndex = 0; + } +} + +uint32 AsScene2203Door::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_doorIndex == getGlobalVar(V_LARGE_DOOR_NUMBER)) + sendMessage(_parentScene, 0x2002, 0); + else + sendMessage(_parentScene, 0x2001, 0); + messageResult = 1; + break; + case 0x2000: + _otherDoor = (Sprite*)param.asEntity(); + break; + case 0x3002: + if (_doorIndex == getGlobalVar(V_LARGE_DOOR_NUMBER)) + sendMessage(_parentScene, 0x4808, 0); + stopAnimation(); + break; + case 0x4808: + setGlobalVar(V_LARGE_DOOR_NUMBER, _doorIndex); + sendMessage(_otherDoor, 0x4809, 0); + openDoor(); + break; + case 0x4809: + closeDoor(); + sendMessage(_parentScene, 0x2003, 0); + break; + } + return messageResult; +} + +void AsScene2203Door::openDoor() { + playSound(0, 0x341014C4); + startAnimation(kAsScene2203DoorFileHashes[_doorIndex], 1, -1); +} + +void AsScene2203Door::closeDoor() { + startAnimation(kAsScene2203DoorFileHashes[_doorIndex], -1, -1); + _playBackwards = true; + _newStickFrameIndex = 0; +} + +Scene2203::Scene2203(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + if (getGlobalVar(V_HAS_FINAL_KEY) && getGlobalVar(V_KEY3_LOCATION) == 0) + setGlobalVar(V_KEY3_LOCATION, 1); + + SetMessageHandler(&Scene2203::handleMessage); + + setBackground(0x82C80334); + setPalette(0x82C80334); + insertScreenMouse(0x80330824); + setHitRects(0x004B8320); + setRectList(0x004B8420); + + if (getGlobalVar(V_KEY3_LOCATION) == 1) { + _asKey = insertSprite<AsCommonKey>(this, 2, 1100, 282, 432); + addCollisionSprite(_asKey); + } + + _asTape = insertSprite<AsScene1201Tape>(this, 1, 1100, 435, 432, 0x9148A011); + addCollisionSprite(_asTape); + _asLeftDoor = insertSprite<AsScene2203Door>(this, 0); + _asRightDoor = insertSprite<AsScene2203Door>(this, 1); + _ssSmallLeftDoor = insertStaticSprite(0x542CC072, 1100); + _ssSmallRightDoor = insertStaticSprite(0x0A2C0432, 1100); + _leftDoorClipRect.set(_ssSmallLeftDoor->getDrawRect().x, 0, 640, 480); + _rightDoorClipRect.set(0, 0, _ssSmallRightDoor->getDrawRect().x2(), 480); + sendEntityMessage(_asLeftDoor, 0x2000, _asRightDoor); + sendEntityMessage(_asRightDoor, 0x2000, _asLeftDoor); + addCollisionSprite(_asLeftDoor); + addCollisionSprite(_asRightDoor); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene2203>(200, 427); + setMessageList(0x004B8340); + } else if (which == 1) { + // Klaymen entering from the right + insertKlaymen<KmScene2203>(640, 427); + setMessageList(0x004B8350); + } else if (which == 2) { + // Klaymen returning from the displayer + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) { + insertKlaymen<KmScene2203>(362, 427); + _klaymen->setDoDeltaX(1); + } else + insertKlaymen<KmScene2203>(202, 427); + setMessageList(0x004B8358); + } else { + // Klaymen entering from the left + insertKlaymen<KmScene2203>(0, 427); + setMessageList(0x004B8348); + } + + if (getGlobalVar(V_LARGE_DOOR_NUMBER)) { + _ssSmallLeftDoor->setVisible(false); + _klaymen->setClipRect(_rightDoorClipRect); + } else { + _ssSmallRightDoor->setVisible(false); + _klaymen->setClipRect(_leftDoorClipRect); + } + +} + +Scene2203::~Scene2203() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX() ? 1 : 0); +} + +uint32 Scene2203::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2001: + sendEntityMessage(_klaymen, 0x1014, sender); + if (sender == _asLeftDoor) + setMessageList2(0x004B83B0); + else + setMessageList2(0x004B83C8); + break; + case 0x2002: + if (sender == _asLeftDoor) + setMessageList2(0x004B8370); + else + setMessageList2(0x004B8360); + break; + case 0x2003: + if (sender == _asLeftDoor) + _ssSmallLeftDoor->setVisible(false); + else + _ssSmallRightDoor->setVisible(false); + break; + case 0x4808: + if (sender == _asLeftDoor) { + _ssSmallLeftDoor->setVisible(true); + _klaymen->setClipRect(_leftDoorClipRect); + } else { + _ssSmallRightDoor->setVisible(true); + _klaymen->setClipRect(_rightDoorClipRect); + } + break; + case 0x4826: + if (sender == _asTape) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList(0x004B83E0); + } else if (sender == _asKey) { + sendEntityMessage(_klaymen, 0x1014, _asKey); + setMessageList(0x004B83F0); + } + break; + } + return messageResult; +} + +SsScene2205DoorFrame::SsScene2205DoorFrame(NeverhoodEngine *vm) + : StaticSprite(vm, 900) { + + SetMessageHandler(&SsScene2205DoorFrame::handleMessage); + createSurface(1100, 45, 206); + loadSprite(getGlobalVar(V_LIGHTS_ON) ? 0x24306227 : 0xD90032A0, kSLFDefDrawOffset | kSLFDefPosition); +} + +uint32 SsScene2205DoorFrame::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + loadSprite(getGlobalVar(V_LIGHTS_ON) ? 0x24306227 : 0xD90032A0, kSLFDefDrawOffset | kSLFDefPosition); + break; + } + return messageResult; +} + +Scene2205::Scene2205(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + SetMessageHandler(&Scene2205::handleMessage); + SetUpdateHandler(&Scene2205::update); + + setHitRects(0x004B0620); + if (getGlobalVar(V_LIGHTS_ON)) { + _isLightOn = true; + setBackground(0x0008028D); + setPalette(0x0008028D); + addEntity(_palette); + insertScreenMouse(0x80289008); + _ssLightSwitch = insertSprite<SsCommonPressButton>(this, 0x2D339030, 0x2D309030, 100, 0); + } else { + _isLightOn = false; + setBackground(0xD00A028D); + setPalette(0xD00A028D); + addEntity(_palette); + insertScreenMouse(0xA0289D08); + _ssLightSwitch = insertSprite<SsCommonPressButton>(this, 0x2D339030, 0xDAC86E84, 100, 0); + } + _palette->addBasePalette(0xD00A028D, 0, 256, 0); + _ssDoorFrame = insertSprite<SsScene2205DoorFrame>(); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene2205>(320, 417); + setMessageList(0x004B0658); + if (!getGlobalVar(V_LIGHTS_ON)) + _palette->addPalette(0x68033B1C, 0, 65, 0); + _isKlaymenInLight = false; + } else if (which == 1) { + // Klaymen entering from the right + insertKlaymen<KmScene2205>(640, 417); + setMessageList(0x004B0648); + if (!getGlobalVar(V_LIGHTS_ON)) + _palette->addPalette(0x68033B1C, 0, 65, 0); + _isKlaymenInLight = false; + } else { + // Klaymen entering from the left + insertKlaymen<KmScene2205>(0, 417); + setMessageList(0x004B0640); + _isKlaymenInLight = true; + } + + _klaymen->setClipRect(_ssDoorFrame->getDrawRect().x, 0, 640, 480); + _klaymen->setSoundFlag(true); + + loadDataResource(0x00144822); + +} + +void Scene2205::update() { + Scene::update(); + if (!_isLightOn && getGlobalVar(V_LIGHTS_ON)) { + _palette->addPalette(0x0008028D, 0, 256, 0); + changeBackground(0x0008028D); + _ssLightSwitch->setFileHashes(0x2D339030, 0x2D309030); + sendMessage(_ssDoorFrame, 0x2000, 0); + changeMouseCursor(0x80289008); + _isLightOn = true; + } else if (_isLightOn && !getGlobalVar(V_LIGHTS_ON)) { + _palette->addPalette(0xD00A028D, 0, 256, 0); + changeBackground(0xD00A028D); + _ssLightSwitch->setFileHashes(0x2D339030, 0xDAC86E84); + sendMessage(_ssDoorFrame, 0x2000, 0); + changeMouseCursor(0xA0289D08); + _isKlaymenInLight = true; + if (_klaymen->getX() > 85) { + _palette->addPalette(0x68033B1C, 0, 65, 0); + _isKlaymenInLight = false; + } + _isLightOn = false; + } + if (!getGlobalVar(V_LIGHTS_ON)) { + if (_isKlaymenInLight && _klaymen->getX() > 85) { + _palette->addBasePalette(0x68033B1C, 0, 65, 0); + _palette->startFadeToPalette(12); + _isKlaymenInLight = false; + } else if (!_isKlaymenInLight && _klaymen->getX() <= 85) { + _palette->addBasePalette(0xD00A028D, 0, 65, 0); + _palette->startFadeToPalette(12); + _isKlaymenInLight = true; + } + } +} + +uint32 Scene2205::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x6449569A) + setMessageList(0x004B0690); + else if (param.asInteger() == 0x2841369C) + setMessageList(0x004B0630); + else if (param.asInteger() == 0x402064D8) + sendEntityMessage(_klaymen, 0x1014, _ssLightSwitch); + break; + case 0x480B: + setGlobalVar(V_LIGHTS_ON, getGlobalVar(V_LIGHTS_ON) ? 0 : 1); + break; + } + return 0; +} + +static const int16 kScene2206XPositions[] = { + 384, 480, 572 +}; + +static const uint32 kScene2206MessageIds1[] = { + 0x004B8998, 0x004B89B8, 0x004B89D8 +}; + +static const uint32 kScene2206MessageIds2[] = { + 0x004B89F8, 0x004B8A20, 0x004B8A48 +}; + +static const int16 kAsScene2206DoorSpikesXDeltasOpen[] = { + -24, -28, -18, 6, 9, -8 +}; + +static const int16 kAsScene2206DoorSpikesXDeltasClose[] = { + -8, 7, 11, 26, 13, 14 +}; + +AsScene2206DoorSpikes::AsScene2206DoorSpikes(NeverhoodEngine *vm, uint32 fileHash) + : StaticSprite(vm, fileHash, 200) { + + if (getGlobalVar(V_SPIKES_RETRACTED)) + _x -= 63; + SetUpdateHandler(&AsScene2206DoorSpikes::update); + SetMessageHandler(&AsScene2206DoorSpikes::handleMessage); + SetSpriteUpdate(NULL); +} + +void AsScene2206DoorSpikes::update() { + handleSpriteUpdate(); + updatePosition(); +} + +uint32 AsScene2206DoorSpikes::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x4808: + _deltaIndex = 0; + playSound(0, 0x032746E0); + SetMessageHandler(NULL); + SetSpriteUpdate(&AsScene2206DoorSpikes::suOpen); + break; + case 0x4809: + _deltaIndex = 0; + playSound(0, 0x002642C0); + SetMessageHandler(NULL); + SetSpriteUpdate(&AsScene2206DoorSpikes::suClose); + break; + } + return messageResult; +} + +void AsScene2206DoorSpikes::suOpen() { + if (_deltaIndex < 6) { + _x += kAsScene2206DoorSpikesXDeltasOpen[_deltaIndex]; + _deltaIndex++; + } else { + SetMessageHandler(&AsScene2206DoorSpikes::handleMessage); + SetSpriteUpdate(NULL); + } +} + +void AsScene2206DoorSpikes::suClose() { + if (_deltaIndex < 6) { + _x += kAsScene2206DoorSpikesXDeltasClose[_deltaIndex]; + _deltaIndex++; + } else { + SetMessageHandler(&AsScene2206DoorSpikes::handleMessage); + SetSpriteUpdate(NULL); + } +} + +AsScene2206Platform::AsScene2206Platform(NeverhoodEngine *vm, uint32 fileHash) + : StaticSprite(vm, fileHash, 50) { + + SetUpdateHandler(&AsScene2206Platform::update); + SetMessageHandler(&AsScene2206Platform::handleMessage); + SetSpriteUpdate(NULL); +} + +void AsScene2206Platform::update() { + handleSpriteUpdate(); + updatePosition(); +} + +uint32 AsScene2206Platform::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x4803: + _yDelta = 0; + SetMessageHandler(NULL); + SetSpriteUpdate(&AsScene2206Platform::suMoveDown); + break; + } + return messageResult; +} + +void AsScene2206Platform::suMoveDown() { + _yDelta++; + _y += _yDelta; +} + +SsScene2206TestTube::SsScene2206TestTube(NeverhoodEngine *vm, Scene *parentScene, int surfacePriority, uint32 fileHash) + : StaticSprite(vm, fileHash, surfacePriority), _parentScene(parentScene) { + + if (getGlobalVar(V_HAS_TEST_TUBE)) { + setVisible(false); + SetMessageHandler(NULL); + } else + SetMessageHandler(&SsScene2206TestTube::handleMessage); + _collisionBoundsOffset = _drawOffset; + updateBounds(); +} + +uint32 SsScene2206TestTube::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + sendMessage(_parentScene, 0x4826, 0); + messageResult = 1; + break; + case 0x4806: + setGlobalVar(V_HAS_TEST_TUBE, 1); + setVisible(false); + SetMessageHandler(NULL); + break; + } + return messageResult; +} + +Scene2206::Scene2206(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + uint32 fileHash; + + SetUpdateHandler(&Scene::update); + SetMessageHandler(&Scene2206::handleMessage); + + if (getGlobalVar(V_LIGHTS_ON)) { + fileHash = 0x41983216; + _sprite1 = insertStaticSprite(0x2201266A, 100); + _sprite2 = insertStaticSprite(0x3406A333, 300); + _sprite3 = insertStaticSprite(0x24A223A2, 100); + _asDoorSpikes = insertSprite<AsScene2206DoorSpikes>(0x26133023); + _asDoorSpikes->setClipRect(_sprite2->getDrawRect().x, 0, 640, 480); + setRectList(0x004B8AF8); + _ssButton = insertSprite<SsCommonButtonSprite>(this, 0x0E038022, 100, 0); + insertScreenMouse(0x83212411); + _ssTestTube = insertSprite<SsScene2206TestTube>(this, 1100, /*464, 433, */0x5E00E262); + _asPlatform = insertSprite<AsScene2206Platform>(0x085E25E0); + } else { + fileHash = 0xE0102A45; + _sprite1 = insertStaticSprite(0x1C1106B8, 100); + _sprite2 = insertStaticSprite(0x020462E0, 300); + _sprite3 = insertStaticSprite(0x900626A2, 100); + _asDoorSpikes = insertSprite<AsScene2206DoorSpikes>(0x544822A8); + _asDoorSpikes->setClipRect(_sprite2->getDrawRect().x, 0, 640, 480); + setRectList(0x004B8B58); + _ssButton = insertSprite<SsCommonButtonSprite>(this, 0x16882608, 100, 0); + insertScreenMouse(0x02A41E09); + _ssTestTube = insertSprite<SsScene2206TestTube>(this, 1100, /*464, 433, */0x52032563); + _asPlatform = insertSprite<AsScene2206Platform>(0x317831A0); + } + + _asPlatform->setClipRect(_sprite2->getDrawRect().x, 0, _sprite3->getDrawRect().x2(), _sprite1->getDrawRect().y2()); + setBackground(fileHash); + setPalette(fileHash); + addEntity(_palette); + _palette->addBasePalette(fileHash, 0, 256, 0); + if (!getGlobalVar(V_LIGHTS_ON)) + _palette->addPalette(0x0263D144, 0, 65, 0); + addCollisionSprite(_ssTestTube); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene2206>(200, 430); + setMessageList(0x004B88A8); + } else if (which == 1) { + // Klaymen entering from the right + insertKlaymen<KmScene2206>(640, 430); + setMessageList(0x004B88B8); + } else if (which == 2) { + // Klaymen entering from the back + insertKlaymen<KmScene2206>(205, 396); + setMessageList(0x004B88C8); + _palette->addPalette(getGlobalVar(V_LIGHTS_ON) ? 0xB103B604 : 0x0263D144, 0, 65, 0); + klaymenBehindSpikes(); + playSound(0, 0x53B8284A); + } else if (which == 3) { + // Klaymen entering from reading a text column + insertKlaymen<KmScene2206>(kScene2206XPositions[getGlobalVar(V_CLICKED_COLUMN_INDEX)], 430); + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) + _klaymen->setDoDeltaX(1); + setMessageList(0x004B8A70); + } else { + // Klaymen entering from the left + insertKlaymen<KmScene2206>(0, 430); + setMessageList(0x004B88B0); + } + + _klaymen->setSoundFlag(true); + _klaymen->setKlaymenIdleTable2(); + +} + +Scene2206::~Scene2206() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX() ? 1 : 0); +} + +uint32 Scene2206::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x800C6694) + readClickedColumn(); + else if (param.asInteger() == 0x402064D8) + sendEntityMessage(_klaymen, 0x1014, _ssButton); + else if (param.asInteger() == 0x11C40840) { + if (getGlobalVar(V_SPIKES_RETRACTED)) + setMessageList(0x004B8948); + else + setMessageList(0x004B8970); + } + break; + case 0x4803: + sendMessage(_asPlatform, 0x4803, 0); + break; + case 0x480B: + if (sender == _ssButton) { + setGlobalVar(V_SPIKES_RETRACTED, getGlobalVar(V_SPIKES_RETRACTED) ? 0 : 1); + if (getGlobalVar(V_SPIKES_RETRACTED)) + sendMessage(_asDoorSpikes, 0x4808, 0); + else + sendMessage(_asDoorSpikes, 0x4809, 0); + } + break; + case 0x4826: + sendEntityMessage(_klaymen, 0x1014, _ssTestTube); + setMessageList(0x004B8988); + break; + case 0x482A: + klaymenBehindSpikes(); + break; + case 0x482B: + klaymenInFrontSpikes(); + break; + } + return messageResult; +} + +void Scene2206::klaymenInFrontSpikes() { + if (getGlobalVar(V_LIGHTS_ON)) { + _palette->addBasePalette(0x41983216, 0, 65, 0); + _palette->startFadeToPalette(12); + } + setSurfacePriority(_sprite1->getSurface(), 100); + setSurfacePriority(_sprite2->getSurface(), 300); + setSurfacePriority(_sprite3->getSurface(), 100); + setSurfacePriority(_asDoorSpikes->getSurface(), 200); + _klaymen->setClipRect(0, 0, 640, 480); +} + +void Scene2206::klaymenBehindSpikes() { + if (!getGlobalVar(V_LIGHTS_ON)) { + _palette->addBasePalette(0xB103B604, 0, 65, 0); + _palette->startFadeToPalette(12); + } + setSurfacePriority(_sprite1->getSurface(), 1100); + setSurfacePriority(_sprite2->getSurface(), 1300); + setSurfacePriority(_sprite3->getSurface(), 1100); + setSurfacePriority(_asDoorSpikes->getSurface(), 1200); + _klaymen->setClipRect(_sprite2->getDrawRect().x, 0, _sprite3->getDrawRect().x2(), _sprite1->getDrawRect().y2()); +} + +void Scene2206::readClickedColumn() { + setGlobalVar(V_CLICKED_COLUMN_INDEX, (_mouseClickPos.x - 354) / 96); + if (getGlobalVar(V_CLICKED_COLUMN_INDEX) > 2) + setGlobalVar(V_CLICKED_COLUMN_INDEX, 2); + setGlobalVar(V_CLICKED_COLUMN_ROW, (_mouseClickPos.y - 183) / 7); + setGlobalVar(V_COLUMN_TEXT_NAME, calcHash("stLineagex")); + setGlobalVar(V_COLUMN_BACK_NAME, 0); + if (ABS(kScene2206XPositions[getGlobalVar(V_CLICKED_COLUMN_INDEX)] - _klaymen->getX()) >= 144) + setMessageList2(kScene2206MessageIds1[getGlobalVar(V_CLICKED_COLUMN_INDEX)]); + else + setMessageList2(kScene2206MessageIds2[getGlobalVar(V_CLICKED_COLUMN_INDEX)]); +} + +static const uint32 kScene2207FileHashes[] = { + 0x33B1E12E, 0x33D1E12E, 0x3311E12E, + 0x3291E12E, 0x3191E12E, 0x3791E12E, + 0x3B91E12E, 0x2391E12E, 0x1391E12E, + 0x3BB1E12E, 0x23B1E12E, 0x13B1E12E +}; + +AsScene2207Elevator::AsScene2207Elevator(NeverhoodEngine *vm, Scene *parentScene) + : AnimatedSprite(vm, 900), _parentScene(parentScene), _pointIndex(0), _destPointIndex(0), _destPointIndexDelta(0) { + + NPoint pt; + + _dataResource.load(0x00524846); + _pointArray = _dataResource.getPointArray(0x005B02B7); + pt = _dataResource.getPoint(0x403A82B1); + _x = pt.x; + _y = pt.y; + createSurface(1100, 129, 103); + startAnimation(getGlobalVar(V_LIGHTS_ON) ? 0xC858CC19 : 0x294B3377, 0, 0); + _newStickFrameIndex = 0; + SetUpdateHandler(&AsScene2207Elevator::update); + SetMessageHandler(&AsScene2207Elevator::handleMessage); + SetSpriteUpdate(&AsScene2207Elevator::suSetPosition); +} + +AsScene2207Elevator::~AsScene2207Elevator() { + _vm->_soundMan->deleteSoundGroup(0x02700413); +} + +void AsScene2207Elevator::update() { + + if (_destPointIndex + _destPointIndexDelta > _pointIndex) { + _pointIndex++; + startAnimation(getGlobalVar(V_LIGHTS_ON) ? 0xC858CC19 : 0x294B3377, _pointIndex, _pointIndex); + _newStickFrameIndex = _pointIndex; + if (_destPointIndex + _destPointIndexDelta == _pointIndex) { + if (_destPointIndexDelta != 0) + _destPointIndexDelta = 0; + else { + _vm->_soundMan->deleteSound(0xD3B02847); + playSound(0, 0x53B8284A); + } + } + } + + if (_destPointIndex + _destPointIndexDelta < _pointIndex) { + _pointIndex--; + if (_pointIndex == 0) + sendMessage(_parentScene, 0x2003, 0); + startAnimation(getGlobalVar(V_LIGHTS_ON) ? 0xC858CC19 : 0x294B3377, _pointIndex, _pointIndex); + _newStickFrameIndex = _pointIndex; + if (_destPointIndex + _destPointIndexDelta == _pointIndex) { + if (_destPointIndexDelta != 0) + _destPointIndexDelta = 0; + else { + _vm->_soundMan->deleteSound(0xD3B02847); + playSound(0, 0x53B8284A); + } + } + } + + if (_pointIndex > 20 && _surface->getPriority() != 900) + sendMessage(_parentScene, 0x2002, 900); + else if (_pointIndex < 20 && _surface->getPriority() != 1100) + sendMessage(_parentScene, 0x2002, 1100); + + AnimatedSprite::update(); + + if (_destPointIndex + _destPointIndexDelta == _pointIndex && _isMoving) { + sendMessage(_parentScene, 0x2004, 0); + _isMoving = false; + } + +} + +void AsScene2207Elevator::suSetPosition() { + _x = (*_pointArray)[_pointIndex].x; + _y = (*_pointArray)[_pointIndex].y - 60; + updateBounds(); +} + +uint32 AsScene2207Elevator::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + moveToY(param.asInteger()); + break; + } + return messageResult; +} + +void AsScene2207Elevator::moveToY(int16 y) { + int16 minDistance = 480; + + if (!_pointArray || _pointArray->size() == 0) + return; + + for (uint i = 0; i < _pointArray->size(); i++) { + int16 distance = ABS(y - (*_pointArray)[i].y); + if (distance < minDistance) { + minDistance = distance; + _destPointIndex = i; + } + } + + if (_destPointIndex != _pointIndex) { + if (_destPointIndex == 0 || _destPointIndex == (int)_pointArray->size() - 1) + _destPointIndexDelta = 0; + else if (_destPointIndex < _pointIndex) + _destPointIndexDelta = -2; + else + _destPointIndexDelta = 2; + _vm->_soundMan->addSound(0x02700413, 0xD3B02847); + _vm->_soundMan->playSoundLooping(0xD3B02847); + } + + _isMoving = true; + +} + +AsScene2207Lever::AsScene2207Lever(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, int doDeltaX) + : AnimatedSprite(vm, 1100), _parentScene(parentScene) { + + _x = x; + _y = y; + createSurface(1010, 71, 73); + setDoDeltaX(doDeltaX); + startAnimation(0x80880090, 0, -1); + _newStickFrameIndex = 0; + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2207Lever::handleMessage); +} + +uint32 AsScene2207Lever::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + sendMessage(_parentScene, 0x4826, 0); + messageResult = 1; + break; + case 0x3002: + gotoNextState(); + stopAnimation(); + break; + case 0x4807: + stLeverUp(); + break; + case 0x480F: + stLeverDown(); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +void AsScene2207Lever::stLeverDown() { + startAnimation(0x80880090, 1, -1); + playSound(0, 0x40581882); + FinalizeState(&AsScene2207Lever::stLeverDownEvent); +} + +void AsScene2207Lever::stLeverDownEvent() { + sendMessage(_parentScene, 0x480F, 0); +} + +void AsScene2207Lever::stLeverUp() { + startAnimation(0x80880090, 6, -1); + _playBackwards = true; + playSound(0, 0x40581882); + FinalizeState(&AsScene2207Lever::stLeverUpEvent); +} + +void AsScene2207Lever::stLeverUpEvent() { + sendMessage(_parentScene, 0x4807, 0); +} + +AsScene2207WallRobotAnimation::AsScene2207WallRobotAnimation(NeverhoodEngine *vm, Scene *parentScene) + : AnimatedSprite(vm, 1200), _idle(true) { + + _x = 309; + _y = 320; + createSurface1(0xCCFD6090, 100); + startAnimation(0xCCFD6090, 0, -1); + _newStickFrameIndex = 0; + loadSound(1, 0x40330872); + loadSound(2, 0x72A2914A); + loadSound(3, 0xD4226080); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2207WallRobotAnimation::handleMessage); +} + +AsScene2207WallRobotAnimation::~AsScene2207WallRobotAnimation() { + _vm->_soundMan->deleteSoundGroup(0x80D00820); +} + +uint32 AsScene2207WallRobotAnimation::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (!_idle) { + if (param.asInteger() == 0x3423093) { + _vm->_soundMan->addSound(0x80D00820, 0x12121943); + _vm->_soundMan->playSoundLooping(0x12121943); + } else if (param.asInteger() == 0x834AB011) { + stopSound(0); + stopSound(1); + stopSound(2); + stopSound(3); + _vm->_soundMan->deleteSound(0x12121943); + } else if (param.asInteger() == 0x3A980501) + playSound(1); + else if (param.asInteger() == 0x2A2AD498) + playSound(2); + else if (param.asInteger() == 0xC4980008) + playSound(3); + else if (param.asInteger() == 0x06B84228) + playSound(0, 0xE0702146); + } + break; + case 0x2006: + stStartAnimation(); + break; + case 0x2007: + stStopAnimation(); + break; + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene2207WallRobotAnimation::stStartAnimation() { + if (!_idle) { + NextState(NULL); + } else { + startAnimation(0xCCFD6090, 0, -1); + _idle = false; + setVisible(true); + } +} + +void AsScene2207WallRobotAnimation::stStopAnimation() { + NextState(&AsScene2207WallRobotAnimation::cbStopAnimation); +} + +void AsScene2207WallRobotAnimation::cbStopAnimation() { + stopAnimation(); + stopSound(0); + stopSound(1); + stopSound(2); + stopSound(3); + _vm->_soundMan->deleteSound(0x12121943); + _idle = true; + setVisible(false); +} + +AsScene2207WallCannonAnimation::AsScene2207WallCannonAnimation(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1200), _idle(true) { + + _x = 309; + _y = 320; + createSurface1(0x8CAA0099, 100); + startAnimation(0x8CAA0099, 0, -1); + _newStickFrameIndex = 0; + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2207WallCannonAnimation::handleMessage); +} + +uint32 AsScene2207WallCannonAnimation::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2006: + stStartAnimation(); + break; + case 0x2007: + stStopAnimation(); + break; + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene2207WallCannonAnimation::stStartAnimation() { + if (!_idle) { + NextState(NULL); + } else { + setVisible(true); + startAnimation(0x8CAA0099, 0, -1); + _idle = false; + } +} + +void AsScene2207WallCannonAnimation::stStopAnimation() { + NextState(&AsScene2207WallCannonAnimation::cbStopAnimation); +} + +void AsScene2207WallCannonAnimation::cbStopAnimation() { + stopAnimation(); + setVisible(false); + _idle = true; +} + +SsScene2207Symbol::SsScene2207Symbol(NeverhoodEngine *vm, uint32 fileHash, int index) + : StaticSprite(vm, fileHash, 100) { + + _x = 330; + _y = 246 + index * 50; + updatePosition(); +} + +Scene2207::Scene2207(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule), _klaymenAtElevator(true), _elevatorSurfacePriority(0) { + + _vm->gameModule()->initCannonSymbolsPuzzle(); + + if (!getSubVar(VA_IS_PUZZLE_INIT, 0x88460852)) + setSubVar(VA_IS_PUZZLE_INIT, 0x88460852, 1); + + SetMessageHandler(&Scene2207::handleMessage); + SetUpdateHandler(&Scene2207::update); + + insertKlaymen<KmScene2207>(0, 0); + _klaymen->setRepl(64, 0); + setMessageList(0x004B38E8); + _asElevator = insertSprite<AsScene2207Elevator>(this); + + if (getGlobalVar(V_LIGHTS_ON)) { + setBackground(0x88C00241); + setPalette(0x88C00241); + insertScreenMouse(0x00245884); + _ssMaskPart1 = insertStaticSprite(0xE20A28A0, 1200); + _ssMaskPart2 = insertStaticSprite(0x688F62A5, 1100); + _ssMaskPart3 = insertStaticSprite(0x0043B038, 1100); + _asTape = insertSprite<AsScene1201Tape>(this, 4, 1100, 277, 428, 0x9148A011); + addCollisionSprite(_asTape); + _asLever = insertSprite<AsScene2207Lever>(this, 527, 333, 0); + addCollisionSprite(_asLever); + _asWallRobotAnimation = insertSprite<AsScene2207WallRobotAnimation>(this); + _asWallCannonAnimation = insertSprite<AsScene2207WallCannonAnimation>(); + _asWallRobotAnimation->setVisible(false); + _asWallCannonAnimation->setVisible(false); + _ssButton = insertSprite<SsCommonButtonSprite>(this, 0x2C4061C4, 100, 0); + _asLever->setClipRect(0, 0, _ssMaskPart3->getDrawRect().x2(), 480); + _klaymen->setClipRect(0, _ssMaskPart1->getDrawRect().y, 640, _ssMaskPart2->getDrawRect().y2()); + _asElevator->setClipRect(0, _ssMaskPart1->getDrawRect().y, 640, _ssMaskPart2->getDrawRect().y2()); + } else { + setGlobalVar(V_SEEN_SYMBOLS_NO_LIGHT, 1); + setBackground(0x05C02A55); + setPalette(0x05C02A55); + insertScreenMouse(0x02A51054); + _ssMaskPart1 = insertStaticSprite(0x980E46A4, 1200); + insertSprite<SsScene2207Symbol>(kScene2207FileHashes[getSubVar(VA_GOOD_CANNON_SYMBOLS_1, 0)], 0); + insertSprite<SsScene2207Symbol>(kScene2207FileHashes[getSubVar(VA_GOOD_CANNON_SYMBOLS_1, 1)], 1); + insertSprite<SsScene2207Symbol>(kScene2207FileHashes[getSubVar(VA_GOOD_CANNON_SYMBOLS_1, 2)], 2); + _asTape = NULL; + _asLever = NULL; + _asWallRobotAnimation = NULL; + _asWallCannonAnimation = NULL; + _ssButton = NULL; + _klaymen->setClipRect(0, _ssMaskPart1->getDrawRect().y, 640, 480); + _asElevator->setClipRect(0, _ssMaskPart1->getDrawRect().y, 640, 480); + } + + _dataResource.load(0x00524846); + setRectList(0x004B38B8); + + sendEntityMessage(_klaymen, 0x1014, _asElevator); + sendMessage(_klaymen, 0x2001, 0); + sendMessage(_asElevator, 0x2000, 480); + + loadSound(1, calcHash("fxFogHornSoft")); + +} + +void Scene2207::update() { + Scene::update(); + if (_elevatorSurfacePriority != 0) { + setSurfacePriority(_asElevator->getSurface(), _elevatorSurfacePriority); + _elevatorSurfacePriority = 0; + } + if (_klaymen->getY() == 423) + _klaymenAtElevator = _klaymen->getX() > 459 && _klaymen->getX() < 525; +} + +uint32 Scene2207::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x0014F275) { + if (_klaymenAtElevator) { + sendMessage(_asElevator, 0x2000, _mouseClickPos.y); + sendEntityMessage(_klaymen, 0x1014, _asElevator); + sendMessage(_klaymen, 0x2001, 0); + } else + cancelMessageList(); + } else if (param.asInteger() == 0x34569073) { + if (_klaymenAtElevator) { + _isKlaymenBusy = true; + sendMessage(_asElevator, 0x2000, 0); + sendEntityMessage(_klaymen, 0x1014, _asElevator); + sendMessage(_klaymen, 0x2001, 0); + } else + cancelMessageList(); + } else if (param.asInteger() == 0x4054C877) { + if (_klaymenAtElevator) { + sendMessage(_asElevator, 0x2000, 480); + sendEntityMessage(_klaymen, 0x1014, _asElevator); + sendMessage(_klaymen, 0x2001, 0); + } else + cancelMessageList(); + } else if (param.asInteger() == 0x0CBC6211) { + sendEntityMessage(_klaymen, 0x1014, _asElevator); + sendMessage(_klaymen, 0x2001, 0); + setRectList(0x004B38B8); + } else if (param.asInteger() == 0x402064D8) + sendEntityMessage(_klaymen, 0x1014, _ssButton); + else if (param.asInteger() == 0x231DA241) { + if (_ssButton) + setMessageList(0x004B38F0); + else + setMessageList(0x004B37D8); + } + break; + case 0x2002: + _elevatorSurfacePriority = param.asInteger(); + break; + case 0x2003: + _isKlaymenBusy = false; + break; + case 0x4807: + sendMessage(_asWallRobotAnimation, 0x2007, 0); + sendMessage(_asWallCannonAnimation, 0x2007, 0); + break; + case 0x480B: + if (sender == _ssButton) { + if (getSubVar(VA_LOCKS_DISABLED, 0x40119852)) { + setSubVar(VA_LOCKS_DISABLED, 0x40119852, 0); + playSound(0, calcHash("fx3LocksDisable")); + } else { + setSubVar(VA_LOCKS_DISABLED, 0x40119852, 1); + playSound(1); + } + } + break; + case 0x480F: + sendMessage(_asWallRobotAnimation, 0x2006, 0); + sendMessage(_asWallCannonAnimation, 0x2006, 0); + _asWallRobotAnimation->setVisible(true); + _asWallCannonAnimation->setVisible(true); + break; + case 0x4826: + if (sender == _asTape) { + if (_klaymen->getY() == 423) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList(0x004B3958); + } + } else if (_klaymenAtElevator) { + SetMessageHandler(&Scene2207::handleMessage2); + sendMessage(_asElevator, 0x2000, 347); + sendEntityMessage(_klaymen, 0x1014, _asElevator); + sendMessage(_klaymen, 0x2001, 0); + } + break; + } + return messageResult; +} + +uint32 Scene2207::handleMessage2(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2002: + _elevatorSurfacePriority = param.asInteger(); + break; + case 0x2004: + SetMessageHandler(&Scene2207::handleMessage); + sendMessage(_klaymen, 0x2005, 0); + sendEntityMessage(_klaymen, 0x1014, _asLever); + setMessageList(0x004B3920); + setRectList(0x004B3948); + break; + } + return messageResult; +} + +static const uint32 kScene2208FileHashes1[] = { + 0x041023CB, 0x041020CB, 0x041026CB, 0x04102ACB, + 0x041032CB, 0x041002CB +}; + +static const uint32 kScene2208FileHashes2[] = { + 0x091206C9, 0x091406C9, 0x091806C9, 0x090006C9, + 0x093006C9, 0x095006C9 +}; + +Scene2208::Scene2208(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _textResource(vm) { + + SpriteResource spriteResource(_vm); + const char *textStart, *textEnd; + + if (!getGlobalVar(V_COLUMN_TEXT_NAME)) + setGlobalVar(V_COLUMN_TEXT_NAME, calcHash("stLineagex")); + + _textResource.load(getGlobalVar(V_COLUMN_TEXT_NAME)); + + textStart = _textResource.getString(getGlobalVar(V_CLICKED_COLUMN_INDEX), textEnd); + while (textStart < textEnd) { + _strings.push_back(textStart); + textStart += strlen(textStart) + 1; + } + + _maxRowIndex = 8 + 10 * (3 - (getGlobalVar(V_COLUMN_TEXT_NAME) == calcHash("stLineagex") ? 1 : 0)); + + _background = new Background(_vm, 0); + _background->createSurface(0, 640, 528); + _background->getSpriteResource().getPosition().y = 480; + addBackground(_background); + setPalette(0x08100289); + addEntity(_palette); + insertPuzzleMouse(0x0028D089, 40, 600); + + _fontSurface = FontSurface::createFontSurface(_vm, 0x0800090C); + + _backgroundSurface = new BaseSurface(_vm, 0, 640, 480); + spriteResource.load(0x08100289, true); + _backgroundSurface->drawSpriteResourceEx(spriteResource, false, false, 0, 0); + + _topBackgroundSurface = new BaseSurface(_vm, 0, 640, 192); + spriteResource.load(!getGlobalVar(V_COLUMN_BACK_NAME) + ? kScene2208FileHashes1[getGlobalVar(V_CLICKED_COLUMN_INDEX) % 6] + : getGlobalVar(V_COLUMN_BACK_NAME), true); + _topBackgroundSurface->drawSpriteResourceEx(spriteResource, false, false, 0, 0); + + _bottomBackgroundSurface = new BaseSurface(_vm, 0, 640, 192); + spriteResource.load(kScene2208FileHashes2[getGlobalVar(V_CLICKED_COLUMN_INDEX) % 6], true); + _bottomBackgroundSurface->drawSpriteResourceEx(spriteResource, false, false, 0, 0); + + SetUpdateHandler(&Scene2208::update); + SetMessageHandler(&Scene2208::handleMessage); + + _visibleRowsCount = 10; + _newRowIndex = (int16)getGlobalVar(V_CLICKED_COLUMN_ROW); + if (_newRowIndex + _visibleRowsCount > _maxRowIndex) + _newRowIndex = _maxRowIndex - _visibleRowsCount; + if (_newRowIndex < 6) + _newRowIndex = 0; + _rowScrollY = 0; + _backgroundScrollY = 48 * _newRowIndex; + _currRowIndex = _newRowIndex; + + for (int16 rowIndex = 0; rowIndex < _visibleRowsCount; rowIndex++) + drawRow(_newRowIndex + rowIndex); + + _background->getSurface()->getSysRect().y = _backgroundScrollY; + +} + +Scene2208::~Scene2208() { + delete _fontSurface; + delete _backgroundSurface; + delete _topBackgroundSurface; + delete _bottomBackgroundSurface; +} + +void Scene2208::update() { + + int16 mouseY = _vm->getMouseY(); + + if (mouseY < 48) { + if (_currRowIndex > 0) + _newRowIndex = _currRowIndex - 1; + } else if (mouseY > 432) { + if (_currRowIndex < _maxRowIndex - _visibleRowsCount) + _newRowIndex = _currRowIndex + 1; + } else { + if (_currRowIndex > _newRowIndex) + _newRowIndex = _currRowIndex; + } + + if (_currRowIndex < _newRowIndex) { + if (_rowScrollY == 0) + drawRow(_currRowIndex + _visibleRowsCount); + _backgroundScrollY += 4; + _rowScrollY += 4; + if (_rowScrollY == 48) { + _rowScrollY = 0; + _currRowIndex++; + } + _background->getSurface()->getSysRect().y = _backgroundScrollY; + } else if (_currRowIndex > _newRowIndex || _rowScrollY > 0) { + if (_rowScrollY == 0) { + drawRow(_currRowIndex - 1); + _currRowIndex--; + } + _backgroundScrollY -= 4; + if (_rowScrollY == 0) + _rowScrollY = 48; + _rowScrollY -= 4; + _background->getSurface()->getSysRect().y = _backgroundScrollY; + } + + Scene::update(); + +} + +uint32 Scene2208::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 40 || param.asPoint().x >= 600) + leaveScene(0); + break; + } + return messageResult; +} + +void Scene2208::drawRow(int16 rowIndex) { + NDrawRect sourceRect; + int16 y = (rowIndex * 48) % 528; + if (rowIndex < 4) { + sourceRect.x = 0; + sourceRect.y = y; + sourceRect.width = 640; + sourceRect.height = 48; + _background->getSurface()->copyFrom(_topBackgroundSurface->getSurface(), 0, y, sourceRect); + } else if (rowIndex > _maxRowIndex - 5) { + sourceRect.x = 0; + sourceRect.y = (rowIndex - _maxRowIndex + 4) * 48; + sourceRect.width = 640; + sourceRect.height = 48; + _background->getSurface()->copyFrom(_bottomBackgroundSurface->getSurface(), 0, y, sourceRect); + } else { + rowIndex -= 4; + sourceRect.x = 0; + sourceRect.y = (rowIndex * 48) % 480; + sourceRect.width = 640; + sourceRect.height = 48; + _background->getSurface()->copyFrom(_backgroundSurface->getSurface(), 0, y, sourceRect); + if (rowIndex < (int)_strings.size()) { + const char *text = _strings[rowIndex]; + _fontSurface->drawString(_background->getSurface(), 95, y, (const byte*)text); + } + } +} + +static const int16 kScene2242XPositions[] = { + 68, 158 +}; + +static const uint32 kScene2242MessageListIds2[] = { + 0x004B3CB8, 0x004B3CD8 +}; + +static const uint32 kScene2242MessageListIds1[] = { + 0x004B3CF8, 0x004B3D20 +}; + +Scene2242::Scene2242(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _isKlaymenInLight(false) { + + SetMessageHandler(&Scene2242::handleMessage); + SetUpdateHandler(&Scene2242::update); + + if (getGlobalVar(V_LIGHTS_ON)) { + setBackground(0x11840E24); + setPalette(0x11840E24); + insertScreenMouse(0x40E20110); + setRectList(0x004B3DC8); + } else { + setBackground(0x25848E24); + setPalette(0x25848E24); + addEntity(_palette); + _palette->copyBasePalette(0, 256, 0); + _palette->addPalette(0x68033B1C, 0, 65, 0); + insertScreenMouse(0x48E20250); + setRectList(0x004B3E18); + } + + _asTape = insertSprite<AsScene1201Tape>(this, 10, 1100, 464, 435, 0x9148A011); + addCollisionSprite(_asTape); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene2242>(200, 430); + setMessageList(0x004B3C18); + } else if (which == 1) { + // Klaymen entering from looking through the window + insertKlaymen<KmScene2242>(530, 430); + setMessageList(0x004B3D60); + } else if (which == 2) { + // Klaymen returning from reading a text column + insertKlaymen<KmScene2242>(kScene2242XPositions[!getGlobalVar(V_CLICKED_COLUMN_INDEX) ? 0 : 1], 430); + setMessageList(0x004B3D48); + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) + _klaymen->setDoDeltaX(1); + } else { + // Klaymen entering from the left + insertKlaymen<KmScene2242>(0, 430); + setMessageList(0x004B3C20); + } + + _klaymen->setSoundFlag(true); + +} + +Scene2242::~Scene2242() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX() ? 1 : 0); +} + +void Scene2242::update() { + if (!getGlobalVar(V_LIGHTS_ON)) { + if (_isKlaymenInLight && _klaymen->getX() < 440) { + _palette->addBasePalette(0x68033B1C, 0, 65, 0); + _palette->startFadeToPalette(12); + _isKlaymenInLight = false; + } else if (!_isKlaymenInLight && _klaymen->getX() >= 440) { + _palette->addBasePalette(0x25848E24, 0, 65, 0); + _palette->startFadeToPalette(12); + _isKlaymenInLight = true; + } + } + Scene::update(); +} + +uint32 Scene2242::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x800C6694) + readClickedColumn(); + break; + case 0x4826: + if (sender == _asTape) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList(0x004B3D50); + } + break; + } + return messageResult; +} + +void Scene2242::readClickedColumn() { + int index; + if (_mouseClickPos.x < 108) { + setGlobalVar(V_COLUMN_TEXT_NAME, 0x04290188); + setGlobalVar(V_CLICKED_COLUMN_INDEX, 42); + setGlobalVar(V_COLUMN_BACK_NAME, calcHash("bgRecPanelStart1")); + index = 0; + } else { + setGlobalVar(V_COLUMN_TEXT_NAME, 0x04290188); + setGlobalVar(V_CLICKED_COLUMN_INDEX, 43); + setGlobalVar(V_COLUMN_BACK_NAME, calcHash("bgRecPanelStart2")); + index = 1; + } + setGlobalVar(V_CLICKED_COLUMN_ROW, (_mouseClickPos.y - 100) / 7); + if (ABS(_klaymen->getX() - kScene2242XPositions[index]) < 133) + setMessageList2(kScene2242MessageListIds1[index]); + else + setMessageList2(kScene2242MessageListIds2[index]); +} + +static const int16 kHallOfRecordsKlaymenXPos[] = { + 68, 157, 246, 335, + 424, 513, 602 +}; + +static const uint32 kHallOfRecordsSceneMessageListIds2[] = { + 0x004B2978, 0x004B2998, 0x004B29B8, 0x004B29D8, + 0x004B29F8, 0x004B2A18, 0x004B2A38 +}; + +static const uint32 kHallOfRecordsSceneMessageListIds1[] = { + 0x004B2A58, 0x004B2A80, 0x004B2AA8, 0x004B2AD0, + 0x004B2AF8, 0x004B2B20, 0x004B2B48 +}; + +HallOfRecordsScene::HallOfRecordsScene(NeverhoodEngine *vm, Module *parentModule, int which, uint32 hallOfRecordsInfoId) + : Scene(vm, parentModule) { + + _hallOfRecordsInfo = _vm->_staticData->getHallOfRecordsInfoItem(hallOfRecordsInfoId); + + SetMessageHandler(&HallOfRecordsScene::handleMessage); + SetUpdateHandler(&Scene::update); + + if (!getGlobalVar(V_LIGHTS_ON) && _hallOfRecordsInfo->bgFilename2) { + setRectList(0x004B2BF8); + setBackground(_hallOfRecordsInfo->bgFilename2); + setPalette(_hallOfRecordsInfo->bgFilename2); + insertScreenMouse(0x14320138); + } else { + setRectList(0x004B2BB8); + setBackground(_hallOfRecordsInfo->bgFilename1); + setPalette(_hallOfRecordsInfo->bgFilename1); + insertScreenMouse(0x63A40028); + } + + if (which < 0) { + // Restoring game + insertKlaymen<KmHallOfRecords>(200, 430); + setMessageList(0x004B2900); + } else if (which == 1) { + // Klaymen entering from the right + insertKlaymen<KmHallOfRecords>(640, 430); + setMessageList(0x004B2910); + } else if (which == 2) { + // Klaymen returning from reading a text column + insertKlaymen<KmHallOfRecords>(kHallOfRecordsKlaymenXPos[getGlobalVar(V_CLICKED_COLUMN_INDEX) - _hallOfRecordsInfo->xPosIndex], 430); + setMessageList(0x004B2B70); + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) + _klaymen->setDoDeltaX(1); + } else { + // Klaymen entering from the left + insertKlaymen<KmHallOfRecords>(0, 430); + setMessageList(0x004B2908); + } + + _klaymen->setSoundFlag(true); + _klaymen->setKlaymenIdleTable2(); + +} + +HallOfRecordsScene::~HallOfRecordsScene() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX() ? 1 : 0); +} + +uint32 HallOfRecordsScene::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x800C6694) + readClickedColumn(); + break; + } + return messageResult; +} + +void HallOfRecordsScene::readClickedColumn() { + int16 index = (_mouseClickPos.x - 23) / 89; + if (index >= _hallOfRecordsInfo->count) + setMessageList2(0x004B2920); + else { + setGlobalVar(V_CLICKED_COLUMN_INDEX, _hallOfRecordsInfo->xPosIndex + index); + setGlobalVar(V_CLICKED_COLUMN_ROW, (_mouseClickPos.y - 100) / 7); + setGlobalVar(V_COLUMN_TEXT_NAME, _hallOfRecordsInfo->txFilename); + if (index == 0 && _hallOfRecordsInfo->bgFilename3) + setGlobalVar(V_COLUMN_BACK_NAME, _hallOfRecordsInfo->bgFilename3); + else + setGlobalVar(V_COLUMN_BACK_NAME, 0); + if (ABS(_klaymen->getX() - kHallOfRecordsKlaymenXPos[index]) < 133) + setMessageList2(kHallOfRecordsSceneMessageListIds1[index]); + else + setMessageList2(kHallOfRecordsSceneMessageListIds2[index]); + } +} + +static const int16 kScene2247XPositions[] = { + 513, 602 +}; + +static const uint32 kScene2247MessageListIds2[] = { + 0x004B54A0, 0x004B54C0 +}; + +static const uint32 kScene2247MessageListIds1[] = { + 0x004B54E0, 0x004B5508 +}; + +Scene2247::Scene2247(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + SetMessageHandler(&Scene2247::handleMessage); + SetUpdateHandler(&Scene::update); + + if (getGlobalVar(V_LIGHTS_ON)) { + setRectList(0x004B5588); + setBackground(0x40339414); + setPalette(0x40339414); + insertScreenMouse(0x3941040B); + } else { + setRectList(0x004B55C8); + setBackground(0x071963E5); + setPalette(0x071963E5); + insertScreenMouse(0x14320138); + } + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene2247>(200, 430); + setMessageList(0x004B5428); + } else if (which == 1) { + // Klaymen entering from the right + insertKlaymen<KmScene2247>(640, 430); + setMessageList(0x004B5438); + } else if (which == 2) { + // Klaymen returning from reading a text column + insertKlaymen<KmScene2247>(kScene2247XPositions[getGlobalVar(V_COLUMN_TEXT_NAME) == 0x0008E486 ? 0 : 1], 430); + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) + _klaymen->setDoDeltaX(1); + setMessageList(0x004B5530); + } else { + // Klaymen entering from the left + insertKlaymen<KmScene2247>(0, 430); + setMessageList(0x004B5430); + } + + _klaymen->setSoundFlag(true); + +} + +Scene2247::~Scene2247() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX() ? 1 : 0); +} + +uint32 Scene2247::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x800C6694) + readClickedColumn(); + break; + } + return messageResult; +} + +void Scene2247::readClickedColumn() { + int index; + if (_mouseClickPos.x < 553) { + setGlobalVar(V_COLUMN_TEXT_NAME, 0x0008E486); + setGlobalVar(V_COLUMN_BACK_NAME, calcHash("bgFatherHeader")); + index = 0; + } else { + setGlobalVar(V_COLUMN_TEXT_NAME, 0x03086004); + setGlobalVar(V_COLUMN_BACK_NAME, calcHash("bgQuaterHeader")); + index = 1; + } + setGlobalVar(V_CLICKED_COLUMN_INDEX, 0); + setGlobalVar(V_CLICKED_COLUMN_ROW, (_mouseClickPos.y - 100) / 7); + if (ABS(_klaymen->getX() - kScene2247XPositions[index]) < 133) + setMessageList2(kScene2247MessageListIds1[index]); + else + setMessageList2(kScene2247MessageListIds2[index]); +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module2200.h b/engines/neverhood/modules/module2200.h new file mode 100644 index 0000000000..af7171dd53 --- /dev/null +++ b/engines/neverhood/modules/module2200.h @@ -0,0 +1,375 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_MODULES_MODULE2200_H +#define NEVERHOOD_MODULES_MODULE2200_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" +#include "neverhood/modules/module1000.h" +#include "neverhood/graphics.h" + +namespace Neverhood { + +// Module2200 + +class Module2200 : public Module { +public: + Module2200(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module2200(); +protected: + int _sceneNum; + void createScene(int sceneNum, int which); + void updateScene(); + void createHallOfRecordsScene(int which, uint32 hallOfRecordsInfoId); +}; + +// Scene2201 + +static const NPoint kSsScene2201PuzzleCubePoints[] = { + {305, 305}, {321, 305}, {336, 305}, {305, 319}, + {321, 319}, {336, 319}, {305, 332}, {321, 332}, + {336, 333} +}; + +static const uint32 kSsScene2201PuzzleCubeFileHashes[] = { + 0x88134A44, 0xAA124340, 0xB8124602, 0xA902464C, + 0x890A4244, 0xA8124642, 0xB812C204, 0x381A4A4C +}; + +class AsScene2201CeilingFan : public AnimatedSprite { +public: + AsScene2201CeilingFan(NeverhoodEngine *vm); +}; + +class AsScene2201Door : public AnimatedSprite { +public: + AsScene2201Door(NeverhoodEngine *vm, Klaymen *klaymen, Sprite *ssDoorLight, bool isOpen); +protected: + Klaymen *_klaymen; + Sprite *_ssDoorLight; + bool _isOpen; + int _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stOpenDoor(); + void stCloseDoor(); +}; + +class SsScene2201PuzzleCube : public StaticSprite { +public: + SsScene2201PuzzleCube(NeverhoodEngine *vm, uint32 positionIndex, uint32 cubeIndex); +}; + +class Scene2201 : public Scene { +public: + Scene2201(NeverhoodEngine *vm, Module *parentModule, int which); + ~Scene2201(); +protected: + NRect _clipRects[2]; + Sprite *_ssDoorLight; + Sprite *_asDoor; + Sprite *_ssDoorButton; + Sprite *_asTape; + bool _isSoundPlaying; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsScene2202PuzzleCube : public StaticSprite { +public: + SsScene2202PuzzleCube(NeverhoodEngine *vm, Scene *parentScene, int16 cubePosition, int16 cubeSymbol); +protected: + Scene *_parentScene; + int16 _cubeSymbol; + int16 _cubePosition; + int16 _newX, _newY; + int16 _xDelta, _yDelta; + int16 _xIncr; + int16 _yIncr; + int16 _errValue; + int16 _counter; + int16 _xFlagPos; + bool _counterDirection; + bool _isMoving; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void suMoveCubeX(); + void suMoveCubeY(); + void moveCube(int16 newCubePosition); + void stopMoving(); +}; + +class Scene2202 : public Scene { +public: + Scene2202(NeverhoodEngine *vm, Module *parentModule, int which); + ~Scene2202(); +protected: + Sprite *_ssMovingCube; + Sprite *_ssDoneMovingCube; + bool _isCubeMoving; + int16 _movingCubePosition; + int _surfacePriority; + bool _leaveScene; + bool _isSolved; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + int16 getFreeCubePosition(int16 index); + bool testIsSolved(); +}; + +class AsCommonKey : public AnimatedSprite { +public: + AsCommonKey(NeverhoodEngine *vm, Scene *parentScene, int keyIndex, int surfacePriority, int16 x, int16 y); +protected: + Scene *_parentScene; + int _keyIndex; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2203Door : public AnimatedSprite { +public: + AsScene2203Door(NeverhoodEngine *vm, Scene *parentScene, uint doorIndex); +protected: + Scene *_parentScene; + Sprite *_otherDoor; + uint _doorIndex; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void openDoor(); + void closeDoor(); +}; + +class Scene2203 : public Scene { +public: + Scene2203(NeverhoodEngine *vm, Module *parentModule, int which); + ~Scene2203(); +protected: + Sprite *_asLeftDoor; + Sprite *_asRightDoor; + Sprite *_ssSmallLeftDoor; + Sprite *_ssSmallRightDoor; + Sprite *_asTape; + Sprite *_asKey; + NRect _leftDoorClipRect; + NRect _rightDoorClipRect; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsScene2205DoorFrame : public StaticSprite { +public: + SsScene2205DoorFrame(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2205 : public Scene { +public: + Scene2205(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + SsCommonPressButton *_ssLightSwitch; + Sprite *_ssDoorFrame; + bool _isKlaymenInLight; + bool _isLightOn; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2206DoorSpikes : public StaticSprite { +public: + AsScene2206DoorSpikes(NeverhoodEngine *vm, uint32 fileHash); +protected: + int _deltaIndex; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void suOpen(); + void suClose(); +}; + +class AsScene2206Platform : public StaticSprite { +public: + AsScene2206Platform(NeverhoodEngine *vm, uint32 fileHash); +protected: + int16 _yDelta; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void suMoveDown(); +}; + +class SsScene2206TestTube : public StaticSprite { +public: + SsScene2206TestTube(NeverhoodEngine *vm, Scene *parentScene, int surfacePriority, uint32 fileHash); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2206 : public Scene { +public: + Scene2206(NeverhoodEngine *vm, Module *parentModule, int which); + ~Scene2206(); +protected: + Sprite *_sprite1; + Sprite *_sprite2; + Sprite *_sprite3; + Sprite *_asDoorSpikes; + Sprite *_ssButton; + Sprite *_asPlatform; + Sprite *_ssTestTube; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void klaymenInFrontSpikes(); + void klaymenBehindSpikes(); + void readClickedColumn(); +}; + +class AsScene2207Elevator : public AnimatedSprite { +public: + AsScene2207Elevator(NeverhoodEngine *vm, Scene *parentScene); + ~AsScene2207Elevator(); +protected: + Scene *_parentScene; + NPointArray *_pointArray; + int16 _pointIndex; + int16 _destPointIndex, _destPointIndexDelta; + bool _isMoving; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void suSetPosition(); + void moveToY(int16 y); +}; + +class AsScene2207Lever : public AnimatedSprite { +public: + AsScene2207Lever(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y, int doDeltaX); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stLeverDown(); + void stLeverDownEvent(); + void stLeverUp(); + void stLeverUpEvent(); +}; + +class AsScene2207WallRobotAnimation : public AnimatedSprite { +public: + AsScene2207WallRobotAnimation(NeverhoodEngine *vm, Scene *parentScene); + ~AsScene2207WallRobotAnimation(); +protected: + bool _idle; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stStartAnimation(); + void stStopAnimation(); + void cbStopAnimation(); +}; + +class AsScene2207WallCannonAnimation : public AnimatedSprite { +public: + AsScene2207WallCannonAnimation(NeverhoodEngine *vm); +protected: + bool _idle; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stStartAnimation(); + void stStopAnimation(); + void cbStopAnimation(); +}; + +class SsScene2207Symbol : public StaticSprite { +public: + SsScene2207Symbol(NeverhoodEngine *vm, uint32 fileHash, int index); +}; + +class Scene2207 : public Scene { +public: + Scene2207(NeverhoodEngine *vm, Module *parentModule); +protected: + Sprite *_asElevator; + Sprite *_ssMaskPart1; + Sprite *_ssMaskPart2; + Sprite *_ssMaskPart3; + Sprite *_asTape; + Sprite *_asLever; + Sprite *_asWallRobotAnimation; + Sprite *_asWallCannonAnimation; + Sprite *_ssButton; + int _elevatorSurfacePriority; + bool _klaymenAtElevator; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 handleMessage2(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2208 : public Scene { +public: + Scene2208(NeverhoodEngine *vm, Module *parentModule, int which); + ~Scene2208(); +protected: + FontSurface *_fontSurface; + BaseSurface *_backgroundSurface; + BaseSurface *_topBackgroundSurface; + BaseSurface *_bottomBackgroundSurface; + TextResource _textResource; + int16 _backgroundScrollY; + int16 _newRowIndex; + int16 _currRowIndex; + int16 _rowScrollY; + int16 _maxRowIndex; + int16 _visibleRowsCount; + Common::Array<const char*> _strings; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void drawRow(int16 rowIndex); +}; + +class Scene2242 : public Scene { +public: + Scene2242(NeverhoodEngine *vm, Module *parentModule, int which); + ~Scene2242(); +protected: + Sprite *_asTape; + bool _isKlaymenInLight; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void readClickedColumn(); +}; + +class HallOfRecordsScene : public Scene { +public: + HallOfRecordsScene(NeverhoodEngine *vm, Module *parentModule, int which, uint32 hallOfRecordsInfoId); + ~HallOfRecordsScene(); +protected: + HallOfRecordsInfo *_hallOfRecordsInfo; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void readClickedColumn(); +}; + +class Scene2247 : public Scene { +public: + Scene2247(NeverhoodEngine *vm, Module *parentModule, int which); + ~Scene2247(); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void readClickedColumn(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE2200_H */ diff --git a/engines/neverhood/modules/module2300.cpp b/engines/neverhood/modules/module2300.cpp new file mode 100644 index 0000000000..34eca14bea --- /dev/null +++ b/engines/neverhood/modules/module2300.cpp @@ -0,0 +1,186 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module2300.h" +#include "neverhood/navigationscene.h" + +namespace Neverhood { + +static const uint32 kModule2300SoundList[] = { + 0x90805C50, 0x90804450, 0xB4005E60, 0x91835066, + 0x90E14440, 0x90F0D1C3, 0 +}; + +Module2300::Module2300(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule), _soundVolume(0) { + + _vm->_soundMan->addSoundList(0x1A214010, kModule2300SoundList); + _vm->_soundMan->setSoundListParams(kModule2300SoundList, true, 50, 600, 10, 150); + + _isWallBroken = getGlobalVar(V_WALL_BROKEN) != 0; + + if (_isWallBroken) { + _vm->_soundMan->setSoundVolume(0x90F0D1C3, 0); + _vm->_soundMan->playSoundLooping(0x90F0D1C3); + } else { + _vm->_soundMan->setSoundParams(0x90F0D1C3, false, 0, 0, 0, 0); + } + + _vm->_soundMan->playTwoSounds(0x1A214010, 0x48498E46, 0x50399F64, 0); + _vm->_soundMan->playTwoSounds(0x1A214010, 0x41861371, 0x43A2507F, 0); + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else if (which == 1) + createScene(2, 0); + else if (which == 2) + createScene(3, 0); + else if (which == 3) + createScene(4, -1); + else if (which == 4) + createScene(1, 3); + else + createScene(0, 1); + +} + +Module2300::~Module2300() { + _vm->_soundMan->deleteGroup(0x1A214010); +} + +void Module2300::createScene(int sceneNum, int which) { + debug("Module2300::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + createNavigationScene(0x004B67B8, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + createNavigationScene(0x004B67E8, which); + if (_isWallBroken) { + _soundVolume = 15; + _vm->_soundMan->setSoundVolume(0x90F0D1C3, 15); + } + break; + case 2: + _vm->gameState().sceneNum = 2; + createNavigationScene(0x004B6878, which); + break; + case 3: + _vm->gameState().sceneNum = 3; + if (getGlobalVar(V_WALL_BROKEN)) + createNavigationScene(0x004B68F0, which); + else { + _vm->_soundMan->setSoundVolume(0x90F0D1C3, _soundVolume); + createNavigationScene(0x004B68A8, which); + if (_isWallBroken) { + _soundVolume = 87; + _vm->_soundMan->setSoundVolume(0x90F0D1C3, 87); + } + } + break; + case 4: + _vm->gameState().sceneNum = 4; + _vm->_soundMan->setTwoSoundsPlayFlag(true); + createSmackerScene(0x20080A0B, true, true, false); + break; + case 9999: + createDemoScene(); + break; + } + SetUpdateHandler(&Module2300::updateScene); + _childObject->handleUpdate(); +} + +void Module2300::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 1) + createScene(1, 4); + else + leaveModule(0); + break; + case 1: + if (_moduleResult == 1) + createScene(0, 0); + else if (_vm->isDemo()) + createScene(9999, 0); + else if (_moduleResult == 2) + createScene(2, 1); + else if (_moduleResult == 3) + createScene(1, 3); + else if (_moduleResult == 4) + createScene(3, 1); + else if (_moduleResult == 5) + leaveModule(3); + else + leaveModule(4); + break; + case 2: + if (_moduleResult == 1) + leaveModule(1); + else + createScene(1, 5); + break; + case 3: + if (_moduleResult == 1) + leaveModule(2); + else + createScene(1, 1); + break; + case 4: + _vm->_soundMan->setTwoSoundsPlayFlag(false); + createScene(1, 2); + break; + case 9999: + createScene(1, -1); + break; + } + } else { + switch (_sceneNum) { + case 1: + if (_isWallBroken && navigationScene()->isWalkingForward() && navigationScene()->getNavigationIndex() == 4 && + navigationScene()->getFrameNumber() % 2) { + _soundVolume++; + _vm->_soundMan->setSoundVolume(0x90F0D1C3, _soundVolume); + } + if (navigationScene()->isWalkingForward() && navigationScene()->getNavigationIndex() == 0 && + navigationScene()->getFrameNumber() == 50) { + _vm->_soundMan->playTwoSounds(0x1A214010, 0x48498E46, 0x50399F64, 0); + _vm->_soundMan->setSoundVolume(0x48498E46, 70); + _vm->_soundMan->setSoundVolume(0x50399F64, 70); + } + break; + case 3: + if (_isWallBroken && navigationScene()->isWalkingForward() && navigationScene()->getFrameNumber() % 2) { + _soundVolume--; + _vm->_soundMan->setSoundVolume(0x90F0D1C3, _soundVolume); + } + break; + } + } +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module2300.h b/engines/neverhood/modules/module2300.h new file mode 100644 index 0000000000..0a1e1d57a4 --- /dev/null +++ b/engines/neverhood/modules/module2300.h @@ -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. + * + */ + +#ifndef NEVERHOOD_MODULES_MODULE2300_H +#define NEVERHOOD_MODULES_MODULE2300_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +// Module2300 + +class Module2300 : public Module { +public: + Module2300(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module2300(); +protected: + int _sceneNum; + bool _isWallBroken; + int _soundVolume; + void createScene(int sceneNum, int which); + void updateScene(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE2300_H */ diff --git a/engines/neverhood/modules/module2400.cpp b/engines/neverhood/modules/module2400.cpp new file mode 100644 index 0000000000..450812a5f3 --- /dev/null +++ b/engines/neverhood/modules/module2400.cpp @@ -0,0 +1,992 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module2400.h" + +namespace Neverhood { + +Module2400::Module2400(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + _vm->_soundMan->addMusic(0x202D1010, 0xB110382D); + + if (which < 0) + createScene(_vm->gameState().sceneNum, _vm->gameState().which); + else + createScene(0, 0); + +} + +Module2400::~Module2400() { + _vm->_soundMan->deleteMusicGroup(0x202D1010); +} + +void Module2400::createScene(int sceneNum, int which) { + debug("Module2400::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _vm->_soundMan->stopMusic(0xB110382D, 0, 0); + _childObject = new Scene2401(_vm, this, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + _vm->_soundMan->startMusic(0xB110382D, 0, 2); + _childObject = new Scene2402(_vm, this, which); + break; + case 2: + _vm->gameState().sceneNum = 2; + _vm->_soundMan->startMusic(0xB110382D, 0, 0); + _childObject = new Scene2403(_vm, this, which); + break; + case 4: + _vm->gameState().sceneNum = 4; + _vm->_soundMan->stopMusic(0xB110382D, 0, 2); + _childObject = new DiskplayerScene(_vm, this, 0); + break; + case 5: + _vm->gameState().sceneNum = 5; + _vm->_soundMan->startMusic(0xB110382D, 0, 2); + _childObject = new Scene2406(_vm, this, which); + break; + case 6: + _vm->gameState().sceneNum = 6; + _vm->_soundMan->stopMusic(0xB110382D, 0, 2); + createSmackerScene(0x20D80001, true, true, false); + break; + case 7: + _vm->gameState().sceneNum = 7; + createStaticScene(0x81523218, 0x2321C81D); + break; + case 8: + _vm->gameState().sceneNum = 8; + createStaticScene(0x08100210, 0x00214089); + break; + case 9: + _vm->gameState().sceneNum = 9; + createStaticScene(0x8C020505, 0x205018C8); + break; + } + SetUpdateHandler(&Module2400::updateScene); + _childObject->handleUpdate(); +} + +void Module2400::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 1) + createScene(1, 0); + else + leaveModule(0); + break; + case 1: + if (_moduleResult == 1) + createScene(5, 0); + else if (_moduleResult == 2) + createScene(7, -1); + else + createScene(0, 1); + break; + case 2: + if (_moduleResult == 1) + createScene(9, -1); + else if (_moduleResult == 2) + createScene(6, -1); + else + createScene(5, 1); + break; + case 4: + createScene(5, 2); + break; + case 5: + if (_moduleResult == 1) + createScene(2, 0); + else if (_moduleResult == 2) + createScene(4, 0); + else if (_moduleResult == 3) + createScene(8, -1); + else + createScene(1, 1); + break; + case 6: + createScene(2, 2); + break; + case 7: + createScene(1, 2); + break; + case 8: + createScene(5, 3); + break; + case 9: + createScene(2, 1); + break; + } + } +} + +static const NPoint kScene2401Points[] = { + {384, 389}, {406, 389}, {429, 389}, + {453, 389}, {477, 389} +}; + +static const uint32 kScene2401FileHashes1[] = { + 0x02842920, 0x02882920, 0x02902920, + 0x02A02920, 0x02C02920, 0x02002920, + 0x03802920, 0x00802920, 0x06802920, + 0x03842920 +}; + +static const uint32 kScene2401FileHashes2[] = { + 0xD0910020, 0xD0910038, 0xD0910008, + 0xD0910068, 0xD09100A8, 0 +}; + +static const uint32 kScene2401FileHashes3[] = { + 0xD0910020, 0xD0910038, 0xD0910008, + 0xD0910068, 0xD09100A8, 0 +}; + +static const NRect kScene2401Rects[] = { + NRect(369, 331, 394, 389), + NRect(395, 331, 419, 389), + NRect(420, 331, 441, 389), + NRect(442, 331, 464, 389), + NRect(465, 331, 491, 389) +}; + +static const uint32 kAsScene2401WaterSpitFileHashes2[] = { + 0x5C044690, 0x5C644690, 0x5CA44690, + 0x5D244690, 0x5E244690 +}; + +static const uint32 kAsScene2401WaterSpitFileHashes1[] = { + 0xF4418408, 0xF4418808, 0xF4419008, + 0xF441A008, 0xCD4F8411 +}; + +AsScene2401WaterSpit::AsScene2401WaterSpit(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1200) { + + _x = 240; + _y = 447; + createSurface(100, 146, 74); + setVisible(false); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2401WaterSpit::handleMessage); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); +} + +uint32 AsScene2401WaterSpit::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x120A0013) + playSound(0, kAsScene2401WaterSpitFileHashes1[_soundIndex]); + break; + case 0x2000: + _x = 240; + _y = 447; + _soundIndex = getSubVar(VA_CURR_WATER_PIPES_LEVEL, param.asInteger()); + startAnimation(kAsScene2401WaterSpitFileHashes2[param.asInteger()], 0, -1); + setVisible(true); + playSound(0, 0x48640244); + break; + case 0x3002: + stopAnimation(); + setVisible(false); + break; + } + return messageResult; +} + +AsScene2401FlowingWater::AsScene2401FlowingWater(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1200), _isWaterFlowing(false) { + + _x = 88; + _y = 421; + createSurface1(0x10203116, 100); + setVisible(false); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2401FlowingWater::handleMessage); +} + +AsScene2401FlowingWater::~AsScene2401FlowingWater() { + _vm->_soundMan->deleteSoundGroup(0x40F11C09); +} + +uint32 AsScene2401FlowingWater::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (_isWaterFlowing && param.asInteger() == 0x02421405) + startAnimationByHash(0x10203116, 0x01084280, 0); + break; + case 0x2002: + if (!_isWaterFlowing) { + _vm->_soundMan->addSound(0x40F11C09, 0x980C1420); + _vm->_soundMan->playSoundLooping(0x980C1420); + startAnimation(0x10203116, 0, -1); + setVisible(true); + _isWaterFlowing = true; + } + break; + case 0x2003: + _vm->_soundMan->deleteSound(0x980C1420); + _isWaterFlowing = false; + break; + case 0x3002: + stopAnimation(); + setVisible(false); + break; + } + return messageResult; +} + +AsScene2401WaterFlushing::AsScene2401WaterFlushing(NeverhoodEngine *vm, int16 x, int16 y) + : AnimatedSprite(vm, 1200), _countdown(0), _flushLoopCount(0) { + + _x = x; + _y = y; + createSurface1(0xB8596884, 100); + setVisible(false); + SetUpdateHandler(&AsScene2401WaterFlushing::update); + SetMessageHandler(&AsScene2401WaterFlushing::handleMessage); +} + +void AsScene2401WaterFlushing::update() { + if (_countdown != 0 && (--_countdown) == 0) { + setDoDeltaX(_vm->_rnd->getRandomNumber(1)); + startAnimation(0xB8596884, 0, -1); + setVisible(true); + } + AnimatedSprite::update(); +} + +uint32 AsScene2401WaterFlushing::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (_flushLoopCount > 0 && param.asInteger() == 0x02421405) { + startAnimationByHash(0xB8596884, 0x01084280, 0); + _flushLoopCount--; + } + break; + case 0x2002: + if (param.asInteger() > 0) { + _flushLoopCount = param.asInteger() - 1; + _countdown = _vm->_rnd->getRandomNumber(3) + 1; + } + break; + case 0x3002: + stopAnimation(); + setVisible(false); + break; + } + return messageResult; +} + +AsScene2401Door::AsScene2401Door(NeverhoodEngine *vm, bool isOpen) + : AnimatedSprite(vm, 1100), _countdown(0), _isOpen(isOpen) { + + _x = 320; + _y = 240; + createSurface1(0x44687810, 100); + _newStickFrameIndex = STICK_LAST_FRAME; + if (_isOpen) { + stopAnimation(); + setVisible(false); + _countdown = 48; + } else { + startAnimation(0x44687810, 0, -1); + _newStickFrameIndex = 0; + } + SetUpdateHandler(&AsScene2401Door::update); + SetMessageHandler(&AsScene2401Door::handleMessage); +} + +void AsScene2401Door::update() { + if (_isOpen && _countdown != 0 && (--_countdown) == 0) { + _isOpen = false; + setVisible(true); + startAnimation(0x44687810, -1, -1); + _newStickFrameIndex = 0; + _playBackwards = true; + playSound(0, calcHash("fxDoorClose38")); + } + AnimatedSprite::update(); +} + +uint32 AsScene2401Door::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2004: + if (_isOpen) + _countdown = 168; + messageResult = _isOpen ? 1 : 0; + break; + case 0x3002: + gotoNextState(); + break; + case 0x4808: + if (!_isOpen) { + _countdown = 168; + _isOpen = true; + setVisible(true); + startAnimation(0x44687810, 0, -1); + playSound(0, calcHash("fxDoorOpen38")); + NextState(&AsScene2401Door::stDoorOpenFinished); + } + break; + } + return messageResult; +} + +void AsScene2401Door::stDoorOpenFinished() { + stopAnimation(); + setVisible(false); +} + +Scene2401::Scene2401(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _countdown1(0), _countdown2(0), _unkFlag(false), + _soundToggle(false), _asWaterSpitIndex(0) { + + _vm->gameModule()->initWaterPipesPuzzle(); + + SetMessageHandler(&Scene2401::handleMessage); + SetUpdateHandler(&Scene2401::update); + + setRectList(0x004B3140); + setBackground(0x8C030206); + setPalette(0x8C030206); + addEntity(_palette); + _palette->addBasePalette(0x8C030206, 0, 256, 0); + _palette->addPalette(0x91D3A391, 0, 65, 0); + insertScreenMouse(0x302028C8); + + _sprite1 = insertStaticSprite(0x2E068A23, 200); + insertStaticSprite(0x401410A6, 200); + _asFlowingWater = insertSprite<AsScene2401FlowingWater>(); + insertStaticSprite(0x90C0A4B4, 200); + _ssButton = insertSprite<SsCommonButtonSprite>(this, 0x0092916A, 100, 0); + _ssFloorButton = insertSprite<SsCommonFloorButton>(this, 0x28001120, 0x00911068, 100, 0); + + for (uint i = 0; i < 5; i++) + _asWaterFlushing[i] = insertSprite<AsScene2401WaterFlushing>(kScene2401Points[i].x, kScene2401Points[i].y); + + for (uint i = 0; i < 10; i++) { + _ssWaterPipes[i] = insertStaticSprite(kScene2401FileHashes1[i], 300); + _ssWaterPipes[i]->setVisible(false); + } + + _asWaterSpit[0] = insertSprite<AsScene2401WaterSpit>(); + _asWaterSpit[1] = insertSprite<AsScene2401WaterSpit>(); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene2401>(200, 447); + setMessageList(0x004B2F70); + _asDoor = insertSprite<AsScene2401Door>(false); + } else if (which == 1) { + // Klaymen entering from the back + insertKlaymen<KmScene2401>(280, 413); + setMessageList(0x004B2F80); + _palette->addBasePalette(0xB103B604, 0, 65, 0); + _palette->addPalette(0xB103B604, 0, 65, 0); + _asDoor = insertSprite<AsScene2401Door>(true); + } else { + // Klaymen entering from the left + insertKlaymen<KmScene2401>(-20, 447); + setMessageList(0x004B2F78); + _asDoor = insertSprite<AsScene2401Door>(false); + } + +} + +void Scene2401::update() { + + if (_countdown1 != 0 && (--_countdown1) == 0) { + if (_pipeStatus > 0 && _pipeStatus <= 10) + _ssWaterPipes[_pipeStatus - 1]->setVisible(false); + if (_pipeStatus >= 10) { + bool puzzleSolved = true, waterInside = false; + for (uint pipeIndex = 0; pipeIndex < 5; pipeIndex++) { + if (getSubVar(VA_CURR_WATER_PIPES_LEVEL, pipeIndex) != getSubVar(VA_GOOD_WATER_PIPES_LEVEL, pipeIndex)) + puzzleSolved = false; + if (getSubVar(VA_CURR_WATER_PIPES_LEVEL, pipeIndex) != 0) + waterInside = true; + } + if (puzzleSolved) { + setGlobalVar(V_NOTES_DOOR_UNLOCKED, 1); + setGlobalVar(V_NOTES_PUZZLE_SOLVED, 1); + sendMessage(_asDoor, 0x4808, 0); + } else if (waterInside) { + playPipeSound(0xD0431020); + for (uint i = 0; i < 5; i++) { + sendMessage(_asWaterFlushing[i], 0x2002, getSubVar(VA_CURR_WATER_PIPES_LEVEL, i)); + setSubVar(VA_CURR_WATER_PIPES_LEVEL, i, 0); + } + } + } else if (_pipeStatus >= 5) { + _ssWaterPipes[_pipeStatus]->setVisible(true); + _countdown1 = 8; + playPipeSound(kScene2401FileHashes3[getSubVar(VA_CURR_WATER_PIPES_LEVEL, _pipeStatus - 5)]); + } else { + _ssWaterPipes[_pipeStatus]->setVisible(true); + _countdown1 = _pipeStatus == 4 ? 16 : 8; + playPipeSound(kScene2401FileHashes3[getSubVar(VA_GOOD_WATER_PIPES_LEVEL, _pipeStatus)]); + } + _pipeStatus++; + } + + if (_countdown2 != 0 && (--_countdown2) == 0) + sendMessage(_asFlowingWater, 0x2003, 0); + + Scene::update(); + +} + +uint32 Scene2401::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x402064D8) + sendEntityMessage(_klaymen, 0x1014, _ssButton); + else if (param.asInteger() == 0x02144CB1) + sendEntityMessage(_klaymen, 0x1014, _ssFloorButton); + else if (param.asInteger() == 0x11C40840) { + if (getGlobalVar(V_NOTES_DOOR_UNLOCKED) && sendMessage(_asDoor, 0x2004, 0)) + setMessageList(0x004B3090); + else + setMessageList(0x004B30B0); + } else if (param.asInteger() == 0x412722C0) { + if (_countdown2 > 0 && getGlobalVar(V_HAS_TEST_TUBE)) { + _countdown2 = 144; + setMessageList(0x004B3020); + } else + setMessageList(0x004B3050); + } else if (param.asInteger() == 0x21142050) { + if (_unkFlag && _countdown1 == 0 && !getGlobalVar(V_NOTES_PUZZLE_SOLVED)) + setMessageList(0x004B2FA8); + else + setMessageList(0x004B2FC8); + } else if (param.asInteger() == 0x87441031) + setSurfacePriority(_sprite1->getSurface(), 1100); + else if (param.asInteger() == 0x80C40322) { + setSurfacePriority(_sprite1->getSurface(), 200); + cancelMessageList(); + _unkFlag = true; + } else if (param.asInteger() == 0x09C4B40A && _countdown2 > 12) + _countdown2 = 12; + break; + case 0x2000: + messageResult = 0; + for (uint32 i = 0; i < 5; i++) + if (kScene2401Rects[i].contains(_mouseClickPos.x, _mouseClickPos.y)) { + messageResult = i; + break; + } + break; + case 0x2001: + sendMessage(_asWaterSpit[_asWaterSpitIndex], 0x2000, param.asInteger()); + _asWaterSpitIndex = (_asWaterSpitIndex + 1) & 1; + incSubVar(VA_CURR_WATER_PIPES_LEVEL, param.asInteger(), 1); + if (getSubVar(VA_CURR_WATER_PIPES_LEVEL, param.asInteger()) >= 5) + setSubVar(VA_CURR_WATER_PIPES_LEVEL, param.asInteger(), 4); + break; + case 0x480B: + if (sender == _ssButton) { + _pipeStatus = 0; + _countdown1 = 8; + } else if (sender == _ssFloorButton && getGlobalVar(V_WATER_RUNNING)) { + _countdown2 = 144; + sendMessage(_asFlowingWater, 0x2002, 0); + playSound(0, 0xE1130324); + } + break; + case 0x482A: + _palette->addBasePalette(0xB103B604, 0, 65, 0); + _palette->startFadeToPalette(12); + break; + case 0x482B: + _palette->addBasePalette(0x91D3A391, 0, 65, 0); + _palette->startFadeToPalette(12); + break; + } + return messageResult; +} + +void Scene2401::playPipeSound(uint32 fileHash) { + playSound(_soundToggle ? 0 : 1, fileHash); + _soundToggle = !_soundToggle; +} + +static const uint32 kScene2402FileHashes[] = { + 0xD0910020, 0xD0910038, 0xD0910008, + 0xD0910068, 0xD09100A8 +}; + +AsScene2402Door::AsScene2402Door(NeverhoodEngine *vm, Scene *parentScene, bool isOpen) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _isOpen(isOpen), _countdown(0) { + + _x = 320; + _y = 240; + createSurface1(0x80495831, 100); + if (_isOpen) { + startAnimation(0x80495831, -1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + _countdown = 48; + } else { + stopAnimation(); + setVisible(false); + } + SetUpdateHandler(&AsScene2402Door::update); + SetMessageHandler(&AsScene2402Door::handleMessage); +} + +void AsScene2402Door::update() { + if (_isOpen && _countdown != 0 && (--_countdown) == 0) { + _isOpen = false; + setVisible(true); + startAnimation(0x80495831, -1, -1); + _playBackwards = true; + playSound(0, calcHash("fxDoorClose38")); + NextState(&AsScene2402Door::stDoorClosingFinished); + } + AnimatedSprite::update(); +} + +uint32 AsScene2402Door::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + if (_isOpen) + _countdown = 144; + messageResult = _isOpen ? 1 : 0; + break; + case 0x3002: + gotoNextState(); + break; + case 0x4808: + _countdown = 144; + _isOpen = true; + setVisible(true); + startAnimation(0x80495831, 0, -1); + _newStickFrameIndex = STICK_LAST_FRAME; + playSound(0, calcHash("fxDoorOpen38")); + break; + } + return messageResult; +} + +void AsScene2402Door::stDoorClosingFinished() { + sendMessage(_parentScene, 0x2001, 0); + setVisible(false); +} + +AsScene2402TV::AsScene2402TV(NeverhoodEngine *vm, Klaymen *klaymen) + : AnimatedSprite(vm, 1100), _klaymen(klaymen), _countdown1(0), _countdown2(0) { + + _x = 260; + _y = 210; + createSurface(100, 127, 90); + setDoDeltaX(1); + SetMessageHandler(&Sprite::handleMessage); + if (!getGlobalVar(V_TV_JOKE_TOLD)) { + loadSound(0, 0x58208810); + _countdown1 = 48; + startAnimation(0x4919397A, 0, -1); + _newStickFrameIndex = 0; + SetUpdateHandler(&AsScene2402TV::upWait); + } else { + int16 frameIndex; + if (_klaymen->getX() > 320) + _currFrameIndex = 29; + frameIndex = CLIP<int16>((_klaymen->getX() - _x + 150) / 10, 0, 29); + startAnimation(0x050A0103, frameIndex, -1); + _newStickFrameIndex = frameIndex; + _countdown1 = 0; + SetUpdateHandler(&AsScene2402TV::upFocusKlaymen); + } +} + +AsScene2402TV::~AsScene2402TV() { + _vm->_soundMan->deleteSoundGroup(0x01520123); +} + +void AsScene2402TV::upWait() { + if (_countdown1 != 0 && (--_countdown1) == 0) { + startAnimation(0x4919397A, 0, -1); + SetMessageHandler(&AsScene2402TV::hmJoke); + NextState(&AsScene2402TV::stJokeFinished); + } + AnimatedSprite::update(); +} + +void AsScene2402TV::upFocusKlaymen() { + int16 frameIndex = CLIP<int16>((_klaymen->getX() - _x + 150) / 10, 0, 29); + if (frameIndex != _currFrameIndex) { + if (frameIndex > _currFrameIndex) + _currFrameIndex++; + else if (frameIndex < _currFrameIndex) + _currFrameIndex--; + startAnimation(0x050A0103, _currFrameIndex, -1); + _newStickFrameIndex = _currFrameIndex; + if (_countdown2 == 0) { + _vm->_soundMan->addSound(0x01520123, 0xC42D4528); + _vm->_soundMan->playSoundLooping(0xC42D4528); + } + _countdown2 = 5; + } else if (_countdown2 != 0 && (--_countdown2 == 0)) + _vm->_soundMan->deleteSound(0xC42D4528); + AnimatedSprite::update(); +} + +void AsScene2402TV::stJokeFinished() { + setGlobalVar(V_TV_JOKE_TOLD, 1); + startAnimation(0x050A0103, 0, -1); + _newStickFrameIndex = 0; + SetUpdateHandler(&AsScene2402TV::upFocusKlaymen); +} + +uint32 AsScene2402TV::hmJoke(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x431EA0B0) + playSound(0); + break; + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +Scene2402::Scene2402(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _countdown(0), _soundToggle(false) { + + Sprite *tempSprite; + + SetMessageHandler(&Scene2402::handleMessage); + SetUpdateHandler(&Scene2402::update); + + setRectList(0x004AF900); + setBackground(0x81660220); + setPalette(0x81660220); + insertScreenMouse(0x6022481E); + _asTape = insertSprite<AsScene1201Tape>(this, 9, 1100, 286, 409, 0x9148A011); + addCollisionSprite(_asTape); + _ssButton = insertSprite<SsCommonButtonSprite>(this, 0x15288120, 100, 0); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene2402>(198, 404); + setMessageList(0x004AF7C8); + } else if (which == 1) { + // Klaymen entering from the right + insertKlaymen<KmScene2402>(660, 404); + setMessageList(0x004AF7D8); + } else if (which == 2) { + // Klaymen returning from looking through the window + insertKlaymen<KmScene2402>(409, 404); + _klaymen->setDoDeltaX(getGlobalVar(V_KLAYMEN_IS_DELTA_X) ? 1 : 0); + setMessageList(0x004AF888); + } else { + // Klaymen entering from the left + insertKlaymen<KmScene2402>(0, 404); + setMessageList(0x004AF7D0); + } + + tempSprite = insertStaticSprite(0x081A60A8, 1100); + _ssDoorFrame = (StaticSprite*)insertStaticSprite(0x406C0AE0, 1100); + _klaymen->setClipRect(_ssDoorFrame->getDrawRect().x, 0, 639, tempSprite->getDrawRect().y2()); + _asDoor = insertSprite<AsScene2402Door>(this, which == 0); + insertSprite<AsScene2402TV>(_klaymen); + insertStaticSprite(0x3A01A020, 200); + +} + +Scene2402::~Scene2402() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX() ? 1 : 0); +} + +void Scene2402::update() { + if (_countdown != 0 && (--_countdown) == 0) { + if (_pipeStatus >= 10) { + sendMessage(_asDoor, 0x4808, 0); + _ssDoorFrame->loadSprite(0x00B415E0, kSLFDefDrawOffset | kSLFDefPosition); + } else if (_pipeStatus >= 5) { + _countdown = 8; + playPipeSound(kScene2402FileHashes[getSubVar(VA_CURR_WATER_PIPES_LEVEL, _pipeStatus - 5)]); + } else { + _countdown = _pipeStatus == 4 ? 16 : 8; + playPipeSound(kScene2402FileHashes[getSubVar(VA_GOOD_WATER_PIPES_LEVEL, _pipeStatus)]); + } + _pipeStatus++; + } + Scene::update(); +} + +uint32 Scene2402::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x402064D8) + sendEntityMessage(_klaymen, 0x1014, _ssButton); + else if (param.asInteger() == 0x01C66840) { + if (sendMessage(_asDoor, 0x2000, 0)) + setMessageList(0x004AF800); + else + setMessageList(0x004AF818); + } + break; + case 0x2001: + _ssDoorFrame->loadSprite(0x406C0AE0, kSLFDefDrawOffset | kSLFDefPosition); + break; + case 0x480B: + if (sender == _ssButton) { + _pipeStatus = 0; + _countdown = 8; + } + break; + case 0x4826: + if (sender == _asTape) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList(0x004AF890); + } + break; + } + return messageResult; +} + +void Scene2402::playPipeSound(uint32 fileHash) { + playSound(_soundToggle ? 0 : 1, fileHash); + _soundToggle = !_soundToggle; +} + +Scene2403::Scene2403(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + Sprite *tempSprite1, *tempSprite2, *tempSprite3; + + SetMessageHandler(&Scene2403::handleMessage); + setBackground(0x0C05060C); + setPalette(0x0C05060C); + _palette->addPalette(0x414364B0, 0, 65, 0); + insertScreenMouse(0x506080C8); + _asTape = insertSprite<AsScene1201Tape>(this, 2, 1100, 480, 454, 0x9148A011); + addCollisionSprite(_asTape); + _asLightCord = insertSprite<AsScene2803LightCord>(this, 0xA1095A10, 0x836D3813, 368, 200); + _asLightCord->setClipRect(0, 25, 640, 480); + + if (which < 0) { + // Restoring game + _isClimbingLadder = false; + insertKlaymen<KmScene2403>(220, 449); + setMessageList(0x004B5C98); + setRectList(0x004B5E18); + } else if (which == 1) { + // Klaymen returning from looking through the window + _isClimbingLadder = false; + insertKlaymen<KmScene2403>(433, 449); + setMessageList(0x004B5D70); + setRectList(0x004B5E18); + } else if (which == 2) { + // Klaymen standing around after the critter video + _isClimbingLadder = false; + insertKlaymen<KmScene2403>(440, 449); + _klaymen->setDoDeltaX(1); + setMessageList(0x004B5C98); + setRectList(0x004B5E18); + } else { + // Klaymen coming up from ladder + _isClimbingLadder = true; + insertKlaymen<KmScene2403>(122, 599); + setMessageList(0x004B5CA0); + setRectList(0x004B5E28); + } + + _ssButton = insertSprite<SsCommonButtonSprite>(this, 0x3130B0EB, 100, 0); + tempSprite1 = insertStaticSprite(0x20C24220, 1100); + tempSprite2 = insertStaticSprite(0x03080900, 1300); + tempSprite3 = insertSprite<AsScene1002KlaymenLadderHands>(_klaymen); + tempSprite3->setClipRect(tempSprite1->getDrawRect().x, 0, 640, tempSprite2->getDrawRect().y2()); + _klaymen->setClipRect(tempSprite1->getDrawRect().x, 0, 640, tempSprite2->getDrawRect().y2()); + loadSound(1, calcHash("fxFogHornSoft")); +} + +uint32 Scene2403::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x040424D0) + sendEntityMessage(_klaymen, 0x1014, _ssButton); + else if (param.asInteger() == 0x180CE614) + sendEntityMessage(_klaymen, 0x1014, _asLightCord); + break; + case 0x2000: + _isClimbingLadder = true; + setRectList(0x004B5E28); + break; + case 0x2001: + _isClimbingLadder = false; + setRectList(0x004B5E18); + break; + case 0x480B: + if (sender == _ssButton) { + if (getSubVar(VA_LOCKS_DISABLED, 0x304008D2)) { + setSubVar(VA_LOCKS_DISABLED, 0x304008D2, 0); + playSound(0, calcHash("fx3LocksDisable")); + } else { + setSubVar(VA_LOCKS_DISABLED, 0x304008D2, 1); + playSound(1); + } + } + break; + case 0x480F: + if (sender == _asLightCord) + leaveScene(2); + break; + case 0x4826: + if (sender == _asTape && !_isClimbingLadder) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList(0x004B5D98); + } + break; + } + return messageResult; +} + +Scene2406::Scene2406(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + Sprite *tempSprite1, *tempSprite2; + + if (getGlobalVar(V_HAS_FINAL_KEY) && getGlobalVar(V_KEY3_LOCATION) == 0) + setGlobalVar(V_KEY3_LOCATION, 2); + + SetMessageHandler(&Scene2406::handleMessage); + + setRectList(0x004B78C8); + insertScreenMouse(0xB03001A8); + + if (getGlobalVar(V_KEY3_LOCATION) == 2) { + _asKey = insertSprite<AsCommonKey>(this, 2, 1100, 560, 409); + addCollisionSprite(_asKey); + } + + _asTape = insertSprite<AsScene1201Tape>(this, 5, 1100, 456, 409, 0x9148A011); + addCollisionSprite(_asTape); + tempSprite2 = insertStaticSprite(0x19625293, 1100); + _clipRects[0].x1 = 0; + _clipRects[0].y1 = 0; + _clipRects[0].x2 = tempSprite2->getDrawRect().x2(); + _clipRects[0].y2 = 480; + + if (getGlobalVar(V_SPIKES_RETRACTED)) { + setBackground(0x1A0B0304); + setPalette(0x1A0B0304); + tempSprite1 = insertStaticSprite(0x32923922, 1100); + } else { + setBackground(0x0A038595); + setPalette(0x0A038595); + tempSprite1 = insertStaticSprite(0x1712112A, 1100); + } + + tempSprite2 = insertStaticSprite(0x22300924, 1300); + _clipRects[1].x1 = tempSprite1->getDrawRect().x; + _clipRects[1].y1 = tempSprite2->getDrawRect().y; + _clipRects[1].x2 = 640; + _clipRects[1].y2 = 480; + + if (which < 0) { + // Restoring game + _isClimbingLadder = false; + insertKlaymen<KmScene2406>(307, 404, _clipRects, 2); + setMessageList(0x004B76C8); + setRectList(0x004B78C8); + } else if (which == 1) { + // Klaymen coming down the ladder + _isClimbingLadder = true; + insertKlaymen<KmScene2406>(253, -16, _clipRects, 2); + setMessageList(0x004B76D8); + setRectList(0x004B78D8); + } else if (which == 2) { + // Klaymen returning from the diskplayer + _isClimbingLadder = false; + insertKlaymen<KmScene2406>(480, 404, _clipRects, 2); + setMessageList(0x004B77C0); + setRectList(0x004B78C8); + } else if (which == 3) { + // Klaymen returning from looking through the window + _isClimbingLadder = false; + insertKlaymen<KmScene2406>(387, 404, _clipRects, 2); + setMessageList(0x004B7810); + setRectList(0x004B78C8); + } else { + // Klaymen entering from the left + _isClimbingLadder = false; + insertKlaymen<KmScene2406>(0, 404, _clipRects, 2); + setMessageList(0x004B76D0); + setRectList(0x004B78C8); + } + + tempSprite2 = insertSprite<AsScene1002KlaymenLadderHands>(_klaymen); + tempSprite2->setClipRect(_clipRects[1]); + +} + +uint32 Scene2406::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x41062804) { + if (getGlobalVar(V_SPIKES_RETRACTED)) + setMessageList(0x004B7758); + else + setMessageList(0x004B7738); + } + break; + case 0x2000: + _isClimbingLadder = true; + setRectList(0x004B78D8); + break; + case 0x2001: + _isClimbingLadder = false; + setRectList(0x004B78C8); + break; + case 0x4826: + if (sender == _asTape && !_isClimbingLadder) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList(0x004B77C8); + } else if (sender == _asKey && !_isClimbingLadder) { + sendEntityMessage(_klaymen, 0x1014, _asKey); + setMessageList(0x004B77D8); + } + break; + } + return messageResult; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module2400.h b/engines/neverhood/modules/module2400.h new file mode 100644 index 0000000000..b50fff91c4 --- /dev/null +++ b/engines/neverhood/modules/module2400.h @@ -0,0 +1,182 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_MODULES_MODULE2400_H +#define NEVERHOOD_MODULES_MODULE2400_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" +#include "neverhood/gamemodule.h" +#include "neverhood/modules/module1000.h" +#include "neverhood/modules/module1100.h" +#include "neverhood/modules/module1200.h" +#include "neverhood/modules/module2100.h" +#include "neverhood/modules/module2200.h" +#include "neverhood/modules/module2800.h" +#include "neverhood/diskplayerscene.h" + +namespace Neverhood { + +// Module2400 + +class Module2400 : public Module { +public: + Module2400(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module2400(); +protected: + int _sceneNum; + void createScene(int sceneNum, int which); + void updateScene(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2401WaterSpit : public AnimatedSprite { +public: + AsScene2401WaterSpit(NeverhoodEngine *vm); +protected: + int _soundIndex; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2401FlowingWater : public AnimatedSprite { +public: + AsScene2401FlowingWater(NeverhoodEngine *vm); + virtual ~AsScene2401FlowingWater(); +protected: + bool _isWaterFlowing; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2401WaterFlushing : public AnimatedSprite { +public: + AsScene2401WaterFlushing(NeverhoodEngine *vm, int16 x, int16 y); +protected: + int _countdown; + int _flushLoopCount; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2401Door : public AnimatedSprite { +public: + AsScene2401Door(NeverhoodEngine *vm, bool isOpen); +protected: + int _countdown; + bool _isOpen; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stDoorOpenFinished(); +}; + +class Scene2401 : public Scene { +public: + Scene2401(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_sprite1; + Sprite *_asFlowingWater; + Sprite *_ssButton; + Sprite *_ssFloorButton; + Sprite *_asWaterSpit[2]; + Sprite *_ssWaterPipes[10]; + Sprite *_asWaterFlushing[5]; + Sprite *_asDoor; + bool _soundToggle; + bool _unkFlag; + int _countdown1; + int _countdown2; + int _pipeStatus; + int _asWaterSpitIndex; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void playPipeSound(uint32 fileHash); +}; + +class AsScene2402Door : public AnimatedSprite { +public: + AsScene2402Door(NeverhoodEngine *vm, Scene *parentScene, bool isOpen); +protected: + Scene *_parentScene; + int _countdown; + bool _isOpen; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void stDoorClosingFinished(); +}; + +class AsScene2402TV : public AnimatedSprite { +public: + AsScene2402TV(NeverhoodEngine *vm, Klaymen *klaymen); + virtual ~AsScene2402TV(); +protected: + Klaymen *_klaymen; + int _countdown1; + int _countdown2; + void upWait(); + void upFocusKlaymen(); + void stJokeFinished(); + uint32 hmJoke(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2402 : public Scene { +public: + Scene2402(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Scene2402(); +protected: + Sprite *_asDoor; + Sprite *_ssButton; + Sprite *_asTape; + StaticSprite *_ssDoorFrame; + int _pipeStatus; + int _countdown; + bool _soundToggle; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void playPipeSound(uint32 fileHash); +}; + +class Scene2403 : public Scene { +public: + Scene2403(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_ssButton; + Sprite *_asTape; + Sprite *_asKey; + Sprite *_asLightCord; + bool _isClimbingLadder; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2406 : public Scene { +public: + Scene2406(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_asTape; + Sprite *_asKey; + NRect _clipRects[2]; + bool _isClimbingLadder; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE2400_H */ diff --git a/engines/neverhood/modules/module2500.cpp b/engines/neverhood/modules/module2500.cpp new file mode 100644 index 0000000000..a997b5aab1 --- /dev/null +++ b/engines/neverhood/modules/module2500.cpp @@ -0,0 +1,546 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module2500.h" +#include "neverhood/modules/module1600.h" + +namespace Neverhood { + +static const uint32 kScene2505StaticSprites[] = { + 0x4000A226, 0 +}; + +static const NRect kScene2505ClipRect = NRect(0, 0, 564, 480); + +static const uint32 kScene2506StaticSprites[] = { + 0x4027AF02, 0 +}; + +static const NRect kScene2506ClipRect = NRect(0, 0, 640, 441); + +static const uint32 kScene2508StaticSprites1[] = { + 0x2F08E610, 0xD844E6A0, 0 +}; + +static const NRect kScene2508ClipRect1 = NRect(0, 0, 594, 448); + +static const uint32 kScene2508StaticSprites2[] = { + 0x2F08E610, 0 +}; + +static const NRect kScene2508ClipRect2 = NRect(0, 0, 594, 448); + +Module2500::Module2500(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule), _soundIndex(0) { + + _vm->_soundMan->addMusic(0x29220120, 0x05343184); + _vm->_soundMan->startMusic(0x05343184, 0, 0); + SetMessageHandler(&Module2500::handleMessage); + + if (which < 0) + createScene(_vm->gameState().sceneNum, _vm->gameState().which); + else + createScene(0, 0); + + loadSound(0, 0x00880CCC); + loadSound(1, 0x00880CC0); + loadSound(2, 0x00880CCC); + loadSound(3, 0x00880CC0); + +} + +Module2500::~Module2500() { + _vm->_soundMan->deleteMusicGroup(0x29220120); +} + +void Module2500::createScene(int sceneNum, int which) { + debug("Module2500::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _childObject = new Scene2501(_vm, this, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + _vm->gameState().which = which; + createScene2704(which, 0x004B01B8, 220); + break; + case 2: + _vm->gameState().sceneNum = 2; + _vm->gameState().which = which; + if (getGlobalVar(V_WORLDS_JOINED)) + createScene2704(which, 0x004B01E0, 150); + else + createScene2704(which, 0x004B0208, 150); + break; + case 3: + _vm->gameState().sceneNum = 3; + _childObject = new Scene2504(_vm, this, which); + break; + case 4: + _vm->gameState().sceneNum = 4; + _vm->gameState().which = which; + createScene2704(which, 0x004B0230, 150, kScene2505StaticSprites, &kScene2505ClipRect); + break; + case 5: + setGlobalVar(V_CAR_DELTA_X, 1); + _vm->gameState().sceneNum = 5; + _vm->gameState().which = which; + createScene2704(which, 0x004B0268, 150, kScene2506StaticSprites, &kScene2506ClipRect); + break; + case 6: + _vm->gameState().sceneNum = 6; + _vm->gameState().which = which; + createScene2704(which, 0x004B02A0, 150); + break; + case 7: + _vm->gameState().sceneNum = 7; + _vm->gameState().which = which; + if (getGlobalVar(V_ENTRANCE_OPEN)) + createScene2704(which, 0x004B02C8, 150, kScene2508StaticSprites1, &kScene2508ClipRect1); + else + createScene2704(which, 0x004B02C8, 150, kScene2508StaticSprites2, &kScene2508ClipRect2); + break; + case 8: + _vm->gameState().sceneNum = 8; + _childObject = new Scene1608(_vm, this, which); + break; + case 9: + _vm->gameState().sceneNum = 9; + if (getGlobalVar(V_ENTRANCE_OPEN)) + createStaticScene(0xC62A0645, 0xA0641C6A); + else + createStaticScene(0x7A343546, 0x435427AB); + break; + } + SetUpdateHandler(&Module2500::updateScene); + _childObject->handleUpdate(); +} + +void Module2500::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 1) + createScene(2, 0); + else if (_moduleResult == 2) + createScene(1, 0); + else + leaveModule(0); + break; + case 1: + if (_moduleResult == 1) + createScene(3, -1); + else + createScene(0, 2); + break; + case 2: + if (_moduleResult == 1) + createScene(4, 0); + else + createScene(0, 1); + break; + case 3: + createScene(1, 1); + break; + case 4: + if (_moduleResult == 1) + createScene(5, 0); + else + createScene(2, 1); + break; + case 5: + if (_moduleResult == 1) + createScene(6, 0); + else + createScene(4, 1); + break; + case 6: + if (_moduleResult == 1) + createScene(7, 0); + else + createScene(5, 1); + break; + case 7: + if (_moduleResult == 1) + createScene(8, 1); + else + createScene(6, 1); + break; + case 8: + if (_moduleResult == 2) + createScene(9, -1); + else + createScene(7, 1); + break; + case 9: + createScene(8, 2); + break; + } + } +} + +uint32 Module2500::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Module::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x200D: + playSound(_soundIndex); + _soundIndex++; + if (_soundIndex >= 4) + _soundIndex = 0; + break; + } + return messageResult; +} + +void Module2500::createScene2704(int which, uint32 sceneInfoId, int16 value, const uint32 *staticSprites, const NRect *clipRect) { + _childObject = new Scene2704(_vm, this, which, sceneInfoId, value, staticSprites, clipRect); +} + +Scene2501::Scene2501(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + _tracks.push_back(_vm->_staticData->getTrackInfo(0x004B2628)); + _tracks.push_back(_vm->_staticData->getTrackInfo(0x004B264C)); + _tracks.push_back(_vm->_staticData->getTrackInfo(0x004B2670)); + + setGlobalVar(V_CAR_DELTA_X, 1); + SetUpdateHandler(&Scene2501::update); + setBackground(0x1B8E8115); + setPalette(0x1B8E8115); + _palette->addPalette(0x00128842, 65, 31, 65); + _palette->addPalette("paKlayRed", 0, 64, 0); + insertScreenMouse(0xE81111B0); + + _ssTrackShadowBackground = createSprite<SsCommonTrackShadowBackground>(0x99BE9015); // Don't add this to the sprite list + addEntity(_ssTrackShadowBackground); + _asCar = createSprite<AsCommonCar>(this, 211, 400); // Create but don't add to the sprite list yet + _asIdleCarLower = insertSprite<AsCommonIdleCarLower>(211, 400); + _asIdleCarFull = insertSprite<AsCommonIdleCarFull>(211, 400); + insertStaticSprite(0xC42AC521, 1500); + + if (which < 0) { + // Restoring game + insertKlaymen<KmScene2501>(162, 393); + _kmScene2501 = _klaymen; + _klaymenInCar = false; + setMessageList(0x004B2538); + setRectList(0x004B2608); + SetMessageHandler(&Scene2501::handleMessage); + SetUpdateHandler(&Scene2501::update); + sendMessage(_asCar, 0x2009, 0); + _asCar->setVisible(false); + _currTrackIndex = 0; + } else if (which == 1 || which == 2) { + // 1: Klaymen entering riding the car on the left track + // 2: Klaymen entering riding the car on the bottom track + addSprite(_asCar); + _kmScene2501 = (Klaymen*)new KmScene2501(_vm, this, 275, 393); + _klaymenInCar = true; + sendMessage(_kmScene2501, 0x2000, 1); + _kmScene2501->setDoDeltaX(1); + SetMessageHandler(&Scene2501::hmRidingCar); + SetUpdateHandler(&Scene2501::upRidingCar); + _asIdleCarLower->setVisible(false); + _asIdleCarFull->setVisible(false); + _currTrackIndex = which; + } else { + // Klaymen entering the car + insertKlaymen<KmScene2501>(162, 393); + _kmScene2501 = _klaymen; + _klaymenInCar = false; + setMessageList(0x004B2538); + setRectList(0x004B2608); + SetMessageHandler(&Scene2501::handleMessage); + SetUpdateHandler(&Scene2501::update); + sendMessage(_asCar, 0x2009, 0); + _asCar->setVisible(false); + _currTrackIndex = 0; + } + + _asCarShadow = insertSprite<AsCommonCarShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + _asCarTrackShadow = insertSprite<AsCommonCarTrackShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + _asCarConnectorShadow = insertSprite<AsCommonCarConnectorShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + insertSprite<AsCommonCarConnector>(_asCar); + + _newTrackIndex = -1; + _dataResource.load(calcHash("Ashooded")); + + _trackPoints = _dataResource.getPointArray(_tracks[_currTrackIndex]->trackPointsName); + _asCar->setPathPoints(_trackPoints); + + if (which >= 0 && _tracks[_currTrackIndex]->which2 == which) { + NPoint testPoint = (*_trackPoints)[_trackPoints->size() - 1]; + sendMessage(_asCar, 0x2002, _trackPoints->size() - 1); + if (testPoint.x < 0 || testPoint.x >= 640 || testPoint.y < 0 || testPoint.y >= 480) + sendMessage(_asCar, 0x2007, 150); + } else { + NPoint testPoint = (*_trackPoints)[0]; + sendMessage(_asCar, 0x2002, 0); + if (testPoint.x < 0 || testPoint.x >= 640 || testPoint.y < 0 || testPoint.y >= 480) + sendMessage(_asCar, 0x2008, 150); + } + + _carStatus = 0; + +} + +Scene2501::~Scene2501() { + // Free sprites not currently in the sprite list + if (_klaymenInCar) + delete _kmScene2501; + else + delete _asCar; +} + +void Scene2501::update() { + Scene::update(); + if (_carStatus == 1) { + removeSprite(_klaymen); + addSprite(_asCar); + clearRectList(); + _klaymenInCar = true; + SetMessageHandler(&Scene2501::hmCarAtHome); + SetUpdateHandler(&Scene2501::upCarAtHome); + _asIdleCarLower->setVisible(false); + _asIdleCarFull->setVisible(false); + _asCar->setVisible(true); + sendMessage(_asCar, 0x2009, 0); + _asCar->handleUpdate(); + _klaymen = NULL; + _carStatus = 0; + } + updateKlaymenClipRect(); +} + +void Scene2501::upCarAtHome() { + Scene::update(); + if (_mouseClicked) { + if (_mouseClickPos.x <= 210 && _asCar->getX() == 211 && _asCar->getY() == 400) { + sendMessage(_asCar, 0x200A, 0); + SetUpdateHandler(&Scene2501::upGettingOutOfCar); + } else { + moveCarToPoint(_mouseClickPos); + SetMessageHandler(&Scene2501::hmRidingCar); + SetUpdateHandler(&Scene2501::upRidingCar); + } + _mouseClicked = false; + } + updateKlaymenClipRect(); +} + +void Scene2501::upGettingOutOfCar() { + Scene::update(); + if (_carStatus == 2) { + _klaymen = _kmScene2501; + removeSprite(_asCar); + addSprite(_klaymen); + _klaymenInCar = false; + SetMessageHandler(&Scene2501::handleMessage); + SetUpdateHandler(&Scene2501::update); + setRectList(0x004B2608); + _asIdleCarLower->setVisible(true); + _asIdleCarFull->setVisible(true); + _asCar->setVisible(false); + setMessageList(0x004B2570); + processMessageList(); + _klaymen->handleUpdate(); + _carStatus = 0; + } + updateKlaymenClipRect(); +} + +void Scene2501::upRidingCar() { + Scene::update(); + if (_mouseClicked) { + moveCarToPoint(_mouseClickPos); + _mouseClicked = false; + } +} + +uint32 Scene2501::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x60842040) + _carStatus = 1; + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + } + return messageResult; +} + +uint32 Scene2501::hmRidingCar(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2005: + if (_tracks[_currTrackIndex]->which1 < 0 && _newTrackIndex >= 0) + changeTrack(); + else if (_tracks[_currTrackIndex]->which1 == 0) { + SetMessageHandler(&Scene2501::hmCarAtHome); + SetUpdateHandler(&Scene2501::upCarAtHome); + sendMessage(_asCar, 0x200F, 1); + } else if (_tracks[_currTrackIndex]->which1 > 0) + leaveScene(_tracks[_currTrackIndex]->which1); + break; + case 0x2006: + if (_tracks[_currTrackIndex]->which2 < 0 && _newTrackIndex >= 0) + changeTrack(); + else if (_tracks[_currTrackIndex]->which2 == 0) { + SetMessageHandler(&Scene2501::hmCarAtHome); + SetUpdateHandler(&Scene2501::upCarAtHome); + sendMessage(_asCar, 0x200F, 1); + } else if (_tracks[_currTrackIndex]->which2 > 0) + leaveScene(_tracks[_currTrackIndex]->which2); + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + } + return messageResult; +} + +uint32 Scene2501::hmCarAtHome(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x200A: + _carStatus = 2; + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + } + return messageResult; +} + +void Scene2501::moveCarToPoint(NPoint &pt) { + int minMatchTrackIndex, minMatchDistance; + _tracks.findTrackPoint(pt, minMatchTrackIndex, minMatchDistance, _dataResource); + if (minMatchTrackIndex >= 0 && minMatchTrackIndex != _currTrackIndex) { + _newTrackIndex = minMatchTrackIndex; + _clickPoint = pt; + if (_currTrackIndex == 0) + sendMessage(_asCar, 0x2003, _trackPoints->size() - 1); + else + sendMessage(_asCar, 0x2003, 0); + } else { + _newTrackIndex = -1; + sendMessage(_asCar, 0x2004, pt); + } +} + +void Scene2501::changeTrack() { + _currTrackIndex = _newTrackIndex; + _trackPoints = _dataResource.getPointArray(_tracks[_currTrackIndex]->trackPointsName); + _asCar->setPathPoints(_trackPoints); + if (_currTrackIndex == 0) + sendMessage(_asCar, 0x2002, _trackPoints->size() - 1); + else + sendMessage(_asCar, 0x2002, 0); + sendPointMessage(_asCar, 0x2004, _clickPoint); + _newTrackIndex = -1; +} + +void Scene2501::updateKlaymenClipRect() { + if (_kmScene2501->getX() <= 211) + _kmScene2501->setClipRect(0, 0, 640, 480); + else + _kmScene2501->setClipRect(0, 0, 640, 388); +} + +SsScene2504Button::SsScene2504Button(NeverhoodEngine *vm) + : StaticSprite(vm, 1400), _countdown(0), _isSoundPlaying(false) { + + loadSprite(0x070220D9, kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset, 400); + setVisible(false); + loadSound(0, 0x4600204C); + loadSound(1, 0x408C0034); + loadSound(2, 0x44043000); + loadSound(3, 0x44045000); + SetMessageHandler(&SsScene2504Button::handleMessage); + SetUpdateHandler(&SsScene2504Button::update); +} + +void SsScene2504Button::update() { + updatePosition(); + if (_isSoundPlaying && !isSoundPlaying(0) && !isSoundPlaying(1)) { + playSound(3); + setVisible(false); + _isSoundPlaying = false; + } + if (_countdown != 0 && (--_countdown) == 0) { + if (getSubVar(VA_LOCKS_DISABLED, 0x01180951)) + playSound(0); + else + playSound(1); + _isSoundPlaying = true; + } +} + +uint32 SsScene2504Button::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_countdown == 0 && !_isSoundPlaying) { + setVisible(true); + _countdown = 2; + if (getSubVar(VA_LOCKS_DISABLED, 0x01180951)) + setSubVar(VA_LOCKS_DISABLED, 0x01180951, 0); + else + setSubVar(VA_LOCKS_DISABLED, 0x01180951, 1); + playSound(2); + } + messageResult = 1; + break; + } + return messageResult; +} + +Scene2504::Scene2504(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + Sprite *ssButton; + + setBackground(0x90791B80); + setPalette(0x90791B80); + ssButton = insertSprite<SsScene2504Button>(); + addCollisionSprite(ssButton); + insertPuzzleMouse(0x91B8490F, 20, 620); + SetMessageHandler(&Scene2504::handleMessage); + SetUpdateHandler(&Scene::update); +} + +uint32 Scene2504::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) + leaveScene(0); + break; + } + return messageResult; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module2500.h b/engines/neverhood/modules/module2500.h new file mode 100644 index 0000000000..07db7907d5 --- /dev/null +++ b/engines/neverhood/modules/module2500.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 NEVERHOOD_MODULES_MODULE2500_H +#define NEVERHOOD_MODULES_MODULE2500_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" +#include "neverhood/modules/module1000.h" +#include "neverhood/modules/module1600.h" +#include "neverhood/modules/module2700.h" + +namespace Neverhood { + +// Module2500 + +class Module2500 : public Module { +public: + Module2500(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module2500(); +protected: + int _sceneNum; + int _soundIndex; + void createScene(int sceneNum, int which); + void updateScene(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void createScene2704(int which, uint32 sceneInfoId, int16 value, const uint32 *staticSprites = NULL, const NRect *clipRect = NULL); +}; + +class Scene2501 : public Scene { +public: + Scene2501(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Scene2501(); +protected: + AsCommonCar *_asCar; + Sprite *_ssTrackShadowBackground; + Sprite *_asCarShadow; + Sprite *_asCarConnectorShadow; + Sprite *_asCarTrackShadow; + Sprite *_asIdleCarLower; + Sprite *_asIdleCarFull; + Klaymen *_kmScene2501; + Tracks _tracks; + NPointArray *_trackPoints; + int _currTrackIndex; + NPoint _clickPoint; + int _newTrackIndex; + int _carStatus; + bool _klaymenInCar; + void update(); + void upCarAtHome(); + void upGettingOutOfCar(); + void upRidingCar(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmRidingCar(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmCarAtHome(int messageNum, const MessageParam ¶m, Entity *sender); + void moveCarToPoint(NPoint &pt); + void changeTrack(); + void updateKlaymenClipRect(); +}; + +class SsScene2504Button : public StaticSprite { +public: + SsScene2504Button(NeverhoodEngine *vm); +protected: + int _countdown; + bool _isSoundPlaying; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2504 : public Scene { +public: + Scene2504(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE2500_H */ diff --git a/engines/neverhood/modules/module2600.cpp b/engines/neverhood/modules/module2600.cpp new file mode 100644 index 0000000000..b8dbf7bff1 --- /dev/null +++ b/engines/neverhood/modules/module2600.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. + * + */ + +#include "neverhood/modules/module2600.h" + +namespace Neverhood { + +static const uint32 kModule2600SoundList[] = { + 0xB288D450, + 0x90804450, + 0x99801500, + 0xB288D455, + 0x93825040, + 0 +}; + +Module2600::Module2600(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + if (which < 0) + createScene(_vm->gameState().sceneNum, -1); + else if (which == 1) + createScene(4, 1); + else + createScene(0, 1); + + _vm->_soundMan->addSoundList(0x40271018, kModule2600SoundList); + _vm->_soundMan->setSoundListParams(kModule2600SoundList, true, 50, 600, 5, 150); + _vm->_soundMan->playTwoSounds(0x40271018, 0x41861371, 0x43A2507F, 0); + +} + +Module2600::~Module2600() { + _vm->_soundMan->deleteGroup(0x40271018); +} + +void Module2600::createScene(int sceneNum, int which) { + debug("Module2600::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + createNavigationScene(0x004B8608, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + createNavigationScene(0x004B8638, which); + break; + case 2: + _vm->gameState().sceneNum = 2; + createNavigationScene(0x004B86C8, which); + break; + case 3: + _vm->gameState().sceneNum = 3; + if (getGlobalVar(V_CREATURE_ANGRY)) + createNavigationScene(0x004B8758, which); + else + createNavigationScene(0x004B86F8, which); + break; + case 4: + _vm->gameState().sceneNum = 4; + createNavigationScene(0x004B87B8, which); + break; + case 6: + _vm->gameState().sceneNum = 6; + createNavigationScene(0x004B8698, which); + break; + case 7: + _vm->gameState().sceneNum = 7; + _vm->_soundMan->deleteGroup(0x40271018); + createSmackerScene(0x30090001, true, true, false); + break; + case 8: + _vm->gameState().sceneNum = 8; + _childObject = new Scene2609(_vm, this, which); + break; + case 1002: + _vm->gameState().sceneNum = 2; + if (getGlobalVar(V_FRUIT_COUNTING_INDEX) == 1) + createSmackerScene(0x018C0404, true, true, false); + else if (getGlobalVar(V_FRUIT_COUNTING_INDEX) == 2) + createSmackerScene(0x018C0407, true, true, false); + else + createSmackerScene(0x818C0405, true, true, false); + if (getGlobalVar(V_FRUIT_COUNTING_INDEX) >= 2) + setGlobalVar(V_FRUIT_COUNTING_INDEX, 0); + else + incGlobalVar(V_FRUIT_COUNTING_INDEX, +1); + break; + case 1003: + _vm->gameState().sceneNum = 3; + createSmackerScene(0x001C0007, true, true, false); + break; + case 1006: + _vm->gameState().sceneNum = 6; + if (getGlobalVar(V_WATER_RUNNING)) + createSmackerScene(0x049A1181, true, true, false); + else + createSmackerScene(0x04981181, true, true, false); + break; + case 1008: + _vm->gameState().sceneNum = 8; + if (getGlobalVar(V_WATER_RUNNING)) + createSmackerScene(0x42B80941, true, true, false); + else + createSmackerScene(0x42980941, true, true, false); + break; + case 9999: + createDemoScene(); + break; + } + SetUpdateHandler(&Module2600::updateScene); + _childObject->handleUpdate(); +} + +void Module2600::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == 1) + createScene(1, 3); + else + leaveModule(0); + break; + case 1: + if (_moduleResult == 0) + createScene(6, 0); + else if (_moduleResult == 1) + createScene(0, 0); + else if (_moduleResult == 2) + createScene(2, 1); + else if (_moduleResult == 3) + createScene(3, 0); + break; + case 2: + if (_moduleResult == 0) + createScene(1, 0); + else if (_moduleResult == 1) { + if (_vm->isDemo()) + createScene(9999, -1); + else + createScene(1002, -1); + } + break; + case 3: + if (_moduleResult == 0) { + if (getGlobalVar(V_CREATURE_ANGRY)) + createScene(4, 0); + else + createScene(1003, -1); + } else if (_moduleResult == 2) + createScene(1, 1); + else if (_moduleResult == 3) { + if (getGlobalVar(V_CREATURE_ANGRY)) + createScene(4, 0); + else { + setGlobalVar(V_CREATURE_ANGRY, 1); + createScene(7, -1); + } + } + break; + case 4: + if (_moduleResult == 0) + leaveModule(1); + else + createScene(3, 1); + break; + case 6: + if (_moduleResult == 0) { + if (_vm->isDemo()) + createScene(9999, -1); + else + createScene(1006, -1); + } + else if (_moduleResult == 1) + createScene(1, 2); + break; + case 7: + leaveModule(0); + break; + case 8: + createScene(1008, -1); + break; + case 1002: + createScene(2, 1); + break; + case 1003: + createScene(3, 0); + break; + case 1006: + createScene(8, -1); + break; + case 1008: + createScene(6, 0); + break; + case 9999: + createScene(_vm->gameState().sceneNum, -1); + break; + } + } +} + +SsScene2609Button::SsScene2609Button(NeverhoodEngine *vm, Scene *parentScene) + : StaticSprite(vm, 1400), _parentScene(parentScene), _countdown(0) { + + SetUpdateHandler(&SsScene2609Button::update); + SetMessageHandler(&SsScene2609Button::handleMessage); + + loadSprite(0x825A6923, kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset, 400); + if (!getGlobalVar(V_WATER_RUNNING)) + setVisible(false); + loadSound(0, 0x10267160); + loadSound(1, 0x7027FD64); + loadSound(2, 0x44043000); + loadSound(3, 0x44045000); +} + +void SsScene2609Button::update() { + updatePosition(); + if (_countdown != 0 && (--_countdown == 0)) { + if (getGlobalVar(V_WATER_RUNNING)) { + setGlobalVar(V_WATER_RUNNING, 0); + sendMessage(_parentScene, 0x2001, 0); + } else { + setGlobalVar(V_WATER_RUNNING, 1); + sendMessage(_parentScene, 0x2002, 0); + } + } +} + +uint32 SsScene2609Button::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_countdown == 0) { + sendMessage(_parentScene, 0x2000, 0); + if (getGlobalVar(V_WATER_RUNNING)) { + setVisible(false); + playSound(3); + playSound(1); + _countdown = 12; + } else { + setVisible(true); + playSound(2); + playSound(0); + _countdown = 96; + } + } + messageResult = 1; + break; + } + return messageResult; +} + +AsScene2609Water::AsScene2609Water(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1000) { + + _x = 240; + _y = 420; + setDoDeltaX(1); + createSurface1(0x9C210C90, 1200); + setClipRect(260, 260, 400, 368); + _vm->_soundMan->addSound(0x08526C36, 0xDC2769B0); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2609Water::handleMessage); + if (getGlobalVar(V_WATER_RUNNING)) + sendMessage(this, 0x2002, 0); +} + +AsScene2609Water::~AsScene2609Water() { + _vm->_soundMan->deleteSoundGroup(0x08526C36); +} + +uint32 AsScene2609Water::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2001: + stopAnimation(); + setVisible(false); + _vm->_soundMan->stopSound(0xDC2769B0); + break; + case 0x2002: + startAnimation(0x9C210C90, 0, -1); + setVisible(true); + _vm->_soundMan->playSoundLooping(0xDC2769B0); + break; + } + return messageResult; +} + +Scene2609::Scene2609(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _isBusy(false) { + + SetUpdateHandler(&Scene::update); + SetMessageHandler(&Scene2609::handleMessage); + + setBackground(0x51409A16); + setPalette(0x51409A16); + _asWater = insertSprite<AsScene2609Water>(); + _ssButton = insertSprite<SsScene2609Button>(this); + addCollisionSprite(_ssButton); + insertPuzzleMouse(0x09A1251C, 20, 620); + insertStaticSprite(0x02138002, 1200); + insertStaticSprite(0x825E2827, 1200); +} + +uint32 Scene2609::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if ((param.asPoint().x <= 20 || param.asPoint().x >= 620) && !_isBusy) + leaveScene(0); + break; + case 0x2000: + _isBusy = true; + break; + case 0x2001: + _isBusy = false; + sendMessage(_asWater, 0x2001, 0); + break; + case 0x2002: + _isBusy = false; + sendMessage(_asWater, 0x2002, 0); + break; + } + return 0; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module2600.h b/engines/neverhood/modules/module2600.h new file mode 100644 index 0000000000..d972e0fb0d --- /dev/null +++ b/engines/neverhood/modules/module2600.h @@ -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. + * + */ + +#ifndef NEVERHOOD_MODULES_MODULE2600_H +#define NEVERHOOD_MODULES_MODULE2600_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +// Module2600 + +class Module2600 : public Module { +public: + Module2600(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module2600(); +protected: + int _sceneNum; + void createScene(int sceneNum, int which); + void updateScene(); +}; + +class SsScene2609Button : public StaticSprite { +public: + SsScene2609Button(NeverhoodEngine *vm, Scene *parentScene); +protected: + Scene *_parentScene; + int _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2609Water : public AnimatedSprite { +public: + AsScene2609Water(NeverhoodEngine *vm); + virtual ~AsScene2609Water(); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2609 : public Scene { +public: + Scene2609(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + bool _isBusy; + Sprite *_asWater; + Sprite *_ssButton; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE2600_H */ diff --git a/engines/neverhood/modules/module2700.cpp b/engines/neverhood/modules/module2700.cpp new file mode 100644 index 0000000000..8b69bc050e --- /dev/null +++ b/engines/neverhood/modules/module2700.cpp @@ -0,0 +1,1211 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module2700.h" +#include "neverhood/gamemodule.h" +#include "neverhood/modules/module1000.h" + +namespace Neverhood { + +static const NRect kScene2710ClipRect = NRect(0, 0, 626, 480); + +static const uint32 kScene2710StaticSprites[] = { + 0x0D2016C0, + 0 +}; + +static const NRect kScene2711ClipRect = NRect(0, 0, 521, 480); + +static const uint32 kScene2711FileHashes1[] = { + 0, + 0x100801A1, + 0x201081A0, + 0x006800A4, + 0x40390120, + 0x000001B1, + 0x001000A1, + 0 +}; + +static const uint32 kScene2711FileHashes2[] = { + 0, + 0x40403308, + 0x71403168, + 0x80423928, + 0x224131A8, + 0x50401328, + 0x70423328, + 0 +}; + +static const uint32 kScene2711FileHashes3[] = { + 0, + 0x1088A021, + 0x108120E5, + 0x18A02321, + 0x148221A9, + 0x10082061, + 0x188820E1, + 0 +}; + +static const NRect kScene2724ClipRect = NRect(0, 141, 640, 480); + +static const uint32 kScene2724StaticSprites[] = { + 0xC20D00A5, + 0 +}; + +static const NRect kScene2725ClipRect = NRect(0, 0, 640, 413); + +static const uint32 kScene2725StaticSprites[] = { + 0xC20E00A5, + 0 +}; + +Module2700::Module2700(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule), _soundIndex(0), _raidoMusicInitialized(false) { + + _vm->_soundMan->addMusic(0x42212411, 0x04020210); + _vm->_soundMan->startMusic(0x04020210, 24, 2); + SetMessageHandler(&Module2700::handleMessage); + + if (which < 0) { + which = _vm->gameState().which; + // Scenes 0, 30 and 31 are "normal" scenes, whereas the other scenes are tracks. + // "gameState().which" indicates which track the car is at. + if (_vm->gameState().sceneNum == 0 || _vm->gameState().sceneNum == 30 || _vm->gameState().sceneNum == 31) + which = -1; + createScene(_vm->gameState().sceneNum, which); + } else + createScene(0, 0); + + loadSound(0, 0x00880CCC); + loadSound(1, 0x00880CC0); + loadSound(2, 0x00880CCC); + loadSound(3, 0x00880CC0); + +} + +Module2700::~Module2700() { + _vm->_soundMan->deleteGroup(0x42212411); +} + +void Module2700::createScene(int sceneNum, int which) { + debug("Module2700::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _vm->gameState().which = which; + _childObject = new Scene2701(_vm, this, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + _vm->gameState().which = which; + _childObject = new Scene2702(_vm, this, which); + break; + case 2: + _vm->gameState().sceneNum = 2; + _vm->gameState().which = which; + if (which == 6 || which == 7) + createScene2703(which, 0x004B1710); + else if (which == 4 || which == 5) + createScene2703(which, 0x004B1738); + else if (which == 2 || which == 3) + createScene2703(which, 0x004B1760); + else + createScene2703(which, 0x004B1788); + break; + case 3: + _vm->gameState().sceneNum = 3; + _vm->gameState().which = which; + createScene2704(which, 0x004B17B0, 150); + break; + case 4: + _vm->gameState().sceneNum = 4; + _vm->gameState().which = which; + createScene2704(which, 0x004B17D8, 150); + break; + case 5: + _vm->gameState().sceneNum = 5; + _vm->gameState().which = which; + if (which >= 4) + _childObject = new Scene2706(_vm, this, which); + else if (which == 2 || which == 3) + createScene2704(which, 0x004B1828, 150); + else + createScene2704(which, 0x004B1800, 150); + break; + case 6: + _vm->gameState().sceneNum = 6; + _vm->gameState().which = which; + createScene2704(which, 0x004B1850, 150); + break; + case 7: + _vm->gameState().sceneNum = 7; + _vm->gameState().which = which; + if (which == 2 || which == 3) + createScene2704(which, 0x004B1878, 150); + else + createScene2704(which, 0x004B18A0, 150); + break; + case 8: + _vm->gameState().sceneNum = 8; + _vm->gameState().which = which; + if (which == 2 || which == 3) + createScene2704(which, 0x004B18C8, 150); + else + createScene2704(which, 0x004B18F0, 150); + break; + case 9: + _vm->gameState().sceneNum = 9; + _vm->gameState().which = which; + createScene2704(which, 0x004B1918, 150, kScene2710StaticSprites, &kScene2710ClipRect); + break; + case 10: + _vm->gameState().sceneNum = 10; + _vm->gameState().which = which; + _vm->gameModule()->initTestTubes2Puzzle(); + _scene2711StaticSprites[0] = kScene2711FileHashes1[getSubVar(VA_GOOD_TEST_TUBES_LEVEL_2, 2)]; + _scene2711StaticSprites[1] = kScene2711FileHashes2[getSubVar(VA_GOOD_TEST_TUBES_LEVEL_2, 1)]; + _scene2711StaticSprites[2] = kScene2711FileHashes3[getSubVar(VA_GOOD_TEST_TUBES_LEVEL_2, 0)]; + _scene2711StaticSprites[3] = 0x0261282E; + _scene2711StaticSprites[4] = 0x9608E5A0; + _scene2711StaticSprites[5] = 0; + createScene2704(which, 0x004B1950, 150, _scene2711StaticSprites, &kScene2711ClipRect); + break; + case 11: + _vm->gameState().sceneNum = 11; + _vm->gameState().which = which; + createScene2704(which, 0x004B19E0, 150); + break; + case 12: + _vm->gameState().sceneNum = 12; + _vm->gameState().which = which; + createScene2704(which, 0x004B1A08, 150); + break; + case 13: + _vm->gameState().sceneNum = 13; + _vm->gameState().which = which; + createScene2704(which, 0x004B1A30, 150); + break; + case 14: + _vm->gameState().sceneNum = 14; + _vm->gameState().which = which; + if (which == 4 || which == 5) + createScene2704(which, 0x004B1A58, 150); + else if (which == 2 || which == 3) + createScene2704(which, 0x004B1A80, 150); + else + createScene2704(which, 0x004B1AA8, 150); + break; + case 15: + _vm->gameState().sceneNum = 15; + _vm->gameState().which = which; + if (which == 4 || which == 5) + createScene2704(which, 0x004B1AD0, 150); + else if (which == 2 || which == 3) + createScene2704(which, 0x004B1AF8, 150); + else + createScene2704(which, 0x004B1B20, 150); + break; + case 16: + _vm->gameState().sceneNum = 16; + _vm->gameState().which = which; + if (which == 4 || which == 5) + createScene2704(which, 0x004B1B48, 150); + else if (which == 2 || which == 3) + createScene2704(which, 0x004B1B70, 150); + else + createScene2704(which, 0x004B1B98, 150); + break; + case 17: + _vm->gameState().sceneNum = 17; + _vm->gameState().which = which; + if (which == 4 || which == 5) + createScene2704(which, 0x004B1BC0, 150); + else if (which == 2 || which == 3) + createScene2704(which, 0x004B1BE8, 150); + else + createScene2704(which, 0x004B1C10, 150); + break; + case 18: + _vm->gameState().sceneNum = 18; + _vm->gameState().which = which; + if (which == 2 || which == 3) + createScene2704(which, 0x004B1C38, 150); + else + createScene2704(which, 0x004B1C60, 150); + break; + case 19: + _vm->gameState().sceneNum = 19; + _vm->gameState().which = which; + if (which == 2 || which == 3) + createScene2704(which, 0x004B1CB0, 150); + else + createScene2704(which, 0x004B1C88, 150); + break; + case 20: + _vm->gameState().sceneNum = 20; + _vm->gameState().which = which; + if (which == 2 || which == 3) + createScene2704(which, 0x004B1CD8, 150); + else + createScene2704(which, 0x004B1D00, 150); + break; + case 21: + _vm->gameState().sceneNum = 21; + _vm->gameState().which = which; + createScene2704(which, 0x004B1D28, 150); + break; + case 22: + _vm->gameState().sceneNum = 22; + _vm->gameState().which = which; + createScene2704(which, 0x004B1D50, 150); + break; + case 23: + _vm->gameState().sceneNum = 23; + _vm->gameState().which = which; + createScene2704(which, 0x004B1D78, 150, kScene2724StaticSprites, &kScene2724ClipRect); + break; + case 24: + _vm->gameState().sceneNum = 24; + _vm->gameState().which = which; + createScene2704(which, 0x004B1DB0, 150, kScene2725StaticSprites, &kScene2725ClipRect); + break; + case 25: + _vm->gameState().sceneNum = 25; + _vm->gameState().which = which; + createScene2704(which, 0x004B1DE8, 150); + break; + case 26: + _vm->gameState().sceneNum = 26; + _vm->gameState().which = which; + createScene2704(which, 0x004B1E10, 150); + break; + case 27: + _vm->gameState().sceneNum = 27; + _vm->gameState().which = which; + createScene2704(which, 0x004B1E38, 150); + break; + case 28: + _vm->gameState().sceneNum = 28; + _vm->gameState().which = which; + createScene2704(which, 0x004B1E60, 150); + break; + case 30: + _vm->gameState().sceneNum = 30; + createStaticScene(0x09507248, 0x0724C09D); + break; + case 31: + _vm->gameState().sceneNum = 31; + _childObject = new Scene2732(_vm, this); + break; + } + SetUpdateHandler(&Module2700::updateScene); + _childObject->handleUpdate(); +} + +#define SceneLinkIf(moduleResult, sceneNum, which) \ + if (_moduleResult == moduleResult) { createScene(sceneNum, which); break; } + +void Module2700::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + SceneLinkIf(1, 1, 0); + leaveModule(0); + break; + case 1: + SceneLinkIf(1, 14, 1); + SceneLinkIf(2, 2, 2); + SceneLinkIf(3, 14, 3); + SceneLinkIf(4, 2, 6); + SceneLinkIf(5, 2, 4); + createScene(0, 1); + break; + case 2: + SceneLinkIf(1, 5, 0); + SceneLinkIf(2, 1, 2); + SceneLinkIf(3, 5, 2); + SceneLinkIf(4, 1, 5); + SceneLinkIf(5, 5, 4); + SceneLinkIf(6, 1, 4); + SceneLinkIf(7, 11, 0); + createScene(3, 0); + break; + case 3: + createScene(2, 0); + break; + case 4: + SceneLinkIf(1, 7, 2); + createScene(5, 5); + break; + case 5: + SceneLinkIf(1, 6, 0); + SceneLinkIf(2, 2, 3); + SceneLinkIf(3, 8, 2); + SceneLinkIf(4, 2, 5); + SceneLinkIf(5, 4, 0); + SceneLinkIf(6, 7, 0); + createScene(2, 1); + break; + case 6: + SceneLinkIf(1, 8, 0); + createScene(5, 1); + break; + case 7: + SceneLinkIf(1, 8, 3); + SceneLinkIf(2, 4, 1); + SceneLinkIf(3, 9, 0); + createScene(5, 6); + break; + case 8: + SceneLinkIf(1, 10, 0); + SceneLinkIf(2, 5, 3); + SceneLinkIf(3, 7, 1); + createScene(6, 1); + break; + case 9: + SceneLinkIf(1, 10, 1); + createScene(7, 3); + break; + case 10: + SceneLinkIf(1, 9, 1); + createScene(8, 1); + break; + case 11: + SceneLinkIf(1, 12, 0); + createScene(2, 7); + break; + case 12: + SceneLinkIf(1, 13, 0); + createScene(11, 1); + break; + case 13: + SceneLinkIf(1, 30, 0); + createScene(12, 1); + break; + case 14: + SceneLinkIf(1, 1, 1); + SceneLinkIf(2, 15, 3); + SceneLinkIf(3, 1, 3); + SceneLinkIf(4, 15, 5); + SceneLinkIf(5, 22, 0); + createScene(15, 1); + break; + case 15: + SceneLinkIf(1, 14, 0); + SceneLinkIf(2, 16, 3); + SceneLinkIf(3, 14, 2); + SceneLinkIf(4, 16, 5); + SceneLinkIf(5, 14, 4); + createScene(16, 1); + break; + case 16: + SceneLinkIf(1, 15, 0); + SceneLinkIf(2, 17, 3); + SceneLinkIf(3, 15, 2); + SceneLinkIf(4, 17, 5); + SceneLinkIf(5, 15, 4); + createScene(17, 1); + break; + case 17: + SceneLinkIf(1, 16, 0); + SceneLinkIf(2, 18, 3); + SceneLinkIf(3, 16, 2); + SceneLinkIf(4, 20, 1); + SceneLinkIf(5, 16, 4); + createScene(18, 1); + break; + case 18: + SceneLinkIf(1, 17, 0); + SceneLinkIf(2, 19, 2); + SceneLinkIf(3, 17, 2); + createScene(19, 0); + break; + case 19: + SceneLinkIf(1, 20, 2); + SceneLinkIf(2, 18, 2); + SceneLinkIf(3, 20, 0); + createScene(18, 0); + break; + case 20: + SceneLinkIf(1, 17, 4); + SceneLinkIf(2, 19, 1); + SceneLinkIf(3, 21, 0); + createScene(19, 3); + break; + case 21: + _vm->_soundMan->deleteMusic(_musicFileHash); + _vm->_soundMan->startMusic(0x04020210, 0, 2); + _vm->_soundMan->deleteSoundGroup(0x42212411); + createScene(20, 3); + break; + case 22: + SceneLinkIf(1, 23, 0); + createScene(14, 5); + break; + case 23: + SceneLinkIf(1, 24, 0); + createScene(22, 1); + break; + case 24: + SceneLinkIf(1, 25, 0); + createScene(23, 1); + break; + case 25: + SceneLinkIf(1, 26, 0); + createScene(24, 1); + break; + case 26: + SceneLinkIf(1, 27, 0); + createScene(25, 1); + break; + case 27: + SceneLinkIf(1, 28, 0); + createScene(26, 1); + break; + case 28: + SceneLinkIf(1, 31, 0); + createScene(27, 1); + break; + case 30: + createScene(13, 1); + break; + case 31: + createScene(28, 1); + break; + } + } else { + switch (_sceneNum) { + case 21: + if (!_raidoMusicInitialized) { + _vm->_soundMan->stopMusic(0x04020210, 0, 1); + _vm->gameModule()->initRadioPuzzle(); + _musicFileHash = getGlobalVar(V_GOOD_RADIO_MUSIC_NAME); + _vm->_soundMan->addMusic(0x42212411, _musicFileHash); + _vm->_soundMan->startMusic(_musicFileHash, 0, 2); + _vm->_soundMan->addSound(0x42212411, 0x44014282); + _vm->_soundMan->setSoundParams(0x44014282, true, 120, 360, 72, 0); + _raidoMusicInitialized = true; + } + break; + } + } +} + +uint32 Module2700::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Module::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x200D: + playSound(_soundIndex); + _soundIndex++; + if (_soundIndex >= 4) + _soundIndex = 0; + break; + } + return messageResult; +} + +void Module2700::createScene2703(int which, uint32 trackInfoId) { + _childObject = new Scene2703(_vm, this, which, trackInfoId); +} + +void Module2700::createScene2704(int which, uint32 trackInfoId, int16 value, const uint32 *staticSprites, const NRect *clipRect) { + _childObject = new Scene2704(_vm, this, which, trackInfoId, value, staticSprites, clipRect); +} + +static const NPoint kCarShadowOffsets[] = { + {-63, 3}, {-48, 40}, {-33, 58}, + { 0, 65}, { 40, 53}, { 56, 27}, + { 63, 0}, {-30, 26}, { 0, 30}, + { 26, 25} +}; + +SsCommonTrackShadowBackground::SsCommonTrackShadowBackground(NeverhoodEngine *vm, uint32 fileHash) + : StaticSprite(vm, 0) { + + loadSprite(fileHash, kSLFDefDrawOffset | kSLFDefPosition, 0); +} + +AsCommonCarShadow::AsCommonCarShadow(NeverhoodEngine *vm, AnimatedSprite *asCar, BaseSurface *shadowSurface, uint index) + : AnimatedSprite(vm, 1100), _asCar(asCar), _index(index), _animFileHash(0) { + + SetUpdateHandler(&AsCommonCarShadow::update); + createShadowSurface(shadowSurface, 211, 147, 100); + updateShadow(); +} + +void AsCommonCarShadow::update() { + updateShadow(); + AnimatedSprite::update(); +} + +void AsCommonCarShadow::updateShadow() { + if (_asCar->getFrameIndex() != _currFrameIndex || _asCar->getCurrAnimFileHash() != _animFileHash) { + uint32 fileHash = _asCar->getCurrAnimFileHash(); + if (fileHash == 0x35698F78 || fileHash == 0x192ADD30 || fileHash == 0x9C220DA4 || + fileHash == 0x9966B138 || fileHash == 0xB579A77C || fileHash == 0xA86A9538 || + fileHash == 0xD4220027 || fileHash == 0xD00A1364 || fileHash == 0xD4AA03A4 || + fileHash == 0xF46A0324) { + startAnimation(fileHash, _asCar->getFrameIndex(), -1); + _newStickFrameIndex = _asCar->getFrameIndex(); + } + _animFileHash = fileHash; + } + _x = _asCar->getX() + kCarShadowOffsets[_index].x; + _y = _asCar->getY() + kCarShadowOffsets[_index].y; + if (!_asCar->getVisible()) { + startAnimation(0x1209E09F, 0, -1); + _newStickFrameIndex = 0; + } + setDoDeltaX(_asCar->isDoDeltaX() ? 1 : 0); +} + +AsCommonCarConnectorShadow::AsCommonCarConnectorShadow(NeverhoodEngine *vm, Sprite *asCar, BaseSurface *shadowSurface, uint index) + : AnimatedSprite(vm, 1100), _asCar(asCar), _index(index) { + + SetUpdateHandler(&AsCommonCarConnectorShadow::update); + createShadowSurface1(shadowSurface, 0x60281C10, 150); + startAnimation(0x60281C10, -1, -1); + _newStickFrameIndex = STICK_LAST_FRAME; +} + +void AsCommonCarConnectorShadow::update() { + _x = _asCar->getX() + kCarShadowOffsets[_index].x; + _y = _asCar->getY() + kCarShadowOffsets[_index].y; + AnimatedSprite::update(); +} + +AsCommonCarTrackShadow::AsCommonCarTrackShadow(NeverhoodEngine *vm, Sprite *asCar, BaseSurface *shadowSurface, int16 frameIndex) + : AnimatedSprite(vm, 1100), _asCar(asCar) { + + SetUpdateHandler(&AsCommonCarTrackShadow::update); + createShadowSurface1(shadowSurface, 0x0759129C, 100); + startAnimation(0x0759129C, frameIndex, -1); + _newStickFrameIndex = frameIndex; +} + +void AsCommonCarTrackShadow::update() { + _x = _asCar->getX(); + _y = _asCar->getY(); + AnimatedSprite::update(); +} + +Scene2701::Scene2701(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + Sprite *tempSprite; + + NRect clipRect; + TrackInfo *tracks = _vm->_staticData->getTrackInfo(0x004B2240); + setGlobalVar(V_CAR_DELTA_X, 1); + + setBackground(tracks->bgFilename); + setPalette(tracks->bgFilename); + _palette->addPalette(calcHash("paPodFloor"), 65, 31, 65); + _palette->addPalette(calcHash("paKlayFloor"), 0, 65, 0); + insertScreenMouse(0x08B08180); + + tempSprite = insertStaticSprite(0x1E086325, 1200); + clipRect.set(0, 0, 640, tempSprite->getDrawRect().y2()); + + if (tracks->bgShadowFilename) { + _ssTrackShadowBackground = createSprite<SsCommonTrackShadowBackground>(tracks->bgShadowFilename); + addEntity(_ssTrackShadowBackground); + _asCar = insertSprite<AsCommonCar>(this, 320, 240); + _asCarShadow = insertSprite<AsCommonCarShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + _asCarTrackShadow = insertSprite<AsCommonCarTrackShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + _asCarConnectorShadow = insertSprite<AsCommonCarConnectorShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + } else { + _ssTrackShadowBackground = NULL; + _asCar = insertSprite<AsCommonCar>(this, 320, 240); + } + + _asCarConnector = insertSprite<AsCommonCarConnector>(_asCar); + _which1 = tracks->which1; + _which2 = tracks->which2; + _dataResource.load(tracks->dataResourceFilename); + _trackPoints = _dataResource.getPointArray(tracks->trackPointsName); + _asCar->setPathPoints(_trackPoints); + + if (which == _which2) { + NPoint testPoint = (*_trackPoints)[_trackPoints->size() - 1]; + sendMessage(_asCar, 0x2002, _trackPoints->size() - 1); + if (testPoint.x < 0 || testPoint.x >= 640 || testPoint.y < 0 || testPoint.y >= 480) + sendMessage(_asCar, 0x2007, 150); + } else { + NPoint testPoint = (*_trackPoints)[0]; + sendMessage(_asCar, 0x2002, 0); + if (testPoint.x < 0 || testPoint.x >= 640 || testPoint.y < 0 || testPoint.y >= 480) + sendMessage(_asCar, 0x2008, 150); + } + + _asCar->setClipRect(clipRect); + _asCarConnector->setClipRect(clipRect); + + if (which == 1) { + SetMessageHandler(&Scene2701::hmRidingCar); + } else { + sendMessage(_asCar, 0x2009, 0); + SetMessageHandler(&Scene2701::hmCarAtHome); + } + +} + +uint32 Scene2701::hmRidingCar(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + sendPointMessage(_asCar, 0x2004, param.asPoint()); + break; + case 0x2005: + if (_which1 >= 0) + SetMessageHandler(&Scene2701::hmCarAtHome); + break; + case 0x2006: + if (_which2 >= 0) + leaveScene(_which2); + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + } + return 0; +} + +uint32 Scene2701::hmCarAtHome(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x >= 385) + leaveScene(0); + else { + sendPointMessage(_asCar, 0x2004, param.asPoint()); + SetMessageHandler(&Scene2701::hmRidingCar); + } + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + } + return 0; +} + +Scene2702::Scene2702(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _isInLight(true), _newTrackIndex(-1) { + + SetMessageHandler(&Scene2702::handleMessage); + SetUpdateHandler(&Scene2702::update); + + setBackground(0x18808B00); + setPalette(0x18808B00); + _palette->addPalette(calcHash("paPodFloor"), 65, 31, 65); + _palette->addPalette(calcHash("paKlayFloor"), 0, 65, 0); + addEntity(_palette); + insertScreenMouse(0x08B04180); + + _ssTrackShadowBackground = createSprite<SsCommonTrackShadowBackground>(0x12002035); + addEntity(_ssTrackShadowBackground); + _asCar = insertSprite<AsCommonCar>(this, 320, 240); + _asCarShadow = insertSprite<AsCommonCarShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + insertSprite<AsCommonCarConnector>(_asCar); + _asCarTrackShadow = insertSprite<AsCommonCarTrackShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + _asCarConnectorShadow = insertSprite<AsCommonCarConnectorShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + _dataResource.load(0x04310014); + + if (which == 1) { + _isUpperTrack = false; + _currTrackIndex = 1; + } else if (which == 2) { + _isUpperTrack = false; + _currTrackIndex = 2; + _palette->addPalette(calcHash("paPodShade"), 65, 31, 65); + _palette->addPalette(calcHash("paKlayShade"), 0, 65, 0); + _isInLight = false; + } else if (which == 3) { + _isUpperTrack = true; + _currTrackIndex = 0; + } else if (which == 4) { + _isUpperTrack = true; + _currTrackIndex = 2; + _palette->addPalette(calcHash("paPodShade"), 65, 31, 65); + _palette->addPalette(calcHash("paKlayShade"), 0, 65, 0); + _isInLight = false; + } else if (which == 5) { + _isUpperTrack = true; + _currTrackIndex = 1; + _palette->addPalette(calcHash("paPodShade"), 65, 31, 65); + _palette->addPalette(calcHash("paKlayShade"), 0, 65, 0); + _isInLight = false; + } else { + _isUpperTrack = false; + _currTrackIndex = 0; + } + + if (_isUpperTrack) { + _tracks.push_back(_vm->_staticData->getTrackInfo(0x004B5F68)); + _tracks.push_back(_vm->_staticData->getTrackInfo(0x004B5F8C)); + _tracks.push_back(_vm->_staticData->getTrackInfo(0x004B5FB0)); + } else { + _tracks.push_back(_vm->_staticData->getTrackInfo(0x004B5FD8)); + _tracks.push_back(_vm->_staticData->getTrackInfo(0x004B5FFC)); + _tracks.push_back(_vm->_staticData->getTrackInfo(0x004B6020)); + } + + _trackPoints = _dataResource.getPointArray(_tracks[_currTrackIndex]->trackPointsName); + _asCar->setPathPoints(_trackPoints); + + if (which == _tracks[_currTrackIndex]->which2) { + sendMessage(_asCar, 0x2002, _trackPoints->size() - 1); + sendMessage(_asCar, 0x2007, 150); + } else { + sendMessage(_asCar, 0x2002, 0); + sendMessage(_asCar, 0x2008, 150); + } + + _palette->copyBasePalette(0, 256, 0); + +} + +void Scene2702::update() { + Scene::update(); + if (_isInLight && _asCar->getX() > 422) { + _palette->addBasePalette(calcHash("paPodShade"), 65, 31, 65); + _palette->addBasePalette(calcHash("paKlayShade"), 0, 65, 0); + _palette->startFadeToPalette(12); + _isInLight = false; + } else if (!_isInLight && _asCar->getX() <= 422) { + _palette->addBasePalette(calcHash("paPodFloor"), 65, 31, 65); + _palette->addBasePalette(calcHash("paKlayFloor"), 0, 65, 0); + _palette->startFadeToPalette(12); + _isInLight = true; + } +} + +uint32 Scene2702::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + moveCarToPoint(param.asPoint()); + break; + case 0x2005: + if (_newTrackIndex >= 0) { + if (_tracks[_currTrackIndex]->which1 < 0) + changeTrack(); + } else if (_tracks[_currTrackIndex]->which1 >= 0) + leaveScene(_tracks[_currTrackIndex]->which1); + break; + case 0x2006: + if (_newTrackIndex >= 0) { + if (_tracks[_currTrackIndex]->which2 < 0) + changeTrack(); + } else if (_tracks[_currTrackIndex]->which2 >= 0) + leaveScene(_tracks[_currTrackIndex]->which2); + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + } + return 0; +} + +void Scene2702::moveCarToPoint(NPoint pt) { + int minMatchTrackIndex, minMatchDistance; + _tracks.findTrackPoint(pt, minMatchTrackIndex, minMatchDistance, _dataResource); + if (minMatchTrackIndex >= 0 && minMatchTrackIndex != _currTrackIndex) { + _newTrackIndex = minMatchTrackIndex; + _newTrackDestX = pt.x; + if (_isUpperTrack) { + if (_currTrackIndex == 0) + sendMessage(_asCar, 0x2003, _trackPoints->size() - 1); + else + sendMessage(_asCar, 0x2003, 0); + } else if (_currTrackIndex == 2) + sendMessage(_asCar, 0x2003, 0); + else + sendMessage(_asCar, 0x2003, _trackPoints->size() - 1); + } else { + _newTrackIndex = -1; + sendMessage(_asCar, 0x2004, pt.x); + } +} + +void Scene2702::changeTrack() { + _currTrackIndex = _newTrackIndex; + _trackPoints = _dataResource.getPointArray(_tracks[_currTrackIndex]->trackPointsName); + _asCar->setPathPoints(_trackPoints); + if (_isUpperTrack) { + if (_currTrackIndex == 0) + sendMessage(_asCar, 0x2002, _trackPoints->size() - 1); + else + sendMessage(_asCar, 0x2002, 0); + } else if (_currTrackIndex == 2) + sendMessage(_asCar, 0x2002, 0); + else + sendMessage(_asCar, 0x2002, _trackPoints->size() - 1); + sendMessage(_asCar, 0x2004, _newTrackDestX); + _newTrackIndex = -1; +} + +Scene2703::Scene2703(NeverhoodEngine *vm, Module *parentModule, int which, uint32 trackInfoId) + : Scene(vm, parentModule) { + + TrackInfo *tracks = _vm->_staticData->getTrackInfo(trackInfoId); + + SetMessageHandler(&Scene2703::handleMessage); + SetUpdateHandler(&Scene2703::update); + + setBackground(tracks->bgFilename); + setPalette(tracks->bgFilename); + _palette->addPalette(calcHash("paPodShade"), 65, 31, 65); + _palette->addPalette(calcHash("paKlayShade"), 0, 65, 0); + addEntity(_palette); + insertScreenMouse(tracks->mouseCursorFilename); + + _palStatus = 2; + + if (tracks->bgShadowFilename) { + _ssTrackShadowBackground = createSprite<SsCommonTrackShadowBackground>(tracks->bgShadowFilename); + addEntity(_ssTrackShadowBackground); + _asCar = insertSprite<AsCommonCar>(this, 320, 240); + _asCarShadow = insertSprite<AsCommonCarShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + _asCarTrackShadow = insertSprite<AsCommonCarTrackShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + _asCarConnectorShadow = insertSprite<AsCommonCarConnectorShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + } else { + _ssTrackShadowBackground = NULL; + _asCarShadow = NULL; + _asCar = insertSprite<AsCommonCar>(this, 320, 240); + } + + _asCarConnector = insertSprite<AsCommonCarConnector>(_asCar); + _which1 = tracks->which1; + _which2 = tracks->which2; + _dataResource.load(tracks->dataResourceFilename); + _trackPoints = _dataResource.getPointArray(tracks->trackPointsName); + _asCar->setPathPoints(_trackPoints); + + if (which == _which2) { + NPoint testPoint = (*_trackPoints)[_trackPoints->size() - 1]; + sendMessage(_asCar, 0x2002, _trackPoints->size() - 1); + if (testPoint.x > 0 && testPoint.x < 640 && testPoint.y > 0 && testPoint.y < 480) + sendMessage(_asCar, 0x2009, 0); + else + sendMessage(_asCar, 0x2007, 150); + } else { + NPoint testPoint = (*_trackPoints)[0]; + sendMessage(_asCar, 0x2002, 0); + if (testPoint.x > 0 && testPoint.x < 640 && testPoint.y > 0 && testPoint.y < 480) + sendMessage(_asCar, 0x2009, 0); + else + sendMessage(_asCar, 0x2008, 150); + } + + if (which == 0) { + _palette->addPalette(calcHash("paPodShade"), 65, 31, 65); + _palette->addPalette(calcHash("paKlayShade"), 0, 65, 0); + _palStatus = 1; + } else if (which == 2 || which == 4 || which == 6) { + _palette->addPalette(calcHash("paPodBlack"), 65, 31, 65); + _palette->addPalette(calcHash("paKlayBlack"), 0, 65, 0); + _palStatus = 0; + } + + _palette->copyBasePalette(0, 256, 0); + +} + +void Scene2703::update() { + Scene::update(); + if (_mouseClicked) { + sendPointMessage(_asCar, 0x2004, _mouseClickPos); + _mouseClicked = false; + } + if (_asCar->getX() > 469) { + if (_palStatus != 2) { + _palette->addBasePalette(calcHash("paPodShade"), 65, 31, 65); + _palette->addBasePalette(calcHash("paKlayShade"), 0, 65, 0); + _palette->startFadeToPalette(12); + _palStatus = 2; + } + } else if (_asCar->getX() > 181) { + if (_palStatus != 1) { + _palette->addBasePalette(calcHash("paPodShade"), 65, 31, 65); + _palette->addBasePalette(calcHash("paKlayShade"), 0, 65, 0); + _palette->startFadeToPalette(12); + _palStatus = 1; + } + } else if (_palStatus != 0) { + _palette->addBasePalette(calcHash("paPodBlack"), 65, 31, 65); + _palette->addBasePalette(calcHash("paKlayBlack"), 0, 65, 0); + _palette->startFadeToPalette(12); + _palStatus = 0; + } +} + +uint32 Scene2703::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2005: + if (_which1 >= 0) + leaveScene(_which1); + break; + case 0x2006: + if (_which2 >= 0) + leaveScene(_which2); + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + } + return 0; +} + +Scene2704::Scene2704(NeverhoodEngine *vm, Module *parentModule, int which, uint32 trackInfoId, int16 value, + const uint32 *staticSprites, const NRect *clipRect) + : Scene(vm, parentModule) { + + TrackInfo *tracks = _vm->_staticData->getTrackInfo(trackInfoId); + + SetMessageHandler(&Scene2704::handleMessage); + SetUpdateHandler(&Scene2704::update); + + setBackground(tracks->bgFilename); + setPalette(tracks->bgFilename); + + if (tracks->exPaletteFilename1) + _palette->addPalette(tracks->exPaletteFilename1, 0, 65, 0); + + if (tracks->exPaletteFilename2) + _palette->addPalette(tracks->exPaletteFilename2, 65, 31, 65); + + while (staticSprites && *staticSprites) + insertStaticSprite(*staticSprites++, 1100); + + insertScreenMouse(tracks->mouseCursorFilename); + + if (tracks->bgShadowFilename) { + _ssTrackShadowBackground = createSprite<SsCommonTrackShadowBackground>(tracks->bgShadowFilename); + addEntity(_ssTrackShadowBackground); + _asCar = insertSprite<AsCommonCar>(this, 320, 240); + _asCarShadow = insertSprite<AsCommonCarShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + _asCarTrackShadow = insertSprite<AsCommonCarTrackShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + _asCarConnectorShadow = insertSprite<AsCommonCarConnectorShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + } else { + _ssTrackShadowBackground = NULL; + _asCarShadow = NULL; + _asCar = insertSprite<AsCommonCar>(this, 320, 240); + } + + _asCarConnector = insertSprite<AsCommonCarConnector>(_asCar); + _which1 = tracks->which1; + _which2 = tracks->which2; + _dataResource.load(tracks->dataResourceFilename); + _trackPoints = _dataResource.getPointArray(tracks->trackPointsName); + _asCar->setPathPoints(_trackPoints); + + if (which == _which2) { + NPoint testPoint = (*_trackPoints)[_trackPoints->size() - 1]; + sendMessage(_asCar, 0x2002, _trackPoints->size() - 1); + if (testPoint.x > 0 && testPoint.x < 640 && testPoint.y > 0 && testPoint.y < 480) + sendMessage(_asCar, 0x2009, 0); + else + sendMessage(_asCar, 0x2007, 0); + } else { + NPoint testPoint = (*_trackPoints)[0]; + sendMessage(_asCar, 0x2002, 0); + if (testPoint.x > 0 && testPoint.x < 640 && testPoint.y > 0 && testPoint.y < 480) + sendMessage(_asCar, 0x2009, 0); + else + sendMessage(_asCar, 0x2008, 0); + } + + if (clipRect) { + _asCar->getClipRect() = *clipRect; + if (_asCarShadow) + _asCarShadow->getClipRect() = *clipRect; + if (_asCarTrackShadow) + _asCarTrackShadow->getClipRect() = *clipRect; + if (_asCarConnectorShadow) + _asCarConnectorShadow->getClipRect() = *clipRect; + if (_asCarConnector) + _asCarConnector->getClipRect() = *clipRect; + } + +} + +void Scene2704::update() { + Scene::update(); + if (_mouseClicked) { + sendPointMessage(_asCar, 0x2004, _mouseClickPos); + _mouseClicked = false; + } +} + +uint32 Scene2704::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2005: + if (_which1 >= 0) + leaveScene(_which1); + break; + case 0x2006: + if (_which2 >= 0) + leaveScene(_which2); + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + } + return 0; +} + +Scene2706::Scene2706(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _newTrackIndex(-1) { + + SetMessageHandler(&Scene2706::handleMessage); + + _tracks.push_back(_vm->_staticData->getTrackInfo(0x004B22A0)); + _tracks.push_back(_vm->_staticData->getTrackInfo(0x004B22C4)); + _tracks.push_back(_vm->_staticData->getTrackInfo(0x004B22E8)); + + setBackground(0x18808B88); + setPalette(0x18808B88); + + _palette->addPalette(calcHash("paPodShade"), 65, 31, 65); + _palette->addPalette(calcHash("paKlayShade"), 0, 65, 0); + + insertScreenMouse(0x08B8C180); + + _ssTrackShadowBackground = createSprite<SsCommonTrackShadowBackground>(0x18808B88); + addEntity(_ssTrackShadowBackground); + + _asCar = insertSprite<AsCommonCar>(this, 320, 240); + _asCarShadow = insertSprite<AsCommonCarShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + _asCarConnector = insertSprite<AsCommonCarConnector>(_asCar); + _asCarTrackShadow = insertSprite<AsCommonCarTrackShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + _asCarConnectorShadow = insertSprite<AsCommonCarConnectorShadow>(_asCar, _ssTrackShadowBackground->getSurface(), 4); + + _dataResource.load(0x06000162); + + if (which == 5) + _currTrackIndex = 2; + else if (which == 6) + _currTrackIndex = 1; + else + _currTrackIndex = 0; + + _trackPoints = _dataResource.getPointArray(_tracks[_currTrackIndex]->trackPointsName); + _asCar->setPathPoints(_trackPoints); + + if (which == _tracks[_currTrackIndex]->which2) { + sendMessage(_asCar, 0x2002, _trackPoints->size() - 1); + if (which == 5) + sendMessage(_asCar, 0x2007, 50); + else + sendMessage(_asCar, 0x2007, 150); + } else { + sendMessage(_asCar, 0x2002, 0); + if (which == 5) + sendMessage(_asCar, 0x2008, 50); + else + sendMessage(_asCar, 0x2008, 150); + } + +} + +uint32 Scene2706::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + moveCarToPoint(param.asPoint()); + break; + case 0x2005: + if (_newTrackIndex >= 0) { + if (_tracks[_currTrackIndex]->which1 < 0) + changeTrack(); + } else if (_tracks[_currTrackIndex]->which1 >= 0) + leaveScene(_tracks[_currTrackIndex]->which1); + break; + case 0x2006: + if (_newTrackIndex >= 0) { + if (_tracks[_currTrackIndex]->which2 < 0) + changeTrack(); + } else if (_tracks[_currTrackIndex]->which2 >= 0) + leaveScene(_tracks[_currTrackIndex]->which2); + break; + case 0x200D: + sendMessage(_parentModule, 0x200D, 0); + break; + } + return 0; +} + +void Scene2706::moveCarToPoint(NPoint pt) { + int minMatchTrackIndex, minMatchDistance; + _tracks.findTrackPoint(pt, minMatchTrackIndex, minMatchDistance, _dataResource); + if (minMatchTrackIndex >= 0 && minMatchTrackIndex != _currTrackIndex) { + _newTrackIndex = minMatchTrackIndex; + _newTrackDestX = pt.x; + if (_currTrackIndex == 0) + sendMessage(_asCar, 0x2003, _trackPoints->size() - 1); + else + sendMessage(_asCar, 0x2003, 0); + } else { + _newTrackIndex = -1; + sendMessage(_asCar, 0x2004, pt.x); + } +} + +void Scene2706::changeTrack() { + _currTrackIndex = _newTrackIndex; + _trackPoints = _dataResource.getPointArray(_tracks[_currTrackIndex]->trackPointsName); + _asCar->setPathPoints(_trackPoints); + if (_currTrackIndex == 0) + sendMessage(_asCar, 0x2002, _trackPoints->size() - 1); + else + sendMessage(_asCar, 0x2002, 0); + sendMessage(_asCar, 0x2004, _newTrackDestX); + _newTrackIndex = -1; +} + +Scene2732::Scene2732(NeverhoodEngine *vm, Module *parentModule) + : Scene(vm, parentModule) { + + Sprite *tempSprite; + + setBackground(0x0220C041); + setPalette(0x0220C041); + insertScreenMouse(0x0C04502A); + setRectList(0x004AE360); + + insertKlaymen<KmScene2732>(108, 331); + setMessageList(0x004AE328); + + tempSprite = insertStaticSprite(0x50C22C48, 1100); + _klaymen->setClipRect(tempSprite->getDrawRect().x, 0, 640, 480); + +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module2700.h b/engines/neverhood/modules/module2700.h new file mode 100644 index 0000000000..003666bb7f --- /dev/null +++ b/engines/neverhood/modules/module2700.h @@ -0,0 +1,182 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_MODULES_MODULE2700_H +#define NEVERHOOD_MODULES_MODULE2700_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" +#include "neverhood/modules/module1600.h" + +namespace Neverhood { + +// Module2700 + +class Module2700 : public Module { +public: + Module2700(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module2700(); +protected: + int _sceneNum; + int _soundIndex; + bool _raidoMusicInitialized; + uint32 _scene2711StaticSprites[6]; + uint32 _musicFileHash; + void createScene(int sceneNum, int which); + void updateScene(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void createScene2703(int which, uint32 trackInfoId); + void createScene2704(int which, uint32 trackInfoId, int16 value, const uint32 *staticSprites = NULL, const NRect *clipRect = NULL); +}; + +class SsCommonTrackShadowBackground : public StaticSprite { +public: + SsCommonTrackShadowBackground(NeverhoodEngine *vm, uint32 fileHash); +}; + +class AsCommonCarShadow : public AnimatedSprite { +public: + AsCommonCarShadow(NeverhoodEngine *vm, AnimatedSprite *asCar, BaseSurface *shadowSurface, uint index); +protected: + uint _index; + AnimatedSprite *_asCar; + uint32 _animFileHash; + void update(); + void updateShadow(); +}; + +class AsCommonCarConnectorShadow : public AnimatedSprite { +public: + AsCommonCarConnectorShadow(NeverhoodEngine *vm, Sprite *asCar, BaseSurface *shadowSurface, uint index); +protected: + uint _index; + Sprite *_asCar; + void update(); +}; + +class AsCommonCarTrackShadow : public AnimatedSprite { +public: + AsCommonCarTrackShadow(NeverhoodEngine *vm, Sprite *asCar, BaseSurface *shadowSurface, int16 frameIndex); +protected: + Sprite *_asCar; + void update(); +}; + +class Scene2701 : public Scene { +public: + Scene2701(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + AsCommonCar *_asCar; + Sprite *_ssTrackShadowBackground; + Sprite *_asCarShadow; + Sprite *_asCarTrackShadow; + Sprite *_asCarConnectorShadow; + Sprite *_asCarConnector; + int _which1, _which2; + NPointArray *_trackPoints; + uint32 hmRidingCar(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmCarAtHome(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2702 : public Scene { +public: + Scene2702(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + AsCommonCar *_asCar; + Sprite *_ssTrackShadowBackground; + Sprite *_asCarShadow; + Sprite *_asCarTrackShadow; + Sprite *_asCarConnectorShadow; + int16 _newTrackDestX; + bool _isInLight; + int _currTrackIndex, _newTrackIndex; + bool _isUpperTrack; + Tracks _tracks; + NPointArray *_trackPoints; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void moveCarToPoint(NPoint pt); + void changeTrack(); +}; + +class Scene2703 : public Scene { +public: + Scene2703(NeverhoodEngine *vm, Module *parentModule, int which, uint32 trackInfoId); +protected: + AsCommonCar *_asCar; + Sprite *_ssTrackShadowBackground; + Sprite *_asCarShadow; + Sprite *_asCarConnector; + Sprite *_asCarTrackShadow; + Sprite *_asCarConnectorShadow; + int _palStatus; + int _which1, _which2; + NPointArray *_trackPoints; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2704 : public Scene { +public: + Scene2704(NeverhoodEngine *vm, Module *parentModule, int which, uint32 trackInfoId, int16 value, + const uint32 *staticSprites = NULL, const NRect *clipRect = NULL); +protected: + AsCommonCar *_asCar; + Sprite *_ssTrackShadowBackground; + Sprite *_asCarShadow; + Sprite *_asCarConnector; + Sprite *_asCarTrackShadow; + Sprite *_asCarConnectorShadow; + int _which1, _which2; + NPointArray *_trackPoints; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2706 : public Scene { +public: + Scene2706(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + AsCommonCar *_asCar; + Sprite *_ssTrackShadowBackground; + Sprite *_asCarShadow; + Sprite *_asCarConnector; + Sprite *_asCarTrackShadow; + Sprite *_asCarConnectorShadow; + int16 _newTrackDestX; + int _currTrackIndex, _newTrackIndex; + Tracks _tracks; + NPointArray *_trackPoints; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void moveCarToPoint(NPoint pt); + void changeTrack(); +}; + +class Scene2732 : public Scene { +public: + Scene2732(NeverhoodEngine *vm, Module *parentModule); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE2700_H */ diff --git a/engines/neverhood/modules/module2800.cpp b/engines/neverhood/modules/module2800.cpp new file mode 100644 index 0000000000..183de8e6b2 --- /dev/null +++ b/engines/neverhood/modules/module2800.cpp @@ -0,0 +1,3205 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module2800.h" +#include "neverhood/gamemodule.h" +#include "neverhood/modules/module1000.h" +#include "neverhood/modules/module1200.h" +#include "neverhood/modules/module1700.h" +#include "neverhood/modules/module2200.h" +#include "neverhood/diskplayerscene.h" + +namespace Neverhood { + +Module2800::Module2800(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule), _musicResource(NULL) { + + _currentMusicFileHash = 0; + _vm->_soundMan->addMusic(0x64210814, 0xD2FA4D14); + setGlobalVar(V_RADIO_MOVE_DISH_VIDEO, 1); + + if (which < 0) { + createScene(_vm->gameState().sceneNum, which); + } else if (which == 2) { + createScene(4, 3); + } else if (which == 1) { + createScene(4, 1); + } else { + createScene(0, 0); + } + +} + +Module2800::~Module2800() { + if (_musicResource) { + _musicResource->unload(); + delete _musicResource; + } + _vm->_soundMan->deleteGroup(0x64210814); +} + +void Module2800::createScene(int sceneNum, int which) { + debug("Module2800::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _vm->_soundMan->stopMusic(0xD2FA4D14, 0, 0); + _childObject = new Scene2801(_vm, this, which); + break; + case 1: + _vm->gameState().sceneNum = 1; + _vm->_soundMan->stopMusic(0xD2FA4D14, 0, 0); + if (getGlobalVar(V_RADIO_ENABLED)) + _childObject = new Scene2802(_vm, this, which); + else + createStaticScene(0x000C6444, 0xC6440008); + break; + case 2: + _vm->gameState().sceneNum = 2; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + if (getGlobalVar(V_KLAYMEN_SMALL)) + _childObject = new Scene2803Small(_vm, this, which); + else + _childObject = new Scene2803(_vm, this, which); + break; + case 3: + _vm->gameState().sceneNum = 3; + _childObject = new Scene2804(_vm, this, which); + break; + case 4: + _vm->gameState().sceneNum = 4; + _vm->_soundMan->stopMusic(0xD2FA4D14, 0, 2); + _childObject = new Scene2805(_vm, this, which); + break; + case 5: + _vm->gameState().sceneNum = 5; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + _childObject = new Scene2806(_vm, this, which); + break; + case 6: + _vm->gameState().sceneNum = 6; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + _childObject = new Scene2807(_vm, this, which); + break; + case 7: + _vm->gameState().sceneNum = 7; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + _childObject = new Scene2808(_vm, this, 0); + break; + case 8: + _vm->gameState().sceneNum = 8; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + _childObject = new Scene2809(_vm, this, which); + break; + case 9: + _vm->gameState().sceneNum = 9; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + _childObject = new Scene2810(_vm, this, which); + break; + case 10: + _vm->gameState().sceneNum = 10; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + _childObject = new Scene2808(_vm, this, 1); + break; + case 11: + _vm->gameState().sceneNum = 11; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + _childObject = new Scene2812(_vm, this, which); + break; + case 12: + _vm->gameState().sceneNum = 12; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + createStaticScene(0x0000A245, 0x0A241008); + break; + case 13: + _vm->gameState().sceneNum = 13; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + createStaticScene(0x81C60635, 0x60631814); + break; + case 14: + _vm->gameState().sceneNum = 14; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + createStaticScene(0xCA811204, 0x11200CA0); + break; + case 15: + _vm->gameState().sceneNum = 15; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + createStaticScene(0x2D438A00, 0x38A042DC); + break; + case 16: + _vm->gameState().sceneNum = 16; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + createStaticScene(0x0A806204, 0x062000A0); + break; + case 17: + _vm->gameState().sceneNum = 17; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + createStaticScene(0x010F9284, 0xF9280018); + break; + case 18: + _vm->gameState().sceneNum = 18; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + createStaticScene(0x0100022B, 0x0022F018); + break; + case 19: + _vm->gameState().sceneNum = 19; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + createStaticScene(0x10866205, 0x66201100); + break; + case 20: + _vm->gameState().sceneNum = 20; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + createStaticScene(0x01C58000, 0x58004014); + break; + case 21: + _vm->gameState().sceneNum = 21; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + _childObject = new Scene2822(_vm, this, which); + break; + case 22: + _vm->gameState().sceneNum = 22; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + createStaticScene(0x9408121E, 0x8121A948); + break; + case 23: + _vm->gameState().sceneNum = 23; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + createStaticScene(0x048C0600, 0xC0604040); + break; + case 24: + _vm->gameState().sceneNum = 24; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + createStaticScene(0x04270A94, 0x70A9004A); + break; + case 25: + _vm->gameState().sceneNum = 25; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + if (getGlobalVar(V_SHRINK_LIGHTS_ON)) + createStaticScene(0x01600204, 0x0020001E); + else + createStaticScene(0x08611204, 0x1120008E); + break; + case 26: + _vm->gameState().sceneNum = 26; + _vm->_soundMan->startMusic(0xD2FA4D14, 0, 2); + _childObject = new DiskplayerScene(_vm, this, 4); + break; + case 1001: + _vm->_soundMan->stopMusic(0xD2FA4D14, 0, 0); + createSmackerScene(0x00800801, true, true, false); + break; + } + SetUpdateHandler(&Module2800::updateScene); + _childObject->handleUpdate(); +} + +void Module2800::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult != 2) { + if (_musicResource) { + _musicResource->unload(); + delete _musicResource; + _musicResource = NULL; + } + _currentMusicFileHash = 0; + } + if (_moduleResult == 1) { + createScene(2, 0); + } else if (_moduleResult == 2) { + createScene(1, 0); + } else { + leaveModule(0); + } + break; + case 1: + if (_moduleResult == 0) { + createScene(0, 2); + } else { + createScene(1001, -1); + } + break; + case 2: + if (_moduleResult == 1) + createScene(3, 0); + else if (_moduleResult == 2) + createScene(5, 0); + else if (_moduleResult == 3) + createScene(6, 0); + else if (_moduleResult == 4) + createScene(9, 0); + else if (_moduleResult == 5) + createScene(25, 0); + else + createScene(0, 1); + break; + case 3: + createScene(2, 1); + break; + case 4: + if (_moduleResult == 1) { + leaveModule(1); + } else { + createScene(11, 1); + } + break; + case 5: + if (_moduleResult == 1) { + createScene(7, 0); + } else { + createScene(2, 2); + } + break; + case 6: + createScene(2, 3); + break; + case 7: + createScene(5, _moduleResult); + break; + case 8: + if (_moduleResult == 1) + createScene(10, 0); + else + createScene(9, 4); + break; + case 9: + if (_moduleResult == 1) + createScene(11, 0); + else if (_moduleResult == 2) + createScene(2, 0); + else if (_moduleResult == 3) + createScene(24, 0); + else if (_moduleResult == 4) + createScene(8, 0); + else if (_moduleResult == 6) + createScene(2, 6); + else if (_moduleResult == 11) + createScene(12, 0); + else if (_moduleResult == 12) + createScene(13, 0); + else if (_moduleResult == 13) + createScene(14, 0); + else if (_moduleResult == 14) + createScene(15, 0); + else if (_moduleResult == 15) + createScene(16, 0); + else if (_moduleResult == 16) + createScene(17, 0); + else if (_moduleResult == 17) + createScene(18, 0); + else if (_moduleResult == 18) + createScene(19, 0); + else if (_moduleResult == 19) + createScene(20, 0); + else if (_moduleResult == 20) + createScene(21, 0); + else if (_moduleResult == 21) + createScene(22, 0); + else if (_moduleResult == 22) + createScene(23, 0); + else + createScene(2, 4); + break; + case 10: + createScene(8, _moduleResult); + break; + case 11: + if (_moduleResult == 1) + createScene(4, 0); + else if (_moduleResult == 2) + createScene(26, 0); + else if (_moduleResult == 3) + createScene(9, 5); + else + createScene(9, 1); + break; + case 12: + createScene(9, 11); + break; + case 13: + createScene(9, 12); + break; + case 14: + createScene(9, 13); + break; + case 15: + createScene(9, 14); + break; + case 16: + createScene(9, 15); + break; + case 17: + createScene(9, 16); + break; + case 18: + createScene(9, 17); + break; + case 19: + createScene(9, 18); + break; + case 20: + createScene(9, 19); + break; + case 21: + createScene(9, 20); + break; + case 22: + createScene(9, 21); + break; + case 23: + createScene(9, 22); + break; + case 24: + createScene(9, 3); + break; + case 25: + createScene(2, 5); + break; + case 26: + createScene(11, 2); + break; + case 1001: + createScene(1, -1); + break; + } + } else { + switch (_sceneNum) { + case 0: + updateMusic(true); + break; + case 1: + updateMusic(false); + break; + } + } +} + +void Module2800::updateMusic(bool halfVolume) { + + uint32 newMusicFileHash = _vm->_gameModule->getCurrRadioMusicFileHash(); + + if (!_musicResource) + _musicResource = new MusicResource(_vm); + + if (newMusicFileHash != _currentMusicFileHash) { + _currentMusicFileHash = newMusicFileHash; + if (_currentMusicFileHash != 0) { + _musicResource->load(_currentMusicFileHash); + _musicResource->setVolume(halfVolume ? 60 : 100); + _musicResource->play(0); + } else { + _musicResource->stop(0); + } + } else if (_currentMusicFileHash != 0) { + if (!_musicResource->isPlaying()) { + _musicResource->setVolume(halfVolume ? 60 : 100); + _musicResource->play(0); + } else { + _musicResource->setVolume(halfVolume ? 60 : 100); + } + } else { + _musicResource->stop(0); + } + +} + +Scene2801::Scene2801(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + Sprite *_sprite1; + Sprite *_sprite2; + + _vm->gameModule()->initRadioPuzzle(); + + SetMessageHandler(&Scene2801::handleMessage); + SetUpdateHandler(&Scene::update); + + // Display the disabled radio; only possible when the left door is open + if (!getGlobalVar(V_RADIO_ENABLED)) + insertStaticSprite(0x0001264C, 100); + + if (which < 0) { + insertKlaymen<KmScene2801>(194, 430); + setMessageList(0x004B6BB8); + } else if (which == 1) { + insertKlaymen<KmScene2801>(443, 398); + setMessageList(0x004B6BC0); + } else if (which == 2) { + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) { + insertKlaymen<KmScene2801>(312, 432); + _klaymen->setDoDeltaX(1); + } else { + insertKlaymen<KmScene2801>(194, 432); + } + setMessageList(0x004B6C10); + } else { + insertKlaymen<KmScene2801>(0, 432); + setMessageList(0x004B6BB0); + } + + if (getGlobalVar(V_RADIO_ROOM_LEFT_DOOR)) { + setRectList(0x004B6CE0); + setBackground(0x01400666); + setPalette(0x01400666); + _paletteHash = 0x15021024; + _palette->addBasePalette(0x01400666, 0, 256, 0); + _sprite1 = insertStaticSprite(0x100CA0A8, 1100); + _sprite2 = insertStaticSprite(0x287C21A4, 1100); + _klaymen->setClipRect(_sprite1->getDrawRect().x, 0, _sprite2->getDrawRect().x2(), 480); + insertScreenMouse(0x0066201C); + _asTape = insertSprite<AsScene1201Tape>(this, 8, 1100, 302, 437, 0x9148A011); + addCollisionSprite(_asTape); + } else if (getGlobalVar(V_RADIO_ROOM_RIGHT_DOOR)) { + setRectList(0x004B6CD0); + setBackground(0x11E00684); + setPalette(0x11E00684); + _paletteHash = 0x15021024; + _palette->addBasePalette(0x11E00684, 0, 256, 0); + _sprite2 = insertStaticSprite(0x061601C8, 1100); + _klaymen->setClipRect(0, 0, _sprite2->getDrawRect().x2(), 480); + insertScreenMouse(0x00680116); + _asTape = insertSprite<SsScene1705Tape>(this, 8, 1100, 302, 437, 0x01142428); + addCollisionSprite(_asTape); + } else { + setRectList(0x004B6CF0); + setBackground(0x030006E6); + setPalette(0x030006E6); + _paletteHash = 0x15021024; + _palette->addBasePalette(0x030006E6, 0, 256, 0); + _sprite2 = insertStaticSprite(0x273801CE, 1100); + _klaymen->setClipRect(0, 0, _sprite2->getDrawRect().x2(), 480); + insertScreenMouse(0x006E2038); + _asTape = insertSprite<AsScene1201Tape>(this, 8, 1100, 302, 437, 0x9148A011); + addCollisionSprite(_asTape); + } + + addEntity(_palette); + + if (which == 1) { + _palette->addPalette(0xB103B604, 0, 65, 0); + _palette->addBasePalette(0xB103B604, 0, 65, 0); + } else { + _palette->addPalette(_paletteHash, 0, 65, 0); + _palette->addBasePalette(_paletteHash, 0, 65, 0); + } + +} + +Scene2801::~Scene2801() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX() ? 1 : 0); +} + +uint32 Scene2801::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x4826: + if (sender == _asTape) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList(0x004B6C40); + } + break; + case 0x482A: + _palette->addBasePalette(0xB103B604, 0, 65, 0); + _palette->startFadeToPalette(12); + break; + case 0x482B: + _palette->addBasePalette(_paletteHash, 0, 65, 0); + _palette->startFadeToPalette(12); + break; + } + return messageResult; +} + +Scene2802::Scene2802(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _currTuneStatus(0), _countdown1(0), _countdown2(0) { + + SetMessageHandler(&Scene2802::handleMessage); + SetUpdateHandler(&Scene2802::update); + insertPuzzleMouse(0x008810A8, 20, 620); + _smackerPlayer = addSmackerPlayer(new SmackerPlayer(_vm, this, 0x8284C100, true, true, true)); + _currRadioMusicIndex = getGlobalVar(V_CURR_RADIO_MUSIC_INDEX); + // Need to go to the first frame first to load up the palette + _smackerPlayer->gotoFrame(0); + // Now we can actually set the current radio frame + _smackerPlayer->gotoFrame(_currRadioMusicIndex); + _vm->_soundMan->addSound(0x04360A18, 0x422630C2); + _vm->_soundMan->addSound(0x04360A18, 0x00632252); + _vm->_soundMan->addSound(0x04360A18, 0x00372241); + _vm->_soundMan->setSoundVolume(0x00372241, 60); + changeTuneStatus(0, 0); + _vm->_soundMan->playSoundLooping(0x00372241); +} + +Scene2802::~Scene2802() { + _vm->_soundMan->deleteSoundGroup(0x04360A18); + if (_currRadioMusicIndex == 0) { + setGlobalVar(V_RADIO_ROOM_LEFT_DOOR, 1); + setGlobalVar(V_RADIO_ROOM_RIGHT_DOOR, 0); + } else if (_currRadioMusicIndex == getGlobalVar(V_GOOD_RADIO_MUSIC_INDEX)) { + setGlobalVar(V_RADIO_ROOM_LEFT_DOOR, 0); + setGlobalVar(V_RADIO_ROOM_RIGHT_DOOR, 1); + } else { + setGlobalVar(V_RADIO_ROOM_LEFT_DOOR, 0); + setGlobalVar(V_RADIO_ROOM_RIGHT_DOOR, 0); + } + setGlobalVar(V_CURR_RADIO_MUSIC_INDEX, _currRadioMusicIndex); +} + +void Scene2802::update() { + int prevTuneStatus = _currTuneStatus; + uint prevRadioMusicIndex = _currRadioMusicIndex; + + Scene::update(); + if (_countdown1 > 0) + --_countdown1; + else if (_currTuneStatus == 1) + _currTuneStatus = 3; + else if (_currTuneStatus == 4) + _currTuneStatus = 6; + + switch (_currTuneStatus) { + case 2: + if (_currRadioMusicIndex < 90) + incRadioMusicIndex(+1); + _currTuneStatus = 0; + break; + case 3: + if (_countdown2 > 0) + --_countdown2; + else if (_currRadioMusicIndex < 90) { + incRadioMusicIndex(+1); + _countdown2 = 1; + } else + _currTuneStatus = 0; + break; + case 5: + if (_currRadioMusicIndex > 0) + incRadioMusicIndex(-1); + _currTuneStatus = 0; + break; + case 6: + if (_countdown2 > 0) + --_countdown2; + else if (_currRadioMusicIndex > 0) { + incRadioMusicIndex(-1); + _countdown2 = 1; + } else + _currTuneStatus = 0; + break; + + } + + if (prevRadioMusicIndex != _currRadioMusicIndex) + _smackerPlayer->gotoFrame(_currRadioMusicIndex); + + if (prevTuneStatus != _currTuneStatus) + changeTuneStatus(prevTuneStatus, _currTuneStatus); + + //DEBUG>>> + //debug("_currRadioMusicIndex = %d; V_GOOD_RADIO_MUSIC_INDEX = %d", _currRadioMusicIndex, getGlobalVar(V_GOOD_RADIO_MUSIC_INDEX)); + //DEBUG<<< + + if (getGlobalVar(V_RADIO_MOVE_DISH_VIDEO) && prevTuneStatus != _currTuneStatus && _currRadioMusicIndex != 0) { + setGlobalVar(V_RADIO_MOVE_DISH_VIDEO, 0); + leaveScene(1); + } + +} + +uint32 Scene2802::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + int prevTuneStatus = _currTuneStatus; + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) { + leaveScene(0); + } else if (_currTuneStatus == 0) { + if (param.asPoint().x > 180 && param.asPoint().x < 300 && + param.asPoint().y > 130 && param.asPoint().y < 310) { + _currTuneStatus = 4; + } else if (param.asPoint().x > 300 && param.asPoint().x < 400 && + param.asPoint().y > 130 && param.asPoint().y < 310) { + _currTuneStatus = 1; + } + if (_currTuneStatus == 1 || _currTuneStatus == 4) { + _countdown1 = 8; + changeTuneStatus(0, _currTuneStatus); + } + } + break; + case 0x0002: + if (_countdown1 == 0) + _currTuneStatus = 0; + else { + if (_currTuneStatus == 1) + _currTuneStatus = 2; + else if (_currTuneStatus == 4) + _currTuneStatus = 5; + else + _currTuneStatus = 0; + _countdown1 = 0; + } + if (prevTuneStatus != _currTuneStatus) + changeTuneStatus(prevTuneStatus, _currTuneStatus); + break; + } + return 0; +} + +void Scene2802::incRadioMusicIndex(int delta) { + _currRadioMusicIndex += delta; + setGlobalVar(V_CURR_RADIO_MUSIC_INDEX, _currRadioMusicIndex); +} + +void Scene2802::changeTuneStatus(int prevTuneStatus, int newTuneStatus) { + + if (prevTuneStatus == 3 || prevTuneStatus == 6) { + _vm->_soundMan->stopSound(0x422630C2); + _vm->_soundMan->stopSound(0x00632252); + } + + if (newTuneStatus == 0) { + if (_vm->_gameModule->getCurrRadioMusicFileHash() != 0) + _vm->_soundMan->stopSound(0x00632252); + else + _vm->_soundMan->playSoundLooping(0x00632252); + } else if (newTuneStatus == 3 || newTuneStatus == 6) { + _vm->_soundMan->playSoundLooping(0x422630C2); + _vm->_soundMan->playSoundLooping(0x00632252); + } + +} + +AsScene2803LightCord::AsScene2803LightCord(NeverhoodEngine *vm, Scene *parentScene, uint32 fileHash1, uint32 fileHash2, int16 x, int16 y) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _fileHash1(fileHash1), _fileHash2(fileHash2), + _isPulled(false), _isBusy(false) { + + createSurface(1010, 28, 379); + SetUpdateHandler(&AnimatedSprite::update); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + _x = x; + _y = y; + stIdle(); +} + +uint32 AsScene2803LightCord::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (!_isBusy && param.asInteger() == calcHash("ClickSwitch")) { + sendMessage(_parentScene, 0x480F, 0); + playSound(0, 0x4E1CA4A0); + } + break; + case 0x480F: + stPulled(); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +uint32 AsScene2803LightCord::hmPulled(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene2803LightCord::stPulled() { + _isBusy = false; + _isPulled = true; + startAnimation(_fileHash2, 0, -1); + SetMessageHandler(&AsScene2803LightCord::hmPulled); + NextState(&AsScene2803LightCord::stIdle); +} + +void AsScene2803LightCord::stIdle() { + _isPulled = false; + startAnimation(_fileHash1, 0, -1); + SetMessageHandler(&AsScene2803LightCord::handleMessage); +} + +void AsScene2803LightCord::setFileHashes(uint32 fileHash1, uint32 fileHash2) { + _fileHash1 = fileHash1; + _fileHash2 = fileHash2; + if (_isPulled) { + startAnimation(_fileHash2, _currFrameIndex, -1); + _isBusy = true; + } else { + startAnimation(_fileHash1, 0, -1); + } +} + +AsScene2803TestTubeOne::AsScene2803TestTubeOne(NeverhoodEngine *vm, uint32 fileHash1, uint32 fileHash2) + : AnimatedSprite(vm, 1200), _fileHash1(fileHash1), _fileHash2(fileHash2) { + + createSurface1(fileHash1, 100); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2803TestTubeOne::handleMessage); + _x = 529; + _y = 326; +} + +uint32 AsScene2803TestTubeOne::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + if (param.asInteger()) + startAnimation(_fileHash2, 0, -1); + else + startAnimation(_fileHash1, 0, -1); + break; + } + return messageResult; +} + +AsScene2803Rope::AsScene2803Rope(NeverhoodEngine *vm, Scene *parentScene, int16 x) + : AnimatedSprite(vm, 1100), _parentScene(parentScene) { + + createSurface(990, 68, 476); + SetUpdateHandler(&AnimatedSprite::update); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + SetMessageHandler(&AsScene2803Rope::handleMessage); + startAnimation(0x9D098C23, 35, 53); + NextState(&AsScene2803Rope::stReleased); + _x = x; + _y = -276; +} + +uint32 AsScene2803Rope::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + startAnimation(0x9D098C23, 50, -1); + SetMessageHandler(&AsScene2803Rope::hmReleased); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +uint32 AsScene2803Rope::hmReleased(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +void AsScene2803Rope::stReleased() { + startAnimation(0x8258A030, 0, 1); + NextState(&AsScene2803Rope::stHide); +} + +void AsScene2803Rope::stHide() { + stopAnimation(); + setVisible(false); +} + +Scene2803::Scene2803(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _paletteArea(0) { + + static const uint32 kScene2803FileHashes1[] = { + 0, + 0x081000F1, + 0x08100171, + 0x08100271 + }; + + static const uint32 kScene2803FileHashes2[] = { + 0, + 0x286800D4, + 0x286806D4, + 0x28680AD4 + }; + + setGlobalVar(V_BEEN_SHRINKING_ROOM, 1); + _vm->gameModule()->initTestTubes1Puzzle(); + + SetMessageHandler(&Scene2803::handleMessage); + + loadDataResource(0x00900849); + + _background = new Background(_vm, 0); + _background->createSurface(0, 640, 480); + addBackground(_background); + + setPalette(0x412A423E); + addEntity(_palette); + + insertScreenMouse(0xA423A41A); + + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0) == 0) { + _asTestTubeOne = (StaticSprite*)insertStaticSprite(0x66121222, 100); + } else { + _asTestTubeOne = (StaticSprite*)insertSprite<AsScene2803TestTubeOne>( + kScene2803FileHashes1[getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0)], + kScene2803FileHashes2[getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0)]); + } + + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 1) == 3) + _asTestTubeTwo = (StaticSprite*)insertStaticSprite(0x64330236, 100); + + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 2) == 3) + _asTestTubeThree = (StaticSprite*)insertStaticSprite(0x2E4A22A2, 100); + + _asLightCord = insertSprite<AsScene2803LightCord>(this, 0x8FAD5932, 0x276E1A3D, 578, 200); + _sprite3 = (StaticSprite*)insertStaticSprite(0xA40EF2FB, 1100); + _sprite4 = (StaticSprite*)insertStaticSprite(0x0C03AA23, 1100); + _sprite5 = (StaticSprite*)insertStaticSprite(0x2A822E2E, 1100); + _sprite6 = (StaticSprite*)insertStaticSprite(0x2603A202, 1100); + _sprite7 = (StaticSprite*)insertStaticSprite(0x24320220, 1100); + _sprite8 = (StaticSprite*)insertStaticSprite(0x3C42022F, 1100); + _sprite9 = (StaticSprite*)insertStaticSprite(0x341A0237, 1100); + _sprite10 = insertStaticSprite(0x855820A3, 1200); + + _clipRectsFloor[0].x1 = 0; + _clipRectsFloor[0].y1 = 0; + _clipRectsFloor[0].x2 = 640; + _clipRectsFloor[0].y2 = _sprite8->getDrawRect().y2(); + + _clipRectsFloor[1].x1 = _sprite8->getDrawRect().x2(); + _clipRectsFloor[1].y1 = _sprite8->getDrawRect().y2(); + _clipRectsFloor[1].x2 = 640; + _clipRectsFloor[1].y2 = 480; + + _clipRectsStairs[0].x1 = _sprite5->getDrawRect().x; + _clipRectsStairs[0].y1 = 0; + _clipRectsStairs[0].x2 = _sprite5->getDrawRect().x2(); + _clipRectsStairs[0].y2 = _sprite5->getDrawRect().y2(); + + _clipRectsStairs[1].x1 = _sprite6->getDrawRect().x; + _clipRectsStairs[1].y1 = 0; + _clipRectsStairs[1].x2 = _sprite3->getDrawRect().x; + _clipRectsStairs[1].y2 = _sprite6->getDrawRect().y2(); + + _clipRectsStairs[2].x1 = _sprite3->getDrawRect().x; + _clipRectsStairs[2].y1 = 0; + _clipRectsStairs[2].x2 = _sprite4->getDrawRect().x2(); + _clipRectsStairs[2].y2 = 480; + + if (which < 0) { + insertKlaymen<KmScene2803>(302, 445, _clipRectsFloor, 2); + setMessageList(0x004B79F0); + klaymenFloor(); + } else if (which == 1) { + insertKlaymen<KmScene2803>(200, 445, _clipRectsFloor, 2); + setMessageList(0x004B79C8); + klaymenFloor(); + } else if (which == 3) { + NPoint pt = _dataResource.getPoint(0xC2A08694); + insertKlaymen<KmScene2803>(pt.x, pt.y, _clipRectsStairs, 3); + setMessageList(0x004B7A00); + klaymenStairs(); + } else if (which == 5) { + insertKlaymen<KmScene2803>(253, 298, _clipRectsStairs, 3); + setMessageList(0x004B7A00); + klaymenStairs(); + } else if (which == 6) { + _asRope = insertSprite<AsScene2803Rope>(this, 384); + _asRope->setClipRect(0, 25, 640, 480); + insertKlaymen<KmScene2803>(384, 0, _clipRectsFloor, 2); + sendEntityMessage(_klaymen, 0x1014, _asRope); + _klaymen->setClipRect(0, 25, 640, 480); + setMessageList(0x004B7A78); + klaymenFloor(); + } else if (which == 2) { + insertKlaymen<KmScene2803>(400, 445, _clipRectsFloor, 2); + setMessageList(0x004B79F8); + klaymenFloor(); + } else { + insertKlaymen<KmScene2803>(50, 231, _clipRectsStairs, 3); + setMessageList(0x004B79C0); + klaymenStairs(); + } + + changeBackground(); + +} + +void Scene2803::upKlaymenStairs() { + if (_klaymen->getX() < 350) { + setPaletteArea0(); + } else { + setPaletteArea1(); + } + Scene::update(); +} + +uint32 Scene2803::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x480F: + toggleBackground(); + // NOTE Intentional fall-through + case 0x100D: + if (param.asInteger() == 0x84251F82) + setMessageList(0x004B7A50); + else if (param.asInteger() == 0x4254A2D2) + setMessageList(0x004B7A58); + else if (param.asInteger() == 0xE90A40A0) + setMessageList(0x004B7A08); + else if (param.asInteger() == 0x482D1210) + setMessageList(0x004B7A30); + else if (param.asInteger() == 0x802402B2) { + sendEntityMessage(_klaymen, 0x1014, _asLightCord); + setMessageList(0x004B7A68); + } else if (param.asInteger() == 0x9626F390) + setMessageList(0x004B7A88); + break; + case 0x482A: + klaymenStairs(); + setPaletteArea1(); + break; + case 0x482B: + klaymenFloor(); + setPaletteArea0(); + break; + } + return messageResult; +} + +void Scene2803::klaymenStairs() { + SetUpdateHandler(&Scene2803::upKlaymenStairs); + _klaymen->getSurface()->setClipRects(_clipRectsStairs, 3); + sendMessage(_klaymen, 0x482C, 0xE5A48297); + _sprite3->setVisible(true); + _sprite4->setVisible(true); + _sprite5->setVisible(true); + _sprite6->setVisible(true); + _sprite7->setVisible(true); + _sprite8->setVisible(false); + _sprite9->setVisible(false); +} + +void Scene2803::klaymenFloor() { + SetUpdateHandler(&Scene::update); + _klaymen->getSurface()->setClipRects(_clipRectsFloor, 2); + sendMessage(_klaymen, 0x482C, 0); + _sprite3->setVisible(false); + _sprite4->setVisible(false); + _sprite5->setVisible(false); + _sprite6->setVisible(false); + _sprite7->setVisible(false); + _sprite8->setVisible(true); + _sprite9->setVisible(true); +} + +void Scene2803::toggleBackground() { + setGlobalVar(V_SHRINK_LIGHTS_ON, getGlobalVar(V_SHRINK_LIGHTS_ON) ? 0 : 1); + changeBackground(); +} + +void Scene2803::changeBackground() { + if (getGlobalVar(V_SHRINK_LIGHTS_ON)) { + _asLightCord->setFileHashes(0x8FAD5932, 0x276E1A3D); + _background->load(0x412A423E); + _palette->addPalette(0x412A423E, 0, 256, 0); + _palette->addBasePalette(0x412A423E, 0, 256, 0); + _sprite3->loadSprite(0xA40EF2FB); + _sprite4->loadSprite(0x0C03AA23); + _sprite5->loadSprite(0x2A822E2E); + _sprite6->loadSprite(0x2603A202); + _sprite7->loadSprite(0x24320220); + _mouseCursor->load(0xA423A41A); + _mouseCursor->updateCursor(); + _sprite8->loadSprite(0x3C42022F); + _sprite9->loadSprite(0x341A0237); + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0) == 0) + _asTestTubeOne->loadSprite(0x66121222); + else + sendMessage(_asTestTubeOne, 0x2000, 0); + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 1) == 3) + _asTestTubeTwo->loadSprite(0x64330236); + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 2) == 3) + _asTestTubeThree->loadSprite(0x2E4A22A2); + _sprite10->setVisible(true); + } else { + _asLightCord->setFileHashes(0xAFAD591A, 0x276E321D); + _background->load(0x29800A01); + _palette->addPalette(0x29800A01, 0, 256, 0); + _palette->addBasePalette(0x29800A01, 0, 256, 0); + _sprite3->loadSprite(0x234340A0); + _sprite4->loadSprite(0x16202200); + _sprite5->loadSprite(0x1030169A); + _sprite6->loadSprite(0x1600A6A8); + _sprite7->loadSprite(0xD0802EA0); + _mouseCursor->load(0x00A05290); + _mouseCursor->updateCursor(); + _sprite8->loadSprite(0x108012C1); + _sprite9->loadSprite(0x708072E0); + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0) != 0) + sendMessage(_asTestTubeOne, 0x2000, 1); + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 1) == 3) + _asTestTubeTwo->loadSprite(0xD48077A0); + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 2) == 3) + _asTestTubeThree->loadSprite(0x30022689); + _sprite10->setVisible(false); + } + updatePaletteArea(); +} + +void Scene2803::setPaletteArea0() { + if (_paletteArea != 0) { + _paletteArea = 0; + updatePaletteArea(); + } +} + +void Scene2803::setPaletteArea1() { + if (_paletteArea != 1) { + _paletteArea = 1; + updatePaletteArea(); + } +} + +void Scene2803::updatePaletteArea() { + uint32 fadePaletteHash; + if (getGlobalVar(V_SHRINK_LIGHTS_ON)) + fadePaletteHash = (_paletteArea == 1) ? 0xB103B604 : 0x412A423E; + else + fadePaletteHash = (_paletteArea == 1) ? 0x0263D144 : 0x29800A01; + _palette->addBasePalette(fadePaletteHash, 0, 64, 0); + _palette->startFadeToPalette(12); +} + +Scene2803Small::Scene2803Small(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _paletteArea(0) { + + static const uint32 kScene2803SmallFileHashes1[] = { + 0, 0x081000F1, 0x08100171, 0x08100271 + }; + + static const uint32 kScene2803SmallFileHashes2[] = { + 0, 0x286800D4, 0x286806D4, 0x28680AD4 + }; + + SetMessageHandler(&Scene2803Small::handleMessage); + + loadDataResource(0x81120132); + insertScreenMouse(0x00A05290); + + insertSprite<AsScene2803LightCord>(this, 0xAFAD591A, 0x276E321D, 578, 200); + + if (getGlobalVar(V_SHRINK_LIGHTS_ON)) { + setBackground(0x412A423E); + setPalette(0x412A423E); + _palette->addBasePalette(0x412A423E, 0, 256, 0); + addEntity(_palette); + _sprite1 = insertStaticSprite(0x0C03AA23, 1100); + _sprite2 = insertStaticSprite(0x24320220, 1100); + _sprite3 = insertStaticSprite(0x1A032204, 1100); + _sprite4 = insertStaticSprite(0x18032204, 1100); + _sprite5 = insertStaticSprite(0x34422912, 1100); + _sprite6 = insertStaticSprite(0x3C42022F, 1100); + _sprite7 = insertStaticSprite(0x341A0237, 1100); + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0) == 0) + insertStaticSprite(0x66121222, 100); + else + insertSprite<AnimatedSprite>(kScene2803SmallFileHashes1[getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0)], 100, 529, 326); + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 1) == 3) + insertStaticSprite(0x64330236, 100); + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 2) == 3) + insertStaticSprite(0x2E4A22A2, 100); + } else { + setBackground(0x29800A01); + setPalette(0x29800A01); + _palette->addBasePalette(0x29800A01, 0, 256, 0); + addEntity(_palette); + _sprite1 = insertStaticSprite(0x16202200, 1100); + _sprite2 = insertStaticSprite(0xD0802EA0, 1100); + _sprite3 = insertStaticSprite(0x780C2E30, 1100); + _sprite4 = insertStaticSprite(0x700C2E30, 1100); + _sprite5 = insertStaticSprite(0x102CE6E1, 900); + _sprite6 = insertStaticSprite(0x108012C1, 1100); + _sprite7 = insertStaticSprite(0x708072E0, 1100); + insertStaticSprite(0x90582EA4, 100); + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0) != 0) + insertSprite<AnimatedSprite>(kScene2803SmallFileHashes2[getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0)], 100, 529, 326); + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 1) == 3) + insertStaticSprite(0xD48077A0, 100); + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 2) == 3) + insertStaticSprite(0x30022689, 100); + } + + _sprite6->setVisible(false); + _sprite7->setVisible(false); + + if (which < 0) { + insertKlaymen<KmScene2803Small>(479, 435); + klaymenFloor(); + setMessageList(0x004B60D8); + } else if (which == 3) { + NPoint pt = _dataResource.getPoint(0x096520ED); + insertKlaymen<KmScene2803Small>(pt.x, pt.y); + klaymenSlope(); + setMessageList(0x004B6100); + _klaymen->setRepl(64, 0); + } else if (which == 4) { + NPoint pt = _dataResource.getPoint(0x20C6238D); + insertKlaymen<KmScene2803Small>(pt.x, pt.y); + klaymenSlope(); + setMessageList(0x004B60F8); + _klaymen->setRepl(64, 0); + } else if (which == 5) { + NPoint pt = _dataResource.getPoint(0x2146690D); + insertKlaymen<KmScene2803Small>(pt.x, pt.y); + klaymenSlope(); + setMessageList(0x004B6100); + _klaymen->setRepl(64, 0); + } else if (which == 2) { + NPoint pt = _dataResource.getPoint(0x104C03ED); + insertKlaymen<KmScene2803Small>(pt.x, pt.y); + klaymenFloor(); + setMessageList(0x004B6138); + } else { + insertKlaymen<KmScene2803Small>(135, 444); + klaymenFloor(); + setMessageList(0x004B60E0, false); + _sprite6->setVisible(true); + _sprite7->setVisible(true); + } + +} + +uint32 Scene2803Small::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0xB4E4884C) { + setMessageList(0x004B6180); + } else if (param.asInteger() == 0xB1FDAB2E) { + NPoint pt = _dataResource.getPoint(0x0D84A1AD); + _klaymen->setX(pt.x); + _klaymen->setY(pt.y); + _klaymen->updateBounds(); + klaymenFloor(); + _klaymen->setClipRect(517, 401, 536, 480); + setMessageList(0x004B6198); + } else if (param.asInteger() == 0xB00C7C48) { + setMessageList(0x004B6108); + } else if (param.asInteger() == 0x61F64346) { + setMessageList(0x004B6150); + } else if (param.asInteger() == 0xAC69A28D) { + setMessageList(0x004B6168); + } else if (param.asInteger() == 0x00086212) { + _klaymen->setClipRect(0, 0, 560, 315); + _klaymen->setX(560); + _klaymen->setY(315); + _klaymen->updateBounds(); + klaymenSlope(); + setMessageList(0x004B61A0); + } else if (param.asInteger() == 0x002CAA68) { + setMessageList(0x004B61A8); + } + break; + case 0x482A: + if (_klaymen->getX() < 200) { + setPaletteArea3(); + } else if (_klaymen->getX() < 500) { + setSurfacePriority(_sprite5->getSurface(), 1100); + sendMessage(_klaymen, 0x482C, 0); + setPaletteArea2(); + } else { + _klaymen->setClipRect(517, 401, 536, 480); + setPaletteArea2(); + } + break; + case 0x482B: + _sprite6->setVisible(false); + _sprite7->setVisible(false); + _klaymen->setClipRect(0, 0, 640, 480); + setSurfacePriority(_sprite5->getSurface(), 900); + sendMessage(_klaymen, 0x482C, 0x2086222D); + break; + } + return 0; +} + +void Scene2803Small::upKlaymenSlope() { + if (_klaymen->getX() < 388) { + _klaymen->setClipRect(_sprite3->getDrawRect().x, 0, 640, _sprite3->getDrawRect().y2()); + setPaletteArea0(); + } else if (_klaymen->getX() < 500) { + _klaymen->setClipRect(0, 0, _sprite1->getDrawRect().x2(), _sprite1->getDrawRect().y2()); + setPaletteArea1(); + } + Scene::update(); +} + +void Scene2803Small::upKlaymenFloor() { + if (_klaymen->getX() > 194 && _klaymen->getX() < 273) + setPaletteArea2(); + else if (_klaymen->getX() > 155 && _klaymen->getX() < 300) + setPaletteArea0(); + Scene::update(); +} + +void Scene2803Small::klaymenSlope() { + SetUpdateHandler(&Scene2803Small::upKlaymenSlope); + sendMessage(_klaymen, 0x482C, 0x23C630D9); + _klaymen->setClipRect(0, 0, _sprite1->getDrawRect().x2(), _sprite1->getDrawRect().y2()); + _klaymen->setRepl(64, 0); + _sprite1->setVisible(true); +} + +void Scene2803Small::klaymenFloor() { + SetUpdateHandler(&Scene2803Small::upKlaymenFloor); + sendMessage(_klaymen, 0x482C, 0x2086222D); + _klaymen->setClipRect(0, 0, 640, 480); + _klaymen->clearRepl(); + _sprite1->setVisible(false); +} + +void Scene2803Small::setPaletteArea0() { + if (_paletteArea != 0) { + _paletteArea = 0; + updatePaletteArea(false); + } +} + +void Scene2803Small::setPaletteArea1() { + if (_paletteArea != 1) { + _paletteArea = 1; + updatePaletteArea(false); + } +} + +void Scene2803Small::setPaletteArea2() { + if (_paletteArea != 2) { + _paletteArea = 2; + updatePaletteArea(false); + } +} + +void Scene2803Small::setPaletteArea3() { + if (_paletteArea != 3) { + _paletteArea = 3; + updatePaletteArea(true); + } +} + +void Scene2803Small::updatePaletteArea(bool instantly) { + if (getGlobalVar(V_SHRINK_LIGHTS_ON)) { + switch (_paletteArea) { + case 1: + _palette->addBasePalette(0x0A938204, 0, 64, 0); + break; + case 2: + _palette->addBasePalette(0xB103B604, 0, 64, 0); + break; + case 3: + _palette->fillBaseBlack(0, 64); + break; + default: + _palette->addBasePalette(0x412A423E, 0, 64, 0); + break; + } + } else { + switch (_paletteArea) { + case 2: + _palette->addBasePalette(0x0263D144, 0, 64, 0); + break; + case 3: + _palette->fillBaseBlack(0, 64); + break; + default: + _palette->addBasePalette(0x29800A01, 0, 64, 0); + break; + } + } + _palette->startFadeToPalette(instantly ? 0 : 12); +} + +SsScene2804RedButton::SsScene2804RedButton(NeverhoodEngine *vm, Scene2804 *parentScene) + : StaticSprite(vm, 900), _countdown(0), _parentScene(parentScene) { + + loadSprite(getGlobalVar(V_SHRINK_LIGHTS_ON) ? 0x51A10202 : 0x11814A21, kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset, 400); + setVisible(false); + SetUpdateHandler(&SsScene2804RedButton::update); + SetMessageHandler(&SsScene2804RedButton::handleMessage); + loadSound(0, 0x44241240); +} + +void SsScene2804RedButton::update() { + updatePosition(); + if (_countdown != 0 && (--_countdown) == 0) { + setVisible(false); + } +} + +uint32 SsScene2804RedButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_countdown == 0 && !_parentScene->isWorking()) { + playSound(0); + setVisible(true); + _countdown = 4; + sendMessage(_parentScene, 0x2000, 0); + } + messageResult = 1; + break; + } + return messageResult; +} + +SsScene2804LightCoil::SsScene2804LightCoil(NeverhoodEngine *vm) + : StaticSprite(vm, 900) { + + loadSprite(0x8889B008, kSLFDefDrawOffset | kSLFDefPosition, 400); + setVisible(false); + SetMessageHandler(&SsScene2804LightCoil::handleMessage); +} + +uint32 SsScene2804LightCoil::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2002: + setVisible(true); + updatePosition(); + messageResult = 1; + break; + case 0x2003: + setVisible(false); + updatePosition(); + messageResult = 1; + break; + } + return messageResult; +} + +SsScene2804LightTarget::SsScene2804LightTarget(NeverhoodEngine *vm) + : StaticSprite(vm, 900) { + + loadSprite(0x06092132, kSLFDefDrawOffset | kSLFDefPosition, 400); + setVisible(false); + SetMessageHandler(&SsScene2804LightTarget::handleMessage); +} + +uint32 SsScene2804LightTarget::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2004: + setVisible(true); + updatePosition(); + messageResult = 1; + break; + case 0x2005: + setVisible(false); + updatePosition(); + messageResult = 1; + break; + } + return messageResult; +} + +SsScene2804Flash::SsScene2804Flash(NeverhoodEngine *vm) + : StaticSprite(vm, 900) { + + loadSprite(0x211003A0, kSLFDefDrawOffset | kSLFDefPosition, 400); + setVisible(false); + loadSound(0, 0xCB36BA54); +} + +void SsScene2804Flash::show() { + setVisible(true); + updatePosition(); + playSound(0); +} + +SsScene2804BeamCoilBody::SsScene2804BeamCoilBody(NeverhoodEngine *vm) + : StaticSprite(vm, 900) { + + loadSprite(0x9A816000, kSLFDefDrawOffset | kSLFDefPosition, 400); + setVisible(false); +} + +AsScene2804CrystalWaves::AsScene2804CrystalWaves(NeverhoodEngine *vm, uint crystalIndex) + : AnimatedSprite(vm, 1100), _crystalIndex(crystalIndex) { + + static const NPoint kAsScene2804CrystalWavesPoints[] = { + {323, 245}, + {387, 76}, + {454, 260}, + {527, 70} + }; + + _x = kAsScene2804CrystalWavesPoints[crystalIndex].x; + _y = kAsScene2804CrystalWavesPoints[crystalIndex].y; + createSurface1(0x840C41F0, 1200); + if (crystalIndex & 1) + setDoDeltaY(1); + setVisible(false); + _needRefresh = true; + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&Sprite::handleMessage); +} + +void AsScene2804CrystalWaves::show() { + setVisible(true); + startAnimation(0x840C41F0, 0, -1); +} + +void AsScene2804CrystalWaves::hide() { + setVisible(false); + stopAnimation(); +} + +static const int16 kAsScene2804CrystalFrameNums[] = { + 0, 6, 2, 8, 1, 10, 0, 0 +}; + +static const uint32 kAsScene2804CrystalFileHashes[] = { + 0x000540B0, + 0x001280D0, + 0x003D0010, + 0x00620190, + 0x00DC0290 +}; + +AsScene2804Crystal::AsScene2804Crystal(NeverhoodEngine *vm, AsScene2804CrystalWaves *asCrystalWaves, uint crystalIndex) + : AnimatedSprite(vm, 1100), _asCrystalWaves(asCrystalWaves), _crystalIndex(crystalIndex), _isShowing(false) { + + static const NPoint kAsScene2804CrystalPoints[] = { + {204, 196}, + {272, 316}, + {334, 206}, + {410, 334}, + {470, 180} + }; + + _colorNum = (int16)getSubVar(VA_CURR_CRYSTAL_COLORS, crystalIndex); + _isLightOn = getGlobalVar(V_SHRINK_LIGHTS_ON) != 0; + if (_isLightOn) { + _x = kAsScene2804CrystalPoints[crystalIndex].x; + _y = kAsScene2804CrystalPoints[crystalIndex].y; + createSurface1(0x108DFB12, 1200); + startAnimation(0x108DFB12, kAsScene2804CrystalFrameNums[_colorNum], -1); + _needRefresh = true; + _newStickFrameIndex = kAsScene2804CrystalFrameNums[_colorNum]; + } else { + _x = 320; + _y = 240; + createSurface1(kAsScene2804CrystalFileHashes[crystalIndex], 1200); + startAnimation(kAsScene2804CrystalFileHashes[crystalIndex], _colorNum, -1); + setVisible(false); + _needRefresh = true; + _newStickFrameIndex = _colorNum; + } + loadSound(0, 0x725294D4); + SetUpdateHandler(&AnimatedSprite::update); +} + +void AsScene2804Crystal::show() { + if (!_isLightOn) { + setVisible(true); + _isShowing = true; + if (_asCrystalWaves) + _asCrystalWaves->show(); + playSound(0); + } +} + +void AsScene2804Crystal::hide() { + if (!_isLightOn) { + setVisible(false); + _isShowing = false; + if (_asCrystalWaves) + _asCrystalWaves->hide(); + } +} + +void AsScene2804Crystal::activate() { + if (!_isShowing) { + int16 frameNum = kAsScene2804CrystalFrameNums[_colorNum]; + _colorNum++; + if (_colorNum >= 6) + _colorNum = 0; + if (_isLightOn) { + startAnimation(0x108DFB12, frameNum, kAsScene2804CrystalFrameNums[_colorNum]); + _playBackwards = kAsScene2804CrystalFrameNums[_colorNum] < _colorNum; + _newStickFrameIndex = kAsScene2804CrystalFrameNums[_colorNum]; + } else { + startAnimation(kAsScene2804CrystalFileHashes[_crystalIndex], _colorNum, -1); + _newStickFrameIndex = _colorNum; + } + setSubVar(VA_CURR_CRYSTAL_COLORS, _crystalIndex, _colorNum); + } +} + +SsScene2804CrystalButton::SsScene2804CrystalButton(NeverhoodEngine *vm, Scene2804 *parentScene, AsScene2804Crystal *asCrystal, uint crystalIndex) + : StaticSprite(vm, 900), _countdown(0), _parentScene(parentScene), _asCrystal(asCrystal), _crystalIndex(crystalIndex) { + + static const uint32 kSsScene2804CrystalButtonFileHashes1[] = { + 0x911101B0, + 0x22226001, + 0x4444A362, + 0x888925A4, + 0x11122829 + }; + + static const uint32 kSsScene2804CrystalButtonFileHashes2[] = { + 0xB500A1A0, + 0x6A012021, + 0xD4022322, + 0xA8042525, + 0x5008292B + }; + + loadSprite(getGlobalVar(V_SHRINK_LIGHTS_ON) ? kSsScene2804CrystalButtonFileHashes1[crystalIndex] : kSsScene2804CrystalButtonFileHashes2[crystalIndex], + kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset, 400); + setVisible(false); + loadSound(0, 0x44045140); + SetUpdateHandler(&SsScene2804CrystalButton::update); + SetMessageHandler(&SsScene2804CrystalButton::handleMessage); +} + +void SsScene2804CrystalButton::update() { + updatePosition(); + if (_countdown != 0 && (--_countdown) == 0) { + setVisible(false); + } +} + +uint32 SsScene2804CrystalButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_countdown == 0 && !_parentScene->isWorking()) { + playSound(0); + setVisible(true); + _countdown = 4; + _asCrystal->activate(); + } + messageResult = 1; + break; + } + return messageResult; +} + +AsScene2804BeamCoil::AsScene2804BeamCoil(NeverhoodEngine *vm, Scene *parentScene, SsScene2804BeamCoilBody *ssBeamCoilBody) + : AnimatedSprite(vm, 1400), _parentScene(parentScene), _ssBeamCoilBody(ssBeamCoilBody), _countdown(0) { + + createSurface1(0x00494891, 1000); + _x = 125; + _y = 184; + setVisible(false); + _needRefresh = true; + AnimatedSprite::updatePosition(); + loadSound(0, 0x6352F051); + _vm->_soundMan->addSound(0xC5EA0B28, 0xEF56B094); + SetUpdateHandler(&AsScene2804BeamCoil::update); + SetMessageHandler(&AsScene2804BeamCoil::handleMessage); +} + +AsScene2804BeamCoil::~AsScene2804BeamCoil() { + _vm->_soundMan->deleteSoundGroup(0xC5EA0B28); +} + +void AsScene2804BeamCoil::update() { + updateAnim(); + updatePosition(); + if (_countdown != 0 && (--_countdown) == 0) { + sendMessage(_parentScene, 0x2001, 0); + } +} + +uint32 AsScene2804BeamCoil::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2002: + show(); + _countdown = 92; + messageResult = 1; + break; + case 0x2003: + hide(); + messageResult = 1; + break; + } + return messageResult; +} + +void AsScene2804BeamCoil::show() { + _ssBeamCoilBody->setVisible(true); + setVisible(true); + startAnimation(0x00494891, 0, -1); + playSound(0); + SetMessageHandler(&AsScene2804BeamCoil::hmBeaming); + NextState(&AsScene2804BeamCoil::stBeaming); +} + +void AsScene2804BeamCoil::hide() { + stopAnimation(); + SetMessageHandler(&AsScene2804BeamCoil::handleMessage); + setVisible(false); + _ssBeamCoilBody->setVisible(false); + _vm->_soundMan->stopSound(0xEF56B094); +} + +void AsScene2804BeamCoil::stBeaming() { + startAnimation(0x00494891, 93, -1); + NextState(&AsScene2804BeamCoil::stBeaming); + _vm->_soundMan->playSoundLooping(0xEF56B094); +} + +uint32 AsScene2804BeamCoil::hmBeaming(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +AsScene2804BeamTarget::AsScene2804BeamTarget(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1400) { + + createSurface1(0x03842000, 1000); + _x = 475; + _y = 278; + setVisible(false); + _needRefresh = true; + updatePosition(); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2804BeamTarget::handleMessage); +} + +uint32 AsScene2804BeamTarget::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2004: + setVisible(true); + startAnimation(0x03842000, 0, -1); + messageResult = 1; + break; + case 0x2005: + setVisible(false); + stopAnimation(); + messageResult = 1; + break; + } + return messageResult; +} + +Scene2804::Scene2804(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _countdown1(0), _countdown2(0), _countdown3(0), + _beamStatus(0), _isSolved(false), _isWorking(false) { + + _vm->gameModule()->initCrystalColorsPuzzle(); + + SetMessageHandler(&Scene2804::handleMessage); + SetUpdateHandler(&Scene2804::update); + + if (getGlobalVar(V_SHRINK_LIGHTS_ON)) { + setBackground(0xA1D03005); + setPalette(0xA1D03005); + addEntity(_palette); + insertPuzzleMouse(0x03001A15, 20, 620); + _asCoil = insertSprite<SsScene2804LightCoil>(); + _asTarget = insertSprite<SsScene2804LightTarget>(); + } else { + SsScene2804BeamCoilBody *ssBeamCoilBody; + setBackground(0x01C01414); + setPalette(0x01C01414); + addEntity(_palette); + insertPuzzleMouse(0x01410014, 20, 620); + ssBeamCoilBody = insertSprite<SsScene2804BeamCoilBody>(); + _asCoil = insertSprite<AsScene2804BeamCoil>(this, ssBeamCoilBody); + _asTarget = insertSprite<AsScene2804BeamTarget>(); + _ssFlash = insertSprite<SsScene2804Flash>(); + } + + _ssRedButton = insertSprite<SsScene2804RedButton>(this); + addCollisionSprite(_ssRedButton); + + for (uint crystalIndex = 0; crystalIndex < 5; crystalIndex++) { + AsScene2804CrystalWaves *asCrystalWaves = NULL; + if (crystalIndex < 4 && getGlobalVar(V_SHRINK_LIGHTS_ON) == 0) + asCrystalWaves = insertSprite<AsScene2804CrystalWaves>(crystalIndex); + _asCrystals[crystalIndex] = insertSprite<AsScene2804Crystal>(asCrystalWaves, crystalIndex); + _ssCrystalButtons[crystalIndex] = insertSprite<SsScene2804CrystalButton>(this, _asCrystals[crystalIndex], crystalIndex); + addCollisionSprite(_ssCrystalButtons[crystalIndex]); + } + +} + +uint32 Scene2804::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) { + leaveScene(0); + } + break; + case 0x2000: + _isWorking = true; + sendMessage(_asCoil, 0x2002, 0); + if (getGlobalVar(V_SHRINK_LIGHTS_ON)) { + sendMessage(_asTarget, 0x2004, 0); + _countdown2 = 48; + } + break; + case 0x2001: + _countdown3 = 2; + _isSolved = true; + _beamStatus = 0; + for (uint index = 0; index < 5; index++) + if (_asCrystals[index]->getColorNum() != (int16)getSubVar(VA_GOOD_CRYSTAL_COLORS, index)) + _isSolved = false; + _countdown2 = 48; + break; + } + return 0; +} + +void Scene2804::update() { + + Scene::update(); + + if (_countdown1 != 0 && (--_countdown1) == 0) { + leaveScene(0); + } + + if (_countdown2 != 0 && (--_countdown2) == 0) { + _isWorking = false; + sendMessage(_asCoil, 0x2003, 0); + sendMessage(_asTarget, 0x2005, 0); + for (uint index = 0; index < 5; index++) + _asCrystals[index]->hide(); + } + + if (_countdown3 != 0 && (--_countdown3) == 0) { + if (_beamStatus == 5) { + sendMessage(_asTarget, 0x2004, 0); + if (_isSolved) { + _palette->fillBaseWhite(0, 256); + _palette->startFadeToPalette(18); + setGlobalVar(V_KLAYMEN_SMALL, 1); + _countdown1 = 48; + } + } else if (_beamStatus == 6) { + if (_isSolved) + _ssFlash->show(); + } else { + _asCrystals[_beamStatus]->show(); + } + _beamStatus++; + if (_beamStatus < 6) + _countdown3 = 2; + else if (_beamStatus < 7) + _countdown3 = 4; + } + +} + +Scene2805::Scene2805(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + SetMessageHandler(&Scene2805::handleMessage); + + setBackground(0x08021E04); + setPalette(0x08021E04); + _palette->addPalette(0x8A6B1F91, 0, 65, 0); + insertScreenMouse(0x21E00088); + + _sprite1 = insertStaticSprite(0x008261E7, 1100); + _sprite2 = insertStaticSprite(0x020CE421, 1100); + + if (which < 0) { + insertKlaymen<KmScene2805>(380, 338); + setMessageList(0x004AE1C8); + sendMessage(this, 0x2000, 0); + } else if (which == 1) { + insertKlaymen<KmScene2805>(493, 338); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004AE1D0, false); + sendMessage(this, 0x2000, 1); + } else if (which == 2) { + insertKlaymen<KmScene2805>(493, 338); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004AE288, false); + sendMessage(this, 0x2000, 1); + } else if (which == 3) { + insertKlaymen<KmScene2805>(493, 338); + sendMessage(_klaymen, 0x2000, 1); + setMessageList(0x004AE1E0, false); + sendMessage(this, 0x2000, 1); + } else { + insertKlaymen<KmScene2805>(340, 338); + setMessageList(0x004AE1C0); + sendMessage(this, 0x2000, 0); + } + + _klaymen->setClipRect(_sprite1->getDrawRect().x, 0, _sprite2->getDrawRect().x2(), 480); + +} + +uint32 Scene2805::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + if (param.asInteger()) { + setRectList(0x004AE318); + _klaymen->setKlaymenIdleTable3(); + } else { + setRectList(0x004AE308); + _klaymen->setKlaymenIdleTable1(); + } + break; + } + return 0; +} + +AsScene2806Spew::AsScene2806Spew(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1200) { + + createSurface1(0x04211490, 1200); + _x = 378; + _y = 423; + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2806Spew::handleMessage); + setDoDeltaX(1); + setVisible(false); +} + +uint32 AsScene2806Spew::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + playSound(0, 0x48640244); + startAnimation(0x04211490, 0, -1); + setVisible(true); + break; + case 0x3002: + stopAnimation(); + setVisible(false); + break; + } + return messageResult; +} + +Scene2806::Scene2806(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + Sprite *tempSprite; + + which = 3; + + SetMessageHandler(&Scene2806::handleMessage); + SetUpdateHandler(&Scene2806::update); + + loadDataResource(0x98182003); + loadHitRectList(); + + _pointList = _dataResource.getPointArray(0x3606A422); + + insertScreenMouse(0x22114C13); + setBackground(0xC1B22110); + setPalette(0xC1B22110); + + _sprite1 = insertStaticSprite(0xA21F82CB, 1100); + _clipRects[0].x1 = _sprite1->getDrawRect().x; + _clipRects[0].y1 = _sprite1->getDrawRect().y; + _clipRects[0].x2 = _sprite1->getDrawRect().x2(); + _clipRects[0].y2 = _sprite1->getDrawRect().y2(); + + _sprite2 = insertStaticSprite(0x92035301, 1100); + _clipRects[1].y2 = _sprite2->getDrawRect().y2(); + + _sprite3 = insertStaticSprite(0x3182220E, 1100); + + _sprite4 = insertStaticSprite(0x72090342, 1100); + _clipRects[1].x1 = _sprite4->getDrawRect().x; + _clipRects[1].y1 = _sprite4->getDrawRect().y; + + tempSprite = insertStaticSprite(0xD2012C02, 1100); + _clipRects[2].x1 = tempSprite->getDrawRect().x; + _clipRects[2].y2 = tempSprite->getDrawRect().y2(); + _clipRects[3].y1 = tempSprite->getDrawRect().y2(); + _clipRects[1].x2 = tempSprite->getDrawRect().x; + + tempSprite = insertStaticSprite(0x72875F42, 1100); + _clipRects[3].x1 = tempSprite->getDrawRect().x; + + insertStaticSprite(0x0201410A, 1100); + insertStaticSprite(0x72875F42, 1100); + + _asSpew = insertSprite<AsScene2806Spew>(); + + _clipRects[2].y1 = 0; + _clipRects[3].y2 = 480; + _clipRects[2].x2 = 640; + _clipRects[3].x2 = 640; + + if (which < 0) { + insertKlaymen<KmScene2806>(441, 423, false, _clipRects, 4); + setMessageList(0x004AF098); + } else if (which == 1) { + insertKlaymen<KmScene2806>(378, 423, false, _clipRects, 4); + setMessageList(0x004AF098); + } else if (which == 2) { + insertKlaymen<KmScene2806>(378, 423, false, _clipRects, 4); + setMessageList(0x004AF0C8, false); + } else if (which == 3) { + insertKlaymen<KmScene2806>(378, 423, true, _clipRects, 4); + setMessageList(0x004AF0A0, false); + setGlobalVar(V_KLAYMEN_SMALL, 0); + } else { + insertKlaymen<KmScene2806>(670, 423, false, _clipRects, 4); + setMessageList(0x004AF090); + } + + _pointIndex = -1; + findClosestPoint(); + +} + +uint32 Scene2806::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x44262B12) { + setMessageList(0x004AF0E0); + } + break; + case 0x2000: + sendMessage(_asSpew, 0x2000, 0); + break; + } + return 0; +} + +void Scene2806::update() { + Scene::update(); + findClosestPoint(); +} + +void Scene2806::findClosestPoint() { + + static const uint32 kScene2806PaletteFileHashes[] = { + 0x48052508, + 0x01139404, + 0x01138C04, + 0x01138004, + 0x01138604, + 0x086B8890 + }; + + int16 x = MIN<int16>(_klaymen->getX(), 639); + int index = 1; + + while (index < (int)_pointList->size() && (*_pointList)[index].x < x) + ++index; + --index; + + if (_pointIndex != index) { + _pointIndex = index; + _palette->addPalette(kScene2806PaletteFileHashes[index], 0, 64, 0); + } + +} + +Scene2807::Scene2807(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + SetMessageHandler(&Scene2807::handleMessage); + + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0) == 1) { + insertStaticSprite(0x103021E2, 300); + } else if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0) == 2) { + insertStaticSprite(0x103022E2, 300); + } else if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0) == 3) { + insertStaticSprite(0x103024E2, 300); + } + + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 1) == 1) { + insertStaticSprite(0x4800A52A, 200); + } else if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 1) == 2) { + insertStaticSprite(0x4800A62A, 200); + } else if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 1) == 3) { + insertStaticSprite(0x4800A02A, 200); + } + + if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 2) == 1) { + insertStaticSprite(0x31203430, 100); + } else if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 2) == 2) { + insertStaticSprite(0x31203400, 100); + } else if (getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 2) == 3) { + insertStaticSprite(0x31203460, 100); + } + + setBackground(0x3E049A95); + setPalette(0x3E049A95); + insertPuzzleMouse(0x49A913E8, 20, 620); + +} + +uint32 Scene2807::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) { + leaveScene(0); + } + break; + } + return 0; +} + +static const uint32 kScene2808FileHashes1[] = { + 0x90B0392, + 0x90B0192 +}; + +static const uint32 kScene2808FileHashes2[] = { + 0xB0396098, + 0xB0196098 +}; + +static const uint32 kClass428FileHashes[] = { + 0x140022CA, + 0x4C30A602, + 0xB1633402, + 0x12982135, + 0x0540B728, + 0x002A81E3, + 0x08982841, + 0x10982841, + 0x20982841, + 0x40982841, + 0x80982841, + 0x40800711 +}; + +static const int kClass428Countdowns1[] = { + 18, 16, 10, 0 +}; + +static const int kClass428Countdowns2[] = { + 9, 9, 8, 8, 5, 5, 0, 0 +}; + +static const uint32 kClass490FileHashes[] = { + 0x08100071, + 0x24084215, + 0x18980A10 +}; + +static const int16 kClass490FrameIndices1[] = { + 0, 8, 15, 19 +}; + +static const int16 kClass490FrameIndices2[] = { + 0, 4, 8, 11, 15, 17, 19, 0 +}; + +SsScene2808Dispenser::SsScene2808Dispenser(NeverhoodEngine *vm, Scene *parentScene, int testTubeSetNum, int testTubeIndex) + : StaticSprite(vm, 900), _parentScene(parentScene), _countdown(0), _testTubeSetNum(testTubeSetNum), + _testTubeIndex(testTubeIndex) { + + loadSprite(kClass428FileHashes[testTubeSetNum * 3 + testTubeIndex], kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset, 1500); + setVisible(false); + SetUpdateHandler(&SsScene2808Dispenser::update); + SetMessageHandler(&SsScene2808Dispenser::handleMessage); +} + +void SsScene2808Dispenser::update() { + updatePosition(); + if (_countdown != 0 && (--_countdown) == 0) { + setVisible(false); + } +} + +uint32 SsScene2808Dispenser::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + sendMessage(_parentScene, 0x2000, _testTubeIndex); + messageResult = 1; + break; + } + return messageResult; +} + +void SsScene2808Dispenser::startCountdown(int index) { + setVisible(true); + updatePosition(); + if (_testTubeSetNum == 0) { + _countdown = kClass428Countdowns1[index]; + } else { + _countdown = kClass428Countdowns2[index]; + } +} + +AsScene2808TestTube::AsScene2808TestTube(NeverhoodEngine *vm, int testTubeSetNum, int testTubeIndex, SsScene2808Dispenser *ssDispenser) + : AnimatedSprite(vm, 1100), _testTubeSetNum(testTubeSetNum), _testTubeIndex(testTubeIndex), _ssDispenser(ssDispenser), _fillLevel(0) { + + if (testTubeSetNum == 0) { + _x = 504; + _y = 278; + } else { + setDoDeltaX(1); + _x = 136; + _y = 278; + } + + createSurface1(kClass490FileHashes[testTubeIndex], 1100); + + if (testTubeSetNum == 0) { + loadSound(0, 0x30809E2D); + loadSound(1, 0x72811E2D); + loadSound(2, 0x78B01625); + } else { + loadSound(3, 0x70A41E0C); + loadSound(4, 0x50205E2D); + loadSound(5, 0xF8621E2D); + loadSound(6, 0xF1A03C2D); + loadSound(7, 0x70A43D2D); + loadSound(8, 0xF0601E2D); + } + + startAnimation(kClass490FileHashes[testTubeIndex], 0, -1); + _newStickFrameIndex = 0; + + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2808TestTube::handleMessage); + + if (_fillLevel == 0) + setVisible(false); + +} + +uint32 AsScene2808TestTube::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + fill(); + messageResult = 1; + break; + } + return messageResult; +} + +void AsScene2808TestTube::fill() { + if ((int)_fillLevel < _testTubeSetNum * 3 + 3) { + if (_testTubeSetNum == 0) { + playSound(_fillLevel); + setVisible(true); + startAnimation(kClass490FileHashes[_testTubeIndex], kClass490FrameIndices1[_fillLevel], kClass490FrameIndices1[_fillLevel + 1]); + _newStickFrameIndex = kClass490FrameIndices1[_fillLevel + 1]; + } else { + playSound(3 + _fillLevel); + setVisible(true); + startAnimation(kClass490FileHashes[_testTubeIndex], kClass490FrameIndices2[_fillLevel], kClass490FrameIndices2[_fillLevel + 1]); + _newStickFrameIndex = kClass490FrameIndices2[_fillLevel + 1]; + } + _ssDispenser->startCountdown(_fillLevel); + _fillLevel++; + } +} + +void AsScene2808TestTube::flush() { + if (_fillLevel != 0) { + if (_testTubeSetNum == 0) { + startAnimation(kClass490FileHashes[_testTubeIndex], kClass490FrameIndices1[_fillLevel], -1); + } else { + startAnimation(kClass490FileHashes[_testTubeIndex], kClass490FrameIndices2[_fillLevel], -1); + } + _newStickFrameIndex = 0; + _playBackwards = true; + setVisible(true); + } +} + +AsScene2808Handle::AsScene2808Handle(NeverhoodEngine *vm, Scene *parentScene, int testTubeSetNum) + : AnimatedSprite(vm, 1300), _parentScene(parentScene), _testTubeSetNum(testTubeSetNum), _isActivated(false) { + + loadSound(0, 0xE18D1F30); + _x = 320; + _y = 240; + if (_testTubeSetNum == 1) + setDoDeltaX(1); + createSurface1(0x040900D0, 1300); + startAnimation(0x040900D0, 0, -1); + _needRefresh = true; + _newStickFrameIndex = 0; + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2808Handle::handleMessage); + AnimatedSprite::updatePosition(); +} + +uint32 AsScene2808Handle::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (!_isActivated) { + sendMessage(_parentScene, 0x2001, 0); + playSound(0); + activate(); + } + messageResult = 1; + break; + } + return messageResult; +} + +uint32 AsScene2808Handle::hmActivating(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene2808Handle::activate() { + startAnimation(0x040900D0, 0, -1); + SetMessageHandler(&AsScene2808Handle::hmActivating); + NextState(&AsScene2808Handle::stActivated); + _isActivated = true; + _newStickFrameIndex = -1; +} + +void AsScene2808Handle::stActivated() { + stopAnimation(); + sendMessage(_parentScene, 0x2002, 0); +} + +AsScene2808Flow::AsScene2808Flow(NeverhoodEngine *vm, Scene *parentScene, int testTubeSetNum) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _testTubeSetNum(testTubeSetNum) { + + if (testTubeSetNum == 0) { + _x = 312; + _y = 444; + } else { + _x = 328; + _y = 444; + } + createSurface1(0xB8414818, 1200); + startAnimation(0xB8414818, 0, -1); + setVisible(false); + _newStickFrameIndex = 0; + _needRefresh = true; + loadSound(0, 0x6389B652); + SetUpdateHandler(&AnimatedSprite::update); + AnimatedSprite::updatePosition(); +} + +uint32 AsScene2808Flow::hmFlowing(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene2808Flow::start() { + startAnimation(0xB8414818, 0, -1); + setVisible(true); + SetMessageHandler(&AsScene2808Flow::hmFlowing); + NextState(&AsScene2808Flow::stKeepFlowing); + playSound(0); +} + +void AsScene2808Flow::stKeepFlowing() { + startAnimation(0xB8414818, 1, -1); + NextState(&AsScene2808Flow::stKeepFlowing); +} + +AsScene2808LightEffect::AsScene2808LightEffect(NeverhoodEngine *vm, int testTubeSetNum) + : AnimatedSprite(vm, 800), _countdown(1) { + + _x = 320; + _y = 240; + if (testTubeSetNum == 1) + setDoDeltaX(1); + createSurface1(0x804C2404, 800); + SetUpdateHandler(&AsScene2808LightEffect::update); + _needRefresh = true; + AnimatedSprite::updatePosition(); +} + +void AsScene2808LightEffect::update() { + if (_countdown != 0 && (--_countdown) == 0) { + int16 frameIndex = _vm->_rnd->getRandomNumber(3 - 1); + startAnimation(0x804C2404, frameIndex, frameIndex); + updateAnim(); + updatePosition(); + _countdown = _vm->_rnd->getRandomNumber(3 - 1) + 1; + } +} + +Scene2808::Scene2808(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _countdown(0), _testTubeSetNum(which), _leaveResult(0), _isFlowing(false) { + + Sprite *asHandle; + + if (which == 0) + _vm->gameModule()->initTestTubes1Puzzle(); + else + _vm->gameModule()->initTestTubes2Puzzle(); + + SetMessageHandler(&Scene2808::handleMessage); + SetUpdateHandler(&Scene2808::update); + + setBackground(kScene2808FileHashes1[which]); + setPalette(kScene2808FileHashes1[which]); + + asHandle = insertSprite<AsScene2808Handle>(this, which); + addCollisionSprite(asHandle); + + _asFlow = insertSprite<AsScene2808Flow>(this, which); + insertSprite<AsScene2808LightEffect>(which); + + for (int testTubeIndex = 0; testTubeIndex < 3; testTubeIndex++) { + SsScene2808Dispenser *ssDispenser = insertSprite<SsScene2808Dispenser>(this, which, testTubeIndex); + addCollisionSprite(ssDispenser); + _asTestTubes[testTubeIndex] = insertSprite<AsScene2808TestTube>(which, testTubeIndex, ssDispenser); + addCollisionSprite(_asTestTubes[testTubeIndex]); + } + + insertScreenMouse(kScene2808FileHashes2[which]); + +} + +uint32 Scene2808::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if ((param.asPoint().x <= 20 || param.asPoint().x >= 620) && !isAnyTestTubeFilled()) { + leaveScene(1); + } + break; + case 0x2000: + if (!_isFlowing) + _asTestTubes[param.asInteger()]->fill(); + break; + case 0x2001: + _isFlowing = true; + break; + case 0x2002: + if (isAnyTestTubeFilled()) { + _leaveResult = 3; + if (!isMixtureGood()) + _leaveResult = 2; + _asFlow->start(); + for (int i = 0; i < 3; i++) + _asTestTubes[i]->flush(); + _mouseCursor->setVisible(false); + _countdown = 16; + } else { + leaveScene(1); + } + break; + } + return 0; +} + +void Scene2808::update() { + + // DEBUG>>> Show correct values + #if 1 + debug("---------------"); + if (_testTubeSetNum == 0) + debug("%03d %03d %03d", getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0), getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 1), getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 2)); + else + debug("%03d %03d %03d", getSubVar(VA_GOOD_TEST_TUBES_LEVEL_2, 0), getSubVar(VA_GOOD_TEST_TUBES_LEVEL_2, 1), getSubVar(VA_GOOD_TEST_TUBES_LEVEL_2, 2)); + debug("%03d %03d %03d", _asTestTubes[0]->getFillLevel(), _asTestTubes[1]->getFillLevel(), _asTestTubes[2]->getFillLevel()); + #endif + // DEBUG<<< + + Scene::update(); + if (_countdown != 0 && (--_countdown) == 0) { + leaveScene(_leaveResult); + } +} + +bool Scene2808::isMixtureGood() { + if (_testTubeSetNum == 0) { + return + _asTestTubes[0]->getFillLevel() == getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 0) && + _asTestTubes[1]->getFillLevel() == getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 1) && + _asTestTubes[2]->getFillLevel() == getSubVar(VA_GOOD_TEST_TUBES_LEVEL_1, 2); + } else { + return + _asTestTubes[0]->getFillLevel() == getSubVar(VA_GOOD_TEST_TUBES_LEVEL_2, 0) && + _asTestTubes[1]->getFillLevel() == getSubVar(VA_GOOD_TEST_TUBES_LEVEL_2, 1) && + _asTestTubes[2]->getFillLevel() == getSubVar(VA_GOOD_TEST_TUBES_LEVEL_2, 2); + } +} + +bool Scene2808::isAnyTestTubeFilled() { + return + _asTestTubes[0]->getFillLevel() > 0 || + _asTestTubes[1]->getFillLevel() > 0 || + _asTestTubes[2]->getFillLevel() > 0; +} + +AsScene2809Spew::AsScene2809Spew(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1200) { + + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2809Spew::handleMessage); + createSurface1(0x04211490, 1200); + _x = 262; + _y = 423; + setDoDeltaX(0); + setVisible(false); +} + +uint32 AsScene2809Spew::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + playSound(0, 0x48640244); + startAnimation(0x04211490, 0, -1); + setVisible(true); + break; + case 0x3002: + stopAnimation(); + setVisible(false); + break; + } + return messageResult; +} + +Scene2809::Scene2809(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + Sprite *tempSprite; + + SetMessageHandler(&Scene2809::handleMessage); + SetUpdateHandler(&Scene2809::update); + + loadDataResource(0x1830009A); + loadHitRectList(); + + _pointList = _dataResource.getPointArray(0x064A310E); + + setBackground(0xB22116C5); + setPalette(0xB22116C5); + insertScreenMouse(0x116C1B2A); + + _sprite1 = insertStaticSprite(0x1FA2EB82, 1100); + + _clipRects[0].x1 = _sprite1->getDrawRect().x; + _clipRects[0].y1 = _sprite1->getDrawRect().y; + _clipRects[0].x2 = _sprite1->getDrawRect().x2(); + _clipRects[0].y2 = _sprite1->getDrawRect().y2(); + + _sprite2 = insertStaticSprite(0x037321B2, 1100); + _clipRects[1].y2 = _sprite2->getDrawRect().y2(); + + _sprite3 = insertStaticSprite(0x82022E11, 1100); + + _sprite4 = insertStaticSprite(0x09236252, 1100); + _clipRects[1].x2 = _sprite4->getDrawRect().x2(); + _clipRects[1].y1 = _sprite4->getDrawRect().y; + + tempSprite = insertStaticSprite(0x010C22F2, 1100); + _clipRects[2].x2 = tempSprite->getDrawRect().x2(); + _clipRects[2].y2 = tempSprite->getDrawRect().y2(); + _clipRects[3].y1 = tempSprite->getDrawRect().y2(); + _clipRects[1].x1 = tempSprite->getDrawRect().x2(); + + tempSprite = insertStaticSprite(0x877F6252, 1100); + _clipRects[3].x2 = tempSprite->getDrawRect().x2(); + + insertStaticSprite(0x01612A22, 1100); + insertStaticSprite(0x877F6252, 1100); + + _asSpew = insertSprite<AsScene2809Spew>(); + _clipRects[2].y1 = 0; + _clipRects[3].y2 = 480; + _clipRects[2].x1 = 0; + _clipRects[3].x1 = 0; + + if (which < 0) { + insertKlaymen<KmScene2809>(226, 423, false, _clipRects, 4); + setMessageList(0x004B5B90); + } else if (which == 1) { + insertKlaymen<KmScene2809>(262, 423, false, _clipRects, 4); + setMessageList(0x004B5B90); + } else if (which == 2) { + insertKlaymen<KmScene2809>(262, 423, false, _clipRects, 4); + setMessageList(0x004B5BD0); + } else if (which == 3) { + insertKlaymen<KmScene2809>(262, 423, true, _clipRects, 4); + setMessageList(0x004B5BA8, false); + setGlobalVar(V_KLAYMEN_SMALL, 0); + } else { + insertKlaymen<KmScene2809>(-30, 423, false, _clipRects, 4); + setMessageList(0x004B5B88); + } + + _pointIndex = -1; + findClosestPoint(); + +} + +void Scene2809::update() { + Scene::update(); + findClosestPoint(); +} + +uint32 Scene2809::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x160DA937) { + setMessageList(0x004B5B98); + } + break; + case 0x2000: + sendMessage(_asSpew, 0x2000, 0); + break; + } + return 0; +} + +void Scene2809::findClosestPoint() { + + static const uint32 kScene2809PaletteFileHashes[] = { + 0x04260848, + 0x12970401, + 0x128F0401, + 0x12830401, + 0x12850401, + 0x6A8B9008 + }; + + int16 x = MAX<int16>(_klaymen->getX(), 2); + int index = 1; + + while (index < (int)_pointList->size() && (*_pointList)[index].x >= x) + ++index; + --index; + + if (_pointIndex != index) { + _pointIndex = index; + _palette->addPalette(kScene2809PaletteFileHashes[index], 0, 64, 0); + } + +} + +AsScene2810Rope::AsScene2810Rope(NeverhoodEngine *vm, Scene *parentScene, int16 x) + : AnimatedSprite(vm, 1100) { + + createSurface(990, 68, 476); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2810Rope::handleMessage); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + _x = x; + _y = -276; + startAnimation(0x9D098C23, 35, 53); +} + +uint32 AsScene2810Rope::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + startAnimation(0x9D098C23, 35, 53); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +Scene2810::Scene2810(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule) { + + Sprite *tempSprite; + + SetMessageHandler(&Scene2810::handleMessage); + + setBackground(0x26508804); + setPalette(0x26508804); + insertScreenMouse(0x0880026D); + + _sprite6 = insertStaticSprite(0x03615227, 1100); + _sprite5 = insertStaticSprite(0xE059A224, 1100); + + _clipRects[0].x1 = 0; + _clipRects[0].y1 = 0; + _clipRects[0].x2 = 640; + _clipRects[0].y2 = 400; + _clipRects[1].x1 = _sprite5->getDrawRect().x; + _clipRects[1].y1 = 400; + _clipRects[1].x2 = _sprite6->getDrawRect().x2(); + _clipRects[1].y2 = 480; + + if (getGlobalVar(V_KLAYMEN_SMALL)) { + _asTape = insertSprite<AsScene1201Tape>(this, 0, 900, 245, 429, 0x9148A011); + addCollisionSprite(_asTape); + } else { + _asTape = insertSprite<AsScene1201Tape>(this, 0, 1100, 245, 429, 0x9148A011); + addCollisionSprite(_asTape); + } + + _sprite1 = insertStaticSprite(0x430001C4, 1200); + + if (getGlobalVar(V_LADDER_DOWN)) { + setGlobalVar(V_BEEN_STATUE_ROOM, 1); + if (getGlobalVar(V_KLAYMEN_SMALL)) { + _sprite4 = insertStaticSprite(0x82653808, 100); + } else { + _sprite4 = insertStaticSprite(0x82653808, 1100); + } + _sprite4->setClipRect(0, _sprite1->getDrawRect().y, 640, 480); + } + + if (which < 0) { + if (getGlobalVar(V_KLAYMEN_SMALL)) { + insertKlaymen<KmScene2810Small>(240, 448); + _klaymen->setClipRect(_sprite5->getDrawRect().x, 0, 640, 480); + setMessageList(0x004AE438); + setRectList(0x004AE810); + _isRopingDown = false; + removeCollisionSprite(_asTape); + } else { + insertKlaymen<KmScene2810>(300, 424, _clipRects, 2); + setMessageList(0x004AE438); + if (getGlobalVar(V_LADDER_DOWN)) + loadDataResource(0x84130112); + else + loadDataResource(0x84500132); + tempSprite = insertSprite<AsScene1002KlaymenLadderHands>(_klaymen); + tempSprite->setClipRect(0, _sprite1->getDrawRect().y, 640, 480); + _clipRects[0].y1 = _sprite1->getDrawRect().y; + _isRopingDown = false; + } + } else if (which == 1) { + insertKlaymen<KmScene2810>(186, 64, _clipRects, 2); + setMessageList(0x004AE440); + loadDataResource(0x84130112); + tempSprite = insertSprite<AsScene1002KlaymenLadderHands>(_klaymen); + tempSprite->setClipRect(0, _sprite1->getDrawRect().y, 640, 480); + _isRopingDown = true; + _clipRects[0].y1 = _sprite1->getDrawRect().y; + } else if (which == 5) { + insertStaticSprite(0xC3007EA0, 100); + _sprite2 = insertStaticSprite(0x02780936, 1100); + _sprite3 = insertStaticSprite(0x1CA02160, 1100); + _asRope = insertSprite<AsScene2810Rope>(this, 384); + insertKlaymen<KmScene2810>(384, 0, _clipRects, 0); + sendEntityMessage(_klaymen, 0x1014, _asRope); + setMessageList(0x004AE738); + _klaymen->setClipRect(0, _sprite2->getDrawRect().y, 640, _sprite3->getDrawRect().y2()); + _asRope->setClipRect(0, _sprite2->getDrawRect().y, 640, _sprite3->getDrawRect().y2()); + _vm->_soundMan->addSound(0x84400112, 0xC874EE6C); + _vm->_soundMan->playSoundLooping(0xC874EE6C); + _vm->_soundMan->setSoundVolume(0xC874EE6C, 50); + _isRopingDown = false; + } else if ((which >= 11 && which <= 14) || (which >= 19 && which <= 22) || which == 3) { + if (getGlobalVar(V_KLAYMEN_SMALL)) { + insertKlaymen<KmScene2810Small>((int16)getGlobalVar(V_KLAYMEN_SAVED_X), 448); + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) + _klaymen->setDoDeltaX(1); + _klaymen->setClipRect(_sprite5->getDrawRect().x, 0, 640, 480); + setMessageList(0x004AE6D8); + setRectList(0x004AE810); + _isRopingDown = false; + removeCollisionSprite(_asTape); + } else { + insertKlaymenLadder(); + if (getGlobalVar(V_LADDER_DOWN_ACTION)) { + setMessageList(0x004AE6E8); + setGlobalVar(V_LADDER_DOWN_ACTION, 0); + _isRopingDown = false; + } else { + setMessageList(0x004AE6D8); + _isRopingDown = false; + } + } + } else if (which >= 15 && which <= 18) { + insertKlaymenLadder(); + setMessageList(0x004AE6E0); + _isRopingDown = false; + } else if (which == 4) { + if (getGlobalVar(V_KLAYMEN_SMALL)) { + insertKlaymen<KmScene2810Small>(473, 448); + _klaymen->setClipRect(_sprite5->getDrawRect().x, 0, 640, 480); + setMessageList(0x004AE428); + setRectList(0x004AE810); + _isRopingDown = false; + removeCollisionSprite(_asTape); + } else { + insertKlaymen<KmScene2810>(450, 424, _clipRects, 2); + setMessageList(0x004AE418); + if (getGlobalVar(V_LADDER_DOWN)) + loadDataResource(0x84130112); + else + loadDataResource(0x84500132); + tempSprite = insertSprite<AsScene1002KlaymenLadderHands>(_klaymen); + tempSprite->setClipRect(0, _sprite1->getDrawRect().y, 640, 480); + _clipRects[0].y1 = _sprite1->getDrawRect().y; + _isRopingDown = false; + } + } else { + insertKlaymen<KmScene2810Small>(120, 448); + _klaymen->setClipRect(_sprite5->getDrawRect().x, 0, 640, 480); + setMessageList(0x004AE410); + setRectList(0x004AE810); + _isRopingDown = false; + removeCollisionSprite(_asTape); + } + +} + +Scene2810::~Scene2810() { + setGlobalVar(V_KLAYMEN_IS_DELTA_X, _klaymen->isDoDeltaX() ? 1 : 0); + setGlobalVar(V_KLAYMEN_SAVED_X, _klaymen->getX()); + _vm->_soundMan->deleteSoundGroup(0x84400112); +} + +void Scene2810::insertKlaymenLadder() { + Sprite *tempSprite; + + if (getGlobalVar(V_LADDER_DOWN_ACTION)) { + insertKlaymen<KmScene2810>(430, 424, _clipRects, 2); + _klaymen->setDoDeltaX(1); + } else { + insertKlaymen<KmScene2810>((int16)getGlobalVar(V_KLAYMEN_SAVED_X), 424, _clipRects, 2); + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) + _klaymen->setDoDeltaX(1); + } + if (getGlobalVar(V_LADDER_DOWN)) + loadDataResource(0x84130112); + else + loadDataResource(0x84500132); + tempSprite = insertSprite<AsScene1002KlaymenLadderHands>(_klaymen); + tempSprite->setClipRect(0, _sprite1->getDrawRect().y, 640, 480); + _clipRects[0].y1 = _sprite1->getDrawRect().y; +} + +uint32 Scene2810::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0xE574F14C) + setMessageList(0x004AE458); + else if (param.asInteger() == 0x7214A05C || param.asInteger() == 0x2905E574) + setMessageList(0x004AE4A8); + else if (param.asInteger() == 0x7274E24C || param.asInteger() == 0x2D24E572) + setMessageList(0x004AE4D0); + else if (param.asInteger() == 0x4A07A040 || param.asInteger() == 0x190426F5) + setMessageList(0x004AE4F8); + else if (param.asInteger() == 0x6604200C || param.asInteger() == 0x2100E435) + setMessageList(0x004AE520); + else if (param.asInteger() == 0xE216A05C || param.asInteger() == 0x0905EC74) + setMessageList(0x004AE548); + else if (param.asInteger() == 0x721DA05C || param.asInteger() == 0xB905E574) + setMessageList(0x004AE570); + else if (param.asInteger() == 0x6214E09C || param.asInteger() == 0x2D09E474) + setMessageList(0x004AE598); + else if (param.asInteger() == 0x6276A04C || param.asInteger() == 0x0904E472) + setMessageList(0x004AE5C0); + else if (param.asInteger() == 0x6E14A00C || param.asInteger() == 0x2900E4B4) + setMessageList(0x004AE5E8); + else if (param.asInteger() == 0x6014A04D || param.asInteger() == 0x2904F454) + setMessageList(0x004AE610); + else if (param.asInteger() == 0x6215A3C4 || param.asInteger() == 0x393C6474) + setMessageList(0x004AE638); + else if (param.asInteger() == 0x6A54E24D || param.asInteger() == 0x2D24F4F0) + setMessageList(0x004AE660); + else if (param.asInteger() == 0x2064294C || param.asInteger() == 0x2194E053) + setMessageList(0x004AE688); + break; + case 0x2000: + setRectList(0x004AE800); + _isRopingDown = true; + break; + case 0x2001: + if (getGlobalVar(V_LADDER_DOWN)) + loadDataResource(0x84130112); + else + loadDataResource(0x84500132); + _isRopingDown = false; + break; + case 0x4826: + if (sender == _asTape && getGlobalVar(V_KLAYMEN_SMALL) == 0 && !_isRopingDown) { + sendEntityMessage(_klaymen, 0x1014, _asTape); + setMessageList(0x004AE750); + } + break; + } + return messageResult; +} + +AsScene2812Winch::AsScene2812Winch(NeverhoodEngine *vm) + : AnimatedSprite(vm, 1100) { + + createSurface1(0x20DA08A0, 1200); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2812Winch::handleMessage); + setVisible(false); + _x = 280; + _y = 184; +} + +AsScene2812Winch::~AsScene2812Winch() { + _vm->_soundMan->deleteSoundGroup(0x00B000E2); +} + +uint32 AsScene2812Winch::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + startAnimation(0x20DA08A0, 0, -1); + setVisible(true); + _vm->_soundMan->addSound(0x00B000E2, 0xC874EE6C); + _vm->_soundMan->playSoundLooping(0xC874EE6C); + break; + case 0x3002: + startAnimation(0x20DA08A0, 7, -1); + break; + } + return messageResult; +} + +AsScene2812Rope::AsScene2812Rope(NeverhoodEngine *vm, Scene *parentScene) + : AnimatedSprite(vm, 1100), _parentScene(parentScene) { + + createSurface(990, 68, 476); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene2812Rope::handleMessage); + SetSpriteUpdate(&AnimatedSprite::updateDeltaXY); + startAnimation(0xAE080551, 0, -1); + _x = 334; + _y = 201; +} + +uint32 AsScene2812Rope::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x4806: + setDoDeltaX(((Sprite*)sender)->isDoDeltaX() ? 1 : 0); + stRopingDown(); + break; + case 0x482A: + sendMessage(_parentScene, 0x1022, 990); + break; + case 0x482B: + sendMessage(_parentScene, 0x1022, 1010); + break; + } + return messageResult; +} + +uint32 AsScene2812Rope::hmRopingDown(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene2812Rope::stRopingDown() { + sendMessage(_parentScene, 0x4806, 0); + startAnimation(0x9D098C23, 0, -1); + SetMessageHandler(&AsScene2812Rope::hmRopingDown); +} + +AsScene2812TrapDoor::AsScene2812TrapDoor(NeverhoodEngine *vm) + : AnimatedSprite(vm, 0x805D0029, 100, 320, 240) { + + SetMessageHandler(&AsScene2812TrapDoor::handleMessage); + _newStickFrameIndex = 0; +} + +uint32 AsScene2812TrapDoor::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2000: + startAnimation(0x805D0029, 0, -1); + playSound(0, 0xEA005F40); + _newStickFrameIndex = STICK_LAST_FRAME; + break; + } + return messageResult; +} + +Scene2812::Scene2812(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _paletteArea(0) { + + if (getGlobalVar(V_HAS_FINAL_KEY) && getGlobalVar(V_KEY3_LOCATION) == 0) + setGlobalVar(V_KEY3_LOCATION, 3); + + SetMessageHandler(&Scene2812::handleMessage); + SetUpdateHandler(&Scene2812::update); + + setRectList(0x004AF700); + + setBackground(0x03600606); + setPalette(0x03600606); + addEntity(_palette); + _palette->addBasePalette(0x03600606, 0, 256, 0); + + _sprite1 = insertStaticSprite(0x0C06C860, 1100); + insertScreenMouse(0x0060203E); + + if (getGlobalVar(V_KEY3_LOCATION) == 3) { + _asKey = insertSprite<AsCommonKey>(this, 2, 1100, 474, 437); + addCollisionSprite(_asKey); + } + + _ssTape = insertSprite<SsScene1705Tape>(this, 6, 1100, 513, 437, 0xA1361863); + addCollisionSprite(_ssTape); + + _asWinch = insertSprite<AsScene2812Winch>(); + _asTrapDoor = insertSprite<AsScene2812TrapDoor>(); + _asRope = insertSprite<AsScene2812Rope>(this); + + _sprite2 = insertStaticSprite(0x08478078, 1100); + _sprite3 = insertStaticSprite(0x2203B821, 1100); + _sprite4 = insertStaticSprite(0x08592134, 1100); + + if (which < 0) { + _isRopingDown = false; + insertKlaymen<KmScene2812>(272, 432); + setMessageList(0x004AF560); + _sprite1->setVisible(false); + _klaymen->setClipRect(_sprite4->getDrawRect().x, 0, 640, _sprite3->getDrawRect().y2()); + } else if (which == 1) { + _isRopingDown = false; + insertKlaymen<KmScene2812>(338, 398); + setMessageList(0x004AF588); + setPaletteArea1(true); + _klaymen->setClipRect(_sprite1->getDrawRect().x, 0, _sprite1->getDrawRect().x2(), _sprite3->getDrawRect().y2()); + } else if (which == 2) { + _isRopingDown = false; + if (getGlobalVar(V_KLAYMEN_IS_DELTA_X)) { + insertKlaymen<KmScene2812>(554, 432); + _klaymen->setDoDeltaX(1); + } else { + insertKlaymen<KmScene2812>(394, 432); + } + setMessageList(0x004AF5F0); + _sprite1->setVisible(false); + _klaymen->setClipRect(_sprite4->getDrawRect().x, 0, 640, _sprite3->getDrawRect().y2()); + } else { + _isRopingDown = true; + insertKlaymen<KmScene2812>(150, 582); + setMessageList(0x004AF568); + setPaletteArea2(true); + _sprite1->setVisible(false); + _klaymen->setClipRect(_sprite4->getDrawRect().x, 0, 640, _sprite3->getDrawRect().y2()); + } + + _asRope->setClipRect(0, _sprite2->getDrawRect().y, 640, _sprite3->getDrawRect().y2()); + +} + +void Scene2812::update() { + if (_klaymen->getX() < 220) + setPaletteArea2(false); + else if (_klaymen->getX() < 240) + setPaletteArea0(false); + Scene::update(); +} + +uint32 Scene2812::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x100D: + if (param.asInteger() == 0x0004269B) + sendEntityMessage(_klaymen, 0x1014, _asRope); + break; + case 0x2001: + _isRopingDown = true; + setRectList(0x004AF710); + _klaymen->setClipRect(_sprite4->getDrawRect().x, 0, 640, _sprite4->getDrawRect().y2()); + break; + case 0x2002: + _isRopingDown = false; + setRectList(0x004AF700); + _klaymen->setClipRect(_sprite4->getDrawRect().x, 0, 640, _sprite3->getDrawRect().y2()); + break; + case 0x4806: + sendMessage(_asWinch, 0x2000, 0); + sendMessage(_asTrapDoor, 0x2000, 0); + break; + case 0x4826: + if (sender == _ssTape && !_isRopingDown) { + sendEntityMessage(_klaymen, 0x1014, _ssTape); + setMessageList(0x004AF658); + } else if (sender == _asKey && !_isRopingDown) { + sendEntityMessage(_klaymen, 0x1014, _asKey); + setMessageList(0x004AF668); + } + break; + case 0x482A: + setPaletteArea1(false); + _sprite1->setVisible(true); + _klaymen->setClipRect(_sprite1->getDrawRect().x, 0, _sprite1->getDrawRect().x2(), _sprite3->getDrawRect().y2()); + break; + case 0x482B: + setPaletteArea0(false); + _sprite1->setVisible(false); + _klaymen->setClipRect(_sprite4->getDrawRect().x, 0, 640, _sprite3->getDrawRect().y2()); + break; + } + return messageResult; +} + +void Scene2812::setPaletteArea0(bool instantly) { + if (_paletteArea != 0) { + _paletteArea = 0; + updatePaletteArea(instantly); + } +} + +void Scene2812::setPaletteArea1(bool instantly) { + if (_paletteArea != 1) { + _paletteArea = 1; + updatePaletteArea(instantly); + } +} + +void Scene2812::setPaletteArea2(bool instantly) { + if (_paletteArea != 2) { + _paletteArea = 2; + updatePaletteArea(instantly); + } +} + +void Scene2812::updatePaletteArea(bool instantly) { + if (_paletteArea == 0) + _palette->addBasePalette(0x05D30F11, 0, 64, 0); + else if (_paletteArea == 1) + _palette->addBasePalette(0x92CA2C9B, 0, 64, 0); + else if (_paletteArea == 2) + _palette->addBasePalette(0x381F92C5, 0, 64, 0); + _palette->startFadeToPalette(instantly ? 0 : 12); +} + +Scene2822::Scene2822(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _countdown(0), _scrollIndex(0) { + + SetMessageHandler(&Scene2822::handleMessage); + SetUpdateHandler(&Scene2822::update); + _background = new Background(_vm, 0xD542022E, 0, 0); + addBackground(_background); + _background->getSurface()->getDrawRect().y = -10; + setPalette(0xD542022E); + insertPuzzleMouse(0x0028D089, 20, 620); + _ssButton = insertStaticSprite(0x1A4D4120, 1100); + _ssButton->setVisible(false); + loadSound(2, 0x19044E72); +} + +void Scene2822::update() { + + static const int16 kScene2822BackgroundYPositions[] = { + 0, -20, -5, -15, -8, -12, -9, -11, -10, 0 + }; + + Scene::update(); + + if (_countdown != 0) { + if ((--_countdown) == 0) { + if (_countdownStatus == 0) { + _ssButton->setVisible(false); + _countdownStatus = 1; + _countdown = 48; + } else if (_countdownStatus == 1) { + playSound(0, 0x1384CB60); + _countdownStatus = 2; + _countdown = 12; + } else if (_countdownStatus == 2 && getGlobalVar(V_LADDER_DOWN_ACTION)) { + leaveScene(0); + } + } else if (_countdownStatus == 2 && getGlobalVar(V_LADDER_DOWN_ACTION)) { + if (_scrollIndex < 9) { + _background->getSurface()->getDrawRect().y = kScene2822BackgroundYPositions[_scrollIndex]; + _scrollIndex++; + } else { + _background->getSurface()->getDrawRect().y = -10; + } + } + } + +} + +uint32 Scene2822::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) { + leaveScene(0); + } else if (param.asPoint().x >= 257 && param.asPoint().y >= 235 && + param.asPoint().x <= 293 && param.asPoint().y <= 273) { + _ssButton->setVisible(true); + _countdownStatus = 0; + _countdown = 12; + playSound(1, 0x44061000); + if (getGlobalVar(V_LADDER_DOWN) == 0) { + setGlobalVar(V_LADDER_DOWN, 1); + setGlobalVar(V_LADDER_DOWN_ACTION, 1); + SetMessageHandler(NULL); + playSound(2); + _mouseCursor->setVisible(false); + } + } + break; + } + return messageResult; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module2800.h b/engines/neverhood/modules/module2800.h new file mode 100644 index 0000000000..fe62f11307 --- /dev/null +++ b/engines/neverhood/modules/module2800.h @@ -0,0 +1,505 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_MODULES_MODULE2800_H +#define NEVERHOOD_MODULES_MODULE2800_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +// Module2800 + +class Module2800 : public Module { +public: + Module2800(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module2800(); +protected: + int _sceneNum; + uint32 _currentMusicFileHash; + MusicResource *_musicResource; + void createScene(int sceneNum, int which); + void updateScene(); + void updateMusic(bool halfVolume); +}; + +class Scene2801 : public Scene { +public: + Scene2801(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Scene2801(); +protected: + Sprite *_asTape; + uint32 _paletteHash; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2802 : public Scene { +public: + Scene2802(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Scene2802(); +protected: + SmackerPlayer *_smackerPlayer; + uint _currRadioMusicIndex; + int _currTuneStatus; + int _countdown1; + int _countdown2; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void incRadioMusicIndex(int delta); + void changeTuneStatus(int prevTuneStatus, int newTuneStatus); +}; + +class AsScene2803LightCord : public AnimatedSprite { +public: + AsScene2803LightCord(NeverhoodEngine *vm, Scene *parentScene, uint32 fileHash1, uint32 fileHash2, int16 x, int16 y); + void stPulled(); + void stIdle(); + void setFileHashes(uint32 fileHash1, uint32 fileHash2); +protected: + Scene *_parentScene; + uint32 _fileHash1, _fileHash2; + bool _isPulled, _isBusy; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmPulled(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2803TestTubeOne : public AnimatedSprite { +public: + AsScene2803TestTubeOne(NeverhoodEngine *vm, uint32 fileHash1, uint32 fileHash2); +protected: + uint32 _fileHash1, _fileHash2; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2803Rope : public AnimatedSprite { +public: + AsScene2803Rope(NeverhoodEngine *vm, Scene *parentScene, int16 x); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmReleased(int messageNum, const MessageParam ¶m, Entity *sender); + void stReleased(); + void stHide(); +}; + +class Scene2803 : public Scene { +public: + Scene2803(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + StaticSprite *_asTestTubeOne; + StaticSprite *_asTestTubeTwo; + StaticSprite *_asTestTubeThree; + Sprite *_asRope; + AsScene2803LightCord *_asLightCord; + StaticSprite *_sprite3; + StaticSprite *_sprite4; + StaticSprite *_sprite5; + StaticSprite *_sprite6; + StaticSprite *_sprite7; + StaticSprite *_sprite8; + StaticSprite *_sprite9; + Sprite *_sprite10; + NRect _clipRectsFloor[2]; + NRect _clipRectsStairs[3]; + int _paletteArea; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void upKlaymenStairs(); + void klaymenStairs(); + void klaymenFloor(); + void toggleBackground(); + void changeBackground(); + void setPaletteArea0(); + void setPaletteArea1(); + void updatePaletteArea(); +}; + +class Scene2803Small : public Scene { +public: + Scene2803Small(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + int _paletteArea; + Sprite *_sprite1; + Sprite *_sprite2; + Sprite *_sprite3; + Sprite *_sprite4; + Sprite *_sprite5; + Sprite *_sprite6; + Sprite *_sprite7; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void upKlaymenSlope(); + void upKlaymenFloor(); + void klaymenSlope(); + void klaymenFloor(); + void setPaletteArea0(); + void setPaletteArea1(); + void setPaletteArea2(); + void setPaletteArea3(); + void updatePaletteArea(bool instantly); +}; + +class Scene2804; + +class SsScene2804RedButton : public StaticSprite { +public: + SsScene2804RedButton(NeverhoodEngine *vm, Scene2804 *parentScene); +protected: + Scene2804 *_parentScene; + int _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsScene2804LightCoil : public StaticSprite { +public: + SsScene2804LightCoil(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsScene2804BeamCoilBody : public StaticSprite { +public: + SsScene2804BeamCoilBody(NeverhoodEngine *vm); +}; + +class SsScene2804LightTarget : public StaticSprite { +public: + SsScene2804LightTarget(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsScene2804Flash : public StaticSprite { +public: + SsScene2804Flash(NeverhoodEngine *vm); + void show(); +}; + +class AsScene2804CrystalWaves : public AnimatedSprite { +public: + AsScene2804CrystalWaves(NeverhoodEngine *vm, uint crystalIndex); + void show(); + void hide(); +protected: + uint _crystalIndex; +}; + +class AsScene2804Crystal : public AnimatedSprite { +public: + AsScene2804Crystal(NeverhoodEngine *vm, AsScene2804CrystalWaves *asCrystalWaves, uint crystalIndex); + void show(); + void hide(); + void activate(); + int16 getColorNum() const { return _colorNum; } +protected: + AsScene2804CrystalWaves *_asCrystalWaves; + uint _crystalIndex; + int16 _colorNum; + bool _isLightOn; + bool _isShowing; +}; + +class SsScene2804CrystalButton : public StaticSprite { +public: + SsScene2804CrystalButton(NeverhoodEngine *vm, Scene2804 *parentScene, AsScene2804Crystal *asCrystal, uint crystalIndex); +protected: + Scene2804 *_parentScene; + AsScene2804Crystal *_asCrystal; + uint _crystalIndex; + int _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2804BeamCoil : public AnimatedSprite { +public: + AsScene2804BeamCoil(NeverhoodEngine *vm, Scene *parentScene, SsScene2804BeamCoilBody *ssBeamCoilBody); + virtual ~AsScene2804BeamCoil(); +protected: + Scene *_parentScene; + SsScene2804BeamCoilBody *_ssBeamCoilBody; + int _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void show(); + void hide(); + void stBeaming(); + uint32 hmBeaming(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2804BeamTarget : public AnimatedSprite { +public: + AsScene2804BeamTarget(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2804 : public Scene { +public: + Scene2804(NeverhoodEngine *vm, Module *parentModule, int which); + bool isWorking() const { return _isWorking; } +protected: + int _countdown1; + int _countdown2; + int _countdown3; + int _beamStatus; + bool _isSolved; + bool _isWorking; + Sprite *_ssRedButton; + Sprite *_asCoil; + Sprite *_asTarget; + SsScene2804Flash *_ssFlash; + AsScene2804Crystal *_asCrystals[5]; + Sprite *_ssCrystalButtons[5]; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2805 : public Scene { +public: + Scene2805(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_sprite1; + Sprite *_sprite2; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2806Spew : public AnimatedSprite { +public: + AsScene2806Spew(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2806 : public Scene { +public: + Scene2806(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + NPointArray *_pointList; + int _pointIndex; + NRect _clipRects[4]; + Sprite *_sprite1; + Sprite *_sprite2; + Sprite *_sprite3; + Sprite *_sprite4; + Sprite *_asSpew; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void update(); + void findClosestPoint(); +}; + +class Scene2807 : public Scene { +public: + Scene2807(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsScene2808Dispenser : public StaticSprite { +public: + SsScene2808Dispenser(NeverhoodEngine *vm, Scene *parentScene, int testTubeSetNum, int testTubeIndex); + void startCountdown(int index); +protected: + Scene *_parentScene; + int _countdown; + int _testTubeSetNum, _testTubeIndex; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2808TestTube : public AnimatedSprite { +public: + AsScene2808TestTube(NeverhoodEngine *vm, int testTubeSetNum, int testTubeIndex, SsScene2808Dispenser *ssDispenser); + void fill(); + void flush(); + uint32 getFillLevel() const { return _fillLevel; } +protected: + SsScene2808Dispenser *_ssDispenser; + int _testTubeSetNum; + uint32 _fillLevel; + int _testTubeIndex; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2808Handle : public AnimatedSprite { +public: + AsScene2808Handle(NeverhoodEngine *vm, Scene *parentScene, int testTubeSetNum); + void activate(); + void stActivated(); +protected: + Scene *_parentScene; + int _testTubeSetNum; + bool _isActivated; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmActivating(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2808Flow : public AnimatedSprite { +public: + AsScene2808Flow(NeverhoodEngine *vm, Scene *parentScene, int testTubeSetNum); + void start(); + void stKeepFlowing(); +protected: + Scene *_parentScene; + int _testTubeSetNum; + uint32 hmFlowing(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2808LightEffect : public AnimatedSprite { +public: + AsScene2808LightEffect(NeverhoodEngine *vm, int which); +protected: + int _countdown; + void update(); +}; + +class Scene2808 : public Scene { +public: + Scene2808(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + int _countdown; + int _testTubeSetNum; + AsScene2808Flow *_asFlow; + int _leaveResult; + bool _isFlowing; + AsScene2808TestTube *_asTestTubes[3]; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void update(); + bool isMixtureGood(); + bool isAnyTestTubeFilled(); +}; + +class AsScene2809Spew : public AnimatedSprite { +public: + AsScene2809Spew(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2809 : public Scene { +public: + Scene2809(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + NPointArray *_pointList; + int _pointIndex; + NRect _clipRects[4]; + Sprite *_sprite1; + Sprite *_sprite2; + Sprite *_sprite3; + Sprite *_sprite4; + Sprite *_asSpew; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void findClosestPoint(); +}; + +class AsScene2810Rope : public AnimatedSprite { +public: + AsScene2810Rope(NeverhoodEngine *vm, Scene *parentScene, int16 x); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2810 : public Scene { +public: + Scene2810(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Scene2810(); +protected: + Sprite *_sprite1; + Sprite *_sprite2; + Sprite *_sprite3; + Sprite *_asRope; + Sprite *_sprite4; + Sprite *_asTape; + Sprite *_sprite5; + Sprite *_sprite6; + bool _isRopingDown; + NRect _clipRects[2]; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void insertKlaymenLadder(); +}; + +class AsScene2812Winch : public AnimatedSprite { +public: + AsScene2812Winch(NeverhoodEngine *vm); + virtual ~AsScene2812Winch(); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene2812Rope : public AnimatedSprite { +public: + AsScene2812Rope(NeverhoodEngine *vm, Scene *parentScene); +protected: + Scene *_parentScene; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + uint32 hmRopingDown(int messageNum, const MessageParam ¶m, Entity *sender); + void stRopingDown(); +}; + +class AsScene2812TrapDoor : public AnimatedSprite { +public: + AsScene2812TrapDoor(NeverhoodEngine *vm); +protected: + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2812 : public Scene { +public: + Scene2812(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_asWinch; + Sprite *_asTrapDoor; + Sprite *_asRope; + Sprite *_sprite3; + Sprite *_sprite2; + Sprite *_sprite4; + Sprite *_ssTape; + Sprite *_asKey; + Sprite *_sprite1; + bool _isRopingDown; + int _paletteArea; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void setPaletteArea0(bool instantly); + void setPaletteArea1(bool instantly); + void setPaletteArea2(bool instantly); + void updatePaletteArea(bool instantly); +}; + +class Scene2822 : public Scene { +public: + Scene2822(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_ssButton; + int _scrollIndex; + int _countdown; + int _countdownStatus; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE2800_H */ diff --git a/engines/neverhood/modules/module2900.cpp b/engines/neverhood/modules/module2900.cpp new file mode 100644 index 0000000000..bd95b82f4c --- /dev/null +++ b/engines/neverhood/modules/module2900.cpp @@ -0,0 +1,439 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module2900.h" +#include "neverhood/gamemodule.h" +#include "neverhood/modules/module1100.h" +#include "neverhood/modules/module1300.h" +#include "neverhood/modules/module1700.h" +#include "neverhood/modules/module2000.h" +#include "neverhood/modules/module2100.h" +#include "neverhood/modules/module2800.h" + +namespace Neverhood { + +Module2900::Module2900(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule) { + + if (which >= 0) + setGlobalVar(V_TELEPORTER_WHICH, which); + + createScene(0, 0); + +} + +void Module2900::createScene(int sceneNum, int which) { + debug("Module2900::createScene(%d, %d)", sceneNum, which); + _sceneNum = sceneNum; + switch (_sceneNum) { + case 0: + _vm->gameState().sceneNum = 0; + _childObject = new Scene2901(_vm, this, getGlobalVar(V_TELEPORTER_WHICH)); + break; + case 1: + _vm->gameState().sceneNum = 0; + _childObject = new Scene2805(_vm, this, which); + break; + case 2: + _vm->gameState().sceneNum = 0; + _childObject = new Scene2101(_vm, this, which); + break; + case 3: + _vm->gameState().sceneNum = 0; + _childObject = new Scene1306(_vm, this, which); + break; + case 4: + _vm->gameState().sceneNum = 0; + _childObject = new Scene1705(_vm, this, which); + break; + case 5: + _vm->gameState().sceneNum = 0; + _childObject = new Scene1109(_vm, this, which); + break; + case 6: + _vm->gameState().sceneNum = 0; + _childObject = new Scene2001(_vm, this, which); + break; + } + SetUpdateHandler(&Module2900::updateScene); + _childObject->handleUpdate(); +} + +void Module2900::updateScene() { + if (!updateChild()) { + switch (_sceneNum) { + case 0: + if (_moduleResult == (uint32)-1) { + leaveModule((uint32)-1); + } else { + _teleporterModuleResult = _moduleResult; + switch (getGlobalVar(V_TELEPORTER_WHICH)) { + case 0: + createScene(3, 4); + break; + case 1: + createScene(2, 2); + break; + case 2: + createScene(5, 2); + break; + case 3: + createScene(4, 2); + break; + case 4: + createScene(6, 2); + break; + case 5: + createScene(1, 2); + break; + default: + leaveModule(_moduleResult); + break; + } + } + break; + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + leaveModule(_teleporterModuleResult); + break; + } + } +} + +static const uint32 kScene2901FileHashes1[] = { + 0x023023B4, + 0x36204507, + 0x046CF08E, + 0x9313A237, + 0xA651F246, + 0x02108034 +}; + +static const uint32 kScene2901FileHashes2[] = { + 0x023B002B, + 0x0450336A, + 0xCF08A04E, + 0x3A233939, + 0x1F242A6D, + 0x08030029 +}; + +static const uint32 kSsScene2901LocationButtonFileHashes[] = { + 0x2311326A, + 0x212323AC, + 0x10098138, + 0x25213167, + 0x1119A363, + 0x94452612, + 0x39464212, + 0x01860450, + 0x53002104, + 0x58E68412, + 0x18600300, + 0xB650A890, + 0x2452A7C4, + 0xA0232748, + 0x08862B02, + 0x2491E648, + 0x0010EB46, + 0x214C8A11, + 0x16A31921, + 0x0AC33A00, + 0x238028AA, + 0x26737A21, + 0x063039A8, + 0x51286C60, + 0x464006B4, + 0x42242538, + 0x20716010, + 0x4A2000AE, + 0x225124A6, + 0x28E82E45, + 0x58652C04, + 0xC82210A4, + 0x62A84060, + 0xC0693CB4, + 0x22212C64, + 0x5034EA71 +}; + +static const NPoint kSsScene2901LocationButtonPoints[] = { + {525, 120}, {576, 149}, {587, 205}, + {538, 232}, {484, 205}, {479, 153} +}; + +static const uint32 kSsScene2901LocationButtonLightFileHashes1[] = { + 0x03136246, + 0x2106216E, + 0x4025A13A, + 0x21816927, + 0x110B2202, + 0xCC0522B2, + 0x3CC24258, + 0x59C600F0, + 0x534A2480, + 0x50E61019, + 0x34400150, + 0x225BA090, + 0xB059AFC4, + 0xE093A741, + 0x0086BF09, + 0x3281E760, + 0xA048AB42, + 0x20649C01, + 0x14611904, + 0x26E33850, + 0x23A52A68, + 0xA2733024, + 0x10203880, + 0x1B2DE860, + 0x0644A6EC, + 0x426E20BC, + 0x80292014, + 0x4360B02E, + 0x22742664, + 0x98682705, + 0x0925B82C, + 0x5C2918A4, + 0xD2284920, + 0x41083CA6, + 0x6824A864, + 0x50266B10 +}; + +static const uint32 kSsScene2901LocationButtonLightFileHashes2[] = { + 0x43C46D4C, + 0x43C4AD4C, + 0x43C52D4C, + 0x43C62D4C, + 0x43C02D4C, + 0x43CC2D4C +}; + +static const uint32 kSsScene2901BrokenButtonFileHashes[] = { + 0x3081BD3A, + 0xD3443003, + 0x0786A320, + 0xE3A22029, + 0x61611814, + 0x425848E2 +}; + +static const uint32 kSsScene2901BigButtonFileHashes[] = { + 0x010D7748, + 0x9D02019A, + 0x351A2F43, + 0x448138E5, + 0x02788CF0, + 0x71718024 +}; + +SsScene2901LocationButton::SsScene2901LocationButton(NeverhoodEngine *vm, Scene *parentScene, int which, uint index) + : StaticSprite(vm, 900), _parentScene(parentScene), _index(index), _countdown1(0) { + + const NPoint &pt = kSsScene2901LocationButtonPoints[_index]; + + loadSprite(kSsScene2901LocationButtonFileHashes[which * 6 + index], kSLFDefDrawOffset | kSLFDefPosition, 800); + _collisionBounds.set(pt.x - 25, pt.y - 25, pt.x + 25, pt.y + 25); + setVisible(false); + loadSound(0, 0x440430C0); + SetUpdateHandler(&SsScene2901LocationButton::update); + SetMessageHandler(&SsScene2901LocationButton::handleMessage); +} + +void SsScene2901LocationButton::update() { + updatePosition(); + if (_countdown1 != 0 && (--_countdown1) == 0) { + setVisible(false); + } +} + +uint32 SsScene2901LocationButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_countdown1 == 0) { + playSound(0); + setVisible(true); + _countdown1 = 4; + sendMessage(_parentScene, 0x2001, _index); + } + messageResult = 1; + break; + } + return messageResult; +} + +SsScene2901LocationButtonLight::SsScene2901LocationButtonLight(NeverhoodEngine *vm, int which, uint index) + : StaticSprite(vm, 900), _index(index) { + + loadSprite(kSsScene2901LocationButtonLightFileHashes1[which * 6 + index], kSLFDefDrawOffset | kSLFDefPosition, 900); + setVisible(false); + loadSound(0, kSsScene2901LocationButtonLightFileHashes2[_index]); +} + +void SsScene2901LocationButtonLight::show() { + playSound(0); + setVisible(true); + updatePosition(); +} + +void SsScene2901LocationButtonLight::hide() { + setVisible(false); + updatePosition(); +} + +SsScene2901BrokenButton::SsScene2901BrokenButton(NeverhoodEngine *vm, int which) + : StaticSprite(vm, 900) { + + loadSprite(kSsScene2901BrokenButtonFileHashes[which], kSLFDefDrawOffset | kSLFDefPosition, 900); +} + +SsScene2901BigButton::SsScene2901BigButton(NeverhoodEngine *vm, Scene *parentScene, int which) + : StaticSprite(vm, 900), _parentScene(parentScene), _which(which), _countdown1(0) { + + loadSprite(kSsScene2901BigButtonFileHashes[which], kSLFDefDrawOffset | kSLFDefPosition, 400); + _collisionBounds.set(62, 94, 322, 350); + setVisible(false); + loadSound(0, 0xF3D420C8); + SetUpdateHandler(&SsScene2901BigButton::update); + SetMessageHandler(&SsScene2901BigButton::handleMessage); +} + +void SsScene2901BigButton::update() { + updatePosition(); + if (_countdown1 != 0 && (--_countdown1) == 0) { + setVisible(false); + sendMessage(_parentScene, 0x2000, 0); + } +} + +uint32 SsScene2901BigButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_countdown1 == 0) { + playSound(0); + setVisible(true); + _countdown1 = 4; + } + messageResult = 1; + break; + } + return messageResult; +} + +Scene2901::Scene2901(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _currLocationButtonNum(which), _selectedButtonNum(which), + _currWhirlButtonNum(0), _prevWhirlButtonNum(0), _countdown1(1), _skipCountdown(0), _blinkOn(0) { + + _isButton2Broken = getGlobalVar(V_ENTRANCE_OPEN) != 0; + + setSubVar(V_TELEPORTER_DEST_AVAILABLE, which, 1); + setSubVar(V_TELEPORTER_DEST_AVAILABLE, 5, 1); + setSubVar(V_TELEPORTER_DEST_AVAILABLE, 4, 1); + + if (_currLocationButtonNum == 3) + setSubVar(V_TELEPORTER_DEST_AVAILABLE, 2, 1); + + setBackground(kScene2901FileHashes1[_currLocationButtonNum]); + setPalette(kScene2901FileHashes1[_currLocationButtonNum]); + + for (uint i = 0; i < 6; ++i) { + if (i != 2 || !_isButton2Broken) { + _ssLocationButtons[i] = insertSprite<SsScene2901LocationButton>(this, _currLocationButtonNum, i); + addCollisionSprite(_ssLocationButtons[i]); + _ssLocationButtonLights[i] = insertSprite<SsScene2901LocationButtonLight>(_currLocationButtonNum, i); + } + } + + if (_isButton2Broken) + insertSprite<SsScene2901BrokenButton>(_currLocationButtonNum); + + _ssBigButton = insertSprite<SsScene2901BigButton>(this, _currLocationButtonNum); + addCollisionSprite(_ssBigButton); + + insertPuzzleMouse(kScene2901FileHashes2[_currLocationButtonNum], 20, 620); + + SetUpdateHandler(&Scene2901::update); + SetMessageHandler(&Scene2901::handleMessage); + +} + +void Scene2901::update() { + Scene::update(); + if (_countdown1 != 0 && (--_countdown1) == 0) { + if (_currLocationButtonNum == _selectedButtonNum) { + _ssLocationButtonLights[_currWhirlButtonNum]->hide(); + ++_currWhirlButtonNum; + while (!getSubVar(V_TELEPORTER_DEST_AVAILABLE, _currWhirlButtonNum) || (_currWhirlButtonNum == 2 && _isButton2Broken) || _currLocationButtonNum == _currWhirlButtonNum) { + ++_currWhirlButtonNum; + if (_currWhirlButtonNum >= 6) + _currWhirlButtonNum = 0; + } + if (_currWhirlButtonNum != _prevWhirlButtonNum || _skipCountdown == 0) { + _ssLocationButtonLights[_currWhirlButtonNum]->show(); + _skipCountdown = 4; + } + _countdown1 = 2; + --_skipCountdown; + _prevWhirlButtonNum = _currWhirlButtonNum; + } else if (_blinkOn) { + _blinkOn = false; + _ssLocationButtonLights[_selectedButtonNum]->hide(); + _countdown1 = 16; + } else { + _blinkOn = true; + _ssLocationButtonLights[_selectedButtonNum]->show(); + _countdown1 = 4; + } + } +} + +uint32 Scene2901::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) + leaveScene((uint32)-1); + break; + case 0x2000: + if (_currLocationButtonNum != _selectedButtonNum) + leaveScene(_selectedButtonNum); + break; + case 0x2001: + if (_currLocationButtonNum == _selectedButtonNum) + _selectedButtonNum = _currWhirlButtonNum; + _ssLocationButtonLights[_selectedButtonNum]->hide(); + _selectedButtonNum = param.asInteger(); + if (!getSubVar(V_TELEPORTER_DEST_AVAILABLE, _selectedButtonNum)) + _selectedButtonNum = _currLocationButtonNum; + break; + } + return 0; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module2900.h b/engines/neverhood/modules/module2900.h new file mode 100644 index 0000000000..75b29567f6 --- /dev/null +++ b/engines/neverhood/modules/module2900.h @@ -0,0 +1,102 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_MODULES_MODULE2900_H +#define NEVERHOOD_MODULES_MODULE2900_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +// Module2900 + +class Module2900 : public Module { +public: + Module2900(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + int _sceneNum; + int _teleporterModuleResult; + void createScene(int sceneNum, int which); + void updateScene(); + void updateMusic(bool halfVolume); +}; + +class SsScene2901LocationButton : public StaticSprite { +public: + SsScene2901LocationButton(NeverhoodEngine *vm, Scene *parentScene, int which, uint index); +protected: + Scene *_parentScene; + uint _index; + int _countdown1; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsScene2901LocationButtonLight : public StaticSprite { +public: + SsScene2901LocationButtonLight(NeverhoodEngine *vm, int which, uint index); + void show(); + void hide(); +protected: + uint _index; +}; + +class SsScene2901BrokenButton : public StaticSprite { +public: + SsScene2901BrokenButton(NeverhoodEngine *vm, int which); +}; + +class SsScene2901BigButton : public StaticSprite { +public: + SsScene2901BigButton(NeverhoodEngine *vm, Scene *parentScene, int which); +protected: + Scene *_parentScene; + int _which; + int _countdown1; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene2901 : public Scene { +public: + Scene2901(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_ssLocationButtons[6]; + SsScene2901LocationButtonLight *_ssLocationButtonLights[6]; + Sprite *_ssBigButton; + int _currWhirlButtonNum; + int _prevWhirlButtonNum; + int _countdown1; + int _currLocationButtonNum; + int _selectedButtonNum; + int _skipCountdown; + int _blinkOn; + bool _isButton2Broken; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE2900_H */ diff --git a/engines/neverhood/modules/module3000.cpp b/engines/neverhood/modules/module3000.cpp new file mode 100644 index 0000000000..2bdb9f0497 --- /dev/null +++ b/engines/neverhood/modules/module3000.cpp @@ -0,0 +1,1548 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/modules/module3000.h" +#include "neverhood/gamemodule.h" +#include "neverhood/navigationscene.h" + +namespace Neverhood { + +static const uint32 kModule3000SoundList[] = { + 0x92025040, + 0x90035066, + 0x90815450, + 0x99801500, + 0x90E14440, + 0x16805048, + 0x90F0D1C3, + 0 +}; + +Module3000::Module3000(NeverhoodEngine *vm, Module *parentModule, int which) + : Module(vm, parentModule), _soundVolume(0) { + + _vm->_soundMan->addSoundList(0x81293110, kModule3000SoundList); + _vm->_soundMan->setSoundListParams(kModule3000SoundList, true, 50, 600, 5, 150); + _vm->_soundMan->setSoundParams(0x90F0D1C3, false, 20000, 30000, 20000, 30000); + _vm->_soundMan->playTwoSounds(0x81293110, 0x48498E46, 0x50399F64, 0); + _vm->_soundMan->playTwoSounds(0x81293110, 0x40030A51, 0xC862CA15, 0); + _vm->_soundMan->playTwoSounds(0x81293110, 0x41861371, 0x43A2507F, 0); + + _isWallBroken = getGlobalVar(V_WALL_BROKEN) != 0; + + if (_isWallBroken) { + _vm->_soundMan->setSoundVolume(0x90F0D1C3, 0); + _vm->_soundMan->playSoundLooping(0x90F0D1C3); + } + + if (which < 0) { + createScene(_vm->gameState().sceneNum, -1); + } else if (which == 0) { + createScene(1, 0); + } else if (which == 1) { + createScene(4, 2); + } else if (which == 2) { + createScene(4, 1); + } else if (which == 3) { + createScene(5, 1); + } + +} + +Module3000::~Module3000() { + _vm->_soundMan->deleteGroup(0x81293110); +} + +void Module3000::createScene(int sceneNum, int which) { + static const byte kNavigationTypes05[] = {3, 0}; + static const byte kNavigationTypes06[] = {5}; + debug("Module3000::createScene(%d, %d)", sceneNum, which); + _vm->gameState().sceneNum = sceneNum; + switch (_vm->gameState().sceneNum) { + case 1: + if (!getGlobalVar(V_BOLT_DOOR_OPEN)) { + createNavigationScene(0x004B7C80, which); + } else if (getGlobalVar(V_WALL_BROKEN)) { + createNavigationScene(0x004B7CE0, which); + } else { + createNavigationScene(0x004B7CB0, which); + } + break; + case 2: + _vm->_soundMan->playTwoSounds(0x81293110, 0x40030A51, 0xC862CA15, 0); + if (_isWallBroken) { + _soundVolume = 90; + _vm->_soundMan->setSoundVolume(0x90F0D1C3, 90); + } + if (getGlobalVar(V_WALL_BROKEN)) { + createNavigationScene(0x004B7D58, which); + } else { + createNavigationScene(0x004B7D10, which); + } + break; + case 3: + if (getGlobalVar(V_STAIRS_DOWN)) + createNavigationScene(0x004B7E60, which); + else if (getGlobalVar(V_WALL_BROKEN)) + createNavigationScene(0x004B7DA0, which); + else + createNavigationScene(0x004B7E00, which); + break; + case 4: + if (getGlobalVar(V_STAIRS_DOWN)) + createNavigationScene(0x004B7F20, which); + else + createNavigationScene(0x004B7EC0, which); + break; + case 5: + createNavigationScene(0x004B7F80, which, kNavigationTypes05); + break; + case 6: + createNavigationScene(0x004B7FB0, which, kNavigationTypes06); + break; + case 7: + _vm->_soundMan->setSoundListParams(kModule3000SoundList, false, 0, 0, 0, 0); + if (!getSubVar(VA_IS_PUZZLE_INIT, 0x089809C2)) { + setSubVar(VA_IS_PUZZLE_INIT, 0x089809C2, 1); + createSmackerScene(0x90022001, true, true, false); + } else + createSmackerScene(0x98022001, true, true, false); + break; + case 8: + _childObject = new Scene3009(_vm, this, which); + break; + case 9: + _childObject = new Scene3010(_vm, this, 0); + break; + case 10: + _childObject = new Scene3011(_vm, this, 0); + break; + case 11: + _vm->_soundMan->setSoundListParams(kModule3000SoundList, false, 0, 0, 0, 0); + if (!getSubVar(VA_IS_PUZZLE_INIT, 0x10130993)) { + setSubVar(VA_IS_PUZZLE_INIT, 0x10130993, 1); + createSmackerScene(0x31093019, true, true, false); + } else + createSmackerScene(0x20093019, true, true, false); + break; + case 12: + _childObject = new Scene3010(_vm, this, 1); + break; + // NOTE: Newly introduced sceneNums + case 1001: + if (!getGlobalVar(V_BOLT_DOOR_OPEN)) + if (getGlobalVar(V_WALL_BROKEN)) + createSmackerScene(0x00940021, true, true, false); + else + createSmackerScene(0x01140021, true, true, false); + else + if (getGlobalVar(V_WALL_BROKEN)) + createSmackerScene(0x001011B1, true, true, false); + else + createSmackerScene(0x001021B1, true, true, false); + setGlobalVar(V_BOLT_DOOR_OPEN, getGlobalVar(V_BOLT_DOOR_OPEN) ? 0 : 1); + break; + case 1006: + createSmackerScene(0x080810C5, true, true, false); + break; + case 1008: + createSmackerScene(getGlobalVar(V_CANNON_SMACKER_NAME), true, true, false); + break; + } + SetUpdateHandler(&Module3000::updateScene); + _childObject->handleUpdate(); +} + +void Module3000::updateScene() { + if (!updateChild()) { + switch (_vm->gameState().sceneNum) { + case 1: + if (!getGlobalVar(V_BOLT_DOOR_OPEN)) { + if (_moduleResult == 0) + createScene(9, -1); + else if (_moduleResult == 1) + leaveModule(0); + } else { + if (_moduleResult == 0) + if (_navigationAreaType == 2) + createScene(2, 0); + else + createScene(1001, -1); + else if (_moduleResult == 1) + leaveModule(0); + } + break; + case 2: + _vm->_soundMan->playTwoSounds(0x81293110, 0x41861371, 0x43A2507F, 0); + if (_isWallBroken) { + _soundVolume = 0; + _vm->_soundMan->setSoundVolume(0x90F0D1C3, 0); + } + if (_moduleResult == 0) { + createScene(3, 0); + } else if (_moduleResult == 1) { + setGlobalVar(V_BOLT_DOOR_OPEN, 0); + createScene(1, 1); + } + break; + case 3: + if (_moduleResult == 1) + createScene(4, 0); + else if (_moduleResult == 3) + createScene(10, -1); + else if (getGlobalVar(V_STAIRS_DOWN)) + createScene(5, 0); + else + createScene(2, 1); + break; + case 4: + if (_moduleResult == 0) + leaveModule(1); + else if (_moduleResult == 1) + createScene(7, -1); + else if (_moduleResult == 2) + createScene(3, 3); + break; + case 5: + if (_moduleResult == 0) + createScene(6, 0); + else if (_moduleResult == 1) + createScene(3, 0); + break; + case 6: + if (_navigationAreaType == 4) + createScene(11, -1); + else + createScene(1006, -1); + break; + case 7: + createScene(8, -1); + break; + case 8: + _isWallBroken = getGlobalVar(V_WALL_BROKEN) != 0; + if (_moduleResult != 1) { + _vm->_soundMan->setSoundListParams(kModule3000SoundList, true, 0, 0, 0, 0); + createScene(4, 1); + } else if (getGlobalVar(V_CANNON_SMACKER_NAME)) { + createScene(1008, -1); + } else { + _vm->_soundMan->setSoundListParams(kModule3000SoundList, true, 0, 0, 0, 0); + createScene(4, 1); + } + break; + case 9: + if (_moduleResult == 0 || _moduleResult == 2) + createScene(1, 0); + else if (_moduleResult == 1) + createScene(1001, -1); + break; + case 10: + createScene(3, 3); + break; + case 11: + leaveModule(3); + break; + case 12: + createScene(1, 0); + break; + case 1001: + if (getGlobalVar(V_BOLT_DOOR_OPEN)) + createScene(1, 0); + else + createScene(12, -1); + break; + case 1006: + createScene(5, 0); + break; + case 1008: + createScene(8, -1); + break; + } + } else { + switch (_vm->gameState().sceneNum) { + case 1: + if (navigationScene()->isWalkingForward()) { + uint32 frameNumber = navigationScene()->getFrameNumber(); + int navigationIndex = navigationScene()->getNavigationIndex(); + if (navigationIndex == 1) { + if (frameNumber == 0) { + _vm->_soundMan->playTwoSounds(0x81293110, 0x48498E46, 0x50399F64, 0); + _vm->_soundMan->setSoundVolume(0x48498E46, 70); + _vm->_soundMan->setSoundVolume(0x50399F64, 70); + } else if (frameNumber == 100) { + _vm->_soundMan->playTwoSounds(0x81293110, 0x41861371, 0x43A2507F, 0); + } + } else if (navigationIndex == 0) { + if (frameNumber == 0) { + _vm->_soundMan->playTwoSounds(0x81293110, 0x48498E46, 0x50399F64, 0); + _vm->_soundMan->setSoundVolume(0x48498E46, 70); + _vm->_soundMan->setSoundVolume(0x50399F64, 70); + } else if (frameNumber == 10) { + _vm->_soundMan->playTwoSounds(0x81293110, 0x40030A51, 0xC862CA15, 0); + } + if (_isWallBroken && _soundVolume < 90 && frameNumber % 2) { + if (frameNumber == 0) + _soundVolume = 40; + else + _soundVolume++; + _vm->_soundMan->setSoundVolume(0x90F0D1C3, _soundVolume); + } + } + } + break; + case 2: + if (navigationScene()->isWalkingForward()) { + uint32 frameNumber = navigationScene()->getFrameNumber(); + int navigationIndex = navigationScene()->getNavigationIndex(); + if (_isWallBroken && _soundVolume > 1 && frameNumber % 2) { + _soundVolume--; + _vm->_soundMan->setSoundVolume(0x90F0D1C3, _soundVolume); + } + if (navigationIndex == 0) { + if (frameNumber == 35) { + _vm->_soundMan->playTwoSounds(0x81293110, 0x41861371, 0x43A2507F, 0); + } + } else if (navigationIndex == 1) { + if (frameNumber == 55) { + _vm->_soundMan->playTwoSounds(0x81293110, 0x48498E46, 0x50399F64, 0); + _vm->_soundMan->setSoundVolume(0x48498E46, 70); + _vm->_soundMan->setSoundVolume(0x50399F64, 70); + } + } + } + break; + case 3: + if (navigationScene()->isWalkingForward()) { + uint32 frameNumber = navigationScene()->getFrameNumber(); + int navigationIndex = navigationScene()->getNavigationIndex(); + if (navigationIndex == 2) { + if (frameNumber == 40) { + _vm->_soundMan->playTwoSounds(0x81293110, 0x40030A51, 0xC862CA15, 0); + } + if (_isWallBroken && _soundVolume < 90 && frameNumber % 2) { + if (frameNumber == 0) + _soundVolume = 40; + else + _soundVolume++; + _vm->_soundMan->setSoundVolume(0x90F0D1C3, _soundVolume); + } + } + } + break; + case 5: + if (navigationScene()->isWalkingForward() && navigationScene()->getNavigationIndex() == 0) { + _vm->_soundMan->setTwoSoundsPlayFlag(false); + } + break; + } + } +} + +// Scene3009 + +enum { + kCTSNull = 0, + kCTSBreakWall = 1, + kCTSWall = 2, + kCTSEmptyness = 3, + kCTSFireRobotNoTarget = 4, + kCTSFireRobotIsTarget = 5, + kCTSFireNoRobot = 6, + kCTSRaiseCannon = 7, + kCTSRightRobotNoTarget = 8, + kCTSRightRobotIsTarget = 9, + kCTSRightNoRobot = 10, + kCTSLeftRobotNoTarget = 11, + kCTSLeftRobotIsTarget = 12, + kCTSLeftNoRobot = 13, + kCTSLowerCannon = 14, + kCTSCount = 14 +}; + +static const uint32 kScene3009CannonScopeVideos[] = { + 0x1010000D, + 0x340A0049, + 0x340A0049, + 0x0282081D, + 0x0082080D, + 0x0882080D, + 0x0882080D, + 0x0282081D, + 0x004B000B, + 0x014B000B, + 0x044B000B, + 0x0282081D, + 0x0282081D, + 0x0282081D, + 0x340A0049 +}; + +static const uint32 kScene3009CannonActionVideos[] = { + 0x00000000, + 0x8004001B, // 1 Fire cannon at wall, it breaks (lowered) + 0x0004001A, // 2 Fire cannon at wall, nothing happens (lowered) + 0x1048404B, // 3 Fire cannon at emptyness (raised) + 0x50200109, // 4 Fire cannon, robot missed (raised) + 0x12032109, // 5 Fire cannon, robot hit (raised) + 0x10201109, // 6 Fire cannon, no robot (raised) + 0x000A2030, // 7 Raise the cannon + 0x000A0028, // 8 + 0x000A0028, // 9 + 0x000A0028, // 10 + 0x040A1069, // 11 + 0x040A1069, // 12 + 0x040A1069, // 13 + 0x240A1101 // 14 Lower the cannon +}; + +static const uint32 kSsScene3009SymbolEdgesFileHashes[] = { + 0x618827A0, + 0xB1A92322 +}; + +static const uint32 kSsScene3009TargetLineFileHashes[] = { + 0x4011018C, + 0x15086623 +}; + +static const NPoint kAsScene3009SymbolPoints[] = { + {289, 338}, + {285, 375}, + {284, 419}, + {456, 372}, + {498, 372}, + {541, 372} +}; + +static const uint32 kAsScene3009SymbolFileHashes[] = { + 0x24542582, + 0x1CD61D96 +}; + +static const uint32 kSsScene3009SymbolArrowFileHashes1[] = { + 0x24016060, + 0x21216221, + 0x486160A0, + 0x42216422, + 0x90A16120, + 0x84216824, + 0x08017029, + 0x08217029, + 0x10014032, + 0x10214032, + 0x20012004, + 0x20212004 +}; + +static const uint32 kSsScene3009SymbolArrowFileHashes2[] = { + 0x40092024, + 0x01636002, + 0x8071E028, + 0x02A56064, + 0x00806031, + 0x052960A8, + 0x0A116130, + 0x0A316130, + 0x14216200, + 0x14016200, + 0x28416460, + 0x28616460 +}; + +SsScene3009FireCannonButton::SsScene3009FireCannonButton(NeverhoodEngine *vm, Scene3009 *parentScene) + : StaticSprite(vm, 1400), _parentScene(parentScene), _isClicked(false) { + + loadSprite(0x120B24B0, kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset, 400); + setVisible(false); + SetUpdateHandler(&SsScene3009FireCannonButton::update); + SetMessageHandler(&SsScene3009FireCannonButton::handleMessage); + loadSound(0, 0x3901B44F); +} + +void SsScene3009FireCannonButton::update() { + updatePosition(); + if (_isClicked && !isSoundPlaying(0)) { + sendMessage(_parentScene, 0x2000, 0); + setVisible(false); + } +} + +uint32 SsScene3009FireCannonButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (!_isClicked && !_parentScene->isTurning()) { + _isClicked = true; + setVisible(true); + playSound(0); + } + messageResult = 1; + break; + } + return messageResult; +} + +SsScene3009SymbolEdges::SsScene3009SymbolEdges(NeverhoodEngine *vm, int index) + : StaticSprite(vm, 1400), _blinkCountdown(0) { + + loadSprite(kSsScene3009SymbolEdgesFileHashes[index], kSLFDefDrawOffset | kSLFDefPosition, 600); + if (getGlobalVar(V_ROBOT_HIT)) + hide(); + else + startBlinking(); + SetUpdateHandler(&SsScene3009SymbolEdges::update); +} + +void SsScene3009SymbolEdges::update() { + if (_blinkCountdown != 0 && (--_blinkCountdown == 0)) { + if (_blinkToggle) { + setVisible(true); + } else { + setVisible(false); + } + updatePosition(); + _blinkCountdown = 3; + _blinkToggle = !_blinkToggle; + } +} + +void SsScene3009SymbolEdges::show() { + setVisible(true); + updatePosition(); + _blinkCountdown = 0; +} + +void SsScene3009SymbolEdges::hide() { + setVisible(false); + updatePosition(); + _blinkCountdown = 0; +} + +void SsScene3009SymbolEdges::startBlinking() { + setVisible(true); + updatePosition(); + _blinkCountdown = 3; + _blinkToggle = true; +} + +SsScene3009TargetLine::SsScene3009TargetLine(NeverhoodEngine *vm, int index) + : StaticSprite(vm, 1400) { + + loadSprite(kSsScene3009TargetLineFileHashes[index], kSLFDefDrawOffset | kSLFDefPosition, 600); + setVisible(false); +} + +void SsScene3009TargetLine::show() { + setVisible(true); + updatePosition(); +} + +SsScene3009SymbolArrow::SsScene3009SymbolArrow(NeverhoodEngine *vm, Sprite *asSymbol, int index) + : StaticSprite(vm, 1400), _asSymbol(asSymbol), _index(index), _enabled(true), _countdown(0) { + + _incrDecr = _index % 2; + + createSurface(1200, 33, 31); + loadSprite(kSsScene3009SymbolArrowFileHashes2[_index], kSLFDefPosition); + _drawOffset.set(0, 0, 33, 31); + _collisionBoundsOffset = _drawOffset; + updateBounds(); + _needRefresh = true; + + SetUpdateHandler(&SsScene3009SymbolArrow::update); + SetMessageHandler(&SsScene3009SymbolArrow::handleMessage); + loadSound(0, 0x2C852206); +} + +void SsScene3009SymbolArrow::hide() { + _enabled = false; + setVisible(false); +} + +void SsScene3009SymbolArrow::update() { + updatePosition(); + if (_countdown != 0 && (--_countdown == 0)) { + loadSprite(kSsScene3009SymbolArrowFileHashes2[_index], kSLFDefDrawOffset); + } +} + +uint32 SsScene3009SymbolArrow::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_enabled && _countdown == 0) { + _countdown = 2; + loadSprite(kSsScene3009SymbolArrowFileHashes1[_index], kSLFDefDrawOffset); + playSound(0); + sendMessage(_asSymbol, 0x2005, _incrDecr); + } + messageResult = 1; + break; + } + return messageResult; +} + +AsScene3009VerticalIndicator::AsScene3009VerticalIndicator(NeverhoodEngine *vm, Scene3009 *parentScene, int index) + : AnimatedSprite(vm, 1000), _parentScene(parentScene), _enabled(false) { + + _x = 300; + _y = getGlobalVar(V_CANNON_RAISED) ? 52 : 266; + createSurface1(0xC2463913, 1200); + _needRefresh = true; + updatePosition(); + setVisible(false); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene3009VerticalIndicator::handleMessage); +} + +void AsScene3009VerticalIndicator::show() { + startAnimation(0xC2463913, 0, -1); + setVisible(true); + updatePosition(); + _enabled = true; +} + +uint32 AsScene3009VerticalIndicator::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_enabled) { + sendMessage(_parentScene, 0x2002, 0); + } + messageResult = 1; + break; + } + return messageResult; +} + +AsScene3009HorizontalIndicator::AsScene3009HorizontalIndicator(NeverhoodEngine *vm, Scene3009 *parentScene, uint32 cannonTargetStatus) + : AnimatedSprite(vm, 1000), _parentScene(parentScene), _enabled(false) { + + _x = getGlobalVar(V_CANNON_TURNED) ? 533 : 92; + _y = 150; + createSurface1(0xC0C12954, 1200); + _needRefresh = true; + updatePosition(); + setVisible(false); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene3009HorizontalIndicator::handleMessage); + if (cannonTargetStatus == kCTSRightRobotNoTarget || cannonTargetStatus == kCTSRightRobotIsTarget || cannonTargetStatus == kCTSRightNoRobot) { + SetSpriteUpdate(&AsScene3009HorizontalIndicator::suMoveRight); + _x = 280; + } +} + +uint32 AsScene3009HorizontalIndicator::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_enabled) { + sendMessage(_parentScene, 0x2004, 0); + } + messageResult = 1; + break; + } + return messageResult; +} + +void AsScene3009HorizontalIndicator::suMoveLeft() { + _x -= 6; + if (_x < 92) { + SetSpriteUpdate(NULL); + _x = 92; + } +} + +void AsScene3009HorizontalIndicator::suMoveRight() { + _x += 6; + if (_x > 533) { + SetSpriteUpdate(NULL); + _x = 533; + } +} + +void AsScene3009HorizontalIndicator::show() { + startAnimation(0xC0C12954, 0, -1); + setVisible(true); + updatePosition(); + _enabled = true; +} + +void AsScene3009HorizontalIndicator::stMoveLeft() { + _x = 533; + SetSpriteUpdate(&AsScene3009HorizontalIndicator::suMoveLeft); +} + +void AsScene3009HorizontalIndicator::stMoveRight() { + _x = 330; + SetSpriteUpdate(&AsScene3009HorizontalIndicator::suMoveRight); +} + +AsScene3009Symbol::AsScene3009Symbol(NeverhoodEngine *vm, Scene3009 *parentScene, int symbolPosition) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _symbolPosition(symbolPosition) { + + _symbolIndex = getSubVar(VA_CURR_CANNON_SYMBOLS, _symbolPosition); + + _x = kAsScene3009SymbolPoints[_symbolPosition].x; + _y = kAsScene3009SymbolPoints[_symbolPosition].y; + createSurface1(kAsScene3009SymbolFileHashes[_symbolPosition / 3], 1200); + startAnimation(kAsScene3009SymbolFileHashes[_symbolPosition / 3], _symbolIndex, -1); + _newStickFrameIndex = _symbolIndex; + _needRefresh = true; + updatePosition(); + SetUpdateHandler(&AnimatedSprite::update); + SetMessageHandler(&AsScene3009Symbol::handleMessage); + _ssArrowPrev = _parentScene->insertSprite<SsScene3009SymbolArrow>(this, _symbolPosition * 2 + 0); + _parentScene->addCollisionSprite(_ssArrowPrev); + _ssArrowNext = _parentScene->insertSprite<SsScene3009SymbolArrow>(this, _symbolPosition * 2 + 1); + _parentScene->addCollisionSprite(_ssArrowNext); +} + +uint32 AsScene3009Symbol::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2005: + if (param.asInteger()) { + if (_symbolIndex == 11) + _symbolIndex = 0; + else + _symbolIndex++; + } else { + if (_symbolIndex == 0) + _symbolIndex = 11; + else + _symbolIndex--; + } + startAnimation(kAsScene3009SymbolFileHashes[_symbolPosition / 3], _symbolIndex, -1); + _newStickFrameIndex = _symbolIndex; + setSubVar(VA_CURR_CANNON_SYMBOLS, _symbolPosition, _symbolIndex); + if (_symbolPosition / 3 == 0) { + sendMessage(_parentScene, 0x2001, 0); + } else { + sendMessage(_parentScene, 0x2003, 0); + } + messageResult = 1; + break; + } + return messageResult; +} + +void AsScene3009Symbol::hide() { + _ssArrowPrev->hide(); + _ssArrowNext->hide(); +} + +Scene3009::Scene3009(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _keepVideo(false), _moveCannonLeftFirst(false), + _isTurning(false), _lockSymbolsPart1Countdown(1), _lockSymbolsPart2Countdown(1) { + + _cannonTargetStatus = getGlobalVar(V_CANNON_TARGET_STATUS); + + _vm->gameModule()->initCannonSymbolsPuzzle(); + + setGlobalVar(V_CANNON_SMACKER_NAME, 0); + + _vm->_screen->clear(); + + setBackground(0xD000420C); + setPalette(0xD000420C); + insertPuzzleMouse(0x04208D08, 20, 620); + + _ssFireCannonButton = insertSprite<SsScene3009FireCannonButton>(this); + addCollisionSprite(_ssFireCannonButton); + + _asVerticalIndicator = insertSprite<AsScene3009VerticalIndicator>(this, _cannonTargetStatus); + addCollisionSprite(_asVerticalIndicator); + + _asHorizontalIndicator = insertSprite<AsScene3009HorizontalIndicator>(this, _cannonTargetStatus); + addCollisionSprite(_asHorizontalIndicator); + + if (_cannonTargetStatus != kCTSNull && _cannonTargetStatus != kCTSRightRobotNoTarget && _cannonTargetStatus != kCTSRightRobotIsTarget && _cannonTargetStatus != kCTSRightNoRobot) { + _keepVideo = true; + } else { + _keepVideo = false; + if (_cannonTargetStatus != kCTSNull) { + _asHorizontalIndicator->stMoveRight(); + _isTurning = true; + } + } + + _smackerPlayer = addSmackerPlayer(new SmackerPlayer(_vm, this, kScene3009CannonScopeVideos[_cannonTargetStatus], false, _keepVideo)); + _smackerPlayer->setDrawPos(89, 37); + _palette->usePalette(); // Use it again since the SmackerPlayer overrides the usage + + insertStaticSprite(0x8540252C, 400); + + for (int i = 0; i < 2; i++) { + _ssSymbolEdges[i] = insertSprite<SsScene3009SymbolEdges>(i); + _ssTargetLines[i] = insertSprite<SsScene3009TargetLine>(i); + } + + for (int symbolPosition = 0; symbolPosition < 6; symbolPosition++) { + _asSymbols[symbolPosition] = insertSprite<AsScene3009Symbol>(this, symbolPosition); + if (symbolPosition < 3) + _correctSymbols[symbolPosition] = getSubVar(VA_GOOD_CANNON_SYMBOLS_1, symbolPosition); + else + _correctSymbols[symbolPosition] = getSubVar(VA_GOOD_CANNON_SYMBOLS_2, symbolPosition - 3); + } + + SetMessageHandler(&Scene3009::handleMessage); + SetUpdateHandler(&Scene3009::update); + + // DEBUG Enable to set the correct code +#if 0 + for (int i = 0; i < 6; i++) + setSubVar(VA_CURR_CANNON_SYMBOLS, i, _correctSymbols[i]); + sendMessage(this, 0x2003, 0); +#endif + +} + +void Scene3009::update() { + Scene::update(); + + if (!_keepVideo && _smackerPlayer->isDone() && _cannonTargetStatus <= kCTSCount) { + switch (_cannonTargetStatus) { + case kCTSNull: + case kCTSLowerCannon: + _smackerPlayer->open(0x340A0049, true); + _palette->usePalette(); + _keepVideo = true; + break; + case kCTSRightRobotNoTarget: + _smackerPlayer->open(0x0082080D, true); + _palette->usePalette(); + _keepVideo = true; + _isTurning = false; + break; + case kCTSRightRobotIsTarget: + _smackerPlayer->open(0x0282080D, true); + _palette->usePalette(); + _keepVideo = true; + _isTurning = false; + break; + case kCTSRightNoRobot: + _smackerPlayer->open(0x0882080D, true); + _palette->usePalette(); + _keepVideo = true; + _isTurning = false; + break; + case kCTSLeftRobotNoTarget: + case kCTSLeftRobotIsTarget: + case kCTSLeftNoRobot: + if (_moveCannonLeftFirst) { + if (_cannonTargetStatus == kCTSLeftRobotNoTarget) + _smackerPlayer->open(0x110A000F, false); + else if (_cannonTargetStatus == kCTSLeftRobotIsTarget) + _smackerPlayer->open(0x500B004F, false); + else if (_cannonTargetStatus == kCTSLeftNoRobot) + _smackerPlayer->open(0x100B010E, false); + _palette->usePalette(); + _moveCannonLeftFirst = false; + _asHorizontalIndicator->stMoveLeft(); + } else { + playActionVideo(); + } + break; + } + } + + if (_lockSymbolsPart1Countdown != 0 && (--_lockSymbolsPart1Countdown == 0) && isSymbolsPart1Solved()) { + for (int i = 0; i < 3; i++) + _asSymbols[i]->hide(); + if (!getGlobalVar(V_ROBOT_HIT) || getGlobalVar(V_CANNON_RAISED) || getGlobalVar(V_CANNON_TURNED)) { + _ssSymbolEdges[0]->show(); + _ssTargetLines[0]->show(); + _asVerticalIndicator->show(); + } + } + + if (_lockSymbolsPart2Countdown != 0 && (--_lockSymbolsPart2Countdown == 0) && isSymbolsPart2Solved()) { + for (int i = 3; i < 6; i++) + _asSymbols[i]->hide(); + if (!getGlobalVar(V_ROBOT_HIT) || getGlobalVar(V_CANNON_RAISED) || getGlobalVar(V_CANNON_TURNED)) { + _ssSymbolEdges[1]->show(); + _ssTargetLines[1]->show(); + _asHorizontalIndicator->show(); + } + } + +} + +uint32 Scene3009::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if ((param.asPoint().x <= 20 || param.asPoint().x >= 620) && !getGlobalVar(V_CANNON_RAISED)) { + setGlobalVar(V_CANNON_TARGET_STATUS, 0); + leaveScene(0); + } + break; + case 0x2000: + if (!getGlobalVar(V_CANNON_RAISED)) { + if (!getGlobalVar(V_WALL_BROKEN)) { + _cannonTargetStatus = kCTSBreakWall; + setGlobalVar(V_WALL_BROKEN, 1); + } else { + _cannonTargetStatus = kCTSWall; + } + } else if (!getGlobalVar(V_CANNON_TURNED)) { + _cannonTargetStatus = kCTSEmptyness; + } else if (!getGlobalVar(V_ROBOT_TARGET)) { + _cannonTargetStatus = kCTSFireRobotNoTarget; + } else if (!getGlobalVar(V_ROBOT_HIT)) { + setGlobalVar(V_ROBOT_HIT, 1); + _cannonTargetStatus = kCTSFireRobotIsTarget; + } else { + _cannonTargetStatus = kCTSFireNoRobot; + } + playActionVideo(); + break; + case 0x2001: + _lockSymbolsPart1Countdown = 24; + break; + case 0x2002: + // Raise/lower the cannon + if (!getGlobalVar(V_CANNON_TURNED) && !_isTurning) { + if (getGlobalVar(V_CANNON_RAISED)) { + _cannonTargetStatus = kCTSLowerCannon; + setGlobalVar(V_CANNON_RAISED, 0); + } else { + _cannonTargetStatus = kCTSRaiseCannon; + setGlobalVar(V_CANNON_RAISED, 1); + } + playActionVideo(); + } + break; + case 0x2003: + _lockSymbolsPart2Countdown = 24; + break; + case 0x2004: + // Turn the cannon if it's raised + if (getGlobalVar(V_CANNON_RAISED)) { + if (!getGlobalVar(V_CANNON_TURNED)) { + // Cannon is at the left position + if (!getGlobalVar(V_ROBOT_TARGET)) { + _cannonTargetStatus = kCTSRightRobotNoTarget; + } else if (!getGlobalVar(V_ROBOT_HIT)) { + _cannonTargetStatus = kCTSRightRobotIsTarget; + } else { + _cannonTargetStatus = kCTSRightNoRobot; + } + setGlobalVar(V_CANNON_TURNED, 1); + _isTurning = true; + playActionVideo(); + } else { + // Cannon is at the right position + if (!getGlobalVar(V_ROBOT_TARGET)) { + _cannonTargetStatus = kCTSLeftRobotNoTarget; + _smackerPlayer->open(0x108A000F, false); + } else if (!getGlobalVar(V_ROBOT_HIT)) { + _cannonTargetStatus = kCTSLeftRobotIsTarget; + _smackerPlayer->open(0x500B002F, false); + } else { + _cannonTargetStatus = kCTSLeftNoRobot; + _smackerPlayer->open(0x100B008E, false); + } + _palette->usePalette(); + _moveCannonLeftFirst = true; + _isTurning = true; + _keepVideo = false; + setGlobalVar(V_CANNON_TURNED, 0); + } + } + break; + } + return 0; +} + +void Scene3009::playActionVideo() { + setGlobalVar(V_CANNON_TARGET_STATUS, _cannonTargetStatus); + setGlobalVar(V_CANNON_SMACKER_NAME, kScene3009CannonActionVideos[_cannonTargetStatus]); + leaveScene(1); +} + +bool Scene3009::isSymbolsPart1Solved() { + for (int i = 0; i < 3; i++) + if (_correctSymbols[i] != getSubVar(VA_CURR_CANNON_SYMBOLS, i)) + return false; + return true; +} + +bool Scene3009::isSymbolsPart2Solved() { + for (int i = 3; i < 6; i++) + if (_correctSymbols[i] != getSubVar(VA_CURR_CANNON_SYMBOLS, i)) + return false; + return true; +} + +bool Scene3009::isTurning() { + return _isTurning; +} + +// Scene3010 + +static const uint32 kScene3010ButtonNameHashes[] = { + 0x304008D2, + 0x40119852, + 0x01180951 +}; + +static const uint32 kScene3010DeadBoltButtonFileHashes1[] = { + 0x301024C2, + 0x20280580, + 0x30200452 +}; + +static const uint32 kScene3010DeadBoltButtonFileHashes2[] = { + 0x50C025A8, + 0x1020A0A0, + 0x5000A7E8 +}; + +static const NPoint kAsScene3010DeadBoltPoints[] = { + {550, 307}, + {564, 415}, + {560, 514} +}; + +static const uint32 kAsScene3010DeadBoltFileHashes2[] = { + 0x181A0042, + 0x580A08F2, + 0x18420076 +}; + +static const uint32 kAsScene3010DeadBoltFileHashes1[] = { + 0x300E105A, + 0x804E0052, + 0x040E485A +}; + +SsScene3010DeadBoltButton::SsScene3010DeadBoltButton(NeverhoodEngine *vm, Scene *parentScene, int buttonIndex, int initCountdown, bool initDisabled) + : StaticSprite(vm, 900), _parentScene(parentScene), _buttonLocked(false), _countdown1(0), _countdown2(0), _buttonIndex(buttonIndex) { + + _buttonEnabled = getSubVar(VA_LOCKS_DISABLED, kScene3010ButtonNameHashes[_buttonIndex]) != 0; + createSurface(400, 88, 95); + setSprite(kScene3010DeadBoltButtonFileHashes2[_buttonIndex]); + if (initDisabled) + disableButton(); + else if (_buttonEnabled) + _countdown1 = initCountdown * 12 + 1; + loadSound(0, 0xF4217243); + loadSound(1, 0x44049000); + loadSound(2, 0x6408107E); + SetUpdateHandler(&SsScene3010DeadBoltButton::update); + SetMessageHandler(&SsScene3010DeadBoltButton::handleMessage); +} + +void SsScene3010DeadBoltButton::update() { + + if (_countdown1 != 0 && (--_countdown1 == 0)) { + playSound(0); + setVisible(false); + setSprite(kScene3010DeadBoltButtonFileHashes1[_buttonIndex]); + } + + if (_countdown2 != 0 && (--_countdown2 == 0)) { + setVisible(true); + setSprite(kScene3010DeadBoltButtonFileHashes2[_buttonIndex]); + } + +} + +uint32 SsScene3010DeadBoltButton::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (!_buttonLocked && _countdown1 == 0) { + if (_buttonEnabled) { + playSound(1); + playSound(2); + setVisible(true); + _buttonLocked = true; + sendMessage(_parentScene, 0x2000, _buttonIndex); + } else { + sendMessage(_parentScene, 0x2002, _buttonIndex); + } + _needRefresh = true; + updatePosition(); + } + messageResult = 1; + break; + } + return messageResult; +} + +void SsScene3010DeadBoltButton::disableButton() { + _buttonLocked = true; + setSprite(kScene3010DeadBoltButtonFileHashes1[_buttonIndex]); + setVisible(true); +} + +void SsScene3010DeadBoltButton::setSprite(uint32 fileHash) { + loadSprite(fileHash, kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset); +} + +void SsScene3010DeadBoltButton::setCountdown(int count) { + _countdown2 = count * 18 + 1; +} + +AsScene3010DeadBolt::AsScene3010DeadBolt(NeverhoodEngine *vm, Scene *parentScene, int boltIndex, bool initUnlocked) + : AnimatedSprite(vm, 1100), _parentScene(parentScene), _boltIndex(boltIndex), _soundToggle(true), + _unlocked(false), _locked(false), _countdown(0) { + + _x = kAsScene3010DeadBoltPoints[_boltIndex].x; + _y = kAsScene3010DeadBoltPoints[_boltIndex].y; + + if (getSubVar(VA_LOCKS_DISABLED, kScene3010ButtonNameHashes[_boltIndex])) { + createSurface1(kAsScene3010DeadBoltFileHashes1[_boltIndex], 1200); + startAnimation(kAsScene3010DeadBoltFileHashes1[_boltIndex], 0, -1); + loadSound(0, 0x46005BC4); + } else { + createSurface1(kAsScene3010DeadBoltFileHashes2[_boltIndex], 1200); + startAnimation(kAsScene3010DeadBoltFileHashes2[_boltIndex], 0, -1); + loadSound(0, 0x420073DC); + loadSound(1, 0x420073DC); + } + + setVisible(false); + stIdle(); + if (initUnlocked) + unlock(true); + + _needRefresh = true; + AnimatedSprite::updatePosition(); + +} + +void AsScene3010DeadBolt::update() { + updateAnim(); + updatePosition(); + if (_countdown != 0 && (--_countdown == 0)) { + stDisabled(); + } +} + +uint32 AsScene3010DeadBolt::hmAnimation(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x3002: + gotoNextState(); + break; + } + return messageResult; +} + +void AsScene3010DeadBolt::stIdle() { + stopAnimation(); + SetUpdateHandler(&AsScene3010DeadBolt::update); + SetMessageHandler(&Sprite::handleMessage); + _locked = false; +} + +void AsScene3010DeadBolt::unlock(bool skipAnim) { + if (!_unlocked) { + setVisible(true); + if (skipAnim) { + startAnimation(kAsScene3010DeadBoltFileHashes1[_boltIndex], -1, 0); + _newStickFrameIndex = STICK_LAST_FRAME; + } else { + startAnimation(kAsScene3010DeadBoltFileHashes1[_boltIndex], 0, -1); + SetMessageHandler(&AsScene3010DeadBolt::hmAnimation); + FinalizeState(&AsScene3010DeadBolt::stIdleMessage); + NextState(&AsScene3010DeadBolt::stIdle); + playSound(0); + } + _unlocked = true; + loadSound(2, 0x4010C345); + } +} + +void AsScene3010DeadBolt::stIdleMessage() { + stopAnimation(); + SetMessageHandler(&Sprite::handleMessage); + sendMessage(_parentScene, 0x2001, _boltIndex); +} + +void AsScene3010DeadBolt::lock() { + if (!_locked) { + _locked = true; + setVisible(true); + startAnimation(kAsScene3010DeadBoltFileHashes2[_boltIndex], 0, -1); + SetMessageHandler(&AsScene3010DeadBolt::hmAnimation); + FinalizeState(&AsScene3010DeadBolt::stDisabledMessage); + NextState(&AsScene3010DeadBolt::stIdle); + if (_soundToggle) { + playSound(0); + } else { + playSound(1); + } + _soundToggle = !_soundToggle; + } +} + +void AsScene3010DeadBolt::setCountdown(int count) { + _countdown = count * 18 + 1; +} + +void AsScene3010DeadBolt::stDisabled() { + setVisible(true); + startAnimation(kAsScene3010DeadBoltFileHashes1[_boltIndex], 0, -1); + SetMessageHandler(&AsScene3010DeadBolt::hmAnimation); + FinalizeState(&AsScene3010DeadBolt::stDisabledMessage); + NextState(&AsScene3010DeadBolt::stIdle); + _playBackwards = true; + playSound(2); +} + +void AsScene3010DeadBolt::stDisabledMessage() { + setVisible(false); + sendMessage(_parentScene, 0x2003, _boltIndex); +} + +Scene3010::Scene3010(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _countdown(0), _doorUnlocked(false), _checkUnlocked(false) { + + int initCountdown = 0; + + // DEBUG Enable to activate all buttons +#if 0 + setSubVar(VA_LOCKS_DISABLED, kScene3010ButtonNameHashes[0], 1); + setSubVar(VA_LOCKS_DISABLED, kScene3010ButtonNameHashes[1], 1); + setSubVar(VA_LOCKS_DISABLED, kScene3010ButtonNameHashes[2], 1); +#endif + + setBackground(0x80802626); + setPalette(0x80802626); + + for (int i = 0; i < 3; i++) { + _asDeadBolts[i] = insertSprite<AsScene3010DeadBolt>(this, i, which == 1);//CHECKME + _ssDeadBoltButtons[i] = insertSprite<SsScene3010DeadBoltButton>(this, i, initCountdown, which == 1);//CHECKME + addCollisionSprite(_ssDeadBoltButtons[i]); + if (getSubVar(VA_LOCKS_DISABLED, kScene3010ButtonNameHashes[i])) + initCountdown++; + _boltUnlocking[i] = false; + _boltUnlocked[i] = false; + } + + if (which == 0) { + insertPuzzleMouse(0x02622800, 20, 620); + } + + loadSound(0, 0x68E25540); + + SetMessageHandler(&Scene3010::handleMessage); + SetUpdateHandler(&Scene3010::update); + + if (which == 1) { + _checkUnlocked = true; + for (int i = 0; i < 3; i++) { + _boltUnlocked[i] = true; + _ssDeadBoltButtons[i]->setCountdown(i + 1); + _asDeadBolts[i]->setCountdown(i + 1); + } + } + +} + +void Scene3010::update() { + Scene::update(); + if (_checkUnlocked && !_boltUnlocked[0] && !_boltUnlocked[1] && !_boltUnlocked[2]) { + _countdown = 24; + _checkUnlocked = false; + } + if (_countdown != 0 && (--_countdown == 0)) { + leaveScene(_doorUnlocked ? 1 : 0); + } +} + +uint32 Scene3010::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if ((param.asPoint().x <= 20 || param.asPoint().x >= 620) && _countdown == 0 && !_checkUnlocked) { + if (!_boltUnlocking[0] && !_boltUnlocking[1] && !_boltUnlocking[2]) { + showMouse(false); + if (!_boltUnlocked[0] && !_boltUnlocked[1] && !_boltUnlocked[2]) { + _countdown = 1; + } else { + _checkUnlocked = true; + for (int i = 0; i < 3; i++) { + _ssDeadBoltButtons[i]->setCountdown(i); + if (_boltUnlocked[i]) { + _asDeadBolts[i]->setCountdown(i); + } + } + } + } + } + break; + case 0x2000: + if (!_boltUnlocked[param.asInteger()] && !_checkUnlocked && _countdown == 0) { + _asDeadBolts[param.asInteger()]->unlock(false); + _boltUnlocking[param.asInteger()] = true; + } + break; + case 0x2001: + _boltUnlocked[param.asInteger()] = true; + _boltUnlocking[param.asInteger()] = false; + if (_boltUnlocked[0] && _boltUnlocked[1] && _boltUnlocked[2]) { + if (!getGlobalVar(V_BOLT_DOOR_UNLOCKED)) { + setGlobalVar(V_BOLT_DOOR_UNLOCKED, 1); + playSound(0); + _countdown = 60; + } else { + _countdown = 48; + } + _doorUnlocked = true; + } + break; + case 0x2002: + if (!_checkUnlocked && _countdown == 0) { + _asDeadBolts[param.asInteger()]->lock(); + } + break; + case 0x2003: + _boltUnlocked[param.asInteger()] = false; + break; + } + return 0; +} + +// Scene3011 + +static const uint32 kAsScene3011SymbolFileHashes[] = { + 0x00C88050, + 0x01488050, + 0x02488050, + 0x04488050, + 0x08488050, + 0x10488050, + 0x20488050, + 0x40488050, + 0x80488050, + 0x00488051, + 0x00488052, + 0x00488054, + 0x008B0000, + 0x008D0000, + 0x00810000, + 0x00990000, + 0x00A90000, + 0x00C90000, + 0x00090000, + 0x01890000, + 0x02890000, + 0x04890000, + 0x08890000, + 0x10890000 +}; + +SsScene3011Button::SsScene3011Button(NeverhoodEngine *vm, Scene *parentScene, bool flag) + : StaticSprite(vm, 1400), _parentScene(parentScene), _countdown(0) { + + loadSprite(flag ? 0x11282020 : 0x994D0433, kSLFDefDrawOffset | kSLFDefPosition | kSLFDefCollisionBoundsOffset, 400); + setVisible(false); + loadSound(0, 0x44061000); + SetUpdateHandler(&SsScene3011Button::update); + SetMessageHandler(&SsScene3011Button::handleMessage); +} + +void SsScene3011Button::update() { + updatePosition(); + if (_countdown != 0 && (--_countdown == 0)) { + setVisible(false); + } +} + +uint32 SsScene3011Button::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = 0; + StaticSprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x1011: + if (_countdown == 0) { + setVisible(true); + _countdown = 4; + sendMessage(_parentScene, 0x2000, 0); + playSound(0); + } + messageResult = 1; + break; + } + return messageResult; +} + +AsScene3011Symbol::AsScene3011Symbol(NeverhoodEngine *vm, int symbolIndex, bool largeSymbol) + : AnimatedSprite(vm, 1000), _symbolIndex(symbolIndex), _largeSymbol(largeSymbol), _isNoisy(false) { + + if (_largeSymbol) { + _x = 310; + _y = 200; + createSurface1(kAsScene3011SymbolFileHashes[_symbolIndex], 1200); + loadSound(0, 0x6052C60F); + loadSound(1, 0x6890433B); + } else { + _symbolIndex = 12; + _x = symbolIndex * 39 + 96; + _y = 225; + createSurface(1200, 41, 48); + loadSound(0, 0x64428609); + loadSound(1, 0x7080023B); + } + setVisible(false); + _needRefresh = true; + SetUpdateHandler(&AnimatedSprite::update); +} + +void AsScene3011Symbol::show(bool isNoisy) { + _isNoisy = isNoisy; + startAnimation(kAsScene3011SymbolFileHashes[_symbolIndex], 0, -1); + setVisible(true); + if (_isNoisy) { + playSound(1); + } else { + playSound(0); + } +} + +void AsScene3011Symbol::hide() { + stopAnimation(); + setVisible(false); +} + +void AsScene3011Symbol::stopSymbolSound() { + if (_isNoisy) { + stopSound(1); + } else { + stopSound(0); + } +} + +void AsScene3011Symbol::change(int symbolIndex, bool isNoisy) { + _symbolIndex = symbolIndex; + _isNoisy = isNoisy; + startAnimation(kAsScene3011SymbolFileHashes[_symbolIndex], 0, -1); + setVisible(true); + if (_isNoisy) { + playSound(1); + } else { + playSound(0); + } +} + +Scene3011::Scene3011(NeverhoodEngine *vm, Module *parentModule, int which) + : Scene(vm, parentModule), _updateStatus(0), _buttonClicked(false), _currentSymbolIndex(0), _countdown(0) { + + _vm->gameModule()->initCodeSymbolsPuzzle(); + _noisySymbolIndex = getGlobalVar(V_NOISY_SYMBOL_INDEX); + + SetMessageHandler(&Scene3011::handleMessage); + SetUpdateHandler(&Scene3011::update); + + setBackground(0x92124A04); + setPalette(0xA4070114); + addEntity(_palette); + + insertPuzzleMouse(0x24A00929, 20, 620); + + for (int symbolIndex = 0; symbolIndex < 12; symbolIndex++) + _asSymbols[symbolIndex] = insertSprite<AsScene3011Symbol>(symbolIndex, true); + + _ssButton = insertSprite<SsScene3011Button>(this, true); + addCollisionSprite(_ssButton); + +} + +void Scene3011::update() { + Scene::update(); + + if (_countdown != 0 && (--_countdown == 0)) { + switch (_updateStatus) { + case 0: + if (_buttonClicked) { + if (_noisySymbolIndex == _currentSymbolIndex) { + do { + _noisyRandomSymbolIndex = _vm->_rnd->getRandomNumber(12 - 1); + } while (_noisySymbolIndex == _noisyRandomSymbolIndex); + _asSymbols[getSubVar(VA_CODE_SYMBOLS, _noisyRandomSymbolIndex)]->show(true); + } else { + _asSymbols[getSubVar(VA_CODE_SYMBOLS, _currentSymbolIndex)]->show(false); + } + _updateStatus = 1; + _countdown = 24; + fadeIn(); + _buttonClicked = false; + } + break; + case 1: + _updateStatus = 2; + _countdown = 24; + break; + case 2: + fadeOut(); + _updateStatus = 3; + _countdown = 24; + break; + case 3: + _updateStatus = 0; + _countdown = 1; + if (_noisySymbolIndex == _currentSymbolIndex) { + _asSymbols[getSubVar(VA_CODE_SYMBOLS, _noisyRandomSymbolIndex)]->hide(); + } else { + _asSymbols[getSubVar(VA_CODE_SYMBOLS, _currentSymbolIndex)]->hide(); + } + _currentSymbolIndex++; + if (_currentSymbolIndex >= 12) + _currentSymbolIndex = 0; + break; + } + } +} + +uint32 Scene3011::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0001: + if (param.asPoint().x <= 20 || param.asPoint().x >= 620) { + leaveScene(0); + } + break; + case 0x2000: + _buttonClicked = true; + if (_countdown == 0) + _countdown = 1; + break; + } + return 0; +} + +void Scene3011::fadeIn() { + _palette->addBasePalette(0x92124A04, 0, 256, 0); + _palette->startFadeToPalette(24); +} + +void Scene3011::fadeOut() { + _palette->addBasePalette(0xA4070114, 0, 256, 0); + _palette->startFadeToPalette(24); +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/modules/module3000.h b/engines/neverhood/modules/module3000.h new file mode 100644 index 0000000000..7634360d7c --- /dev/null +++ b/engines/neverhood/modules/module3000.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 NEVERHOOD_MODULES_MODULE3000_H +#define NEVERHOOD_MODULES_MODULE3000_H + +#include "neverhood/neverhood.h" +#include "neverhood/module.h" +#include "neverhood/scene.h" +#include "neverhood/modules/module1200.h" + +namespace Neverhood { + +class Module3000 : public Module { +public: + Module3000(NeverhoodEngine *vm, Module *parentModule, int which); + virtual ~Module3000(); +protected: + int _soundVolume; + bool _isWallBroken; + void createScene(int sceneNum, int which); + void updateScene(); +}; + +// Scene3009 + +class Scene3009; + +class SsScene3009FireCannonButton : public StaticSprite { +public: + SsScene3009FireCannonButton(NeverhoodEngine *vm, Scene3009 *parentScene); +protected: + Scene3009 *_parentScene; + bool _isClicked; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsScene3009SymbolEdges : public StaticSprite { +public: + SsScene3009SymbolEdges(NeverhoodEngine *vm, int index); + void show(); + void hide(); + void startBlinking(); +protected: + int _blinkCountdown; + bool _blinkToggle; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class SsScene3009TargetLine : public StaticSprite { +public: + SsScene3009TargetLine(NeverhoodEngine *vm, int index); + void show(); +}; + +class SsScene3009SymbolArrow : public StaticSprite { +public: + SsScene3009SymbolArrow(NeverhoodEngine *vm, Sprite *asSymbol, int index); + void hide(); +protected: + Sprite *_asSymbol; + int _index; + int _incrDecr; + bool _enabled; + int _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene3009VerticalIndicator : public AnimatedSprite { +public: + AsScene3009VerticalIndicator(NeverhoodEngine *vm, Scene3009 *parentScene, int index); + void show(); +protected: + Scene3009 *_parentScene; + bool _enabled; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene3009HorizontalIndicator : public AnimatedSprite { +public: + AsScene3009HorizontalIndicator(NeverhoodEngine *vm, Scene3009 *parentScene, uint32 cannonTargetStatus); + void show(); + void stMoveLeft(); + void stMoveRight(); +protected: + Scene3009 *_parentScene; + bool _enabled; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void suMoveLeft(); + void suMoveRight(); +}; + +class AsScene3009Symbol : public AnimatedSprite { +public: + AsScene3009Symbol(NeverhoodEngine *vm, Scene3009 *parentScene, int symbolPosition); + void hide(); +protected: + Scene3009 *_parentScene; + int _symbolPosition; + uint32 _symbolIndex; + SsScene3009SymbolArrow *_ssArrowPrev; + SsScene3009SymbolArrow *_ssArrowNext; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class Scene3009 : public Scene { +public: + Scene3009(NeverhoodEngine *vm, Module *parentModule, int which); + bool isTurning(); +protected: + int _lockSymbolsPart1Countdown; + int _lockSymbolsPart2Countdown; + SmackerPlayer *_smackerPlayer; + Sprite *_ssFireCannonButton; + SsScene3009SymbolEdges *_ssSymbolEdges[2]; + SsScene3009TargetLine *_ssTargetLines[2]; + AsScene3009VerticalIndicator *_asVerticalIndicator; + AsScene3009HorizontalIndicator *_asHorizontalIndicator; + AsScene3009Symbol *_asSymbols[6]; + uint32 _cannonTargetStatus; + uint32 _correctSymbols[6]; + bool _keepVideo; + bool _moveCannonLeftFirst; + bool _isTurning; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void playActionVideo(); + bool isSymbolsPart1Solved(); + bool isSymbolsPart2Solved(); +}; + +// Scene3010 + +class SsScene3010DeadBoltButton : public StaticSprite { +public: + SsScene3010DeadBoltButton(NeverhoodEngine *vm, Scene *parentScene, int buttonIndex, int initCountdown, bool initDisabled); + void setCountdown(int count); +protected: + Scene *_parentScene; + int _buttonIndex; + bool _buttonEnabled; + bool _buttonLocked; + int _countdown1; + int _countdown2; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void disableButton(); + void setSprite(uint32 fileHash); +}; + +class AsScene3010DeadBolt : public AnimatedSprite { +public: + AsScene3010DeadBolt(NeverhoodEngine *vm, Scene *parentScene, int boltIndex, bool initUnlocked); + void setCountdown(int count); + void lock(); + void unlock(bool skipAnim); +protected: + Scene *_parentScene; + int _boltIndex; + int _countdown; + bool _soundToggle; + bool _unlocked; + bool _locked; + void update(); + uint32 hmAnimation(int messageNum, const MessageParam ¶m, Entity *sender); + void stIdle(); + void stIdleMessage(); + void stDisabled(); + void stDisabledMessage(); +}; + +class Scene3010 : public Scene { +public: + Scene3010(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + int _countdown; + bool _doorUnlocked; + bool _checkUnlocked; + SsScene3010DeadBoltButton *_ssDeadBoltButtons[3]; + AsScene3010DeadBolt *_asDeadBolts[3]; + bool _boltUnlocked[3]; + bool _boltUnlocking[3]; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +// Scene3011 + +class SsScene3011Button : public StaticSprite { +public: + SsScene3011Button(NeverhoodEngine *vm, Scene *parentScene, bool flag); +protected: + Scene *_parentScene; + int _countdown; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +class AsScene3011Symbol : public AnimatedSprite { +public: + AsScene3011Symbol(NeverhoodEngine *vm, int symbolIndex, bool largeSymbol); + void show(bool isNoisy); + void hide(); + void stopSymbolSound(); + void change(int symbolIndex, bool isNoisy); + int getSymbolIndex() { return _largeSymbol ? _symbolIndex : _symbolIndex - 12; } +protected: + bool _largeSymbol; + bool _isNoisy; + int _symbolIndex; +}; + +class Scene3011 : public Scene { +public: + Scene3011(NeverhoodEngine *vm, Module *parentModule, int which); +protected: + Sprite *_ssButton; + AsScene3011Symbol *_asSymbols[12]; + int _updateStatus; + bool _buttonClicked; + int _countdown; + int _noisySymbolIndex; + int _currentSymbolIndex; + int _noisyRandomSymbolIndex; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void fadeIn(); + void fadeOut(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MODULES_MODULE3000_H */ diff --git a/engines/neverhood/mouse.cpp b/engines/neverhood/mouse.cpp new file mode 100644 index 0000000000..632f56fb74 --- /dev/null +++ b/engines/neverhood/mouse.cpp @@ -0,0 +1,256 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/mouse.h" +#include "graphics/cursorman.h" + +namespace Neverhood { + +Mouse::Mouse(NeverhoodEngine *vm, uint32 fileHash, const NRect &mouseRect) + : StaticSprite(vm, 2000), _mouseType(kMouseType433), + _mouseCursorResource(vm), _frameNum(0) { + + _mouseRect = mouseRect; + init(fileHash); + if (_x >= _mouseRect.x1 && _x <= _mouseRect.x2 && + _y >= _mouseRect.y1 && _y <= _mouseRect.y2) { + _mouseCursorResource.setCursorNum(1); + } else { + _mouseCursorResource.setCursorNum(4); + } + updateCursor(); +} + +Mouse::Mouse(NeverhoodEngine *vm, uint32 fileHash, int16 x1, int16 x2) + : StaticSprite(vm, 2000), _mouseType(kMouseType435), + _mouseCursorResource(vm), _frameNum(0), _x1(x1), _x2(x2) { + + init(fileHash); + if (_x <= _x1) { + _mouseCursorResource.setCursorNum(6); + } else if (_x >= _x2) { + _mouseCursorResource.setCursorNum(5); + } else { + _mouseCursorResource.setCursorNum(4); + } + updateCursor(); +} + +Mouse::Mouse(NeverhoodEngine *vm, uint32 fileHash, int type) + : StaticSprite(vm, 2000), _mouseType(kMouseTypeNavigation), + _mouseCursorResource(vm), _type(type), _frameNum(0) { + + init(fileHash); + _mouseCursorResource.setCursorNum(0); +} + +Mouse::~Mouse() { + CursorMan.showMouse(false); +} + +void Mouse::init(uint32 fileHash) { + _mouseCursorResource.load(fileHash); + _x = _vm->getMouseX(); + _y = _vm->getMouseY(); + createSurface(2000, 32, 32); + SetUpdateHandler(&Mouse::update); + SetMessageHandler(&Mouse::handleMessage); + _drawOffset.x = 0; + _drawOffset.y = 0; + _drawOffset.width = 32; + _drawOffset.height = 32; + _collisionBoundsOffset = _drawOffset; + updateBounds(); + _needRefresh = true; + CursorMan.showMouse(false); +} + +void Mouse::load(uint32 fileHash) { + _mouseCursorResource.load(fileHash); + _needRefresh = true; +} + +void Mouse::update() { + if (CursorMan.isVisible() && !_surface->getVisible()) { + CursorMan.showMouse(false); + } else if (!CursorMan.isVisible() && _surface->getVisible()) { + CursorMan.showMouse(true); + } + updateCursor(); + _frameNum++; + if (_frameNum >= 6) + _frameNum = 0; + _needRefresh = _frameNum % 2 == 0; +} + +uint32 Mouse::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + debug(7, "Mouse::handleMessage(%04X)", messageNum); + uint32 messageResult = Sprite::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x2064: + _x = param.asPoint().x; + _y = param.asPoint().y; + switch (_type) { + case 1: + if (_x >= 320) + messageResult = 1; + else + messageResult = 0; + break; + case 2: + default: + if (_x < 100) + messageResult = 0; + else if (_x > 540) + messageResult = 1; + else + messageResult = 2; + break; + case 3: + if (_x < 100) + messageResult = 0; + else if (_x > 540) + messageResult = 1; + else + messageResult = 4; + break; + case 4: + if (_x < 100) + messageResult = 0; + else if (_x > 540) + messageResult = 1; + else if (_y >= 150) + messageResult = 2; + else + messageResult = 3; + break; + case 5: + if (_y >= 240) + messageResult = 4; + else + messageResult = 3; + break; + } + break; + case 0x4002: + _x = param.asPoint().x; + _y = param.asPoint().y; + updateCursorNum(); + updateBounds(); + break; + } + return messageResult; +} + +void Mouse::updateCursor() { + + if (!_surface) + return; + + if (_doDeltaX) { + _surface->getDrawRect().x = filterX(_x - _drawOffset.width - _drawOffset.x + 1); + } else { + _surface->getDrawRect().x = filterX(_x + _drawOffset.x); + } + + if (_doDeltaY) { + _surface->getDrawRect().y = filterY(_y - _drawOffset.height - _drawOffset.y + 1); + } else { + _surface->getDrawRect().y = filterY(_y + _drawOffset.y); + } + + if (_needRefresh) { + _needRefresh = false; + _drawOffset = _mouseCursorResource.getRect(); + _surface->drawMouseCursorResource(_mouseCursorResource, _frameNum / 2); + Graphics::Surface *cursorSurface = _surface->getSurface(); + CursorMan.replaceCursor((const byte*)cursorSurface->pixels, + cursorSurface->w, cursorSurface->h, -_drawOffset.x, -_drawOffset.y, 0); + } + +} + +void Mouse::updateCursorNum() { + switch (_mouseType) { + case kMouseType433: + if (_x >= _mouseRect.x1 && _x <= _mouseRect.x2 && + _y >= _mouseRect.y1 && _y <= _mouseRect.y2) { + _mouseCursorResource.setCursorNum(1); + } else { + _mouseCursorResource.setCursorNum(4); + } + break; + case kMouseType435: + if (_x <= _x1) { + _mouseCursorResource.setCursorNum(6); + } else if (_x >= _x2) { + _mouseCursorResource.setCursorNum(5); + } else { + _mouseCursorResource.setCursorNum(4); + } + break; + case kMouseTypeNavigation: + switch (_type) { + case 1: + if (_x >= 320) + _mouseCursorResource.setCursorNum(5); + else + _mouseCursorResource.setCursorNum(6); + break; + case 2: + default: + if (_x < 100) + _mouseCursorResource.setCursorNum(6); + else if (_x > 540) + _mouseCursorResource.setCursorNum(5); + else + _mouseCursorResource.setCursorNum(0); + break; + case 3: + if (_x < 100) + _mouseCursorResource.setCursorNum(1); + else if (_x > 540) + _mouseCursorResource.setCursorNum(1); + break; + case 4: + if (_x < 100) + _mouseCursorResource.setCursorNum(6); + else if (_x > 540) + _mouseCursorResource.setCursorNum(5); + else if (_y >= 150) + _mouseCursorResource.setCursorNum(0); + else + _mouseCursorResource.setCursorNum(3); + break; + case 5: + if (_y >= 240) + _mouseCursorResource.setCursorNum(2); + else + _mouseCursorResource.setCursorNum(3); + break; + } + break; + } + +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/mouse.h b/engines/neverhood/mouse.h new file mode 100644 index 0000000000..0b927de4df --- /dev/null +++ b/engines/neverhood/mouse.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 NEVERHOOD_MOUSE_H +#define NEVERHOOD_MOUSE_H + +#include "neverhood/neverhood.h" +#include "neverhood/sprite.h" +#include "neverhood/graphics.h" +#include "neverhood/resource.h" + +namespace Neverhood { + +enum MouseType { + kMouseType433, + kMouseType435, + kMouseTypeNavigation +}; + +class Mouse : public StaticSprite { +public: + Mouse(NeverhoodEngine *vm, uint32 fileHash, const NRect &mouseRect); + Mouse(NeverhoodEngine *vm, uint32 fileHash, int16 x1, int16 x2); + Mouse(NeverhoodEngine *vm, uint32 fileHash, int _type); + virtual ~Mouse(); + void load(uint32 fileHash); + void updateCursor(); +protected: + MouseType _mouseType; + MouseCursorResource _mouseCursorResource; + int _frameNum; + NRect _mouseRect; + int16 _x1; + int16 _x2; + int _type; + void init(uint32 fileHash); + void update(); + void updateCursorNum(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_MOUSE_H */ diff --git a/engines/neverhood/navigationscene.cpp b/engines/neverhood/navigationscene.cpp new file mode 100644 index 0000000000..d802322858 --- /dev/null +++ b/engines/neverhood/navigationscene.cpp @@ -0,0 +1,218 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 "neverhood/navigationscene.h" +#include "neverhood/mouse.h" + +namespace Neverhood { + +NavigationScene::NavigationScene(NeverhoodEngine *vm, Module *parentModule, uint32 navigationListId, int navigationIndex, const byte *itemsTypes) + : Scene(vm, parentModule), _itemsTypes(itemsTypes), _navigationIndex(navigationIndex), _smackerDone(false), + _isWalkingForward(false), _isTurning(false), _smackerFileHash(0), _interactive(true), _leaveSceneAfter(false) { + + _navigationList = _vm->_staticData->getNavigationList(navigationListId); + + if (_navigationIndex < 0) { + _navigationIndex = (int)getGlobalVar(V_NAVIGATION_INDEX); + if (_navigationIndex >= (int)_navigationList->size()) + _navigationIndex = 0; + } + setGlobalVar(V_NAVIGATION_INDEX, _navigationIndex); + + SetUpdateHandler(&NavigationScene::update); + SetMessageHandler(&NavigationScene::handleMessage); + + _smackerPlayer = new SmackerPlayer(_vm, this, (*_navigationList)[_navigationIndex].fileHash, true, true); + addEntity(_smackerPlayer); + addSurface(_smackerPlayer->getSurface()); + + createMouseCursor(); + + _vm->_screen->clear(); + _vm->_screen->setSmackerDecoder(_smackerPlayer->getSmackerDecoder()); + + sendMessage(_parentModule, 0x100A, _navigationIndex); + +} + +NavigationScene::~NavigationScene() { + _vm->_soundMan->setTwoSoundsPlayFlag(false); + _vm->_soundMan->setSoundThreePlayFlag(false); +} + +int NavigationScene::getNavigationAreaType() { + NPoint mousePos; + mousePos.x = _mouseCursor->getX(); + mousePos.y = _mouseCursor->getY(); + return sendPointMessage(_mouseCursor, 0x2064, mousePos); +} + +void NavigationScene::update() { + if (_smackerFileHash != 0) { + showMouse(false); + _smackerPlayer->open(_smackerFileHash, false); + _vm->_screen->clear(); + _vm->_screen->setSmackerDecoder(_smackerPlayer->getSmackerDecoder()); + _smackerDone = false; + /* + if (!_interactive) + _smackerDone = true; + */ + _smackerFileHash = 0; + } else if (_smackerDone) { + if (_leaveSceneAfter) { + _vm->_screen->setSmackerDecoder(NULL); + sendMessage(_parentModule, 0x1009, _navigationIndex); + } else { + const NavigationItem &navigationItem = (*_navigationList)[_navigationIndex]; + createMouseCursor(); + showMouse(true); + _isTurning = false; + _isWalkingForward = false; + _interactive = true; + _vm->_soundMan->setTwoSoundsPlayFlag(false); + _vm->_soundMan->setSoundThreePlayFlag(false); + _smackerDone = false; + _smackerPlayer->open(navigationItem.fileHash, true); + _vm->_screen->clear(); + _vm->_screen->setSmackerDecoder(_smackerPlayer->getSmackerDecoder()); + sendMessage(_parentModule, 0x100A, _navigationIndex); + } + } + Scene::update(); +} + +uint32 NavigationScene::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + switch (messageNum) { + case 0x0000: + if (_interactive) + sendMessage(_mouseCursor, 0x4002, param); + break; + case 0x0001: + if (_interactive) + handleNavigation(param.asPoint()); + break; + case 0x0009: + if (!_interactive) + _smackerDone = true; + break; + case 0x3002: + _smackerDone = true; + break; + } + return 0; +} + +void NavigationScene::createMouseCursor() { + + const NavigationItem &navigationItem = (*_navigationList)[_navigationIndex]; + uint32 mouseCursorFileHash; + int areaType; + + if (_mouseCursor) { + deleteSprite((Sprite**)&_mouseCursor); + } + + mouseCursorFileHash = navigationItem.mouseCursorFileHash; + if (mouseCursorFileHash == 0) + mouseCursorFileHash = 0x63A40028; + + if (_itemsTypes) { + areaType = _itemsTypes[_navigationIndex]; + } else if (navigationItem.middleSmackerFileHash != 0 || navigationItem.middleFlag) { + areaType = 0; + } else { + areaType = 1; + } + + insertNavigationMouse(mouseCursorFileHash, areaType); + sendPointMessage(_mouseCursor, 0x4002, _vm->getMousePos()); + +} + +void NavigationScene::handleNavigation(const NPoint &mousePos) { + + const NavigationItem &navigationItem = (*_navigationList)[_navigationIndex]; + bool oldIsWalkingForward = _isWalkingForward; + bool oldIsTurning = _isTurning; + uint32 direction = sendPointMessage(_mouseCursor, 0x2064, mousePos); + + switch (direction) { + case 0: + if (navigationItem.leftSmackerFileHash != 0) { + _smackerFileHash = navigationItem.leftSmackerFileHash; + _interactive = false; + _isWalkingForward = false; + _isTurning = true; + do { + _navigationIndex--; + if (_navigationIndex < 0) + _navigationIndex = _navigationList->size() - 1; + } while (!(*_navigationList)[_navigationIndex].interactive); + setGlobalVar(V_NAVIGATION_INDEX, _navigationIndex); + } else { + _vm->_screen->setSmackerDecoder(NULL); + sendMessage(_parentModule, 0x1009, _navigationIndex); + } + break; + case 1: + if (navigationItem.rightSmackerFileHash != 0) { + _smackerFileHash = navigationItem.rightSmackerFileHash; + _interactive = false; + _isWalkingForward = false; + _isTurning = true; + do { + _navigationIndex++; + if (_navigationIndex >= (int)_navigationList->size()) + _navigationIndex = 0; + } while (!(*_navigationList)[_navigationIndex].interactive); + setGlobalVar(V_NAVIGATION_INDEX, _navigationIndex); + } else { + _vm->_screen->setSmackerDecoder(NULL); + sendMessage(_parentModule, 0x1009, _navigationIndex); + } + break; + case 2: + case 3: + case 4: + if (navigationItem.middleFlag) { + _vm->_screen->setSmackerDecoder(NULL); + sendMessage(_parentModule, 0x1009, _navigationIndex); + } else if (navigationItem.middleSmackerFileHash != 0) { + _smackerFileHash = navigationItem.middleSmackerFileHash; + _interactive = false; + _isWalkingForward = true; + _isTurning = false; + _leaveSceneAfter = true; + } + break; + } + + if (oldIsTurning != _isTurning) + _vm->_soundMan->setSoundThreePlayFlag(_isTurning); + + if (oldIsWalkingForward != _isWalkingForward) + _vm->_soundMan->setTwoSoundsPlayFlag(_isWalkingForward); + +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/navigationscene.h b/engines/neverhood/navigationscene.h new file mode 100644 index 0000000000..ebe9a3597c --- /dev/null +++ b/engines/neverhood/navigationscene.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 NEVERHOOD_NAVIGATIONSCENE_H +#define NEVERHOOD_NAVIGATIONSCENE_H + +#include "neverhood/neverhood.h" +#include "neverhood/resourceman.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +class NavigationScene : public Scene { +public: + NavigationScene(NeverhoodEngine *vm, Module *parentModule, uint32 navigationListId, int navigationIndex, const byte *itemsTypes); + virtual ~NavigationScene(); + int getNavigationAreaType(); + int getNavigationIndex() const { return _navigationIndex; } + bool isWalkingForward() const { return _isWalkingForward; } + bool isTurning() const { return _isTurning; } + int getFrameNumber() const { return _smackerPlayer->getFrameNumber(); } +protected: + SmackerPlayer *_smackerPlayer; + bool _smackerDone; + NavigationList *_navigationList; + int _navigationIndex; + uint32 _smackerFileHash; + bool _interactive; + bool _isWalkingForward; + bool _isTurning; + bool _leaveSceneAfter; + const byte *_itemsTypes; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void createMouseCursor(); + void handleNavigation(const NPoint &mousePos); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_NAVIGATIONSCENE_H */ diff --git a/engines/neverhood/neverhood.cpp b/engines/neverhood/neverhood.cpp new file mode 100644 index 0000000000..475a459df4 --- /dev/null +++ b/engines/neverhood/neverhood.cpp @@ -0,0 +1,185 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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/file.h" +#include "common/config-manager.h" +#include "base/plugins.h" +#include "base/version.h" +#include "graphics/cursorman.h" +#include "engines/util.h" +#include "neverhood/neverhood.h" +#include "neverhood/blbarchive.h" +#include "neverhood/gamemodule.h" +#include "neverhood/gamevars.h" +#include "neverhood/graphics.h" +#include "neverhood/resourceman.h" +#include "neverhood/resource.h" +#include "neverhood/screen.h" +#include "neverhood/sound.h" +#include "neverhood/staticdata.h" + +namespace Neverhood { + +NeverhoodEngine::NeverhoodEngine(OSystem *syst, const NeverhoodGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc) { + // Setup mixer + if (!_mixer->isReady()) { + warning("Sound initialization failed."); + } + + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); + + _rnd = new Common::RandomSource("neverhood"); + +} + +NeverhoodEngine::~NeverhoodEngine() { + delete _rnd; +} + +Common::Error NeverhoodEngine::run() { + initGraphics(640, 480, true); + + _isSaveAllowed = false; + + _mouseX = 0; + _mouseY = 0; + + _gameState.sceneNum = 0; + _gameState.which = 0; + + _staticData = new StaticData(); + _staticData->load("neverhood.dat"); + _gameVars = new GameVars(); + _screen = new Screen(this); + _res = new ResourceMan(); + + if (isDemo()) { + _res->addArchive("a.blb"); + _res->addArchive("nevdemo.blb"); + } else { + _res->addArchive("a.blb"); + _res->addArchive("c.blb"); + _res->addArchive("hd.blb"); + _res->addArchive("i.blb"); + _res->addArchive("m.blb"); + _res->addArchive("s.blb"); + _res->addArchive("t.blb"); + } + + CursorMan.showMouse(true); + + _soundMan = new SoundMan(this); + _audioResourceMan = new AudioResourceMan(this); + + _gameModule = new GameModule(this); + + _isSaveAllowed = true; + + if (isDemo()) { + // Adjust this navigation list for the demo version + NavigationList *navigationList = _staticData->getNavigationList(0x004B67E8); + (*navigationList)[0].middleSmackerFileHash = 0; + (*navigationList)[0].middleFlag = 1; + (*navigationList)[2].middleSmackerFileHash = 0; + (*navigationList)[2].middleFlag = 1; + (*navigationList)[4].middleSmackerFileHash = 0; + (*navigationList)[4].middleFlag = 1; + (*navigationList)[5].middleSmackerFileHash = 0; + (*navigationList)[5].middleFlag = 1; + } + + if (ConfMan.hasKey("save_slot")) + loadGameState(ConfMan.getInt("save_slot")); + else + _gameModule->startup(); + + mainLoop(); + + delete _gameModule; + delete _soundMan; + delete _audioResourceMan; + + delete _res; + delete _screen; + + delete _gameVars; + delete _staticData; + + return Common::kNoError; +} + +void NeverhoodEngine::mainLoop() { + uint32 nextFrameTime = 0; + while (!shouldQuit()) { + Common::Event event; + Common::EventManager *eventMan = _system->getEventManager(); + while (eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + _gameModule->handleKeyDown(event.kbd.keycode); + _gameModule->handleAsciiKey(event.kbd.ascii); + break; + case Common::EVENT_KEYUP: + break; + case Common::EVENT_MOUSEMOVE: + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + _gameModule->handleMouseMove(event.mouse.x, event.mouse.y); + break; + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_RBUTTONDOWN: + _gameModule->handleMouseDown(event.mouse.x, event.mouse.y); + break; + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONUP: + _gameModule->handleMouseUp(event.mouse.x, event.mouse.y); + break; + case Common::EVENT_QUIT: + _system->quit(); + break; + default: + break; + } + } + if (_system->getMillis() >= nextFrameTime) { + _gameModule->checkRequests(); + _gameModule->handleUpdate(); + _gameModule->draw(); + _screen->update(); + nextFrameTime = _screen->getNextFrameTime(); + }; + _soundMan->update(); + _audioResourceMan->updateMusic(); + _system->updateScreen(); + _system->delayMillis(10); + } +} + +NPoint NeverhoodEngine::getMousePos() { + NPoint pt; + pt.x = _mouseX; + pt.y = _mouseY; + return pt; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/neverhood.h b/engines/neverhood/neverhood.h new file mode 100644 index 0000000000..577fbd7a66 --- /dev/null +++ b/engines/neverhood/neverhood.h @@ -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. + * + */ + +#ifndef NEVERHOOD_H +#define NEVERHOOD_H + +#include "common/scummsys.h" +#include "common/events.h" +#include "common/keyboard.h" +#include "common/random.h" +#include "common/savefile.h" +#include "common/str-array.h" +#include "common/system.h" +#include "audio/mixer.h" +#include "engines/engine.h" +#include "neverhood/messages.h" + +namespace Neverhood { + +enum NeverhoodGameFeatures { +}; + +struct NeverhoodGameDescription; + +class GameModule; +class GameVars; +class ResourceMan; +class Screen; +class SoundMan; +class AudioResourceMan; +class StaticData; +struct NPoint; + +struct GameState { + int sceneNum; + int which; +}; + +class NeverhoodEngine : public ::Engine { +protected: + + Common::Error run(); + void mainLoop(); + +public: + NeverhoodEngine(OSystem *syst, const NeverhoodGameDescription *gameDesc); + virtual ~NeverhoodEngine(); + + // Detection related functions + const NeverhoodGameDescription *_gameDescription; + const char *getGameId() const; + uint32 getFeatures() const; + uint16 getVersion() const; + Common::Platform getPlatform() const; + bool hasFeature(EngineFeature f) const; + bool isDemo() const; + Common::String getTargetName() { return _targetName; }; + + Common::RandomSource *_rnd; + + int16 _mouseX, _mouseY; + uint16 _buttonState; + + GameState _gameState; + GameVars *_gameVars; + Screen *_screen; + ResourceMan *_res; + GameModule *_gameModule; + StaticData *_staticData; + + SoundMan *_soundMan; + AudioResourceMan *_audioResourceMan; + +public: + + /* Save/load */ + + enum kReadSaveHeaderError { + kRSHENoError = 0, + kRSHEInvalidType = 1, + kRSHEInvalidVersion = 2, + kRSHEIoError = 3 + }; + + struct SaveHeader { + Common::String description; + uint32 version; + byte gameID; + uint32 flags; + uint32 saveDate; + uint32 saveTime; + uint32 playTime; + Graphics::Surface *thumbnail; + }; + + bool _isSaveAllowed; + + bool canLoadGameStateCurrently() { return _isSaveAllowed; } + bool canSaveGameStateCurrently() { return _isSaveAllowed; } + + Common::Error loadGameState(int slot); + Common::Error saveGameState(int slot, const Common::String &description); + Common::Error removeGameState(int slot); + void savegame(const char *filename, const char *description); + void loadgame(const char *filename); + const char *getSavegameFilename(int num); + static Common::String getSavegameFilename(const Common::String &target, int num); + static kReadSaveHeaderError readSaveHeader(Common::SeekableReadStream *in, bool loadThumbnail, SaveHeader &header); + + GameState& gameState() { return _gameState; } + GameModule *gameModule() { return _gameModule; } + int16 getMouseX() const { return _mouseX; } + int16 getMouseY() const { return _mouseY; } + NPoint getMousePos(); + +public: + +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_H */ diff --git a/engines/neverhood/palette.cpp b/engines/neverhood/palette.cpp new file mode 100644 index 0000000000..d4b9b67f53 --- /dev/null +++ b/engines/neverhood/palette.cpp @@ -0,0 +1,201 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/palette.h" +#include "neverhood/resource.h" +#include "neverhood/screen.h" + +namespace Neverhood { + +// Palette + +Palette::Palette(NeverhoodEngine *vm) : Entity(vm, 0) { + init(); + memset(_palette, 0, 1024); + SetUpdateHandler(&Palette::update); +} + +Palette::Palette(NeverhoodEngine *vm, byte *palette) : Entity(vm, 0) { + init(); + memcpy(_palette, palette, 1024); + SetUpdateHandler(&Palette::update); +} + +Palette::Palette(NeverhoodEngine *vm, const char *filename) : Entity(vm, 0) { + PaletteResource paletteResource(_vm); + init(); + paletteResource.load(calcHash(filename)); + paletteResource.copyPalette(_palette); + SetUpdateHandler(&Palette::update); +} + +Palette::Palette(NeverhoodEngine *vm, uint32 fileHash) : Entity(vm, 0) { + PaletteResource paletteResource(_vm); + init(); + paletteResource.load(fileHash); + paletteResource.copyPalette(_palette); + SetUpdateHandler(&Palette::update); +} + +Palette::~Palette() { + _vm->_screen->unsetPaletteData(_palette); + delete[] _palette; + delete[] _basePalette; +} + +void Palette::init() { + _status = 0; + _palette = new byte[1024]; + _basePalette = new byte[1024]; +} + +void Palette::usePalette() { + _vm->_screen->setPaletteData(_palette); +} + +void Palette::addPalette(const char *filename, int toIndex, int count, int fromIndex) { + addPalette(calcHash(filename), toIndex, count, fromIndex); +} + +void Palette::addPalette(uint32 fileHash, int toIndex, int count, int fromIndex) { + PaletteResource paletteResource(_vm); + if (toIndex + count > 256) + count = 256 - toIndex; + paletteResource.load(fileHash); + memcpy(_palette + toIndex * 4, paletteResource.palette() + fromIndex * 4, count * 4); + _vm->_screen->testPalette(_palette); +} + +void Palette::addBasePalette(uint32 fileHash, int toIndex, int count, int fromIndex) { + PaletteResource paletteResource(_vm); + if (toIndex + count > 256) + count = 256 - toIndex; + paletteResource.load(fileHash); + memcpy(_basePalette + toIndex * 4, paletteResource.palette() + fromIndex * 4, count * 4); +} + +void Palette::copyPalette(const byte *palette, int toIndex, int count, int fromIndex) { + if (toIndex + count > 256) + count = 256 - toIndex; + memcpy(_palette + toIndex * 4, palette + fromIndex * 4, count * 4); + _vm->_screen->testPalette(_palette); +} + +void Palette::copyBasePalette(int toIndex, int count, int fromIndex) { + if (toIndex + count > 256) + count = 256 - toIndex; + memcpy(_basePalette + toIndex * 4, _palette + fromIndex * 4, count * 4); +} + +void Palette::startFadeToBlack(int counter) { + debug(2, "Palette::startFadeToBlack(%d)", counter); + if (counter == 0) + counter = 1; + _fadeToR = 0; + _fadeToG = 0; + _fadeToB = 0; + _palCounter = counter; + _fadeStep = 255 / counter; + _status = 1; +} + +void Palette::startFadeToWhite(int counter) { + debug(2, "Palette::startFadeToWhite(%d)", counter); + if (counter == 0) + counter = 1; + _fadeToR = 255; + _fadeToG = 255; + _fadeToB = 255; + _palCounter = counter; + _fadeStep = 255 / counter; + _status = 1; +} + +void Palette::startFadeToPalette(int counter) { + debug(2, "Palette::startFadeToPalette(%d)", counter); + if (counter == 0) + counter = 1; + _palCounter = counter; + _fadeStep = 255 / counter; + _status = 2; +} + +void Palette::fillBaseWhite(int index, int count) { + if (index + count > 256) + count = 256 - index; + for (int i = 0; i < count; i++) { + _basePalette[(i + index) * 4 + 0] = 0xFF; + _basePalette[(i + index) * 4 + 1] = 0xFF; + _basePalette[(i + index) * 4 + 2] = 0xFF; + _basePalette[(i + index) * 4 + 3] = 0; + } +} + +void Palette::fillBaseBlack(int index, int count) { + if (index + count > 256) + count = 256 - index; + for (int i = 0; i < count; i++) { + _basePalette[(i + index) * 4 + 0] = 0; + _basePalette[(i + index) * 4 + 1] = 0; + _basePalette[(i + index) * 4 + 2] = 0; + _basePalette[(i + index) * 4 + 3] = 0; + } +} + +void Palette::copyToBasePalette(byte *palette) { + memcpy(_basePalette, palette, 256 * 4); +} + +void Palette::update() { + debug(2, "Palette::update() _status = %d", _status); + if (_status == 1) { + if (_palCounter > 1) { + for (int i = 0; i < 256; i++) + fadeColor(_palette + i * 4, _fadeToR, _fadeToG, _fadeToB); + _vm->_screen->testPalette(_palette); + _palCounter--; + } else { + memset(_palette, 0, 1024); + _status = 0; + } + } else if (_status == 2) { + if (_palCounter > 1) { + for (int i = 0; i < 256; i++) + fadeColor(_palette + i * 4, _basePalette[i * 4 + 0], _basePalette[i * 4 + 1], _basePalette[i * 4 + 2]); + _vm->_screen->testPalette(_palette); + _palCounter--; + } else { + memcpy(_palette, _basePalette, 256 * 4); + _status = 0; + } + } +} + +void Palette::fadeColor(byte *rgb, byte toR, byte toG, byte toB) { + #define FADE(color, toColor) color += _fadeStep < toColor - color ? _fadeStep : (-_fadeStep <= toColor - color ? toColor - color : -_fadeStep) + FADE(rgb[0], toR); + FADE(rgb[1], toG); + FADE(rgb[2], toB); + #undef FADE +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/palette.h b/engines/neverhood/palette.h new file mode 100644 index 0000000000..c83207caae --- /dev/null +++ b/engines/neverhood/palette.h @@ -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. + * + */ + +#ifndef NEVERHOOD_PALETTE_H +#define NEVERHOOD_PALETTE_H + +#include "neverhood/neverhood.h" +#include "neverhood/entity.h" + +namespace Neverhood { + +class Palette : public Entity { +public: + // Default constructor with black palette + Palette(NeverhoodEngine *vm); + // Create from existing palette + Palette(NeverhoodEngine *vm, byte *palette); + // Create from resource with filename + Palette(NeverhoodEngine *vm, const char *filename); + // Create from resource with fileHash + Palette(NeverhoodEngine *vm, uint32 fileHash); + virtual ~Palette(); + void init(); + void usePalette(); + void addPalette(const char *filename, int toIndex, int count, int fromIndex); + void addPalette(uint32 fileHash, int toIndex, int count, int fromIndex); + void addBasePalette(uint32 fileHash, int toIndex, int count, int fromIndex); + void copyPalette(const byte *palette, int toIndex, int count, int fromIndex); + void copyBasePalette(int toIndex, int count, int fromIndex); + void startFadeToBlack(int counter); + void startFadeToWhite(int counter); + void startFadeToPalette(int counter); + void fillBaseWhite(int index, int count); + void fillBaseBlack(int index, int count); + void copyToBasePalette(byte *palette); +protected: + int _status; + byte *_palette; + byte *_basePalette; + int _palCounter; + byte _fadeToR, _fadeToG, _fadeToB; + int _fadeStep; + void update(); + void fadeColor(byte *rgb, byte toR, byte toG, byte toB); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_PALETTE_H */ diff --git a/engines/neverhood/resource.cpp b/engines/neverhood/resource.cpp new file mode 100644 index 0000000000..442713196e --- /dev/null +++ b/engines/neverhood/resource.cpp @@ -0,0 +1,583 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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/algorithm.h" +#include "common/memstream.h" +#include "neverhood/resource.h" +#include "neverhood/resourceman.h" + +namespace Neverhood { + +// SpriteResource + +SpriteResource::SpriteResource(NeverhoodEngine *vm) + : _vm(vm), _pixels(NULL) { +} + +SpriteResource::~SpriteResource() { + unload(); +} + +void SpriteResource::draw(Graphics::Surface *destSurface, bool flipX, bool flipY) { + if (_pixels) { + byte *dest = (byte*)destSurface->pixels; + const int destPitch = destSurface->pitch; + if (_rle) + unpackSpriteRle(_pixels, _dimensions.width, _dimensions.height, dest, destPitch, flipX, flipY); + else + unpackSpriteNormal(_pixels, _dimensions.width, _dimensions.height, dest, destPitch, flipX, flipY); + } +} + +bool SpriteResource::load(uint32 fileHash, bool doLoadPosition) { + debug(2, "SpriteResource::load(%08X)", fileHash); + unload(); + _vm->_res->queryResource(fileHash, _resourceHandle); + if (_resourceHandle.isValid() && _resourceHandle.type() == kResTypeBitmap) { + _vm->_res->loadResource(_resourceHandle); + const byte *spriteData = _resourceHandle.data(); + NPoint *position = doLoadPosition ? &_position : NULL; + parseBitmapResource(spriteData, &_rle, &_dimensions, position, NULL, &_pixels); + } + return _pixels != NULL; +} + +void SpriteResource::unload() { + _vm->_res->unloadResource(_resourceHandle); + _pixels = NULL; + _rle = false; +} + +// PaletteResource + +PaletteResource::PaletteResource(NeverhoodEngine *vm) + : _vm(vm), _palette(NULL) { +} + +PaletteResource::~PaletteResource() { + unload(); +} + +bool PaletteResource::load(uint32 fileHash) { + debug(2, "PaletteResource::load(%08X)", fileHash); + unload(); + _vm->_res->queryResource(fileHash, _resourceHandle); + if (_resourceHandle.isValid() && + (_resourceHandle.type() == kResTypeBitmap || _resourceHandle.type() == kResTypePalette)) { + _vm->_res->loadResource(_resourceHandle); + _palette = _resourceHandle.data(); + // Check if the palette is stored in a bitmap + if (_resourceHandle.type() == kResTypeBitmap) + parseBitmapResource(_palette, NULL, NULL, NULL, &_palette, NULL); + + } + return _palette != NULL; +} + +void PaletteResource::unload() { + _vm->_res->unloadResource(_resourceHandle); + _palette = NULL; +} + +void PaletteResource::copyPalette(byte *destPalette) { + if (_palette) + memcpy(destPalette, _palette, 1024); +} + +// AnimResource + +AnimResource::AnimResource(NeverhoodEngine *vm) + : _vm(vm), _width(0), _height(0), _currSpriteData(NULL), _fileHash(0), _paletteData(NULL), + _spriteData(NULL), _replEnabled(false), _replOldColor(0), _replNewColor(0) { +} + +AnimResource::~AnimResource() { + unload(); +} + +void AnimResource::draw(uint frameIndex, Graphics::Surface *destSurface, bool flipX, bool flipY) { + const AnimFrameInfo frameInfo = _frames[frameIndex]; + byte *dest = (byte*)destSurface->pixels; + const int destPitch = destSurface->pitch; + _currSpriteData = _spriteData + frameInfo.spriteDataOffs; + _width = frameInfo.drawOffset.width; + _height = frameInfo.drawOffset.height; + if (_replEnabled && _replOldColor != _replNewColor) + unpackSpriteRle(_currSpriteData, _width, _height, dest, destPitch, flipX, flipY, _replOldColor, _replNewColor); + else + unpackSpriteRle(_currSpriteData, _width, _height, dest, destPitch, flipX, flipY); +} + +bool AnimResource::load(uint32 fileHash) { + debug(2, "AnimResource::load(%08X)", fileHash); + + if (fileHash == _fileHash) + return true; + + unload(); + + _vm->_res->queryResource(fileHash, _resourceHandle); + if (!_resourceHandle.isValid() || _resourceHandle.type() != kResTypeAnimation) + return false; + + const byte *resourceData, *animList, *frameList; + uint16 animInfoStartOfs, animListIndex, animListCount; + uint16 frameListStartOfs, frameCount; + uint32 spriteDataOfs, paletteDataOfs; + + _vm->_res->loadResource(_resourceHandle); + resourceData = _resourceHandle.data(); + + animListCount = READ_LE_UINT16(resourceData); + animInfoStartOfs = READ_LE_UINT16(resourceData + 2); + spriteDataOfs = READ_LE_UINT32(resourceData + 4); + paletteDataOfs = READ_LE_UINT32(resourceData + 8); + + animList = resourceData + 12; + for (animListIndex = 0; animListIndex < animListCount; animListIndex++) { + debug(8, "hash: %08X", READ_LE_UINT32(animList)); + if (READ_LE_UINT32(animList) == fileHash) + break; + animList += 8; + } + + if (animListIndex >= animListCount) { + _vm->_res->unloadResource(_resourceHandle); + return false; + } + + _spriteData = resourceData + spriteDataOfs; + if (paletteDataOfs > 0) + _paletteData = resourceData + paletteDataOfs; + + frameCount = READ_LE_UINT16(animList + 4); + frameListStartOfs = READ_LE_UINT16(animList + 6); + + debug(8, "frameCount = %d; frameListStartOfs = %04X; animInfoStartOfs = %04X", frameCount, frameListStartOfs, animInfoStartOfs); + + frameList = resourceData + animInfoStartOfs + frameListStartOfs; + + _frames.clear(); + _frames.reserve(frameCount); + + for (uint16 frameIndex = 0; frameIndex < frameCount; frameIndex++) { + AnimFrameInfo frameInfo; + frameInfo.frameHash = READ_LE_UINT32(frameList); + frameInfo.counter = READ_LE_UINT16(frameList + 4); + frameInfo.drawOffset.x = READ_LE_UINT16(frameList + 6); + frameInfo.drawOffset.y = READ_LE_UINT16(frameList + 8); + frameInfo.drawOffset.width = READ_LE_UINT16(frameList + 10); + frameInfo.drawOffset.height = READ_LE_UINT16(frameList + 12); + frameInfo.deltaX = READ_LE_UINT16(frameList + 14); + frameInfo.deltaY = READ_LE_UINT16(frameList + 16); + frameInfo.collisionBoundsOffset.x = READ_LE_UINT16(frameList + 18); + frameInfo.collisionBoundsOffset.y = READ_LE_UINT16(frameList + 20); + frameInfo.collisionBoundsOffset.width = READ_LE_UINT16(frameList + 22); + frameInfo.collisionBoundsOffset.height = READ_LE_UINT16(frameList + 24); + frameInfo.spriteDataOffs = READ_LE_UINT32(frameList + 28); + debug(8, "frameHash = %08X; counter = %d; rect = (%d,%d,%d,%d); deltaX = %d; deltaY = %d; collisionBoundsOffset = (%d,%d,%d,%d); spriteDataOffs = %08X", + frameInfo.frameHash, frameInfo.counter, + frameInfo.drawOffset.x, frameInfo.drawOffset.y, frameInfo.drawOffset.width, frameInfo.drawOffset.height, + frameInfo.deltaX, frameInfo.deltaY, + frameInfo.collisionBoundsOffset.x, frameInfo.collisionBoundsOffset.y, frameInfo.collisionBoundsOffset.width, frameInfo.collisionBoundsOffset.height, + frameInfo.spriteDataOffs); + frameList += 32; + _frames.push_back(frameInfo); + } + + _fileHash = fileHash; + + return true; + +} + +void AnimResource::unload() { + _vm->_res->unloadResource(_resourceHandle); + _currSpriteData = NULL; + _fileHash = 0; + _paletteData = NULL; + _spriteData = NULL; + _replEnabled = true; + _replOldColor = 0; + _replNewColor = 0; +} + +int16 AnimResource::getFrameIndex(uint32 frameHash) { + int16 frameIndex = -1; + for (uint i = 0; i < _frames.size(); i++) + if (_frames[i].frameHash == frameHash) { + frameIndex = (int16)i; + break; + } + debug(2, "AnimResource::getFrameIndex(%08X) -> %d", frameHash, frameIndex); + return frameIndex; +} + +void AnimResource::setRepl(byte oldColor, byte newColor) { + _replOldColor = oldColor; + _replNewColor = newColor; +} + +NDimensions AnimResource::loadSpriteDimensions(uint32 fileHash) { + ResourceHandle resourceHandle; + NDimensions dimensions; + _vm->_res->queryResource(fileHash, resourceHandle); + const byte *resDimensions = resourceHandle.extData(); + if (resDimensions) { + dimensions.width = READ_LE_UINT16(resDimensions + 0); + dimensions.height = READ_LE_UINT16(resDimensions + 2); + } else { + dimensions.width = 0; + dimensions.height = 0; + } + return dimensions; +} + +// MouseCursorResource + +MouseCursorResource::MouseCursorResource(NeverhoodEngine *vm) + : _cursorSprite(vm), _cursorNum(4), _currFileHash(0) { + + _rect.width = 32; + _rect.height = 32; +} + +void MouseCursorResource::load(uint32 fileHash) { + if (_currFileHash != fileHash) { + if (_cursorSprite.load(fileHash) && !_cursorSprite.isRle() && + _cursorSprite.getDimensions().width == 96 && _cursorSprite.getDimensions().height == 224) { + _currFileHash = fileHash; + } else { + unload(); + } + } +} + +void MouseCursorResource::unload() { + _cursorSprite.unload(); + _currFileHash = 0; + _cursorNum = 4; +} + +NDrawRect& MouseCursorResource::getRect() { + static const NPoint kCursorHotSpots[] = { + {-15, -5}, + {-17, -25}, + {-17, -30}, + {-14, -1}, + {-3, -7}, + {-30, -18}, + {-1, -18} + }; + _rect.x = kCursorHotSpots[_cursorNum].x; + _rect.y = kCursorHotSpots[_cursorNum].y; + return _rect; +} + +void MouseCursorResource::draw(int frameNum, Graphics::Surface *destSurface) { + if (_cursorSprite.getPixels()) { + const int sourcePitch = (_cursorSprite.getDimensions().width + 3) & 0xFFFC; // 4 byte alignment + const int destPitch = destSurface->pitch; + const byte *source = _cursorSprite.getPixels() + _cursorNum * (sourcePitch * 32) + frameNum * 32; + byte *dest = (byte*)destSurface->pixels; + for (int16 yc = 0; yc < 32; yc++) { + memcpy(dest, source, 32); + source += sourcePitch; + dest += destPitch; + } + } +} + +// TextResource + +TextResource::TextResource(NeverhoodEngine *vm) + : _vm(vm), _textData(NULL), _count(0) { + +} + +TextResource::~TextResource() { + unload(); +} + +void TextResource::load(uint32 fileHash) { + debug(2, "TextResource::load(%08X)", fileHash); + unload(); + _vm->_res->queryResource(fileHash, _resourceHandle); + if (_resourceHandle.isValid() && _resourceHandle.type() == kResTypeText) { + _vm->_res->loadResource(_resourceHandle); + _textData = _resourceHandle.data(); + _count = READ_LE_UINT32(_textData); + } +} + +void TextResource::unload() { + _vm->_res->unloadResource(_resourceHandle); + _textData = NULL; + _count = 0; +} + +const char *TextResource::getString(uint index, const char *&textEnd) { + const char *textStart = (const char*)(_textData + 4 + _count * 4 + READ_LE_UINT32(_textData + (index + 1) * 4)); + textEnd = (const char*)(_textData + 4 + _count * 4 + READ_LE_UINT32(_textData + (index + 2) * 4)); + return textStart; +} + +// DataResource + +DataResource::DataResource(NeverhoodEngine *vm) + : _vm(vm) { +} + +DataResource::~DataResource() { + unload(); +} + +void DataResource::load(uint32 fileHash) { + if (_resourceHandle.fileHash() == fileHash) + return; + const byte *data = NULL; + uint32 dataSize = 0; + unload(); + _vm->_res->queryResource(fileHash, _resourceHandle); + if (_resourceHandle.isValid() && _resourceHandle.type() == kResTypeData) { + _vm->_res->loadResource(_resourceHandle); + data = _resourceHandle.data(); + dataSize = _resourceHandle.size(); + } + if (data && dataSize) { + Common::MemoryReadStream dataS(data, dataSize); + uint itemCount = dataS.readUint16LE(); + uint32 itemStartOffs = 2 + itemCount * 8; + debug(2, "itemCount = %d", itemCount); + for (uint i = 0; i < itemCount; i++) { + dataS.seek(2 + i * 8); + DRDirectoryItem drDirectoryItem; + drDirectoryItem.nameHash = dataS.readUint32LE(); + drDirectoryItem.offset = dataS.readUint16LE(); + drDirectoryItem.type = dataS.readUint16LE(); + debug(2, "%03d nameHash = %08X; offset = %04X; type = %d", i, drDirectoryItem.nameHash, drDirectoryItem.offset, drDirectoryItem.type); + dataS.seek(itemStartOffs + drDirectoryItem.offset); + switch (drDirectoryItem.type) { + case 1: + { + debug(3, "NPoint"); + NPoint point; + point.x = dataS.readUint16LE(); + point.y = dataS.readUint16LE(); + debug(3, "(%d, %d)", point.x, point.y); + drDirectoryItem.offset = _points.size(); + _points.push_back(point); + break; + } + case 2: + { + uint count = dataS.readUint16LE(); + NPointArray *pointArray = new NPointArray(); + debug(3, "NPointArray; count = %d", count); + for (uint j = 0; j < count; j++) { + NPoint point; + point.x = dataS.readUint16LE(); + point.y = dataS.readUint16LE(); + debug(3, "(%d, %d)", point.x, point.y); + pointArray->push_back(point); + } + drDirectoryItem.offset = _pointArrays.size(); + _pointArrays.push_back(pointArray); + break; + } + case 3: + { + uint count = dataS.readUint16LE(); + HitRectList *hitRectList = new HitRectList(); + debug(3, "HitRectList; count = %d", count); + for (uint j = 0; j < count; j++) { + HitRect hitRect; + hitRect.rect.x1 = dataS.readUint16LE(); + hitRect.rect.y1 = dataS.readUint16LE(); + hitRect.rect.x2 = dataS.readUint16LE(); + hitRect.rect.y2 = dataS.readUint16LE(); + hitRect.type = dataS.readUint16LE() + 0x5001; + debug(3, "(%d, %d, %d, %d) -> %04d", hitRect.rect.x1, hitRect.rect.y1, hitRect.rect.x2, hitRect.rect.y2, hitRect.type); + hitRectList->push_back(hitRect); + } + drDirectoryItem.offset = _hitRectLists.size(); + _hitRectLists.push_back(hitRectList); + break; + } + case 4: + { + uint count = dataS.readUint16LE(); + MessageList *messageList = new MessageList(); + debug(3, "MessageList; count = %d", count); + for (uint j = 0; j < count; j++) { + MessageItem messageItem; + messageItem.messageNum = dataS.readUint32LE(); + messageItem.messageValue = dataS.readUint32LE(); + debug(3, "(%08X, %08X)", messageItem.messageNum, messageItem.messageValue); + messageList->push_back(messageItem); + } + drDirectoryItem.offset = _messageLists.size(); + _messageLists.push_back(messageList); + break; + } + case 5: + { + uint count = dataS.readUint16LE(); + DRSubRectList *drSubRectList = new DRSubRectList(); + debug(3, "SubRectList; count = %d", count); + for (uint j = 0; j < count; j++) { + DRSubRect drSubRect; + drSubRect.rect.x1 = dataS.readUint16LE(); + drSubRect.rect.y1 = dataS.readUint16LE(); + drSubRect.rect.x2 = dataS.readUint16LE(); + drSubRect.rect.y2 = dataS.readUint16LE(); + drSubRect.messageListHash = dataS.readUint32LE(); + drSubRect.messageListItemIndex = dataS.readUint16LE(); + debug(3, "(%d, %d, %d, %d) -> %08X (%d)", drSubRect.rect.x1, drSubRect.rect.y1, drSubRect.rect.x2, drSubRect.rect.y2, drSubRect.messageListHash, drSubRect.messageListItemIndex); + drSubRectList->push_back(drSubRect); + } + drDirectoryItem.offset = _drSubRectLists.size(); + _drSubRectLists.push_back(drSubRectList); + break; + } + case 6: + { + DRRect drRect; + drRect.rect.x1 = dataS.readUint16LE(); + drRect.rect.y1 = dataS.readUint16LE(); + drRect.rect.x2 = dataS.readUint16LE(); + drRect.rect.y2 = dataS.readUint16LE(); + drRect.subRectIndex = dataS.readUint16LE(); + debug(3, "(%d, %d, %d, %d) -> %d", drRect.rect.x1, drRect.rect.y1, drRect.rect.x2, drRect.rect.y2, drRect.subRectIndex); + drDirectoryItem.offset = _drRects.size(); + _drRects.push_back(drRect); + break; + } + case 7: + { + uint count = dataS.readUint16LE(); + NRectArray *rectArray = new NRectArray(); + debug(3, "NRectArray; count = %d", count); + for (uint j = 0; j < count; j++) { + NRect rect; + rect.x1 = dataS.readUint16LE(); + rect.y1 = dataS.readUint16LE(); + rect.x2 = dataS.readUint16LE(); + rect.y2 = dataS.readUint16LE(); + debug(3, "(%d, %d, %d, %d)", rect.x1, rect.y1, rect.x2, rect.y2); + rectArray->push_back(rect); + } + drDirectoryItem.offset = _rectArrays.size(); + _rectArrays.push_back(rectArray); + break; + } + } + _directory.push_back(drDirectoryItem); + } + } +} + +void DataResource::unload() { + _directory.clear(); + _points.clear(); + for (Common::Array<NPointArray*>::iterator it = _pointArrays.begin(); it != _pointArrays.end(); ++it) + delete (*it); + _pointArrays.clear(); + for (Common::Array<NRectArray*>::iterator it = _rectArrays.begin(); it != _rectArrays.end(); ++it) + delete (*it); + _rectArrays.clear(); + for (Common::Array<HitRectList*>::iterator it = _hitRectLists.begin(); it != _hitRectLists.end(); ++it) + delete (*it); + _hitRectLists.clear(); + for (Common::Array<MessageList*>::iterator it = _messageLists.begin(); it != _messageLists.end(); ++it) + delete (*it); + _messageLists.clear(); + _drRects.clear(); + for (Common::Array<DRSubRectList*>::iterator it = _drSubRectLists.begin(); it != _drSubRectLists.end(); ++it) + delete (*it); + _drSubRectLists.clear(); + _vm->_res->unloadResource(_resourceHandle); +} + +NPoint DataResource::getPoint(uint32 nameHash) { + DataResource::DRDirectoryItem *drDirectoryItem = findDRDirectoryItem(nameHash, 1); + return drDirectoryItem ? _points[drDirectoryItem->offset] : NPoint(); +} + +NPointArray *DataResource::getPointArray(uint32 nameHash) { + DataResource::DRDirectoryItem *drDirectoryItem = findDRDirectoryItem(nameHash, 2); + return drDirectoryItem ? _pointArrays[drDirectoryItem->offset] : NULL; +} + +NRectArray *DataResource::getRectArray(uint32 nameHash) { + DataResource::DRDirectoryItem *drDirectoryItem = findDRDirectoryItem(nameHash, 3); + return drDirectoryItem ? _rectArrays[drDirectoryItem->offset] : NULL; +} + +HitRectList *DataResource::getHitRectList() { + DataResource::DRDirectoryItem *drDirectoryItem = findDRDirectoryItem(calcHash("HitArray"), 3); + return drDirectoryItem ? _hitRectLists[drDirectoryItem->offset] : NULL; +} + +MessageList *DataResource::getMessageListAtPos(int16 klaymenX, int16 klaymenY, int16 mouseX, int16 mouseY) { + for (uint i = 0; i < _drRects.size(); i++) { + if (klaymenX >= _drRects[i].rect.x1 && klaymenX <= _drRects[i].rect.x2 && + klaymenY >= _drRects[i].rect.y1 && klaymenY <= _drRects[i].rect.y2) { + DRSubRectList *drSubRectList = _drSubRectLists[_drRects[i].subRectIndex]; + for (uint j = 0; j < drSubRectList->size(); j++) { + DRSubRect &subRect = (*drSubRectList)[j]; + if (mouseX >= subRect.rect.x1 && mouseX <= subRect.rect.x2 && + mouseY >= subRect.rect.y1 && mouseY <= subRect.rect.y2) { + return _messageLists[subRect.messageListItemIndex]; + } + } + } + } + return NULL; +} + +DataResource::DRDirectoryItem *DataResource::findDRDirectoryItem(uint32 nameHash, uint16 type) { + for (Common::Array<DRDirectoryItem>::iterator it = _directory.begin(); it != _directory.end(); it++) + if ((*it).nameHash == nameHash && (*it).type == type) + return &(*it); + return NULL; +} + +uint32 calcHash(const char *value) { + uint32 hash = 0, shiftValue = 0; + while (*value != 0) { + char ch = *value++; + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) { + if (ch >= 'a' && ch <= 'z') + ch -= 32; + else if (ch >= '0' && ch <= '9') + ch += 22; + shiftValue += ch - 64; + if (shiftValue >= 32) + shiftValue -= 32; + hash ^= 1 << shiftValue; + } + } + return hash; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/resource.h b/engines/neverhood/resource.h new file mode 100644 index 0000000000..6a84a69ecd --- /dev/null +++ b/engines/neverhood/resource.h @@ -0,0 +1,205 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_RESOURCE_H +#define NEVERHOOD_RESOURCE_H + +#include "common/str.h" +#include "neverhood/neverhood.h" +#include "neverhood/graphics.h" +#include "neverhood/staticdata.h" +#include "neverhood/resourceman.h" + +namespace Neverhood { + +enum { + kResTypeBitmap = 2, + kResTypePalette = 3, + kResTypeAnimation = 4, + kResTypeData = 5, + kResTypeText = 6, + kResTypeSound = 7, + kResTypeMusic = 8, + kResTypeVideo = 10 +}; + +class SpriteResource { +public: + SpriteResource(NeverhoodEngine *vm); + ~SpriteResource(); + void draw(Graphics::Surface *destSurface, bool flipX, bool flipY); + bool load(uint32 fileHash, bool doLoadPosition = false); + void unload(); + const NDimensions& getDimensions() { return _dimensions; } + NPoint& getPosition() { return _position; } + bool isRle() const { return _rle; } + const byte *getPixels() const { return _pixels; } +protected: + NeverhoodEngine *_vm; + ResourceHandle _resourceHandle; + NDimensions _dimensions; + NPoint _position; + const byte *_pixels; + bool _rle; +}; + +class PaletteResource { +public: + PaletteResource(NeverhoodEngine *vm); + ~PaletteResource(); + bool load(uint32 fileHash); + void unload(); + void copyPalette(byte *destPalette); + const byte *palette() { return _palette; } +protected: + NeverhoodEngine *_vm; + ResourceHandle _resourceHandle; + const byte *_palette; +}; + +struct AnimFrameInfo { + uint32 frameHash; + int16 counter; + NDrawRect drawOffset; + int16 deltaX, deltaY; + NDrawRect collisionBoundsOffset; + uint32 spriteDataOffs; +}; + +class AnimResource { +public: + AnimResource(NeverhoodEngine *vm); + ~AnimResource(); + void draw(uint frameIndex, Graphics::Surface *destSurface, bool flipX, bool flipY); + bool load(uint32 fileHash); + void unload(); + void clear(); + uint getFrameCount() const { return _frames.size(); } + const AnimFrameInfo& getFrameInfo(int16 index) const { return _frames[index]; } + int16 getFrameIndex(uint32 frameHash); + void setReplEnabled(bool value) { _replEnabled = value; } + void setRepl(byte oldColor, byte newColor); + NDimensions loadSpriteDimensions(uint32 fileHash); +protected: + NeverhoodEngine *_vm; + ResourceHandle _resourceHandle; + int16 _width, _height; + const byte *_currSpriteData; + uint32 _fileHash; + const byte *_paletteData; + const byte *_spriteData; + bool _replEnabled; + byte _replOldColor; + byte _replNewColor; + Common::Array<AnimFrameInfo> _frames; +}; + +class MouseCursorResource { +public: + MouseCursorResource(NeverhoodEngine *vm); + void load(uint32 fileHash); + void unload(); + NDrawRect& getRect(); + void draw(int frameNum, Graphics::Surface *destSurface); + int getCursorNum() const { return _cursorNum; } + void setCursorNum(int cursorNum) { _cursorNum = cursorNum; } +protected: + int _cursorNum; + SpriteResource _cursorSprite; + NDrawRect _rect; + uint32 _currFileHash; +}; + +class TextResource { +public: + TextResource(NeverhoodEngine *vm); + ~TextResource(); + void load(uint32 fileHash); + void unload(); + const char *getString(uint index, const char *&textEnd); + uint getCount() const { return _count;} +protected: + NeverhoodEngine *_vm; + ResourceHandle _resourceHandle; + const byte *_textData; + uint _count; +}; + +/* DataResource + 1 Single NPoint + 2 Array of NPoints + 3 Array of NRects + 4 MessageList + 5 SubRectList + 6 RectList +*/ + +class DataResource { +public: + DataResource(NeverhoodEngine *vm); + ~DataResource(); + void load(uint32 fileHash); + void unload(); + NPoint getPoint(uint32 nameHash); + NPointArray *getPointArray(uint32 nameHash); + NRectArray *getRectArray(uint32 nameHash); + HitRectList *getHitRectList(); + MessageList *getMessageListAtPos(int16 klaymenX, int16 klaymenY, int16 mouseX, int16 mouseY); +protected: + + struct DRDirectoryItem { + uint32 nameHash; + uint16 offset; + uint16 type; + }; + + struct DRRect { + NRect rect; + uint16 subRectIndex; + }; + + struct DRSubRect { + NRect rect; + uint32 messageListHash; + uint16 messageListItemIndex; + }; + + typedef Common::Array<DRSubRect> DRSubRectList; + + NeverhoodEngine *_vm; + ResourceHandle _resourceHandle; + Common::Array<DRDirectoryItem> _directory; + Common::Array<NPoint> _points; + Common::Array<NPointArray*> _pointArrays; + Common::Array<NRectArray*> _rectArrays; + Common::Array<HitRectList*> _hitRectLists; + Common::Array<MessageList*> _messageLists; + Common::Array<DRRect> _drRects; + Common::Array<DRSubRectList*> _drSubRectLists; + DataResource::DRDirectoryItem *findDRDirectoryItem(uint32 nameHash, uint16 type); +}; + +uint32 calcHash(const char *value); + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_RESOURCE_H */ diff --git a/engines/neverhood/resourceman.cpp b/engines/neverhood/resourceman.cpp new file mode 100644 index 0000000000..d5e7786c17 --- /dev/null +++ b/engines/neverhood/resourceman.cpp @@ -0,0 +1,128 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 "neverhood/resourceman.h" + +namespace Neverhood { + +ResourceHandle::ResourceHandle() + : _resourceFileEntry(NULL), _data(NULL) { +} + +ResourceHandle::~ResourceHandle() { +} + +ResourceMan::ResourceMan() { +} + +ResourceMan::~ResourceMan() { +} + +void ResourceMan::addArchive(const Common::String &filename) { + BlbArchive *archive = new BlbArchive(); + archive->open(filename); + _archives.push_back(archive); + debug(3, "ResourceMan::addArchive(%s) %d files", filename.c_str(), archive->getCount()); + for (uint archiveEntryIndex = 0; archiveEntryIndex < archive->getCount(); archiveEntryIndex++) { + BlbArchiveEntry *archiveEntry = archive->getEntry(archiveEntryIndex); + ResourceFileEntry *entry = findEntrySimple(archiveEntry->fileHash); + if (entry) { + if (archiveEntry->timeStamp > entry->archiveEntry->timeStamp) { + entry->archive = archive; + entry->archiveEntry = archiveEntry; + } + } else { + ResourceFileEntry newEntry; + newEntry.resourceHandle = -1; + newEntry.archive = archive; + newEntry.archiveEntry = archiveEntry; + _entries[archiveEntry->fileHash] = newEntry; + } + } +} + +ResourceFileEntry *ResourceMan::findEntrySimple(uint32 fileHash) { + EntriesMap::iterator p = _entries.find(fileHash); + return p != _entries.end() ? &(*p)._value : NULL; +} + +ResourceFileEntry *ResourceMan::findEntry(uint32 fileHash, ResourceFileEntry **firstEntry) { + ResourceFileEntry *entry = findEntrySimple(fileHash); + if (firstEntry) + *firstEntry = entry; + for (; entry && entry->archiveEntry->comprType == 0x65; fileHash = entry->archiveEntry->diskSize) + entry = findEntrySimple(fileHash); + return entry; +} + +Common::SeekableReadStream *ResourceMan::createStream(uint32 fileHash) { + ResourceFileEntry *entry = findEntry(fileHash); + return entry ? entry->archive->createStream(entry->archiveEntry) : NULL; +} + +void ResourceMan::queryResource(uint32 fileHash, ResourceHandle &resourceHandle) { + ResourceFileEntry *firstEntry; + resourceHandle._resourceFileEntry = findEntry(fileHash, &firstEntry); + resourceHandle._extData = firstEntry ? firstEntry->archiveEntry->extData : NULL; +} + +void ResourceMan::loadResource(ResourceHandle &resourceHandle) { + resourceHandle._data = NULL; + if (resourceHandle.isValid()) { + const uint32 fileHash = resourceHandle.fileHash(); + ResourceData *resourceData = _data[fileHash]; + if (!resourceData) { + resourceData = new ResourceData(); + _data[fileHash] = resourceData; + } + if (resourceData->data != NULL) { + resourceData->dataRefCount++; + } else { + resourceData->data = new byte[resourceHandle._resourceFileEntry->archiveEntry->size]; + resourceHandle._resourceFileEntry->archive->load(resourceHandle._resourceFileEntry->archiveEntry, resourceData->data, 0); + resourceData->dataRefCount = 1; + } + resourceHandle._data = resourceData->data; + } +} + +void ResourceMan::unloadResource(ResourceHandle &resourceHandle) { + if (resourceHandle.isValid()) { + ResourceData *resourceData = _data[resourceHandle.fileHash()]; + if (resourceData && resourceData->dataRefCount > 0) + --resourceData->dataRefCount; + resourceHandle._resourceFileEntry = NULL; + resourceHandle._data = NULL; + } +} + +void ResourceMan::purgeResources() { + for (Common::HashMap<uint32, ResourceData*>::iterator it = _data.begin(); it != _data.end(); ++it) { + ResourceData *resourceData = (*it)._value; + if (resourceData->dataRefCount == 0) { + delete[] resourceData->data; + resourceData->data = NULL; + } + } +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/resourceman.h b/engines/neverhood/resourceman.h new file mode 100644 index 0000000000..5a3697fe0d --- /dev/null +++ b/engines/neverhood/resourceman.h @@ -0,0 +1,94 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_RESOURCEMAN_H +#define NEVERHOOD_RESOURCEMAN_H + +#include "common/array.h" +#include "common/file.h" +#include "common/hashmap.h" +#include "neverhood/neverhood.h" +#include "neverhood/blbarchive.h" + +namespace Neverhood { + +struct ResourceFileEntry { + int resourceHandle; + BlbArchive *archive; + BlbArchiveEntry *archiveEntry; +}; + +struct Resource { + ResourceFileEntry *entry; + int useRefCount; +}; + +struct ResourceData { + byte *data; + int dataRefCount; + ResourceData() : data(NULL), dataRefCount() {} +}; + +class ResourceMan; + +struct ResourceHandle { +friend class ResourceMan; +public: + ResourceHandle(); + ~ResourceHandle(); + bool isValid() const { return _resourceFileEntry != NULL && _resourceFileEntry->archiveEntry != NULL; } + byte type() const { return isValid() ? _resourceFileEntry->archiveEntry->type : 0; }; + const byte *data() const { return _data; } + uint32 size() const { return isValid() ? _resourceFileEntry->archiveEntry->size : 0; }; + const byte *extData() const { return _extData; }; + uint32 fileHash() const { return isValid() ? _resourceFileEntry->archiveEntry->fileHash : 0; }; +protected: + ResourceFileEntry *_resourceFileEntry; + const byte *_extData; + const byte *_data; +}; + +class ResourceMan { +public: + ResourceMan(); + ~ResourceMan(); + void addArchive(const Common::String &filename); + ResourceFileEntry *findEntrySimple(uint32 fileHash); + ResourceFileEntry *findEntry(uint32 fileHash, ResourceFileEntry **firstEntry = NULL); + Common::SeekableReadStream *createStream(uint32 fileHash); + const ResourceFileEntry& getEntry(uint index) { return _entries[index]; } + uint getEntryCount() { return _entries.size(); } + void queryResource(uint32 fileHash, ResourceHandle &resourceHandle); + void loadResource(ResourceHandle &resourceHandle); + void unloadResource(ResourceHandle &resourceHandle); + void purgeResources(); +protected: + typedef Common::HashMap<uint32, ResourceFileEntry> EntriesMap; + Common::Array<BlbArchive*> _archives; + EntriesMap _entries; + Common::HashMap<uint32, ResourceData*> _data; + Common::Array<Resource*> _resources; +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_RESOURCEMAN_H */ diff --git a/engines/neverhood/saveload.cpp b/engines/neverhood/saveload.cpp new file mode 100644 index 0000000000..578d9858ff --- /dev/null +++ b/engines/neverhood/saveload.cpp @@ -0,0 +1,164 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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/savefile.h" + +#include "graphics/thumbnail.h" + +#include "neverhood/neverhood.h" +#include "neverhood/gamemodule.h" +#include "neverhood/gamevars.h" + +namespace Neverhood { + +#define NEVERHOOD_SAVEGAME_VERSION 0 + +NeverhoodEngine::kReadSaveHeaderError NeverhoodEngine::readSaveHeader(Common::SeekableReadStream *in, bool loadThumbnail, SaveHeader &header) { + + header.version = in->readUint32LE(); + if (header.version > NEVERHOOD_SAVEGAME_VERSION) + return kRSHEInvalidVersion; + + byte descriptionLen = in->readByte(); + header.description = ""; + while (descriptionLen--) + header.description += (char)in->readByte(); + + if (loadThumbnail) { + header.thumbnail = Graphics::loadThumbnail(*in); + } else { + Graphics::skipThumbnail(*in); + } + + // Not used yet, reserved for future usage + header.gameID = in->readByte(); + header.flags = in->readUint32LE(); + + header.saveDate = in->readUint32LE(); + header.saveTime = in->readUint32LE(); + header.playTime = in->readUint32LE(); + + return ((in->eos() || in->err()) ? kRSHEIoError : kRSHENoError); +} + +void NeverhoodEngine::savegame(const char *filename, const char *description) { + + Common::OutSaveFile *out; + if (!(out = g_system->getSavefileManager()->openForSaving(filename))) { + warning("Can't create file '%s', game not saved", filename); + return; + } + + TimeDate curTime; + g_system->getTimeAndDate(curTime); + + // Header start + out->writeUint32LE(NEVERHOOD_SAVEGAME_VERSION); + + byte descriptionLen = strlen(description); + out->writeByte(descriptionLen); + out->write(description, descriptionLen); + + Graphics::saveThumbnail(*out); + + // Not used yet, reserved for future usage + out->writeByte(0); + out->writeUint32LE(0); + uint32 saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF); + uint32 saveTime = ((curTime.tm_hour & 0xFF) << 16) | (((curTime.tm_min) & 0xFF) << 8) | ((curTime.tm_sec) & 0xFF); + uint32 playTime = g_engine->getTotalPlayTime() / 1000; + out->writeUint32LE(saveDate); + out->writeUint32LE(saveTime); + out->writeUint32LE(playTime); + // Header end + + _gameVars->setGlobalVar(V_CURRENT_SCENE, _gameState.sceneNum); + _gameVars->setGlobalVar(V_CURRENT_SCENE_WHICH, _gameState.which); + + _gameVars->saveState(out); + + out->finalize(); + delete out; +} + +void NeverhoodEngine::loadgame(const char *filename) { + Common::InSaveFile *in; + if (!(in = g_system->getSavefileManager()->openForLoading(filename))) { + warning("Can't open file '%s', game not loaded", filename); + return; + } + + SaveHeader header; + + kReadSaveHeaderError errorCode = readSaveHeader(in, false, header); + + if (errorCode != kRSHENoError) { + warning("Error loading savegame '%s'", filename); + delete in; + return; + } + + g_engine->setTotalPlayTime(header.playTime * 1000); + + _gameVars->loadState(in); + + _gameState.sceneNum = _gameVars->getGlobalVar(V_CURRENT_SCENE); + _gameState.which = _gameVars->getGlobalVar(V_CURRENT_SCENE_WHICH); + + _gameModule->requestRestoreGame(); + + delete in; + +} + +Common::Error NeverhoodEngine::loadGameState(int slot) { + const char *fileName = getSavegameFilename(slot); + loadgame(fileName); + return Common::kNoError; +} + +Common::Error NeverhoodEngine::saveGameState(int slot, const Common::String &description) { + const char *fileName = getSavegameFilename(slot); + savegame(fileName, description.c_str()); + return Common::kNoError; +} + +Common::Error NeverhoodEngine::removeGameState(int slot) { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::String filename = Neverhood::NeverhoodEngine::getSavegameFilename(_targetName, slot); + saveFileMan->removeSavefile(filename.c_str()); + return Common::kNoError; +} + +const char *NeverhoodEngine::getSavegameFilename(int num) { + static Common::String filename; + filename = getSavegameFilename(_targetName, num); + return filename.c_str(); +} + +Common::String NeverhoodEngine::getSavegameFilename(const Common::String &target, int num) { + assert(num >= 0 && num <= 999); + return Common::String::format("%s.%03d", target.c_str(), num); +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/scene.cpp b/engines/neverhood/scene.cpp new file mode 100644 index 0000000000..07d41754c9 --- /dev/null +++ b/engines/neverhood/scene.cpp @@ -0,0 +1,581 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/scene.h" + +namespace Neverhood { + +Scene::Scene(NeverhoodEngine *vm, Module *parentModule) + : Entity(vm, 0), _parentModule(parentModule), _dataResource(vm), _hitRects(NULL), + _mouseCursorWasVisible(true) { + + _isKlaymenBusy = false; + _doConvertMessages = false; + _messageList = NULL; + _rectType = 0; + _mouseClickPos.x = 0; + _mouseClickPos.y = 0; + _mouseClicked = false; + _rectList = NULL; + _klaymen = NULL; + _mouseCursor = NULL; + _palette = NULL; + _background = NULL; + clearHitRects(); + clearCollisionSprites(); + _vm->_screen->setFps(24); + _vm->_screen->setSmackerDecoder(NULL); + _canAcceptInput = true; + _messageList2 = NULL; + _smackerPlayer = NULL; + _isMessageListBusy = false; + _messageValue = -1; + + SetUpdateHandler(&Scene::update); + SetMessageHandler(&Scene::handleMessage); + + _vm->_screen->clearRenderQueue(); +} + +Scene::~Scene() { + + if (_palette) { + removeEntity(_palette); + delete _palette; + } + + // Delete all entities + for (Common::Array<Entity*>::iterator iter = _entities.begin(); iter != _entities.end(); iter++) + delete *iter; + + // Don't delete surfaces since they always belong to an entity + + // Purge the resources after each scene + _vm->_res->purgeResources(); + +} + +void Scene::draw() { + if (_smackerPlayer) { + if (_smackerPlayer->getSurface()) + _smackerPlayer->getSurface()->draw(); + } else { + for (Common::Array<BaseSurface*>::iterator iter = _surfaces.begin(); iter != _surfaces.end(); iter++) + (*iter)->draw(); + } +} + +void Scene::addEntity(Entity *entity) { + int index = 0, insertIndex = -1; + for (Common::Array<Entity*>::iterator iter = _entities.begin(); iter != _entities.end(); iter++) { + if ((*iter)->getPriority() > entity->getPriority()) { + insertIndex = index; + break; + } + index++; + } + if (insertIndex >= 0) + _entities.insert_at(insertIndex, entity); + else + _entities.push_back(entity); +} + +bool Scene::removeEntity(Entity *entity) { + for (uint index = 0; index < _entities.size(); index++) + if (_entities[index] == entity) { + _entities.remove_at(index); + return true; + } + return false; +} + +void Scene::addSurface(BaseSurface *surface) { + if (surface) { + int index = 0, insertIndex = -1; + for (Common::Array<BaseSurface*>::iterator iter = _surfaces.begin(); iter != _surfaces.end(); iter++) { + if ((*iter)->getPriority() > surface->getPriority()) { + insertIndex = index; + break; + } + index++; + } + if (insertIndex >= 0) + _surfaces.insert_at(insertIndex, surface); + else + _surfaces.push_back(surface); + } +} + +bool Scene::removeSurface(BaseSurface *surface) { + for (uint index = 0; index < _surfaces.size(); index++) { + if (_surfaces[index] == surface) { + _surfaces.remove_at(index); + return true; + } + } + return false; +} + +Sprite *Scene::addSprite(Sprite *sprite) { + addEntity(sprite); + addSurface(sprite->getSurface()); + return sprite; +} + +void Scene::removeSprite(Sprite *sprite) { + removeSurface(sprite->getSurface()); + removeEntity(sprite); +} + +void Scene::setSurfacePriority(BaseSurface *surface, int priority) { + surface->setPriority(priority); + if (removeSurface(surface)) + addSurface(surface); +} + +void Scene::setSpriteSurfacePriority(Sprite *sprite, int priority) { + if (sprite) + setSurfacePriority(sprite->getSurface(), priority); +} + +void Scene::deleteSprite(Sprite **sprite) { + removeCollisionSprite(*sprite); + removeSurface((*sprite)->getSurface()); + removeEntity(*sprite); + delete *sprite; + *sprite = NULL; +} + +Background *Scene::addBackground(Background *background) { + addEntity(background); + addSurface(background->getSurface()); + return background; +} + +void Scene::setBackground(uint32 fileHash) { + _background = addBackground(new Background(_vm, fileHash, 0, 0)); +} + +void Scene::changeBackground(uint32 fileHash) { + _background->load(fileHash); +} + +void Scene::setPalette(uint32 fileHash) { + _palette = fileHash ? new Palette(_vm, fileHash) : new Palette(_vm); + _palette->usePalette(); +} + +void Scene::setHitRects(uint32 id) { + setHitRects(_vm->_staticData->getHitRectList(id)); +} + +void Scene::setHitRects(HitRectList *hitRects) { + _hitRects = hitRects; +} + +Sprite *Scene::insertStaticSprite(uint32 fileHash, int surfacePriority) { + return addSprite(new StaticSprite(_vm, fileHash, surfacePriority)); +} + +void Scene::insertScreenMouse(uint32 fileHash, const NRect *mouseRect) { + NRect rect(-1, -1, -1, -1); + if (mouseRect) + rect = *mouseRect; + insertMouse(new Mouse(_vm, fileHash, rect)); +} + +void Scene::insertPuzzleMouse(uint32 fileHash, int16 x1, int16 x2) { + insertMouse(new Mouse(_vm, fileHash, x1, x2)); +} + +void Scene::insertNavigationMouse(uint32 fileHash, int type) { + insertMouse(new Mouse(_vm, fileHash, type)); +} + +void Scene::showMouse(bool visible) { + _mouseCursor->getSurface()->setVisible(visible); +} + +void Scene::changeMouseCursor(uint32 fileHash) { + _mouseCursor->load(fileHash); + _mouseCursor->updateCursor(); +} + +SmackerPlayer *Scene::addSmackerPlayer(SmackerPlayer *smackerPlayer) { + addEntity(smackerPlayer); + addSurface(smackerPlayer->getSurface()); + return smackerPlayer; +} + +void Scene::update() { + + if (_mouseClicked) { + if (_klaymen) { + if (_canAcceptInput && + _klaymen->hasMessageHandler() && + sendMessage(_klaymen, 0x1008, 0) != 0 && + queryPositionSprite(_mouseClickPos.x, _mouseClickPos.y)) { + _mouseClicked = false; + } else if (_canAcceptInput && + _klaymen->hasMessageHandler() && + sendMessage(_klaymen, 0x1008, 0) != 0) { + _mouseClicked = !queryPositionRectList(_mouseClickPos.x, _mouseClickPos.y); + } + } else if (queryPositionSprite(_mouseClickPos.x, _mouseClickPos.y)) { + _mouseClicked = false; + } + } + + processMessageList(); + + // Update all entities + for (Common::Array<Entity*>::iterator iter = _entities.begin(); iter != _entities.end(); iter++) + (*iter)->handleUpdate(); + +} + +void Scene::leaveScene(uint32 result) { + sendMessage(_parentModule, 0x1009, result); +} + +uint32 Scene::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + switch (messageNum) { + case 0x0000: // mouse moved + if (_mouseCursor && _mouseCursor->hasMessageHandler()) + sendMessage(_mouseCursor, 0x4002, param); + break; + case 0x0001: // mouse clicked + _mouseClicked = true; + _mouseClickPos = param.asPoint(); + break; + case 0x0006: + sendMessage(_parentModule, 0x1009, param); + break; + case 0x1006: + // Sent by Klaymen when its animation sequence has finished + if (_isKlaymenBusy) { + _isKlaymenBusy = false; + if (_messageListIndex == _messageListCount) { + // If the current message list was processed completely, + // sent Klaymen into the idle state. + sendMessage(_klaymen, 0x4004, 0); + } else { + // Else continue with the next message in the current message list + processMessageList(); + } + } + break; + case 0x1007: + // This isn't sent by any code, check if it's in a message list + // This cancels the current message list and sets Klaymen into the idle state. + if (_isKlaymenBusy) { + _isKlaymenBusy = false; + _messageList = NULL; + sendMessage(_klaymen, 0x4004, 0); + } + break; + case 0x101D: + // Hide the mouse cursor + if (_mouseCursor) { + _mouseCursorWasVisible = _mouseCursor->getSurface()->getVisible(); + _mouseCursor->getSurface()->setVisible(false); + } + break; + case 0x101E: + // Show the mouse cursor + if (_mouseCursorWasVisible && _mouseCursor) { + _mouseCursor->getSurface()->setVisible(true); + } + break; + case 0x1022: + // Set the sender's surface priority + setSurfacePriority(((Sprite*)sender)->getSurface(), param.asInteger()); + break; + } + return 0; +} + +bool Scene::queryPositionSprite(int16 mouseX, int16 mouseY) { + for (uint i = 0; i < _collisionSprites.size(); i++) { + Sprite *sprite = _collisionSprites[i]; + if (sprite->hasMessageHandler() && sprite->isPointInside(mouseX, mouseY) && + sendPointMessage(sprite, 0x1011, _mouseClickPos) != 0) { + return true; + } + } + return false; +} + +bool Scene::queryPositionRectList(int16 mouseX, int16 mouseY) { + int16 klaymenX = _klaymen->getX(); + int16 klaymenY = _klaymen->getY(); + if (_rectType == 1) { + RectList &rectList = *_rectList; + for (uint i = 0; i < rectList.size(); i++) { + debug(2, "(%d, %d) ? (%d, %d, %d, %d)", klaymenX, klaymenY, rectList[i].rect.x1, rectList[i].rect.y1, rectList[i].rect.x2, rectList[i].rect.y2); + if (rectList[i].rect.contains(klaymenX, klaymenY)) { + for (uint j = 0; j < rectList[i].subRects.size(); j++) { + debug(2, " (%d, %d) ? (%d, %d, %d, %d)", mouseX, mouseY, rectList[i].subRects[j].rect.x1, rectList[i].subRects[j].rect.y1, rectList[i].subRects[j].rect.x2, rectList[i].subRects[j].rect.y2); + if (rectList[i].subRects[j].rect.contains(mouseX, mouseY)) { + return setMessageList2(rectList[i].subRects[j].messageListId); + } + } + } + } + } else if (_rectType == 2) { + MessageList *messageList = _dataResource.getMessageListAtPos(klaymenX, klaymenY, mouseX, mouseY); + if (messageList && messageList->size()) + setMessageList2(messageList, true, true); + } + return true; +} + +void Scene::setMessageList(uint32 id, bool canAcceptInput, bool doConvertMessages) { + setMessageList(_vm->_staticData->getMessageList(id), canAcceptInput, doConvertMessages); +} + +void Scene::setMessageList(MessageList *messageList, bool canAcceptInput, bool doConvertMessages) { + _messageList = messageList; + _messageListCount = _messageList ? _messageList->size() : 0; + _messageListIndex = 0; + _isKlaymenBusy = false; + _canAcceptInput = canAcceptInput; + _doConvertMessages = doConvertMessages; + _messageListStatus = 1; + sendMessage(_klaymen, 0x101C, 0); +} + +bool Scene::setMessageList2(uint32 id, bool canAcceptInput, bool doConvertMessages) { + return setMessageList2(_vm->_staticData->getMessageList(id), canAcceptInput, doConvertMessages); +} + +bool Scene::setMessageList2(MessageList *messageList, bool canAcceptInput, bool doConvertMessages) { + if (_messageListStatus == 0 || + (_messageListStatus == 1 && messageList != _messageList2) || + (_messageListStatus == 2 && messageList == _messageList2)) { + // NOTE Skipped unneeded resource preloading code + _messageValue = -1; + _messageList2 = messageList; + setMessageList(messageList, canAcceptInput, doConvertMessages); + return true; + } + return false; +} + +bool Scene::isMessageList2(uint32 id) { + return _messageList2 == _vm->_staticData->getMessageList(id); +} + +void Scene::processMessageList() { + debug(7, "Scene::processMessageList() _isMessageListBusy = %d; _isKlaymenBusy = %d", _isMessageListBusy, _isKlaymenBusy); + + if (_isMessageListBusy || _isKlaymenBusy) + return; + + _isMessageListBusy = true; + + if (!_messageList) { + _messageList2 = NULL; + _messageListStatus = 0; + } + + if (_messageList && _klaymen) { + +#if 0 + debug("MessageList: %p, %d", (void*)_messageList, _messageList->size()); + for (uint i = 0; i < _messageList->size(); ++i) { + if (i == _messageListIndex) debugN("**"); else debugN(" "); + debug("(%08X, %08X)", (*_messageList)[i].messageNum, (*_messageList)[i].messageValue); + } + debug("--------------------------------"); +#endif + + while (_messageList && _messageListIndex < _messageListCount && !_isKlaymenBusy) { + uint32 messageNum = (*_messageList)[_messageListIndex].messageNum; + uint32 messageParam = (*_messageList)[_messageListIndex].messageValue; + + ++_messageListIndex; + if (_messageListIndex == _messageListCount) + sendMessage(_klaymen, 0x1021, 0); + if (_doConvertMessages) + messageNum = convertMessageNum(messageNum); + if (messageNum == 0x1009 || messageNum == 0x1024) { + sendMessage(_parentModule, messageNum, messageParam); + } else if (messageNum == 0x100A) { + _messageValue = messageParam; + sendMessage(_parentModule, messageNum, messageParam); + } else if (messageNum == 0x4001) { + _isKlaymenBusy = true; + sendPointMessage(_klaymen, 0x4001, _mouseClickPos); + } else if (messageNum == 0x100D) { + if (this->hasMessageHandler() && sendMessage(this, 0x100D, messageParam) != 0) + continue; + } else if (messageNum == 0x101A) { + _messageListStatus = 0; + } else if (messageNum == 0x101B) { + _messageListStatus = 2; + } else if (messageNum == 0x1020) { + _canAcceptInput = false; + } else if (messageNum >= 0x2000 && messageNum <= 0x2FFF) { + if (this->hasMessageHandler() && sendMessage(this, messageNum, messageParam) != 0) { + _isMessageListBusy = false; + return; + } + } else if (messageNum != 0x4003) { + _isKlaymenBusy = true; + if (_klaymen->hasMessageHandler() && sendMessage(_klaymen, messageNum, messageParam) != 0) { + _isKlaymenBusy = false; + } + } + if (_messageListIndex == _messageListCount) { + _canAcceptInput = true; + _messageList = NULL; + } + } + } + + _isMessageListBusy = false; + +} + +void Scene::cancelMessageList() { + _isKlaymenBusy = false; + _messageList = NULL; + _canAcceptInput = true; + sendMessage(_klaymen, 0x4004, 0); +} + +void Scene::setRectList(uint32 id) { + setRectList(_vm->_staticData->getRectList(id)); +} + +void Scene::setRectList(RectList *rectList) { + _rectList = rectList; + _rectType = 1; +} + +void Scene::clearRectList() { + _rectList = NULL; + _rectType = 0; +} + +void Scene::loadHitRectList() { + HitRectList *hitRectList = _dataResource.getHitRectList(); + if (hitRectList) { + _hitRectList = *hitRectList; + setHitRects(&_hitRectList); + } +} + +void Scene::loadDataResource(uint32 fileHash) { + _dataResource.load(fileHash); + _rectType = 2; + if (_klaymen) + _klaymen->loadDataResource(fileHash); +} + +uint16 Scene::convertMessageNum(uint32 messageNum) { + switch (messageNum) { + case 0x00004004: + return 0x4001; + case 0x00000083: + return 0x100A; + case 0x044001C8: + return 0x481C; + case 0x02420480: + return 0x4818; + case 0x08004025: + return 0x100D; + case 0x04404281: + return 0x4824; + case 0x08400880: + return 0x4825; + case 0x08209081: + return 0x4823; + case 0x24000060: + return 0x1009; + case 0x42002200: + return 0x4004; + case 0x428D4894: + return 0x101A; + } + return 0x1000; +} + +void Scene::clearHitRects() { + _hitRects = NULL; +} + +HitRect *Scene::findHitRectAtPos(int16 x, int16 y) { + static HitRect kDefaultHitRect = {NRect(), 0x5000}; + if (_hitRects) + for (HitRectList::iterator it = _hitRects->begin(); it != _hitRects->end(); it++) + if ((*it).rect.contains(x, y)) + return &(*it); + return &kDefaultHitRect; +} + +void Scene::addCollisionSprite(Sprite *sprite) { + int index = 0, insertIndex = -1; + for (Common::Array<Sprite*>::iterator iter = _collisionSprites.begin(); iter != _collisionSprites.end(); iter++) { + if ((*iter)->getPriority() > sprite->getPriority()) { + insertIndex = index; + break; + } + index++; + } + if (insertIndex >= 0) + _collisionSprites.insert_at(insertIndex, sprite); + else + _collisionSprites.push_back(sprite); +} + +void Scene::removeCollisionSprite(Sprite *sprite) { + for (uint index = 0; index < _collisionSprites.size(); index++) { + if (_collisionSprites[index] == sprite) { + _collisionSprites.remove_at(index); + break; + } + } +} + +void Scene::clearCollisionSprites() { + _collisionSprites.clear(); +} + +void Scene::checkCollision(Sprite *sprite, uint16 flags, int messageNum, uint32 messageParam) { + for (Common::Array<Sprite*>::iterator iter = _collisionSprites.begin(); iter != _collisionSprites.end(); iter++) { + Sprite *collSprite = *iter; + if ((sprite->getFlags() & flags) && collSprite->checkCollision(sprite->getCollisionBounds())) { + sprite->sendMessage(collSprite, messageNum, messageParam); + } + } +} + +void Scene::insertMouse(Mouse *mouseCursor) { + if (_mouseCursor) + deleteSprite((Sprite**)&_mouseCursor); + _mouseCursor = mouseCursor; + addEntity(_mouseCursor); +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/scene.h b/engines/neverhood/scene.h new file mode 100644 index 0000000000..1abcbfb964 --- /dev/null +++ b/engines/neverhood/scene.h @@ -0,0 +1,226 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_SCENE_H +#define NEVERHOOD_SCENE_H + +#include "common/array.h" +#include "neverhood/neverhood.h" +#include "neverhood/background.h" +#include "neverhood/entity.h" +#include "neverhood/graphics.h" +#include "neverhood/klaymen.h" +#include "neverhood/module.h" +#include "neverhood/palette.h" +#include "neverhood/smackerplayer.h" +#include "neverhood/sprite.h" +#include "neverhood/staticdata.h" + +namespace Neverhood { + +class Scene : public Entity { +public: + Scene(NeverhoodEngine *vm, Module *parentModule); + virtual ~Scene(); + virtual void draw(); + void addEntity(Entity *entity); + bool removeEntity(Entity *entity); + void addSurface(BaseSurface *surface); + bool removeSurface(BaseSurface *surface); + Sprite *addSprite(Sprite *sprite); + void removeSprite(Sprite *sprite); + void setSurfacePriority(BaseSurface *surface, int priority); + void setSpriteSurfacePriority(Sprite *sprite, int priority); + void deleteSprite(Sprite **sprite); + Background *addBackground(Background *background); + void setBackground(uint32 fileHash); + void changeBackground(uint32 fileHash); + void setBackgroundY(int16 y) { _background->getSurface()->getDrawRect().y = y; } + int16 getBackgroundY() { return _background->getSurface()->getDrawRect().y; } + void setPalette(uint32 fileHash = 0); + void setHitRects(uint32 id); + Sprite *insertStaticSprite(uint32 fileHash, int surfacePriority); + void insertScreenMouse(uint32 fileHash, const NRect *mouseRect = NULL); + void insertPuzzleMouse(uint32 fileHash, int16 x1, int16 x2); + void insertNavigationMouse(uint32 fileHash, int type); + void showMouse(bool visible); + void changeMouseCursor(uint32 fileHash); + SmackerPlayer *addSmackerPlayer(SmackerPlayer *smackerPlayer); + void update(); + void leaveScene(uint32 result); + HitRect *findHitRectAtPos(int16 x, int16 y); + void addCollisionSprite(Sprite *sprite); + void removeCollisionSprite(Sprite *sprite); + void checkCollision(Sprite *sprite, uint16 flags, int messageNum, uint32 messageParam); + // Some crazy templated functions to make the logic code smaller/simpler (imo!) + // insertKlaymen + template<class T> + void insertKlaymen() { + _klaymen = (T*)addSprite(new T(_vm, this)); + } + template<class T, class Arg1> + void insertKlaymen(Arg1 arg1) { + _klaymen = (T*)addSprite(new T(_vm, this, arg1)); + } + template<class T, class Arg1, class Arg2> + void insertKlaymen(Arg1 arg1, Arg2 arg2) { + _klaymen = (T*)addSprite(new T(_vm, this, arg1, arg2)); + } + template<class T, class Arg1, class Arg2, class Arg3> + void insertKlaymen(Arg1 arg1, Arg2 arg2, Arg3 arg3) { + _klaymen = (T*)addSprite(new T(_vm, this, arg1, arg2, arg3)); + } + template<class T, class Arg1, class Arg2, class Arg3, class Arg4> + void insertKlaymen(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4) { + _klaymen = (T*)addSprite(new T(_vm, this, arg1, arg2, arg3, arg4)); + } + template<class T, class Arg1, class Arg2, class Arg3, class Arg4, class Arg5> + void insertKlaymen(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5) { + _klaymen = (T*)addSprite(new T(_vm, this, arg1, arg2, arg3, arg4, arg5)); + } + template<class T, class Arg1, class Arg2, class Arg3, class Arg4, class Arg5, class Arg6> + void insertKlaymen(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6) { + _klaymen = (T*)addSprite(new T(_vm, this, arg1, arg2, arg3, arg4, arg5, arg6)); + } + // insertSprite + template<class T> + T* insertSprite() { + return (T*)addSprite(new T(_vm)); + } + template<class T, class Arg1> + T* insertSprite(Arg1 arg1) { + return (T*)addSprite(new T(_vm, arg1)); + } + template<class T, class Arg1, class Arg2> + T* insertSprite(Arg1 arg1, Arg2 arg2) { + return (T*)addSprite(new T(_vm, arg1, arg2)); + } + template<class T, class Arg1, class Arg2, class Arg3> + T* insertSprite(Arg1 arg1, Arg2 arg2, Arg3 arg3) { + return (T*)addSprite(new T(_vm, arg1, arg2, arg3)); + } + template<class T, class Arg1, class Arg2, class Arg3, class Arg4> + T* insertSprite(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4) { + return (T*)addSprite(new T(_vm, arg1, arg2, arg3, arg4)); + } + template<class T, class Arg1, class Arg2, class Arg3, class Arg4, class Arg5> + T* insertSprite(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5) { + return (T*)addSprite(new T(_vm, arg1, arg2, arg3, arg4, arg5)); + } + template<class T, class Arg1, class Arg2, class Arg3, class Arg4, class Arg5, class Arg6> + T* insertSprite(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6) { + return (T*)addSprite(new T(_vm, arg1, arg2, arg3, arg4, arg5, arg6)); + } + // createSprite + template<class T> + T* createSprite() { + return new T(_vm); + } + template<class T, class Arg1> + T* createSprite(Arg1 arg1) { + return new T(_vm, arg1); + } + template<class T, class Arg1, class Arg2> + T* createSprite(Arg1 arg1, Arg2 arg2) { + return new T(_vm, arg1, arg2); + } + template<class T, class Arg1, class Arg2, class Arg3> + T* createSprite(Arg1 arg1, Arg2 arg2, Arg3 arg3) { + return new T(_vm, arg1, arg2, arg3); + } + template<class T, class Arg1, class Arg2, class Arg3, class Arg4> + T* createSprite(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4) { + return new T(_vm, arg1, arg2, arg3, arg4); + } + template<class T, class Arg1, class Arg2, class Arg3, class Arg4, class Arg5> + T* createSprite(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5) { + return new T(_vm, arg1, arg2, arg3, arg4, arg5); + } + template<class T, class Arg1, class Arg2, class Arg3, class Arg4, class Arg5, class Arg6> + T* createSprite(Arg1 arg1, Arg2 arg2, Arg3 arg3, Arg4 arg4, Arg5 arg5, Arg6 arg6) { + return new T(_vm, arg1, arg2, arg3, arg4, arg5, arg6); + } +protected: + Module *_parentModule; + Common::Array<Entity*> _entities; + Common::Array<BaseSurface*> _surfaces; + + Klaymen *_klaymen; + Background *_background; + Palette *_palette; + SmackerPlayer *_smackerPlayer; + + MessageList *_messageList; + MessageList *_messageList2; + int _messageListStatus; + uint _messageListCount; + uint _messageListIndex; + bool _doConvertMessages; + + bool _canAcceptInput; + bool _isKlaymenBusy; + bool _isMessageListBusy; + + Mouse *_mouseCursor; + NPoint _mouseClickPos; + bool _mouseClicked; + bool _mouseCursorWasVisible; + + int _rectType; + RectList *_rectList; + DataResource _dataResource; + + HitRectList _hitRectList; + + HitRectList *_hitRects; + Common::Array<Sprite*> _collisionSprites; + + void (Entity::*_savedUpdateHandlerCb)(); + uint32 (Entity::*_savedMessageHandlerCb)(int messageNum, const MessageParam ¶m, Entity *sender); + int _messageValue; + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + bool queryPositionSprite(int16 mouseX, int16 mouseY); + bool queryPositionRectList(int16 mouseX, int16 mouseY); + void setMessageList(uint32 id, bool canAcceptInput = true, bool doConvertMessages = false); + void setMessageList(MessageList *messageList, bool canAcceptInput = true, bool doConvertMessages = false); + bool setMessageList2(uint32 id, bool canAcceptInput = true, bool doConvertMessages = false); + bool setMessageList2(MessageList *messageList, bool canAcceptInput = true, bool doConvertMessages = false); + bool isMessageList2(uint32 id); + void processMessageList(); + void setRectList(uint32 id); + void setRectList(RectList *rectList); + void clearRectList(); + void loadHitRectList(); + void cancelMessageList(); + void loadDataResource(uint32 fileHash); + uint16 convertMessageNum(uint32 messageNum); + + void setHitRects(HitRectList *hitRects); + void clearHitRects(); + void clearCollisionSprites(); + + void insertMouse(Mouse *mouseCursor); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_SCENE_H */ diff --git a/engines/neverhood/screen.cpp b/engines/neverhood/screen.cpp new file mode 100644 index 0000000000..5a748cfab4 --- /dev/null +++ b/engines/neverhood/screen.cpp @@ -0,0 +1,420 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "graphics/palette.h" +#include "neverhood/screen.h" + +namespace Neverhood { + +Screen::Screen(NeverhoodEngine *vm) + : _vm(vm), _paletteData(NULL), _paletteChanged(false), _smackerDecoder(NULL), + _yOffset(0), _fullRefresh(false) { + + _ticks = _vm->_system->getMillis(); + + _backScreen = new Graphics::Surface(); + _backScreen->create(640, 480, Graphics::PixelFormat::createFormatCLUT8()); + + _renderQueue = new RenderQueue(); + _prevRenderQueue = new RenderQueue(); + _microTiles = new MicroTileArray(640, 480); + +} + +Screen::~Screen() { + delete _microTiles; + delete _renderQueue; + delete _prevRenderQueue; + _backScreen->free(); + delete _backScreen; +} + +void Screen::update() { + _ticks = _vm->_system->getMillis(); + updatePalette(); + + if (_fullRefresh) { + // NOTE When playing a fullscreen/doubled Smacker video usually a full screen refresh is needed + _vm->_system->copyRectToScreen((const byte*)_backScreen->pixels, _backScreen->pitch, 0, 0, 640, 480); + _fullRefresh = false; + return; + } + + _microTiles->clear(); + + for (RenderQueue::iterator it = _renderQueue->begin(); it != _renderQueue->end(); ++it) { + RenderItem &renderItem = (*it); + renderItem._refresh = true; + for (RenderQueue::iterator jt = _prevRenderQueue->begin(); jt != _prevRenderQueue->end(); ++jt) { + RenderItem &prevRenderItem = (*jt); + if (prevRenderItem == renderItem) { + prevRenderItem._refresh = false; + renderItem._refresh = false; + } + } + } + + for (RenderQueue::iterator jt = _prevRenderQueue->begin(); jt != _prevRenderQueue->end(); ++jt) { + RenderItem &prevRenderItem = (*jt); + if (prevRenderItem._refresh) + _microTiles->addRect(Common::Rect(prevRenderItem._destX, prevRenderItem._destY, prevRenderItem._destX + prevRenderItem._width, prevRenderItem._destY + prevRenderItem._height)); + } + + for (RenderQueue::iterator it = _renderQueue->begin(); it != _renderQueue->end(); ++it) { + RenderItem &renderItem = (*it); + if (renderItem._refresh) + _microTiles->addRect(Common::Rect(renderItem._destX, renderItem._destY, renderItem._destX + renderItem._width, renderItem._destY + renderItem._height)); + renderItem._refresh = true; + } + + RectangleList *updateRects = _microTiles->getRectangles(); + + for (RenderQueue::iterator it = _renderQueue->begin(); it != _renderQueue->end(); ++it) { + RenderItem &renderItem = (*it); + for (RectangleList::iterator ri = updateRects->begin(); ri != updateRects->end(); ++ri) + blitRenderItem(renderItem, *ri); + } + + SWAP(_renderQueue, _prevRenderQueue); + _renderQueue->clear(); + + for (RectangleList::iterator ri = updateRects->begin(); ri != updateRects->end(); ++ri) { + Common::Rect &r = *ri; + _vm->_system->copyRectToScreen((const byte*)_backScreen->getBasePtr(r.left, r.top), _backScreen->pitch, r.left, r.top, r.width(), r.height()); + } + + delete updateRects; + +} + +uint32 Screen::getNextFrameTime() { + int32 frameDelay = _frameDelay; + if (_smackerDecoder && _smackerDecoder->isVideoLoaded() && !_smackerDecoder->endOfVideo()) + frameDelay = _smackerDecoder->getTimeToNextFrame(); + int32 waitTicks = frameDelay - (_vm->_system->getMillis() - _ticks); + return _vm->_system->getMillis() + waitTicks; +} + +void Screen::saveParams() { + _savedSmackerDecoder = _smackerDecoder; + _savedFrameDelay = _frameDelay; + _savedYOffset = _yOffset; +} + +void Screen::restoreParams() { + _smackerDecoder = _savedSmackerDecoder; + _frameDelay = _savedFrameDelay; + _yOffset = _savedYOffset; +} + +void Screen::setFps(int fps) { + _frameDelay = 1000 / fps; +} + +int Screen::getFps() { + return 1000 / _frameDelay; +} + +void Screen::setYOffset(int16 yOffset) { + _yOffset = yOffset; +} + +int16 Screen::getYOffset() { + return _yOffset; +} + +void Screen::setPaletteData(byte *paletteData) { + _paletteChanged = true; + _paletteData = paletteData; +} + +void Screen::unsetPaletteData(byte *paletteData) { + if (_paletteData == paletteData) { + _paletteChanged = false; + _paletteData = NULL; + } +} + +void Screen::testPalette(byte *paletteData) { + if (_paletteData == paletteData) + _paletteChanged = true; +} + +void Screen::updatePalette() { + if (_paletteChanged && _paletteData) { + byte *tempPalette = new byte[768]; + for (int i = 0; i < 256; i++) { + tempPalette[i * 3 + 0] = _paletteData[i * 4 + 0]; + tempPalette[i * 3 + 1] = _paletteData[i * 4 + 1]; + tempPalette[i * 3 + 2] = _paletteData[i * 4 + 2]; + } + _vm->_system->getPaletteManager()->setPalette(tempPalette, 0, 256); + delete[] tempPalette; + _paletteChanged = false; + } +} + +void Screen::clear() { + memset(_backScreen->pixels, 0, _backScreen->pitch * _backScreen->h); + _fullRefresh = true; + clearRenderQueue(); +} + +void Screen::clearRenderQueue() { + _renderQueue->clear(); + _prevRenderQueue->clear(); +} + +void Screen::drawSurface2(const Graphics::Surface *surface, NDrawRect &drawRect, NRect &clipRect, bool transparent, byte version, + const Graphics::Surface *shadowSurface) { + + int16 destX, destY; + NRect ddRect; + + if (drawRect.x + drawRect.width >= clipRect.x2) + ddRect.x2 = clipRect.x2 - drawRect.x; + else + ddRect.x2 = drawRect.width; + + if (drawRect.x < clipRect.x1) { + destX = clipRect.x1; + ddRect.x1 = clipRect.x1 - drawRect.x; + } else { + destX = drawRect.x; + ddRect.x1 = 0; + } + + if (drawRect.y + drawRect.height >= clipRect.y2) + ddRect.y2 = clipRect.y2 - drawRect.y; + else + ddRect.y2 = drawRect.height; + + if (drawRect.y < clipRect.y1) { + destY = clipRect.y1; + ddRect.y1 = clipRect.y1 - drawRect.y; + } else { + destY = drawRect.y; + ddRect.y1 = 0; + } + + queueBlit(surface, destX, destY, ddRect, transparent, version, shadowSurface); + +} + +void Screen::drawSurface3(const Graphics::Surface *surface, int16 x, int16 y, NDrawRect &drawRect, NRect &clipRect, bool transparent, byte version) { + + int16 destX, destY; + NRect ddRect; + + if (x + drawRect.width >= clipRect.x2) + ddRect.x2 = clipRect.x2 - drawRect.x - x; + else + ddRect.x2 = drawRect.x + drawRect.width; + + if (x < clipRect.x1) { + destX = clipRect.x1; + ddRect.x1 = clipRect.x1 + drawRect.x - x; + } else { + destX = x; + ddRect.x1 = drawRect.x; + } + + if (y + drawRect.height >= clipRect.y2) + ddRect.y2 = clipRect.y2 + drawRect.y - y; + else + ddRect.y2 = drawRect.y + drawRect.height; + + if (y < clipRect.y1) { + destY = clipRect.y1; + ddRect.y1 = clipRect.y1 + drawRect.y - y; + } else { + destY = y; + ddRect.y1 = drawRect.y; + } + + queueBlit(surface, destX, destY, ddRect, transparent, version); + +} + +void Screen::drawDoubleSurface2(const Graphics::Surface *surface, NDrawRect &drawRect) { + + const byte *source = (const byte*)surface->getBasePtr(0, 0); + byte *dest = (byte*)_backScreen->getBasePtr(drawRect.x, drawRect.y); + + for (int16 yc = 0; yc < surface->h; yc++) { + byte *row = dest; + for (int16 xc = 0; xc < surface->w; xc++) { + *row++ = *source; + *row++ = *source++; + } + memcpy(dest + _backScreen->pitch, dest, surface->w * 2); + dest += _backScreen->pitch; + dest += _backScreen->pitch; + } + + _fullRefresh = true; // See Screen::update + +} + +void Screen::drawUnk(const Graphics::Surface *surface, NDrawRect &drawRect, NDrawRect &sysRect, NRect &clipRect, bool transparent, byte version) { + + int16 x, y; + bool xflag, yflag; + NDrawRect newDrawRect; + + x = sysRect.x; + if (sysRect.width <= x || -sysRect.width >= x) + x = x % sysRect.width; + if (x < 0) + x += sysRect.width; + + y = sysRect.y; + if (y >= sysRect.height || -sysRect.height >= y) + y = y % sysRect.height; + if (y < 0) + y += sysRect.height; + + xflag = x <= 0; + yflag = y <= 0; + + newDrawRect.x = x; + newDrawRect.width = sysRect.width - x; + if (drawRect.width < newDrawRect.width) { + xflag = true; + newDrawRect.width = drawRect.width; + } + + newDrawRect.y = y; + newDrawRect.height = sysRect.height - y; + if (drawRect.height < newDrawRect.height) { + yflag = true; + newDrawRect.height = drawRect.height; + } + + drawSurface3(surface, drawRect.x, drawRect.y, newDrawRect, clipRect, transparent, version); + + if (!xflag) { + newDrawRect.x = 0; + newDrawRect.y = y; + newDrawRect.width = x + drawRect.width - sysRect.width; + newDrawRect.height = sysRect.height - y; + if (drawRect.height < newDrawRect.height) + newDrawRect.height = drawRect.height; + drawSurface3(surface, sysRect.width + drawRect.x - x, drawRect.y, newDrawRect, clipRect, transparent, version); + } + + if (!yflag) { + newDrawRect.x = x; + newDrawRect.y = 0; + newDrawRect.width = sysRect.width - x; + newDrawRect.height = y + drawRect.height - sysRect.height; + if (drawRect.width < newDrawRect.width) + newDrawRect.width = drawRect.width; + drawSurface3(surface, drawRect.x, sysRect.height + drawRect.y - y, newDrawRect, clipRect, transparent, version); + } + + if (!xflag && !yflag) { + newDrawRect.x = 0; + newDrawRect.y = 0; + newDrawRect.width = x + drawRect.width - sysRect.width; + newDrawRect.height = y + drawRect.height - sysRect.height; + drawSurface3(surface, sysRect.width + drawRect.x - x, sysRect.height + drawRect.y - y, newDrawRect, clipRect, transparent, version); + } + +} + +void Screen::drawSurfaceClipRects(const Graphics::Surface *surface, NDrawRect &drawRect, NRect *clipRects, uint clipRectsCount, bool transparent, byte version) { + NDrawRect clipDrawRect(0, 0, drawRect.width, drawRect.height); + for (uint i = 0; i < clipRectsCount; i++) + drawSurface3(surface, drawRect.x, drawRect.y, clipDrawRect, clipRects[i], transparent, version); +} + +void Screen::queueBlit(const Graphics::Surface *surface, int16 destX, int16 destY, NRect &ddRect, bool transparent, byte version, + const Graphics::Surface *shadowSurface) { + + const int width = ddRect.x2 - ddRect.x1; + const int height = ddRect.y2 - ddRect.y1; + + if (width <= 0 || height <= 0) + return; + + RenderItem renderItem; + renderItem._surface = surface; + renderItem._shadowSurface = shadowSurface; + renderItem._destX = destX; + renderItem._destY = destY; + renderItem._srcX = ddRect.x1; + renderItem._srcY = ddRect.y1; + renderItem._width = width; + renderItem._height = height; + renderItem._transparent = transparent; + renderItem._version = version; + _renderQueue->push_back(renderItem); + +} + +void Screen::blitRenderItem(const RenderItem &renderItem, const Common::Rect &clipRect) { + + const Graphics::Surface *surface = renderItem._surface; + const Graphics::Surface *shadowSurface = renderItem._shadowSurface; + const int16 x0 = MAX<int16>(clipRect.left, renderItem._destX); + const int16 y0 = MAX<int16>(clipRect.top, renderItem._destY); + const int16 x1 = MIN<int16>(clipRect.right, renderItem._destX + renderItem._width); + const int16 y1 = MIN<int16>(clipRect.bottom, renderItem._destY + renderItem._height); + const int16 width = x1 - x0; + int16 height = y1 - y0; + + if (width < 0 || height < 0) + return; + + const byte *source = (const byte*)surface->getBasePtr(renderItem._srcX + x0 - renderItem._destX, renderItem._srcY + y0 - renderItem._destY); + byte *dest = (byte*)_backScreen->getBasePtr(x0, y0); + + if (shadowSurface) { + const byte *shadowSource = (const byte*)shadowSurface->getBasePtr(x0, y0); + while (height--) { + for (int xc = 0; xc < width; xc++) + if (source[xc] != 0) + dest[xc] = shadowSource[xc]; + source += surface->pitch; + shadowSource += shadowSurface->pitch; + dest += _backScreen->pitch; + } + } else if (!renderItem._transparent) { + while (height--) { + memcpy(dest, source, width); + source += surface->pitch; + dest += _backScreen->pitch; + } + } else { + while (height--) { + for (int xc = 0; xc < width; xc++) + if (source[xc] != 0) + dest[xc] = source[xc]; + source += surface->pitch; + dest += _backScreen->pitch; + } + } + +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/screen.h b/engines/neverhood/screen.h new file mode 100644 index 0000000000..c778066152 --- /dev/null +++ b/engines/neverhood/screen.h @@ -0,0 +1,106 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 NEVERHOOD_SCREEN_H +#define NEVERHOOD_SCREEN_H + +#include "common/array.h" +#include "graphics/surface.h" +#include "video/smk_decoder.h" +#include "neverhood/neverhood.h" +#include "neverhood/microtiles.h" +#include "neverhood/graphics.h" + +namespace Neverhood { + +struct RenderItem { + const Graphics::Surface *_surface; + const Graphics::Surface *_shadowSurface; + int16 _destX, _destY; + int16 _srcX, _srcY, _width, _height; + bool _transparent; + byte _version; + bool _refresh; + bool operator==(const RenderItem &second) const { + return + _surface == second._surface && + _shadowSurface == second._shadowSurface && + _destX == second._destX && + _destY == second._destY && + _srcX == second._srcX && + _srcY == second._srcY && + _width == second._width && + _height == second._height && + _transparent == second._transparent && + _version == second._version; + } +}; + +typedef Common::Array<RenderItem> RenderQueue; + +class Screen { +public: + Screen(NeverhoodEngine *vm); + ~Screen(); + void update(); + uint32 getNextFrameTime(); + void saveParams(); + void restoreParams(); + void setFps(int fps); + int getFps(); + void setYOffset(int16 yOffset); + int16 getYOffset(); + void setPaletteData(byte *paletteData); + void unsetPaletteData(byte *paletteData); + byte *getPaletteData() { return _paletteData; } + void testPalette(byte *paletteData); + void updatePalette(); + void clear(); + void clearRenderQueue(); + void drawSurface2(const Graphics::Surface *surface, NDrawRect &drawRect, NRect &clipRect, bool transparent, byte version, + const Graphics::Surface *shadowSurface = NULL); + void drawSurface3(const Graphics::Surface *surface, int16 x, int16 y, NDrawRect &drawRect, NRect &clipRect, bool transparent, byte version); + void drawShadowSurface(const Graphics::Surface *surface, const Graphics::Surface *shadowSurface, int16 x, int16 y, NDrawRect &drawRect, NRect &clipRect); + void drawDoubleSurface2(const Graphics::Surface *surface, NDrawRect &drawRect); + void drawUnk(const Graphics::Surface *surface, NDrawRect &drawRect, NDrawRect &sysRect, NRect &clipRect, bool transparent, byte version); + void drawSurfaceClipRects(const Graphics::Surface *surface, NDrawRect &drawRect, NRect *clipRects, uint clipRectsCount, bool transparent, byte version); + void setSmackerDecoder(Video::SmackerDecoder *smackerDecoder) { _smackerDecoder = smackerDecoder; } + void queueBlit(const Graphics::Surface *surface, int16 destX, int16 destY, NRect &ddRect, bool transparent, byte version, + const Graphics::Surface *shadowSurface = NULL); + void blitRenderItem(const RenderItem &renderItem, const Common::Rect &clipRect); +protected: + NeverhoodEngine *_vm; + MicroTileArray *_microTiles; + Graphics::Surface *_backScreen; + Video::SmackerDecoder *_smackerDecoder, *_savedSmackerDecoder; + int32 _ticks; + int32 _frameDelay, _savedFrameDelay; + byte *_paletteData; + bool _paletteChanged; + int16 _yOffset, _savedYOffset; + bool _fullRefresh; + RenderQueue *_renderQueue, *_prevRenderQueue; +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_SCREEN_H */ diff --git a/engines/neverhood/smackerplayer.cpp b/engines/neverhood/smackerplayer.cpp new file mode 100644 index 0000000000..b67c8db9fc --- /dev/null +++ b/engines/neverhood/smackerplayer.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 "graphics/palette.h" +#include "neverhood/smackerplayer.h" +#include "neverhood/palette.h" +#include "neverhood/resourceman.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +// SmackerSurface + +SmackerSurface::SmackerSurface(NeverhoodEngine *vm) + : BaseSurface(vm, 0, 0, 0), _smackerFrame(NULL) { +} + +void SmackerSurface::draw() { + if (_smackerFrame && _visible && _drawRect.width > 0 && _drawRect.height > 0) + _vm->_screen->drawSurface2(_smackerFrame, _drawRect, _clipRect, false, ++_version); +} + +void SmackerSurface::setSmackerFrame(const Graphics::Surface *smackerFrame) { + _drawRect.x = 0; + _drawRect.y = 0; + _drawRect.width = smackerFrame->w; + _drawRect.height = smackerFrame->h; + _sysRect.x = 0; + _sysRect.y = 0; + _sysRect.width = (smackerFrame->w + 3) & 0xFFFC; // align by 4 bytes + _sysRect.height = smackerFrame->h; + _smackerFrame = smackerFrame; +} + +// SmackerDoubleSurface + +SmackerDoubleSurface::SmackerDoubleSurface(NeverhoodEngine *vm) + : SmackerSurface(vm) { +} + +void SmackerDoubleSurface::draw() { + if (_smackerFrame && _visible && _drawRect.width > 0 && _drawRect.height > 0) + _vm->_screen->drawDoubleSurface2(_smackerFrame, _drawRect); +} + +void NeverhoodSmackerDecoder::forceSeekToFrame(uint frame) { + if (!isVideoLoaded()) + return; + + if (frame >= getFrameCount()) + error("Can't force Smacker seek to invalid frame %d", frame); + + if (_header.audioInfo[0].hasAudio) + error("Can't force Smacker frame seek with audio"); + if (!rewind()) + error("Failed to rewind"); + + SmackerVideoTrack *videoTrack = (SmackerVideoTrack *)getTrack(0); + uint32 offset = 0; + for (uint32 i = 0; i < frame; i++) { + videoTrack->increaseCurFrame(); + offset += _frameSizes[i] & ~3; + } + + _fileStream->seek(offset, SEEK_CUR); +} + +// SmackerPlayer + +SmackerPlayer::SmackerPlayer(NeverhoodEngine *vm, Scene *scene, uint32 fileHash, bool doubleSurface, bool flag, bool paused) + : Entity(vm, 0), _scene(scene), _doubleSurface(doubleSurface), _videoDone(false), _paused(paused), + _palette(NULL), _smackerDecoder(NULL), _smackerSurface(NULL), _stream(NULL), _smackerFirst(true), + _drawX(-1), _drawY(-1) { + + SetUpdateHandler(&SmackerPlayer::update); + open(fileHash, flag); +} + +SmackerPlayer::~SmackerPlayer() { + close(); +} + +void SmackerPlayer::open(uint32 fileHash, bool keepLastFrame) { + debug(0, "SmackerPlayer::open(%08X)", fileHash); + + _fileHash = fileHash; + _keepLastFrame = keepLastFrame; + + close(); + + if (_doubleSurface) { + _smackerSurface = new SmackerDoubleSurface(_vm); + } else { + _smackerSurface = new SmackerSurface(_vm); + } + + _smackerFirst = true; + + _stream = _vm->_res->createStream(fileHash); + + _smackerDecoder = new NeverhoodSmackerDecoder(); + _smackerDecoder->loadStream(_stream); + + _palette = new Palette(_vm); + _palette->usePalette(); + + if (!_paused) + _smackerDecoder->start(); + +} + +void SmackerPlayer::close() { + if (_smackerDecoder) + _smackerDecoder->stop(); + delete _smackerDecoder; + delete _palette; + // NOTE The SmackerDecoder deletes the _stream + delete _smackerSurface; + _smackerDecoder = NULL; + _palette = NULL; + _stream = NULL; + _smackerSurface = NULL; +} + +void SmackerPlayer::gotoFrame(int frameNumber) { + if (_smackerDecoder) { + _smackerDecoder->forceSeekToFrame(frameNumber); + _smackerDecoder->decodeNextFrame(); + } +} + +uint32 SmackerPlayer::getFrameCount() { + return _smackerDecoder ? _smackerDecoder->getFrameCount() : 0; +} + +uint32 SmackerPlayer::getFrameNumber() { + return _smackerDecoder ? _smackerDecoder->getCurFrame() : 0; +} + +uint SmackerPlayer::getStatus() { + return 0; +} + +void SmackerPlayer::setDrawPos(int16 x, int16 y) { + _drawX = x; + _drawY = y; + if (_smackerSurface) { + _smackerSurface->getDrawRect().x = _drawX; + _smackerSurface->getDrawRect().y = _drawY; + } +} + +void SmackerPlayer::rewind() { + if (_smackerDecoder) + _smackerDecoder->rewind(); +} + +void SmackerPlayer::update() { + + if (!_smackerDecoder) + return; + + if (_paused) { + if (_smackerFirst) + updateFrame(); + } else { + if (!_smackerDecoder->endOfVideo()) { + updateFrame(); + } else if (!_keepLastFrame) { + // Inform the scene about the end of the video playback + if (_scene) + sendMessage(_scene, 0x3002, 0); + _videoDone = true; + } else { + rewind(); + updateFrame(); + _videoDone = false; + } + } + +} + +void SmackerPlayer::updateFrame() { + const Graphics::Surface *smackerFrame = _smackerDecoder->decodeNextFrame(); + + if (_smackerFirst) { + _smackerSurface->setSmackerFrame(smackerFrame); + if (_drawX < 0 || _drawY < 0) { + if (_doubleSurface) { + _drawX = 320 - _smackerDecoder->getWidth(); + _drawY = 240 - _smackerDecoder->getHeight(); + } else { + _drawX = (640 - _smackerDecoder->getWidth()) / 2; + _drawY = (480 - _smackerDecoder->getHeight()) / 2; + } + } + _smackerSurface->getDrawRect().x = _drawX; + _smackerSurface->getDrawRect().y = _drawY; + _smackerFirst = false; + } + + if (_smackerDecoder->hasDirtyPalette()) + updatePalette(); + +} + +void SmackerPlayer::updatePalette() { + byte tempPalette[1024]; + const byte *smackerPalette = _smackerDecoder->getPalette(); + for (int i = 0; i < 256; i++) { + tempPalette[i * 4 + 0] = smackerPalette[i * 3 + 0]; + tempPalette[i * 4 + 1] = smackerPalette[i * 3 + 1]; + tempPalette[i * 4 + 2] = smackerPalette[i * 3 + 2]; + } + _palette->copyPalette(tempPalette, 0, 256, 0); +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/smackerplayer.h b/engines/neverhood/smackerplayer.h new file mode 100644 index 0000000000..26ebff5d33 --- /dev/null +++ b/engines/neverhood/smackerplayer.h @@ -0,0 +1,90 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_SMACKERPLAYER_H +#define NEVERHOOD_SMACKERPLAYER_H + +#include "video/smk_decoder.h" +#include "neverhood/neverhood.h" +#include "neverhood/entity.h" + +namespace Neverhood { + +class Scene; +class Palette; + +class SmackerSurface : public BaseSurface { +public: + SmackerSurface(NeverhoodEngine *vm); + virtual void draw(); + void setSmackerFrame(const Graphics::Surface *smackerFrame); +protected: + const Graphics::Surface *_smackerFrame; +}; + +class SmackerDoubleSurface : public SmackerSurface { +public: + SmackerDoubleSurface(NeverhoodEngine *vm); + virtual void draw(); +}; + +class NeverhoodSmackerDecoder : public Video::SmackerDecoder { +public: + void forceSeekToFrame(uint frame); +}; + +class SmackerPlayer : public Entity { +public: + SmackerPlayer(NeverhoodEngine *vm, Scene *scene, uint32 fileHash, bool doubleSurface, bool flag, bool paused = false); + ~SmackerPlayer(); + BaseSurface *getSurface() { return _smackerSurface; } + void open(uint32 fileHash, bool keepLastFrame); + void close(); + void gotoFrame(int frameNumber); + uint32 getFrameCount(); + uint32 getFrameNumber(); + uint getStatus(); + void setDrawPos(int16 x, int16 y); + void rewind(); + bool isDone() { return getFrameNumber() + 1 == getFrameCount(); } + NeverhoodSmackerDecoder *getSmackerDecoder() const { return _smackerDecoder; } +protected: + Scene *_scene; + Palette *_palette; + NeverhoodSmackerDecoder *_smackerDecoder; + SmackerSurface *_smackerSurface; + uint32 _fileHash; + bool _smackerFirst; + bool _doubleSurface; + Common::SeekableReadStream *_stream; + bool _keepLastFrame; + bool _videoDone; + bool _paused; + int _drawX, _drawY; + void update(); + void updateFrame(); + void updatePalette(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_SMACKERPLAYER_H */ diff --git a/engines/neverhood/smackerscene.cpp b/engines/neverhood/smackerscene.cpp new file mode 100644 index 0000000000..115aafe5be --- /dev/null +++ b/engines/neverhood/smackerscene.cpp @@ -0,0 +1,122 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 "neverhood/smackerscene.h" + +namespace Neverhood { + +SmackerScene::SmackerScene(NeverhoodEngine *vm, Module *parentModule, bool doubleSurface, bool canSkip, bool canAbort) + : Scene(vm, parentModule), _doubleSurface(doubleSurface), _canSkip(canSkip), _canAbort(canAbort), _videoPlayedBefore(false), + _fileHashListIndex(-1), _fileHashList(NULL), _playNextVideoFlag(false) { + + debug(0, "SmackerScene::SmackerScene(%d, %d, %d)", doubleSurface, canSkip, canAbort); + + // NOTE: Merged from SmackerScene::init, maybe split again if needed (incl. parameter flags) + + if (getGlobalVar(V_SMACKER_CAN_ABORT)) { + _canSkip = true; + _canAbort = true; + } + + if (!_doubleSurface) + _vm->_screen->clear(); + + _fileHash[0] = 0; + _fileHash[1] = 0; + + SetUpdateHandler(&SmackerScene::update); + SetMessageHandler(&SmackerScene::handleMessage); + +} + +SmackerScene::~SmackerScene() { + +} + +void SmackerScene::setFileHash(uint32 fileHash) { + debug(0, "SmackerScene::setFileHash(%08X)", fileHash); + _fileHash[0] = fileHash; + _fileHashList = _fileHash; +} + +void SmackerScene::setFileHashList(const uint32 *fileHashList) { + debug(0, "SmackerScene::setFileHashList(...)"); + _fileHashList = fileHashList; +} + +void SmackerScene::nextVideo() { + debug(0, "SmackerScene::nextVideo()"); + + _fileHashListIndex++; + + if (_fileHashList && _fileHashList[_fileHashListIndex] != 0) { + uint32 smackerFileHash = _fileHashList[_fileHashListIndex]; + ResourceHandle resourceHandle; + _vm->_res->queryResource(smackerFileHash, resourceHandle); + if (resourceHandle.type() != kResTypeVideo) { + // Not a Smacker file + _vm->_screen->setSmackerDecoder(NULL); + sendMessage(_parentModule, 0x1009, 0); + return; + } + _videoPlayedBefore = getSubVar(VA_SMACKER_PLAYED, smackerFileHash); + if (!_videoPlayedBefore) + setSubVar(VA_SMACKER_PLAYED, smackerFileHash, 1); + if (_fileHashListIndex == 0) + _smackerPlayer = addSmackerPlayer(new SmackerPlayer(_vm, this, smackerFileHash, _doubleSurface, false)); + else + _smackerPlayer->open(smackerFileHash, false); + _vm->_screen->setSmackerDecoder(_smackerPlayer->getSmackerDecoder()); + } else { + _vm->_screen->setSmackerDecoder(NULL); + sendMessage(_parentModule, 0x1009, 0); + } + +} + +void SmackerScene::update() { + if (_playNextVideoFlag) { + nextVideo(); + _playNextVideoFlag = false; + } + Scene::update(); +} + +uint32 SmackerScene::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + uint32 messageResult = Scene::handleMessage(messageNum, param, sender); + switch (messageNum) { + case 0x0009: + if ((_videoPlayedBefore && _canSkip) || (_canAbort && _canSkip)) + _playNextVideoFlag = true; + break; + case 0x000C: + if (_canAbort) + sendMessage(_parentModule, 0x1009, 0); + break; + case 0x3002: + _playNextVideoFlag = true; + break; + } + return messageResult; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/smackerscene.h b/engines/neverhood/smackerscene.h new file mode 100644 index 0000000000..7ed2e0262b --- /dev/null +++ b/engines/neverhood/smackerscene.h @@ -0,0 +1,54 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 NEVERHOOD_SMACKERSCENE_H +#define NEVERHOOD_SMACKERSCENE_H + +#include "neverhood/neverhood.h" +#include "neverhood/resourceman.h" +#include "neverhood/scene.h" + +namespace Neverhood { + +class SmackerScene : public Scene { +public: + SmackerScene(NeverhoodEngine *vm, Module *parentModule, bool doubleSurface, bool canSkip, bool canAbort); + virtual ~SmackerScene(); + void setFileHash(uint32 fileHash); + void setFileHashList(const uint32 *fileHashList); + void nextVideo(); +protected: + bool _doubleSurface; + bool _canSkip; + bool _canAbort; + bool _videoPlayedBefore; + bool _playNextVideoFlag; + int _fileHashListIndex; + const uint32 *_fileHashList; + uint32 _fileHash[2]; + void update(); + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_SMACKERSCENE_H */ diff --git a/engines/neverhood/sound.cpp b/engines/neverhood/sound.cpp new file mode 100644 index 0000000000..c84b751e44 --- /dev/null +++ b/engines/neverhood/sound.cpp @@ -0,0 +1,788 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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/memstream.h" +#include "graphics/palette.h" +#include "neverhood/sound.h" +#include "neverhood/resourceman.h" + +namespace Neverhood { + +SoundResource::SoundResource(NeverhoodEngine *vm) + : _vm(vm), _soundIndex(-1) { +} + +SoundResource::~SoundResource() { + unload(); +} + +bool SoundResource::isPlaying() { + AudioResourceManSoundItem *soundItem = getSoundItem(); + return soundItem ? soundItem->isPlaying() : false; +} + +void SoundResource::load(uint32 fileHash) { + unload(); + _soundIndex = _vm->_audioResourceMan->addSound(fileHash); + AudioResourceManSoundItem *soundItem = getSoundItem(); + if (soundItem) + soundItem->loadSound(); +} + +void SoundResource::unload() { + if (_soundIndex >= 0) { + _vm->_audioResourceMan->removeSound(_soundIndex); + _soundIndex = -1; + } +} + +void SoundResource::play(uint32 fileHash) { + load(fileHash); + play(); +} + +void SoundResource::play() { + AudioResourceManSoundItem *soundItem = getSoundItem(); + if (soundItem) + soundItem->playSound(false); +} + +void SoundResource::stop() { + AudioResourceManSoundItem *soundItem = getSoundItem(); + if (soundItem) + soundItem->stopSound(); +} + +void SoundResource::setVolume(int16 volume) { + AudioResourceManSoundItem *soundItem = getSoundItem(); + if (soundItem) + soundItem->setVolume(volume); +} + +void SoundResource::setPan(int16 pan) { + AudioResourceManSoundItem *soundItem = getSoundItem(); + if (soundItem) + soundItem->setPan(pan); +} + +AudioResourceManSoundItem *SoundResource::getSoundItem() { + return _vm->_audioResourceMan->getSoundItem(_soundIndex); +} + +MusicResource::MusicResource(NeverhoodEngine *vm) + : _vm(vm), _musicIndex(-1) { +} + +bool MusicResource::isPlaying() { + AudioResourceManMusicItem *musicItem = getMusicItem(); + return musicItem && musicItem->isPlaying(); +} + +void MusicResource::load(uint32 fileHash) { + unload(); + _musicIndex = _vm->_audioResourceMan->loadMusic(fileHash); +} + +void MusicResource::unload() { + AudioResourceManMusicItem *musicItem = getMusicItem(); + if (musicItem) { + musicItem->unloadMusic(); + _musicIndex = -1; + } +} + +void MusicResource::play(int16 fadeVolumeStep) { + AudioResourceManMusicItem *musicItem = getMusicItem(); + if (musicItem) + musicItem->playMusic(fadeVolumeStep); +} + +void MusicResource::stop(int16 fadeVolumeStep) { + AudioResourceManMusicItem *musicItem = getMusicItem(); + if (musicItem) + musicItem->stopMusic(fadeVolumeStep); +} + +void MusicResource::setVolume(int16 volume) { + AudioResourceManMusicItem *musicItem = getMusicItem(); + if (musicItem) + musicItem->setVolume(volume); +} + +AudioResourceManMusicItem *MusicResource::getMusicItem() { + return _vm->_audioResourceMan->getMusicItem(_musicIndex); +} + +MusicItem::MusicItem(NeverhoodEngine *vm, uint32 groupNameHash, uint32 musicFileHash) + : _vm(vm), _musicResource(NULL) { + + _groupNameHash = groupNameHash; + _fileHash = musicFileHash; + _play = false; + _stop = false; + _fadeVolumeStep = 0; + _countdown = 24; + _musicResource = new MusicResource(_vm); + _musicResource->load(musicFileHash); +} + +MusicItem::~MusicItem() { + if (_musicResource) + _musicResource->unload(); + delete _musicResource; +} + +void MusicItem::startMusic(int16 countdown, int16 fadeVolumeStep) { + _play = true; + _stop = false; + _countdown = countdown; + _fadeVolumeStep = fadeVolumeStep; +} + +void MusicItem::stopMusic(int16 countdown, int16 fadeVolumeStep) { + _play = false; + _stop = true; + _countdown = countdown; + _fadeVolumeStep = fadeVolumeStep; +} + +void MusicItem::update() { + if (_countdown) { + --_countdown; + } else if (_play && !_musicResource->isPlaying()) { + debug(1, "MusicItem: play music %08X (fade %d)", _fileHash, _fadeVolumeStep); + _musicResource->play(_fadeVolumeStep); + _fadeVolumeStep = 0; + } else if (_stop) { + debug(1, "MusicItem: stop music %08X (fade %d)", _fileHash, _fadeVolumeStep); + _musicResource->stop(_fadeVolumeStep); + _fadeVolumeStep = 0; + _stop = false; + } +} + +SoundItem::SoundItem(NeverhoodEngine *vm, uint32 groupNameHash, uint32 soundFileHash, + bool playOnceAfterRandomCountdown, int16 minCountdown, int16 maxCountdown, + bool playOnceAfterCountdown, int16 initialCountdown, bool playLooping, int16 currCountdown) + : _vm(vm), _soundResource(NULL), _groupNameHash(groupNameHash), _fileHash(soundFileHash), + _playOnceAfterRandomCountdown(false), _minCountdown(0), _maxCountdown(0), + _playOnceAfterCountdown(playOnceAfterCountdown), _initialCountdown(initialCountdown), + _playLooping(false), _currCountdown(currCountdown) { + + _soundResource = new SoundResource(vm); + _soundResource->load(soundFileHash); +} + +SoundItem::~SoundItem() { + if (_soundResource) + _soundResource->unload(); + delete _soundResource; +} + +void SoundItem::setSoundParams(bool playOnceAfterRandomCountdown, int16 minCountdown, int16 maxCountdown, + int16 firstMinCountdown, int16 firstMaxCountdown) { + + _playOnceAfterCountdown = false; + _playLooping = false; + _playOnceAfterRandomCountdown = playOnceAfterRandomCountdown; + if (minCountdown > 0) + _minCountdown = minCountdown; + if (maxCountdown > 0) + _maxCountdown = maxCountdown; + if (firstMinCountdown >= firstMaxCountdown) + _currCountdown = firstMinCountdown; + else if (firstMinCountdown > 0 && firstMaxCountdown > 0 && firstMinCountdown < firstMaxCountdown) + _currCountdown = _vm->_rnd->getRandomNumberRng(firstMinCountdown, firstMaxCountdown); +} + +void SoundItem::playSoundLooping() { + _playOnceAfterRandomCountdown = false; + _playOnceAfterCountdown = false; + _playLooping = true; +} + +void SoundItem::stopSound() { + _playOnceAfterRandomCountdown = false; + _playOnceAfterCountdown = false; + _playLooping = false; + _soundResource->stop(); +} + +void SoundItem::setVolume(int volume) { + _soundResource->setVolume(volume); +} + +void SoundItem::update() { + if (_playOnceAfterCountdown) { + if (_currCountdown == 0) + _currCountdown = _initialCountdown; + else if (--_currCountdown == 0) + _soundResource->play(); + } else if (_playOnceAfterRandomCountdown) { + if (_currCountdown == 0) { + if (_minCountdown > 0 && _maxCountdown > 0 && _minCountdown < _maxCountdown) + _currCountdown = _vm->_rnd->getRandomNumberRng(_minCountdown, _maxCountdown); + } else if (--_currCountdown == 0) + _soundResource->play(); + } else if (_playLooping && !_soundResource->isPlaying()) + _soundResource->play(); +} + +// SoundMan + +SoundMan::SoundMan(NeverhoodEngine *vm) + : _vm(vm), _soundIndex1(-1), _soundIndex2(-1), _soundIndex3(-1) { +} + +SoundMan::~SoundMan() { + for (uint i = 0; i < _soundItems.size(); ++i) + delete _soundItems[i]; + for (uint i = 0; i < _musicItems.size(); ++i) + delete _musicItems[i]; +} + +void SoundMan::addMusic(uint32 groupNameHash, uint32 musicFileHash) { + addMusicItem(new MusicItem(_vm, groupNameHash, musicFileHash)); +} + +void SoundMan::deleteMusic(uint32 musicFileHash) { + MusicItem *musicItem = getMusicItemByHash(musicFileHash); + if (musicItem) { + delete musicItem; + for (uint i = 0; i < _musicItems.size(); ++i) + if (_musicItems[i] == musicItem) { + _musicItems[i] = NULL; + break; + } + } +} + +void SoundMan::startMusic(uint32 musicFileHash, int16 countdown, int16 fadeVolumeStep) { + MusicItem *musicItem = getMusicItemByHash(musicFileHash); + if (musicItem) + musicItem->startMusic(countdown, fadeVolumeStep); +} + +void SoundMan::stopMusic(uint32 musicFileHash, int16 countdown, int16 fadeVolumeStep) { + MusicItem *musicItem = getMusicItemByHash(musicFileHash); + if (musicItem) + musicItem->stopMusic(countdown, fadeVolumeStep); +} + +void SoundMan::addSound(uint32 groupNameHash, uint32 soundFileHash) { + addSoundItem(new SoundItem(_vm, groupNameHash, soundFileHash, false, 50, 600, false, 0, false, 0)); +} + +void SoundMan::addSoundList(uint32 groupNameHash, const uint32 *soundFileHashList) { + while (*soundFileHashList) + addSound(groupNameHash, *soundFileHashList++); +} + +void SoundMan::deleteSound(uint32 soundFileHash) { + SoundItem *soundItem = getSoundItemByHash(soundFileHash); + if (soundItem) { + delete soundItem; + for (uint i = 0; i < _soundItems.size(); ++i) + if (_soundItems[i] == soundItem) { + _soundItems[i] = NULL; + break; + } + } +} + +void SoundMan::setSoundParams(uint32 soundFileHash, bool playOnceAfterRandomCountdown, + int16 minCountdown, int16 maxCountdown, int16 firstMinCountdown, int16 firstMaxCountdown) { + + SoundItem *soundItem = getSoundItemByHash(soundFileHash); + if (soundItem) + soundItem->setSoundParams(playOnceAfterRandomCountdown, minCountdown, maxCountdown, + firstMinCountdown, firstMaxCountdown); +} + +void SoundMan::setSoundListParams(const uint32 *soundFileHashList, bool playOnceAfterRandomCountdown, + int16 minCountdown, int16 maxCountdown, int16 firstMinCountdown, int16 firstMaxCountdown) { + + while (*soundFileHashList) + setSoundParams(*soundFileHashList++, playOnceAfterRandomCountdown, + minCountdown, maxCountdown, firstMinCountdown, firstMaxCountdown); +} + +void SoundMan::playSoundLooping(uint32 soundFileHash) { + SoundItem *soundItem = getSoundItemByHash(soundFileHash); + if (soundItem) + soundItem->playSoundLooping(); +} + +void SoundMan::stopSound(uint32 soundFileHash) { + SoundItem *soundItem = getSoundItemByHash(soundFileHash); + if (soundItem) + soundItem->stopSound(); +} + +void SoundMan::setSoundVolume(uint32 soundFileHash, int volume) { + SoundItem *soundItem = getSoundItemByHash(soundFileHash); + if (soundItem) + soundItem->setVolume(volume); +} + +void SoundMan::update() { + + for (uint i = 0; i < _soundItems.size(); ++i) { + SoundItem *soundItem = _soundItems[i]; + if (soundItem) + soundItem->update(); + } + + for (uint i = 0; i < _musicItems.size(); ++i) { + MusicItem *musicItem = _musicItems[i]; + if (musicItem) + musicItem->update(); + } + +} + +void SoundMan::deleteGroup(uint32 groupNameHash) { + deleteMusicGroup(groupNameHash); + deleteSoundGroup(groupNameHash); +} + +void SoundMan::deleteMusicGroup(uint32 groupNameHash) { + for (uint index = 0; index < _musicItems.size(); ++index) { + MusicItem *musicItem = _musicItems[index]; + if (musicItem && musicItem->getGroupNameHash() == groupNameHash) { + delete musicItem; + _musicItems[index] = NULL; + } + } +} + +void SoundMan::deleteSoundGroup(uint32 groupNameHash) { + + if (_soundIndex1 != -1 && _soundItems[_soundIndex1]->getGroupNameHash() == groupNameHash) { + deleteSoundByIndex(_soundIndex1); + _soundIndex1 = -1; + } + + if (_soundIndex2 != -1 && _soundItems[_soundIndex2]->getGroupNameHash() == groupNameHash) { + deleteSoundByIndex(_soundIndex2); + _soundIndex2 = -1; + } + + for (uint index = 0; index < _soundItems.size(); ++index) + if (_soundItems[index] && _soundItems[index]->getGroupNameHash() == groupNameHash) + deleteSoundByIndex(index); + +} + +void SoundMan::playTwoSounds(uint32 groupNameHash, uint32 soundFileHash1, uint32 soundFileHash2, int16 initialCountdown) { + + int16 currCountdown1 = _initialCountdown; + int16 currCountdown2 = _initialCountdown / 2; + + if (_soundIndex1 != -1) { + currCountdown1 = _soundItems[_soundIndex1]->getCurrCountdown(); + deleteSoundByIndex(_soundIndex1); + _soundIndex1 = -1; + } + + if (_soundIndex2 != -1) { + currCountdown2 = _soundItems[_soundIndex2]->getCurrCountdown(); + deleteSoundByIndex(_soundIndex2); + _soundIndex2 = -1; + } + + if (initialCountdown > 0) + _initialCountdown = initialCountdown; + + if (soundFileHash1 != 0) { + SoundItem *soundItem = new SoundItem(_vm, groupNameHash, soundFileHash1, false, 0, 0, + _playOnceAfterCountdown, _initialCountdown, false, currCountdown1); + soundItem->setVolume(80); + _soundIndex1 = addSoundItem(soundItem); + } + + if (soundFileHash2 != 0) { + SoundItem *soundItem = new SoundItem(_vm, groupNameHash, soundFileHash2, false, 0, 0, + _playOnceAfterCountdown, _initialCountdown, false, currCountdown2); + soundItem->setVolume(80); + _soundIndex2 = addSoundItem(soundItem); + } + +} + +void SoundMan::playSoundThree(uint32 groupNameHash, uint32 soundFileHash) { + + if (_soundIndex3 != -1) { + deleteSoundByIndex(_soundIndex3); + _soundIndex3 = -1; + } + + if (soundFileHash != 0) { + SoundItem *soundItem = new SoundItem(_vm, groupNameHash, soundFileHash, false, 0, 0, false, _initialCountdown3, false, 0); + _soundIndex3 = addSoundItem(soundItem); + } + +} + +void SoundMan::setTwoSoundsPlayFlag(bool playOnceAfterCountdown) { + if (_soundIndex1 != -1) + _soundItems[_soundIndex1]->setPlayOnceAfterCountdown(playOnceAfterCountdown); + if (_soundIndex2 != -1) + _soundItems[_soundIndex2]->setPlayOnceAfterCountdown(playOnceAfterCountdown); + _playOnceAfterCountdown = playOnceAfterCountdown; +} + +void SoundMan::setSoundThreePlayFlag(bool playOnceAfterCountdown) { + if (_soundIndex3 != -1) + _soundItems[_soundIndex3]->setPlayOnceAfterCountdown(playOnceAfterCountdown); + _playOnceAfterCountdown3 = playOnceAfterCountdown; +} + +MusicItem *SoundMan::getMusicItemByHash(uint32 musicFileHash) { + for (uint i = 0; i < _musicItems.size(); ++i) + if (_musicItems[i] && _musicItems[i]->getFileHash() == musicFileHash) + return _musicItems[i]; + return NULL; +} + +SoundItem *SoundMan::getSoundItemByHash(uint32 soundFileHash) { + for (uint i = 0; i < _soundItems.size(); ++i) + if (_soundItems[i] && _soundItems[i]->getFileHash() == soundFileHash) + return _soundItems[i]; + return NULL; +} + +int16 SoundMan::addMusicItem(MusicItem *musicItem) { + for (uint i = 0; i < _musicItems.size(); ++i) + if (!_musicItems[i]) { + _musicItems[i] = musicItem; + return i; + } + int16 musicIndex = _musicItems.size(); + _musicItems.push_back(musicItem); + return musicIndex; +} + +int16 SoundMan::addSoundItem(SoundItem *soundItem) { + for (uint i = 0; i < _soundItems.size(); ++i) + if (!_soundItems[i]) { + _soundItems[i] = soundItem; + return i; + } + int16 soundIndex = _soundItems.size(); + _soundItems.push_back(soundItem); + return soundIndex; +} + +void SoundMan::deleteSoundByIndex(int index) { + delete _soundItems[index]; + _soundItems[index] = NULL; +} + +// NeverhoodAudioStream + +NeverhoodAudioStream::NeverhoodAudioStream(int rate, byte shiftValue, bool isLooping, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream) + : _rate(rate), _shiftValue(shiftValue), _isLooping(isLooping), _isStereo(false), _stream(stream, disposeStream), _endOfData(false), _buffer(0), + _isCompressed(_shiftValue != 0xFF), _prevValue(0) { + // Setup our buffer for readBuffer + _buffer = new byte[kSampleBufferLength * (_isCompressed ? 1 : 2)]; + assert(_buffer); +} + +NeverhoodAudioStream::~NeverhoodAudioStream() { + delete[] _buffer; +} + +int NeverhoodAudioStream::readBuffer(int16 *buffer, const int numSamples) { + int samplesLeft = numSamples; + + while (samplesLeft > 0 && !_endOfData) { + + const int maxSamples = MIN<int>(kSampleBufferLength, samplesLeft); + const int bytesToRead = maxSamples * (_isCompressed ? 1 : 2); + int bytesRead = _stream->read(_buffer, bytesToRead); + int samplesRead = bytesRead / (_isCompressed ? 1 : 2); + + samplesLeft -= samplesRead; + + const byte *src = _buffer; + if (_isCompressed) { + while (samplesRead--) { + _prevValue += (int8)(*src++); + *buffer++ = _prevValue << _shiftValue; + } + } else { + memcpy(buffer, _buffer, bytesRead); + buffer += bytesRead; + } + + if (bytesRead < bytesToRead || _stream->pos() >= _stream->size() || _stream->err() || _stream->eos()) { + if (_isLooping) + _stream->seek(0); + else + _endOfData = true; + } + + } + + return numSamples - samplesLeft; +} + +AudioResourceManSoundItem::AudioResourceManSoundItem(NeverhoodEngine *vm, uint32 fileHash) + : _vm(vm), _fileHash(fileHash), _data(NULL), _isLoaded(false), _isPlaying(false), + _volume(100), _panning(50) { + + _vm->_res->queryResource(_fileHash, _resourceHandle); +} + +void AudioResourceManSoundItem::loadSound() { + if (!_data && _resourceHandle.isValid() && + (_resourceHandle.type() == kResTypeSound || _resourceHandle.type() == kResTypeMusic)) { + _vm->_res->loadResource(_resourceHandle); + _data = _resourceHandle.data(); + } +} + +void AudioResourceManSoundItem::unloadSound() { + if (_vm->_mixer->isSoundHandleActive(_soundHandle)) + _vm->_mixer->stopHandle(_soundHandle); + _vm->_res->unloadResource(_resourceHandle); + _data = NULL; +} + +void AudioResourceManSoundItem::setVolume(int16 volume) { + _volume = MIN<int16>(volume, 100); + if (_isPlaying && _vm->_mixer->isSoundHandleActive(_soundHandle)) + _vm->_mixer->setChannelVolume(_soundHandle, VOLUME(_volume)); +} + +void AudioResourceManSoundItem::setPan(int16 pan) { + _panning = MIN<int16>(pan, 100); + if (_isPlaying && _vm->_mixer->isSoundHandleActive(_soundHandle)) + _vm->_mixer->setChannelVolume(_soundHandle, PANNING(_panning)); +} + +void AudioResourceManSoundItem::playSound(bool looping) { + if (!_data) + loadSound(); + if (_data) { + const byte *shiftValue = _resourceHandle.extData(); + Common::MemoryReadStream *stream = new Common::MemoryReadStream(_data, _resourceHandle.size(), DisposeAfterUse::NO); + NeverhoodAudioStream *audioStream = new NeverhoodAudioStream(22050, *shiftValue, false, DisposeAfterUse::YES, stream); + _vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, + audioStream, -1, VOLUME(_volume), PANNING(_panning)); + debug(1, "playing sound %08X", _fileHash); + _isPlaying = true; + } +} + +void AudioResourceManSoundItem::stopSound() { + if (_vm->_mixer->isSoundHandleActive(_soundHandle)) + _vm->_mixer->stopHandle(_soundHandle); + _isPlaying = false; +} + +bool AudioResourceManSoundItem::isPlaying() { + return _vm->_mixer->isSoundHandleActive(_soundHandle); +} + +AudioResourceManMusicItem::AudioResourceManMusicItem(NeverhoodEngine *vm, uint32 fileHash) + : _vm(vm), _fileHash(fileHash), _terminate(false), _canRestart(false), + _volume(100), _panning(50), _start(false), _isFadingIn(false), _isFadingOut(false), _isPlaying(false) { + +} + +void AudioResourceManMusicItem::playMusic(int16 fadeVolumeStep) { + if (!_isPlaying) { + _isFadingIn = false; + _isFadingOut = false; + if (fadeVolumeStep != 0) { + _isFadingIn = true; + _fadeVolume = 0; + _fadeVolumeStep = fadeVolumeStep; + } + _start = true; + _terminate = false; + } +} + +void AudioResourceManMusicItem::stopMusic(int16 fadeVolumeStep) { + if (_vm->_mixer->isSoundHandleActive(_soundHandle)) { + if (fadeVolumeStep != 0) { + if (_isFadingIn) + _isFadingIn = false; + else + _fadeVolume = _volume; + _isFadingOut = true; + _fadeVolumeStep = fadeVolumeStep; + } else { + _vm->_mixer->stopHandle(_soundHandle); + } + _isPlaying = false; + } +} + +void AudioResourceManMusicItem::unloadMusic() { + if (_isFadingOut) { + _canRestart = true; + } else { + if (_vm->_mixer->isSoundHandleActive(_soundHandle)) + _vm->_mixer->stopHandle(_soundHandle); + _isPlaying = false; + _terminate = true; + } +} + +void AudioResourceManMusicItem::setVolume(int16 volume) { + _volume = MIN<int16>(volume, 100); + if (_isPlaying && _vm->_mixer->isSoundHandleActive(_soundHandle)) + _vm->_mixer->setChannelVolume(_soundHandle, VOLUME(_volume)); +} + +void AudioResourceManMusicItem::restart() { + _canRestart = false; + _isFadingOut = false; + _isFadingIn = true; +} + +void AudioResourceManMusicItem::update() { + + if (_start && !_vm->_mixer->isSoundHandleActive(_soundHandle)) { + ResourceHandle resourceHandle; + _vm->_res->queryResource(_fileHash, resourceHandle); + Common::SeekableReadStream *stream = _vm->_res->createStream(_fileHash); + const byte *shiftValue = resourceHandle.extData(); + NeverhoodAudioStream *audioStream = new NeverhoodAudioStream(22050, *shiftValue, true, DisposeAfterUse::YES, stream); + _vm->_mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, + audioStream, -1, VOLUME(_isFadingIn ? _fadeVolume : _volume), + PANNING(_panning)); + _start = false; + _isPlaying = true; + } + + if (_vm->_mixer->isSoundHandleActive(_soundHandle)) { + if (_isFadingIn) { + _fadeVolume += _fadeVolumeStep; + if (_fadeVolume >= _volume) { + _fadeVolume = _volume; + _isFadingIn = false; + } + _vm->_mixer->setChannelVolume(_soundHandle, VOLUME(_fadeVolume)); + } + if (_isFadingOut) { + _fadeVolume -= _fadeVolumeStep; + if (_fadeVolume < 0) + _fadeVolume = 0; + _vm->_mixer->setChannelVolume(_soundHandle, VOLUME(_fadeVolume)); + if (_fadeVolume == 0) { + _isFadingOut = false; + stopMusic(0); + if (_canRestart) + unloadMusic(); + } + } + } + +} + +AudioResourceMan::AudioResourceMan(NeverhoodEngine *vm) + : _vm(vm) { +} + +AudioResourceMan::~AudioResourceMan() { + for (uint i = 0; i < _soundItems.size(); ++i) + delete _soundItems[i]; + for (uint i = 0; i < _musicItems.size(); ++i) + delete _musicItems[i]; +} + +int16 AudioResourceMan::addSound(uint32 fileHash) { + AudioResourceManSoundItem *soundItem = new AudioResourceManSoundItem(_vm, fileHash); + + for (uint i = 0; i < _soundItems.size(); ++i) + if (!_soundItems[i]) { + _soundItems[i] = soundItem; + return i; + } + + int16 soundIndex = (int16)_soundItems.size(); + _soundItems.push_back(soundItem); + return soundIndex; +} + +void AudioResourceMan::removeSound(int16 soundIndex) { + AudioResourceManSoundItem *soundItem = getSoundItem(soundIndex); + if (soundItem) { + soundItem->unloadSound(); + delete soundItem; + _soundItems[soundIndex] = NULL; + } +} + +int16 AudioResourceMan::loadMusic(uint32 fileHash) { + AudioResourceManMusicItem *musicItem; + + for (uint i = 0; i < _musicItems.size(); ++i) { + musicItem = _musicItems[i]; + if (musicItem && musicItem->getFileHash() == fileHash && musicItem->canRestart()) { + musicItem->restart(); + return i; + } + } + + musicItem = new AudioResourceManMusicItem(_vm, fileHash); + + for (uint i = 0; i < _musicItems.size(); ++i) { + if (!_musicItems[i]) { + _musicItems[i] = musicItem; + return i; + } + } + + int16 musicIndex = _musicItems.size(); + _musicItems.push_back(musicItem); + return musicIndex; + +} + +void AudioResourceMan::updateMusic() { + for (uint musicIndex = 0; musicIndex < _musicItems.size(); ++musicIndex) { + AudioResourceManMusicItem *musicItem = _musicItems[musicIndex]; + if (musicItem) { + musicItem->update(); + if (musicItem->isTerminated()) { + delete musicItem; + _musicItems[musicIndex] = NULL; + } + } + } +} + +AudioResourceManSoundItem *AudioResourceMan::getSoundItem(int16 index) { + return (index >= 0 && index < (int16)_soundItems.size()) ? _soundItems[index] : NULL; +} + +AudioResourceManMusicItem *AudioResourceMan::getMusicItem(int16 index) { + return (index >= 0 && index < (int16)_musicItems.size()) ? _musicItems[index] : NULL; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/sound.h b/engines/neverhood/sound.h new file mode 100644 index 0000000000..d3318998db --- /dev/null +++ b/engines/neverhood/sound.h @@ -0,0 +1,286 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_SOUND_H +#define NEVERHOOD_SOUND_H + +#include "audio/audiostream.h" +#include "common/array.h" +#include "graphics/surface.h" +#include "neverhood/neverhood.h" +#include "neverhood/resource.h" + +namespace Neverhood { + +// Convert volume from percent to 0..255 +#define VOLUME(volume) (Audio::Mixer::kMaxChannelVolume / 100 * (volume)) + +// Convert panning from percent (50% equals center) to -127..0..+127 +#define PANNING(panning) (254 / 100 * (panning) - 127) + +class AudioResourceManSoundItem; +class AudioResourceManMusicItem; +class AudioResourceMan; + +class SoundResource { +public: + SoundResource(NeverhoodEngine *vm); + ~SoundResource(); + bool isPlaying(); + void load(uint32 fileHash); + void unload(); + void play(uint32 fileHash); + void play(); + void stop(); + void setVolume(int16 volume); + void setPan(int16 pan); +protected: + NeverhoodEngine *_vm; + int16 _soundIndex; + AudioResourceManSoundItem *getSoundItem(); +}; + +class MusicResource { +public: + MusicResource(NeverhoodEngine *vm); + bool isPlaying(); + void load(uint32 fileHash); + void unload(); + void play(int16 fadeVolumeStep); + void stop(int16 fadeVolumeStep); + void setVolume(int16 volume); +protected: + NeverhoodEngine *_vm; + int16 _musicIndex; + AudioResourceManMusicItem *getMusicItem(); +}; + +class MusicItem { +public: + MusicItem(NeverhoodEngine *vm, uint32 groupNameHash, uint32 musicFileHash); + ~MusicItem(); + void startMusic(int16 countdown, int16 fadeVolumeStep); + void stopMusic(int16 countdown, int16 fadeVolumeStep); + void update(); + uint32 getGroupNameHash() const { return _groupNameHash; } + uint32 getFileHash() const { return _fileHash; } +protected: + NeverhoodEngine *_vm; + uint32 _groupNameHash; + uint32 _fileHash; + bool _play; + bool _stop; + int16 _fadeVolumeStep; + int16 _countdown; + MusicResource *_musicResource; +}; + +class SoundItem { +public: + SoundItem(NeverhoodEngine *vm, uint32 groupNameHash, uint32 soundFileHash, + bool playOnceAfterRandomCountdown, int16 minCountdown, int16 maxCountdown, + bool playOnceAfterCountdown, int16 initialCountdown, bool playLooping, int16 currCountdown); + ~SoundItem(); + void setSoundParams(bool playOnceAfterRandomCountdown, int16 minCountdown, int16 maxCountdown, + int16 firstMinCountdown, int16 firstMaxCountdown); + void playSoundLooping(); + void stopSound(); + void setVolume(int volume); + void update(); + void setPlayOnceAfterCountdown(bool playOnceAfterCountdown) { _playOnceAfterCountdown = playOnceAfterCountdown; } + uint32 getGroupNameHash() const { return _groupNameHash; } + uint32 getFileHash() const { return _fileHash; } + int16 getCurrCountdown() const { return _currCountdown; } +protected: + NeverhoodEngine *_vm; + uint32 _groupNameHash; + uint32 _fileHash; + bool _playOnceAfterRandomCountdown; + int16 _minCountdown; + int16 _maxCountdown; + bool _playOnceAfterCountdown; + int16 _initialCountdown; + bool _playLooping; + int16 _currCountdown; + SoundResource *_soundResource; +}; + +class SoundMan { +public: + SoundMan(NeverhoodEngine *vm); + ~SoundMan(); + + // Music + void addMusic(uint32 groupNameHash, uint32 musicFileHash); + void deleteMusic(uint32 musicFileHash); + void startMusic(uint32 musicFileHash, int16 countdown, int16 fadeVolumeStep); + void stopMusic(uint32 musicFileHash, int16 countdown, int16 fadeVolumeStep); + + // Sound + void addSound(uint32 groupNameHash, uint32 soundFileHash); + void addSoundList(uint32 groupNameHash, const uint32 *soundFileHashList); + void deleteSound(uint32 soundFileHash); + void setSoundParams(uint32 soundFileHash, bool playOnceAfterRandomCountdown, + int16 minCountdown, int16 maxCountdown, int16 firstMinCountdown, int16 firstMaxCountdown); + void setSoundListParams(const uint32 *soundFileHashList, bool playOnceAfterRandomCountdown, + int16 minCountdown, int16 maxCountdown, int16 firstMinCountdown, int16 firstMaxCountdown); + void playSoundLooping(uint32 soundFileHash); + void stopSound(uint32 soundFileHash); + void setSoundVolume(uint32 soundFileHash, int volume); + + // Misc + void update(); + void deleteGroup(uint32 groupNameHash); + void deleteMusicGroup(uint32 groupNameHash); + void deleteSoundGroup(uint32 groupNameHash); + void playTwoSounds(uint32 groupNameHash, uint32 soundFileHash1, uint32 soundFileHash2, int16 initialCountdown); + void playSoundThree(uint32 groupNameHash, uint32 soundFileHash); + void setTwoSoundsPlayFlag(bool playOnceAfterCountdown); + void setSoundThreePlayFlag(bool playOnceAfterCountdown); + +protected: + NeverhoodEngine *_vm; + + // TODO Find out what these special sounds are used for (door sounds?) + int _soundIndex1, _soundIndex2; + int16 _initialCountdown; + bool _playOnceAfterCountdown; + + int _soundIndex3; + int16 _initialCountdown3; + bool _playOnceAfterCountdown3; + + Common::Array<MusicItem*> _musicItems; + Common::Array<SoundItem*> _soundItems; + + MusicItem *getMusicItemByHash(uint32 musicFileHash); + SoundItem *getSoundItemByHash(uint32 soundFileHash); + int16 addMusicItem(MusicItem *musicItem); + int16 addSoundItem(SoundItem *soundItem); + void deleteSoundByIndex(int index); + +}; + +class NeverhoodAudioStream : public Audio::AudioStream { +public: + NeverhoodAudioStream(int rate, byte shiftValue, bool isLooping, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream); + ~NeverhoodAudioStream(); + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return _isStereo; } + bool endOfData() const { return _endOfData; } + int getRate() const { return _rate; } +private: + const int _rate; + const bool _isLooping; + const bool _isStereo; + const byte _shiftValue; + const bool _isCompressed; + int16 _prevValue; + Common::DisposablePtr<Common::SeekableReadStream> _stream; + bool _endOfData; + byte *_buffer; + enum { + kSampleBufferLength = 2048 + }; + int fillBuffer(int maxSamples); +}; + +// TODO Rename these + +class AudioResourceManSoundItem { +public: + AudioResourceManSoundItem(NeverhoodEngine *vm, uint32 fileHash); + void loadSound(); + void unloadSound(); + void setVolume(int16 volume); + void setPan(int16 pan); + void playSound(bool looping); + void stopSound(); + bool isPlaying(); +protected: + NeverhoodEngine *_vm; + uint32 _fileHash; + ResourceHandle _resourceHandle; + const byte *_data; + bool _isLoaded; + bool _isPlaying; + int16 _volume; + int16 _panning; + Audio::SoundHandle _soundHandle; +}; + +class AudioResourceManMusicItem { +public: + AudioResourceManMusicItem(NeverhoodEngine *vm, uint32 fileHash); + void playMusic(int16 fadeVolumeStep); + void stopMusic(int16 fadeVolumeStep); + void unloadMusic(); + void setVolume(int16 volume); + void restart(); + void update(); + bool isPlaying() const { return _isPlaying; } + bool canRestart() const { return _canRestart; } + bool isTerminated() const { return _terminate; } + uint32 getFileHash() const { return _fileHash; } +protected: + NeverhoodEngine *_vm; + uint32 _fileHash; + bool _isPlaying; + bool _canRestart; + bool _terminate; + int16 _volume; + int16 _panning; + bool _start; + bool _isFadingIn; + bool _isFadingOut; + int16 _fadeVolume; + int16 _fadeVolumeStep; + Audio::SoundHandle _soundHandle; +}; + +class AudioResourceMan { +public: + AudioResourceMan(NeverhoodEngine *vm); + ~AudioResourceMan(); + + int16 addSound(uint32 fileHash); + void removeSound(int16 soundIndex); + + int16 loadMusic(uint32 fileHash); + void updateMusic(); + + AudioResourceManSoundItem *getSoundItem(int16 index); + AudioResourceManMusicItem *getMusicItem(int16 index); + +protected: + NeverhoodEngine *_vm; + + Common::Array<AudioResourceManMusicItem*> _musicItems; + Common::Array<AudioResourceManSoundItem*> _soundItems; + + int16 addSoundItem(AudioResourceManSoundItem *soundItem); + +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_SOUND_H */ diff --git a/engines/neverhood/sprite.cpp b/engines/neverhood/sprite.cpp new file mode 100644 index 0000000000..45d131fd3c --- /dev/null +++ b/engines/neverhood/sprite.cpp @@ -0,0 +1,486 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 "neverhood/sprite.h" +#include "neverhood/screen.h" + +namespace Neverhood { + +// Sprite + +Sprite::Sprite(NeverhoodEngine *vm, int objectPriority) + : Entity(vm, objectPriority), _x(0), _y(0), _spriteUpdateCb(NULL), _filterXCb(NULL), _filterYCb(NULL), + _dataResource(vm), _doDeltaX(false), _doDeltaY(false), _needRefresh(false), _flags(0), _surface(NULL) { + + SetMessageHandler(&Sprite::handleMessage); + +} + +Sprite::~Sprite() { + delete _surface; +} + +void Sprite::updateBounds() { + if (_doDeltaX) { + _collisionBounds.x1 = _x - _collisionBoundsOffset.x - _collisionBoundsOffset.width + 1; + _collisionBounds.x2 = _x - _collisionBoundsOffset.x; + } else { + _collisionBounds.x1 = _x + _collisionBoundsOffset.x; + _collisionBounds.x2 = _x + _collisionBoundsOffset.x + _collisionBoundsOffset.width - 1; + } + if (_doDeltaY) { + _collisionBounds.y1 = _y - _collisionBoundsOffset.y - _collisionBoundsOffset.height + 1; + _collisionBounds.y2 = _y - _collisionBoundsOffset.y; + } else { + _collisionBounds.y1 = _y + _collisionBoundsOffset.y; + _collisionBounds.y2 = _y + _collisionBoundsOffset.y + _collisionBoundsOffset.height - 1; + } +} + +void Sprite::setDoDeltaX(int type) { + // Clear, set or toggle + _doDeltaX = type == 2 ? !_doDeltaX : type == 1; +} + +void Sprite::setDoDeltaY(int type) { + // Clear, set or toggle + _doDeltaY = type == 2 ? !_doDeltaY : type == 1; +} + +bool Sprite::isPointInside(int16 x, int16 y) { + return x >= _collisionBounds.x1 && x <= _collisionBounds.x2 && y >= _collisionBounds.y1 && y <= _collisionBounds.y2; +} + +bool Sprite::checkCollision(NRect &rect) { + return (_collisionBounds.x1 < rect.x2) && (rect.x1 < _collisionBounds.x2) && (_collisionBounds.y1 < rect.y2) && (rect.y1 < _collisionBounds.y2); +} + +uint32 Sprite::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) { + return 0; +} + +void Sprite::loadDataResource(uint32 fileHash) { + _dataResource.load(fileHash); +} + +void Sprite::createSurface(int surfacePriority, int16 width, int16 height) { + _surface = new BaseSurface(_vm, surfacePriority, width, height); +} + +int16 Sprite::defFilterY(int16 y) { + return y - _vm->_screen->getYOffset(); +} + +void Sprite::setClipRect(int16 x1, int16 y1, int16 x2, int16 y2) { + NRect &clipRect = _surface->getClipRect(); + clipRect.x1 = x1; + clipRect.y1 = y1; + clipRect.x2 = x2; + clipRect.y2 = y2; +} + +void Sprite::setClipRect(NRect& clipRect) { + _surface->getClipRect() = clipRect; +} + +void Sprite::setClipRect(NDrawRect& drawRect) { + setClipRect(drawRect.x, drawRect.y, drawRect.x2(), drawRect.y2()); +} + +// StaticSprite + +StaticSprite::StaticSprite(NeverhoodEngine *vm, int objectPriority) + : Sprite(vm, objectPriority), _spriteResource(vm) { + +} + +StaticSprite::StaticSprite(NeverhoodEngine *vm, uint32 fileHash, int surfacePriority, int16 x, int16 y) + : Sprite(vm, 0), _spriteResource(vm) { + + _spriteResource.load(fileHash, true); + createSurface(surfacePriority, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height); + _x = x == kDefPosition ? _spriteResource.getPosition().x : x; + _y = y == kDefPosition ? _spriteResource.getPosition().y : y; + _drawOffset.set(0, 0, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height); + _needRefresh = true; + updatePosition(); +} + +void StaticSprite::loadSprite(uint32 fileHash, uint flags, int surfacePriority, int16 x, int16 y) { + _spriteResource.load(fileHash, true); + if (!_surface) + createSurface(surfacePriority, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height); + if (flags & kSLFDefDrawOffset) + _drawOffset.set(0, 0, _spriteResource.getDimensions().width, _spriteResource.getDimensions().height); + else if (flags & kSLFCenteredDrawOffset) + _drawOffset.set(-(_spriteResource.getDimensions().width / 2), -(_spriteResource.getDimensions().height / 2), + _spriteResource.getDimensions().width, _spriteResource.getDimensions().height); + if (flags & kSLFDefPosition) { + _x = _spriteResource.getPosition().x; + _y = _spriteResource.getPosition().y; + } else if (flags & kSLFSetPosition) { + _x = x; + _y = y; + } + if (flags & kSLFDefCollisionBoundsOffset) { + _collisionBoundsOffset = _drawOffset; + updateBounds(); + } + _needRefresh = true; + updatePosition(); +} + +void StaticSprite::updatePosition() { + + if (!_surface) + return; + + if (_doDeltaX) { + _surface->getDrawRect().x = filterX(_x - _drawOffset.x - _drawOffset.width + 1); + } else { + _surface->getDrawRect().x = filterX(_x + _drawOffset.x); + } + + if (_doDeltaY) { + _surface->getDrawRect().y = filterY(_y - _drawOffset.y - _drawOffset.height + 1); + } else { + _surface->getDrawRect().y = filterY(_y + _drawOffset.y); + } + + if (_needRefresh) { + _surface->drawSpriteResourceEx(_spriteResource, _doDeltaX, _doDeltaY, _drawOffset.width, _drawOffset.height); + _needRefresh = false; + } + +} + +// AnimatedSprite + +AnimatedSprite::AnimatedSprite(NeverhoodEngine *vm, int objectPriority) + : Sprite(vm, objectPriority), _animResource(vm) { + + init(); +} + +AnimatedSprite::AnimatedSprite(NeverhoodEngine *vm, uint32 fileHash, int surfacePriority, int16 x, int16 y) + : Sprite(vm, 1100), _animResource(vm) { + + init(); + SetUpdateHandler(&AnimatedSprite::update); + createSurface1(fileHash, surfacePriority); + _x = x; + _y = y; + startAnimation(fileHash, 0, -1); +} + +void AnimatedSprite::init() { + _currFrameTicks = 0; + _newAnimFileHash = 0; + _deltaX = 0; + _deltaY = 0; + _nextAnimFileHash = 0; + _plFirstFrameIndex = 0; + _currFrameIndex = 0; + _currStickFrameIndex = -1; + _finalizeStateCb = NULL; + _currStateCb = NULL; + _nextStateCb = NULL; + _newStickFrameIndex = -1; + _newStickFrameHash = 0; + _frameChanged = false; + _replOldColor = 0; + _replNewColor = 0; + _animResource.setReplEnabled(false); + _playBackwards = false; +} + +void AnimatedSprite::update() { + updateAnim(); + handleSpriteUpdate(); + updatePosition(); +} + +void AnimatedSprite::updateDeltaXY() { + if (_doDeltaX) { + _x -= _deltaX; + } else { + _x += _deltaX; + } + if (_doDeltaY) { + _y -= _deltaY; + } else { + _y += _deltaY; + } + _deltaX = 0; + _deltaY = 0; + updateBounds(); +} + +void AnimatedSprite::setRepl(byte oldColor, byte newColor) { + _replOldColor = oldColor; + _replNewColor = newColor; + _animResource.setReplEnabled(true); +} + +void AnimatedSprite::clearRepl() { + _replOldColor = 0; + _replNewColor = 0; + _animResource.setReplEnabled(false); +} + +void AnimatedSprite::updateAnim() { + + _frameChanged = false; + + if (_newAnimFileHash == 0) { + if (_newStickFrameIndex != -1) { + _currStickFrameIndex = _newStickFrameIndex == STICK_LAST_FRAME ? _animResource.getFrameCount() - 1 : _newStickFrameIndex; + _newStickFrameIndex = -1; + } else if (_newStickFrameHash != 0) { + _currStickFrameIndex = MAX<int16>(0, _animResource.getFrameIndex(_newStickFrameHash)); + _newStickFrameHash = 0; + } + if (_newAnimFileHash == 0 && _currFrameIndex != _currStickFrameIndex) { + if (_currFrameTicks != 0 && (--_currFrameTicks == 0) && _animResource.getFrameCount() != 0) { + + if (_nextAnimFileHash != 0) { + if (_animResource.load(_nextAnimFileHash)) { + _currAnimFileHash = _nextAnimFileHash; + } else { + _animResource.load(calcHash("sqDefault")); + _currAnimFileHash = 0; + } + if (_replOldColor != _replNewColor) { + _animResource.setRepl(_replOldColor, _replNewColor); + } + _nextAnimFileHash = 0; + if (_animStatus != 0) { + _currFrameIndex = _plFirstFrameHash != 0 ? MAX<int16>(0, _animResource.getFrameIndex(_plFirstFrameHash)) : 0; + _lastFrameIndex = _plLastFrameHash != 0 ? MAX<int16>(0, _animResource.getFrameIndex(_plLastFrameHash)) : _animResource.getFrameCount() - 1; + } else { + _currFrameIndex = _plFirstFrameIndex != -1 ? _plFirstFrameIndex : _animResource.getFrameCount() - 1; + _lastFrameIndex = _plLastFrameIndex != -1 ? _plLastFrameIndex : _animResource.getFrameCount() - 1; + } + } else { + updateFrameIndex(); + } + if (_newAnimFileHash == 0) + updateFrameInfo(); + } + } + } + + if (_newAnimFileHash != 0) { + if (_animStatus == 2) { + _currStickFrameIndex = _currFrameIndex; + } else { + if (_animStatus == 1) { + if (_animResource.load(_newAnimFileHash)) { + _currAnimFileHash = _newAnimFileHash; + } else { + _animResource.load(calcHash("sqDefault")); + _currAnimFileHash = 0; + } + if (_replOldColor != _replNewColor) { + _animResource.setRepl(_replOldColor, _replNewColor); + } + _newAnimFileHash = 0; + _currFrameIndex = _plFirstFrameHash != 0 ? MAX<int16>(0, _animResource.getFrameIndex(_plFirstFrameHash)) : 0; + _lastFrameIndex = _plLastFrameHash != 0 ? MAX<int16>(0, _animResource.getFrameIndex(_plLastFrameHash)) : _animResource.getFrameCount() - 1; + } else { + if (_animResource.load(_newAnimFileHash)) { + _currAnimFileHash = _newAnimFileHash; + } else { + _animResource.load(calcHash("sqDefault")); + _currAnimFileHash = 0; + } + if (_replOldColor != _replNewColor) { + _animResource.setRepl(_replOldColor, _replNewColor); + } + _newAnimFileHash = 0; + _currFrameIndex = _plFirstFrameIndex != -1 ? _plFirstFrameIndex : _animResource.getFrameCount() - 1; + _lastFrameIndex = _plLastFrameIndex != -1 ? _plLastFrameIndex : _animResource.getFrameCount() - 1; + } + updateFrameInfo(); + } + + if (_newStickFrameIndex != -1) { + _currStickFrameIndex = _newStickFrameIndex == STICK_LAST_FRAME ? _animResource.getFrameCount() - 1 : _newStickFrameIndex; + _newStickFrameIndex = -1; + } else if (_newStickFrameHash != 0) { + _currStickFrameIndex = MAX<int16>(0, _animResource.getFrameIndex(_newStickFrameHash)); + _newStickFrameHash = 0; + } + + } + +} + +void AnimatedSprite::updatePosition() { + + if (!_surface) + return; + + if (_doDeltaX) { + _surface->getDrawRect().x = filterX(_x - _drawOffset.x - _drawOffset.width + 1); + } else { + _surface->getDrawRect().x = filterX(_x + _drawOffset.x); + } + + if (_doDeltaY) { + _surface->getDrawRect().y = filterY(_y - _drawOffset.y - _drawOffset.height + 1); + } else { + _surface->getDrawRect().y = filterY(_y + _drawOffset.y); + } + + if (_needRefresh) { + _surface->drawAnimResource(_animResource, _currFrameIndex, _doDeltaX, _doDeltaY, _drawOffset.width, _drawOffset.height); + _needRefresh = false; + } + +} + +void AnimatedSprite::updateFrameIndex() { + if (!_playBackwards) { + if (_currFrameIndex < _lastFrameIndex) { + _currFrameIndex++; + } else { + // Inform self about end of current animation + // The caller can then e.g. set a new animation fileHash + sendMessage(this, 0x3002, 0); + if (_newAnimFileHash == 0) + _currFrameIndex = 0; + } + } else { + if (_currFrameIndex > 0) { + _currFrameIndex--; + } else { + sendMessage(this, 0x3002, 0); + if (_newAnimFileHash == 0) + _currFrameIndex = _lastFrameIndex; + } + } +} + +void AnimatedSprite::updateFrameInfo() { + debug(8, "AnimatedSprite::updateFrameInfo()"); + const AnimFrameInfo &frameInfo = _animResource.getFrameInfo(_currFrameIndex); + _frameChanged = true; + _drawOffset = frameInfo.drawOffset; + _deltaX = frameInfo.deltaX; + _deltaY = frameInfo.deltaY; + _collisionBoundsOffset = frameInfo.collisionBoundsOffset; + _currFrameTicks = frameInfo.counter; + updateBounds(); + _needRefresh = true; + if (frameInfo.frameHash != 0) + sendMessage(this, 0x100D, frameInfo.frameHash); +} + +void AnimatedSprite::createSurface1(uint32 fileHash, int surfacePriority) { + NDimensions dimensions = _animResource.loadSpriteDimensions(fileHash); + _surface = new BaseSurface(_vm, surfacePriority, dimensions.width, dimensions.height); +} + +void AnimatedSprite::createShadowSurface1(BaseSurface *shadowSurface, uint32 fileHash, int surfacePriority) { + NDimensions dimensions = _animResource.loadSpriteDimensions(fileHash); + _surface = new ShadowSurface(_vm, surfacePriority, dimensions.width, dimensions.height, shadowSurface); +} + +void AnimatedSprite::createShadowSurface(BaseSurface *shadowSurface, int16 width, int16 height, int surfacePriority) { + _surface = new ShadowSurface(_vm, surfacePriority, width, height, shadowSurface); +} + +void AnimatedSprite::startAnimation(uint32 fileHash, int16 plFirstFrameIndex, int16 plLastFrameIndex) { + debug(2, "AnimatedSprite::startAnimation(%08X, %d, %d)", fileHash, plFirstFrameIndex, plLastFrameIndex); + _newAnimFileHash = fileHash; + _plFirstFrameIndex = plFirstFrameIndex; + _plLastFrameIndex = plLastFrameIndex; + _newStickFrameHash = 0; + _animStatus = 0; + _playBackwards = false; + _newStickFrameIndex = -1; + _currStickFrameIndex = -1; +} + +void AnimatedSprite::stopAnimation() { + _newAnimFileHash = 1; + _animStatus = 2; +} + +void AnimatedSprite::startAnimationByHash(uint32 fileHash, uint32 plFirstFrameHash, uint32 plLastFrameHash) { + debug(2, "AnimatedSprite::startAnimationByHash(%08X, %08X, %08X)", fileHash, plFirstFrameHash, plLastFrameHash); + _newAnimFileHash = fileHash; + _plFirstFrameHash = plFirstFrameHash; + _plLastFrameHash = plLastFrameHash; + _newStickFrameHash = 0; + _animStatus = 1; + _playBackwards = false; + _newStickFrameIndex = -1; + _currStickFrameIndex = -1; +} + +void AnimatedSprite::nextAnimationByHash(uint32 fileHash2, uint32 plFirstFrameHash, uint32 plLastFrameHash) { + _nextAnimFileHash = fileHash2; + _plFirstFrameHash = plFirstFrameHash; + _plLastFrameHash = plLastFrameHash; + _newStickFrameHash = 0; + _animStatus = 1; + _playBackwards = false; + _newStickFrameIndex = -1; + _currStickFrameIndex = -1; +} + +void AnimatedSprite::setFinalizeState(AnimationCb finalizeStateCb) { + if (_finalizeStateCb) + (this->*_finalizeStateCb)(); + _finalizeStateCb = finalizeStateCb; +} + +void AnimatedSprite::gotoState(AnimationCb currStateCb) { + if (_finalizeStateCb) { + AnimationCb cb = _finalizeStateCb; + _finalizeStateCb = NULL; + (this->*cb)(); + } + _nextStateCb = NULL; + _currStateCb = currStateCb; + if (_currStateCb) + (this->*_currStateCb)(); +} + +void AnimatedSprite::gotoNextState() { + if (_finalizeStateCb) { + AnimationCb cb = _finalizeStateCb; + _finalizeStateCb = NULL; + (this->*cb)(); + } + if (_nextStateCb) { + _currStateCb = _nextStateCb; + _nextStateCb = NULL; + (this->*_currStateCb)(); + } else { + _currStateCb = NULL; + } +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/sprite.h b/engines/neverhood/sprite.h new file mode 100644 index 0000000000..80da1768bd --- /dev/null +++ b/engines/neverhood/sprite.h @@ -0,0 +1,173 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can 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 NEVERHOOD_SPRITE_H +#define NEVERHOOD_SPRITE_H + +#include "neverhood/neverhood.h" +#include "neverhood/entity.h" +#include "neverhood/graphics.h" +#include "neverhood/resource.h" + +namespace Neverhood { + +#define SetSpriteUpdate(callback) _spriteUpdateCb = static_cast <void (Sprite::*)(void)> (callback); debug(2, "SetSpriteUpdate(" #callback ")"); _spriteUpdateCbName = #callback +#define SetFilterX(callback) _filterXCb = static_cast <int16 (Sprite::*)(int16)> (callback); debug(2, "SetFilterX(" #callback ")") +#define SetFilterY(callback) _filterYCb = static_cast <int16 (Sprite::*)(int16)> (callback); debug(2, "SetFilterY(" #callback ")") + +const int16 kDefPosition = -32768; + +class Sprite : public Entity { +public: + Sprite(NeverhoodEngine *vm, int objectPriority); + ~Sprite(); + void init() {} + BaseSurface *getSurface() { return _surface; } + void updateBounds(); + void setDoDeltaX(int type); + void setDoDeltaY(int type); + bool isPointInside(int16 x, int16 y); + bool checkCollision(NRect &rect); + int16 getX() const { return _x; } + int16 getY() const { return _y; } + void setX(int16 value) { _x = value; } + void setY(int16 value) { _y = value; } + uint16 getFlags() const { return _flags; } + bool isDoDeltaX() const { return _doDeltaX; } + bool isDoDeltaY() const { return _doDeltaY; } + NRect& getCollisionBounds() { return _collisionBounds; } + uint32 handleMessage(int messageNum, const MessageParam ¶m, Entity *sender); + void loadDataResource(uint32 fileHash); + int16 defFilterY(int16 y); + bool getVisible() const { return _surface->getVisible(); } + void setVisible(bool value) { _surface->setVisible(value); } + NDrawRect& getDrawRect() { return _surface->getDrawRect(); } + // Some shortcuts to set the clipRect + NRect& getClipRect() { return _surface->getClipRect(); } + void setClipRect(int16 x1, int16 y1, int16 x2, int16 y2); + void setClipRect(NRect& clipRect); + void setClipRect(NDrawRect& drawRect); +protected: + void (Sprite::*_spriteUpdateCb)(); + Common::String _spriteUpdateCbName; // For debugging purposes + int16 (Sprite::*_filterXCb)(int16); + int16 (Sprite::*_filterYCb)(int16); + BaseSurface *_surface; + int16 _x, _y; + bool _doDeltaX, _doDeltaY; + bool _needRefresh; + NDrawRect _drawOffset; + NRect _collisionBounds; + NDrawRect _collisionBoundsOffset; + uint16 _flags; + DataResource _dataResource; + void createSurface(int surfacePriority, int16 width, int16 height); + void handleSpriteUpdate() { + if (_spriteUpdateCb) + (this->*_spriteUpdateCb)(); + } + int16 filterX(int16 x) { + return _filterXCb ? (this->*_filterXCb)(x) : x; + } + int16 filterY(int16 y) { + return _filterYCb ? (this->*_filterYCb)(y) : y; + } +}; + +enum { + kSLFDefDrawOffset = 1 << 0, + kSLFCenteredDrawOffset = 1 << 1, + kSLFDefPosition = 1 << 2, + kSLFSetPosition = 1 << 3, + kSLFDefCollisionBoundsOffset = 1 << 4 +}; + +class StaticSprite : public Sprite { +public: + StaticSprite(NeverhoodEngine *vm, int objectPriority); + StaticSprite(NeverhoodEngine *vm, uint32 fileHash, int surfacePriority, int16 x = kDefPosition, int16 y = kDefPosition); + void loadSprite(uint32 fileHash, uint flags = 0, int surfacePriority = 0, int16 x = kDefPosition, int16 y = kDefPosition); + void updatePosition(); +protected: + SpriteResource _spriteResource; +}; + +#define AnimationCallback(callback) static_cast <void (AnimatedSprite::*)()> (callback) +#define GotoState(callback) gotoState(static_cast <void (AnimatedSprite::*)()> (callback)) +#define NextState(callback) _nextStateCb = static_cast <void (AnimatedSprite::*)(void)> (callback); debug(2, "NextState(" #callback ")"); _nextStateCbName = #callback +#define FinalizeState(callback) setFinalizeState(static_cast <void (AnimatedSprite::*)()> (callback)); + +const int STICK_LAST_FRAME = -2; + +class AnimatedSprite : public Sprite { +public: + AnimatedSprite(NeverhoodEngine *vm, int objectPriority); + AnimatedSprite(NeverhoodEngine *vm, uint32 fileHash, int surfacePriority, int16 x, int16 y); + void update(); + void updateDeltaXY(); + void setRepl(byte oldColor, byte newColor); + void clearRepl(); + uint32 getCurrAnimFileHash() const { return _currAnimFileHash; } + int16 getFrameIndex() const { return _currFrameIndex; } + int16 getFrameIndex(uint32 frameHash) { return _animResource.getFrameIndex(frameHash); } + void setNewHashListIndex(int value) { _newStickFrameIndex = value; } + void startAnimation(uint32 fileHash, int16 plFirstFrameIndex, int16 plLastFrameIndex); +protected: + typedef void (AnimatedSprite::*AnimationCb)(); + AnimResource _animResource; + uint32 _currAnimFileHash, _newAnimFileHash, _nextAnimFileHash; + int16 _currFrameIndex, _lastFrameIndex; + int16 _plFirstFrameIndex, _plLastFrameIndex; + uint32 _plFirstFrameHash, _plLastFrameHash; + int16 _animStatus; + int16 _currFrameTicks; + int _currStickFrameIndex, _newStickFrameIndex; + uint32 _newStickFrameHash; + int16 _deltaX, _deltaY; + byte _replOldColor, _replNewColor; + bool _playBackwards, _frameChanged; + AnimationCb _finalizeStateCb; + AnimationCb _currStateCb; + AnimationCb _nextStateCb; + // For debugging purposes + Common::String _finalizeStateCbName; + Common::String _currStateCbName; + Common::String _nextStateCbName; + void init(); + void updateAnim(); + void updatePosition(); + void updateFrameIndex(); + void updateFrameInfo(); + void createSurface1(uint32 fileHash, int surfacePriority); + void createShadowSurface1(BaseSurface *shadowSurface, uint32 fileHash, int surfacePriority); + void createShadowSurface(BaseSurface *shadowSurface, int16 width, int16 height, int surfacePriority); + void stopAnimation(); + void startAnimationByHash(uint32 fileHash, uint32 plFirstFrameHash, uint32 plLastFrameHash); + void nextAnimationByHash(uint32 fileHash2, uint32 plFirstFrameHash, uint32 plLastFrameHash); + void setFinalizeState(AnimationCb finalizeStateCb); + void gotoState(AnimationCb currStateCb); + void gotoNextState(); +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_SPRITE_H */ diff --git a/engines/neverhood/staticdata.cpp b/engines/neverhood/staticdata.cpp new file mode 100644 index 0000000000..3f89c2236f --- /dev/null +++ b/engines/neverhood/staticdata.cpp @@ -0,0 +1,200 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 "neverhood/staticdata.h" + +namespace Neverhood { + +StaticData::StaticData() { +} + +StaticData::~StaticData() { +} + +void StaticData::load(const char *filename) { + + Common::File fd; + + if (!fd.open(filename)) + error("StaticData::load() Could not open %s", filename); + + fd.readUint32LE(); // magic + fd.readUint32LE(); // version + + // Load message lists + uint32 messageListsCount = fd.readUint32LE(); + debug(3, "messageListsCount: %d", messageListsCount); + for (uint32 i = 0; i < messageListsCount; i++) { + MessageList *messageList = new MessageList(); + uint32 id = fd.readUint32LE(); + uint32 itemCount = fd.readUint32LE(); + for (uint32 itemIndex = 0; itemIndex < itemCount; itemIndex++) { + MessageItem messageItem; + messageItem.messageNum = fd.readUint16LE(); + messageItem.messageValue = fd.readUint32LE(); + messageList->push_back(messageItem); + } + _messageLists[id] = messageList; + } + + // Load rect lists + uint32 rectListsCount = fd.readUint32LE(); + debug(3, "rectListsCount: %d", rectListsCount); + for (uint32 i = 0; i < rectListsCount; i++) { + RectList *rectList = new RectList(); + uint32 id = fd.readUint32LE(); + uint32 itemCount = fd.readUint32LE(); + for (uint32 itemIndex = 0; itemIndex < itemCount; itemIndex++) { + RectItem rectItem; + rectItem.rect.x1 = fd.readUint16LE(); + rectItem.rect.y1 = fd.readUint16LE(); + rectItem.rect.x2 = fd.readUint16LE(); + rectItem.rect.y2 = fd.readUint16LE(); + uint32 subItemCount = fd.readUint32LE(); + rectItem.subRects.reserve(subItemCount); + for (uint32 subItemIndex = 0; subItemIndex < subItemCount; subItemIndex++) { + SubRectItem subRectItem; + subRectItem.rect.x1 = fd.readUint16LE(); + subRectItem.rect.y1 = fd.readUint16LE(); + subRectItem.rect.x2 = fd.readUint16LE(); + subRectItem.rect.y2 = fd.readUint16LE(); + subRectItem.messageListId = fd.readUint32LE(); + rectItem.subRects.push_back(subRectItem); + } + rectList->push_back(rectItem); + } + _rectLists[id] = rectList; + } + + // Load hit rects + uint32 hitRectListsCount = fd.readUint32LE(); + debug(3, "hitRectListsCount: %d", hitRectListsCount); + for (uint32 i = 0; i < hitRectListsCount; i++) { + HitRectList *hitRectList = new HitRectList(); + uint32 id = fd.readUint32LE(); + uint32 itemCount = fd.readUint32LE(); + for (uint32 itemIndex = 0; itemIndex < itemCount; itemIndex++) { + HitRect hitRect; + hitRect.rect.x1 = fd.readUint16LE(); + hitRect.rect.y1 = fd.readUint16LE(); + hitRect.rect.x2 = fd.readUint16LE(); + hitRect.rect.y2 = fd.readUint16LE(); + hitRect.type = fd.readUint16LE(); + hitRectList->push_back(hitRect); + } + _hitRectLists[id] = hitRectList; + } + + // Load navigation lists + uint32 navigationListsCount = fd.readUint32LE(); + debug(3, "navigationListsCount: %d", navigationListsCount); + for (uint32 i = 0; i < navigationListsCount; i++) { + NavigationList *navigationList = new NavigationList(); + uint32 id = fd.readUint32LE(); + uint32 itemCount = fd.readUint32LE(); + for (uint32 itemIndex = 0; itemIndex < itemCount; itemIndex++) { + NavigationItem navigationItem; + navigationItem.fileHash = fd.readUint32LE(); + navigationItem.leftSmackerFileHash = fd.readUint32LE(); + navigationItem.rightSmackerFileHash = fd.readUint32LE(); + navigationItem.middleSmackerFileHash = fd.readUint32LE(); + navigationItem.interactive = fd.readByte(); + navigationItem.middleFlag = fd.readByte(); + navigationItem.mouseCursorFileHash = fd.readUint32LE(); + navigationList->push_back(navigationItem); + } + _navigationLists[id] = navigationList; + } + + // Load HallOfRecordsInfo items + uint32 hallOfRecordsInfoItemsCount = fd.readUint32LE(); + debug(3, "hallOfRecordsInfoItemsCount: %d", hallOfRecordsInfoItemsCount); + for (uint32 i = 0; i < hallOfRecordsInfoItemsCount; i++) { + HallOfRecordsInfo *hallOfRecordsInfo = new HallOfRecordsInfo(); + uint32 id = fd.readUint32LE(); + hallOfRecordsInfo->bgFilename1 = fd.readUint32LE(); + hallOfRecordsInfo->bgFilename2 = fd.readUint32LE(); + hallOfRecordsInfo->txFilename = fd.readUint32LE(); + hallOfRecordsInfo->bgFilename3 = fd.readUint32LE(); + hallOfRecordsInfo->xPosIndex = fd.readByte(); + hallOfRecordsInfo->count = fd.readByte(); + _hallOfRecordsInfoItems[id] = hallOfRecordsInfo; + } + + // Load TrackInfo items + uint32 trackInfoItemsCount = fd.readUint32LE(); + debug(3, "trackInfoItemsCount: %d", trackInfoItemsCount); + for (uint32 i = 0; i < trackInfoItemsCount; i++) { + TrackInfo *trackInfo = new TrackInfo(); + uint32 id = fd.readUint32LE(); + trackInfo->bgFilename = fd.readUint32LE(); + trackInfo->bgShadowFilename = fd.readUint32LE(); + trackInfo->dataResourceFilename = fd.readUint32LE(); + trackInfo->trackPointsName = fd.readUint32LE(); + trackInfo->rectListName = fd.readUint32LE(); + trackInfo->exPaletteFilename2 = fd.readUint32LE(); + trackInfo->exPaletteFilename1 = fd.readUint32LE(); + trackInfo->mouseCursorFilename = fd.readUint32LE(); + trackInfo->which1 = fd.readUint16LE(); + trackInfo->which2 = fd.readUint16LE(); + _trackInfoItems[id] = trackInfo; + } + +} + +HitRectList *StaticData::getHitRectList(uint32 id) { + if (!_hitRectLists[id]) + error("StaticData::getHitRectList() HitRectList with id %08X not found", id); + return _hitRectLists[id]; +} + +RectList *StaticData::getRectList(uint32 id) { + if (!_rectLists[id]) + error("StaticData::getRectList() RectList with id %08X not found", id); + return _rectLists[id]; +} + +MessageList *StaticData::getMessageList(uint32 id) { + if (!_messageLists[id]) + error("StaticData::getMessageList() MessageList with id %08X not found", id); + return _messageLists[id]; +} + +NavigationList *StaticData::getNavigationList(uint32 id) { + if (!_navigationLists[id]) + error("StaticData::getNavigationList() NavigationList with id %08X not found", id); + return _navigationLists[id]; +} + +HallOfRecordsInfo *StaticData::getHallOfRecordsInfoItem(uint32 id) { + if (!_hallOfRecordsInfoItems[id]) + error("StaticData::getHallOfRecordsInfoItem() HallOfRecordsInfo with id %08X not found", id); + return _hallOfRecordsInfoItems[id]; +} + +TrackInfo *StaticData::getTrackInfo(uint32 id) { + if (!_trackInfoItems[id]) + error("StaticData::getTrackInfo() TrackInfo with id %08X not found", id); + return _trackInfoItems[id]; +} + +} // End of namespace Neverhood diff --git a/engines/neverhood/staticdata.h b/engines/neverhood/staticdata.h new file mode 100644 index 0000000000..b1cab3bfcd --- /dev/null +++ b/engines/neverhood/staticdata.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 NEVERHOOD_STATICDATA_H +#define NEVERHOOD_STATICDATA_H + +#include "common/array.h" +#include "common/hashmap.h" +#include "neverhood/neverhood.h" +#include "neverhood/graphics.h" + +namespace Neverhood { + +struct HitRect { + NRect rect; + uint16 type; +}; + +typedef Common::Array<HitRect> HitRectList; + +struct SubRectItem { + NRect rect; + uint32 messageListId; +}; + +struct RectItem { + NRect rect; + Common::Array<SubRectItem> subRects; +}; + +typedef Common::Array<RectItem> RectList; + +struct MessageItem { + uint32 messageNum; + uint32 messageValue; +}; + +typedef Common::Array<MessageItem> MessageList; + +struct NavigationItem { + uint32 fileHash; + uint32 leftSmackerFileHash; + uint32 rightSmackerFileHash; + uint32 middleSmackerFileHash; + byte interactive; + byte middleFlag; + uint32 mouseCursorFileHash; +}; + +typedef Common::Array<NavigationItem> NavigationList; + +struct HallOfRecordsInfo { + uint32 bgFilename1; + uint32 bgFilename2; + uint32 txFilename; + uint32 bgFilename3; + byte xPosIndex; + byte count; +}; + +struct TrackInfo { + uint32 id; + uint32 bgFilename; + uint32 bgShadowFilename; + uint32 dataResourceFilename; + uint32 trackPointsName; + uint32 rectListName; + uint32 exPaletteFilename2; + uint32 exPaletteFilename1; + uint32 mouseCursorFilename; + int16 which1; + int16 which2; +}; + +class StaticData { +public: + StaticData(); + ~StaticData(); + void load(const char *filename); + HitRectList *getHitRectList(uint32 id); + RectList *getRectList(uint32 id); + MessageList *getMessageList(uint32 id); + NavigationList *getNavigationList(uint32 id); + HallOfRecordsInfo *getHallOfRecordsInfoItem(uint32 id); + TrackInfo *getTrackInfo(uint32 id); +protected: + Common::HashMap<uint32, HitRectList*> _hitRectLists; + Common::HashMap<uint32, RectList*> _rectLists; + Common::HashMap<uint32, MessageList*> _messageLists; + Common::HashMap<uint32, NavigationList*> _navigationLists; + Common::HashMap<uint32, HallOfRecordsInfo*> _hallOfRecordsInfoItems; + Common::HashMap<uint32, TrackInfo*> _trackInfoItems; +}; + +} // End of namespace Neverhood + +#endif /* NEVERHOOD_STATICDATA_H */ diff --git a/engines/neverhood/todo.txt b/engines/neverhood/todo.txt new file mode 100644 index 0000000000..9d781e06ec --- /dev/null +++ b/engines/neverhood/todo.txt @@ -0,0 +1,45 @@ +NOTE: +------- +Some of the TODOs should be done AFTER the whole game logic is implemented +else the game disasm and reimplemtation code become even more different +(unless I decide it's ok to do it :) + +TODOs which can be done any time: +----------------------------------- +- Cleanup +- Clean up staticdata structs to look more like the ones in create_neverhood + (e.g. by using template classes etc.) + - Or use a common base class and manage all stuff in one single table and cast stuff accordingly + +TODOs which should be done only after the game logic is finished: +------------------------------------------------------------------- +- Maybe rework organization of files (e.g. put ALL Sprites into one separate file, same with Modules and Scenes) + - This would solve the problem of how to organize stuff which is used several times, and less headers would have to be included + - The move special scenes (SmackerScene) into the scenes file + +DONE: +------- +- Implement game menus +- Rework sound system (I don't like that SoundResources need to be explicitly initialized in Scene constructors) + - Should be just a handle object which initializes itself + - Play routine should fill the handle so it can be stopped/queried later + - Basically like ScummVM own sound handles +- RE and implement yet unknown music/sound stuff +- Implement clever sprite redrawing code (dirty rectangles, microtiles etc.), only redraw what's neccessary +- Rework the resource system + - The current system can be simplified a lot + - Also resource purging needs to be implemented +- Maybe merge CollisionMan with Scene (since it's so far never used independently) +- Give placeholder stuff (e.g. sub?????, _flag??? etc.) better fitting names +- Use CursorMan for the mouse cursor (instead of using it like a normal sprite) + - This whould make it neccessary to call _system->updateScreen more often else + the mouse movement would be choppy + +TODOs which are experimental: +------------------------------- +NOTE: Since they affect the whole game, they really should be only implemented once the full game logic is implemented. +These are nothing more than wild ideas for now, any might never be implemented. +- Use states instead of separate callback methods +- Try to move more stuff to neverhood.dat +- Try to use more template functions instead of manually creating functions + (Can be coupled with the above to move parameters to the dat and only use IDs) diff --git a/engines/plugins_table.h b/engines/plugins_table.h index e5ac5efeb4..44979458ca 100644 --- a/engines/plugins_table.h +++ b/engines/plugins_table.h @@ -56,6 +56,9 @@ LINK_PLUGIN(MADE) #if PLUGIN_ENABLED_STATIC(MOHAWK) LINK_PLUGIN(MOHAWK) #endif +#if PLUGIN_ENABLED_STATIC(NEVERHOOD) +LINK_PLUGIN(NEVERHOOD) +#endif #if PLUGIN_ENABLED_STATIC(PARALLACTION) LINK_PLUGIN(PARALLACTION) #endif diff --git a/video/smk_decoder.h b/video/smk_decoder.h index 7227238373..e4bc9bab42 100644 --- a/video/smk_decoder.h +++ b/video/smk_decoder.h @@ -122,7 +122,6 @@ protected: Common::SeekableReadStream *_fileStream; -private: enum AudioCompression { kCompressionNone, kCompressionDPCM, @@ -151,6 +150,10 @@ private: uint32 dummy; } _header; + uint32 *_frameSizes; + +private: + class SmackerAudioTrack : public AudioTrack { public: SmackerAudioTrack(const AudioInfo &audioInfo, Audio::Mixer::SoundType soundType); @@ -173,7 +176,6 @@ private: AudioInfo _audioInfo; }; - uint32 *_frameSizes; // The FrameTypes section of a Smacker file contains an array of bytes, where // the 8 bits of each byte describe the contents of the corresponding frame. // The highest 7 bits correspond to audio frames (bit 7 is track 6, bit 6 track 5 |