diff options
Diffstat (limited to 'engines/lastexpress')
129 files changed, 73102 insertions, 0 deletions
diff --git a/engines/lastexpress/data/animation.cpp b/engines/lastexpress/data/animation.cpp new file mode 100644 index 0000000000..88973c4b0b --- /dev/null +++ b/engines/lastexpress/data/animation.cpp @@ -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. + * + * $URL$ + * $Id$ + * + */ + +// Based on Deniz Oezmen's code: http://oezmen.eu/ + +#include "lastexpress/data/animation.h" + +#include "lastexpress/data/sequence.h" +#include "lastexpress/data/snd.h" + +#include "lastexpress/debug.h" + +#include "common/events.h" +#include "engines/engine.h" + +namespace LastExpress { + +Animation::Animation() : _stream(NULL), _currentChunk(NULL), _overlay(NULL), _background1(NULL), _background2(NULL), _backgroundCurrent(0), _audio(NULL), _startTime(0), _changed(false), _flag(0) { +} + +Animation::~Animation() { + reset(); +} + +void Animation::reset() { + delete _overlay; + _overlay = NULL; + delete _background1; + _background1 = NULL; + delete _background2; + _background2 = NULL; + delete _audio; + _audio = NULL; + + _backgroundCurrent = 0; + _chunks.clear(); + + _currentChunk = NULL; + + delete _stream; +} + +bool Animation::load(Common::SeekableReadStream *stream, int flag) { + if (!stream) + return false; + + reset(); + + // Keep stream for later decoding of animation + _stream = stream; + + // Read header to get the number of chunks + uint32 numChunks = _stream->readUint32LE(); + debugC(3, kLastExpressDebugGraphics, "Number of chunks in NIS file: %d", numChunks); + + // Check if there is enough data + if (_stream->size() - _stream->pos() < (signed)(numChunks * sizeof(Chunk))) { + debugC(2, kLastExpressDebugGraphics, "NIS file seems to be corrupted!"); + return false; + } + + // Read all the chunks + for (uint32 i = 0; i < numChunks; ++i) { + Chunk chunk; + chunk.type = (ChunkType)_stream->readUint16LE(); + chunk.frame = _stream->readUint16LE(); + chunk.size = _stream->readUint32LE(); + + _chunks.push_back(chunk); + + debugC(9, kLastExpressDebugGraphics, "Chunk Entry: type 0x%.4x, frame=%d, size=%d", chunk.type, chunk.frame, chunk.size); + } + _currentChunk = _chunks.begin(); + _changed = false; + _startTime = g_engine->_system->getMillis(); + + return true; +} + +bool Animation::process() { + if (!_currentChunk) + error("Animation::process - internal error: the current chunk iterator is invalid!"); + + if (_stream == NULL || _chunks.size() == 0) + error("Trying to show an animation before loading data"); + + // TODO: substract the time paused by the GUI + uint32 currentFrame = (uint32)(((float)(g_engine->_system->getMillis() - _startTime)) / 33.33f); + + // Process all chunks until the current frame + while (!_changed && currentFrame > _currentChunk->frame && !hasEnded()) { + switch(_currentChunk->type) { + //TODO: some info chunks are probably subtitle/sync related + case kChunkTypeUnknown1: + case kChunkTypeUnknown2: + case kChunkTypeUnknown5: + debugC(9, kLastExpressDebugGraphics | kLastExpressDebugUnknown, " info chunk: type 0x%.4x (size %d)", _currentChunk->type, _currentChunk->size); + assert (_currentChunk->frame == 0); + //TODO: _currentChunk->size? + break; + + case kChunkTypeAudioInfo: + debugC(9, kLastExpressDebugGraphics, " audio info: %d blocks", _currentChunk->size); + assert (_currentChunk->frame == 0); + //TODO: save the size? + _audio = new AppendableSound(); + break; + + case kChunkTypeUnknown4: + debugC(9, kLastExpressDebugGraphics | kLastExpressDebugUnknown, " info block 4"); + assert (_currentChunk->frame == 0 && _currentChunk->size == 0); + //TODO unknown type of chunk + break; + + case kChunkTypeBackground1: + debugC(9, kLastExpressDebugGraphics, " background frame 1 (%d bytes, frame %d)", _currentChunk->size, _currentChunk->frame); + delete _background1; + _background1 = processChunkFrame(_stream, *_currentChunk); + break; + + case kChunkTypeSelectBackground1: + debugC(9, kLastExpressDebugGraphics, " select background 1"); + assert (_currentChunk->frame == 0 && _currentChunk->size == 0); + _backgroundCurrent = 1; + break; + + case kChunkTypeBackground2: + debugC(9, kLastExpressDebugGraphics, " background frame 2 (%d bytes, frame %d)", _currentChunk->size, _currentChunk->frame); + delete _background2; + _background2 = processChunkFrame(_stream, *_currentChunk); + break; + + case kChunkTypeSelectBackground2: + debugC(9, kLastExpressDebugGraphics, " select background 2"); + assert (_currentChunk->frame == 0 && _currentChunk->size == 0); + _backgroundCurrent = 2; + break; + + case kChunkTypeOverlay: + debugC(9, kLastExpressDebugGraphics, " overlay frame (%d bytes, frame %d)", _currentChunk->size, _currentChunk->frame); + delete _overlay; + _overlay = processChunkFrame(_stream, *_currentChunk); + break; + + case kChunkTypeUpdate: + case kChunkTypeUpdateTransition: + debugC(9, kLastExpressDebugGraphics, " update%s: frame %d", _currentChunk->type == 15 ? "" : " with transition", _currentChunk->frame); + assert (_currentChunk->size == 0); + _changed = true; + break; + + case kChunkTypeAudioData: + debugC(9, kLastExpressDebugGraphics, " audio (%d blocks, %d bytes, frame %d)", _currentChunk->size / _soundBlockSize, _currentChunk->size, _currentChunk->frame); + processChunkAudio(_stream, *_currentChunk); + + // Synchronize the audio by resetting the start time + if (_currentChunk->frame == 0) + _startTime = g_engine->_system->getMillis(); + break; + + case kChunkTypeAudioEnd: + debugC(9, kLastExpressDebugGraphics, " audio end: %d blocks", _currentChunk->frame); + assert (_currentChunk->size == 0); + _audio->finish(); + //TODO: we need to start the linked sound (.LNK) after the audio from the animation ends + break; + + default: + error(" UNKNOWN chunk type=%x frame=%d size=%d", _currentChunk->type, _currentChunk->frame, _currentChunk->size); + break; + } + _currentChunk++; + } + + return true; +} + +bool Animation::hasEnded() { + return _currentChunk == _chunks.end(); +} + +Common::Rect Animation::draw(Graphics::Surface *surface) { + if (!_overlay) + error("Animation::draw - internal error: the current overlay animation frame is invalid!"); + + // Paint the background + if (_backgroundCurrent == 1 && _background1) + _background1->draw(surface); + else if (_backgroundCurrent == 2 && _background2) + _background2->draw(surface); + + // Paint the overlay + _overlay->draw(surface); + + //TODO + return Common::Rect(); +} + +AnimFrame *Animation::processChunkFrame(Common::SeekableReadStream *in, const Chunk &c) const { + assert (c.frame == 0); + + // Create a temporary chunk buffer + Common::MemoryReadStream *str = in->readStream(c.size); + + // Read the frame information + FrameInfo i; + i.read(str, false); + + // Decode the frame + AnimFrame *f = new AnimFrame(str, i); + + // Delete the temporary chunk buffer + delete str; + + return f; +} + +void Animation::processChunkAudio(Common::SeekableReadStream *in, const Chunk &c) { + if (!_audio) + error("Animation::processChunkAudio - internal error: the audio stream is invalid!"); + + // Skip the Snd header, to queue just the audio blocks + uint32 size = c.size; + if ((c.size % 739) != 0) { + // Read Snd header + uint32 header1 = in->readUint32LE(); + uint16 header2 = in->readUint16LE(); + warning("Start ADPCM: %d, %d", header1, header2); + size -= 6; + } + + // Append the current chunk to the Snd + _audio->queueBuffer(in->readStream(size)); +} + +// TODO: this method will probably go away and be integrated in the main loop +void Animation::play() { + while (!hasEnded() && !g_engine->getEventManager()->shouldQuit() && !g_engine->getEventManager()->shouldRTL()) { + process(); + + if (_changed) { + // Create a temporary surface to merge the overlay with the background + Graphics::Surface *s = new Graphics::Surface; + s->create(640, 480, 2); + + draw(s); + + // XXX: Update the screen + g_system->copyRectToScreen((byte *)s->pixels, s->pitch, 0, 0, s->w, s->h); + + // Free the temporary surface + s->free(); + delete s; + + _changed = false; + } + + g_system->updateScreen(); + + //FIXME: implement subtitles + g_engine->_system->delayMillis(20); + + // Handle right-click to interrupt animations + Common::Event ev; + while (g_engine->getEventManager()->pollEvent(ev)) { + if (ev.type == Common::EVENT_RBUTTONUP) { + // Stop audio + if (_audio) + _audio->finish(); + + // TODO start LNK file sound? + return; + } + } + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/animation.h b/engines/lastexpress/data/animation.h new file mode 100644 index 0000000000..ca1f7c6fa0 --- /dev/null +++ b/engines/lastexpress/data/animation.h @@ -0,0 +1,114 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ANIMATION_H +#define LASTEXPRESS_ANIMATION_H + +/* + Animation format (.NIS) + + uint32 {4} - Number of chunks + + // for each chunk + uint16 {2} - Type + uint16 {2} - Tag + uint32 {4} - Size of chunk + byte {x} - Data (for "data" chunks: backgrounds, overlay & audio data) +*/ + +#include "lastexpress/drawable.h" + +#include "common/array.h" +#include "common/stream.h" + +namespace LastExpress { + +class AnimFrame; +class AppendableSound; + +class Animation : public Drawable { +public: + enum FlagType { + kFlagDefault = 16384, + kFlagProcess = 49152 + }; + + Animation(); + ~Animation(); + + bool load(Common::SeekableReadStream *stream, int flag = kFlagDefault); + bool process(); + bool hasEnded(); + Common::Rect draw(Graphics::Surface *surface); + void play(); + +private: + static const uint32 _soundBlockSize = 739; + + // despite their size field, info chunks don't have a payload + enum ChunkType { + kChunkTypeUnknown1 = 1, + kChunkTypeUnknown2 = 2, + kChunkTypeAudioInfo = 3, + kChunkTypeUnknown4 = 4, + kChunkTypeUnknown5 = 5, + kChunkTypeBackground1 = 10, + kChunkTypeSelectBackground1 = 11, + kChunkTypeBackground2 = 12, + kChunkTypeSelectBackground2 = 13, + kChunkTypeOverlay = 20, + kChunkTypeUpdate = 21, + kChunkTypeUpdateTransition = 22, + kChunkTypeSound1 = 30, + kChunkTypeSound2 = 31, + kChunkTypeAudioData = 32, + kChunkTypeAudioEnd = 99 + }; + + struct Chunk { + ChunkType type; + uint16 frame; + uint32 size; + }; + + void reset(); + AnimFrame *processChunkFrame(Common::SeekableReadStream *in, const Chunk &c) const; + void processChunkAudio(Common::SeekableReadStream *in, const Chunk &c); + + Common::SeekableReadStream *_stream; + Common::Array<Chunk> _chunks; + Common::Array<Chunk>::iterator _currentChunk; + AnimFrame *_overlay, *_background1, *_background2; + byte _backgroundCurrent; + AppendableSound *_audio; + + uint32 _startTime; + bool _changed; + int _flag; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_ANIMATION_H diff --git a/engines/lastexpress/data/archive.cpp b/engines/lastexpress/data/archive.cpp new file mode 100644 index 0000000000..1a5b6905a3 --- /dev/null +++ b/engines/lastexpress/data/archive.cpp @@ -0,0 +1,115 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Based on the Xentax Wiki documentation: +// http://wiki.xentax.com/index.php/The_Last_Express_SND + +#include "lastexpress/data/archive.h" + +#include "lastexpress/debug.h" + +#include "common/debug.h" +#include "common/file.h" + +namespace LastExpress { + +HPFArchive::HPFArchive(const Common::String &path) { + _filename = path; + + // Open a stream to the archive + Common::SeekableReadStream *archive = SearchMan.createReadStreamForMember(_filename); + if (!archive) { + debugC(2, kLastExpressDebugResource, "Error opening file: %s", path.c_str()); + return; + } + + debugC(2, kLastExpressDebugResource, "Opened archive: %s", path.c_str()); + + // Read header to get the number of files + uint32 numFiles = archive->readUint32LE(); + debugC(3, kLastExpressDebugResource, "Number of files in archive: %d", numFiles); + + // Read the list of files + for (unsigned int i = 0; i < numFiles; ++i) { + char name[13]; + HPFEntry entry; + + archive->read(&name, sizeof(char) * _archiveNameSize); + entry.offset = archive->readUint32LE(); + entry.size = archive->readUint32LE(); + entry.isOnHD = archive->readUint16LE(); + + // Terminate string + name[12] = '\0'; + + Common::String filename(name); + filename.toLowercase(); + + _files[filename] = entry; + + //debugC(9, kLastExpressDebugResource, "File entry: %s (offset:%d - Size: %d - HD: %u)", &name, entry.offset, entry.size, entry.isOnHD); + } + + // Close stream + delete archive; +} + +bool HPFArchive::hasFile(const Common::String &name) { + return (_files.find(name) != _files.end()); +} + +int HPFArchive::listMembers(Common::ArchiveMemberList &list) { + int numMembers = 0; + + for (FileMap::const_iterator i = _files.begin(); i != _files.end(); ++i) { + list.push_back(Common::ArchiveMemberList::value_type(new Common::GenericArchiveMember(i->_key, this))); + numMembers++; + } + + return numMembers; +} + +Common::ArchiveMemberPtr HPFArchive::getMember(const Common::String &name) { + if (!hasFile(name)) + return Common::ArchiveMemberPtr(); + + return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this)); +} + +Common::SeekableReadStream *HPFArchive::createReadStreamForMember(const Common::String &name) const { + FileMap::const_iterator fDesc = _files.find(name); + if (fDesc == _files.end()) + return NULL; + + Common::File *archive = new Common::File(); + if (!archive->open(_filename)) { + delete archive; + return NULL; + } + + return new Common::SeekableSubReadStream(archive, fDesc->_value.offset * _archiveSectorSize, fDesc->_value.offset * _archiveSectorSize + fDesc->_value.size * _archiveSectorSize, DisposeAfterUse::YES); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/archive.h b/engines/lastexpress/data/archive.h new file mode 100644 index 0000000000..3860245bc5 --- /dev/null +++ b/engines/lastexpress/data/archive.h @@ -0,0 +1,75 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_HPFARCHIVE_H +#define LASTEXPRESS_HPFARCHIVE_H + +/* + Archive file format (.HPF) + + uint32 {4} - number of files + + // for each file + char {12} - name (zero-terminated) + uint32 {4} - offset (expressed in sectors of 2048 bytes) + uint32 {4} - size (expressed in sectors of 2048 bytes) + byte {2} - file status: 1 = on disk (ie. in HD.HPF), 0 = on CD +*/ + +#include "common/archive.h" + +namespace LastExpress { + +class HPFArchive : public Common::Archive { +public: + HPFArchive(const Common::String &path); + + bool hasFile(const Common::String &name); + int listMembers(Common::ArchiveMemberList &list); + Common::ArchiveMemberPtr getMember(const Common::String &name); + Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const; + + int count() { return _files.size(); } + +private: + static const unsigned int _archiveNameSize = 12; + static const unsigned int _archiveSectorSize = 2048; + + // File entry + struct HPFEntry { + uint32 offset; ///< Offset (in sectors of 2048 bytes) + uint32 size; ///< Size (in sectors of 2048 bytes) + uint16 isOnHD; ///< File location (1: on HD; 0: on CD) + }; + + typedef Common::HashMap<Common::String, HPFEntry, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FileMap; + + FileMap _files; ///< List of files + Common::String _filename; ///< Filename of the archive +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_HPFARCHIVE_H diff --git a/engines/lastexpress/data/background.cpp b/engines/lastexpress/data/background.cpp new file mode 100644 index 0000000000..94d7fb16c3 --- /dev/null +++ b/engines/lastexpress/data/background.cpp @@ -0,0 +1,141 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Based on Deniz Oezmen's code and Xentax Wiki documentation +// http://oezmen.eu/ +// http://wiki.xentax.com/index.php/The_Last_Express_BG + +#include "lastexpress/data/background.h" + +#include "lastexpress/debug.h" + +namespace LastExpress { + +Background::Background() : _data(NULL) { + memset(&_header, 0, sizeof(BackgroundHeader)); +} + +Background::~Background() { + delete[] _data; +} + +bool Background::load(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + // Reset data + delete[] _data; + + // Load Background header + _header.posX = stream->readUint32LE(); + _header.posY = stream->readUint32LE(); + _header.width = stream->readUint32LE(); + _header.height = stream->readUint32LE(); + _header.redSize = stream->readUint32LE(); + _header.blueSize = stream->readUint32LE(); + _header.greenSize = stream->readUint32LE(); + + debugC(3, kLastExpressDebugGraphics, "Background Info: (%d, %d) - (%d x %d) - (%d, %d, %d)", + _header.posX, _header.posY, _header.width, _header.height, + _header.redSize, _header.blueSize, _header.greenSize); + + // Load and decompress Background channel data + uint32 numPix = _header.width * _header.height; + byte *dataR = decodeComponent(stream, _header.redSize, numPix); + byte *dataB = decodeComponent(stream, _header.blueSize, numPix); + byte *dataG = decodeComponent(stream, _header.greenSize, numPix); + + // Save to pixel buffer + // FIXME handle big-endian case + _data = new uint16[_header.width * _header.height]; + for (uint i = 0; i < _header.width * _header.height; i++) + _data[i] = (uint16)((dataR[i] << 10) + (dataG[i] << 5) + dataB[i]); + + // Cleanup buffers + delete[] dataR; + delete[] dataG; + delete[] dataB; + + delete stream; + + return true; +} + +Common::Rect Background::draw(Graphics::Surface *surface) { + if (!_data) { + debugC(2, kLastExpressDebugGraphics, "Trying to show a background before loading data!"); + return Common::Rect(); + } + + int i = 0; + for (uint16 y = 0; y < _header.height; y++) { + for (uint16 x = 0; x < _header.width; x++) { + surface->fillRect(Common::Rect((int16)(_header.posX + x), (int16)(_header.posY + y), (int16)(_header.posX + x + 1), (int16)(_header.posY + y + 1)), _data[i]); + i ++; + } + } + + return Common::Rect((int16)_header.posX, (int16)_header.posY, (int16)(_header.posX + _header.width), (int16)(_header.posY + _header.height)); +} + +byte *Background::decodeComponent(Common::SeekableReadStream *in, uint32 inSize, uint32 outSize) const { + // Create the destination array + byte *out = new byte[outSize]; + if (!out) + return NULL; + + // Initialize the decoding + uint32 inPos = 0; + uint32 outPos = 0; + + // Decode + while (inPos < inSize) { + byte inByte = in->readByte(); + inPos++; + + if (inByte < 0x80) { + // Direct decompression (RLE) + byte len = (inByte >> 5) + 1; + byte data = inByte & 0x1f; + for (int i = 0; i < len && outPos < outSize; i++) + out[outPos++] = data; + } else { + // Buffer back reference, 4096 byte window + // Take inByte and the following value as a big endian + // OfsLen while zeroing the first bit + uint16 ofsLen = ((inByte & 0x7F) << 8) | in->readByte(); + inPos++; + + int32 len = (ofsLen >> 12) + 3; + int32 hisPos = (int32)(outPos + (ofsLen & 0x0FFF) - 4096); + for (int i = 0; i < len && outPos < outSize; i++) + out[outPos++] = out[hisPos++]; + } + } + + return out; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/background.h b/engines/lastexpress/data/background.h new file mode 100644 index 0000000000..fc1cc26fa4 --- /dev/null +++ b/engines/lastexpress/data/background.h @@ -0,0 +1,81 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_BACKGROUND_H +#define LASTEXPRESS_BACKGROUND_H + +/* + Background file format (.BG) + + header: + uint32 {4} - position X on screen + uint32 {4} - position Y on screen + uint32 {4} - image width + uint32 {4} - image height + uint32 {4} - red colour channel data size + uint32 {4} - blue colour channel data size + uint32 {4} - green colour channel data size + + data: + byte {x} - red colour channel data + byte {x} - blue colour channel data + byte {x} - green colour channel data +*/ + +#include "lastexpress/drawable.h" + +#include "common/stream.h" + +namespace LastExpress { + +class Background : public Drawable { +public: + Background(); + ~Background(); + + bool load(Common::SeekableReadStream *stream); + + Common::Rect draw(Graphics::Surface *surface); + +private: + struct BackgroundHeader { + uint32 posX; ///< position X on screen + uint32 posY; ///< position Y on screen + uint32 width; ///< image width + uint32 height; ///< image height + uint32 redSize; ///< red color channel data size + uint32 blueSize; ///< blue color channel data size + uint32 greenSize; ///< green color channel data size + }; + + BackgroundHeader _header; + uint16 *_data; ///< decoded background data + + byte *decodeComponent(Common::SeekableReadStream *in, uint32 inSize, uint32 outSize) const; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_BACKGROUND_H diff --git a/engines/lastexpress/data/cursor.cpp b/engines/lastexpress/data/cursor.cpp new file mode 100644 index 0000000000..4e7003578a --- /dev/null +++ b/engines/lastexpress/data/cursor.cpp @@ -0,0 +1,144 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/data/cursor.h" + +#include "lastexpress/lastexpress.h" + +#include "common/system.h" +#include "graphics/cursorman.h" + +namespace LastExpress { + +Cursor::Cursor() : _current(kCursorMAX) { + memset(&_cursors, 0, sizeof(_cursors)); +} + +bool Cursor::load(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + // Load the whole file to memory + Common::MemoryReadStream *data = stream->readStream((uint32) stream->size()); + delete stream; + if (!data) + return false; + + // Read the hotspot data + for (int i = 0; i < kCursorMAX; i++) { + _cursors[i].hotspotX = data->readUint16LE(); + _cursors[i].hotspotY = data->readUint16LE(); + debugC(15, kLastExpressDebugCursor | kLastExpressDebugAll, + "Cursor %d hotspot x: %d, hotspot y: %d", + i, _cursors[i].hotspotX, _cursors[i].hotspotY); + } + + // Read the pixel data + for (int i = 0; i < kCursorMAX; i++) + for (int pix = 0; pix < 32 * 32; pix++) + _cursors[i].image[pix] = data->readUint16LE(); + + delete data; + return true; +} + +void Cursor::show(bool visible) const { + CursorMan.showMouse(visible); +} + +bool Cursor::checkStyle(CursorStyle style) const { + if (style >= kCursorMAX) { + debugC(2, kLastExpressDebugGraphics, "Trying to use an invalid cursor style: was %d, max %d", (int)style, kCursorMAX); + return false; + } + + return true; +} + +void Cursor::setStyle(CursorStyle style) { + if (!checkStyle(style)) + return; + + if (style == _current) + return; + + debugC(10, kLastExpressDebugCursor | kLastExpressDebugAll, "Cursor: setting style: %d", style); + + // Save the new cursor + _current = style; + + // Reuse the screen pixel format + Graphics::PixelFormat pf = g_system->getScreenFormat(); + CursorMan.replaceCursor((const byte *)getCursorImage(style), + 32, 32, _cursors[style].hotspotX, _cursors[style].hotspotY, + 0, 1, &pf); +} + +const uint16 *Cursor::getCursorImage(CursorStyle style) const { + if (!checkStyle(style)) + return NULL; + + return _cursors[style].image; +} + + +Icon::Icon(CursorStyle style) : _style(style), _x(0), _y(0), _brightness(100) {} + +void Icon::setPosition(int16 x, int16 y) { + _x = x; + _y = y; +} + +void Icon::setBrightness(uint brightness) { + assert(brightness <= 100); + + _brightness = (uint8)brightness; +} + +Common::Rect Icon::draw(Graphics::Surface *surface) { + const uint16 *image = ((LastExpressEngine *)g_engine)->getCursor()->getCursorImage((CursorStyle)_style); + if (!image) + return Common::Rect(); + + // TODO adjust brightness. The original game seems to be using a table for that (at least in the highlighting case) + for (int j = 0; j < 32; j++) { + uint16 *s = (uint16 *)surface->getBasePtr(_x, _y + j); + for (int i = 0; i < 32; i++) { + if (_brightness == 100) + *s = *image; + else + // HACK change color to show highlight + *s = (*image & 0x739C) >> 1; + + // Update the image and surface pointers + image++; + s++; + } + } + + return Common::Rect(_x, _y, _x + 32, _y + 32); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/cursor.h b/engines/lastexpress/data/cursor.h new file mode 100644 index 0000000000..992266569f --- /dev/null +++ b/engines/lastexpress/data/cursor.h @@ -0,0 +1,93 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_CURSOR_H +#define LASTEXPRESS_CURSOR_H + +/* + Cursor format (CURSORS.TBM) + + style table: + (for each cursor) + uint16 {2} - hotspot X + uint16 {2} - hotspot Y + + data: + (for each cursor) + uint16 {32*32} - cursor data +*/ + +#include "lastexpress/drawable.h" + +#include "lastexpress/shared.h" + +#include "common/stream.h" + +namespace LastExpress { + +class Icon : public Drawable { +public: + Icon(CursorStyle style); + + void setPosition(int16 x, int16 y); + void setBrightness(uint brightness); + Common::Rect draw(Graphics::Surface *surface); + +private: + CursorStyle _style; + int16 _x, _y; + uint8 _brightness; +}; + +class Cursor { +public: + Cursor(); + + bool load(Common::SeekableReadStream *stream); + void show(bool visible) const; + + void setStyle(CursorStyle style); + CursorStyle getStyle() const { return _current; } + +private: + // Style + CursorStyle _current; + + // Cursors data + struct { + uint16 image[32 * 32]; + uint16 hotspotX, hotspotY; + } _cursors[kCursorMAX]; + + bool checkStyle(CursorStyle style) const; + const uint16 *getCursorImage(CursorStyle style) const; + + // Only allow full access for drawing (needed for getCursorImage) + friend Common::Rect Icon::draw(Graphics::Surface *surface); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_CURSOR_H diff --git a/engines/lastexpress/data/font.cpp b/engines/lastexpress/data/font.cpp new file mode 100644 index 0000000000..5f4b3b40b8 --- /dev/null +++ b/engines/lastexpress/data/font.cpp @@ -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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/data/font.h" + +#include "common/system.h" + +namespace LastExpress { + +Font::Font() : _numGlyphs(0), _glyphs(NULL), _glyphWidths(0) { + memset(&_palette, 0, sizeof(_palette)); + memset(&_charMap, 0, sizeof(_charMap)); +} + +Font::~Font() { + reset(); +} + +void Font::reset() { + delete[] _glyphs; + delete[] _glyphWidths; +} + +bool Font::load(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + // Reset data + reset(); + + // Read the palette + for (uint i = 0; i < _paletteSize; i++) { + _palette[i] = stream->readUint16LE(); + } + + // Read the character map + stream->read(_charMap, _charMapSize); + + // Read the glyphs + _numGlyphs = stream->readUint16LE(); + _glyphs = new byte[_numGlyphs * 18 * 8]; + stream->read(_glyphs, _numGlyphs * 18 * 8); + + // TODO: Read something else? + //uint16 unknown = fontFile->readByte(); + //warning("unknown = %d", unknown); + //warning("pos = %d", fontFile->pos()); + //warning("left = %d", fontFile->size() - fontFile->pos()); + + //while (!fontFile->eos()) { + //unknown = fontFile->readByte(); + //warning("val = %d", unknown); + //} + + // Precalculate glyph widths + _glyphWidths = new byte[_numGlyphs]; + for (uint16 i = 0; i < _numGlyphs; i++) { + _glyphWidths[i] = getGlyphWidth(i); + } + + delete stream; + + return true; +} + + +uint16 Font::getCharGlyph(uint16 c) const { + //warning("%c", c); + if (c >= 0x200) + error("Express::Font: Invalid character %d", c); + + return _charMap[c]; +} + +byte *Font::getGlyphImg(uint16 g) { + if (!_glyphs) + error("Express::getGlyphImg: Invalid glyphs!"); + + if (g >= _numGlyphs) + error("Express::getGlyphImg: Invalid glyph %d (%d available)", g, _numGlyphs); + + return _glyphs + g * 18 * 8; +} + +uint8 Font::getGlyphWidth(uint16 g) { + byte *p = getGlyphImg(g); + + uint8 maxLineWidth = 0; + for (int j = 0; j < 18; j++) { + uint8 currentLineWidth = 0; + for (uint8 i = 0; i < 16; i++) { + byte index; + if (i % 2) + index = *p & 0xf; + else + index = *p >> 4; + uint16 color = _palette[index]; + if (color != 0x1f) + currentLineWidth = i; + if (i % 2) + p++; + } + if (currentLineWidth > maxLineWidth) + maxLineWidth = currentLineWidth; + } + + return maxLineWidth; +} + +byte *Font::getCharImg(uint16 c) { + return getGlyphImg(getCharGlyph(c)); +} + +uint8 Font::getCharWidth(uint16 c) const{ + if (c == 0x20) { + // Space is a special case + // TODO: this is an arbitrary value + return 10; + } else { + if (!_glyphWidths) + error("Express::getCharWidth: Invalid glyphs widths!"); + + return _glyphWidths[getCharGlyph(c)]; + } +} + +uint16 Font::getStringWidth(Common::String str) const { + uint16 width = 0; + for (uint i = 0; i < str.size(); i++) + width += getCharWidth((unsigned) (int)str[i]); + + return width; +} + +uint16 Font::getStringWidth(const uint16 *str, uint16 length) const { + uint16 width = 0; + for (uint i = 0; i < length; i++) + width += getCharWidth(str[i]); + + return width; +} + +void Font::drawChar(Graphics::Surface *surface, int16 x, int16 y, uint16 c) { + byte *p = getCharImg(c); + + for (int16 j = 0; j < 18; j++) { + for (int16 i = 0; i < 16; i++) { + byte index; + if (i % 2) + index = *p & 0xf; + else + index = *p >> 4; + uint16 color = _palette[index]; + if (color != 0x1f) { + surface->fillRect(Common::Rect(x+i, y+j, x+i+1, y+j+1), color); + } + if (i % 2) + p++; + } + } +} + +Common::Rect Font::drawString(Graphics::Surface *surface, int16 x, int16 y, Common::String str) { + int16 currentX = x; + for (uint i = 0; i < str.size(); i++) { + drawChar(surface, currentX, y, (unsigned) (int)str[i]); + currentX += getCharWidth((unsigned) (int)str[i]); + } + + return Common::Rect(x, y, x + currentX, y + (int16)_charHeight); +} + +Common::Rect Font::drawString(Graphics::Surface *surface, int16 x, int16 y, const uint16 *str, uint16 length) { + int16 currentX = x; + for (uint i = 0; i < length; i++) { + drawChar(surface, currentX, y, str[i]); + currentX += getCharWidth(str[i]); + } + + return Common::Rect(x, y, x + currentX, y + (int16)_charHeight); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/font.h b/engines/lastexpress/data/font.h new file mode 100644 index 0000000000..457c0c3432 --- /dev/null +++ b/engines/lastexpress/data/font.h @@ -0,0 +1,82 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_FONT_H +#define LASTEXPRESS_FONT_H + +/* + Font format (FONT.DAT) + + uint16 {40} - Palette data + byte {200} - Character map + uint16 {2} - Number of glyphs + + // For each glyph + byte {18*8} - Glyph data + + byte {x} - Unknown data (probably just garbage) +*/ + +#include "common/stream.h" +#include "graphics/surface.h" + +namespace LastExpress { + +class Font { +public: + Font(); + ~Font(); + + bool load(Common::SeekableReadStream *stream); + Common::Rect drawString(Graphics::Surface *surface, int16 x, int16 y, Common::String str); + Common::Rect drawString(Graphics::Surface *surface, int16 x, int16 y, const uint16 *str, uint16 length); + +private: + static const uint32 _paletteSize = 0x10; + static const uint32 _charMapSize = 0x200; + static const uint32 _charHeight = 16; + + void reset(); + + uint16 getCharGlyph(uint16 c) const; + byte *getGlyphImg(uint16 g); + uint8 getGlyphWidth(uint16 g); + byte *getCharImg(uint16 c); + uint8 getCharWidth(uint16 c) const; + uint16 getStringWidth(Common::String str) const; + uint16 getStringWidth(const uint16 *str, uint16 length) const; + void drawChar(Graphics::Surface *surface, int16 x, int16 y, uint16 c); + + // Font data + uint16 _palette[_paletteSize]; + uint8 _charMap[_charMapSize]; + uint16 _numGlyphs; + byte *_glyphs; + uint8 *_glyphWidths; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_FONT_H diff --git a/engines/lastexpress/data/scene.cpp b/engines/lastexpress/data/scene.cpp new file mode 100644 index 0000000000..e1be515bbc --- /dev/null +++ b/engines/lastexpress/data/scene.cpp @@ -0,0 +1,292 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/data/scene.h" + +#include "lastexpress/data/background.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +SceneHotspot *SceneHotspot::load(Common::SeekableReadStream *stream) { + SceneHotspot *hs = new SceneHotspot(); + + // Rect + hs->rect.left = (int16)stream->readUint16LE(); + hs->rect.right = (int16)stream->readUint16LE(); + hs->rect.top = (int16)stream->readUint16LE(); + hs->rect.bottom = (int16)stream->readUint16LE(); + + hs->coordsOffset = stream->readUint32LE(); + hs->scene = (SceneIndex)stream->readUint16LE(); + hs->location = stream->readByte(); + hs->action = (Action)stream->readByte(); + hs->param1 = stream->readByte(); + hs->param2 = stream->readByte(); + hs->param3 = stream->readByte(); + hs->cursor = stream->readByte(); + hs->next = stream->readUint32LE(); + + debugC(10, kLastExpressDebugScenes, "\thotspot: scene=%d location=%02d action=%d param1=%02d param2=%02d param3=%02d cursor=%02d rect=(%d, %d)x(%d,%d)", + hs->scene, hs->location, hs->action, hs->param1, hs->param2, hs->param3, hs->cursor, hs->rect.left, hs->rect.top, hs->rect.right, hs->rect.bottom); + debugC(10, kLastExpressDebugScenes, "\t coords=%d next=%d ", hs->coordsOffset, hs->next); + + // Read all coords data + uint32 offset = hs->coordsOffset; + while (offset != 0) { + + SceneCoord *sceneCoord = new SceneCoord; + + stream->seek(offset, SEEK_SET); + + sceneCoord->field_0 = stream->readSint32LE(); + sceneCoord->field_4 = stream->readSint32LE(); + sceneCoord->field_8 = stream->readByte(); + sceneCoord->next = stream->readUint32LE(); + + hs->_coords.push_back(sceneCoord); + + offset = sceneCoord->next; + } + + return hs; +} + +Common::String SceneHotspot::toString() const { + Common::String output = ""; + + output += Common::String::printf(" hotspot: scene=%d location=%02d action=%d param1=%02d param2=%02d param3=%02d cursor=%02d rect=(%d, %d)x(%d,%d)", + scene, location, action, param1, param2, param3, cursor, rect.left, rect.top, rect.right, rect.bottom); + + return output; +} + +bool SceneHotspot::isInside(const Common::Point &point) { + + bool contains = rect.contains(point); + + if (_coords.empty() || !contains) + return contains; + + // Checks extended coordinates + for (uint i = 0; i < _coords.size(); i++) { + + SceneCoord *sCoord = _coords[i]; + + bool cont; + if (sCoord->field_8) + cont = (sCoord->field_4 + point.x * sCoord->field_0 + 1000 * point.y) >= 0; + else + cont = (sCoord->field_4 + point.x * sCoord->field_0 + 1000 * point.y) <= 0; + + if (!cont) + return false; + } + + return true; +} + +////////////////////////////////////////////////////////////////////////// +// Scene +Scene::~Scene() { + // Free the hotspots + for (int i = 0; i < (int)_hotspots.size(); i++) + delete _hotspots[i]; +} + +Scene *Scene::load(Common::SeekableReadStream *stream) { + Scene *scene = new Scene(); + + stream->read(&scene->_name, sizeof(scene->_name)); + scene->_sig = stream->readByte(); + scene->entityPosition = (EntityPosition)stream->readUint16LE();; + scene->location = (Location)stream->readUint16LE(); + scene->car = (CarIndex)stream->readUint16LE(); + scene->position = stream->readByte(); + scene->type = (Type)stream->readByte(); + scene->param1 = stream->readByte(); + scene->param2 = stream->readByte(); + scene->param3 = stream->readByte(); + scene->_hotspot = stream->readUint32LE(); + + return scene; +} + +void Scene::loadHotspots(Common::SeekableReadStream *stream) { + if (!_hotspots.empty()) + return; + + debugC(10, kLastExpressDebugScenes, "Scene: name=%s, sig=%02d, entityPosition=%d, location=%d", _name, _sig, entityPosition, location); + debugC(10, kLastExpressDebugScenes, "\tcar=%02d, position=%02d, type=%02d, param1=%02d", car, position, type, param1); + debugC(10, kLastExpressDebugScenes, "\tparam2=%02d, param3=%02d, hotspot=%d\n", param2, param3, _hotspot); + + // Read all hotspots + if (_hotspot != 0) { + stream->seek((int32)_hotspot, SEEK_SET); + SceneHotspot *hotspot = SceneHotspot::load(stream); + while (hotspot) { + _hotspots.push_back(hotspot); + + if (hotspot->next == 0) + break; + + stream->seek((int32)hotspot->next, SEEK_SET); + hotspot = SceneHotspot::load(stream); + } + } +} + +bool Scene::checkHotSpot(const Common::Point &coord, SceneHotspot **hotspot) { + bool found = false; + int _location = 0; + + for (int i = 0; i < (int)_hotspots.size(); i++) { + if (_hotspots[i]->isInside(coord)) { + if (_location <= _hotspots[i]->location) { + _location = _hotspots[i]->location; + *hotspot = _hotspots[i]; + found = true; + } + } + } + + return found; +} + +SceneHotspot *Scene::getHotspot(uint index) { + if (_hotspots.empty()) + error("Scene::getHotspot: scene does not have any hotspots!"); + + if (index >= _hotspots.size()) + error("Scene::getHotspot: invalid index (was: %d, max: %d)", index, _hotspots.size() - 1); + + return _hotspots[index]; +} + +Common::Rect Scene::draw(Graphics::Surface *surface) { + // Safety checks + Common::Rect rect; + + Common::String sceneName(_name); + sceneName.trim(); + if (sceneName.empty()) + error("Scene::draw: This scene is not a valid drawing scene!"); + + // Load background + Background *background = ((LastExpressEngine *)g_engine)->getResourceManager()->loadBackground(sceneName); + if (background) { + rect = background->draw(surface); + delete background; + } + + return rect; +} + +Common::String Scene::toString() { + Common::String output = ""; + + output += Common::String::printf("Scene: name=%s, sig=%02d, entityPosition=%d, location=%d\n", _name, _sig, entityPosition, location); + output += Common::String::printf(" car=%02d, position=%02d, type=%02d, param1=%02d\n", car, position, type, param1); + output += Common::String::printf(" param2=%02d, param3=%02d, hotspot=%d\n", param2, param3, _hotspot); + + // Hotspots + if (_hotspots.size() != 0) { + output += "\nHotspots:\n"; + for (int i = 0; i < (int)_hotspots.size(); i++) + output += _hotspots[i]->toString() + "\n"; + } + + return output; +} + +////////////////////////////////////////////////////////////////////////// +// SceneLoader +SceneLoader::SceneLoader() : _stream(NULL) {} + +SceneLoader::~SceneLoader() { + clear(); +} + +void SceneLoader::clear() { + // Remove all scenes + for (int i = 0; i < (int)_scenes.size(); i++) + delete _scenes[i]; + + _scenes.clear(); + + delete _stream; +} + +bool SceneLoader::load(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + clear(); + + _stream = stream; + + // Read the default scene to get the total number of scenes + Scene *header = Scene::load(_stream); + if (!header) + error("SceneLoader::load: Invalid data file!"); + + debugC(2, kLastExpressDebugScenes, " found %d entries", header->entityPosition); /* Header entityPosition is the scene count */ + + if (header->entityPosition > 2500) { + delete header; + + return false; + } + + _scenes.push_back(header); + + // Read all the chunks + for (uint i = 0; i < (uint)header->entityPosition; ++i) { + Scene *scene = Scene::load(_stream); + if (!scene) + break; + + _scenes.push_back(scene); + } + + return true; +} + +Scene *SceneLoader::get(SceneIndex index) { + if (_scenes.empty()) + return NULL; + + if (index > _scenes.size()) + return NULL; + + // Load the hotspots if needed + _scenes[(int)index]->loadHotspots(_stream); + + return _scenes[(int)index]; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/scene.h b/engines/lastexpress/data/scene.h new file mode 100644 index 0000000000..ae2f3d2d28 --- /dev/null +++ b/engines/lastexpress/data/scene.h @@ -0,0 +1,245 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SCENE_H +#define LASTEXPRESS_SCENE_H + +/* + Scene format (CDTRAIN.DAT) + + (text:00484750) + header (24 bytes) + char {8} - entry name (null terminated) + byte {1} - 0xCD + uint16 {2} - number of scenes (for first entry - always 0 after?) + uint16 {2} - 11 ?? + uint16 {2} - car + byte {1} - camera position (used to get the proper sequences to show) + byte {1} - type + byte {1} - param1 + byte {1} - param2 + byte {1} - param3 + uint32 {4} - Offset to hotspot info struct + + probably contains cursor type too / scene index : 0 - 2500 (max) + + hotspot info (24 bytes) + uint16 {2} - left + uint16 {2} - right + uint16 {2} - top + uint16 {2} - bottom + uint32 {4} - scene coords data + uint16 {2} - scene + byte {1} - location; + byte {1} - action; + byte {1} - param1; + byte {1} - param2; + byte {1} - param3 + byte {1} - cursor + uint32{4} - offset to next hotpost + + coords data (9 bytes) + uint32 {4} - ?? + uint32 {4} - ?? + byte {1} - ?? + uint32 {4} - offset to next coords data structure + +*/ + +#include "lastexpress/drawable.h" +#include "lastexpress/shared.h" + +#include "common/array.h" +#include "common/stream.h" + +namespace LastExpress { + +class Scene; + +class SceneHotspot { +public: + enum Action { + kActionInventory = 1, + kActionSavePoint = 2, + kActionPlaySound = 3, + kActionPlayMusic = 4, + kActionKnockOnDoor = 5, + kActionCompartment = 6, + kActionPlaySounds = 7, + kActionPlayAnimation = 8, + kActionOpenCloseObject = 9, + kActionObjectUpdateLocation2 = 10, + kActionSetItemLocation = 11, + kAction12 = 12, + kActionPickItem = 13, + kActionDropItem = 14, + kAction15 = 15, + kActionEnterCompartment = 16, + kActionGetOutsideTrain = 18, + kActionSlip = 19, + kActionGetInsideTrain = 20, + kActionClimbUpTrain = 21, + kActionClimbDownTrain = 22, + kActionJumpUpDownTrain = 23, + kActionUnbound = 24, + kAction25 = 25, + kAction26 = 26, + kAction27 = 27, + kActionConcertSitCough = 28, + kAction29 = 29, + kActionCatchBeetle = 30, + kActionExitCompartment = 31, + kAction32 = 32, + KActionUseWhistle = 33, + kActionOpenMatchBox = 34, + kActionOpenBed = 35, + kActionDialog = 37, + kActionEggBox = 38, + kAction39 = 39, + kActionBed = 40, + kAction41 = 41, + kAction42 = 42, + kActionSwitchChapter = 43, + kAction44 = 44 + }; + + struct SceneCoord { + int32 field_0; + int32 field_4; + byte field_8; + uint32 next; + + SceneCoord() { + field_0 = 0; + field_4 = 0; + field_8 = 0; + next = 0; + } + }; + + Common::Rect rect; + uint32 coordsOffset; + SceneIndex scene; + byte location; + Action action; + byte param1; + byte param2; + byte param3; + byte cursor; + uint32 next; + + SceneHotspot() {} + static SceneHotspot *load(Common::SeekableReadStream *stream); + + bool isInside(const Common::Point &point); + + Common::String toString() const; + +private: + Common::Array<SceneCoord *> _coords; +}; + +class SceneLoader { +public: + SceneLoader(); + ~SceneLoader(); + + bool load(Common::SeekableReadStream *stream); + Scene *get(SceneIndex index); + + uint32 count() const { return _scenes.size() - 1; }; + +private: + Common::SeekableReadStream *_stream; + Common::Array<Scene *> _scenes; + + void clear(); +}; + +class Scene : public Drawable { +public: + // Enumerations + enum Type { + // PreProcess + kTypeObject = 1, + kTypeItem = 2, + kTypeItem2 = 3, + kTypeObjectItem = 4, + kTypeItem3 = 5, + kTypeObjectLocation2 = 6, + kTypeCompartments = 7, + kTypeCompartmentsItem = 8, + + // PostProcess + kTypeList = 128, + kTypeSavePointChapter = 129, + kTypeLoadBeetleSequences = 130, + kTypeGameOver = 131, + kTypeReadText = 132, + kType133 = 133 + }; + + // Data + EntityPosition entityPosition; + Location location; + CarIndex car; + Position position; + Type type; + byte param1; + byte param2; + byte param3; + + ~Scene(); + + Common::Rect draw(Graphics::Surface *surface); + + // Hotspots + Common::Array<SceneHotspot *> *getHotspots() { return &_hotspots; } + bool checkHotSpot(const Common::Point &coord, SceneHotspot **hotspot); + SceneHotspot *getHotspot(uint index = 0); + + Common::String toString(); + +private: + char _name[8]; + byte _sig; + uint32 _hotspot; + + Scene() {} + Common::Array<SceneHotspot *> _hotspots; + + static Scene *load(Common::SeekableReadStream *stream); + void loadHotspots(Common::SeekableReadStream *stream); + + void clear(); + + // Only allow full access for loading the scene and the hotspots + friend bool SceneLoader::load(Common::SeekableReadStream *stream); + friend Scene *SceneLoader::get(SceneIndex index); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SCENE_H diff --git a/engines/lastexpress/data/sequence.cpp b/engines/lastexpress/data/sequence.cpp new file mode 100644 index 0000000000..be8b14fc27 --- /dev/null +++ b/engines/lastexpress/data/sequence.cpp @@ -0,0 +1,475 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Based on Deniz Oezmen's code: http://oezmen.eu/ + +#include "lastexpress/data/sequence.h" + +#include "lastexpress/debug.h" + +namespace LastExpress { + +void FrameInfo::read(Common::SeekableReadStream *in, bool isSequence) { + // Save the current position + int32 basePos = in->pos(); + + dataOffset = in->readUint32LE(); + unknown = in->readUint32LE(); + paletteOffset = in->readUint32LE(); + xPos1 = in->readUint32LE(); + yPos1 = in->readUint32LE(); + xPos2 = in->readUint32LE(); + yPos2 = in->readUint32LE(); + initialSkip = in->readUint32LE(); + decompressedEndOffset = in->readUint32LE(); + + // Read the compression type for NIS files + if (!isSequence) { + in->seek(basePos + 0x124); + } else { + hotspot.left = (int16)in->readUint16LE(); + hotspot.right = (int16)in->readUint16LE(); + hotspot.top = (int16)in->readUint16LE(); + hotspot.bottom = (int16)in->readUint16LE(); + } + + compressionType = in->readByte(); + subType = (FrameSubType)in->readByte(); + + // Sequence information + field_2E = in->readByte(); + keepPreviousFrame = in->readByte(); + field_30 = in->readByte(); + field_31 = in->readByte(); + soundAction = in->readByte(); + field_33 = in->readByte(); + position = in->readByte(); + field_35 = in->readByte(); + field_36 = in->readUint16LE(); + field_38 = in->readUint32LE(); + entityPosition = (EntityPosition)in->readUint16LE(); + location = in->readUint16LE(); + next = in->readUint32LE(); +} + + +// AnimFrame + +AnimFrame::AnimFrame(Common::SeekableReadStream *in, const FrameInfo &f) : _palette(NULL) { + _palSize = 1; + // TODO: use just the needed rectangle + _image.create(640, 480, 1); + + //debugC(6, kLastExpressDebugGraphics, " Offsets: data=%d, unknown=%d, palette=%d", f.dataOffset, f.unknown, f.paletteOffset); + //debugC(6, kLastExpressDebugGraphics, " Position: (%d, %d) - (%d, %d)", f.xPos1, f.yPos1, f.xPos2, f.yPos2); + //debugC(6, kLastExpressDebugGraphics, " Initial Skip: %d", f.initialSkip); + //debugC(6, kLastExpressDebugGraphics, " Decompressed end offset: %d", f.decompressedEndOffset); + //debugC(6, kLastExpressDebugGraphics, " Hotspot: (%d, %d) x (%d, %d)\n", f.hotspot.left, f.hotspot.top, f.hotspot.right, f.hotspot.bottom); + //debugC(6, kLastExpressDebugGraphics, " Compression type: %u / %u", f.compressionType, f.subType); + //debugC(6, kLastExpressDebugGraphics, " Unknown: %u - %u - %u - %u - %u - %u - %u - %d", f.field_2E, f.field_2F, f.field_30, f.field_31, f.field_33, f.field_35, f.field_36, f.field_38); + //debugC(6, kLastExpressDebugGraphics, " Sound action: %u", f.soundAction); + //debugC(6, kLastExpressDebugGraphics, " Position: %d", f.position); + //debugC(6, kLastExpressDebugGraphics, " Entity Position: %d", f.entityPosition); + //debugC(6, kLastExpressDebugGraphics, " Location: %d", f.location); + //debugC(6, kLastExpressDebugGraphics, " next: %d", f.next); + + switch (f.compressionType) { + case 0: + // Empty frame + break; + case 3: + decomp3(in, f); + break; + case 4: + decomp4(in, f); + break; + case 5: + decomp5(in, f); + break; + case 7: + decomp7(in, f); + break; + case 255: + decompFF(in, f); + break; + default: + error("Unknown frame compression: %d", f.compressionType); + } + + readPalette(in, f); + _rect = Common::Rect((int16)f.xPos1, (int16)f.yPos1, (int16)f.xPos2, (int16)f.yPos2); + //_rect.debugPrint(0, "Frame rect:"); +} + +AnimFrame::~AnimFrame() { + _image.free(); + delete[] _palette; +} + +Common::Rect AnimFrame::draw(Graphics::Surface *s) { + byte *inp = (byte *)_image.pixels; + uint16 *outp = (uint16 *)s->pixels; + for (int i = 0; i < 640 * 480; i++, inp++, outp++) { + if (*inp) + *outp = _palette[*inp]; + } + return _rect; +} + +void AnimFrame::readPalette(Common::SeekableReadStream *in, const FrameInfo &f) { + // Read the palette + in->seek((int)f.paletteOffset); + _palette = new uint16[_palSize]; + for (uint32 i = 0; i < _palSize; i++) { + _palette[i] = in->readUint16LE(); + } +} + +void AnimFrame::decomp3(Common::SeekableReadStream *in, const FrameInfo &f) { + decomp34(in, f, 0x7, 3); +} + +void AnimFrame::decomp4(Common::SeekableReadStream *in, const FrameInfo &f) { + decomp34(in, f, 0xf, 4); +} + +void AnimFrame::decomp34(Common::SeekableReadStream *in, const FrameInfo &f, byte mask, byte shift) { + byte *p = (byte *)_image.getBasePtr(0, 0); + + uint32 skip = f.initialSkip / 2; + uint32 size = f.decompressedEndOffset / 2; + //warning("skip: %d, %d", skip % 640, skip / 640); + //warning("size: %d, %d", size % 640, size / 640); + //assert (f.yPos1 == skip / 640); + //assert (f.yPos2 == size / 640); + + uint32 numBlanks = 640 - (f.xPos2 - f.xPos1); + + in->seek((int)f.dataOffset); + for (uint32 out = skip; out < size; ) { + uint16 opcode = in->readByte(); + + if (opcode & 0x80) { + if (opcode & 0x40) { + opcode &= 0x3f; + out += numBlanks + opcode + 1; + } else { + opcode &= 0x3f; + if (opcode & 0x20) { + opcode = ((opcode & 0x1f) << 8) + in->readByte(); + if (opcode & 0x1000) { + out += opcode & 0xfff; + continue; + } + } + out += opcode + 2; + } + } else { + byte value = opcode & mask; + opcode >>= shift; + if (_palSize <= value) + _palSize = value + 1; + if (!opcode) + opcode = in->readByte(); + for (int i = 0; i < opcode; i++, out++) { + p[out] = value; + } + } + } +} + +void AnimFrame::decomp5(Common::SeekableReadStream *in, const FrameInfo &f) { + byte *p = (byte *)_image.getBasePtr(0, 0); + + uint32 skip = f.initialSkip / 2; + uint32 size = f.decompressedEndOffset / 2; + //warning("skip: %d, %d", skip % 640, skip / 640); + //warning("size: %d, %d", size % 640, size / 640); + //assert (f.yPos1 == skip / 640); + //assert (f.yPos2 == size / 640); + + in->seek((int)f.dataOffset); + for (uint32 out = skip; out < size; ) { + uint16 opcode = in->readByte(); + if (!(opcode & 0x1f)) { + opcode = (uint16)((opcode << 3) + in->readByte()); + if (opcode & 0x400) { + // skip these 10 bits + out += (opcode & 0x3ff); + } else { + out += opcode + 2; + } + } else { + byte value = opcode & 0x1f; + opcode >>= 5; + if (_palSize <= value) + _palSize = value + 1; + if (!opcode) + opcode = in->readByte(); + for (int i = 0; i < opcode; i++, out++) { + p[out] = value; + } + } + } +} + +void AnimFrame::decomp7(Common::SeekableReadStream *in, const FrameInfo &f) { + byte *p = (byte *)_image.getBasePtr(0, 0); + + uint32 skip = f.initialSkip / 2; + uint32 size = f.decompressedEndOffset / 2; + //warning("skip: %d, %d", skip % 640, skip / 640); + //warning("size: %d, %d", size % 640, size / 640); + //assert (f.yPos1 == skip / 640); + //assert (f.yPos2 == size / 640); + + uint32 numBlanks = 640 - (f.xPos2 - f.xPos1); + + in->seek((int)f.dataOffset); + for (uint32 out = skip; out < size; ) { + uint16 opcode = in->readByte(); + if (opcode & 0x80) { + if (opcode & 0x40) { + if (opcode & 0x20) { + opcode &= 0x1f; + out += numBlanks + opcode + 1; + } else { + opcode &= 0x1f; + if (opcode & 0x10) { + opcode = ((opcode & 0xf) << 8) + in->readByte(); + if (opcode & 0x800) { + // skip these 11 bits + out += (opcode & 0x7ff); + continue; + } + } + + // skip these 4 bits + out += opcode + 2; + } + } else { + opcode &= 0x3f; + byte value = in->readByte(); + if (_palSize <= value) + _palSize = value + 1; + for (int i = 0; i < opcode; i++, out++) { + p[out] = value; + } + } + } else { + if (_palSize <= opcode) + _palSize = opcode + 1; + // set the given value + p[out] = (byte)opcode; + out++; + } + } +} + +void AnimFrame::decompFF(Common::SeekableReadStream *in, const FrameInfo &f) { + byte *p = (byte *)_image.getBasePtr(0, 0); + + uint32 skip = f.initialSkip / 2; + uint32 size = f.decompressedEndOffset / 2; + + in->seek((int)f.dataOffset); + for (uint32 out = skip; out < size; ) { + uint16 opcode = in->readByte(); + + if (opcode < 0x80) { + if (_palSize <= opcode) + _palSize = opcode + 1; + // set the given value + p[out] = (byte)opcode; + out++; + } else { + if (opcode < 0xf0) { + if (opcode < 0xe0) { + // copy old part + uint32 old = out + ((opcode & 0x7) << 8) + in->readByte() - 2048; + opcode = ((opcode >> 3) & 0xf) + 3; + for (int i = 0; i < opcode; i++, out++, old++) { + p[out] = p[old]; + } + } else { + opcode = (opcode & 0xf) + 1; + byte value = in->readByte(); + if (_palSize <= value) + _palSize = value + 1; + for (int i = 0; i < opcode; i++, out++) { + p[out] = value; + } + } + } else { + out += ((opcode & 0xf) << 8) + in->readByte(); + } + } + } +} + + +////////////////////////////////////////////////////////////////////////// +// SEQUENCE +////////////////////////////////////////////////////////////////////////// + +Sequence::~Sequence() { + reset(); +} + +void Sequence::reset() { + _frames.clear(); + delete _stream; + _stream = NULL; +} + +Sequence *Sequence::load(Common::String name, Common::SeekableReadStream *stream, byte field30) { + Sequence *sequence = new Sequence(name); + + if (!sequence->load(stream, field30)) { + delete sequence; + return NULL; + } + + return sequence; +} + +bool Sequence::load(Common::SeekableReadStream *stream, byte field30) { + if (!stream) + return false; + + // Reset data + reset(); + + _field30 = field30; + + // Keep stream for later decoding of sequence + _stream = stream; + + // Read header to get the number of frames + _stream->seek(0); + uint32 numframes = _stream->readUint32LE(); + uint32 unknown = _stream->readUint32LE(); + debugC(3, kLastExpressDebugGraphics, "Number of frames in sequence: %d / unknown=0x%x", numframes, unknown); + + // Store frames information + for (uint i = 0; i < numframes; i++) { + + // Move stream to start of frame + _stream->seek((int32)(_sequenceHeaderSize + i * _sequenceFrameSize), SEEK_SET); + if (_stream->eos()) + error("Couldn't seek to the current frame data"); + + // Check if there is enough data + if ((unsigned)(_stream->size() - _stream->pos()) < _sequenceFrameSize) + error("The sequence frame does not have a valid header"); + + FrameInfo info; + info.read(_stream, true); + _frames.push_back(info); + } + + _isLoaded = true; + + return true; +} + +FrameInfo *Sequence::getFrameInfo(uint16 index) { + if (_frames.size() == 0) + error("Trying to decode a sequence before loading its data"); + + if (index > _frames.size() - 1) + error("Invalid sequence frame requested: %d, max %d", index, _frames.size() - 1); + + return &_frames[index]; +} + +AnimFrame *Sequence::getFrame(uint16 index) { + + FrameInfo *frame = getFrameInfo(index); + + if (!frame) + return NULL; + + // Skip "invalid" frames + if (frame->compressionType == 0) + return NULL; + + debugC(9, kLastExpressDebugGraphics, "Decoding sequence %s: frame %d / %d", _name.c_str(), index, _frames.size() - 1); + + return new AnimFrame(_stream, *frame); +} + +////////////////////////////////////////////////////////////////////////// +// SequenceFrame +SequenceFrame::~SequenceFrame() { + if (_dispose && _sequence) { + delete _sequence; + } + + _sequence = NULL; +} + +Common::Rect SequenceFrame::draw(Graphics::Surface *surface) { + if (!_sequence || _frame >= _sequence->count()) + return Common::Rect(); + + AnimFrame *f = _sequence->getFrame(_frame); + if (!f) + return Common::Rect(); + + Common::Rect rect = f->draw(surface); + + delete f; + + return rect; +} + +bool SequenceFrame::setFrame(uint16 frame) { + if (!_sequence) + return false; + + if (frame < _sequence->count()) { + _frame = frame; + return true; + } else + return false; +} + +bool SequenceFrame::nextFrame() { + return setFrame(_frame + 1); +} + +FrameInfo *SequenceFrame::getInfo() { + if (!_sequence) + error("SequenceFrame::getFrameInfo: Invalid sequence!"); + + return _sequence->getFrameInfo(_frame); +} + +bool SequenceFrame::equal(const SequenceFrame *other) const { + return _sequence->getName() == other->_sequence->getName() && _frame == other->_frame; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/sequence.h b/engines/lastexpress/data/sequence.h new file mode 100644 index 0000000000..eadc2962f8 --- /dev/null +++ b/engines/lastexpress/data/sequence.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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SEQUENCE_H +#define LASTEXPRESS_SEQUENCE_H + +/* + Sequence format (.SEQ / .NIS (frame header & data only)) + + uint32 {4} - Number of frames in sequence + uint32 {4} - Unknown + + frames headers (68 bytes): + // for each frame + uint32 {4} - Data offset (from beginning of file) + uint32 {4} - Unknown + uint32 {4} - Palette offset (from beginning of file) + uint32 {4} - Top-left X coordinate + uint32 {4} - Top-left Y coordinate + uint32 {4} - Bottom-right X coordinate + uint32 {4} - Bottom-right Y coordinate + uint32 {4} - Initial offset of decompressed data (doubled, since each pixel occupies one color word) + uint32 {4} - End of data after decompression + + (for SEQ files only) + uint16 {2} - Hotspot left + uint16 {2} - Hotspot right + uint16 {2} - Hotspot top + uint16 {2} - Hotspot bottom + byte {1} - Compression type + byte {1} - Subtype (determines which set of decompression functions will be called) => 0, 1, 2, 3 + byte {1} - Unknown + byte {1} - Unknown + uint16 {2} - Unknown + byte {1} - Sound action + byte {1} - Unknown + uint32 {4} - positionId + uint32 {4} - Unknown + uint16 {2} - Entity Position + uint16 {2} - Location (~z-order) + uint32 {4} - Next sequence in the linked list + + (for NIS files: found at 0x124) + byte {1} - Compression type + + palette data: + uint16 {x} - palette data (max size: 256) + + data + byte {x} - compressed image data +*/ + +#include "lastexpress/drawable.h" + +#include "lastexpress/shared.h" + +#include "common/array.h" +#include "common/stream.h" + +namespace LastExpress { + +enum FrameSubType { + kFrameTypeNone = 0, + kFrameType1 = 1, + kFrameType2 = 2, + kFrameType3 = 3 +}; + +struct FrameInfo { + void read(Common::SeekableReadStream *in, bool isSequence); + + uint32 dataOffset; ///< Data offset (from beginning of file) + uint32 unknown; ///< FIXME: unknown data + uint32 paletteOffset; ///< Palette offset (from beginning of file) + uint32 xPos1; ///< Top-left X coordinate + uint32 yPos1; ///< Top-left Y coordinate + uint32 xPos2; ///< Bottom-right X coordinate + uint32 yPos2; ///< Bottom-right Y coordinate + uint32 initialSkip; ///< Initial on-screen offset of decompressed data (doubled, since each pixel occupies one color word) + uint32 decompressedEndOffset; ///< End of data after decompression + + // NIS frame headers end here. SEQ frame headers have additional 32 bytes of + // data, notably the compression type at the position outlined above in + // CompPos_SEQ + + Common::Rect hotspot; + + byte compressionType; ///< Type of frame compression (0x03, 0x04, 0x05, 0x07, 0xFF) + FrameSubType subType; ///< Subtype (byte) + + byte field_2E; + byte keepPreviousFrame; + byte field_30; + byte field_31; + byte soundAction; + byte field_33; + Position position; + byte field_35; + int16 field_36; + uint32 field_38; + EntityPosition entityPosition; + uint16 location; + uint32 next; +}; + +class AnimFrame : public Drawable { +public: + AnimFrame(Common::SeekableReadStream *in, const FrameInfo &f); + ~AnimFrame(); + Common::Rect draw(Graphics::Surface *s); + +private: + void decomp3(Common::SeekableReadStream *in, const FrameInfo &f); + void decomp4(Common::SeekableReadStream *in, const FrameInfo &f); + void decomp34(Common::SeekableReadStream *in, const FrameInfo &f, byte mask, byte shift); + void decomp5(Common::SeekableReadStream *in, const FrameInfo &f); + void decomp7(Common::SeekableReadStream *in, const FrameInfo &f); + void decompFF(Common::SeekableReadStream *in, const FrameInfo &f); + void readPalette(Common::SeekableReadStream *in, const FrameInfo &f); + + Graphics::Surface _image; + uint16 _palSize; + uint16 *_palette; + Common::Rect _rect; +}; + +class Sequence { +public: + Sequence(Common::String name) : _stream(NULL), _isLoaded(false), _name(name), _field30(15) {} + ~Sequence(); + + static Sequence *load(Common::String name, Common::SeekableReadStream *stream = NULL, byte field30 = 15); + + bool load(Common::SeekableReadStream *stream, byte field30 = 15); + + uint16 count() const { return (uint16)_frames.size(); }; + AnimFrame *getFrame(uint16 index = 0); + FrameInfo *getFrameInfo(uint16 index = 0); + + Common::String getName() { return _name; } + byte getField30() { return _field30; } + + bool isLoaded() { return _isLoaded; } + +private: + static const uint32 _sequenceHeaderSize = 8; + static const uint32 _sequenceFrameSize = 68; + + void reset(); + + Common::Array<FrameInfo> _frames; + Common::SeekableReadStream *_stream; + bool _isLoaded; + + Common::String _name; + byte _field30; // used when copying sequences +}; + +class SequenceFrame : public Drawable { +public: + SequenceFrame(Sequence *sequence, uint16 frame = 0, bool dispose = false) : _sequence(sequence), _frame(frame), _dispose(dispose) {} + ~SequenceFrame(); + + Common::Rect draw(Graphics::Surface *surface); + + bool setFrame(uint16 frame); + uint32 getFrame() { return _frame; } + bool nextFrame(); + + Common::String getName() { return _sequence->getName(); } + FrameInfo *getInfo(); + + bool equal(const SequenceFrame *other) const; + +private: + Sequence *_sequence; + uint16 _frame; + bool _dispose; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SEQUENCE_H diff --git a/engines/lastexpress/data/snd.cpp b/engines/lastexpress/data/snd.cpp new file mode 100644 index 0000000000..496bd58772 --- /dev/null +++ b/engines/lastexpress/data/snd.cpp @@ -0,0 +1,141 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Based on the Xentax Wiki documentation: +// http://wiki.xentax.com/index.php/The_Last_Express_SND + +#include "lastexpress/data/snd.h" + +#include "lastexpress/debug.h" + +#include "sound/decoders/adpcm.h" +#include "sound/audiostream.h" + +namespace LastExpress { + +////////////////////////////////////////////////////////////////////////// +// Sound +////////////////////////////////////////////////////////////////////////// +SimpleSound::SimpleSound() : _size(0), _blocks(0), _blockSize(0) {} + +SimpleSound::~SimpleSound() { + stop(); +} + +// Stop the sound +void SimpleSound::stop() const { + g_system->getMixer()->stopHandle(_handle); +} + +void SimpleSound::loadHeader(Common::SeekableReadStream *in) { + _size = in->readUint32LE(); + _blocks = in->readUint16LE(); + debugC(5, kLastExpressDebugSound, " sound header data: size=\"%d\", %d blocks", _size, _blocks); + + assert (_size % _blocks == 0); + _blockSize = _size / _blocks; +} + +Audio::AudioStream *SimpleSound::makeDecoder(Common::SeekableReadStream *in, uint32 size) const { + return Audio::makeADPCMStream(in, DisposeAfterUse::YES, size, Audio::kADPCMMSImaLastExpress, 44100, 1, _blockSize); +} + +void SimpleSound::play(Audio::AudioStream *as) { + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &_handle, as); +} + +////////////////////////////////////////////////////////////////////////// +// StreamedSound +////////////////////////////////////////////////////////////////////////// +StreamedSound::StreamedSound() {} +StreamedSound::~StreamedSound() {} + +bool StreamedSound::load(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + g_system->getMixer()->stopHandle(_handle); + + loadHeader(stream); + + // Start decoding the input stream + Audio::AudioStream *as = makeDecoder(stream, _size); + + // Start playing the decoded audio stream + play(as); + + return true; +} + +////////////////////////////////////////////////////////////////////////// +// StreamedSound +////////////////////////////////////////////////////////////////////////// +AppendableSound::AppendableSound() : SimpleSound() { + // Create an audio stream where the decoded chunks will be appended + _as = Audio::makeQueuingAudioStream(44100, false); + _finished = false; + + // Start playing the decoded audio stream + play(_as); + + // Initialize the block size + // TODO: get it as an argument? + _blockSize = 739; +} + +AppendableSound::~AppendableSound() { + finish(); + + _as = NULL; +} + +void AppendableSound::queueBuffer(const byte *data, uint32 size) { + Common::MemoryReadStream *buffer = new Common::MemoryReadStream(data, size); + queueBuffer(buffer); +} + +void AppendableSound::queueBuffer(Common::SeekableReadStream *bufferIn) { + if (!_as) + error("AppendableSound::queueBuffer - internal error: the audio stream is invalid!"); + + // Setup the ADPCM decoder + uint32 sizeIn = (uint32)bufferIn->size(); + Audio::AudioStream *adpcm = makeDecoder(bufferIn, sizeIn); + + // Queue the stream + _as->queueAudioStream(adpcm); +} + +void AppendableSound::finish() { + if (!_as) + error("AppendableSound::queueBuffer - internal error: the audio stream is invalid!"); + + if (!_finished) + _as->finish(); + + _finished = true; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/snd.h b/engines/lastexpress/data/snd.h new file mode 100644 index 0000000000..2e0bc8c1b0 --- /dev/null +++ b/engines/lastexpress/data/snd.h @@ -0,0 +1,97 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SND_H +#define LASTEXPRESS_SND_H + +/* + Sound format (.SND / .LNK) + + uint32 {4} - data size + uint16 {2} - number of blocks + + // for each block + int16 {2} - initial sample + byte {1} - initial index + byte {1} - unused (00) + byte {x} - IMA ADPCM sample codes +*/ + +#include "common/stream.h" +#include "sound/mixer.h" + +namespace Audio { + class AudioStream; + class QueuingAudioStream; +} + +namespace LastExpress { + +class SimpleSound { +public: + SimpleSound(); + virtual ~SimpleSound(); + + void stop() const; + +protected: + void loadHeader(Common::SeekableReadStream *in); + Audio::AudioStream *makeDecoder(Common::SeekableReadStream *in, uint32 size) const; + void play(Audio::AudioStream *as); + + uint32 _size; ///< data size + ///< - NIS: size of all blocks, including those located in the matching LNK file + ///< - LNK: size of the LNK file itself, including the header + ///< - SND: size of all blocks + uint16 _blocks; ///< number of blocks + uint32 _blockSize; + Audio::SoundHandle _handle; +}; + +class StreamedSound : public SimpleSound { +public: + StreamedSound(); + ~StreamedSound(); + + bool load(Common::SeekableReadStream *stream); +}; + +class AppendableSound : public SimpleSound { +public: + AppendableSound(); + ~AppendableSound(); + + void queueBuffer(const byte *data, uint32 size); + void queueBuffer(Common::SeekableReadStream *bufferIn); + void finish(); + +private: + Audio::QueuingAudioStream *_as; + bool _finished; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SND_H diff --git a/engines/lastexpress/data/subtitle.cpp b/engines/lastexpress/data/subtitle.cpp new file mode 100644 index 0000000000..fb7d4ec6fa --- /dev/null +++ b/engines/lastexpress/data/subtitle.cpp @@ -0,0 +1,243 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Based on the Xentax Wiki documentation: +// http://wiki.xentax.com/index.php/The_Last_Express_SBE + +#include "lastexpress/data/subtitle.h" + +#include "lastexpress/data/font.h" + +#include "lastexpress/debug.h" + +#include "common/debug.h" + +namespace LastExpress { + +////////////////////////////////////////////////////////////////////////// +// Subtitle +////////////////////////////////////////////////////////////////////////// +class Subtitle { +public: + Subtitle() : _timeStart(0), _timeStop(0), _topLength(0), _topText(NULL), + _bottomLength(0), _bottomText(NULL) {} + ~Subtitle() { reset(); } + + bool load(Common::SeekableReadStream *in); + Common::Rect draw(Graphics::Surface *surface, Font *font); + + uint16 getTimeStart() const { return _timeStart; } + uint16 getTimeStop() const { return _timeStop; } + +private: + uint16 _timeStart; ///< display start time + uint16 _timeStop; ///< display stop time + + uint16 _topLength; ///< top line length + uint16 *_topText; ///< bottom line length + + uint16 _bottomLength; ///< top line (UTF-16 string) + uint16 *_bottomText; ///< bottom line (UTF-16 string) + + void reset(); +}; + +void Subtitle::reset() { + delete[] _topText; + delete[] _bottomText; + _topText = NULL; + _bottomText = NULL; +} + +template<typename T> +T *newArray(size_t n) +{ + if (n <= (size_t)-1 / sizeof(T)) + return new T[n]; + + // n is too large + return NULL; +} + +bool Subtitle::load(Common::SeekableReadStream *in) { + reset(); + + if (!in) + return false; + + // Read the display times + _timeStart = in->readUint16LE(); + _timeStop = in->readUint16LE(); + + // Read the text lengths + _topLength = in->readUint16LE(); + _bottomLength = in->readUint16LE(); + + // Create the buffers + if (_topLength) { + _topText = newArray<uint16>(_topLength); + if (!_topText) + return false; + } + if (_bottomLength) { + _bottomText = newArray<uint16>(_bottomLength); + if (!_bottomText) + return false; + } + + // Read the texts + for (int i = 0; i < _topLength; i++) + _topText[i] = in->readUint16LE(); + for (int i = 0; i < _bottomLength; i++) + _bottomText[i] = in->readUint16LE(); + + debugC(9, kLastExpressDebugSubtitle, " %d -> %d:", _timeStart, _timeStop); + if (_topLength) + debugC(9, kLastExpressDebugSubtitle, "\t%ls", (wchar_t *)_topText); + if (_bottomLength) + debugC(9, kLastExpressDebugSubtitle, "\t%ls", (wchar_t *)_bottomText); + + return true; +} + +Common::Rect Subtitle::draw(Graphics::Surface *surface, Font *font) { + Common::Rect rectTop, rectBottom; + + //FIXME find out proper subtitles coordinates (and hope it's hardcoded and not stored in the sequence or animation) + rectTop = font->drawString(surface, 100, 100, _topText, _topLength); + rectBottom = font->drawString(surface, 100, 300, _bottomText, _bottomLength); + + rectTop.extend(rectBottom); + + return rectTop; +} + + +////////////////////////////////////////////////////////////////////////// +// SubtitleManager +////////////////////////////////////////////////////////////////////////// +SubtitleManager::SubtitleManager(Font *font) : _font(font), _maxTime(0), _currentIndex(-1), _lastIndex(-1) {} + +SubtitleManager::~SubtitleManager() { + reset(); + + _font = NULL; +} + +void SubtitleManager::reset() { + for (int i = 0; i < (int)_subtitles.size(); i++) + delete _subtitles[i]; + + _subtitles.clear(); + _currentIndex = -1; + _lastIndex = -1; + + // Zero passed pointers + _font = NULL; +} + +bool SubtitleManager::load(Common::SeekableReadStream *stream) { + if (!stream) + return false; + + reset(); + + // Read header to get the number of subtitles + uint32 numSubtitles = stream->readUint16LE(); + if (stream->eos()) + error("Cannot read from subtitle file"); + + debugC(3, kLastExpressDebugSubtitle, "Number of subtitles in file: %d", numSubtitles); + + // TODO: Check that stream contain enough data + //if (stream->size() < (signed)(numSubtitles * sizeof(SubtitleData))) { + //debugC(2, kLastExpressDebugSubtitle, "Subtitle file does not contain valid data!"); + //return false; + //} + + // Read the list of subtitles + _maxTime = 0; + for (uint i = 0; i < numSubtitles; i++) { + Subtitle *subtitle = new Subtitle(); + if (!subtitle->load(stream)) { + // Failed to read this line + reset(); + + delete subtitle; + + return false; + } + + // Update the max time + if (subtitle->getTimeStop() > _maxTime) + _maxTime = subtitle->getTimeStop(); + + _subtitles.push_back(subtitle); + } + + delete stream; + + return true; +} + +uint16 SubtitleManager::getMaxTime() const { + return _maxTime; +} + +void SubtitleManager::setTime(uint16 time) { + _currentIndex = -1; + + // Find the appropriate line to show + for (int16 i = 0; i < (int16)_subtitles.size(); i++) { + if ((time >= _subtitles[i]->getTimeStart()) && (time <= _subtitles[i]->getTimeStop())) { + // Keep the index of the line to show + _currentIndex = i; + return; + } + } +} + +bool SubtitleManager::hasChanged() const { + // TODO: mark the old line rect as dirty + if (_currentIndex != _lastIndex) + return true; + else + return false; +} + +Common::Rect SubtitleManager::draw(Graphics::Surface *surface) { + // Update the last drawn index + _lastIndex = _currentIndex; + + // Return if we don't have to draw any line + if (_currentIndex == -1) + return Common::Rect(); + + // Draw the current line + assert(_currentIndex >= 0 && _currentIndex < (int16)_subtitles.size()); + return _subtitles[_currentIndex]->draw(surface, _font); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/data/subtitle.h b/engines/lastexpress/data/subtitle.h new file mode 100644 index 0000000000..9acb7068f1 --- /dev/null +++ b/engines/lastexpress/data/subtitle.h @@ -0,0 +1,79 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SUBTITLE_H +#define LASTEXPRESS_SUBTITLE_H + +/* + Subtitle format (.SBE) + + uint16 {2} - number of subtitles + + // for each subtitle + uint16 {2} - display start time + uint16 {2} - display stop time + uint16 {2} - top line length + uint16 {2} - bottom line length + byte {x} - top line (UTF-16 string) + byte {x} - bottom line (UTF-16 string) + + Subtitles seem to be drawn on screen at (80, 420) x (560, 458) +*/ + +#include "lastexpress/drawable.h" + +#include "common/array.h" +#include "common/stream.h" + +namespace LastExpress { + +class Font; +class Subtitle; + +class SubtitleManager : public Drawable { +public: + SubtitleManager(Font *font); + ~SubtitleManager(); + + bool load(Common::SeekableReadStream *stream); + uint16 getMaxTime() const; + void setTime(uint16 time); + bool hasChanged() const; + Common::Rect draw(Graphics::Surface *surface); + +private: + Common::Array<Subtitle *> _subtitles; + Font *_font; + uint16 _maxTime; + + int16 _currentIndex; + int16 _lastIndex; + + void reset(); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SUBTITLE_H diff --git a/engines/lastexpress/debug.cpp b/engines/lastexpress/debug.cpp new file mode 100644 index 0000000000..cf81e162ae --- /dev/null +++ b/engines/lastexpress/debug.cpp @@ -0,0 +1,1091 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/debug.h" + +// Data +#include "lastexpress/data/animation.h" +#include "lastexpress/data/background.h" +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/scene.h" +#include "lastexpress/data/sequence.h" +#include "lastexpress/data/snd.h" +#include "lastexpress/data/subtitle.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/beetle.h" +#include "lastexpress/game/fight.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savegame.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +#include "common/debug-channels.h" +#include "common/events.h" + +namespace LastExpress { + +Debugger::Debugger(LastExpressEngine *engine) : _engine(engine), _command(NULL), _numParams(0), _commandParams(NULL) { + + ////////////////////////////////////////////////////////////////////////// + // Register the debugger commands + + // General + DCmd_Register("help", WRAP_METHOD(Debugger, cmdHelp)); + + // Data + DCmd_Register("ls", WRAP_METHOD(Debugger, cmdListFiles)); + + DCmd_Register("showframe", WRAP_METHOD(Debugger, cmdShowFrame)); + DCmd_Register("showbg", WRAP_METHOD(Debugger, cmdShowBg)); + DCmd_Register("playseq", WRAP_METHOD(Debugger, cmdPlaySeq)); + DCmd_Register("playsnd", WRAP_METHOD(Debugger, cmdPlaySnd)); + DCmd_Register("playsbe", WRAP_METHOD(Debugger, cmdPlaySbe)); + DCmd_Register("playnis", WRAP_METHOD(Debugger, cmdPlayNis)); + + // Scene & interaction + DCmd_Register("loadscene", WRAP_METHOD(Debugger, cmdLoadScene)); + DCmd_Register("fight", WRAP_METHOD(Debugger, cmdFight)); + DCmd_Register("beetle", WRAP_METHOD(Debugger, cmdBeetle)); + + // Game + DCmd_Register("delta", WRAP_METHOD(Debugger, cmdTimeDelta)); + DCmd_Register("dump", WRAP_METHOD(Debugger, cmdDump)); + DCmd_Register("entity", WRAP_METHOD(Debugger, cmdEntity)); + + // Misc + DCmd_Register("loadgame", WRAP_METHOD(Debugger, cmdLoadGame)); + DCmd_Register("chapter", WRAP_METHOD(Debugger, cmdSwitchChapter)); + DCmd_Register("clear", WRAP_METHOD(Debugger, cmdClear)); + + resetCommand(); + + _soundStream = new StreamedSound(); +} + +Debugger::~Debugger() { + DebugMan.clearAllDebugChannels(); + + delete _soundStream; + + // Zero passed pointers + _engine = NULL; + _command = NULL; + _commandParams = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Helper functions +////////////////////////////////////////////////////////////////////////// +bool Debugger::hasCommand() const { + return (_numParams != 0); +} + +void Debugger::resetCommand() { + _command = NULL; + _commandParams = NULL; + _numParams = 0; +} + +int Debugger::getNumber(const char *arg) const { + return strtol(arg, (char **)NULL, 0); +} + +void Debugger::copyCommand(int argc, const char **argv) { + _commandParams = (char **)malloc((uint)argc); + if (!_commandParams) + return; + + _numParams = argc; + + for (int i = 0; i < _numParams; i++) { + _commandParams[i] = (char *)malloc(strlen(argv[i])); + strcpy(_commandParams[i], ""); + strcpy(_commandParams[i], argv[i]); + } + + // Exit the debugger! + Cmd_Exit(0, 0); +} + +void Debugger::callCommand() { + if (_command) + (*_command)(_numParams, const_cast<const char **>(_commandParams)); +} + +void Debugger::loadArchive(ArchiveIndex index) const { + _engine->getResourceManager()->loadArchive(index); + getScenes()->loadSceneDataFile(index); +} + +// Restore loaded archive +void Debugger::restoreArchive() const { + + ArchiveIndex index = kArchiveCd1; + + switch (getProgress().chapter) { + default: + case kChapter1: + index = kArchiveCd1; + break; + + case kChapter2: + case kChapter3: + index = kArchiveCd2; + break; + + case kChapter4: + case kChapter5: + index = kArchiveCd3; + break; + } + + _engine->getResourceManager()->loadArchive(index); + getScenes()->loadSceneDataFile(index); +} + +////////////////////////////////////////////////////////////////////////// +// Debugger commands +////////////////////////////////////////////////////////////////////////// +bool Debugger::cmdHelp(int, const char **) { + DebugPrintf("Debug flags\n"); + DebugPrintf("-----------\n"); + DebugPrintf(" debugflag_list - Lists the available debug flags and their status\n"); + DebugPrintf(" debugflag_enable - Enables a debug flag\n"); + DebugPrintf(" debugflag_disable - Disables a debug flag\n"); + DebugPrintf("\n"); + DebugPrintf("Commands\n"); + DebugPrintf("--------\n"); + DebugPrintf(" ls - list files in the archive\n"); + DebugPrintf("\n"); + DebugPrintf(" showframe - show a frame from a sequence\n"); + DebugPrintf(" showbg - show a background\n"); + DebugPrintf(" playseq - play a sequence\n"); + DebugPrintf(" playsnd - play a sound\n"); + DebugPrintf(" playsbe - play a subtitle\n"); + DebugPrintf(" playnis - play an animation\n"); + DebugPrintf("\n"); + DebugPrintf(" loadscene - load a scene\n"); + DebugPrintf(" fight - start a fight\n"); + DebugPrintf(" beetle - start the beetle game\n"); + DebugPrintf("\n"); + DebugPrintf(" delta - Adjust the time delta\n"); + DebugPrintf(" dump - Dump game data\n"); + DebugPrintf(" entity - Dump entity data\n"); + DebugPrintf("\n"); + DebugPrintf(" loadgame - load a saved game\n"); + DebugPrintf(" chapter - switch to a specific chapter\n"); + DebugPrintf(" clear - clear the screen\n"); + DebugPrintf("\n"); + return true; +} + +/** + * Command: list files in archive + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdListFiles(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + Common::String filter(const_cast<char *>(argv[1])); + + // Load the proper archive + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + Common::ArchiveMemberList list; + int count = _engine->getResourceManager()->listMatchingMembers(list, filter); + + DebugPrintf("Number of matches: %d\n", count); + for (Common::ArchiveMemberList::iterator it = list.begin(); it != list.end(); ++it) + DebugPrintf(" %s\n", (*it)->getName().c_str()); + + // Restore archive + if (argc == 3) + restoreArchive(); + } else { + DebugPrintf("Syntax: ls <filter> (use * for all)\n (<cd number>)"); + } + + return true; +} + +/** + * Command: Shows a frame + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdShowFrame(int argc, const char **argv) { + if (argc == 3 || argc == 4) { + Common::String filename(const_cast<char *>(argv[1])); + filename += ".seq"; + + if (argc == 4) + loadArchive((ArchiveIndex)getNumber(argv[3])); + + if (!_engine->getResourceManager()->hasFile(filename)) { + DebugPrintf("Cannot find file: %s\n", filename.c_str()); + return true; + } + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdShowFrame); + copyCommand(argc, argv); + + return Cmd_Exit(0, 0); + } else { + Sequence sequence(filename); + if (sequence.load(getArchive(filename))) { + _engine->getCursor()->show(false); + clearBg(GraphicsManager::kBackgroundOverlay); + + AnimFrame *frame = sequence.getFrame((uint16)getNumber(argv[2])); + if (!frame) { + DebugPrintf("Invalid frame index: %i\n", filename.c_str()); + resetCommand(); + return true; + } + + _engine->getGraphicsManager()->draw(frame, GraphicsManager::kBackgroundOverlay); + delete frame; + + askForRedraw(); + redrawScreen(); + + _engine->_system->delayMillis(1000); + _engine->getCursor()->show(true); + } + + resetCommand(); + + if (argc == 4) + restoreArchive(); + } + } else { + DebugPrintf("Syntax: cmd_showframe <seqname> <index> (<cd number>)\n"); + } + return true; +} + +/** + * Command: shows a background + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdShowBg(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + Common::String filename(const_cast<char *>(argv[1])); + + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + if (!_engine->getResourceManager()->hasFile(filename + ".BG")) { + DebugPrintf("Cannot find file: %s\n", (filename + ".BG").c_str()); + return true; + } + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdShowBg); + copyCommand(argc, argv); + + return Cmd_Exit(0, 0); + } else { + clearBg(GraphicsManager::kBackgroundC); + + Background *background = _engine->getResourceManager()->loadBackground(filename); + if (background) { + _engine->getGraphicsManager()->draw(background, GraphicsManager::kBackgroundC); + delete background; + askForRedraw(); + } + + redrawScreen(); + + if (argc == 3) + restoreArchive(); + + // Pause for a second to be able to see the background + _engine->_system->delayMillis(1000); + + resetCommand(); + } + } else { + DebugPrintf("Syntax: showbg <bgname> (<cd number>)\n"); + } + return true; +} + +/** + * Command: plays a sequence. + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdPlaySeq(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + Common::String filename(const_cast<char *>(argv[1])); + filename += ".seq"; + + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + if (!_engine->getResourceManager()->hasFile(filename)) { + DebugPrintf("Cannot find file: %s\n", filename.c_str()); + return true; + } + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdPlaySeq); + copyCommand(argc, argv); + + return Cmd_Exit(0, 0); + } else { + Sequence *sequence = new Sequence(filename); + if (sequence->load(getArchive(filename))) { + + // Check that we have at least a frame to show + if (sequence->count() == 0) { + delete sequence; + return false; + } + + _engine->getCursor()->show(false); + + SequenceFrame player(sequence, 0, true); + do { + // Clear screen + clearBg(GraphicsManager::kBackgroundA); + + _engine->getGraphicsManager()->draw(&player, GraphicsManager::kBackgroundA); + + askForRedraw(); + redrawScreen(); + + // Handle right-click to interrupt sequence + Common::Event ev; + _engine->getEventManager()->pollEvent(ev); + if (ev.type == Common::EVENT_RBUTTONUP) + break; + + _engine->_system->delayMillis(175); + + // go to the next frame + } while (player.nextFrame()); + _engine->getCursor()->show(true); + } else { + // Sequence player is deleting his reference to the sequence, but we need to take care of it if the + // sequence could not be loaded + delete sequence; + } + + resetCommand(); + + if (argc == 3) + restoreArchive(); + } + } else { + DebugPrintf("Syntax: playseq <seqname> (<cd number>)\n"); + } + return true; +} + +/** + * Command: plays a sound + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdPlaySnd(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + // Add .SND at the end of the filename if needed + Common::String name(const_cast<char *>(argv[1])); + if (!name.contains('.')) + name += ".SND"; + + if (!_engine->getResourceManager()->hasFile(name)) { + DebugPrintf("Cannot find file: %s\n", name.c_str()); + return true; + } + + _engine->_system->getMixer()->stopAll(); + + _soundStream->load(getArchive(name)); + + if (argc == 3) + restoreArchive(); + } else { + DebugPrintf("Syntax: playsnd <sndname> (<cd number>)\n"); + } + return true; +} + +/** + * Command: plays subtitles + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdPlaySbe(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + Common::String filename(const_cast<char *>(argv[1])); + + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + filename += ".sbe"; + + if (!_engine->getResourceManager()->hasFile(filename)) { + DebugPrintf("Cannot find file: %s\n", filename.c_str()); + return true; + } + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdPlaySbe); + copyCommand(argc, argv); + + return Cmd_Exit(0, 0); + } else { + SubtitleManager subtitle(_engine->getFont()); + if (subtitle.load(getArchive(filename))) { + _engine->getCursor()->show(false); + for (uint16 i = 0; i < subtitle.getMaxTime(); i += 25) { + clearBg(GraphicsManager::kBackgroundAll); + + subtitle.setTime(i); + _engine->getGraphicsManager()->draw(&subtitle, GraphicsManager::kBackgroundOverlay); + + askForRedraw(); + redrawScreen(); + + // Handle right-click to interrupt sequence + Common::Event ev; + _engine->getEventManager()->pollEvent(ev); + if (ev.type == Common::EVENT_RBUTTONUP) + break; + + _engine->_system->delayMillis(500); + } + _engine->getCursor()->show(true); + } + + if (argc == 3) + restoreArchive(); + + resetCommand(); + } + } else { + DebugPrintf("Syntax: playsbe <sbename> (<cd number>)\n"); + } + return true; +} + +/** + * Command: plays a NIS animation sequence. + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdPlayNis(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + Common::String name(const_cast<char *>(argv[1])); + + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + // If we got a nis filename, check that the file exists + if (name.contains('.') && _engine->getResourceManager()->hasFile(name)) { + DebugPrintf("Cannot find file: %s\n", name.c_str()); + return true; + } + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdPlayNis); + copyCommand(argc, argv); + + return Cmd_Exit(0, 0); + } else { + // Make sure we are not called in a loop + _numParams = 0; + + + // Check if we got a nis filename or an animation index + if (name.contains('.')) { + Animation animation; + if (animation.load(getArchive(name))) { + _engine->getCursor()->show(false); + animation.play(); + _engine->getCursor()->show(true); + } + } else { + getAction()->playAnimation((EventIndex)atoi(name.c_str()), true); + } + + if (argc == 3) + restoreArchive(); + + resetCommand(); + } + } else { + DebugPrintf("Syntax: playnis <nisname.nis or animation index> (<cd number>)\n"); + } + return true; +} + +/** + * Command: loads a scene + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdLoadScene(int argc, const char **argv) { + if (argc == 2 || argc == 3) { + int cd = 1; + SceneIndex index = (SceneIndex)getNumber(argv[1]);; + + // Check args + if (argc == 3) + loadArchive((ArchiveIndex)getNumber(argv[2])); + + if (index > 2500) { + DebugPrintf("Error: invalid index value (0-2500)"); + return true; + } + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdLoadScene); + copyCommand(argc, argv); + + return Cmd_Exit(0, 0); + } else { + + clearBg(GraphicsManager::kBackgroundAll); + + /************ DEBUG *************************/ + // Use to find scenes with certain values + + //for (int i = index; i < 2500; i++) { + // loadSceneObject(scene, i); + + // if (scene.getHeader() && scene.getHeader()->car == 5 && scene.getHeader()->position == 81) { + // DebugPrintf("Found scene: %d", i); + + // // Draw scene found + // _engine->getGraphicsManager()->draw(&scene, GraphicsManager::kBackgroundC); + + // askForRedraw(); + // redrawScreen(); + // _engine->_system->delayMillis(500); + + // break; + // } + //} + + //delete _sceneLoader; + //resetCommand(); + //return true; + + /*********************************************/ + Scene *scene = getScenes()->get(index); + if (!scene) { + DebugPrintf("Cannot load scene %i from CD %i", index, cd); + resetCommand(); + + return true; + } + + _engine->getGraphicsManager()->draw(scene, GraphicsManager::kBackgroundC); + + askForRedraw(); + redrawScreen(); + + // Pause for a second to be able to see the scene + _engine->_system->delayMillis(500); + + if (argc == 3) + restoreArchive(); + + resetCommand(); + } + } else { + DebugPrintf("Syntax: loadscene <scene index> (<cd number>)\n"); + } + return true; +} + +/** + * Command: starts a fight sequence + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdFight(int argc, const char **argv) { + if (argc == 2) { + FightType type = (FightType)getNumber(argv[1]); + + // Load proper data file + ArchiveIndex index = kArchiveCd1; + switch (type) { + default: + goto error; + + case kFightMilos: + index = kArchiveCd1; + break; + + case kFightAnna: + index = kArchiveCd2; + break; + + case kFightIvo: + case kFightSalko: + case kFightVesna: + index = kArchiveCd3; + break; + } + + loadArchive(index); + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdFight); + copyCommand(argc, argv); + + return false; + } else { + // Make sure we are not called in a loop + _numParams = 0; + + clearBg(GraphicsManager::kBackgroundAll); + askForRedraw(); + redrawScreen(); + + SceneIndex lastScene = getState()->scene; + + getFight()->setup(type) ? DebugPrintf("Lost fight!\n") : DebugPrintf("Won fight!\n"); + + // Pause for a second to be able to see the final scene + _engine->_system->delayMillis(1000); + + // Restore loaded archive + restoreArchive(); + + // Stop audio and restore scene + getSound()->stopAllSound(); + + clearBg(GraphicsManager::kBackgroundAll); + + Scene *scene = getScenes()->get(lastScene); + _engine->getGraphicsManager()->draw(scene, GraphicsManager::kBackgroundC); + + askForRedraw(); + redrawScreen(); + + resetCommand(); + } + } else { +error: + DebugPrintf("Syntax: fight <id> (id=2001-2005)\n"); + } + + return true; +} + +/** + * Command: starts the beetle sequence + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdBeetle(int argc, const char **argv) { + if (argc == 1) { + // Load proper data file (beetle game in in Cd2) + loadArchive(kArchiveCd2); + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdBeetle); + copyCommand(argc, argv); + + return false; + } else { + clearBg(GraphicsManager::kBackgroundAll); + askForRedraw(); + redrawScreen(); + + // Save current state + SceneIndex previousScene = getState()->scene; + ObjectLocation previousLocation = getInventory()->get(kItemBeetle)->location; + ChapterIndex previousChapter = (ChapterIndex)getProgress().chapter; + + // Setup scene & inventory + getProgress().chapter = kChapter2; + Scene *scene = getScenes()->get(kSceneBeetle); + getInventory()->get(kItemBeetle)->location = kObjectLocation3; + + askForRedraw(); + redrawScreen(); + + // Load the beetle game + Action *action = NULL; + Beetle *beetle = new Beetle(_engine); + if (!beetle->isLoaded()) + beetle->load(); + + // Play the game + Common::Event ev; + bool playgame = true; + while (playgame) { + // Update beetle + beetle->update(); + + askForRedraw(); + redrawScreen(); + + while (g_engine->getEventManager()->pollEvent(ev)) { + + switch (ev.type) { + default: + break; + + case Common::EVENT_KEYDOWN: + // Exit beetle game on escape + if (ev.kbd.keycode == Common::KEYCODE_ESCAPE) + playgame = false; + + break; + + case Common::EVENT_MOUSEMOVE: { + // Update cursor + CursorStyle style = kCursorNormal; + SceneHotspot *hotspot = NULL; + if (scene->checkHotSpot(ev.mouse, &hotspot)) { + if (!action) + action = new Action(_engine); + + style = action->getCursor(*hotspot); + } + + _engine->getCursor()->setStyle(style); + break; + } + + + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONUP: + // Update coordinates + getLogic()->getGameState()->setCoordinates(ev.mouse); + + if (beetle->catchBeetle()) + playgame = false; + break; + } + + _engine->_system->delayMillis(10); + } + } + + // Cleanup + beetle->unload(); + delete beetle; + SAFE_DELETE(action); + + // Pause for a second to be able to see the final scene + _engine->_system->delayMillis(1000); + + // Restore state + getProgress().chapter = previousChapter; + getInventory()->get(kItemBeetle)->location = previousLocation; + + // Restore loaded archive + restoreArchive(); + + // Stop audio and restore scene + getSound()->stopAllSound(); + + clearBg(GraphicsManager::kBackgroundAll); + + Scene *oldScene = getScenes()->get(previousScene); + _engine->getGraphicsManager()->draw(oldScene, GraphicsManager::kBackgroundC); + + askForRedraw(); + redrawScreen(); + + resetCommand(); + } + } else { + DebugPrintf("Syntax: beetle\n"); + } + + return true; +} + +/** + * Command: adjusts the time delta + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdTimeDelta(int argc, const char **argv) { + if (argc == 2) { + int delta = getNumber(argv[1]); + + if (delta <= 0 || delta > 500) + goto label_error; + + getState()->timeDelta = (uint)delta; + } else { +label_error: + DebugPrintf("Syntax: delta <time delta> (delta=1-500)\n"); + } + + return true; +} + +/** + * Command: dumps game logic data + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdDump(int argc, const char **argv) { +#define OUTPUT_DUMP(name, text) \ + DebugPrintf(#name "\n"); \ + DebugPrintf("--------------------------------------------------------------------\n\n"); \ + DebugPrintf(text); \ + DebugPrintf("\n"); + + if (argc == 2) { + switch (getNumber(argv[1])) { + default: + goto label_error; + + // GameState + case 1: + OUTPUT_DUMP("Game state", getState()->toString().c_str()); + break; + + // Inventory + case 2: + OUTPUT_DUMP("Inventory", getInventory()->toString().c_str()); + break; + + // Objects + case 3: + OUTPUT_DUMP("Objects", getObjects()->toString().c_str()); + break; + + // SavePoints + case 4: + OUTPUT_DUMP("SavePoints", getSavePoints()->toString().c_str()); + break; + + case 5: + OUTPUT_DUMP("Current scene", getScenes()->get(getState()->scene)->toString().c_str()); + } + } else { +label_error: + DebugPrintf("Syntax: state <option>\n"); + DebugPrintf(" 1 : Game state\n"); + DebugPrintf(" 2 : Inventory\n"); + DebugPrintf(" 3 : Objects\n"); + DebugPrintf(" 4 : SavePoints\n"); + DebugPrintf(" 5 : Current scene\n"); + } + + return true; + +#undef OUTPUT_DUMP +} + +/** + * Command: shows entity data + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdEntity(int argc, const char **argv) { + if (argc == 2) { + EntityIndex index = (EntityIndex)getNumber(argv[1]); + + if (index > 39) + goto label_error; + + DebugPrintf("Entity %s\n", ENTITY_NAME(index)); + DebugPrintf("--------------------------------------------------------------------\n\n"); + DebugPrintf(getEntities()->getData(index)->toString().c_str()); + + // The Player entity does not have any callback data + if (index != kEntityPlayer) { + EntityData *data = getEntities()->get(index)->getParamData(); + for (uint callback = 0; callback < 9; callback++) { + DebugPrintf("Call parameters %d:\n", callback); + for (byte ix = 0; ix < 4; ix++) + DebugPrintf(" %s", data->getParameters(callback, ix)->toString().c_str()); + } + } + + DebugPrintf("\n"); + } else { +label_error: + DebugPrintf("Syntax: entity <index>\n"); + for (int i = 0; i < 40; i += 4) + DebugPrintf(" %s - %d %s - %d %s - %d %s - %d\n", ENTITY_NAME(i), i, ENTITY_NAME(i+1), i+1, ENTITY_NAME(i+2), i+2, ENTITY_NAME(i+3), i+3); + } + + return true; +} + +/** + * Command: loads a game + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdLoadGame(int argc, const char **argv) { + if (argc == 2) { + int id = getNumber(argv[1]); + + if (id == 0 || id > 6) + goto error; + + if (!getSaveLoad()->loadGame((GameId)(id - 1))) + DebugPrintf("Error loading game with id=%d", id); + + } else { +error: + DebugPrintf("Syntax: loadgame <id> (id=1-6)\n"); + } + + return true; +} + +/** + * Command: switch to a specific chapter + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdSwitchChapter(int argc, const char **argv) { + if (argc == 2) { + int id = getNumber(argv[1]); + + if (id <= 1 || id > 6) + goto error; + + // Store command + if (!hasCommand()) { + _command = WRAP_METHOD(Debugger, cmdSwitchChapter); + copyCommand(argc, argv); + + return false; + } else { + // Sets the current chapter and then call Logic::switchChapter to proceed to the next chapter + getState()->progress.chapter = (ChapterIndex)(id - 1); + + getLogic()->switchChapter(); + + resetCommand(); + } + } else { +error: + DebugPrintf("Syntax: chapter <id> (id=2-6)\n"); + } + + return true; +} + +/** + * Command: clears the screen + * + * @param argc The argument count. + * @param argv The values. + * + * @return true if it was handled, false otherwise + */ +bool Debugger::cmdClear(int argc, const char **) { + if (argc == 1) { + clearBg(GraphicsManager::kBackgroundAll); + askForRedraw(); + redrawScreen(); + } else { + DebugPrintf("Syntax: clear - clear the screen\n"); + } + + return true; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/debug.h b/engines/lastexpress/debug.h new file mode 100644 index 0000000000..bb13c76bd0 --- /dev/null +++ b/engines/lastexpress/debug.h @@ -0,0 +1,104 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_DEBUG_H +#define LASTEXPRESS_DEBUG_H + +#include "gui/debugger.h" + +#include "lastexpress/data/snd.h" + +#include "lastexpress/shared.h" + +namespace LastExpress { + +enum { + kLastExpressDebugAll = 1 << 0, + kLastExpressDebugGraphics = 1 << 1, + kLastExpressDebugResource = 1 << 2, + kLastExpressDebugCursor = 1 << 3, + kLastExpressDebugSound = 1 << 4, + kLastExpressDebugSubtitle = 1 << 5, + kLastExpressDebugSavegame = 1 << 6, + kLastExpressDebugLogic = 1 << 7, + kLastExpressDebugScenes = 1 << 8, + kLastExpressDebugUnknown = 1 << 9 + // the current limitation is 32 debug levels (1 << 31 is the last one) +}; + +class LastExpressEngine; + +class Debugger : public GUI::Debugger { +public: + Debugger(LastExpressEngine *engine); + ~Debugger(); + + bool hasCommand() const; + void callCommand(); + +private: + LastExpressEngine *_engine; + + bool cmdHelp(int argc, const char **argv); + + bool cmdListFiles(int argc, const char **argv); + + bool cmdShowFrame(int argc, const char **argv); + bool cmdShowBg(int argc, const char **argv); + bool cmdPlaySeq(int argc, const char **argv); + bool cmdPlaySnd(int argc, const char **argv); + bool cmdPlaySbe(int argc, const char **argv); + bool cmdPlayNis(int argc, const char **argv); + + bool cmdLoadScene(int argc, const char **argv); + bool cmdFight(int argc, const char **argv); + bool cmdBeetle(int argc, const char **argv); + + bool cmdTimeDelta(int argc, const char **argv); + bool cmdDump(int argc, const char **argv); + bool cmdEntity(int argc, const char **argv); + + bool cmdLoadGame(int argc, const char **argv); + bool cmdSwitchChapter(int argc, const char **argv); + bool cmdClear(int argc, const char **argv); + + void resetCommand(); + void copyCommand(int argc, const char **argv); + int getNumber(const char *arg) const; + + void loadArchive(ArchiveIndex index) const; + void restoreArchive() const; + + Debuglet *_command; + int _numParams; + char **_commandParams; + + // Special sound stream for playing sounds + StreamedSound *_soundStream; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_DEBUG_H diff --git a/engines/lastexpress/detection.cpp b/engines/lastexpress/detection.cpp new file mode 100644 index 0000000000..1a0bebc881 --- /dev/null +++ b/engines/lastexpress/detection.cpp @@ -0,0 +1,171 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +static const PlainGameDescriptor lastExpressGames[] = { + // Games + {"lastexpress", "The Last Express"}, + {0, 0} +}; + +static const ADGameDescription gameDescriptions[] = { + + // The Last Express (English) + { + "lastexpress", + "", + { + {"HD.HPF", 0, "2d331459e0e68cf277ef4e4043750413", 29865984}, + {"CD1.HPF", 0, "8c86db47304033fcff32c69fddd5a920", 525522944}, + {"CD2.HPF", 0, "58aa26e782d10ec5d2231e539d2fe6a2", 669581312}, + {"CD3.HPF", 0, "00554fbf78a2ad391d98578fbbbe1c48", 641128448}, + }, + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + Common::GUIO_NONE + }, + + // The Last Express (English) - Interplay Release + { + "lastexpress", + "", + { + {"HD.HPF", 0, "bcc32d977f92bb52c060a0b4e8589cac", 30715904}, + {"CD1.HPF", 0, "8c86db47304033fcff32c69fddd5a920", 525522944}, + {"CD2.HPF", 0, "58aa26e782d10ec5d2231e539d2fe6a2", 669581312}, + {"CD3.HPF", 0, "00554fbf78a2ad391d98578fbbbe1c48", 641128448}, + }, + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + Common::GUIO_NONE + }, + + // The Last Express (French) + { + "lastexpress", + "", + { + {"HD.HPF", 0, "c14c6d685d9bf8705d9f659062e6c5c2", 29505536}, + {"CD1.HPF", 0, "b4277b22bc5cd6ad3b00c2ec04d4645d", 522924032}, + {"CD2.HPF", 0, "8c9610aa4cb707ab51f61c30feb22c1a", 665710592}, + {"CD3.HPF", 0, "411c1bab57b3e8da4fb359c5b40ef5d7", 640884736}, + }, + Common::FR_FRA, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + Common::GUIO_NONE + }, + + // The Last Express (Spanish) + { + "lastexpress", + "", + { + {"HD.HPF", 0, "46bed8832f06cf7160883a2aae2d667f", 29657088}, + {"CD1.HPF", 0, "367a3a8581f6f88ddc51af7cde105ba9", 519927808}, + {"CD2.HPF", 0, "af5566df3000472852ec182c9ec57797", 662210560}, + {"CD3.HPF", 0, "0d1901662f4d063a5c250c9fbf64b771", 639504384}, + }, + Common::ES_ESP, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + Common::GUIO_NONE + }, + + // The Last Express (Demo) + { + "lastexpress", + "Demo", + { + {"Demo.HPF", 0, "baf3b1f64155d34872896e61c3d3cb78", 58191872}, + }, + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_DEMO, + Common::GUIO_NONE + }, + AD_TABLE_END_MARKER +}; + +static const ADParams detectionParams = { + // Pointer to ADGameDescription or its superset structure + (const byte *)gameDescriptions, + // Size of that superset structure + sizeof(ADGameDescription), + // Number of bytes to compute MD5 sum for + 5000, + // List of all engine targets + lastExpressGames, + // Structure for autoupgrading obsolete targets + 0, + // Name of single gameid (optional) + "lastexpress", + // List of files for file-based fallback detection (optional) + 0, + // Flags + 0, + // Additional GUI options (for every game} + Common::GUIO_NOSUBTITLES | Common::GUIO_NOSFX, + // Maximum directory depth + 1, + // List of directory globs + 0 +}; + + +class LastExpressMetaEngine : public AdvancedMetaEngine { +public: + LastExpressMetaEngine() : AdvancedMetaEngine(detectionParams) {} + + const char *getName() const { + return "LastExpress Engine"; + } + + const char *getOriginalCopyright() const { + return "LastExpress Engine (C) 1997 Smoking Car Productions"; + } + + bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const; +}; + +bool LastExpressMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const { + if (gd) { + *engine = new LastExpressEngine(syst, (const ADGameDescription *)gd); + } + return gd != 0; +} + +} // End of namespace LastExpress + +#if PLUGIN_ENABLED_DYNAMIC(LASTEXPRESS) + REGISTER_PLUGIN_DYNAMIC(LASTEXPRESS, PLUGIN_TYPE_ENGINE, LastExpress::LastExpressMetaEngine); +#else + REGISTER_PLUGIN_STATIC(LASTEXPRESS, PLUGIN_TYPE_ENGINE, LastExpress::LastExpressMetaEngine); +#endif diff --git a/engines/lastexpress/drawable.h b/engines/lastexpress/drawable.h new file mode 100644 index 0000000000..e273876362 --- /dev/null +++ b/engines/lastexpress/drawable.h @@ -0,0 +1,42 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_DRAWABLE_H +#define LASTEXPRESS_DRAWABLE_H + +#include "graphics/surface.h" + +namespace LastExpress { + +class Drawable { +public: + virtual ~Drawable() {} + + virtual Common::Rect draw(Graphics::Surface *surface) = 0; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_DRAWABLE_H diff --git a/engines/lastexpress/entities/abbot.cpp b/engines/lastexpress/entities/abbot.cpp new file mode 100644 index 0000000000..d46bb325be --- /dev/null +++ b/engines/lastexpress/entities/abbot.cpp @@ -0,0 +1,1910 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/abbot.h" + +#include "lastexpress/entities/verges.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +Abbot::Abbot(LastExpressEngine *engine) : Entity(engine, kEntityAbbot) { + ADD_CALLBACK_FUNCTION(Abbot, reset); + ADD_CALLBACK_FUNCTION(Abbot, draw); + ADD_CALLBACK_FUNCTION(Abbot, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Abbot, enterExitCompartment2); + ADD_CALLBACK_FUNCTION(Abbot, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Abbot, draw2); + ADD_CALLBACK_FUNCTION(Abbot, updateFromTime); + ADD_CALLBACK_FUNCTION(Abbot, updateFromTicks); + ADD_CALLBACK_FUNCTION(Abbot, playSound); + ADD_CALLBACK_FUNCTION(Abbot, savegame); + ADD_CALLBACK_FUNCTION(Abbot, updateEntity); + ADD_CALLBACK_FUNCTION(Abbot, callSavepoint); + ADD_CALLBACK_FUNCTION(Abbot, updatePosition); + ADD_CALLBACK_FUNCTION(Abbot, callbackActionRestaurantOrSalon); + ADD_CALLBACK_FUNCTION(Abbot, chapter1); + ADD_CALLBACK_FUNCTION(Abbot, chapter2); + ADD_CALLBACK_FUNCTION(Abbot, chapter3); + ADD_CALLBACK_FUNCTION(Abbot, chapter3Handler); + ADD_CALLBACK_FUNCTION(Abbot, function19); + ADD_CALLBACK_FUNCTION(Abbot, function20); + ADD_CALLBACK_FUNCTION(Abbot, function21); + ADD_CALLBACK_FUNCTION(Abbot, function22); + ADD_CALLBACK_FUNCTION(Abbot, function23); + ADD_CALLBACK_FUNCTION(Abbot, function24); + ADD_CALLBACK_FUNCTION(Abbot, function25); + ADD_CALLBACK_FUNCTION(Abbot, function26); + ADD_CALLBACK_FUNCTION(Abbot, function27); + ADD_CALLBACK_FUNCTION(Abbot, function28); + ADD_CALLBACK_FUNCTION(Abbot, function29); + ADD_CALLBACK_FUNCTION(Abbot, function30); + ADD_CALLBACK_FUNCTION(Abbot, function31); + ADD_CALLBACK_FUNCTION(Abbot, function32); + ADD_CALLBACK_FUNCTION(Abbot, function33); + ADD_CALLBACK_FUNCTION(Abbot, function34); + ADD_CALLBACK_FUNCTION(Abbot, function35); + ADD_CALLBACK_FUNCTION(Abbot, function36); + ADD_CALLBACK_FUNCTION(Abbot, function37); + ADD_CALLBACK_FUNCTION(Abbot, function38); + ADD_CALLBACK_FUNCTION(Abbot, chapter4); + ADD_CALLBACK_FUNCTION(Abbot, function40); + ADD_CALLBACK_FUNCTION(Abbot, chapter4Handler); + ADD_CALLBACK_FUNCTION(Abbot, function42); + ADD_CALLBACK_FUNCTION(Abbot, function43); + ADD_CALLBACK_FUNCTION(Abbot, function44); + ADD_CALLBACK_FUNCTION(Abbot, function45); + ADD_CALLBACK_FUNCTION(Abbot, function46); + ADD_CALLBACK_FUNCTION(Abbot, drinkAfterDefuse); + ADD_CALLBACK_FUNCTION(Abbot, function48); + ADD_CALLBACK_FUNCTION(Abbot, pickBomb); + ADD_CALLBACK_FUNCTION(Abbot, chapter5); + ADD_CALLBACK_FUNCTION(Abbot, chapter5Handler); + ADD_CALLBACK_FUNCTION(Abbot, function52); + ADD_CALLBACK_FUNCTION(Abbot, function53); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Abbot, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Abbot, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(3, Abbot, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(4, Abbot, enterExitCompartment2, ObjectIndex) + Entity::enterExitCompartment(savepoint, kPosition_6470, kPosition_6130, kCarRedSleeping, kObjectCompartmentC, true, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(5, Abbot, callbackActionOnDirection) + Entity::callbackActionOnDirection(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SSI(6, Abbot, draw2, EntityIndex) + Entity::draw2(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(7, Abbot, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(8, Abbot, updateFromTicks, uint32) + Entity::updateFromTicks(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(9, Abbot, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(10, Abbot, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(11, Abbot, updateEntity, CarIndex, EntityPosition) + if (savepoint.action == kActionExcuseMeCath) { + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 18) || getEntities()->isPlayerPosition(kCarRedSleeping, 18)) { + getSound()->excuseMe(kEntityAbbot); + } else { + if (getEvent(kEventAbbotIntroduction)) + getSound()->playSound(kEntityPlayer, "CAT1013"); + else + getSound()->excuseMeCath(); + } + return; + } + + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SIIS(12, Abbot, callSavepoint, EntityIndex, ActionIndex) + Entity::callSavepoint(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SII(13, Abbot, updatePosition, CarIndex, Position) + Entity::updatePosition(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Abbot, callbackActionRestaurantOrSalon) + Entity::callbackActionRestaurantOrSalon(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Abbot, chapter1) + if (savepoint.action == kActionDefault) + getSavePoints()->addData(kEntityAbbot, kAction203073664, 0); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Abbot, chapter2) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityAbbot); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Abbot, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAbbot); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + getData()->clothes = kClothesDefault; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Abbot, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_draw("804DD"); + break; + + case 2: + getSavePoints()->push(kEntityAbbot, kEntityCooks, kAction236976550); + getEntities()->drawSequenceRight(kEntityAbbot, "804DS"); + + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityAbbot); + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case 3: + setCallback(4); + setup_updateEntity(kCarRedSleeping, kPosition_6470); + break; + + case 4: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(5); + setup_enterExitCompartment("617AC", kObjectCompartmentC); + break; + + case 5: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationInsideCompartment; + + setup_function19(); + break; + } + break; + + case kAction192054567: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Abbot, function19) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime1953000) { + if (!params->param1) { + params->param1 = 1; + setCallback(3); + setup_playSound("MrB3010"); + } + } + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAbbot, "508A"); + getSavePoints()->push(kEntityAbbot, kEntityBoutarel, kAction122358304); + + setCallback(1); + setup_playSound("Abb3010"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateFromTime(900); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityAbbot, "508B"); + break; + + case 3: + getSavePoints()->push(kEntityAbbot, kEntityBoutarel, kAction122288808); + setup_function20(); + break; + } + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Abbot, function20) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime1966500 && getEntities()->isInRestaurant(kEntityBoutarel)) + setup_function21(); + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAbbot, "509A"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Abbot, function21) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_draw("509B"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + setCallback(2); + setup_enterExitCompartment("617Mc", kObjectCompartmentC); + break; + + case 2: + getData()->location = kLocationOutsideCompartment; + + setCallback(3); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 3: + setCallback(4); + setup_callbackActionRestaurantOrSalon(); + break; + + case 4: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(5); + setup_draw("804US"); + break; + + case 5: + getEntities()->drawSequenceRight(kEntityAbbot, "029J"); + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityAbbot); + + setCallback(6); + setup_callbackActionOnDirection(); + break; + + case 6: + getEntities()->drawSequenceLeft(kEntityAbbot, "029H"); + getSavePoints()->push(kEntityAbbot, kEntityPascale, kAction207769280); + break; + + case 7: + setup_function22(); + break; + } + break; + + case kAction122288808: + getSavePoints()->push(kEntityAbbot, kEntityTables4, kAction136455232); + getData()->location = kLocationInsideCompartment; + + setCallback(7); + setup_draw("029B"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Abbot, function22) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_SAVEPOINT(kTime1971000, params->param1, kEntityAbbot, kEntityServers0, kAction218586752); + + if (getState()->time > kTime1989000 && getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getData()->inventoryItem = kItemNone; + setup_function23(); + } + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAbbotIntroduction); + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAbbot, "029E"); + if (!getEvent(kEventAbbotIntroduction)) + getData()->inventoryItem = (InventoryItem)kCursorProcess; + break; + + case kActionCallback: + if (getCallback() != 1) + break; + + getAction()->playAnimation(kEventAbbotIntroduction); + getSound()->playSound(kEntityPlayer, "LIB036"); + getScenes()->loadSceneFromPosition(kCarRestaurant, 61); + break; + + case kAction122288808: + getEntities()->drawSequenceLeft(kEntityAbbot, "029E"); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityAbbot, "BLANK"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Abbot, function23) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->location = kLocationOutsideCompartment; + getEntities()->updatePositionEnter(kEntityAbbot, kCarRestaurant, 67); + + setCallback(1); + setup_callSavepoint("029F", kEntityTables4, kActionDrawTablesWithChairs, "029G"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->updatePositionExit(kEntityAbbot, kCarRestaurant, 67); + getSavePoints()->push(kEntityAbbot, kEntityServers0, kAction270068760); + getSavePoints()->push(kEntityAbbot, kEntityAnna, kAction238936000); + getEntities()->drawSequenceRight(kEntityAbbot, "804DS"); + + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityAbbot); + + setCallback(2); + setup_callbackActionOnDirection(); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_6470); + break; + + case 3: + setCallback(4); + setup_enterExitCompartment2("617Cc", kObjectCompartmentC); + break; + + case 4: + getData()->location = kLocationInsideCompartment; + + setup_function24(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Abbot, function24) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param1, getState()->time, 900); + + setup_function25(); + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update(kObjectCompartmentC, kEntityAbbot, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject50, kEntityAbbot, kObjectLocation1, kCursorNormal, kCursorNormal); + + if (savepoint.action == kActionKnock) { + setCallback(1); + setup_playSound("LIB012"); + } else { + setCallback(2); + setup_playSound("LIB013"); + } + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAbbot); + getObjects()->update(kObjectCompartmentC, kEntityAbbot, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityAbbot, kObjectLocationNone, kCursorHandKnock, kCursorHand); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + setCallback(3); + setup_playSound("Abb3001"); + break; + + case 3: + getObjects()->update(kObjectCompartmentC, kEntityAbbot, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityAbbot, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Abbot, function25) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("617Dc", kObjectCompartmentC); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_updatePosition("115A", kCarRestaurant, 56); + break; + + case 4: + getData()->location = kLocationInsideCompartment; + getScenes()->loadSceneFromItemPosition(kItem3); + + setup_function26(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Abbot, function26) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param2, getState()->time, 4500); + + if (getEntities()->isSomebodyInsideRestaurantOrSalon()) + setup_function27(); + break; + + case kActionDefault: + getSavePoints()->push(kEntityAbbot, kEntityKronos, kAction157159392); + getEntities()->drawSequenceLeft(kEntityAbbot, "115B"); + break; + + case kAction101169422: + params->param1 = 1; + break; + + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Abbot, function27) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_updatePosition("115C", kCarRestaurant, 56); + break; + + case 2: + getInventory()->setLocationAndProcess(kItem3, kObjectLocation1); + + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_6470); + break; + + case 3: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(4); + setup_enterExitCompartment("617Ac", kObjectCompartmentC); + break; + + case 4: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationInsideCompartment; + + setup_function28(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Abbot, function28) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK(kTime2052000, params->param1, 1, setup_function29); + break; + + case kActionDefault: + getSavePoints()->push(kEntityAbbot, kEntityBoutarel, kAction122358304); + getEntities()->drawSequenceLeft(kEntityAbbot, "508A"); + + setCallback(1); + setup_playSound("abb3013"); + break; + + case kActionCallback: + if (getCallback() == 1) + getEntities()->drawSequenceLeft(kEntityAbbot, "508B"); + break; + + case kAction222609266: + setup_function30(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Abbot, function29) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getSavePoints()->push(kEntityAbbot, kEntityBoutarel, kAction122288808); + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(1); + setup_enterExitCompartment("617Bc", kObjectCompartmentC); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_9460); + break; + + case 2: + setCallback(3); + setup_updateFromTicks(450); + break; + + case 3: + setCallback(4); + setup_updateEntity(kCarGreenSleeping, kPosition_540); + break; + + case 4: + setCallback(5); + setup_updateFromTime(225); + break; + + case 5: + setCallback(6); + setup_updateEntity(kCarRedSleeping, kPosition_6470); + break; + + case 6: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(7); + setup_enterExitCompartment("617Ac", kObjectCompartmentC); + break; + + case 7: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getSavePoints()->push(kEntityAbbot, kEntityBoutarel, kAction122358304); + getEntities()->drawSequenceLeft(kEntityAbbot, "508B"); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Abbot, function30) +switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_playSound("Abb3030"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityAbbot, kEntityBoutarel, kAction122288808); + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(2); + setup_enterExitCompartment("617Bc", kObjectCompartmentC); + break; + + case 2: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getData()->location = kLocationOutsideCompartment; + + setCallback(3); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 3: + setCallback(4); + setup_callbackActionRestaurantOrSalon(); + break; + + case 4: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(5); + setup_updatePosition("115A", kCarRestaurant, 56); + break; + + case 5: + getScenes()->loadSceneFromItemPosition(kItem3); + getData()->location = kLocationInsideCompartment; + + setup_function31(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, Abbot, function31) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param4 != kTimeInvalid && params->param2 < getState()->time) { + if (getState()->time < getState()->time) { + params->param4 = kTimeInvalid; + + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + } else { + if (!getEntities()->isInSalon(kEntityPlayer) || !params->param4) + params->param4 = getState()->time + 450; + + if (params->param4 < getState()->time) { + params->param4 = kTimeInvalid; + + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + } + } + } + + if (!params->param1) + break; + + UPDATE_PARAM(params->param5, getState()->time, 450); + + setCallback(6); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionDefault: + params->param2 = getState()->time + 4500; + params->param3 = getState()->time + 18000; + + getEntities()->drawSequenceLeft(kEntityAbbot, "115B"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_updatePosition("115E", kCarRestaurant, 56); + break; + + case 2: + getInventory()->setLocationAndProcess(kItem3, kObjectLocation1); + getSavePoints()->push(kEntityAbbot, kEntityAlexei, kAction122358304); + getSound()->playSound(kEntityAbbot, "Abb3020"); + + setCallback(3); + setup_updatePosition("125A", kCarRestaurant, 52); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + getEntities()->drawSequenceLeft(kEntityAbbot, "125B"); + + setCallback(4); + setup_playSound("Abb3021"); + break; + + case 4: + getSound()->playSound(kEntityAbbot, "Abb3023"); + getEntities()->updatePositionEnter(kEntityAbbot, kCarRestaurant, 52); + + setCallback(5); + setup_draw2("125C1", "125C2", kEntityAlexei); + break; + + case 5: + getEntities()->updatePositionExit(kEntityAbbot, kCarRestaurant, 52); + getEntities()->drawSequenceLeft(kEntityAbbot, "125D"); + getSavePoints()->push(kEntityAbbot, kEntityAlexei, kAction122288808); + params->param1 = 1; + + UPDATE_PARAM(params->param5, getState()->time, 450); + + setCallback(6); + setup_callbackActionRestaurantOrSalon(); + break; + + case 6: + getData()->location = kLocationOutsideCompartment; + + setCallback(7); + setup_updatePosition("125E", kCarRestaurant, 52); + break; + + case 7: + setup_function32(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Abbot, function32) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_6470); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(1); + setup_enterExitCompartment("617Ac", kObjectCompartmentC); + break; + + case 2: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationInsideCompartment; + getSavePoints()->push(kEntityAbbot, kEntityBoutarel, kAction122358304); + + setup_function33(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Abbot, function33) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 != kTimeInvalid && getState()->time > kTime2115000) { + if (getState()->time <= kTime2124000) { + if (!getEntities()->isDistanceBetweenEntities(kEntityAbbot, kEntityPlayer, 2000) || !params->param1) + params->param1 = getState()->time; + + if (params->param1 >= getState()->time) + break; + } + + params->param1 = kTimeInvalid; + + setCallback(1); + setup_playSound("Abb3014"); + } + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAbbot, "508A"); + break; + + case kActionCallback: + if (getCallback() == 1) + getEntities()->drawSequenceLeft(kEntityAbbot, "508B"); + break; + + case kAction123712592: + setup_function34(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(34, Abbot, function34) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_playSound("Abb3031"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityAbbot, kEntityBoutarel, kAction122288808); + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(2); + setup_enterExitCompartment("617Bc", kObjectCompartmentC); + break; + + case 2: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getData()->location = kLocationOutsideCompartment; + + setCallback(3); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 3: + setCallback(4); + setup_callbackActionRestaurantOrSalon(); + break; + + case 4: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(5); + setup_updatePosition("115A", kCarRestaurant, 56); + break; + + case 5: + getScenes()->loadSceneFromItemPosition(kItem3); + + getData()->location = kLocationInsideCompartment; + setup_function35(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(35, Abbot, function35) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param2 == kTimeInvalid) + break; + + if (params->param1 >= getState()->time) { + if (!getEntities()->isInSalon(kEntityPlayer) || !params->param2) + params->param2 = getState()->time + 450; + + if (params->param2 >= getState()->time) + break; + } + + params->param2 = kTimeInvalid; + + getSavePoints()->push(kEntityAbbot, kEntityAugust, kAction136196244); + + setCallback(1); + setup_updateFromTime(0); + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAbbot, "115B"); + params->param1 = getState()->time + 9000; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_callbackActionRestaurantOrSalon(); + break; + + case 2: + getData()->location = kLocationOutsideCompartment; + getSound()->playSound(kEntityAbbot, "Abb3040", SoundManager::kFlagInvalid, 45); + getEntities()->updatePositionEnter(kEntityAbbot, kCarRestaurant, 57); + + setCallback(3); + setup_callSavepoint("121A", kEntityAugust, kAction122358304, "BOGUS"); + break; + + case 3: + getEntities()->updatePositionExit(kEntityAbbot, kCarRestaurant, 57); + getInventory()->setLocationAndProcess(kItem3, kObjectLocation1); + getData()->location = kLocationInsideCompartment; + + setup_function36(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(36, Abbot, function36) + switch (savepoint.action) { + default: + break; + + case kActionNone: + switch (params->param2) { + default: + break; + + case 1: + if (params->param3 == kTimeInvalid) + break; + + if (params->param1 >= getState()->time) { + + if (!getEntities()->isInSalon(kEntityPlayer) || !params->param3) + params->param3 = getState()->time + 675; + + if (params->param3 >= getState()->time) + break; + } + + params->param3 = kTimeInvalid; + + getSound()->playSound(kEntityAbbot, "Abb3041"); + break; + + case 2: + UPDATE_PARAM(params->param4, getState()->time, 900); + + getSound()->playSound(kEntityAbbot, "Abb3042"); + break; + + case 3: + getSound()->playSound(kEntityAbbot, "Abb3043"); + getEntities()->updatePositionEnter(kEntityAbbot, kCarRestaurant, 57); + + setCallback(1); + setup_callSavepoint("121D", kEntityAugust, kAction122288808, "BOGUS"); + break; + } + break; + + case kActionEndSound: + ++params->param2; + break; + + case kActionDefault: + params->param1 = getState()->time + 4500; + getEntities()->drawSequenceLeft(kEntityAbbot, "121B"); + break; + + case kActionDrawScene: + if (getEntities()->isPlayerPosition(kCarRestaurant, 57)) + getScenes()->loadSceneFromPosition(kCarRestaurant, 50); + break; + + case kActionCallback: + if (getCallback() == 1) { + getEntities()->updatePositionExit(kEntityAbbot, kCarRestaurant, 57); + setup_function37(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(37, Abbot, function37) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_6470); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(2); + setup_enterExitCompartment("617Ac", kObjectCompartmentC); + break; + + case 2: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationInsideCompartment; + getSavePoints()->push(kEntityAbbot, kEntityBoutarel, kAction122358304); + + setup_function38(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(38, Abbot, function38) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAbbot, "508A"); + + setCallback(1); + setup_playSound("Abb3014A"); + break; + + case kActionCallback: + if (getCallback() == 1) + getEntities()->drawSequenceLeft(kEntityAbbot, "508B"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(39, Abbot, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAbbot); + + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 1) = 0; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(40, Abbot, function40, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->updateEntity(kEntityAbbot, (CarIndex)params->param1, (EntityPosition)params->param2)) { + CALLBACK_ACTION(); + } else if (!getEvent(kEventAbbotInvitationDrink) + && getEntities()->isDistanceBetweenEntities(kEntityAbbot, kEntityPlayer, 1000) + && !getEntities()->isInsideCompartments(kEntityPlayer) + && !getEntities()->checkFields10(kEntityPlayer)) { + + if (getData()->car == kCarGreenSleeping || getData()->car == kCarRedSleeping) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAbbotInvitationDrink); + } + } + break; + + case kActionDefault: + if (getEntities()->updateEntity(kEntityAbbot, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventAbbotInvitationDrink); + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + (750 * (getData()->direction == kDirectionUp ? -1 : 1))), getData()->direction == kDirectionUp); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(41, Abbot, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_SAVEPOINT(kTime2358000, params->param1, kEntityAbbot, kEntityServers0, kAction218128129); + + if (getState()->time > kTime2389500 && getEntities()->isSomebodyInsideRestaurantOrSalon()) + setup_function42(); + + break; + + case kActionDefault: + getSavePoints()->push(kEntityAbbot, kEntityTables4, kAction136455232); + getEntities()->drawSequenceLeft(kEntityAbbot, "029E"); + getData()->location = kLocationInsideCompartment; + break; + + case kAction122288808: + getEntities()->drawSequenceLeft(kEntityAbbot, "029E"); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityAbbot, "BLANK"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(42, Abbot, function42) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->location = kLocationOutsideCompartment; + getEntities()->updatePositionExit(kEntityAbbot, kCarRestaurant, 67); + + setCallback(1); + setup_callSavepoint("029F", kEntityTables4, kActionDrawTablesWithChairs, "029G"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->updatePositionExit(kEntityAbbot, kCarRestaurant, 67); + getSavePoints()->push(kEntityAbbot, kEntityServers0, kAction270068760); + getEntities()->drawSequenceRight(kEntityAbbot, "804DS"); + + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityAbbot); + + setCallback(2); + setup_callbackActionOnDirection(); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_6470); + break; + + case 3: + setCallback(4); + setup_enterExitCompartment2("617Cc", kObjectCompartmentC); + break; + + case 4: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityAbbot); + + setup_function43(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(43, Abbot, function43) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 && params->param4 != kTimeInvalid && params->param2 < getState()->time) { + if (getState()->time < kTime2452500) { + params->param4 = kTimeInvalid; + + setCallback(1); + setup_playSound("Abb4002"); + break; + } else { + if (!getEntities()->isDistanceBetweenEntities(kEntityAbbot, kEntityPlayer, 1000) || getSound()->isBuffered(kEntityBoutarel) || !params->param4) + params->param4 = getState()->time + 450; + + if (params->param4 < getState()->time) { + params->param4 = kTimeInvalid; + + setCallback(1); + setup_playSound("Abb4002"); + break; + } + } + } + +label_callback_1: + TIME_CHECK(kTime2466000, params->param5, setup_function44); + + if (params->param3) { + UPDATE_PARAM(params->param6, getState()->timeTicks, 75); + + params->param2 = 1; + params->param3 = 0; + + getObjects()->update(kObjectCompartmentC, kEntityAbbot, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject50, kEntityAbbot, kObjectLocation1, kCursorNormal, kCursorNormal); + } + + params->param6 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update(kObjectCompartmentC, kEntityAbbot, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject50, kEntityAbbot, kObjectLocation1, kCursorNormal, kCursorNormal); + + if (params->param3) { + setCallback(savepoint.param.intValue == 50 ? 5 : 6); + setup_playSound(savepoint.param.intValue == 50 ? getSound()->justAMinuteCath() : getSound()->wrongDoorCath()); + } else { + setCallback(savepoint.action == kActionKnock ? 2 : 3); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentC, kEntityAbbot, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityAbbot, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kActionDrawScene: + if (params->param2 || params->param3) { + getObjects()->update(kObjectCompartmentC, kEntityAbbot, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityAbbot, kObjectLocation1, kCursorHandKnock, kCursorHand); + + params->param2 = 0; + params->param3 = 0; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback_1; + + case 2: + case 3: + setCallback(4); + setup_playSound("Abb3001"); + break; + + case 4: + getObjects()->update(kObjectCompartmentC, kEntityAbbot, kObjectLocation1, kCursorTalk, kCursorNormal); + getObjects()->update(kObject50, kEntityAbbot, kObjectLocation1, kCursorTalk, kCursorNormal); + + params->param3 = 1; + break; + + case 5: + case 6: + params->param2 = 1; + params->param3 = 0; + break; + } + break; + + case kAction101687594: + params->param1 = 1; + break; + + case kAction159003408: + params->param1 = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(44, Abbot, function44) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getEntities()->clearSequences(kEntityAbbot); + + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kAction104060776: + setup_function45(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(45, Abbot, function45) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_6471; + getData()->car = kCarRedSleeping; + getData()->location = kLocationOutsideCompartment; + + RESET_ENTITY_STATE(kEntityVerges, Verges, setup_function38); + + getEntities()->drawSequenceLeft(kEntityAbbot, "617Ec"); + getEntities()->enterCompartment(kEntityAbbot, kObjectCompartmentC, true); + + setCallback(1); + setup_playSound("Abb4010"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("617Kc", kObjectCompartmentC); + break; + + case 2: + getEntities()->exitCompartment(kEntityAbbot, kObjectCompartmentC, true); + getSavePoints()->push(kEntityAbbot, kEntityVerges, kAction125233040); + + setup_function46(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(46, Abbot, function46) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_6471; + + setCallback(1); + setup_function40(kCarRestaurant, kPosition_850); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_drinkAfterDefuse(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(47, Abbot, drinkAfterDefuse) + switch (savepoint.action) { + default: + break; + + case kAction1: + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventAbbotDrinkGiveDetonator); + break; + + case kActionDefault: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_draw("126A"); + break; + + case 2: + getData()->location = kLocationInsideCompartment; + getEntities()->drawSequenceLeft(kEntityAbbot, "126B"); + getData()->inventoryItem = kItemBomb; + break; + + case 3: + getAction()->playAnimation(kEventAbbotDrinkGiveDetonator); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneNone, true); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(48, Abbot, function48) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (ENTITY_PARAM(0, 1)) + getData()->inventoryItem = kItemInvalid; + + UPDATE_PARAM_PROC(params->param1, getState()->time, 1800) + getData()->inventoryItem = kItemNone; + + setCallback(4); + setup_updatePosition("126C", kCarRedSleeping, 52); + UPDATE_PARAM_PROC_END + + TIME_CHECK_CALLBACK_INVENTORY(kTime2533500, params->param2, 5, setup_callbackActionRestaurantOrSalon); + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + + setCallback(9); + setup_savegame(kSavegameTypeEvent, kEventAbbotDrinkDefuse); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_850; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + getData()->inventoryItem = kItemNone; + + getSavePoints()->push(kEntityAbbot, kEntityVerges, kAction125233040); + + setCallback(1); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_callbackActionRestaurantOrSalon(); + break; + + case 2: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(3); + setup_updatePosition("126A", kCarRestaurant, 52); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + getEntities()->drawSequenceLeft(kEntityAbbot, "126B"); + break; + + case 4: + if (!getEvent(kEventAbbotDrinkDefuse) && ENTITY_PARAM(0, 1)) + getData()->inventoryItem = kItemInvalid; + + getEntities()->drawSequenceLeft(kEntityAbbot, "126B"); + params->param1 = 0; + + TIME_CHECK_CALLBACK_INVENTORY(kTime2533500, params->param2, 5, setup_callbackActionRestaurantOrSalon); + break; + + case 5: + getData()->location = kLocationInsideCompartment; + + setCallback(6); + setup_updatePosition("126D", kCarRestaurant, 52); + break; + + case 6: + setCallback(7); + setup_updateEntity(kCarRedSleeping, kPosition_6470); + break; + + case 7: + setCallback(8); + setup_enterExitCompartment2("617Cc", kObjectCompartmentC); + break; + + case 8: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityAbbot); + + setup_function44(); + break; + + case 9: + getAction()->playAnimation(kEventAbbotDrinkDefuse); + getEntities()->drawSequenceLeft(kEntityAbbot, "126B"); + getSavePoints()->push(kEntityAbbot, kEntityAnna, kAction100969180); + getScenes()->loadSceneFromPosition(kCarRestaurant, 58); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(49, Abbot, pickBomb) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param1, getState()->timeTicks, 150); + + getSavePoints()->push(kEntityAbbot, kEntityAbbot, kAction157489665); + break; + + case kActionKnock: + if (!getSound()->isBuffered("LIB012", true)) + getSound()->playSound(kEntityPlayer, "LIB012"); + break; + + case kActionOpenDoor: + case kAction157489665: + getSavePoints()->push(kEntityAbbot, kEntityTatiana, kAction238790488); + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectHandleInsideBathroom, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + break; + + case kActionDefault: + getData()->car = kCarGreenSleeping; + getData()->entityPosition = kPosition_7500; + getData()->location = kLocationInsideCompartment; + + getSavePoints()->call(kEntityAbbot, kEntityTables4, kActionDrawTablesWithChairs, "029G"); + getSavePoints()->push(kEntityAbbot, kEntityServers0, kAction270068760); + getSavePoints()->push(kEntityAbbot, kEntityBoutarel, kAction125039808); + getObjects()->update(kObjectCompartment2, kEntityAbbot, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectHandleInsideBathroom, kEntityAbbot, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(getObjects()->get(kObjectCompartment2).location2 < kObjectLocation2 ? kEventAbbotWrongCompartmentBed : kEventAbbotWrongCompartment); + getEntities()->updateEntity(kEntityAbbot, kCarRedSleeping, kPosition_6470); + getSound()->playSound(kEntityPlayer, "LIB015"); + getScenes()->loadSceneFromObject(kObjectCompartment2, true); + + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_6470); + break; + + case 2: + setCallback(3); + setup_enterExitCompartment2("617Cc", kObjectCompartmentC); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityAbbot); + getObjects()->update(kObjectCompartmentC, kEntityAbbot, kObjectLocation1, kCursorHandKnock, kCursorHand); + + setup_function43(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(50, Abbot, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAbbot); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + getData()->clothes = kClothesDefault; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(51, Abbot, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_function52(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(52, Abbot, function52) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAbbot); + + getData()->entityPosition = kPositionNone; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarNone; + break; + + case kAction135600432: + setup_function53(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(53, Abbot, function53) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getInventory()->setLocationAndProcess(kItem25, kObjectLocation1); + getSavePoints()->push(kEntityAbbot, kEntityAnna, kAction158480160); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventLocomotiveAbbotGetSomeRest); + getScenes()->processScene(); + break; + + case 2: + getAction()->playAnimation(kEventLocomotiveAbbotShoveling); + getScenes()->processScene(); + break; + } + break; + + case kAction168646401: + if (!getEvent(kEventLocomotiveAbbotGetSomeRest)) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventLocomotiveAbbotGetSomeRest); + break; + } + + if (!getEvent(kEventLocomotiveAbbotShoveling)) { + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventLocomotiveAbbotShoveling); + break; + } + + getAction()->playAnimation(kEventLocomotiveAbbotShoveling); + getScenes()->processScene(); + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/abbot.h b/engines/lastexpress/entities/abbot.h new file mode 100644 index 0000000000..4637fbd817 --- /dev/null +++ b/engines/lastexpress/entities/abbot.h @@ -0,0 +1,225 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ABBOT_H +#define LASTEXPRESS_ABBOT_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Abbot : public Entity { +public: + Abbot(LastExpressEngine *engine); + ~Abbot() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment and updates position/play animation + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment2, const char* sequence, ObjectIndex compartment) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Draws the entity along with another one + * + * @param sequence1 The sequence to draw + * @param sequence2 The sequence to draw for the second entity + * @param entity The EntityIndex of the second entity + */ + DECLARE_FUNCTION_3(draw2, const char* sequence1, const char* sequence2, EntityIndex entity) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Updates parameter 2 using ticks value + * + * @param ticks The number of ticks to add + */ + DECLARE_FUNCTION_1(updateFromTicks, uint32 ticks) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + /** + * Call a savepoint (or draw sequence in default case) + * + * @param sequence1 The sequence to draw in the default case + * @param entity The entity + * @param action The action + * @param sequence2 The sequence name for the savepoint + */ + DECLARE_FUNCTION_4(callSavepoint, const char* sequence1, EntityIndex entity, ActionIndex action, const char* sequence2) + + /** + * Updates the position + * + * @param sequence1 The sequence to draw + * @param car The car + * @param position The position + */ + DECLARE_FUNCTION_3(updatePosition, const char* sequence1, CarIndex car, Position position) + + /** + * Process callback action when somebody is standing in the restaurant or salon. + */ + DECLARE_FUNCTION(callbackActionRestaurantOrSalon) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + DECLARE_FUNCTION(function19) + DECLARE_FUNCTION(function20) + DECLARE_FUNCTION(function21) + DECLARE_FUNCTION(function22) + DECLARE_FUNCTION(function23) + DECLARE_FUNCTION(function24) + DECLARE_FUNCTION(function25) + DECLARE_FUNCTION(function26) + DECLARE_FUNCTION(function27) + DECLARE_FUNCTION(function28) + DECLARE_FUNCTION(function29) + DECLARE_FUNCTION(function30) + DECLARE_FUNCTION(function31) + DECLARE_FUNCTION(function32) + DECLARE_FUNCTION(function33) + DECLARE_FUNCTION(function34) + DECLARE_FUNCTION(function35) + DECLARE_FUNCTION(function36) + DECLARE_FUNCTION(function37) + DECLARE_FUNCTION(function38) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * ??? + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(function40, CarIndex car, EntityPosition position) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + DECLARE_FUNCTION(function42) + DECLARE_FUNCTION(function43) + DECLARE_FUNCTION(function44) + DECLARE_FUNCTION(function45) + DECLARE_FUNCTION(function46) + DECLARE_FUNCTION(drinkAfterDefuse) + DECLARE_FUNCTION(function48) + DECLARE_FUNCTION(pickBomb) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + DECLARE_FUNCTION(function52) + DECLARE_FUNCTION(function53) +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_ABBOT_H diff --git a/engines/lastexpress/entities/alexei.cpp b/engines/lastexpress/entities/alexei.cpp new file mode 100644 index 0000000000..30e1c4384b --- /dev/null +++ b/engines/lastexpress/entities/alexei.cpp @@ -0,0 +1,1897 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/alexei.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +Alexei::Alexei(LastExpressEngine *engine) : Entity(engine, kEntityAlexei) { + ADD_CALLBACK_FUNCTION(Alexei, reset); + ADD_CALLBACK_FUNCTION(Alexei, playSound); + ADD_CALLBACK_FUNCTION(Alexei, updateFromTicks); + ADD_CALLBACK_FUNCTION(Alexei, draw); + ADD_CALLBACK_FUNCTION(Alexei, updatePosition); + ADD_CALLBACK_FUNCTION(Alexei, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Alexei, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Alexei, callSavepoint); + ADD_CALLBACK_FUNCTION(Alexei, savegame); + ADD_CALLBACK_FUNCTION(Alexei, updateEntity); + ADD_CALLBACK_FUNCTION(Alexei, draw2); + ADD_CALLBACK_FUNCTION(Alexei, callbackActionRestaurantOrSalon); + ADD_CALLBACK_FUNCTION(Alexei, function13); + ADD_CALLBACK_FUNCTION(Alexei, function14); + ADD_CALLBACK_FUNCTION(Alexei, function15); + ADD_CALLBACK_FUNCTION(Alexei, function16); + ADD_CALLBACK_FUNCTION(Alexei, chapter1); + ADD_CALLBACK_FUNCTION(Alexei, chapter1Handler); + ADD_CALLBACK_FUNCTION(Alexei, function19); + ADD_CALLBACK_FUNCTION(Alexei, function20); + ADD_CALLBACK_FUNCTION(Alexei, function21); + ADD_CALLBACK_FUNCTION(Alexei, function22); + ADD_CALLBACK_FUNCTION(Alexei, function23); + ADD_CALLBACK_FUNCTION(Alexei, function24); + ADD_CALLBACK_FUNCTION(Alexei, function25); + ADD_CALLBACK_FUNCTION(Alexei, function26); + ADD_CALLBACK_FUNCTION(Alexei, function27); + ADD_CALLBACK_FUNCTION(Alexei, chapter2); + ADD_CALLBACK_FUNCTION(Alexei, chapter2Handler); + ADD_CALLBACK_FUNCTION(Alexei, function30); + ADD_CALLBACK_FUNCTION(Alexei, function31); + ADD_CALLBACK_FUNCTION(Alexei, chapter3); + ADD_CALLBACK_FUNCTION(Alexei, chapter3Handler); + ADD_CALLBACK_FUNCTION(Alexei, function34); + ADD_CALLBACK_FUNCTION(Alexei, function35); + ADD_CALLBACK_FUNCTION(Alexei, function36); + ADD_CALLBACK_FUNCTION(Alexei, chapter4); + ADD_CALLBACK_FUNCTION(Alexei, chapter4Handler); + ADD_CALLBACK_FUNCTION(Alexei, function39); + ADD_CALLBACK_FUNCTION(Alexei, function40); + ADD_CALLBACK_FUNCTION(Alexei, function41); + ADD_CALLBACK_FUNCTION(Alexei, function42); + ADD_CALLBACK_FUNCTION(Alexei, function43); + ADD_CALLBACK_FUNCTION(Alexei, function44); + ADD_CALLBACK_FUNCTION(Alexei, function45); + ADD_CALLBACK_FUNCTION(Alexei, function46); + ADD_CALLBACK_FUNCTION(Alexei, function47); + ADD_CALLBACK_FUNCTION(Alexei, chapter5); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Alexei, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Alexei, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(3, Alexei, updateFromTicks, uint32) + Entity::updateFromTicks(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(4, Alexei, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SII(5, Alexei, updatePosition, CarIndex, Position) + Entity::updatePosition(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(6, Alexei, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Alexei, callbackActionOnDirection) + Entity::callbackActionOnDirection(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SIIS(8, Alexei, callSavepoint, EntityIndex, ActionIndex) + Entity::callSavepoint(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(9, Alexei, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(10, Alexei, updateEntity, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionExcuseMeCath: + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 18) || getEntities()->isPlayerPosition(kCarRedSleeping, 18)) { + getSound()->excuseMe(kEntityAlexei); + } else { + if (getEvent(kEventAlexeiSalonVassili) || (getEvent(kEventTatianaAskMatchSpeakRussian) && getInventory()->hasItem(kItemPassengerList))) { + getSound()->playSound(kEntityPlayer, rnd(2) ? "CAT1012" : "CAT1012A"); + } else { + getSound()->excuseMeCath(); + } + } + // Stop execution here + return; + + case kActionDefault: + getData()->inventoryItem = kItemNone; + break; + } + + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(11, Alexei, draw2) + Entity::draw2(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Alexei, callbackActionRestaurantOrSalon) + Entity::callbackActionRestaurantOrSalon(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Alexei, function13) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_7500); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityAlexei, kEntityMertens, kAction302614416); + getEntities()->drawSequenceLeft(kEntityAlexei, "602DB"); + getEntities()->enterCompartment(kEntityAlexei, kObjectCompartment2, true); + + getData()->location = kLocationInsideCompartment; + + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_7500)) { + getAction()->playAnimation(isNight() ? kEventCathTurningNight : kEventCathTurningDay); + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromObject(kObjectCompartment2, true); + } + break; + + case 2: + getEntities()->exitCompartment(kEntityAlexei, kObjectCompartment2, true); + getData()->location = kLocationInsideCompartment; + getData()->entityPosition = kPosition_7500; + getEntities()->clearSequences(kEntityAlexei); + + CALLBACK_ACTION(); + break; + } + break; + + case kAction135664192: + setCallback(2); + setup_enterExitCompartment("602Eb", kObjectCompartment2); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Alexei, function14) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("602Fb", kObjectCompartment2); + break; + + case kActionCallback: + if (getCallback() == 1) { + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityAlexei, kEntityMertens, kAction302614416); + getEntities()->drawSequenceLeft(kEntityAlexei, "602DB"); + getEntities()->enterCompartment(kEntityAlexei, kObjectCompartment2, true); + } + break; + + case kAction135664192: + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getEntities()->exitCompartment(kEntityAlexei, kObjectCompartment2, true); + + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Alexei, function15) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM_CHECK(params->param2, getState()->time, params->param1) + if (getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getData()->location = kLocationOutsideCompartment; + + setCallback(3); + setup_updatePosition("103D", kCarRestaurant, 52); + } + } + break; + + case kActionDefault: + params->param1 = 5 * (3 * rnd(60) + 90); + + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_updatePosition("103C", kCarRestaurant, 52); + break; + + case 2: + getData()->location = kLocationInsideCompartment; + getEntities()->drawSequenceLeft(kEntityAlexei, "103E"); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + getEntities()->drawSequenceLeft(kEntityAlexei, "103B"); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_IS(16, Alexei, function16, TimeValue) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param7 && params->param1 < getState()->time && !params->param8) { + params->param8 = 1; + + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectHandleInsideBathroom, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + } + + if (params->param5) { + UPDATE_PARAM(CURRENT_PARAMS(1, 1), getState()->timeTicks, 75); + + params->param5 = 0; + params->param6 = 1; + + getObjects()->update(kObjectCompartment2, kEntityAlexei, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObjectHandleInsideBathroom, kEntityAlexei, kObjectLocation1, kCursorNormal, kCursorNormal); + } + + CURRENT_PARAMS(1, 1) = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update(kObjectCompartment2, kEntityAlexei, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObjectHandleInsideBathroom, kEntityAlexei, kObjectLocation1, kCursorNormal, kCursorNormal); + + if (params->param5) { + if (savepoint.param.intValue == 18) { + setCallback(4); + setup_playSound(getSound()->justAMinuteCath()); + break; + } + + if (getInventory()->hasItem(kItemPassengerList)) { + setCallback(5); + setup_playSound(rnd(2) ? getSound()->wrongDoorCath() : "CAT1503"); + } else { + setCallback(6); + setup_playSound(getSound()->wrongDoorCath()); + } + } else { + setCallback(savepoint.action == kActionKnock ? 1 : 2); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAlexei, (char*)¶ms->seq); + getObjects()->update(kObjectCompartment2, kEntityAlexei, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectHandleInsideBathroom, kEntityAlexei, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kActionDrawScene: + if (params->param6 || params->param5) { + getObjects()->update(kObjectCompartment2, kEntityAlexei, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectHandleInsideBathroom, kEntityAlexei, kObjectLocation1, kCursorHandKnock, kCursorHand); + + params->param5 = 0; + params->param6 = 0; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + setCallback(3); + setup_playSound("ALX1134A"); + break; + + case 3: + getObjects()->update(kObjectCompartment2, kEntityAlexei, kObjectLocation1, kCursorTalk, kCursorNormal); + getObjects()->update(kObjectHandleInsideBathroom, kEntityAlexei, kObjectLocation1, kCursorTalk, kCursorNormal); + params->param5 = 1; + break; + + case 4: + case 5: + case 6: + params->param5 = 0; + params->param6 = 1; + break; + + case 7: + setCallback(8); + setup_updateFromTicks(300); + break; + + case 8: + setCallback(9); + setup_enterExitCompartment("602Gb", kObjectCompartment2); + break; + + case 9: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityAlexei, kEntityMertens, kAction156567128); + getEntities()->drawSequenceLeft(kEntityAlexei, "602Hb"); + getEntities()->enterCompartment(kEntityAlexei, kObjectCompartment2, true); + break; + + case 10: + getEntities()->exitCompartment(kEntityAlexei, kObjectCompartment2, true); + + getData()->location = kLocationInsideCompartment; + getData()->entityPosition = kPosition_7500; + + getEntities()->drawSequenceLeft(kEntityAlexei, (char *)¶ms->seq); + getObjects()->update(kObjectCompartment2, kEntityAlexei, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectHandleInsideBathroom, kEntityAlexei, kObjectLocation1, kCursorHandKnock, kCursorHand); + + params->param7 = 0; + break; + } + break; + + case kAction124697504: + setCallback(10); + setup_enterExitCompartment("602Ib", kObjectCompartment2); + break; + + case kAction221617184: + params->param7 = 1; + getSavePoints()->push(kEntityAlexei, kEntityMertens, kAction100906246); + + setCallback(7); + setup_playSound("CON1024"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Alexei, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler) + break; + + case kActionDefault: + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject10, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + getObjects()->update(kObjectHandleInsideBathroom, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Alexei, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime1089000 && getEntities()->isSomebodyInsideRestaurantOrSalon()) { + params->param2 = kItemNone; + + getData()->location = kLocationOutsideCompartment; + getData()->inventoryItem = kItemNone; + + getEntities()->updatePositionEnter(kEntityAlexei, kCarRestaurant, 63); + getInventory()->setLocationAndProcess(kItem17, kObjectLocation1); + + setCallback(1); + setup_callSavepoint("005D", kEntityTables1, kActionDrawTablesWithChairs, "005E"); + break; + } + + if (params->param1) { + UPDATE_PARAM(params->param3, getState()->timeTicks, 90); + getScenes()->loadSceneFromPosition(kCarRestaurant, 61); + } else { + params->param3 = 0; + } + break; + + case kAction1: + params->param2 = kItemNone; + getData()->inventoryItem = kItemNone; + + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventAlexeiDiner); + break; + + case kActionDefault: + getSavePoints()->push(kEntityAlexei, kEntityTables1, kAction136455232); + getEntities()->drawSequenceLeft(kEntityAlexei, "005B"); + + params->param2 = kItemInvalid; + getData()->inventoryItem = kItemInvalid; + break; + + case kActionDrawScene: + params->param1 = getEntities()->isPlayerPosition(kCarRestaurant, 63) ? 1 : 0; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->updatePositionExit(kEntityAlexei, kCarRestaurant, 63); + setup_function19(); + break; + + case 2: + getAction()->playAnimation(getProgress().jacket == kJacketGreen ? kEventAlexeiDiner : kEventAlexeiDinerOriginalJacket); + getSavePoints()->push(kEntityAlexei, kEntityTables1, kActionDrawTablesWithChairs, "005E"); + + getData()->entityPosition = kPosition_3650; + getData()->location = kLocationOutsideCompartment; + + getEntities()->clearSequences(kEntityAlexei); + getInventory()->get(kItem17)->location = kObjectLocation1; + getScenes()->loadSceneFromPosition(kCarRestaurant, 63); + + setup_function19(); + break; + } + break; + + case kAction168046720: + getData()->inventoryItem = kItemNone; + break; + + case kAction168627977: + getData()->inventoryItem = (InventoryItem)LOBYTE(params->param2); + break; + + case kAction225182640: + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19 ,Alexei, function19) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_draw("811DS"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_9460); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 3: + setCallback(4); + setup_callbackActionRestaurantOrSalon(); + break; + + case 4: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(5); + setup_draw("811US"); + break; + + case 5: + setCallback(6); + setup_draw("933"); + break; + + case 6: + getEntities()->updatePositionEnter(kEntityAlexei, kCarRestaurant, 63); + getScenes()->loadSceneFromItemPosition(kItem17); + getSavePoints()->push(kEntityAlexei, kEntityTables1, kAction136455232); + + setCallback(7); + setup_callSavepoint("005F", kEntityTables1, kActionDrawTablesWithChairs, "005G"); + break; + + case 7: + getEntities()->updatePositionExit(kEntityAlexei, kCarRestaurant, 63); + getSavePoints()->push(kEntityAlexei, kEntityServers1, kAction302996448); + + setCallback(8); + setup_draw("934"); + break; + + case 8: + setCallback(9); + setup_draw("811DS"); + break; + + case 9: + setCallback(10); + setup_function13(); + break; + + case 10: + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 61)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + + setCallback(11); + setup_function16(kTime1098000, "411"); + break; + + case 11: + setup_function20(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Alexei, function20) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function14(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_updatePosition("103A", kCarRestaurant, 52); + break; + + case 4: + getData()->location = kLocationInsideCompartment; + setup_function26(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Alexei, function21) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM_CHECK(params->param2, getState()->time, params->param1) + getData()->location = kLocationOutsideCompartment; + getData()->inventoryItem = kItemNone; + + setCallback(1); + setup_updatePosition("103C", kCarRestaurant, 52); + } + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventAlexeiSalonPoem); + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAlexei, "103B"); + params->param1 = 225 * (4 * rnd(3) + 4); + + if (!getEvent(kEventAlexeiSalonPoem)) + getData()->inventoryItem = kItemParchemin; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationInsideCompartment; + setup_function22(); + break; + + case 2: + getAction()->playAnimation(kEventAlexeiSalonPoem); + getData()->location = kLocationOutsideCompartment; + + getEntities()->drawSequenceRight(kEntityAlexei, "103D"); + getScenes()->loadSceneFromPosition(kCarRestaurant, 55); + getEntities()->updatePositionEnter(kEntityAlexei, kCarRestaurant, 52); + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityAlexei, "103B"); + getEntities()->updatePositionExit(kEntityAlexei, kCarRestaurant, 52); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Alexei, function22) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM_PROC(params->param2, getState()->time, params->param2) + if (getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getData()->location = kLocationOutsideCompartment; + getData()->inventoryItem = kItemNone; + + setCallback(1); + setup_updatePosition("103D", kCarRestaurant, 52); + break; + } + UPDATE_PARAM_PROC_END + + if (params->param3 == kTimeInvalid || getState()->time <= kTime1111500) + break; + + if (getState()->time > kTime1138500) { + params->param3 = kTimeInvalid; + } else { + if (!getEntities()->isInSalon(kEntityPlayer) || getEntities()->isInSalon(kEntityPlayer) || !params->param3) + params->param3 = getState()->time; + + if (params->param3 >= getState()->time) + break; + + params->param3 = kTimeInvalid; + } + + getData()->inventoryItem = kItemNone; + + setup_function23(); + break; + + case kAction1: + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventAlexeiSalonPoem); + break; + + case kActionDefault: + params->param1 = 255 * (4 * rnd(4) + 8); + getEntities()->drawSequenceLeft(kEntityAlexei, "103E"); + if (!getEvent(kEventAlexeiSalonPoem)) + getData()->inventoryItem = kItemParchemin; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationInsideCompartment; + setup_function21(); + break; + + case 2: + getAction()->playAnimation(kEventAlexeiSalonPoem); + getData()->inventoryItem = kItemNone; + getData()->location = kLocationOutsideCompartment; + + getEntities()->drawSequenceRight(kEntityAlexei, "103D"); + getEntities()->updatePositionEnter(kEntityAlexei, kCarRestaurant, 52); + getScenes()->loadSceneFromPosition(kCarRestaurant, 55); + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case 3: + getEntities()->updatePositionExit(kEntityAlexei, kCarRestaurant, 52); + getData()->location = kLocationInsideCompartment; + + setup_function21(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Alexei, function23) + switch (savepoint.action) { + default: + break; + + case kActionNone: + getData()->inventoryItem = (!getEntities()->isInRestaurant(kEntityAlexei) || getEvent(kEventAlexeiSalonPoem)) ? kItemNone : kItemParchemin; + break; + + case kAction1: + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventAlexeiSalonPoem); + break; + + case kActionDefault: + getData()->location = kLocationInsideCompartment; + getSavePoints()->push(kEntityAlexei, kEntityTatiana, kAction124973510); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventAlexeiSalonVassili); + + getData()->location = kLocationOutsideCompartment; + + getEntities()->drawSequenceLeft(kEntityAlexei, "103F"); + getScenes()->processScene(); + + setup_function24(); + break; + + case 2: + getAction()->playAnimation(kEventAlexeiSalonPoem); + + getData()->inventoryItem = kItemNone; + + getScenes()->loadSceneFromPosition(kCarRestaurant, 55); + break; + } + break; + + case kAction157159392: + if (getEntities()->isInSalon(kEntityPlayer)) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAlexeiSalonVassili); + } else { + setup_function24(); + } + break; + + case kAction188784532: + setup_function24(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Alexei, function24) + switch (savepoint.action) { + default: + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAlexeiSalonCath); + break; + + case kActionDefault: + if (getEvent(kEventAlexeiSalonVassili)) + getData()->inventoryItem = kItemInvalid; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventAlexeiSalonCath); + getData()->car = kCarRestaurant; + getData()->entityPosition = kPosition_9460; + getEntities()->clearSequences(kEntityAlexei); + getScenes()->loadSceneFromPosition(kCarRestaurant, 55); + setup_function25(); + break; + + case 2: + setup_function25(); + break; + } + break; + + case kAction135854208: + getData()->inventoryItem = kItemNone; + setCallback(2); + setup_draw("103G"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Alexei, function25) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function13(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 61)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + + setCallback(2); + setup_function16(kTime1179000, "411"); + break; + + case 2: + setCallback(3); + setup_function16(kTime1323000, "412"); + break; + + case 3: + setup_function26(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Alexei, function26) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK(kTime1512000, params->param1, setup_function27) + break; + + case kActionDefault: + getData()->entityPosition = kPosition_7500; + getData()->car = kCarGreenSleeping; + getData()->location = kLocationInsideCompartment; + + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectHandleInsideBathroom, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject10, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 61)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 66); + + getEntities()->clearSequences(kEntityAlexei); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Alexei, function27) + if (savepoint.action == kActionDefault) { + getObjects()->update(kObject10, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 66)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + + getEntities()->drawSequenceLeft(kEntityAlexei, "412"); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Alexei, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAlexei); + + getObjects()->update(kObjectCompartment2, kEntityAlexei, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject10, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + getObjects()->update(kObjectHandleInsideBathroom, kEntityAlexei, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_7500; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Alexei, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(kTime1791000, "411"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function14(); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 3: + setCallback(4); + setup_callbackActionRestaurantOrSalon(); + break; + + case 4: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(5); + setup_draw("811US"); + break; + + case 5: + getEntities()->updatePositionEnter(kEntityAlexei, kCarRestaurant, 63); + + setCallback(6); + setup_callSavepoint("018B", kEntityTables1, kAction136455232, "BOGUS"); + break; + + case 6: + getEntities()->updatePositionExit(kEntityAlexei, kCarRestaurant, 63); + getSavePoints()->push(kEntityAlexei, kEntityTatiana, kAction290869168); + setup_function30(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Alexei, function30) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getData()->car = kCarRestaurant; + getData()->location = kLocationInsideCompartment; + + getEntities()->drawSequenceLeft(kEntityAlexei, "018C"); + getSavePoints()->push(kEntityAlexei, kEntityTables1, kAction136455232); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->updatePositionExit(kEntityAlexei, kCarRestaurant, 63); + getSavePoints()->push(kEntityAlexei, kEntityTatiana, kAction156444784); + getEntities()->drawSequenceLeft(kEntityAlexei, "018E"); + + if (getEntities()->isInRestaurant(kEntityPlayer)) + getProgress().field_68 = 1; + + setCallback(2); + setup_playSound("TAT2116"); + break; + + case 2: + getSound()->playSound(kEntityAlexei, "TAt2116A"); + getEntities()->updatePositionEnter(kEntityAlexei, kCarRestaurant, 63); + + setCallback(3); + setup_callSavepoint("018F", kEntityTatiana, kAction123857088, "BOGUS"); + break; + + case 3: + getEntities()->updatePositionExit(kEntityAlexei, kCarRestaurant, 63); + setup_function31(); + break; + } + break; + + case kAction236053296: + getEntities()->drawSequenceRight(kEntityAlexei, "018D1"); + getEntities()->drawSequenceRight(kEntityTatiana, "018D2"); + getEntities()->updatePositionEnter(kEntityAlexei, kCarRestaurant, 63); + + if (savepoint.param.intValue) + getScenes()->loadSceneFromPosition(kCarRestaurant, (Position)savepoint.param.intValue); + + setCallback(1); + setup_callbackActionOnDirection(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, Alexei, function31) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityAlexei, "811DS"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityAlexei); + + setCallback(1); + setup_callbackActionOnDirection(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function13(); + break; + + case 2: + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 61)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + + setCallback(3); + setup_function16(kTimeEnd, "411"); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Alexei, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAlexei); + + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject10, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + getObjects()->update(kObjectHandleInsideBathroom, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Alexei, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function34(); + break; + + case kAction122288808: + getData()->entityPosition = kPosition_9270; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + + setCallback(1); + setup_function13(); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityAlexei, "BLANK"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(34, Alexei, function34) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 61)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + + setCallback(1); + setup_function16(kTime2083500, "411"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function14(); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 3: + setCallback(4); + setup_callbackActionRestaurantOrSalon(); + break; + + case 4: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(5); + setup_updatePosition("103A", kCarRestaurant, 52); + break; + + case 5: + setCallback(6); + setup_function35(); + break; + + case 6: + setCallback(7); + setup_function13(); + break; + + case 7: + getObjects()->update(kObject10, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 61)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 66); + + setCallback(8); + setup_function16(kTime2124000, "NONE"); + break; + + case 8: + setCallback(9); + setup_function14(); + break; + + case 9: + setCallback(10); + setup_function36(); + break; + + case 10: + getObjects()->update(kObject10, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 66)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + + setCallback(11); + setup_function16(kTime16451100, "411"); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(35, Alexei, function35) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInSalon(kEntityPlayer)) { + UPDATE_PARAM_PROC(params->param2, getState()->time, 2700) + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + UPDATE_PARAM_PROC_END + } else { + params->param2 = 0; + } + + UPDATE_PARAM_PROC(params->param3, getState()->time, params->param1) + if (getEntities()->isSomebodyInsideRestaurantOrSalon()) { + setCallback(3); + setup_function15(); + break; + } + UPDATE_PARAM_PROC_END + +label_callback_3: + UPDATE_PARAM(params->param4, getState()->time, 9000); + + setCallback(4); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionDefault: + params->param1 = 15 * rnd(120); + getEntities()->drawSequenceLeft(kEntityAlexei, "103B"); + getData()->location = kLocationInsideCompartment; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 4: + getData()->location = kLocationOutsideCompartment; + + setCallback((byte)(getCallback() + 1)); + setup_updatePosition("124C", kCarRestaurant, 52); + break; + + case 2: + case 5: + CALLBACK_ACTION(); + break; + + case 3: + params->param1 = 15 * rnd(120); + params->param3 = 0; + goto label_callback_3; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(36, Alexei, function36) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param3 || params->param2) + break; + + UPDATE_PARAM(params->param4, getState()->timeTicks, params->param1); + + getEntities()->drawSequenceRight(kEntityAlexei, "124B"); + + params->param2 = 1; + params->param4 = 0; + break; + + case kActionExitCompartment: + if (params->param2) { + getEntities()->drawSequenceLeft(kEntityAlexei, "124A"); + params->param1 = 5 * (3 * rnd(15) + 15); + params->param2 = 0; + } + break; + + case kActionDefault: + params->param1 = 5 * (3 * rnd(15) + 15); + + setCallback(1); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_callbackActionRestaurantOrSalon(); + break; + + case 2: + getSavePoints()->push(kEntityAlexei, kEntityAbbot, kAction222609266); + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(3); + setup_updatePosition("103A", kCarRestaurant, 52); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + getEntities()->drawSequenceLeft(kEntityAlexei, "124A"); + break; + + case 4: + CALLBACK_ACTION(); + break; + } + break; + + case kAction122288808: + setCallback(4); + setup_function13(); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityAlexei, "BLANK"); + params->param3 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(37, Alexei, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAlexei); + + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject10, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + getObjects()->update(kObjectHandleInsideBathroom, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_7500; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(38, Alexei, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(kTime2354400, "411"); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function39(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(39, Alexei, function39) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param2) + break; + + if (!params->param4) { + params->param3 = getState()->time + 4500; + params->param4 = getState()->time + 9000; + } + + if (params->param5 != kTimeInvalid && params->param3 < getState()->time) { + + if (params->param4 >= getState()->time) { + if (getEntities()->isInGreenCarEntrance(kEntityPlayer) || !params->param5) + params->param5 = getState()->time; + + if (params->param5 >= getState()->time) + break; + } + + params->param4 = kTimeInvalid; + + getEntities()->updatePositionEnter(kEntityAlexei, kCarGreenSleeping, 70); + getEntities()->updatePositionEnter(kEntityAlexei, kCarGreenSleeping, 71); + + if (getEntities()->isInGreenCarEntrance(kEntityPlayer)) { + getSound()->excuseMe(kEntityAlexei); + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 62)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 72); + } + + setup_function40(); + } + break; + + case kActionExitCompartment: + if (!params->param2 && !params->param2) + getEntities()->drawSequenceLeft(kEntityAlexei, "306F"); + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("602FB", kObjectCompartment2); + break; + + case kActionDrawScene: + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 62)) { + if (params->param1) { + if (!params->param2) + break; + } else if (!params->param2) { + getEntities()->drawSequenceRight(kEntityAlexei, "306A"); + break; + } + + setup_function40(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_540); + break; + + case 2: + getEntities()->clearSequences(kEntityAlexei); + + if (getEntities()->isInGreenCarEntrance(kEntityPlayer)) { + getSound()->excuseMe(kEntityAlexei); + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 62)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 72); + } + + getEntities()->updatePositionEnter(kEntityAlexei, kCarGreenSleeping, 70); + getEntities()->updatePositionEnter(kEntityAlexei, kCarGreenSleeping, 71); + break; + } + break; + + case kAction123536024: + params->param2 = 1; + break; + + case kAction123712592: + getEntities()->clearSequences(kEntityAlexei); + params->param1 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(40, Alexei, function40) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_7500); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceRight(kEntityAlexei, "602Eb"); + getEntities()->enterCompartment(kEntityAlexei, kObjectCompartment2); + + getData()->location = kLocationInsideCompartment; + + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_7500)) { + getAction()->playAnimation(isNight() ? kEventCathTurningNight : kEventCathTurningDay); + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromObject(kObjectCompartment2); + } + + setCallback(2); + setup_callbackActionOnDirection(); + break; + + case 2: + getEntities()->exitCompartment(kEntityAlexei, kObjectCompartment2); + getData()->entityPosition = kPosition_7500; + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityAlexei); + + setup_function41(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(41, Alexei, function41) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 66)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + + setCallback(1); + setup_function16(kTime2403000, "411"); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function42(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(42, Alexei, function42) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function14(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityAlexei, kEntityTatiana, kAction191198209); + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + setCallback(3); + setup_updatePosition("103A", kCarRestaurant, 52); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + setup_function43(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(43, Alexei, function43) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time < kTime1806300 && params->param2 < getState()->time) { + if (!params->param2) + params->param2 = getState()->time + params->param1; + + if (getEntities()->isSomebodyInsideRestaurantOrSalon()) { + setCallback(1); + setup_function15(); + break; + } + } + +label_callback_1: + if (getState()->time > kTime2457000 && !params->param3) { + params->param3 = 1; + + setCallback(2); + setup_callbackActionRestaurantOrSalon(); + } + break; + + case kActionDefault: + params->param1 = 5 * (3 * rnd(120) + 180); + getEntities()->drawSequenceLeft(kEntityAlexei, "103B"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + params->param1 = 5 * (3 * rnd(120) + 180); + params->param2 = 0; + goto label_callback_1; + + case 2: + getData()->location = kLocationOutsideCompartment; + + setCallback(3); + setup_updatePosition("124C", kCarRestaurant, 52); + break; + + case 3: + setup_function44(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(44, Alexei, function44) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime2457000 && !params->param1) { + params->param1 = 1; + + getEntities()->updatePositionExit(kEntityAlexei, kCarGreenSleeping, 70); + getEntities()->updatePositionExit(kEntityAlexei, kCarGreenSleeping, 71); + + if (getEntities()->isInGreenCarEntrance(kEntityPlayer)) { + getSound()->excuseMe(kEntityAlexei); + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 62)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 72); + + setup_function45(); + } + } + break; + + case kActionDefault: + getData()->car = kCarRedSleeping; + getData()->location = kLocationOutsideCompartment; + getData()->entityPosition = kPosition_9460; + + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_540); + break; + + case kActionDrawScene: + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 62)) { + setCallback(2); + setup_draw("306A"); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->clearSequences(kEntityAlexei); + + if (getEntities()->isInGreenCarEntrance(kEntityPlayer)) { + getSound()->excuseMe(kEntityAlexei); + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 62)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 72); + } + + getEntities()->updatePositionEnter(kEntityAlexei, kCarGreenSleeping, 70); + getEntities()->updatePositionEnter(kEntityAlexei, kCarGreenSleeping, 71); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityAlexei, "306F"); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(45, Alexei, function45) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function13(); + break; + + case kActionCallback: + if (getCallback() == 1) { + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 66)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + + if (getInventory()->hasItem(kItemBomb)) { + setup_function46(); + } else { + setCallback(2); + setup_function16(kTimeEnd, "412"); + } + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(46, Alexei, function46) + error("Alexei: callback function 46 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(47, Alexei, function47) + if (savepoint.action == kActionDefault) { + getEntities()->clearSequences(kEntityAlexei); + + getData()->entityPosition = kPositionNone; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarNone; + + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectHandleInsideBathroom, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(48, Alexei, chapter5) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityAlexei); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/alexei.h b/engines/lastexpress/entities/alexei.h new file mode 100644 index 0000000000..dc1a136143 --- /dev/null +++ b/engines/lastexpress/entities/alexei.h @@ -0,0 +1,213 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ALEXEI_H +#define LASTEXPRESS_ALEXEI_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Alexei : public Entity { +public: + Alexei(LastExpressEngine *engine); + ~Alexei() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Updates parameter 2 using ticks value + * + * @param ticks The number of ticks to add + */ + DECLARE_FUNCTION_1(updateFromTicks, uint32 ticks) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Updates the position + * + * @param sequence1 The sequence to draw + * @param car The car + * @param position The position + */ + DECLARE_FUNCTION_3(updatePosition, const char* sequence1, CarIndex car, Position position) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Call a savepoint (or draw sequence in default case) + * + * @param sequence1 The sequence to draw in the default case + * @param entity The entity + * @param action The action + * @param sequence2 The sequence name for the savepoint + */ + DECLARE_FUNCTION_4(callSavepoint, const char* sequence1, EntityIndex entity, ActionIndex action, const char* sequence2) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + /** + * Draws the entity along with another one + * + * @param savepoint The savepoint + * - The sequence to draw + * - The sequence to draw for the second entity + * - The EntityIndex of the second entity + */ + DECLARE_FUNCTION_NOSETUP(draw2) + + /** + * Process callback action when somebody is standing in the restaurant or salon. + */ + DECLARE_FUNCTION(callbackActionRestaurantOrSalon) + + DECLARE_FUNCTION(function13) + DECLARE_FUNCTION(function14) + DECLARE_FUNCTION(function15) + + /** + * ??? + * + * @param timeValue The time value + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_2(function16, TimeValue timeValue, const char* sequence) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + DECLARE_FUNCTION(function19) + DECLARE_FUNCTION(function20) + DECLARE_FUNCTION(function21) + DECLARE_FUNCTION(function22) + DECLARE_FUNCTION(function23) + DECLARE_FUNCTION(function24) + DECLARE_FUNCTION(function25) + DECLARE_FUNCTION(function26) + DECLARE_FUNCTION(function27) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + DECLARE_FUNCTION(function30) + DECLARE_FUNCTION(function31) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + DECLARE_FUNCTION(function34) + DECLARE_FUNCTION(function35) + DECLARE_FUNCTION(function36) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + DECLARE_FUNCTION(function39) + DECLARE_FUNCTION(function40) + DECLARE_FUNCTION(function41) + DECLARE_FUNCTION(function42) + DECLARE_FUNCTION(function43) + DECLARE_FUNCTION(function44) + DECLARE_FUNCTION(function45) + DECLARE_FUNCTION(function46) + DECLARE_FUNCTION(function47) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_ALEXEI_H diff --git a/engines/lastexpress/entities/alouan.cpp b/engines/lastexpress/entities/alouan.cpp new file mode 100644 index 0000000000..0a66004a49 --- /dev/null +++ b/engines/lastexpress/entities/alouan.cpp @@ -0,0 +1,504 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/alouan.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +Alouan::Alouan(LastExpressEngine *engine) : Entity(engine, kEntityAlouan) { + ADD_CALLBACK_FUNCTION(Alouan, reset); + ADD_CALLBACK_FUNCTION(Alouan, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Alouan, playSound); + ADD_CALLBACK_FUNCTION(Alouan, updateFromTime); + ADD_CALLBACK_FUNCTION(Alouan, updateEntity); + ADD_CALLBACK_FUNCTION(Alouan, compartment6); + ADD_CALLBACK_FUNCTION(Alouan, compartment8); + ADD_CALLBACK_FUNCTION(Alouan, compartment6to8); + ADD_CALLBACK_FUNCTION(Alouan, compartment8to6); + ADD_CALLBACK_FUNCTION(Alouan, chapter1); + ADD_CALLBACK_FUNCTION(Alouan, chapter1Handler); + ADD_CALLBACK_FUNCTION(Alouan, function12); + ADD_CALLBACK_FUNCTION(Alouan, chapter2); + ADD_CALLBACK_FUNCTION(Alouan, chapter2Handler); + ADD_CALLBACK_FUNCTION(Alouan, chapter3); + ADD_CALLBACK_FUNCTION(Alouan, chapter3Handler); + ADD_CALLBACK_FUNCTION(Alouan, chapter4); + ADD_CALLBACK_FUNCTION(Alouan, chapter4Handler); + ADD_CALLBACK_FUNCTION(Alouan, function19); + ADD_CALLBACK_FUNCTION(Alouan, chapter5); + ADD_CALLBACK_FUNCTION(Alouan, chapter5Handler); + ADD_CALLBACK_FUNCTION(Alouan, function22); + ADD_CALLBACK_FUNCTION(Alouan, function23); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Alouan, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(2, Alouan, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(3, Alouan, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(4, Alouan, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(5, Alouan, updateEntity, CarIndex, EntityPosition) + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6, Alouan, compartment6) + COMPARTMENT_TO(Alouan, kObjectCompartment6, kPosition_4070, "621Cf", "621Df"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Alouan, compartment8) + COMPARTMENT_TO(Alouan, kObjectCompartment8, kPosition_2740, "621Ch", "621Dh"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Alouan, compartment6to8) + COMPARTMENT_FROM_TO(Alouan, kObjectCompartment6, kPosition_4070, "621Bf", kObjectCompartment8, kPosition_2740, "621Ah"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Alouan, compartment8to6) + COMPARTMENT_FROM_TO(Alouan, kObjectCompartment8, kPosition_2740, "621Bh", kObjectCompartment6, kPosition_4070, "621Af"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Alouan, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Alouan, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + + TIME_CHECK_CALLBACK(kTime1096200, params->param1, 1, setup_compartment8to6); + +label_callback1: + if (getState()->time > kTime1162800 && !params->param2) { + params->param2 = 1; + getSavePoints()->push(kEntityAlouan, kEntityTrain, kAction191070912, kPosition_4070); + getData()->entityPosition = kPosition_4070; + } + + if (getState()->time > kTime1179000 && !params->param3) { + params->param3 = 1; + getSavePoints()->push(kEntityAlouan, kEntityTrain, kAction191070912, kPosition_4840); + + setCallback(2); + setup_compartment6to8(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->entityPosition = kPosition_4840; + goto label_callback1; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Alouan, function12) + if (savepoint.action == kActionDefault) { + getObjects()->update(kObjectCompartment7, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment5, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + getEntities()->clearSequences(kEntityAlouan); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Alouan, chapter2) + if (savepoint.action != kActionDefault) + return; + + getEntities()->clearSequences(kEntityAlouan); + + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + setup_chapter2Handler(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Alouan, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param2 == kTimeInvalid) + break; + + if (getState()->time <= kTime1777500) { + if (!getEntities()->isPlayerInCar(kCarGreenSleeping) || !params->param2) + params->param2 = getState()->time + 75; + + if (params->param2 >= getState()->time) + break; + } + + params->param2 = kTimeInvalid; + + setCallback(params->param1 ? 1 : 2); + if (params->param1) + setup_compartment8(); + else + setup_compartment6(); + break; + + case kActionDefault: + getSavePoints()->push(kEntityAlouan, kEntityTrain, kAction191070912, kPosition_4840); + params->param1 = 1; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 3: + params->param1 = 0; + setCallback(4); + setup_playSound("Har2011"); + break; + + case 4: + setCallback(5); + setup_updateFromTime(900); + break; + + case 5: + getSavePoints()->push(kEntityAlouan, kEntityFrancois, kAction190219584); + break; + } + break; + + case kAction189489753: + setCallback(3); + setup_compartment8to6(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Alouan, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAlouan); + + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Alouan, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK(kTimeCitySalzbourg, params->param1, 1, setup_compartment8to6); + +label_callback1: + if (params->param2 != kTimeInvalid && getState()->time > kTime1989000) + TIME_CHECK_CAR(kTime2119500, params->param5, 5, setup_compartment8); + +label_callback2: + TIME_CHECK_PLAYSOUND(kTime2052000, params->param3, 3, "Har1005"); + +label_callback3: + TIME_CHECK_CALLBACK(kTime2133000, params->param4, 4, setup_compartment6to8); + +label_callback4: + if (params->param5 != kTimeInvalid && getState()->time > kTime2151000) + TIME_CHECK_CAR(kTime2241000, params->param5, 5, setup_compartment8); + break; + + case kActionDefault: + getSavePoints()->push(kEntityAlouan, kEntityTrain, kAction191070912, kPosition_4840); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->entityPosition = kPosition_4840; + goto label_callback1; + + case 2: + goto label_callback2; + + case 3: + goto label_callback3; + + case 4: + goto label_callback4; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Alouan, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAlouan); + + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Alouan, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 != kTimeInvalid) + TIME_CHECK_CAR(kTime2443500, params->param1, 1, setup_compartment8); + +label_callback1: + TIME_CHECK_CALLBACK(kTime2455200, params->param2, 2, setup_compartment8to6); + +label_callback2: + if (getState()->time > kTime2475000 && !params->param3) { + params->param3 = 1; + getSavePoints()->push(kEntityAlouan, kEntityTrain, kAction191070912, kPosition_4840); + + setCallback(3); + setup_compartment6to8(); + } + break; + + case kActionDefault: + getSavePoints()->push(kEntityAlouan, kEntityTrain, kAction191070912, kPosition_4840); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback1; + + case 2: + getSavePoints()->push(kEntityAlouan, kEntityTrain, kAction191070912, kPosition_4070); + goto label_callback2; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Alouan, function19) + if (savepoint.action == kActionDefault) { + getObjects()->update(kObjectCompartment7, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment5, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + getEntities()->clearSequences(kEntityAlouan); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Alouan, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAlouan); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Alouan, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_function22(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Alouan, function22) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param1, getState()->time, 2700); + setup_function23(); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5000; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + break; + + case kActionDrawScene: + if (getEntities()->isInsideTrainCar(kEntityPlayer, kCarGreenSleeping)) + setup_function23(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Alouan, function23) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_4070); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("619AF", kObjectCompartment5); + break; + + case 2: + getEntities()->clearSequences(kEntityAlouan); + + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + + getObjects()->update(kObjectCompartment6, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } + break; + + case kAction135800432: + setup_nullfunction(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(24, Alouan) + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/alouan.h b/engines/lastexpress/entities/alouan.h new file mode 100644 index 0000000000..282117fcea --- /dev/null +++ b/engines/lastexpress/entities/alouan.h @@ -0,0 +1,139 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ALOUAN_H +#define LASTEXPRESS_ALOUAN_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Alouan : public Entity { +public: + Alouan(LastExpressEngine *engine); + ~Alouan() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION(compartment6) + DECLARE_FUNCTION(compartment8) + DECLARE_FUNCTION(compartment6to8) + DECLARE_FUNCTION(compartment8to6) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + DECLARE_FUNCTION(function12) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + DECLARE_FUNCTION(function19) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + DECLARE_FUNCTION(function22) + DECLARE_FUNCTION(function23) + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_ALOUAN_H diff --git a/engines/lastexpress/entities/anna.cpp b/engines/lastexpress/entities/anna.cpp new file mode 100644 index 0000000000..f81d95754e --- /dev/null +++ b/engines/lastexpress/entities/anna.cpp @@ -0,0 +1,3400 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/anna.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/fight.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +Anna::Anna(LastExpressEngine *engine) : Entity(engine, kEntityAnna) { + ADD_CALLBACK_FUNCTION(Anna, reset); + ADD_CALLBACK_FUNCTION(Anna, draw); + ADD_CALLBACK_FUNCTION(Anna, updatePosition); + ADD_CALLBACK_FUNCTION(Anna, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Anna, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Anna, callSavepoint); + ADD_CALLBACK_FUNCTION(Anna, playSound); + ADD_CALLBACK_FUNCTION(Anna, callbackActionRestaurantOrSalon); + ADD_CALLBACK_FUNCTION(Anna, savegame); + ADD_CALLBACK_FUNCTION(Anna, updateEntity); + ADD_CALLBACK_FUNCTION(Anna, updateFromTime); + ADD_CALLBACK_FUNCTION(Anna, function12); + ADD_CALLBACK_FUNCTION(Anna, draw2); + ADD_CALLBACK_FUNCTION(Anna, updateFromTicks); + ADD_CALLBACK_FUNCTION(Anna, function15); + ADD_CALLBACK_FUNCTION(Anna, chapter1); + ADD_CALLBACK_FUNCTION(Anna, function17); + ADD_CALLBACK_FUNCTION(Anna, function18); + ADD_CALLBACK_FUNCTION(Anna, chapter1Handler); + ADD_CALLBACK_FUNCTION(Anna, function20); + ADD_CALLBACK_FUNCTION(Anna, function21); + ADD_CALLBACK_FUNCTION(Anna, function22); + ADD_CALLBACK_FUNCTION(Anna, function23); + ADD_CALLBACK_FUNCTION(Anna, function24); + ADD_CALLBACK_FUNCTION(Anna, function25); + ADD_CALLBACK_FUNCTION(Anna, function26); + ADD_CALLBACK_FUNCTION(Anna, function27); + ADD_CALLBACK_FUNCTION(Anna, function28); + ADD_CALLBACK_FUNCTION(Anna, function29); + ADD_CALLBACK_FUNCTION(Anna, function30); + ADD_CALLBACK_FUNCTION(Anna, function31); + ADD_CALLBACK_FUNCTION(Anna, function32); + ADD_CALLBACK_FUNCTION(Anna, function33); + ADD_CALLBACK_FUNCTION(Anna, function34); + ADD_CALLBACK_FUNCTION(Anna, function35); + ADD_CALLBACK_FUNCTION(Anna, function36); + ADD_CALLBACK_FUNCTION(Anna, function37); + ADD_CALLBACK_FUNCTION(Anna, function38); + ADD_CALLBACK_FUNCTION(Anna, function39); + ADD_CALLBACK_FUNCTION(Anna, function40); + ADD_CALLBACK_FUNCTION(Anna, function41); + ADD_CALLBACK_FUNCTION(Anna, chapter2); + ADD_CALLBACK_FUNCTION(Anna, chapter2Handler); + ADD_CALLBACK_FUNCTION(Anna, chapter3); + ADD_CALLBACK_FUNCTION(Anna, function45); + ADD_CALLBACK_FUNCTION(Anna, chapter3Handler); + ADD_CALLBACK_FUNCTION(Anna, function47); + ADD_CALLBACK_FUNCTION(Anna, function48); + ADD_CALLBACK_FUNCTION(Anna, leaveTableWithAugust); + ADD_CALLBACK_FUNCTION(Anna, function50); + ADD_CALLBACK_FUNCTION(Anna, function51); + ADD_CALLBACK_FUNCTION(Anna, function52); + ADD_CALLBACK_FUNCTION(Anna, function53); + ADD_CALLBACK_FUNCTION(Anna, function54); + ADD_CALLBACK_FUNCTION(Anna, function55); + ADD_CALLBACK_FUNCTION(Anna, function56); + ADD_CALLBACK_FUNCTION(Anna, function57); + ADD_CALLBACK_FUNCTION(Anna, function58); + ADD_CALLBACK_FUNCTION(Anna, function59); + ADD_CALLBACK_FUNCTION(Anna, function60); + ADD_CALLBACK_FUNCTION(Anna, function61); + ADD_CALLBACK_FUNCTION(Anna, function62); + ADD_CALLBACK_FUNCTION(Anna, function63); + ADD_CALLBACK_FUNCTION(Anna, baggage); + ADD_CALLBACK_FUNCTION(Anna, function65); + ADD_CALLBACK_FUNCTION(Anna, chapter4); + ADD_CALLBACK_FUNCTION(Anna, chapter4Handler); + ADD_CALLBACK_FUNCTION(Anna, function68); + ADD_CALLBACK_FUNCTION(Anna, function69); + ADD_CALLBACK_FUNCTION(Anna, function70); + ADD_CALLBACK_FUNCTION(Anna, function71); + ADD_CALLBACK_FUNCTION(Anna, function72); + ADD_CALLBACK_FUNCTION(Anna, function73); + ADD_CALLBACK_FUNCTION(Anna, chapter5); + ADD_CALLBACK_FUNCTION(Anna, chapter5Handler); + ADD_CALLBACK_FUNCTION(Anna, function76); + ADD_CALLBACK_FUNCTION(Anna, function77); + ADD_CALLBACK_FUNCTION(Anna, function78); + ADD_CALLBACK_FUNCTION(Anna, function79); + ADD_CALLBACK_FUNCTION(Anna, function80); + ADD_CALLBACK_FUNCTION(Anna, finalSequence); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Anna, reset) + Entity::reset(savepoint, true, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Anna, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SII(3, Anna, updatePosition, CarIndex, Position) + Entity::updatePosition(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(4, Anna, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(5, Anna, callbackActionOnDirection) + Entity::callbackActionOnDirection(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SIIS(6, Anna, callSavepoint, EntityIndex, ActionIndex) + Entity::callSavepoint(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(7, Anna, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Anna, callbackActionRestaurantOrSalon) + Entity::callbackActionRestaurantOrSalon(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(9, Anna, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(10, Anna, updateEntity, CarIndex, EntityPosition) + if (savepoint.action == kActionExcuseMeCath) { + if (getEvent(kEventAugustPresentAnna) || getEvent(kEventAugustPresentAnnaFirstIntroduction) || getProgress().chapter >= kChapter2) + getSound()->playSound(kEntityPlayer, "CAT1001"); + else + getSound()->excuseMeCath(); + + return; + } + + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(11, Anna, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Anna, function12) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param2 && ENTITY_PARAM(0, 1)) + params->param2 = 1; + + if (params->param6) { + UPDATE_PARAM_PROC(params->param7, getState()->timeTicks, 75) + getSavePoints()->push(kEntityAnna, kEntityAnna, kActionEndSound); + + params->param6 = 0; + params->param7 = 0; + UPDATE_PARAM_PROC_END + } + + if (params->param4) { + UPDATE_PARAM(params->param8, getState()->timeTicks, 75); + + params->param4 = 0; + params->param5 = 1; + + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorHand); + + --params->param1; + + getSavePoints()->push(kEntityAnna, kEntityAnna, kActionEndSound); + } + + params->param8 = 0; + break; + + case kActionEndSound: + if (params->param2) { + CALLBACK_ACTION(); + break; + } + + ++params->param1; + + switch (params->param1) { + default: + break; + + case 1: + getSound()->playSound(kEntityAnna, "ANN2135A"); + break; + + case 2: + getSound()->playSound(kEntityAnna, "ANN2135B"); + break; + + case 3: + getSound()->playSound(kEntityAnna, "ANN2135C"); + break; + + case 4: + getSound()->playSound(kEntityAnna, "ANN2135C"); + break; + + case 5: + getSound()->playSound(kEntityAnna, "ANN2135L"); + break; + + case 6: + getSound()->playSound(kEntityAnna, "ANN2135K"); + break; + + case 7: + getSound()->playSound(kEntityAnna, "ANN2135H"); + break; + + case 8: + getSound()->playSound(kEntityAnna, "ANN2135K"); + break; + + case 9: + getSound()->playSound(kEntityAnna, "ANN2135I"); + break; + + case 10: + getSound()->playSound(kEntityAnna, "ANN2135J"); + break; + + case 11: + getSound()->playSound(kEntityAnna, "ANN2135M"); + break; + + case 12: + getSound()->playSound(kEntityAnna, "ANN2135L"); + break; + + case 13: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + } + break; + + case kActionKnock: + if (params->param4) { + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorHand); + + if (savepoint.param.intValue == 53) { + getSound()->playSound(kEntityPlayer, getSound()->justAMinuteCath()); + } else if (getInventory()->hasItem(kItemPassengerList)) { + if (rnd(2)) { + getSound()->playSound(kEntityPlayer, getSound()->wrongDoorCath()); + } else { + getSound()->playSound(kEntityPlayer, rnd(2) ? "CAT1506A" : "CAT1506"); + } + } else { + getSound()->playSound(kEntityPlayer, getSound()->wrongDoorCath()); + } + + params->param4 = 0; + params->param5 = 0; + } else { + getSound()->removeFromQueue(kEntityAnna); + + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(1); + setup_playSound("LIB012"); + } + break; + + case kActionOpenDoor: + getSound()->removeFromQueue(kEntityAnna); + setCallback(3); + setup_playSound("LIB013"); + break; + + case kActionDefault: + params->param1 = 1; + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectOutsideAnnaCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + if (getEntities()->isPlayerPosition(kCarRedSleeping, 49)) + getScenes()->loadSceneFromPosition(kCarRedSleeping, 49); + + getEntities()->drawSequenceLeft(kEntityAnna, "418C"); + + if (getSound()->isBuffered(kEntityAnna)) + getSound()->processEntry(kEntityAnna); + + getSound()->playSound(kEntityAnna, "ANN2135A"); + break; + + case kActionDrawScene: + if (params->param5 || params->param4) { + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + params->param4 = 0; + params->param5 = 0; + } + + if (getEntities()->isPlayerPosition(kCarRedSleeping, 60)) { + ++params->param3; + if (params->param3 == 2) { + setCallback(2); + setup_draw("418B"); + } + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_playSound("Ann1016"); + break; + + case 2: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorTalk, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorTalk, kCursorHand); + params->param4 = 1; + break; + + case 3: + if (!getSound()->isBuffered(kEntityMax)) { + setCallback(4); + setup_playSound("MAX1120"); + break; + } + // Fallback to next case + + case 4: + --params->param1; + params->param6 = 1; + break; + + case 5: + getEntities()->drawSequenceLeft(kEntityAnna, "418A"); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SSI(13, Anna, draw2, EntityIndex) + Entity::draw2(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(14, Anna, updateFromTicks, uint32) + Entity::updateFromTicks(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_IS(15, Anna, function15, TimeValue) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 < getState()->time && !params->param7) { + params->param7 = 1; + + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + } + + if (params->param5) { + UPDATE_PARAM(params->param8, getState()->timeTicks, 75); + + params->param5 = 0; + params->param6 = 1; + + CursorStyle cursor = getEntities()->isInsideCompartment(kEntityMax, kCarRedSleeping, kPosition_4070) ? kCursorHand : kCursorNormal; + + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorNormal, cursor); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorNormal, cursor); + } + + params->param8 = 0; + break; + + case kActionOpenDoor: + if (getEntities()->isInsideCompartment(kEntityMax, kCarRedSleeping, kPosition_4070)) { + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(1); + setup_playSound("LIB013"); + break; + } + // Fallback to next action + + case kActionKnock: + if (params->param5) { + CursorStyle cursor = getEntities()->isInsideCompartment(kEntityMax, kCarRedSleeping, kPosition_4070) ? kCursorHand : kCursorNormal; + + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorNormal, cursor); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorNormal, cursor); + + if (savepoint.param.intValue == kObject53) { + setCallback(6); + setup_playSound(getSound()->justAMinuteCath()); + } else { + if (getInventory()->hasItem(kItemPassengerList)) { + setCallback(7); + setup_playSound(rnd(2) ? getSound()->wrongDoorCath() : (rnd(2) ? "CAT1506" : "CAT1506A")); + } else { + setCallback(8); + setup_playSound(getSound()->wrongDoorCath()); + } + } + } else { + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(savepoint.action == kActionKnock ? 3 : 4); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + break; + + + case kActionDefault: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getEntities()->drawSequenceLeft(kEntityAnna, (char *)¶ms->seq); + break; + + case kActionDrawScene: + if (params->param6 || params->param5) { + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + + params->param5 = 0; + params->param6 = 0; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (!getSound()->isBuffered(kEntityMax)) { + setCallback(2); + setup_playSound("MAX1120"); + break; + } + // Fallback to next case + + case 2: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case 3: + case 4: + setCallback(5); + setup_playSound("ANN1016"); + break; + + case 5: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorTalk, kCursorNormal); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorTalk, kCursorNormal); + params->param5 = 1; + break; + + case 6: + case 7: + case 8: + params->param5 = 0; + params->param6 = 1; + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Anna, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getSavePoints()->addData(kEntityAnna, kAction291662081, 0); + getSavePoints()->addData(kEntityAnna, kAction238936000, 1); + + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectOutsideAnnaCompartment, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->clothes = kClothesDefault; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(17, Anna, function17, uint32, uint32) + switch (savepoint.action) { + default: + break; + + case kActionNone: + getData()->inventoryItem = (params->param3 && getEntities()->isDistanceBetweenEntities(kEntityAnna, kEntityPlayer, 2000)) ? (InventoryItem)LOBYTE(params->param3) : kItemNone; + + if (getEntities()->updateEntity(kEntityAnna, (CarIndex)params->param1, (EntityPosition)params->param2)) { + getData()->inventoryItem = kItemNone; + CALLBACK_ACTION(); + } + break; + + case kAction1: + if (savepoint.param.intValue == 8) { + getData()->inventoryItem = (InventoryItem)(getData()->inventoryItem & kItemToggleLow); + params->param3 &= 0xFFFFFFF7; + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAnnaGiveScarf); + } else { + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventGotALight); + } + break; + + case kActionExcuseMeCath: + if (getEvent(kEventAugustPresentAnna) || getEvent(kEventAugustPresentAnnaFirstIntroduction) || getProgress().chapter >= kChapter2) + getSound()->playSound(kEntityPlayer, "CAT1001"); + else + getSound()->excuseMeCath(); + break; + + case kActionExcuseMe: + getSound()->excuseMe(kEntityAnna); + break; + + case kActionDefault: + if (getProgress().jacket == kJacketGreen) { + if (!getEvent(kEventGotALight) && !getEvent(kEventGotALightD) && !getEvent(kEventAugustPresentAnna) && !getEvent(kEventAugustPresentAnnaFirstIntroduction)) + params->param3 = kItemInvalid; + + if (!params->param3 && !getEvent(kEventAnnaGiveScarfAsk) && !getEvent(kEventAnnaGiveScarfDinerAsk) && !getEvent(kEventAnnaGiveScarfSalonAsk)) + params->param3 |= 8; + } + + if (getEntities()->updateEntity(kEntityAnna, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getEvent(kEventAnnaGiveScarf) + || getEvent(kEventAnnaGiveScarfDiner) + || getEvent(kEventAnnaGiveScarfSalon) + || getEvent(kEventAnnaGiveScarfMonogram) + || getEvent(kEventAnnaGiveScarfDinerMonogram) + || getEvent(kEventAnnaGiveScarfSalonMonogram)) + getAction()->playAnimation(kEventAnnaGiveScarfAsk); + else if (getEvent(kEventAugustPresentAnna) + || getEvent(kEventAugustPresentAnnaFirstIntroduction)) + getAction()->playAnimation(kEventAnnaGiveScarfMonogram); + else + getAction()->playAnimation(kEventAnnaGiveScarf); + + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + (750 * (getData()->direction == kDirectionUp ? -1 : 1))), getData()->direction == kDirectionUp); + break; + + case 2: + getAction()->playAnimation(getData()->direction == kDirectionUp ? kEventGotALightD : kEventGotALight); + getData()->inventoryItem = (InventoryItem)(getData()->inventoryItem & kItemToggleHigh); + params->param3 &= 0xFFFFFF7F; + + if (getProgress().jacket == kJacketGreen && !getEvent(kEventAnnaGiveScarfAsk) && !getEvent(kEventAnnaGiveScarfDinerAsk) && !getEvent(kEventAnnaGiveScarfSalonAsk)) + params->param3 |= 8; + + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + (750 * (getData()->direction == kDirectionUp ? -1 : 1))), getData()->direction == kDirectionUp); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(18, Anna, function18, TimeValue) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 && params->param1 < getState()->time && getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getData()->inventoryItem = kItemNone; + CALLBACK_ACTION(); + break; + } + + if (params->param5 && !params->param4) { + UPDATE_PARAM_PROC(params->param6, getState()->time, 900) + params->param2 |= kItemScarf; + params->param5 = 0; + params->param6 = 0; + UPDATE_PARAM_PROC_END + } + + if (params->param3) { + UPDATE_PARAM(params->param7, getState()->timeTicks, 90); + + getScenes()->loadSceneFromPosition(kCarRestaurant, 61); + } else { + params->param7 = 0; + } + break; + + case kAction1: + setCallback(savepoint.param.intValue == 8 ? 1 : 2); + setup_savegame(kSavegameTypeEvent, savepoint.param.intValue == 8 ? kEventAnnaGiveScarf : kEventDinerMindJoin); + break; + + case kActionDefault: + if (getProgress().jacket == kJacketGreen) { + if (!getEvent(kEventDinerMindJoin) && !getEvent(kEventAugustPresentAnna) && !getEvent(kEventAugustPresentAnnaFirstIntroduction)) + params->param2 |= kItemInvalid; + + if (!params->param2 && !getEvent(kEventAnnaGiveScarfAsk) && !getEvent(kEventAnnaGiveScarfDinerAsk) && !getEvent(kEventAnnaGiveScarfSalonAsk)) + params->param2 |= 8; + } + + getData()->inventoryItem = (InventoryItem)LOBYTE(params->param2); + break; + + case kActionDrawScene: + params->param3 = getEntities()->isPlayerPosition(kCarRestaurant, 62); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getEvent(kEventAnnaGiveScarf) || getEvent(kEventAnnaGiveScarfDiner) || getEvent(kEventAnnaGiveScarfSalon) + || getEvent(kEventAnnaGiveScarfMonogram) || getEvent(kEventAnnaGiveScarfDinerMonogram) || getEvent(kEventAnnaGiveScarfSalonMonogram)) { + getAction()->playAnimation(kEventAnnaGiveScarfDinerAsk); + } else { + getAction()->playAnimation((getEvent(kEventAugustPresentAnna) || getEvent(kEventAugustPresentAnnaFirstIntroduction)) ? kEventAnnaGiveScarfDinerMonogram : kEventAnnaGiveScarfDiner); + params->param5 = 1; + } + + params->param2 &= 0xFFFFFFF7; + getData()->inventoryItem = (InventoryItem)params->param2; + getScenes()->loadSceneFromPosition(kCarRestaurant, 61); + break; + + case 2: + getAction()->playAnimation(kEventDinerMindJoin); + + params->param2 &= 0xFFFFFFF7; + + if (getProgress().jacket == kJacketGreen + && !getEvent(kEventAnnaGiveScarfAsk) + && !getEvent(kEventAnnaGiveScarfDinerAsk) + && !getEvent(kEventAnnaGiveScarfSalonAsk)) { + params->param2 |= 8; + } + + getData()->inventoryItem = (InventoryItem)LOBYTE(params->param2); + getScenes()->loadSceneFromPosition(kCarRestaurant, 61); + break; + } + break; + + case kAction168046720: + getData()->inventoryItem = kItemNone; + params->param4 = 1; + break; + + case kAction168627977: + getData()->inventoryItem = (InventoryItem)LOBYTE(params->param2); + params->param4 = 0; + break; + + case kAction170016384: + case kAction259136835: + case kAction268773672: + getData()->inventoryItem = kItemNone; + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Anna, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("618Ca", kObjectCompartment1); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->entityPosition = kPosition_8514; + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_4070); + break; + + case 2: + setCallback(3); + setup_enterExitCompartment("618Af", kObjectCompartmentF); + break; + + case 3: + getEntities()->clearSequences(kEntityAnna); + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + + setup_function20(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Anna, function20) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function15(kTime1093500, "NONE"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("618Bf", kObjectCompartmentF); + break; + + case 2: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityAnna, kEntityMax, kAction71277948); + setup_function21(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Anna, function21) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function17(kCarRestaurant, kPosition_850); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_callbackActionRestaurantOrSalon(); + break; + + case 2: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(3); + setup_draw("801US"); + break; + + case 3: + getEntities()->drawSequenceRight(kEntityAnna, "001B"); + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityAnna); + + setCallback(4); + setup_callbackActionOnDirection(); + break; + + case 4: + setup_function22(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Anna, function22) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAnna, "001A"); + getSavePoints()->push(kEntityAnna, kEntityPascale, kAction223262556); + break; + + case kAction157370960: + getData()->location = kLocationInsideCompartment; + setup_function23(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Anna, function23) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAnna, "001D"); + getSavePoints()->push(kEntityAnna, kEntityServers0, kAction270410280); + getSavePoints()->push(kEntityAnna, kEntityTables0, kAction136455232); + + setCallback(1); + setup_function18(kTimeNone); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityAnna, "001E"); + setCallback(2); + setup_playSound("ANN1048"); + break; + + case 2: + setCallback(3); + setup_draw("001F"); + break; + + case 3: + getSavePoints()->push(kEntityAnna, kEntityServers0, kAction203859488); + setup_function24(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Anna, function24) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAnna, "001G"); + + setCallback(1); + setup_function18(kTimeNone); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityAnna, "001H"); + setCallback(2); + setup_playSound("ANN1049"); + break; + + case 2: + getSavePoints()->push(kEntityAnna, kEntityServers0, kAction136702400); + setup_function25(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Anna, function25) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAnna, "001J"); + getProgress().field_28 = 1; + + setCallback(1); + setup_function18(kTimeNone); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 2: + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + setup_function26(); + break; + } + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityAnna, "BLANK"); + break; + + case kAction201437056: + getEntities()->drawSequenceLeft(kEntityAnna, "001J"); + setCallback(2); + setup_function18(kTime1138500); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Anna, function26) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->location = kLocationOutsideCompartment; + getEntities()->updatePositionExit(kEntityAnna, kCarRestaurant, 62); + + setCallback(1); + setup_callSavepoint("001L", kEntityTables0, kActionDrawTablesWithChairs, "001H"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->updatePositionExit(kEntityAnna, kCarRestaurant, 62); + getSavePoints()->push(kEntityAnna, kEntityServers0, kAction237485916); + getEntities()->drawSequenceRight(kEntityAnna, "801DS"); + + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityAnna); + + setCallback(2); + setup_callbackActionOnDirection(); + break; + + case 2: + setCallback(3); + setup_function17(kCarRedSleeping, kPosition_4070); + break; + + case 3: + setCallback(4); + setup_enterExitCompartment("618Af", kObjectCompartmentF); + break; + + case 4: + getEntities()->clearSequences(kEntityAnna); + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + + setup_function27(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Anna, function27) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getSavePoints()->push(kEntityAnna, kEntityMax, kAction101687594); + setCallback(1); + setup_function15(kTime1156500, "NONE"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + if (getProgress().field_14 == 29) { + params->param1 = getState()->time + 900; + setCallback(2); + setup_function15((TimeValue)params->param1, "NONE"); + } else { + setCallback(3); + setup_enterExitCompartment("618Bf", kObjectCompartmentF); + } + break; + + case 3: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityAnna, kEntityMax, kAction71277948); + setup_function28(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Anna, function28) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function17(kCarRestaurant, kPosition_850); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_callbackActionRestaurantOrSalon(); + break; + + case 2: + getData()->location = kLocationOutsideCompartment; + getData()->entityPosition = kPosition_1540; + getScenes()->loadSceneFromItemPosition(kItem3); + + setCallback(3); + setup_updatePosition("104A", kCarRestaurant, 56); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + setup_function29(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Anna, function29) + error("Anna: callback function 29 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Anna, function30) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param3 != kTimeInvalid && getState()->time) { + if (getState()->time > kTime1188000) { + params->param3 = kTimeInvalid; + getSound()->playSound(kEntityAnna, "AUG1004"); + } else { + if (!getEntities()->isInSalon(kEntityPlayer) || !params->param3) + params->param3 = getState()->time + 450; + + if (params->param3 < getState()->time) { + params->param3 = kTimeInvalid; + getSound()->playSound(kEntityAnna, "AUG1004"); + } + } + } + + if (params->param2 && params->param4 != kTimeInvalid && getState()->time > kTime1179000) { + + if (getState()->time > kTime1192500) { + params->param4 = kTimeInvalid; + setup_function30(); + break; + } + + if (!getEntities()->isInSalon(kEntityPlayer) || !params->param4) + params->param4 = getState()->time + 150; + + if (params->param4 < getState()->time) { + params->param4 = kTimeInvalid; + setup_function30(); + break; + } + } + + if (params->param1) { + UPDATE_PARAM(params->param5, getState()->timeTicks, 90); + getScenes()->loadSceneFromPosition(kCarRestaurant, 55); + } else { + params->param5 = 0; + } + break; + + case kActionEndSound: + params->param2 = 1; + break; + + case kActionDefault: + getSavePoints()->push(kEntityAnna, kEntityAugust, kAction122358304); + getEntities()->drawSequenceLeft(kEntityAnna, "106B"); + break; + + case kActionDrawScene: + params->param1 = getEntities()->isPlayerPosition(kCarRestaurant, 56); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, Anna, function31) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getSound()->playSound(kEntityAnna, "AUG1005"); + + setCallback(2); + setup_updateFromTicks(150); + break; + + case 2: + getEntities()->updatePositionEnter(kEntityAnna, kCarRestaurant, 56); + + setCallback(3); + setup_draw2("106C1", "106C2", kEntityAugust); + break; + + case 3: + getEntities()->updatePositionExit(kEntityAnna, kCarRestaurant, 56); + getInventory()->setLocationAndProcess(kItem3, kObjectLocation1); + getSavePoints()->push(kEntityAnna, kEntityAugust, kAction159332865); + + setup_function32(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Anna, function32) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function17(kCarRedSleeping, kPosition_4070); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("618Af", kObjectCompartmentF); + break; + + case 2: + getEntities()->clearSequences(kEntityAnna); + + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + + setup_function33(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Anna, function33) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getSavePoints()->push(kEntityAnna, kEntityMax, kAction101687594); + + params->param1 = getState()->time + 4500; + setCallback(1); + setup_function15((TimeValue)params->param1, "NONE"); + break; + + case kActionCallback: + if (getCallback() == 1) { + getObjects()->updateLocation2(kObjectCompartmentF, kObjectLocation1); + setup_function34(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(34, Anna, function34) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1 && getEntities()->isPlayerPosition(kCarRedSleeping, 60)) { + UPDATE_PARAM_PROC(params->param2, getState()->time, 150) + setCallback(1); + setup_draw("419B"); + break; + UPDATE_PARAM_PROC_END + } + +label_callback_1: + TIME_CHECK(kTime1489500, params->param3, setup_function35); + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(savepoint.action == kActionKnock ? 2 : 3); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectOutsideAnnaCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + if (getEntities()->isPlayerPosition(kCarRedSleeping, 78)) + getScenes()->loadSceneFromPosition(kCarRedSleeping, 49); + + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationOutsideCompartment; + + getEntities()->drawSequenceLeft(kEntityAnna, "419A"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityAnna, "419C"); + params->param1 = 1; + goto label_callback_1; + + case 2: + case 3: + if (!getSound()->isBuffered(kEntityMax)) { + setCallback(4); + setup_playSound("MAX1120"); + break; + } + // Fallback to next case + + case 4: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(35, Anna, function35) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) + break; + + UPDATE_PARAM(params->param3, getState()->timeTicks, 75); + + switch (params->param2) { + default: + break; + + case 0: + getSound()->playSound(kEntityAnna, "ANN2135E"); + break; + + case 1: + getSound()->playSound(kEntityAnna, "ANN2135F"); + break; + + case 2: + getSound()->playSound(kEntityAnna, "ANN2135G"); + break; + + case 3: + getSound()->playSound(kEntityAnna, "ANN2135D"); + break; + } + + params->param1 = 0; + params->param3 = 0; + break; + + case kActionEndSound: + ++params->param2; + + if (params->param2 > 3) + params->param2 = 0; + + params->param1 = 1; + break; + + case kActionKnock: + case kActionOpenDoor: + if (getSound()->isBuffered(kEntityAnna)) + getSound()->processEntry(kEntityAnna); + + if (savepoint.action == kActionKnock) + getSound()->playSound(kEntityPlayer, "LIB012"); + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAnnaVisitToCompartmentGun); + break; + + case kActionDefault: + getData()->clothes = kClothes1; + params->param1 = 1; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventAnnaVisitToCompartmentGun); + getSound()->playSound(kEntityPlayer, "LIB015"); + getData()->location = kLocationOutsideCompartment; + getData()->entityPosition = kPosition_4840; + + getEntities()->updateEntity(kEntityAnna, kCarRedSleeping, kPosition_8200); + getScenes()->loadSceneFromObject(kObjectCompartmentF, true); + getSavePoints()->push(kEntityAnna, kEntityVassili, kAction339669520); + getSavePoints()->push(kEntityAnna, kEntityVerges, kAction339669520); + getSavePoints()->push(kEntityAnna, kEntityCoudert, kAction339669520); + getSavePoints()->push(kEntityAnna, kEntityMax, kAction71277948); + + setup_function36(); + break; + + case 2: + setup_function36(); + break; + } + break; + + case kAction226031488: + if (getSound()->isBuffered(kEntityAnna)) + getSound()->processEntry(kEntityAnna); + + getSavePoints()->push(kEntityAnna, kEntityMax, kAction71277948); + break; + + case kAction238358920: + setCallback(2); + setup_enterExitCompartment("608Cf", kObjectCompartmentF); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(36, Anna, function36) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_8200); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(2); + setup_enterExitCompartment("608Aa", kObjectCompartmentA); + break; + + case 2: + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityAnna); + + setup_function37(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(37, Anna, function37) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + break; + + case kAction191477936: + setup_function38(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(38, Anna, function38) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_7500; + + setCallback(1); + setup_playSound("ANN1010"); + break; + + case kActionCallback: + if (getCallback() == 1) { + getSound()->playSound(kEntityPlayer, "MUS043"); + setup_function40(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(39, Anna, function39, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->updateEntity(kEntityAnna, (CarIndex)params->param1, (EntityPosition)params->param2)) { + getData()->inventoryItem = kItemNone; + + CALLBACK_ACTION(); + } + break; + + case kAction1: + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAnnaGoodNight); + break; + + case kActionExcuseMe: + getSound()->playSound(kEntityAnna, "ANN1107A"); + break; + + case kActionDefault: + getData()->inventoryItem = kItemNone; + if (!getEvent(kEventAnnaGoodNight) && !getEvent(kEventAnnaGoodNightInverse)) + getData()->inventoryItem = kItemInvalid; + + if (getEntities()->updateEntity(kEntityAnna, (CarIndex)params->param1, (EntityPosition)params->param2)) { + getData()->inventoryItem = kItemNone; + + CALLBACK_ACTION(); + } + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(getData()->direction == kDirectionNone ? kEventAnnaGoodNight : kEventAnnaGoodNightInverse); + getData()->inventoryItem = kItemNone; + + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + (750 * (getData()->direction == kDirectionUp ? -1 : 1))), getData()->direction == kDirectionUp); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(40, Anna, function40) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("608Cb", kObjectCompartmentB); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_function39(kCarRedSleeping, kPosition_4070); + break; + + case 2: + setCallback(3); + setup_enterExitCompartment("608Bf", kObjectCompartmentF); + break; + + case 3: + getEntities()->clearSequences(kEntityAnna); + getData()->location = kLocationInsideCompartment; + + setCallback(4); + setup_updateFromTime(150); + break; + + case 4: + setCallback(5); + setup_enterExitCompartment("608Cf", kObjectCompartmentF); + break; + + case 5: + getData()->location = kLocationOutsideCompartment; + + setCallback(6); + setup_function39(kCarRedSleeping, kPosition_7500); + break; + + case 6: + setCallback(7); + setup_enterExitCompartment("608Bb", kObjectCompartmentB); + break; + + case 7: + getEntities()->clearSequences(kEntityAnna); + getData()->location = kLocationInsideCompartment; + + setCallback(8); + setup_updateFromTime(150); + break; + + case 8: + setCallback(9); + setup_enterExitCompartment("608Cb", kObjectCompartmentB); + break; + + case 9: + getData()->location = kLocationOutsideCompartment; + + setCallback(10); + setup_function39(kCarRedSleeping, kPosition_4070); + break; + + case 10: + setCallback(11); + setup_enterExitCompartment("608Bf", kObjectCompartmentF); + break; + + case 11: + getEntities()->clearSequences(kEntityAnna); + getData()->location = kLocationInsideCompartment; + getData()->entityPosition = kPosition_4070; + + setup_function41(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(41, Anna, function41) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param2, getState()->time, 2700); + + params->param5++; + switch (params->param5) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityAnna, "419A"); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityAnna, "419B"); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityAnna, "419C"); + params->param1 = 0; + break; + } + + params->param2 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(savepoint.action == kActionKnock ? 1 : 2); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + break; + + case kActionDefault: + getSavePoints()->push(kEntityAnna, kEntityMax, kAction101687594); + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getEntities()->drawSequenceLeft(kEntityAnna, "419C"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + if (!getSound()->isBuffered(kEntityMax)) { + setCallback(3); + setup_playSound("MAX1120"); + break; + } + // Fallback to next case + + case 3: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(42, Anna, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAnna); + + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothes1; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(43 ,Anna, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObjectOutsideAnnaCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + setCallback(1); + setup_function12(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function15(kTime1786500, "418C"); + break; + + case 2: + setCallback(3); + setup_function12(); + break; + + case 3: + setCallback(4); + setup_function15(kTime1818000, "418C"); + break; + + case 4: + setCallback(5); + setup_function12(); + break; + + case 5: + setCallback(6); + setup_function15(kTimeEnd, "418C"); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(44, Anna, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAnna); + + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothes3; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectOutsideAnnaCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + getObjects()->update(kObject53, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(45, Anna, function45, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_enterExitCompartment("625Bf", kObjectCompartmentF); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityAnna, kEntityCoudert, params->param1 ? kAction185737168 : kAction185671840); + getSound()->playSound(kEntityAnna, "Ann3147"); + getEntities()->drawSequenceLeft(kEntityAnna, "625EF"); + getEntities()->enterCompartment(kEntityAnna, kObjectCompartmentF, true); + break; + + case 2: + getEntities()->exitCompartment(kEntityAnna, kObjectCompartmentF, true); + CALLBACK_ACTION(); + break; + } + break; + + case kAction157894320: + setCallback(2); + setup_updateFromTime(75); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(46, Anna, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (getEntities()->isPlayerPosition(kCarRedSleeping, 60)) + getScenes()->loadSceneFromPosition(kCarRedSleeping, 49); + + setCallback(1); + setup_function12(); + break; + + case kActionCallback: + if (getCallback() == 1 || getCallback() == 2) { + if (ENTITY_PARAM(0, 1)) { + setup_function47(); + } else { + setCallback(2); + setup_function15((TimeValue)(getState()->time + 4500), "418C"); + } + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(47, Anna, function47) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + setCallback(1); + setup_enterExitCompartment("688Bf", kObjectCompartmentF); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityAnna, kEntityMax, kAction71277948); + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_draw("801VS"); + break; + + case 4: + getSound()->playSound(kEntityAnna, getEvent(kEventAugustLunch) ? "Ann3136" : "Ann3136A", SoundManager::kFlagInvalid, 30); + getSavePoints()->push(kEntityAnna, kEntityAugust, kAction122358304); + + setCallback(5); + setup_draw2("026B1", "026B2", kEntityAugust); + break; + + case 5: + getEntities()->drawSequenceLeft(kEntityAugust, "BLANK"); + setup_function48(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(48, Anna, function48) + error("Anna: callback function 48 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(49, Anna, leaveTableWithAugust) + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getSavePoints()->push(kEntityAnna, kEntityTables3, kActionDrawTablesWithChairs, "010M"); + getEntities()->clearSequences(kEntityAugust); + + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityTables3, "026J3"); + getEntities()->drawSequenceRight(kEntityAugust, "026J2"); + getEntities()->drawSequenceRight(kEntityAnna, "026J1"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(50, Anna, function50) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_playSound("ann3141"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_callbackActionRestaurantOrSalon(); + break; + + case 2: + getData()->location = kLocationOutsideCompartment; + setCallback(3); + setup_leaveTableWithAugust(); + break; + + case 3: + setup_function51(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(51, Anna, function51) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) { + if (getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getSound()->playSound(kEntityAnna, "Aug3008"); + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_draw2("112E1", "112E2", kEntityAugust); + } + } + break; + + case kActionDefault: + getSound()->playSound(kEntityAnna, "Aug3142", SoundManager::kFlagInvalid, 30); + getEntities()->updatePositionEnter(kEntityAnna, kCarRestaurant, 57); + getEntities()->drawSequenceRight(kEntityAnna, "112A"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityAnna); + + setCallback(1); + setup_callbackActionOnDirection(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationInsideCompartment; + getEntities()->drawSequenceLeft(kEntityAnna, "112B"); + getEntities()->updatePositionExit(kEntityAnna, kCarRestaurant, 57); + getSavePoints()->push(kEntityAnna, kEntityServers1, kAction219377792); + break; + + case 2: + getSavePoints()->push(kEntityAnna, kEntityAugust, kAction122288808); + + setup_function52(); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityAnna, "112D"); + + if (getState()->time >= kTimeEnterAttnangPuchheim) { + params->param1 = 1; + } else { + setCallback(4); + setup_playSound("Ann3142A"); + } + break; + + case 4: + setCallback(5); + setup_updateFromTime(1800); + break; + + case 5: + setCallback(6); + setup_playSound("Aug3007"); + break; + + case 6: + params->param1 = 1; + break; + } + break; + + case kAction101169422: + if (getEvent(kEventKronosVisit)) { + setCallback(3); + setup_updatePosition("112J", kCarRestaurant, 57); + break; + } + + if (getState()->time >= kTimeEnterAttnangPuchheim) { + params->param1 = 1; + } else { + setCallback(4); + setup_playSound("Ann3142A"); + } + break; + + case kAction122288808: + getEntities()->drawSequenceLeft(kEntityAnna, "112D"); + getSavePoints()->push(kEntityAnna, kEntityKronos, kAction157159392); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityAnna, "BLANK"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(52, Anna, function52) + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getEntities()->exitCompartment(kEntityAnna, kObjectCompartmentF); + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + + getEntities()->clearSequences(kEntityAnna); + + setup_function53(); + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_4070); + break; + + case kActionCallback: + if (getCallback() == 1) { + getEntities()->drawSequenceRight(kEntityAnna, "688Af"); + getEntities()->enterCompartment(kEntityAnna, kObjectCompartmentF); + getData()->location = kLocationInsideCompartment; + + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_4070) || getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_4455)) { + getAction()->playAnimation(isNight() ? kEventCathTurningNight : kEventCathTurningDay); + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromObject(kObjectCompartmentF); + } + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(53, Anna, function53) + error("Anna: callback function 53 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(54, Anna, function54) + error("Anna: callback function 54 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(55, Anna, function55) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObjectOutsideAnnaCompartment, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + + if (getEntities()->isPlayerPosition(kCarRedSleeping, 78)) + getScenes()->loadSceneFromPosition(kCarRedSleeping, 49); + + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject53, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getInventory()->setLocationAndProcess(kItemKey, kObjectLocation1); + + setCallback(1); + setup_function45(true); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_9270); + break; + + case 2: + setup_function56(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(56, Anna, function56) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAnna); + getData()->entityPosition = kPosition_6000; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarKronos; + break; + + case kAction191668032: + setup_function57(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(57, Anna, function57) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->car = kCarGreenSleeping; + getData()->entityPosition = kPosition_850; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_5790); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityAnna, kEntityAugust, kAction191668032); + + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_4070); + break; + + case 2: + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorNormal); + getSavePoints()->push(kEntityAnna, kEntityCoudert, kAction205033696); + getEntities()->drawSequenceLeft(kEntityAnna, "625Ef"); + getEntities()->enterCompartment(kEntityAnna, kObjectCompartmentF, true); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityAnna, "625Gf"); + getEntities()->enterCompartment(kEntityAnna, kObjectCompartmentF, true); + getSavePoints()->push(kEntityAnna, kEntityAugust, kAction169032608); + break; + + case 4: + if (getSound()->isBuffered(kEntityAugust)) { + setCallback(4); + setup_updateFromTime(75); + } else { + setCallback(5); + setup_playSound("Aug3009"); + } + break; + + case 5: + getSound()->playSound(kEntityAnna, "Aug3009A"); + + setCallback(6); + setup_enterExitCompartment("628Bf", kObjectCompartmentF); + break; + + case 6: + getEntities()->exitCompartment(kEntityAnna, kObjectCompartmentF, true); + getSavePoints()->push(kEntityAnna, kEntityAugust, kAction122288808); + + setup_function59(); + break; + } + break; + + case kAction123712592: + getEntities()->drawSequenceLeft(kEntityAnna, "628Af"); + + if (getSound()->isBuffered(kEntityAugust)) { + setCallback(4); + setup_updateFromTime(75); + } else { + setCallback(5); + setup_playSound("Aug3009"); + } + break; + + case kAction192063264: + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_4070) + || getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_4455)) { + getEntities()->exitCompartment(kEntityAnna, kObjectCompartmentF, true); + setup_function58(); + } else { + setCallback(3); + setup_enterExitCompartment("625Ff", kObjectCompartmentF); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(58, Anna, function58) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAnnaSearchingCompartment); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventAnnaSearchingCompartment); + getEntities()->clearSequences(kEntityAnna); + getScenes()->loadSceneFromPosition(kCarRedSleeping, 8); + getSound()->playSound(kEntityAnna, "lib015"); + getSavePoints()->push(kEntityAnna, kEntityAugust, kAction122288808); + setup_function59(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(59, Anna, function59) + error("Anna: callback function 59 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(60, Anna, function60) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getSavePoints()->push(kEntityAnna, kEntityMax, kAction122358304); + getSound()->playSound(kEntityAnna, rnd(2) ? "Ann3126" : "Ann3127"); + + setCallback(1); + setup_enterExitCompartment("630Cf", kObjectCompartmentF); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("630Df", kObjectCompartmentF); + break; + + case 2: + getEntities()->clearSequences(kEntityAnna); + getSavePoints()->push(kEntityAnna, kEntityCoudert, kAction189026624); + break; + + case 3: + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityAnna); + + CALLBACK_ACTION(); + break; + } + break; + + case kAction156049968: + setCallback(3); + setup_enterExitCompartment("629EF", kObjectCompartmentF); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(61, Anna, function61) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getState()->timeDelta = 3; + + setCallback(1); + setup_savegame(kSavegameTypeIndex, 0); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObject53, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + setCallback(2); + setup_function45(false); + break; + + case 2: + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + setCallback(3); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 3: + setCallback(4); + setup_callbackActionRestaurantOrSalon(); + break; + + case 4: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(5); + setup_draw("802US"); + break; + + case 5: + getEntities()->drawSequenceRight(kEntityAnna, "802UD"); + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityAnna); + + setCallback(6); + setup_callbackActionOnDirection(); + break; + + case 6: + getEntities()->clearSequences(kEntityAnna); + setup_function62(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(62, Anna, function62) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime2259000 && !params->param2) { + params->param2 = 1; + getSavePoints()->push(kEntityAnna, kEntityVesna, kAction189299008); + setup_function63(); + } + break; + + case kActionDefault: + getData()->car = kCarBaggage; + getProgress().field_54 = 1; + break; + + case kAction235856512: + params->param1 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(63, Anna, function63) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getSavePoints()->push(kEntityAnna, kEntityChapters, kAction171843264); + break; + + // Game over with Anna killed! + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventAnnaKilled); + getLogic()->gameOver(kSavegameTypeTime, kTime2250000, kSceneGameOverAnnaDied, true); + } + break; + + // Anna will get killed... + case kAction272177921: + if (getSound()->isBuffered("MUS012")) + getSound()->processEntry("MUS012"); + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAnnaKilled); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(64, Anna, baggage) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAnna); + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAnnaBaggageArgument); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventAnnaBaggageArgument); + + setCallback(2); + setup_savegame(kSavegameTypeTime, (EventIndex)kTimeNone); + break; + + case 2: + params->param1 = getFight()->setup(kFightAnna); + + if (params->param1) + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneNone, params->param1 == Fight::kFightEndLost); + else { + getState()->time += 1800; + + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventAnnaBagagePart2); + } + break; + + case 3: + getAction()->playAnimation(kEventAnnaBagagePart2); + getScenes()->loadSceneFromPosition(kCarBaggage, 96); + + getProgress().field_54 = 0; + getState()->time = kTime2266200; + + setup_function65(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(65, Anna, function65) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothes3; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectOutsideAnnaCompartment, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(1); + setup_function15(kTimeEnd, "NONE"); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(66, Anna, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAnna); + + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothes2; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(67, Anna, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isPlayerPosition(kCarRedSleeping, 46)) { + UPDATE_PARAM_GOTO(params->param4, getState()->timeTicks, 30, label_next); + + getScenes()->loadSceneFromPosition(kCarRedSleeping, 8); + } + + params->param4 = 0; + +label_next: + if (params->param1) { + UPDATE_PARAM(params->param5, getState()->timeTicks, 75); + + params->param1 = 0; + params->param2 = 1; + + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorNormal, getEntities()->isInsideCompartment(kEntityMax, kCarRedSleeping, kPosition_4070) ? kCursorHand : kCursorNormal); + } + + params->param5 = 0; + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + getData()->location = kLocationInsideCompartment; + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAnnaConversation_34); + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorNormal, kCursorNormal); + + if (params->param1) { + setCallback(5); + setup_playSound(getSound()->justAMinuteCath()); + } else { + setCallback(savepoint.action == kActionKnock ? 2 : 3); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getObjects()->update(kObjectOutsideAnnaCompartment, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + getEntities()->drawSequenceLeft(kEntityAnna, "511B"); + break; + + case kActionDrawScene: + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + params->param1 = 0; + params->param2 = 0; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventAnnaConversation_34); + getSound()->playSound(kEntityPlayer, "LIB015"); + getScenes()->loadSceneFromPosition(kCarRedSleeping, 8); + + setup_function68(); + break; + + case 2: + case 3: + setCallback(4); + setup_playSound("ANN1016"); + break; + + case 4: + getObjects()->update(kObject53, kEntityAnna, kObjectLocation1, kCursorTalk, kCursorNormal); + params->param1 = 1; + break; + + case 5: + params->param1 = 0; + params->param2 = 1; + break; + } + break; + + case kAction191001984: + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getData()->inventoryItem = kItemNone; + + setup_function69(); + break; + + case kAction219971920: + params->param3 = 1; + getData()->inventoryItem = kItemInvalid; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(68, Anna, function68) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) { + setCallback(1); + setup_function15(kTime2511900, "NONE"); + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + break; + + case kAction191001984: + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + setup_function69(); + break; + + case kAction201431954: + params->param1 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(69, Anna, function69) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) { + UPDATE_PARAM(params->param2, getState()->time, 4500); + + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_9270; + getData()->location = kLocationOutsideCompartment; + + setup_function70(); + break; + } + + TIME_CHECK_CALLBACK(kTime2535300, params->param3, 4, setup_callbackActionRestaurantOrSalon); + break; + + case kActionDefault: + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case kActionDrawScene: + if (params->param1 && getEntities()->isInsideTrainCar(kEntityPlayer, kCarRedSleeping)) { + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationOutsideCompartment; + + setup_function70(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_callbackActionRestaurantOrSalon(); + break; + + case 2: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(3); + setup_updatePosition("127A", kCarRestaurant, 56); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + getEntities()->drawSequenceLeft(kEntityAnna, "127B"); + getSavePoints()->push(kEntityAnna, kEntityServers1, kAction258136010); + break; + + case 4: + getData()->location = kLocationOutsideCompartment; + + setCallback(5); + setup_updatePosition("127G", kCarRestaurant, 56); + break; + + case 5: + setup_function70(); + break; + } + break; + + case kAction100969180: + getEntities()->clearSequences(kEntityAnna); + params->param1 = 1; + + case kAction122288808: + getEntities()->drawSequenceLeft(kEntityAnna, "127E"); + getSavePoints()->push(kEntityAnna, kEntityAbbot, kAction203073664); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityAnna, "BLANK"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(70, Anna, function70) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function72(kCarRedSleeping, kPosition_4070); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function71(); + break; + + case 2: + getData()->location = kLocationOutsideCompartment; + getEntities()->clearSequences(kEntityAnna); + setup_function73(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(71, Anna, function71) + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getEntities()->exitCompartment(kEntityAnna, kObjectCompartmentF); + getData()->entityPosition = kPosition_4070; + + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityAnna, "625Af"); + + if (getEntities()->isPlayerPosition(kCarRedSleeping, 7) + || getEntities()->isPlayerPosition(kCarRedSleeping, 28) + || getEntities()->isPlayerPosition(kCarRedSleeping, 56)) + getScenes()->loadScene(getScenes()->processIndex(getState()->scene)); + + getEntities()->enterCompartment(kEntityAnna, kObjectCompartmentF); + + getData()->location = kLocationInsideCompartment; + + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_4070) + || getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_4455)) { + getAction()->playAnimation(isNight() ? kEventCathTurningNight : kEventCathTurningDay); + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromObject(kObjectCompartmentF, true); + } + break; + + case kActionDrawScene: + if (!getEvent(kEventAnnaTiredKiss) + && getEntities()->isDistanceBetweenEntities(kEntityPlayer, kEntityAnna, 2000) + && getEntities()->hasValidFrame(kEntityAnna) + && getData()->entityPosition < getEntityData(kEntityPlayer)->entityPosition) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAnnaTiredKiss); + } + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventAnnaTiredKiss); + getScenes()->loadSceneFromPosition(kCarRestaurant, 29); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(72, Anna, function72, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEvent(kEventAnnaTired) || getEntities()->isWalkingOppositeToPlayer(kEntityAnna)) + getData()->inventoryItem = kItemNone; + else + getData()->inventoryItem = kItemInvalid; + + if (getEntities()->updateEntity(kEntityAnna, (CarIndex)params->param1, (EntityPosition)params->param2)) { + getData()->inventoryItem = kItemNone; + CALLBACK_ACTION(); + } + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAnnaTired); + break; + + case kActionDefault: + if (getEntities()->updateEntity(kEntityAnna, (CarIndex)params->param1, (EntityPosition)params->param2)) { + CALLBACK_ACTION(); + } else if (!getEvent(kEventAnnaTired)) + getData()->inventoryItem = kItemInvalid; + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventAnnaTired); + + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + (750 * (getData()->direction == kDirectionUp ? -1 : 1))), getData()->direction == kDirectionUp); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(73, Anna, function73) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param3 == kTimeInvalid || params->param1 >= getState()->time) + break; + + if (params->param2 >= getState()->time) { + if (!((getEntities()->isPlayerInCar(kCarGreenSleeping) || getEntities()->isPlayerInCar(kCarRedSleeping)) && params->param3)) + params->param3 = getState()->time; + + if (params->param3 >= getState()->time) + break; + } + + params->param3 = kTimeInvalid; + + if (!getEntities()->isPlayerInCar(kCarGreenSleeping) && !getEntities()->isPlayerInCar(kCarRedSleeping)) + getSound()->playSound(kEntityPlayer, "BUMP"); + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventTrainHijacked); + break; + + case kActionKnock: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocationNone, kCursorNormal, kCursorNormal); + + setCallback(2); + setup_playSound("LIB012"); + break; + + case kActionOpenDoor: + setCallback(4); + setup_savegame(kSavegameTypeEvent, kEventAnnaKissTrainHijacked); + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getState()->timeDelta = 1; + + params->param1 = getState()->time + 4500; + params->param2 = getState()->time + 9000; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventTrainHijacked); + getSavePoints()->push(kEntityAnna, kEntityChapters, kAction139254416); + break; + + case 2: + setCallback(3); + setup_playSound("Ann4200"); + break; + + case 3: + getObjects()->update(kObjectCompartmentF, kEntityAnna, kObjectLocationNone, kCursorHandKnock, kCursorHand); + break; + + case 4: + getAction()->playAnimation(kEventAnnaKissTrainHijacked); + getSavePoints()->push(kEntityAnna, kEntityChapters, kAction139254416); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(74, Anna, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAnna); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarBaggageRear; + getData()->clothes = kClothes3; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectOutsideAnnaCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(75, Anna, chapter5Handler) + switch (savepoint.action) { + default: + break; + + case kActionCallback: + if (getCallback() == 1) { + if (getProgress().field_C) + getAction()->playAnimation(getEvent(kEventAnnaKissTrainHijacked) ? kEventAnnaBaggageTies2 : kEventAnnaBaggageTies); + else + getAction()->playAnimation(getEvent(kEventAnnaKissTrainHijacked) ? kEventAnnaBaggageTies3 : kEventAnnaBaggageTies4); + + getScenes()->loadSceneFromPosition(kCarBaggage, 8); + setup_function76(); + } + break; + + case kAction272177921: + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAnnaBaggageTies); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(76, Anna, function76) + if (savepoint.action == kAction158480160) + setup_function77(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(77, Anna, function77) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime3645000 && !params->param2) { + params->param2 = 1; + getState()->timeDelta = 0; + } + break; + + case kActionKnock: + case kActionOpenDoor: + getSound()->playSound(kEntityPlayer, savepoint.action == kActionKnock ? "LIB012" : "LIB014"); + + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventAnnaDialogGoToJerusalem); + break; + + case kActionDefault: + getObjects()->update(kObject106, kEntityAnna, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kActionDrawScene: + if (!params->param1 && getEntities()->isInsideTrainCar(kEntityPlayer, kCarBaggage)) { + setCallback(1); + setup_savegame(kSavegameTypeTime, kTimeNone); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + params->param1 = 1; + break; + + case 2: + getObjects()->update(kObject106, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getAction()->playAnimation(kEventAnnaDialogGoToJerusalem); + + getState()->time = kTimeCityConstantinople; + getState()->timeDelta = 0; + + getSavePoints()->push(kEntityAnna, kEntityTatiana, kAction236060709); + + getScenes()->loadSceneFromPosition(kCarBaggage, 97, 1); + + setCallback(3); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case 3: + setup_function78(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(78, Anna, function78) + switch (savepoint.action) { + default: + break; + + case kActionDrawScene: + if ((getEntities()->isInRestaurant(kEntityPlayer) || getEntities()->isInSalon(kEntityPlayer)) && getInventory()->hasItem(kItemFirebird)) { + setup_function80(); + break; + } + + getState()->time = kTimeInvalid2; + + setCallback(getInventory()->get(kItemFirebird)->location == kObjectLocation4 ? 2 : 1); + setup_savegame(kSavegameTypeEvent, getInventory()->get(kItemFirebird)->location == kObjectLocation4 ? kEventKronosHostageAnna : kEventKronosHostageAnnaNoFirebird); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventKronosHostageAnnaNoFirebird); + getLogic()->gameOver(kSavegameTypeEvent2, kEventAugustUnhookCarsBetrayal, kSceneNone, true); + break; + + case 2: + getAction()->playAnimation(kEventKronosHostageAnna); + getScenes()->loadSceneFromPosition(kCarRestaurant, 61); + getSound()->playSound(kEntityAnna, "Mus024", SoundManager::kFlagDefault); + setup_function79(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(79, Anna, function79) + switch (savepoint.action) { + default: + break; + + case kActionEndSound: + getState()->time = kTime5933; + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventKahinaPunch); + break; + + case kActionDrawScene: + if (getEntities()->isInRestaurant(kEntityPlayer) && getInventory()->hasItem(kItemFirebird)) { + setup_function80(); + break; + } + + if (getEntities()->isInSalon(kEntityPlayer) && !getEvent(kEventKahinaPunch)) { + getState()->time = kTime5933; + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventKahinaPunch); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getEntities()->isInSalon(kEntityPlayer)) + getAction()->playAnimation(kEventKahinaPunchSalon); + else if (getEntities()->isInRestaurant(kEntityPlayer)) + getAction()->playAnimation(kEventKahinaPunchRestaurant); + else if (getEntities()->isInKitchen(kEntityPlayer)) + getAction()->playAnimation(kEventKahinaPunchKitchen); + else if (getEntities()->isInBaggageCarEntrance(kEntityPlayer)) + getAction()->playAnimation(kEventKahinaPunchBaggageCarEntrance); + else if (getEntities()->isInsideTrainCar(kEntityPlayer, kCarBaggage)) + getAction()->playAnimation(kEventKahinaPunchBaggageCar); + break; + + case 2: + getAction()->playAnimation(kEventKahinaPunchSalon); + break; + } + + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneNone, true); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(80, Anna, function80) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param1, getState()->timeTicks, 450); + + getSound()->playSound(kEntityPlayer, "Kro5001", SoundManager::kFlagDefault); + break; + + case kActionEndSound: + getSound()->playSound(kEntityPlayer, "Kro5002", SoundManager::kFlagDefault); + getState()->time = kTime4923000; + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventKronosBringFirebird); + break; + + case kActionDefault: + getState()->time = kTime4929300; + + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventKahinaPunch); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getSound()->isBuffered(kEntityAnna)) + getSound()->processEntry(kEntityAnna); + + getAction()->playAnimation(kEventKronosBringFirebird); + getScenes()->loadSceneFromItem(kItemFirebird); + getSound()->playSound(kEntityAnna, "Mus025", SoundManager::kFlagDefault); + break; + + case 2: + getAction()->playAnimation(kEventKahinaPunch); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneNone, true); + break; + + case 3: + getProgress().isEggOpen = true; + + if (getSound()->isBuffered(kEntityAnna)) + getSound()->processEntry(kEntityAnna); + + getAction()->playAnimation(kEventKronosOpenFirebird); + getScenes()->loadSceneFromPosition(kCarRestaurant, 3); + + setup_finalSequence(); + break; + } + break; + + case kAction205294778: + getState()->time = kTime4929300; + + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventKronosOpenFirebird); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(81, Anna, finalSequence) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param1, getState()->timeTicks, 180); + + getSound()->playSound(kEntityTrain, "LIB069"); + getLogic()->gameOver(kSavegameTypeIndex, 2, kSceneNone, true); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventCathCloseEggNoBackground); + getAction()->playAnimation(kEventKronosGiveFirebird); + + if (getInventory()->hasItem(kItemWhistle)) + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverTrainExplosion, true); + else if (getInventory()->get(kItemWhistle)->location == kObjectLocation1) + getLogic()->gameOver(kSavegameTypeEvent2, kEventAnnaDialogGoToJerusalem, kSceneNone, true); + else + getLogic()->gameOver(kSavegameTypeEvent2, kEventAugustUnhookCarsBetrayal, kSceneGameOverTrainExplosion2, true); + break; + + case 2: + getInventory()->removeItem(kItemWhistle); + getLogic()->playFinalSequence(); + break; + } + break; + + case kAction224309120: + getProgress().isEggOpen = false; + getState()->time = kTimeCityConstantinople; + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventKronosGiveFirebird); + break; + + case kActionUseWhistle: + getProgress().isEggOpen = false; + setGlobalTimer(0); + getState()->time = kTimeCityConstantinople; + + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventFinalSequence); + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/anna.h b/engines/lastexpress/entities/anna.h new file mode 100644 index 0000000000..4712f4b262 --- /dev/null +++ b/engines/lastexpress/entities/anna.h @@ -0,0 +1,252 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ANNA_H +#define LASTEXPRESS_ANNA_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Anna : public Entity { +public: + Anna(LastExpressEngine *engine); + ~Anna() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Updates the position + * + * @param sequence1 The sequence to draw + * @param car The car + * @param position The position + */ + DECLARE_FUNCTION_3(updatePosition, const char* sequence1, CarIndex car, Position position) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Call a savepoint (or draw sequence in default case) + * + * @param sequence1 The sequence to draw in the default case + * @param entity The entity + * @param action The action + * @param sequence2 The sequence name for the savepoint + */ + DECLARE_FUNCTION_4(callSavepoint, const char* sequence1, EntityIndex entity, ActionIndex action, const char* sequence2) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Process callback action when somebody is standing in the restaurant or salon. + */ + DECLARE_FUNCTION(callbackActionRestaurantOrSalon) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + DECLARE_FUNCTION(function12) + + /** + * Draws the entity along with another one + * + * @param sequence1 The sequence to draw + * @param sequence2 The sequence to draw for the second entity + * @param entity The EntityIndex of the second entity + */ + DECLARE_FUNCTION_3(draw2, const char* sequence1, const char* sequence2, EntityIndex entity) + + /** + * Updates parameter 2 using ticks value + * + * @param ticks The number of ticks to add + */ + DECLARE_FUNCTION_1(updateFromTicks, uint32 ticks) + + DECLARE_FUNCTION_2(function15, TimeValue timeValue, const char *sequence) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + DECLARE_FUNCTION_2(function17, uint32, uint32) + + DECLARE_FUNCTION_1(function18, TimeValue timeValue) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + DECLARE_FUNCTION(function20) + DECLARE_FUNCTION(function21) + DECLARE_FUNCTION(function22) + DECLARE_FUNCTION(function23) + DECLARE_FUNCTION(function24) + DECLARE_FUNCTION(function25) + DECLARE_FUNCTION(function26) + DECLARE_FUNCTION(function27) + DECLARE_FUNCTION(function28) + DECLARE_FUNCTION(function29) + DECLARE_FUNCTION(function30) + DECLARE_FUNCTION(function31) + DECLARE_FUNCTION(function32) + DECLARE_FUNCTION(function33) + DECLARE_FUNCTION(function34) + DECLARE_FUNCTION(function35) + DECLARE_FUNCTION(function36) + DECLARE_FUNCTION(function37) + DECLARE_FUNCTION(function38) + DECLARE_FUNCTION_2(function39, CarIndex car, EntityPosition entityPosition) + DECLARE_FUNCTION(function40) + DECLARE_FUNCTION(function41) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + DECLARE_FUNCTION_1(function45, bool useAction1) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + DECLARE_FUNCTION(function47) + DECLARE_FUNCTION(function48) + DECLARE_FUNCTION(leaveTableWithAugust) + DECLARE_FUNCTION(function50) + DECLARE_FUNCTION(function51) + DECLARE_FUNCTION(function52) + DECLARE_FUNCTION(function53) + DECLARE_FUNCTION(function54) + DECLARE_FUNCTION(function55) + DECLARE_FUNCTION(function56) + DECLARE_FUNCTION(function57) + DECLARE_FUNCTION(function58) + DECLARE_FUNCTION(function59) + DECLARE_FUNCTION(function60) + DECLARE_FUNCTION(function61) + DECLARE_FUNCTION(function62) + DECLARE_FUNCTION(function63) + DECLARE_FUNCTION(baggage) + DECLARE_FUNCTION(function65) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function68) + DECLARE_FUNCTION(function69) + DECLARE_FUNCTION(function70) + DECLARE_FUNCTION(function71) + DECLARE_FUNCTION_2(function72, CarIndex car, EntityPosition entityPosition) + DECLARE_FUNCTION(function73) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + DECLARE_FUNCTION(function76) + DECLARE_FUNCTION(function77) + DECLARE_FUNCTION(function78) + DECLARE_FUNCTION(function79) + DECLARE_FUNCTION(function80) + DECLARE_FUNCTION(finalSequence) +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_ANNA_H diff --git a/engines/lastexpress/entities/august.cpp b/engines/lastexpress/entities/august.cpp new file mode 100644 index 0000000000..419d716ced --- /dev/null +++ b/engines/lastexpress/entities/august.cpp @@ -0,0 +1,2804 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/august.h" + +#include "lastexpress/entities/alexei.h" +#include "lastexpress/entities/salko.h" +#include "lastexpress/entities/verges.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +August::August(LastExpressEngine *engine) : Entity(engine, kEntityAugust) { + ADD_CALLBACK_FUNCTION(August, reset); + ADD_CALLBACK_FUNCTION(August, updateFromTime); + ADD_CALLBACK_FUNCTION(August, draw); + ADD_CALLBACK_FUNCTION(August, updatePosition); + ADD_CALLBACK_FUNCTION(August, enterExitCompartment); + ADD_CALLBACK_FUNCTION(August, enterExitCompartment2); + ADD_CALLBACK_FUNCTION(August, enterExitCompartment3); + ADD_CALLBACK_FUNCTION(August, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(August, callSavepoint); + ADD_CALLBACK_FUNCTION(August, callSavepointNoDrawing); + ADD_CALLBACK_FUNCTION(August, draw2); + ADD_CALLBACK_FUNCTION(August, playSound); + ADD_CALLBACK_FUNCTION(August, playSound16); + ADD_CALLBACK_FUNCTION(August, callbackActionRestaurantOrSalon); + ADD_CALLBACK_FUNCTION(August, savegame); + ADD_CALLBACK_FUNCTION(August, updateEntity); + ADD_CALLBACK_FUNCTION(August, function17); + ADD_CALLBACK_FUNCTION(August, updateEntity2); + ADD_CALLBACK_FUNCTION(August, function19); + ADD_CALLBACK_FUNCTION(August, function20); + ADD_CALLBACK_FUNCTION(August, function21); + ADD_CALLBACK_FUNCTION(August, chapter1); + ADD_CALLBACK_FUNCTION(August, function23); + ADD_CALLBACK_FUNCTION(August, dinner); + ADD_CALLBACK_FUNCTION(August, chapter1Handler); + ADD_CALLBACK_FUNCTION(August, function26); + ADD_CALLBACK_FUNCTION(August, function27); + ADD_CALLBACK_FUNCTION(August, function28); + ADD_CALLBACK_FUNCTION(August, function29); + ADD_CALLBACK_FUNCTION(August, restaurant); + ADD_CALLBACK_FUNCTION(August, function31); + ADD_CALLBACK_FUNCTION(August, function32); + ADD_CALLBACK_FUNCTION(August, function33); + ADD_CALLBACK_FUNCTION(August, function34); + ADD_CALLBACK_FUNCTION(August, chapter2); + ADD_CALLBACK_FUNCTION(August, chapter2Handler); + ADD_CALLBACK_FUNCTION(August, function37); + ADD_CALLBACK_FUNCTION(August, function38); + ADD_CALLBACK_FUNCTION(August, function39); + ADD_CALLBACK_FUNCTION(August, chapter3); + ADD_CALLBACK_FUNCTION(August, function41); + ADD_CALLBACK_FUNCTION(August, function42); + ADD_CALLBACK_FUNCTION(August, chapter3Handler); + ADD_CALLBACK_FUNCTION(August, function44); + ADD_CALLBACK_FUNCTION(August, function45); + ADD_CALLBACK_FUNCTION(August, function46); + ADD_CALLBACK_FUNCTION(August, function47); + ADD_CALLBACK_FUNCTION(August, function48); + ADD_CALLBACK_FUNCTION(August, function49); + ADD_CALLBACK_FUNCTION(August, function50); + ADD_CALLBACK_FUNCTION(August, function51); + ADD_CALLBACK_FUNCTION(August, function52); + ADD_CALLBACK_FUNCTION(August, function53); + ADD_CALLBACK_FUNCTION(August, function54); + ADD_CALLBACK_FUNCTION(August, function55); + ADD_CALLBACK_FUNCTION(August, function56); + ADD_CALLBACK_FUNCTION(August, chapter4); + ADD_CALLBACK_FUNCTION(August, chapter4Handler); + ADD_CALLBACK_FUNCTION(August, function59); + ADD_CALLBACK_FUNCTION(August, function60); + ADD_CALLBACK_FUNCTION(August, function61); + ADD_CALLBACK_FUNCTION(August, function62); + ADD_CALLBACK_FUNCTION(August, function63); + ADD_CALLBACK_FUNCTION(August, function64); + ADD_CALLBACK_FUNCTION(August, function65); + ADD_CALLBACK_FUNCTION(August, chapter5); + ADD_CALLBACK_FUNCTION(August, chapter5Handler); + ADD_CALLBACK_FUNCTION(August, function68); + ADD_CALLBACK_FUNCTION(August, unhookCars); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, August, reset) + Entity::reset(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(2, August, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(3, August, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SII(4, August, updatePosition, CarIndex, Position) + Entity::updatePosition(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(5, August, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(6, August, enterExitCompartment2, ObjectIndex) + Entity::enterExitCompartment(savepoint, kPosition_6470, kPosition_6130, kCarGreenSleeping, kObjectCompartment3, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(7, August, enterExitCompartment3, ObjectIndex) + if (savepoint.action == kAction4) { + getEntities()->exitCompartment(kEntityAugust, (ObjectIndex)params->param4); + CALLBACK_ACTION(); + return; + } + + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, August, callbackActionOnDirection) + Entity::callbackActionOnDirection(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SIIS(9, August, callSavepoint, EntityIndex, ActionIndex) + Entity::callSavepoint(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_IIS(10, August, callSavepointNoDrawing, EntityIndex, ActionIndex) + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + if (!params->param6) + getSavePoints()->call(kEntityAugust, (EntityIndex)params->param1, (ActionIndex)params->param2, (char *)¶ms->seq); + + CALLBACK_ACTION(); + break; + + case kAction10: + if (!params->param6) { + getSavePoints()->call(kEntityAugust, (EntityIndex)params->param1, (ActionIndex)params->param2, (char *)¶ms->seq); + params->param6 = 1; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SSI(11, August, draw2, EntityIndex) + Entity::draw2(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(12, August, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(13, August, playSound16) + Entity::playSound(savepoint, false, SoundManager::kFlagDefault); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, August, callbackActionRestaurantOrSalon) + Entity::callbackActionRestaurantOrSalon(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(15, August, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(16, August, updateEntity, CarIndex, EntityPosition) + if (savepoint.action == kActionExcuseMeCath) { + getProgress().eventMetAugust ? getSound()->playSound(kEntityPlayer, rnd(2) ? "CAT1002A" : "CAT1002") : getSound()->excuseMeCath(); + return; + } + + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(17, August, function17, TimeValue) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 < getState()->time && !params->param2) { + params->param2 = 1; + CALLBACK_ACTION(); + break; + } + + if (getEntities()->isPlayerInCar(kCarGreenSleeping) || getEntities()->isPlayerInCar(kCarRedSleeping)) { + if (getEntities()->isInsideTrainCar(kEntityPlayer, kCarGreenSleeping)) { + setCallback(2); + setup_updateEntity2(kCarGreenSleeping, kPosition_540); + } else { + setCallback(3); + setup_updateEntity2(kCarRedSleeping, kPosition_9460); + } + } + break; + + case kActionDefault: + ENTITY_PARAM(0, 1) = 0; + + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_540); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (ENTITY_PARAM(0, 1)) { + CALLBACK_ACTION(); + break; + } + + getEntities()->clearSequences(kEntityAugust); + break; + + case 2: + case 3: + if (ENTITY_PARAM(0, 1)) { + CALLBACK_ACTION(); + break; + } + + getEntities()->clearSequences(kEntityAugust); + + setCallback(4); + setup_updateFromTime(450); + break; + + case 4: + setCallback(5); + setup_updateEntity2(kCarRedSleeping, kPosition_540); + break; + + case 5: + if (ENTITY_PARAM(0, 1)) { + CALLBACK_ACTION(); + break; + } + + getEntities()->clearSequences(kEntityAugust); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(18, August, updateEntity2, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->updateEntity(_entityIndex, (CarIndex)params->param1, (EntityPosition)params->param2)) { + CALLBACK_ACTION(); + } else if (getEntities()->isDistanceBetweenEntities(kEntityAugust, kEntityPlayer, 1000) + && !getEntities()->isInGreenCarEntrance(kEntityPlayer) + && !getEntities()->isInsideCompartments(kEntityPlayer) + && !getEntities()->checkFields10(kEntityPlayer)) { + + if (getData()->car == kCarGreenSleeping || getData()->car == kCarRedSleeping) { + ENTITY_PARAM(0, 1) = 1; + CALLBACK_ACTION(); + } + } + break; + + case kActionDefault: + if (getEntities()->updateEntity(_entityIndex, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(19, August, function19, bool, bool) + error("August: callback function 19 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(20, August, function20, bool) + // Expose parameters as ISSI and ignore the default exposed parameters + EntityData::EntityParametersISSI *parameters = (EntityData::EntityParametersISSI*)_data->getCurrentParameters(); + + switch (savepoint.action) { + default: + break; + + case kActionDefault: + switch (getProgress().chapter) { + default: + break; + + case kChapter1: + strcpy((char *)¶meters->seq1, "626"); + break; + + case kChapter2: + case kChapter3: + if (getData()->clothes != kClothes2) { + strcpy((char *)¶meters->seq1, "666"); + break; + } + // Fallback to next case + + case kChapter4: + case kChapter5: + strcpy((char *)¶meters->seq1, "696"); + break; + } + + if (params->param1) { + strcpy((char *)¶meters->seq2, Common::String::printf("%s%s", (char *)¶meters->seq1, "Gc").c_str()); + + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + } else { + strcpy((char *)¶meters->seq2, Common::String::printf("%s%s", (char *)¶meters->seq1, "Ec").c_str()); + } + + setCallback(1); + setup_enterExitCompartment((char *)¶meters->seq2, kObjectCompartment3); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: { + getData()->location = kLocationOutsideCompartment; + + Common::String sequence2 = Common::String::printf("%s%s", (char *)¶meters->seq2, "Pc"); + strcpy((char *)¶meters->seq2, (char *)¶meters->seq1); + + getEntities()->drawSequenceLeft(kEntityAugust, sequence2.c_str()); + getEntities()->enterCompartment(kEntityAugust, kObjectCompartment3, true); + + if (getProgress().chapter != kChapter3 || getState()->time >= kTime1998000) { + setCallback(3); + setup_playSound("AUG2095"); + } else { + setCallback(2); + setup_playSound("AUG2094"); + } + } + break; + + case 2: + case 3: + getSavePoints()->push(kEntityAugust, kEntityMertens, kAction269436673); + strcpy((char *)¶meters->seq2, Common::String::printf("%s%s", (char *)¶meters->seq1, "Qc").c_str()); + + getEntities()->drawSequenceLeft(kEntityAugust, (char *)¶meters->seq2); + break; + } + break; + + case kAction69239528: + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getEntities()->exitCompartment(kEntityAugust, kObjectCompartment3, true); + + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(21, August, function21, TimeValue) + error("August: callback function 21 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, August, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject11, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + getData()->entityPosition = kPosition_4691; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + + getProgress().eventMetAugust = false; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(23, August, function23, TimeValue) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getProgress().field_14 == 29 || getProgress().field_14 == 3) { + if (params->param3) { + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_enterExitCompartment("626Ea", kObjectCompartment1); + } else { + getEntities()->exitCompartment(kEntityAugust, kObjectCompartment1, true); + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + CALLBACK_ACTION(); + } + break; + } + + if (!params->param2) { + + if (!CURRENT_PARAMS(1, 3)) + CURRENT_PARAMS(1, 3) = getState()->timeTicks + 45; + + if (CURRENT_PARAMS(1, 3) >= getState()->timeTicks) + break; + + if (!params->param5) { + setCallback(8); + setup_playSound("AUG1002B"); + break; + } + +label_callback_8: + UPDATE_PARAM_PROC(CURRENT_PARAMS(1, 4), getState()->timeTicks, 75) + getEntities()->exitCompartment(kEntityAugust, kObjectCompartment1, true); + + if (getProgress().eventCorpseMovedFromFloor) { + setCallback(9); + setup_enterExitCompartment("626Da", kObjectCompartment1); + } else if (getEntities()->isInsideTrainCar(kEntityPlayer, kCarGreenSleeping)) { + setCallback(10); + setup_enterExitCompartment3("626Da", kObjectCompartment1); + } else { + getScenes()->loadSceneFromPosition(kCarNone, 1); + getObjects()->update(kObjectOutsideTylerCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + setCallback(11); + setup_savegame(kSavegameTypeEvent, kEventAugustFindCorpse); + } + break; + UPDATE_PARAM_PROC_END + +label_callback_9: + if (params->param3 && params->param1 < getState()->time && !CURRENT_PARAMS(1, 5)) { + CURRENT_PARAMS(1, 5) = 1; + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + setCallback(12); + setup_enterExitCompartment("626Ea", kObjectCompartment1); + } + break; + } + + if (!CURRENT_PARAMS(1, 1)) + CURRENT_PARAMS(1, 1) = getState()->timeTicks + 45; + + if (CURRENT_PARAMS(1, 1) >= getState()->timeTicks) + break; + + if (getObjects()->get(kObjectCompartment1).location == kObjectLocation1) { + UPDATE_PARAM(CURRENT_PARAMS(1, 2), getState()->timeTicks, 75); + + getObjects()->update(kObjectCompartment1, kEntityAugust, getObjects()->get(kObjectCompartment1).location, kCursorNormal, kCursorNormal); + + params->param6++; + + switch (params->param6) { + default: + break; + + case 1: + setCallback(5); + setup_playSound("LIB013"); + return; + + case 2: + setCallback(7); + setup_playSound("LIB012"); + return; + + case 3: + params->param8++; + + if (params->param8 >= 3) { + getObjects()->update(kObjectCompartment1, kEntityPlayer, getObjects()->get(kObjectCompartment1).location, kCursorHandKnock, kCursorHand); + CALLBACK_ACTION(); + break; + } + + params->param6 = 0; + } + + getObjects()->update(kObjectCompartment1, kEntityAugust, getObjects()->get(kObjectCompartment1).location, params->param4 ? kCursorNormal : kCursorTalk, kCursorHand); + CURRENT_PARAMS(1, 2) = 0; + } else { + + if (getProgress().eventCorpseMovedFromFloor && getProgress().jacket != kJacketBlood) { + params->param7 = (getObjects()->get(kObjectCompartment1).location2 == kObjectLocation1) ? 8 : 7; + getObjects()->update(kObjectOutsideTylerCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + setCallback(4); + setup_savegame(kSavegameTypeEvent, kEventMeetAugustTylerCompartment); + } else { + getObjects()->update(kObjectOutsideTylerCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventAugustFindCorpse); + } + } + break; + + case kActionKnock: + if (params->param3) { + getObjects()->update(kObjectCompartment1, kEntityAugust, kObjectLocationNone, kCursorNormal, kCursorNormal); + + setCallback(15); + setup_playSound("LIB012"); + } else if (!params->param4) { + getObjects()->update(kObjectCompartment1, kEntityAugust, getObjects()->get(kObjectCompartment1).location, kCursorNormal, kCursorNormal); + + setCallback(17); + setup_playSound("AUG1002A"); + } + break; + + case kActionOpenDoor: + if (getProgress().eventCorpseMovedFromFloor && getProgress().jacket != kJacketBlood) { + if (params->param3) { + getData()->location = kLocationInsideCompartment; + + params->param7 = (getObjects()->get(kObjectCompartment1).location2 == kObjectLocation1) ? kEventMeetAugustHisCompartmentBed : kEventMeetAugustHisCompartment; + } else { + params->param7 = (getObjects()->get(kObjectCompartment1).location2 == kObjectLocation1) ? kEventMeetAugustTylerCompartmentBed : kEventMeetAugustTylerCompartment; + } + + setCallback(14); + setup_savegame(kSavegameTypeEvent, kEventMeetAugustTylerCompartment); + } else { + getObjects()->update(kObjectOutsideTylerCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + setCallback(13); + setup_savegame(kSavegameTypeEvent, kEventAugustFindCorpse); + } + break; + + case kActionDefault: + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_8200) + || getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_7850) + || getEntities()->isOutsideAlexeiWindow()) { + getObjects()->update(kObjectCompartment1, kEntityAugust, getObjects()->get(kObjectCompartment1).location, kCursorNormal, kCursorNormal); + + if (getEntities()->isOutsideAlexeiWindow()) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + + getSound()->playSound(kEntityPlayer, "LIB012"); + + getObjects()->update(kObjectCompartment1, kEntityAugust, getObjects()->get(kObjectCompartment1).location, kCursorTalk, kCursorHand); + + params->param2 = 1; + } else { + setCallback(1); + setup_enterExitCompartment("626Aa", kObjectCompartment1); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityAugust, "626Ba"); + getEntities()->enterCompartment(kEntityAugust, kObjectCompartment1, true); + break; + + case 2: + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + CALLBACK_ACTION(); + break; + + case 3: + getSound()->playSound(kEntityPlayer, "LIB014"); + getAction()->playAnimation(kEventAugustFindCorpse); + if (getEvent(kEventDinerAugustOriginalJacket)) + getLogic()->gameOver(kSavegameTypeEvent2, kEventDinerAugustOriginalJacket, getProgress().eventCorpseFound ? kSceneGameOverStopPolice : kSceneGameOverPolice, true); + else if (getProgress().eventCorpseMovedFromFloor) + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + else + getLogic()->gameOver(kSavegameTypeIndex, 1, getProgress().eventCorpseFound ? kSceneGameOverStopPolice : kSceneGameOverPolice, true); + break; + + case 4: + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getSound()->playSound(kEntityPlayer, "LIB014"); + getEntities()->clearSequences(kEntityAugust); + getData()->location = kLocationInsideCompartment; + + getAction()->playAnimation((EventIndex)params->param7); + getSound()->playSound(kEntityPlayer, "LIB015"); + getProgress().eventMetAugust = true; + getData()->location = kLocationOutsideCompartment; + + getScenes()->loadScene(kScene41); + + CALLBACK_ACTION(); + break; + + case 5: + setCallback(6); + setup_playSound16("AUG1002B"); + break; + + case 6: + case 7: + getObjects()->update(kObjectCompartment1, kEntityAugust, getObjects()->get(kObjectCompartment1).location, params->param4 ? kCursorNormal : kCursorTalk, kCursorHand); + ENTITY_PARAM(1, 2) = 0; + break; + + case 8: + params->param5 = 1; + goto label_callback_8; + + case 9: + params->param3 = 1; + getEntities()->clearSequences(kEntityAugust); + getData()->location = kLocationInsideCompartment; + getObjects()->update(kObjectCompartment1, kEntityAugust, kObjectLocationNone, kCursorHandKnock, kCursorHand); + goto label_callback_9; + + case 10: + getObjects()->update(kObjectOutsideTylerCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + setCallback(11); + setup_savegame(kSavegameTypeEvent, kEventAugustFindCorpse); + break; + + case 11: + getAction()->playAnimation(kEventAugustFindCorpse); + + getLogic()->gameOver(getEvent(kEventDinerAugustOriginalJacket) ? kSavegameTypeEvent2 : kSavegameTypeIndex, + getEvent(kEventDinerAugustOriginalJacket) ? kEventDinerAugustOriginalJacket : 1, + getProgress().eventCorpseFound ? kSceneGameOverStopPolice : kSceneGameOverPolice, + true); + break; + + case 12: + getData()->location = kLocationOutsideCompartment; + CALLBACK_ACTION(); + break; + + case 13: + getSound()->playSound(kEntityPlayer, getObjects()->get(kObjectCompartment1).location == kObjectLocation1 ? "LIB032" : "LIB014"); + getAction()->playAnimation(kEventAugustFindCorpse); + + if (getEvent(kEventDinerAugustOriginalJacket)) + getLogic()->gameOver(kSavegameTypeEvent2, kEventDinerAugustOriginalJacket, getProgress().eventCorpseFound ? kSceneGameOverStopPolice : kSceneGameOverPolice, true); + else if (getProgress().eventCorpseMovedFromFloor) + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + else + getLogic()->gameOver(kSavegameTypeIndex, 1, getProgress().eventCorpseFound ? kSceneGameOverStopPolice : kSceneGameOverPolice, true); + break; + + case 14: + if (!params->param2) + getSound()->playSound(kEntityPlayer, getObjects()->get(kObjectCompartment1).location == kObjectLocation1 ? "LIB032" : "LIB014"); + + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectOutsideTylerCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + getAction()->playAnimation((EventIndex)params->param7); + getProgress().eventMetAugust = true; + getData()->location = kLocationOutsideCompartment; + + getScenes()->loadScene(kScene41); + + CALLBACK_ACTION(); + break; + + case 15: + setCallback(16); + setup_playSound("AUG1128A"); + break; + + case 16: + getObjects()->update(kObjectCompartment1, kEntityAugust, kObjectLocationNone, kCursorHandKnock, kCursorHand); + break; + + case 17: + params->param4 = 1; + getObjects()->update(kObjectCompartment1, kEntityAugust, getObjects()->get(kObjectCompartment1).location, kCursorNormal, kCursorHand); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, August, dinner) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventDinerAugust); + break; + + case kActionCallback: + if (getCallback() == 1) { + + getAction()->playAnimation(getEntities()->isInRestaurant(kEntityAlexei) ? kEventDinerAugustAlexeiBackground : kEventDinerAugust); + getProgress().eventMetAugust = true; + + getScenes()->loadSceneFromPosition(kCarRestaurant, 61); + + CALLBACK_ACTION(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, August, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1 && getProgress().eventCorpseFound) { + getSavePoints()->push(kEntityAugust, kEntityPascale, kAction239072064); + params->param1 = 1; + } + + if (getState()->time > kTime1080000 && !params->param3) { + params->param3 = 1; + + if (!params->param1) { + getSavePoints()->push(kEntityAugust, kEntityPascale, kAction239072064); + params->param1 = 1; + } + } + + if (getState()->time > kTime1093500 && getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getData()->location = kLocationOutsideCompartment; + getData()->inventoryItem = kItemNone; + + setCallback(1); + setup_callSavepoint("010J", kEntityTables3, kActionDrawTablesWithChairs, "010K"); + } + break; + + case kAction1: + params->param2 = 0; + getData()->inventoryItem = kItemNone; + getSavePoints()->push(kEntityAugust, kEntityPascale, kAction191604416); + + if (getProgress().jacket == kJacketGreen) { + setCallback(3); + setup_dinner(); + } else { + setCallback(4); + setup_savegame(kSavegameTypeEvent, kEventDinerAugustOriginalJacket); + } + break; + + case kActionDefault: + getSavePoints()->push(kEntityAugust, kEntityTables3, kAction136455232); + getEntities()->drawSequenceLeft(kEntityAugust, "010B"); + + if (!getProgress().eventMetAugust) + params->param2 = kItemInvalid; + + getData()->inventoryItem = (InventoryItem)params->param2; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityAugust, kEntityServers0, kAction204704037); + getEntities()->drawSequenceRight(kEntityAugust, "803DS"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityAugust); + + setCallback(2); + setup_callbackActionOnDirection(); + break; + + case 2: + setup_function26(); + break; + + case 3: + setup_function28(); + break; + + case 4: + getSavePoints()->push(kEntityAugust, kEntityAlexei, kAction225182640); + getAction()->playAnimation(kEventDinerAugustOriginalJacket); + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocation3, kCursorNormal, kCursorNormal); + + getData()->location = kLocationOutsideCompartment; + + getSavePoints()->push(kEntityAugust, kEntityTables3, kActionDrawTablesWithChairs, "010K"); + getEntities()->drawSequenceRight(kEntityAugust, "010P"); + getScenes()->loadSceneFromPosition(kCarRestaurant, 65); + + setCallback(5); + setup_callbackActionOnDirection(); + break; + + case 5: + getSavePoints()->push(kEntityAugust, kEntityServers0, kAction204704037); + getEntities()->drawSequenceRight(kEntityAugust, "803DS"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityAugust); + + setCallback(6); + setup_callbackActionOnDirection(); + break; + + case 6: + getProgress().field_14 = 2; + + setCallback(7); + setup_updateEntity(kCarGreenSleeping, kPosition_8200); + break; + + case 7: + setCallback(8); + setup_function23(kTimeNone); + break; + + case 8: + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneNone, true); + break; + } + break; + + case kAction168046720: + getData()->inventoryItem = kItemNone; + break; + + case kAction168627977: + getData()->inventoryItem = (InventoryItem)params->param2; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, August, function26) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (getProgress().eventMetAugust || getProgress().field_14) { + setCallback(5); + setup_updateEntity(kCarGreenSleeping, kPosition_6470); + } else { + getProgress().field_14 = 2; + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_8200); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function23((TimeValue)(getState()->time + 13500)); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarGreenSleeping, kPosition_6470); + break; + + case 3: + setCallback(4); + setup_function19(false, false); + break; + + case 4: + if (getProgress().field_14 == 2) + getProgress().field_14 = 0; + + setCallback(7); + setup_function21((TimeValue)(getState()->time + 900)); + break; + + case 5: + setCallback(6); + setup_function19(false, false); + break; + + case 6: + setCallback(7); + setup_function21((TimeValue)(getState()->time + 900)); + break; + + case 7: + setup_function27(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, August, function27) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function20(false); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_draw("803US"); + break; + + case 4: + getEntities()->drawSequenceRight(kEntityAugust, "010A"); + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityAugust); + + setCallback(5); + setup_callSavepointNoDrawing(kEntityTables3, kAction136455232, "BOGUS"); + break; + + case 5: + getData()->location = kLocationInsideCompartment; + setup_function28(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, August, function28) + switch (savepoint.action) { + default: + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + params->param1 = 0; + + setCallback(3); + setup_dinner(); + break; + + case kActionDefault: + if (!getProgress().eventMetAugust && getProgress().jacket == kJacketGreen) + params->param1 = kItemInvalid; + + getEntities()->drawSequenceLeft(kEntityAugust, "010B"); + getSavePoints()->push(kEntityAugust, kEntityServers0, kAction304061224); + getData()->inventoryItem = (InventoryItem)params->param1; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityAugust, kEntityServers0, kAction203859488); + getData()->inventoryItem = (InventoryItem)params->param1; + getEntities()->drawSequenceLeft(kEntityAugust, "010B"); + break; + + case 2: + getSavePoints()->push(kEntityAugust, kEntityServers0, kAction136702400); + getEntities()->drawSequenceLeft(kEntityAugust, "010B"); + setup_function29(); + break; + } + break; + + case kAction168046720: + getData()->inventoryItem = kItemNone; + break; + + case kAction168627977: + getData()->inventoryItem = (InventoryItem)params->param1; + break; + + case kAction170016384: + getData()->inventoryItem = kItemNone; + getEntities()->drawSequenceLeft(kEntityServers0, "BLANK"); + getEntities()->drawSequenceLeft(kEntityAugust, "010G"); + + setCallback(2); + setup_playSound("AUG1053"); + break; + + case kAction268773672: + getData()->inventoryItem = kItemNone; + getEntities()->drawSequenceLeft(kEntityAugust, "010D"); + + setCallback(1); + setup_playSound("AUG1052"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, August, function29) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!getProgress().field_28 || (params->param2 && params->param3 == kTimeInvalid)) + break; + + if (getState()->time < kTime1134000) { + + if (!getEntities()->isInRestaurant(kEntityPlayer) + || getSound()->isBuffered("MRB1076") || getSound()->isBuffered("MRB1078") || getSound()->isBuffered("MRB1078A")) + params->param3 = getState()->time + 225; + + if (params->param3 > getState()->time) + break; + } + + params->param3 = kTimeInvalid; + getData()->inventoryItem = kItemNone; + getProgress().field_28 = 0; + + setup_restaurant(); + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + params->param1 = kItemNone; + + setCallback(1); + setup_dinner(); + break; + + case kActionDefault: + if (!getProgress().eventMetAugust && getProgress().jacket == kJacketGreen) + params->param1 = kItemInvalid; + + getData()->inventoryItem = (InventoryItem)LOBYTE(params->param1); + + getEntities()->drawSequenceLeft(kEntityAugust, "010H"); + break; + + case kAction168046720: + getData()->inventoryItem = kItemNone; + break; + + case kAction168627977: + getData()->inventoryItem = (InventoryItem)LOBYTE(params->param1); + break; + + case kAction189426612: + params->param2 = 1; + break; + + case kAction235257824: + params->param2 = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, August, restaurant) + error("August: callback function 30 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, August, function31) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_6470); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function19(false, false); + break; + + case 2: + setCallback(2); + setup_function21(kTime1161000); + break; + + case 3: + case 4: + if (getProgress().field_14 == 29) { + setCallback(4); + setup_function21((TimeValue)(getState()->time + 900)); + } else { + setup_function32(); + } + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, August, function32) + error("August: callback function 32 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, August, function33) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(getProgress().eventMetAugust ? 1 : 2); + setup_function21(getProgress().eventMetAugust ? (TimeValue)(getState()->time + 9000) : kTimeBedTime); + break; + + case kActionCallback: + if (getCallback() == 1 || getCallback() == 2) + setup_function34(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(34, August, function34) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!getSound()->isBuffered(kEntityAugust) && getProgress().field_18 != 4) + getSound()->playSound(kEntityAugust, "AUG1057"); // August snoring + break; + + case kActionDefault: + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + getEntities()->clearSequences(kEntityAugust); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(35, August, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAugust); + + getData()->entityPosition = kPosition_3970; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothes1; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject11, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(36, August, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_SAVEPOINT(kTime1755000, params->param2, kEntityAugust, kEntityServers0, kAction252568704); + + if (getState()->time > kTime1773000 && params->param1 && getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getData()->inventoryItem = kItemNone; + getData()->location = kLocationOutsideCompartment; + getEntities()->updatePositionEnter(kEntityAugust, kCarRestaurant, 62); + + setCallback(2); + setup_callSavepoint("016C", kEntityTables0, kActionDrawTablesWithChairs, "016D"); + } + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAugustGoodMorning); + break; + + case kActionDefault: + if (!getEvent(kEventAugustGoodMorning)) + getData()->inventoryItem = kItemInvalid; + + getSavePoints()->push(kEntityAugust, kEntityTables0, kAction136455232); + getEntities()->drawSequenceLeft(kEntityAugust, "016B"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventAugustGoodMorning); + getScenes()->loadSceneFromPosition(kCarRestaurant, 61); + break; + + case 2: + getEntities()->updatePositionExit(kEntityAugust, kCarRestaurant, 62); + getEntities()->drawSequenceRight(kEntityAugust, "803ES"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityAugust); + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case 3: + getSavePoints()->push(kEntityAugust, kEntityServers0, kAction286534136); + + setCallback(4); + setup_updateEntity(kCarGreenSleeping, kPosition_6470); + break; + + case 4: + setCallback(5); + setup_function19(true, false); + break; + + case 5: + setup_function37(); + break; + + case 6: + if (!getEvent(kEventAugustGoodMorning)) + getData()->inventoryItem = kItemInvalid; + + getSavePoints()->push(kEntityAugust, kEntityServers0, kAction219522616); + getEntities()->drawSequenceLeft(kEntityAugust, "016B"); + params->param1 = 1; + break; + } + break; + + case kAction123712592: + getEntities()->drawSequenceLeft(kEntityAugust, "016A"); + getData()->inventoryItem = kItemNone; + + setCallback(6); + setup_playSound("AUG2113"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(37, August, function37) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK_1(kTime1791000, params->param2, 5, setup_function20, true); + break; + + case kActionDefault: + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getEntities()->drawSequenceLeft(kEntityAugust, "506A2"); + break; + + case kActionDrawScene: + if (getState()->time > kTime1786500 && getEntities()->isPlayerPosition(kCarGreenSleeping, 43)) { + if (params->param1) { + setCallback(2); + setup_draw("506C2"); + } else { + params->param1 = 1; + + setCallback(1); + setup_draw("506B2"); + } + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 16); + break; + + case 2: + setCallback(3); + setup_function20(true); + break; + + case 3: + case 5: + setCallback(getCallback() == 3 ? 4 : 6); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 4: + case 6: + setup_function38(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(38, August, function38) + error("August: callback function 38 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(39, August, function39) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (!ENTITY_PARAM(0, 1)) + getSound()->playSound(kEntityPlayer, "BUMP"); + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAugustArrivalInMunich); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventAugustArrivalInMunich); + getSavePoints()->push(kEntityAugust, kEntityChapters, kActionChapter3); + getEntities()->clearSequences(kEntityAugust); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(40, August, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAugust); + + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->clothes = kClothes1; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(41, August, function41, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param3 && getEntities()->isDistanceBetweenEntities(kEntityAugust, kEntityPlayer, 2000)) + getData()->inventoryItem = kItemInvalid; + else + getData()->inventoryItem = kItemNone; + + if (getEntities()->updateEntity(kEntityAugust, (CarIndex)params->param1, (EntityPosition)params->param2)) { + CALLBACK_ACTION(); + break; + } + + if (!getEvent(kEventAugustMerchandise) + && getEntities()->isDistanceBetweenEntities(kEntityAugust, kEntityPlayer, 1000) + && !getEntities()->isInsideCompartments(kEntityPlayer) + && !getEntities()->checkFields10(kEntityPlayer)) { + if (getData()->car == kCarGreenSleeping || getData()->car == kCarGreenSleeping) { + getAction()->playAnimation(kEventAugustMerchandise); + + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + (750 * (getData()->direction == kDirectionUp ? -1 : 1))), getData()->direction == kDirectionUp); + } + } + break; + + case kAction1: + params->param3 = kItemNone; + getData()->inventoryItem = kItemNone; + + getAction()->playAnimation((getData()->entityPosition < getEntityData(kEntityPlayer)->entityPosition) ? kEventAugustTalkGoldDay : kEventAugustTalkGold); + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + (750 * (getData()->direction == kDirectionUp ? -1 : 1))), getData()->direction == kDirectionUp); + break; + + case kActionExcuseMeCath: + if (getProgress().eventMetAugust) + getSound()->playSound(kEntityPlayer, rnd(2) ? "CAT1002" : "CAT1002A"); + else + getSound()->excuseMeCath(); + break; + + case kActionExcuseMe: + getSound()->excuseMe(kEntityAugust); + break; + + case kActionDefault: + if (getEntities()->updateEntity(kEntityAugust, (CarIndex)params->param1, (EntityPosition)params->param2)) { + CALLBACK_ACTION(); + break; + } + + if (getEvent(kEventAugustMerchandise) && !getEvent(kEventAugustTalkGold) && !getEvent(kEventAugustTalkGoldDay)) + params->param3 = kItemInvalid; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_III(42, August, function42, CarIndex, EntityPosition, bool) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param4 && getEntities()->isDistanceBetweenEntities(kEntityAugust, kEntityPlayer, 2000)) + getData()->inventoryItem = kItemInvalid; + else + getData()->inventoryItem = kItemNone; + + if (getEntities()->updateEntity(kEntityAugust, (CarIndex)params->param1, (EntityPosition)params->param2)) { + getData()->inventoryItem = kItemNone; + + CALLBACK_ACTION(); + } + break; + + case kAction1: + params->param4 = 0; + getData()->inventoryItem = kItemNone; + + getSound()->playSound(kEntityPlayer, "CAT1002"); + getSound()->playSound(kEntityAugust, getEvent(kEventAugustBringBriefcase) ? "AUG3103" : "AUG3100", SoundManager::kFlagInvalid, 15); + break; + + case kActionExcuseMe: + if (!getSound()->isBuffered(kEntityAugust)) + getSound()->excuseMe(kEntityAugust); + break; + + case kActionDefault: + if (getEntities()->updateEntity(kEntityAugust, (CarIndex)params->param1, (EntityPosition)params->param2)) { + CALLBACK_ACTION(); + break; + } + + if (params->param3) { + params->param4 = 128; + + if (!getEvent(kEventAugustBringBriefcase)) + params->param4 = 147; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(43, August, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_SAVEPOINT(kTime1953000, params->param2, kEntityAugust, kEntityAnna, kAction291662081); + + // Set as same position as Anna + if (params->param1) { + getData()->entityPosition = getEntityData(kEntityAnna)->entityPosition; + getData()->car = getEntityData(kEntityAnna)->car; + } + + if (getState()->time > kTime2016000 && !params->param1) { + if (getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getData()->inventoryItem = kItemNone; + setup_function44(); + } + } + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + + setCallback(6); + setup_savegame(kSavegameTypeEvent, kEventAugustLunch); + break; + + case kActionDefault: + setCallback(1); + setup_function20(true); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function41(kCarRestaurant, kPosition_850); + break; + + case 2: + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_draw("803VS"); + break; + + case 4: + getEntities()->drawSequenceRight(kEntityAugust, "010A2"); + + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityAugust); + + setCallback(5); + setup_callSavepointNoDrawing(kEntityTables3, kAction136455232, "BOGUS"); + break; + + case 5: + getData()->location = kLocationInsideCompartment; + getEntities()->drawSequenceLeft(kEntityAugust, "010B2"); + + if (!getEvent(kEventAugustLunch)) + getData()->inventoryItem = kItemInvalid; + break; + + case 6: + getAction()->playAnimation(kEventAugustLunch); + getScenes()->processScene(); + break; + } + break; + + case kAction122288808: + params->param1 = 0; + getData()->inventoryItem = kItemNone; + getData()->location = kLocationInsideCompartment; + + getEntities()->drawSequenceLeft(kEntityAugust, "112G"); + break; + + case kAction122358304: + params->param1 = 1; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(44, August, function44) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_updatePosition("122H", kCarRestaurant, 57); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getEvent(kEventAugustMerchandise)) { + setCallback(4); + setup_function41(kCarGreenSleeping, kPosition_6470); + } else { + setCallback(2); + setup_function17(kTime2043000); + } + break; + + case 2: + if (!ENTITY_PARAM(0, 1)) { + setCallback(4); + setup_function41(kCarGreenSleeping, kPosition_6470); + } else { + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventAugustMerchandise); + } + break; + + case 3: + getAction()->playAnimation(kEventAugustMerchandise); + if (getData()->car == kCarGreenSleeping && getEntities()->checkDistanceFromPosition(kEntityAugust, kPosition_6470, 500)) + getData()->entityPosition = kPosition_5970; + + getEntities()->updateEntity(kEntityAugust, kCarGreenSleeping, kPosition_6470); + + getEntities()->loadSceneFromEntityPosition(getData()->car, + (EntityPosition)(getData()->entityPosition + 750 * (getData()->direction == kDirectionUp ? -1 : 1)), + getData()->direction == kDirectionUp); + + setCallback(4); + setup_function41(kCarGreenSleeping, kPosition_6470); + break; + + case 4: + setCallback(5); + setup_function19(true, false); + break; + + case 5: + setup_function45(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(45, August, function45) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime2061000 && !params->param1) { + params->param1 = 1; + getData()->inventoryItem = kItemNone; + + setup_function46(); + } + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + getSound()->playSound(kEntityPlayer, "CAT1002"); + getSound()->playSound(kEntityAugust, "AUG3102", SoundManager::kFlagInvalid, 15); + break; + + case kActionDefault: + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getEntities()->drawSequenceLeft(kEntityAugust, "506A2"); + getData()->inventoryItem = kItem146; // TODO which item is that? + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(46, August, function46) + switch (savepoint.action) { + default: + TIME_CHECK_CALLBACK(kTime2088000, params->param1, 1, setup_function47); + break; + + case kActionNone: + break; + + case kActionDrawScene: + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 43)) { + setCallback(2); + setup_draw("507B2"); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setup_function48(); + break; + + case 2: + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 43)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 34); + + getEntities()->clearSequences(kEntityAugust); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(47, August, function47) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function20(true); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function41(kCarGreenSleeping, kPosition_9460); + break; + + case 2: + getEntities()->clearSequences(kEntityAugust); + setCallback(3); + setup_updateFromTime(2700); + break; + + case 3: + setCallback(4); + setup_function41(kCarGreenSleeping, kPosition_6470); + break; + + case 4: + setCallback(5); + setup_function19(false, false); + break; + + case 5: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(48, August, function48) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK(kTimeCityLinz, params->param1, setup_function49); + break; + + case kActionKnock: + case kActionOpenDoor: + if (!getEvent(kEventAugustTalkCompartmentDoor) && !getEvent(kEventAugustTalkCompartmentDoorBlueRedingote) + && !getEvent(kEventAugustBringEgg) && !getEvent(kEventAugustBringBriefcase)) { + + if (savepoint.action == kActionKnock) + getSound()->playSound(kEntityPlayer, "LIB012"); + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAugustTalkCompartmentDoor); + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartment3, kEntityAugust, kObjectLocation1, kCursorHandKnock, kCursorHand); + getData()->clothes = kClothes2; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventAugustTalkCompartmentDoor); + getScenes()->processScene(); + + setCallback(2); + setup_function21(kTimeCityLinz); + break; + + case 2: + setup_function49(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(49, August, function49) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function20(false); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarKronos, kPosition_9270); + break; + + case 2: + setup_function50(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(50, August, function50) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getEntities()->clearSequences(kEntityAugust); + + getData()->entityPosition = kPosition_6000; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarKronos; + break; + + case kAction191668032: + setup_function51(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(51, August, function51) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->car = kCarGreenSleeping; + getData()->entityPosition = kPosition_850; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_function42(kCarGreenSleeping, kPosition_5790, false); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityAugust, kEntityTatiana, kAction191668032); + + setCallback(2); + setup_function42(kCarRedSleeping, kPosition_540, true); + break; + + case 2: + getEntities()->clearSequences(kEntityAugust); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityAugust, "BLANK"); + getSavePoints()->push(kEntityAugust, kEntityAnna, kAction123712592); + break; + } + break; + + case kAction122288808: + setup_function52(); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityAugust, "BLANK"); + break; + + case kAction169032608: + setCallback(3); + setup_function42(kCarRedSleeping, kPosition_3820, true); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(52, August, function52) + switch (savepoint.action) { + default: + break; + + case kActionKnock: + case kActionOpenDoor: + if (getInventory()->hasItem(kItemBriefcase)) { + getData()->location = kLocationInsideCompartment; + if (savepoint.action == kActionKnock) + getSound()->playSound(kEntityPlayer, "LIB012"); + + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventAugustBringBriefcase); + break; + } + + if (getInventory()->hasItem(kItemFirebird) && !getEvent(kEventAugustBringEgg)) { + setCallback(4); + setup_savegame(kSavegameTypeEvent, kEventAugustBringEgg); + break; + } + + if (!getEvent(kEventAugustTalkCompartmentDoorBlueRedingote) && !getEvent(kEventAugustBringEgg) && !getEvent(kEventAugustBringBriefcase)) { + if (savepoint.action == kActionKnock) + getSound()->playSound(kEntityPlayer, "LIB012"); + + setCallback(5); + setup_savegame(kSavegameTypeEvent, kEventAugustBringEgg); + break; + } + + getObjects()->update(kObjectCompartment3, kEntityAugust, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(savepoint.action == kActionKnock ? 6 : 7); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + break; + + case kActionDefault: + setCallback(1); + setup_function42(kCarGreenSleeping, kPosition_6470, true); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function19(false, true); + break; + + case 2: + getObjects()->update(kObjectCompartment3, kEntityAugust, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getSavePoints()->push(kEntityAugust, kEntityKahina, kAction134611040); + break; + + case 3: + getAction()->playAnimation(kEventAugustBringBriefcase); + getSound()->playSound(kEntityPlayer, "LIB015"); + RESET_ENTITY_STATE(kEntitySalko, Salko, setup_function17); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 13); + + setup_function53(); + break; + + case 4: + getAction()->playAnimation(kEventAugustBringEgg); + getScenes()->processScene(); + break; + + case 5: + getAction()->playAnimation(kEventAugustTalkCompartmentDoorBlueRedingote); + getScenes()->processScene(); + break; + + case 6: + case 7: + setCallback(8); + setup_playSound("AUG1128F"); + break; + + case 8: + getObjects()->update(kObjectCompartment3, kEntityAugust, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(53, August, function53) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateFromTime(2700); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function20(false); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 3: + setup_function54(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(54, August, function54) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param4 || params->param2 || getProgress().field_44) + getData()->inventoryItem = kItemNone; + else + getData()->inventoryItem = kItemInvalid; + + if (!params->param2 && params->param1) { + UPDATE_PARAM(params->param5, getState()->time, params->param1); + + getData()->inventoryItem = kItemNone; + setup_function55(); + } + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventAugustTalkCigar); + break; + + case kActionExitCompartment: + getEntities()->updatePositionExit(kEntityAugust, kCarRestaurant, 57); + getEntities()->drawSequenceLeft(kEntityAugust, "105B3"); + break; + + case kActionDefault: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionDrawScene: + if (!getEntities()->isPlayerPosition(kCarRestaurant, 60) || params->param3) { + if (!params->param2 && getEntities()->isPlayerPosition(kCarRestaurant, 57)) + getScenes()->loadSceneFromPosition(kCarRestaurant, 50); + } else if (!params->param2) { + getEntities()->updatePositionEnter(kEntityAugust, kCarRestaurant, 57); + getEntities()->drawSequenceRight(kEntityAugust, "105C3"); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_updatePosition("105A3", kCarRestaurant, 57); + break; + + case 2: + getData()->location = kLocationInsideCompartment; + getSavePoints()->push(kEntityAugust, kEntityAbbot, kAction123712592); + getEntities()->drawSequenceLeft(kEntityAugust, "105B3"); + params->param4 = 1; + break; + + case 3: + getAction()->playAnimation(kEventAugustTalkCigar); + getEntities()->drawSequenceLeft(kEntityAugust, params->param3 ? "122B" : "105B3"); + getScenes()->processScene(); + + params->param1 = 9000; + params->param4 = 0; + break; + } + break; + + case kAction122288808: + getEntities()->drawSequenceLeft(kEntityAugust, "122B"); + params->param2 = 0; + + if (getEvent(kEventAugustTalkCigar)) + params->param1 = 9000; + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityAugust, "BLANK"); + params->param2 = 1; + params->param3 = 1; + break; + + case kAction136196244: + params->param2 = 1; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(55, August, function55) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_updatePosition("105D3", kCarRestaurant, 57); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarGreenSleeping, kPosition_6470); + break; + + case 3: + setCallback(4); + setup_function19(true, false); + break; + + case 4: + setup_function56(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(56, August, function56) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getEntities()->drawSequenceLeft(kEntityAugust, "507A3"); + break; + + case kActionDrawScene: + if (!params->param1 && getEntities()->isPlayerPosition(kCarGreenSleeping, 43)) { + setCallback(1); + setup_draw("507B3"); + } + break; + + case kActionCallback: + if (getCallback() == 1) { + params->param1 = 1; + getEntities()->drawSequenceLeft(kEntityAugust, "507A3"); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(57, August, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAugust); + + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->clothes = kClothes2; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(58, August, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function20(false); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_draw("803WS"); + break; + + case 4: + getEntities()->drawSequenceRight(kEntityAugust, "010A3"); + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityAugust); + + setCallback(5); + setup_callSavepointNoDrawing(kEntityTables3, kAction136455232, "BOGUS"); + break; + + case 5: + getData()->location = kLocationInsideCompartment; + setup_function59(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(59, August, function59) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAugust, "010B3"); + getSavePoints()->push(kEntityAugust, kEntityPascale, kAction190605184); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityAugust, "BLANK"); + break; + + case kAction123793792: + setup_function60(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(60, August, function60) + switch (savepoint.action) { + default: + break; + + case kActionNone: { + bool pushSavepoint = false; + if (!params->param2) { + pushSavepoint = true; + params->param2 = getState()->time + 450; + } + + if (params->param2 < getState()->time) { + pushSavepoint = true; + params->param2 = kTimeInvalid; + } + + if (pushSavepoint) + getSavePoints()->push(kEntityAugust, kEntityServers0, kAction207330561); + + if (!params->param1) + break; + + UPDATE_PARAM(params->param3, getState()->time, 9000); + + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + } + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAugust, "010B3"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_callSavepoint("010J3", kEntityTables3, kActionDrawTablesWithChairs, "010M"); + break; + + case 2: + getSavePoints()->push(kEntityAugust, kEntityServers0, kAction286403504); + setup_function61(); + break; + } + break; + + case kAction122288808: + getEntities()->drawSequenceLeft(kEntityAugust, "010B3"); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityAugust, "BLANK"); + break; + + case kAction201964801: + getEntities()->drawSequenceLeft(kEntityAugust, "010H3"); + params->param1 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(61, August, function61) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->location = kLocationOutsideCompartment; + getEntities()->drawSequenceRight(kEntityAugust, "803FS"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityAugust); + + setCallback(1); + setup_callbackActionOnDirection(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_6470); + break; + + case 2: + setCallback(3); + setup_function19(false, false); + break; + + case 3: + setCallback(4); + setup_function21((TimeValue)(getState()->time + 4500)); + break; + + case 4: + setup_function62(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(62, August, function62) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param1, getState()->time, 900); + + getSound()->playSound(kEntityAugust, "Aug4003A"); + + setCallback(5); + setup_updatePosition("122C", kCarRestaurant, 57); + break; + + case kActionDefault: + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_enterExitCompartment("696Ec", kObjectCompartment3); + break; + + case kActionDrawScene: + if (getEntities()->isPlayerPosition(kCarRestaurant, 57)) + getScenes()->loadSceneFromPosition(kCarRestaurant, 50); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_updatePosition("122A", kCarRestaurant, 57); + break; + + case 4: + getData()->location = kLocationInsideCompartment; + getEntities()->drawSequenceLeft(kEntityAugust, "122B"); + break; + + case 5: + getEntities()->drawSequenceLeft(kEntityAugust, "122B"); + getSavePoints()->push(kEntityAugust, kEntityServers1, kAction291721418); + break; + } + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityAugust, "BLANK"); + break; + + case kAction125826561: + setup_function63(); + break; + + case kAction134486752: + getEntities()->drawSequenceLeft(kEntityAugust, "122B"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(63, August, function63) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM_PROC(params->param3, getState()->time, 1800) + getData()->inventoryItem = kItemInvalid; + UPDATE_PARAM_PROC_END + + if (getState()->time > kTime2488500 && !params->param4) { + params->param4 = 1; + getData()->inventoryItem = kItemNone; + setup_function64(); + break; + } + + UPDATE_PARAM(params->param5, getState()->timeTicks, params->param1); + + params->param2 = (params->param6 < 1 ? 1 : 0); + + getEntities()->drawSequenceLeft(kEntityAugust, params->param2 ? "122H" : "122F"); + + params->param1 = 5 * (3 * rnd(20) + 15); + params->param5 = 0; + break; + + case kAction1: + if (getEntities()->isInSalon(kEntityAlexei)) + RESET_ENTITY_STATE(kEntityAlexei, Alexei, setup_function44); + + getData()->inventoryItem = kItemNone; + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAugustDrink); + break; + + case kActionDefault: + params->param1 = 5 * (3 * rnd(20) + 15); + getEntities()->drawSequenceLeft(kEntityAugust, "122F"); + break; + + case kActionDrawScene: + if (getEntities()->isPlayerPosition(kCarRestaurant, 57)) + getScenes()->loadSceneFromPosition(kCarRestaurant, 50); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventAugustDrink); + getScenes()->loadSceneFromPosition(kCarRestaurant, 55); + + setup_function64(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(64, August, function64) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) + params->param1 = getState()->time + 1800; + + if (params->param1 >= getState()->time) + break; + + if (getState()->time > kTime2430000 && getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_updatePosition("122J", kCarRestaurant, 57); + } + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityAugust, "122H"); + break; + + case kActionDrawScene: + if (getEntities()->isPlayerPosition(kCarRestaurant, 57)) + getScenes()->loadSceneFromPosition(kCarRestaurant, 50); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_6470); + break; + + case 2: + setCallback(3); + setup_enterExitCompartment2("696Dc", kObjectCompartment3); + break; + + case 3: + getEntities()->clearSequences(kEntityAugust); + setup_function65(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(65, August, function65) + switch (savepoint.action) { + default: + break; + + case kActionEndSound: + getSound()->playSound(kEntityAugust, "AUG1057"); // August snoring + break; + + case kActionDefault: + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + getEntities()->clearSequences(kEntityAugust); + + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + if (!getSound()->isBuffered(kEntityAugust)) + getSound()->playSound(kEntityAugust, "AUG1057"); // August snoring + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(66, August, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityAugust); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothes2; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(67, August, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_function68(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(68, August, function68) + error("August: callback function 68 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(69, August, unhookCars) + switch (savepoint.action) { + default: + break; + + case kActionNone: + getSavePoints()->pushAll(kEntityAugust, kAction135800432); + setup_nullfunction(); + break; + + case kActionDefault: + getSound()->processEntries(); + if (getSound()->isBuffered("ARRIVE")) + getSound()->removeFromQueue("ARRIVE"); + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventAugustUnhookCarsBetrayal); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(getProgress().field_C ? kEventAugustUnhookCarsBetrayal : kEventAugustUnhookCars); + getEntities()->clearSequences(kEntityAugust); + getSound()->resetState(); + getSound()->playSound(kEntityPlayer, "MUS050"); + getScenes()->loadSceneFromPosition(kCarRestaurant, 85, 1); + getSavePoints()->pushAll(kEntityAugust, kActionProceedChapter5); + + RESET_ENTITY_STATE(kEntityVerges, Verges, setup_function42) + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(70, August) + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/august.h b/engines/lastexpress/entities/august.h new file mode 100644 index 0000000000..e684fc0361 --- /dev/null +++ b/engines/lastexpress/entities/august.h @@ -0,0 +1,275 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_AUGUST_H +#define LASTEXPRESS_AUGUST_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class August : public Entity { +public: + August(LastExpressEngine *engine); + ~August() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Updates the position + * + * @param sequence1 The sequence to draw + * @param car The car + * @param position The position + */ + DECLARE_FUNCTION_3(updatePosition, const char* sequence1, CarIndex car, Position position) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment and updates position/play animation + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment2, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment3, const char* sequence, ObjectIndex compartment) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Call a savepoint (or draw sequence in default case) + * + * @param sequence1 The sequence to draw in the default case + * @param entity The entity + * @param action The action + * @param sequence2 The sequence name for the savepoint + */ + DECLARE_FUNCTION_4(callSavepoint, const char* sequence1, EntityIndex entity, ActionIndex action, const char* sequence2) + + /** + * Call a savepoint + * + * @param param1 The entity + * @param param2 The action + * @param seq The sequence name for the savepoint + */ + DECLARE_FUNCTION_3(callSavepointNoDrawing, EntityIndex entity, ActionIndex action, const char* sequence) + + /** + * Draws the entity along with another one + * + * @param sequence1 The sequence to draw + * @param sequence2 The sequence to draw for the second entity + * @param entity The EntityIndex of the second entity + */ + DECLARE_FUNCTION_3(draw2, const char* sequence1, const char* sequence2, EntityIndex entity) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound16, const char* filename) + + /** + * Process callback action when somebody is standing in the restaurant or salon. + */ + DECLARE_FUNCTION(callbackActionRestaurantOrSalon) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION_1(function17, TimeValue timeValue) + + /** + * Updates the entity + * + * @param param1 The car + * @param param2 The entity position + */ + DECLARE_FUNCTION_2(updateEntity2, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION_2(function19, bool, bool) + + DECLARE_FUNCTION_1(function20, bool) + DECLARE_FUNCTION_1(function21, TimeValue timeValue) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + DECLARE_FUNCTION_1(function23, TimeValue timeValue) + DECLARE_FUNCTION(dinner) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function26) + DECLARE_FUNCTION(function27) + DECLARE_FUNCTION(function28) + DECLARE_FUNCTION(function29) + DECLARE_FUNCTION(restaurant) + DECLARE_FUNCTION(function31) + DECLARE_FUNCTION(function32) + DECLARE_FUNCTION(function33) + DECLARE_FUNCTION(function34) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + DECLARE_FUNCTION(function37) + DECLARE_FUNCTION(function38) + DECLARE_FUNCTION(function39) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + DECLARE_FUNCTION_2(function41, CarIndex car, EntityPosition entityPosition) + DECLARE_FUNCTION_3(function42, CarIndex car, EntityPosition entityPosition, bool) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + DECLARE_FUNCTION(function44) + DECLARE_FUNCTION(function45) + DECLARE_FUNCTION(function46) + DECLARE_FUNCTION(function47) + DECLARE_FUNCTION(function48) + DECLARE_FUNCTION(function49) + DECLARE_FUNCTION(function50) + DECLARE_FUNCTION(function51) + DECLARE_FUNCTION(function52) + DECLARE_FUNCTION(function53) + DECLARE_FUNCTION(function54) + DECLARE_FUNCTION(function55) + DECLARE_FUNCTION(function56) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function59) + DECLARE_FUNCTION(function60) + DECLARE_FUNCTION(function61) + DECLARE_FUNCTION(function62) + DECLARE_FUNCTION(function63) + DECLARE_FUNCTION(function64) + DECLARE_FUNCTION(function65) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function68) + DECLARE_FUNCTION(unhookCars) + + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_AUGUST_H diff --git a/engines/lastexpress/entities/boutarel.cpp b/engines/lastexpress/entities/boutarel.cpp new file mode 100644 index 0000000000..411456c5c8 --- /dev/null +++ b/engines/lastexpress/entities/boutarel.cpp @@ -0,0 +1,1260 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/boutarel.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +Boutarel::Boutarel(LastExpressEngine *engine) : Entity(engine, kEntityBoutarel) { + ADD_CALLBACK_FUNCTION(Boutarel, reset); + ADD_CALLBACK_FUNCTION(Boutarel, playSound); + ADD_CALLBACK_FUNCTION(Boutarel, draw); + ADD_CALLBACK_FUNCTION(Boutarel, updateFromTime); + ADD_CALLBACK_FUNCTION(Boutarel, updatePosition); + ADD_CALLBACK_FUNCTION(Boutarel, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Boutarel, enterExitCompartment2); + ADD_CALLBACK_FUNCTION(Boutarel, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Boutarel, callbackActionRestaurantOrSalon); + ADD_CALLBACK_FUNCTION(Boutarel, updateEntity); + ADD_CALLBACK_FUNCTION(Boutarel, function11); + ADD_CALLBACK_FUNCTION(Boutarel, enterTableWithMmeBoutarel); + ADD_CALLBACK_FUNCTION(Boutarel, leaveTableWithMmeBoutarel); + ADD_CALLBACK_FUNCTION(Boutarel, function14); + ADD_CALLBACK_FUNCTION(Boutarel, function15); + ADD_CALLBACK_FUNCTION(Boutarel, function16); + ADD_CALLBACK_FUNCTION(Boutarel, function17); + ADD_CALLBACK_FUNCTION(Boutarel, function18); + ADD_CALLBACK_FUNCTION(Boutarel, chapter1); + ADD_CALLBACK_FUNCTION(Boutarel, function20); + ADD_CALLBACK_FUNCTION(Boutarel, chapter1Handler); + ADD_CALLBACK_FUNCTION(Boutarel, function22); + ADD_CALLBACK_FUNCTION(Boutarel, chapter2); + ADD_CALLBACK_FUNCTION(Boutarel, chapter2Handler); + ADD_CALLBACK_FUNCTION(Boutarel, function25); + ADD_CALLBACK_FUNCTION(Boutarel, chapter3); + ADD_CALLBACK_FUNCTION(Boutarel, chapter3Handler); + ADD_CALLBACK_FUNCTION(Boutarel, function28); + ADD_CALLBACK_FUNCTION(Boutarel, function29); + ADD_CALLBACK_FUNCTION(Boutarel, function30); + ADD_CALLBACK_FUNCTION(Boutarel, chapter4); + ADD_CALLBACK_FUNCTION(Boutarel, chapter4Handler); + ADD_CALLBACK_FUNCTION(Boutarel, function33); + ADD_CALLBACK_FUNCTION(Boutarel, function34); + ADD_CALLBACK_FUNCTION(Boutarel, function35); + ADD_CALLBACK_FUNCTION(Boutarel, chapter5); + ADD_CALLBACK_FUNCTION(Boutarel, chapter5Handler); + ADD_CALLBACK_FUNCTION(Boutarel, function38); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Boutarel, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Boutarel, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(3, Boutarel, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(4, Boutarel, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SII(5, Boutarel, updatePosition, CarIndex, Position) + Entity::updatePosition(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(6, Boutarel, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(7, Boutarel, enterExitCompartment2, ObjectIndex) + Entity::enterExitCompartment(savepoint, kPosition_6470, kPosition_6130, kCarRedSleeping, kObjectCompartmentC, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Boutarel, callbackActionOnDirection) + Entity::callbackActionOnDirection(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Boutarel, callbackActionRestaurantOrSalon) + Entity::callbackActionRestaurantOrSalon(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(10, Boutarel, updateEntity, CarIndex, EntityPosition) + if (savepoint.action == kActionExcuseMeCath) { + + if (getInventory()->hasItem(kItemPassengerList) && getState()->time > kTime1089000) + getSound()->playSound(kEntityPlayer, "CAT1022"); + else + getSound()->excuseMeCath(); + + return; + } + + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(11, Boutarel, function11, bool) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (ENTITY_PARAM(0, 1) && ENTITY_PARAM(0, 2)) { + ENTITY_PARAM(0, 2) = 0; + ENTITY_PARAM(0, 1) = 0; + + setCallback(5); + setup_callbackActionRestaurantOrSalon(); + } + break; + + case kActionDefault: + if (params->param1) { + if (getProgress().chapter == kChapter4) { + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + setCallback(1); + setup_enterExitCompartment("607Hc", kObjectCompartmentC); + } else { + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(2); + setup_enterExitCompartment("607Dc", kObjectCompartmentC); + } + } else { + setCallback(3); + setup_enterExitCompartment("607Bc", kObjectCompartmentC); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 2: + case 3: + if (getCallback() == 2) + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + else + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + // Fallback to next case + + case 1: + getObjects()->update(kObject50, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityBoutarel, kEntityFrancois, kAction101107728); + + setCallback(4); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 4: + getEntities()->clearSequences(kEntityBoutarel); + break; + + case 5: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(6); + setup_draw("812US"); + break; + + case 6: + switch (getProgress().chapter) { + default: + break; + + case kChapter1: + getSound()->playSound(kEntityBoutarel, "MRB1075", SoundManager::kFlagInvalid, 60); + break; + + case kChapter3: + getSound()->playSound(kEntityBoutarel, "MRB3101"); + break; + } + + setCallback(7); + setup_enterTableWithMmeBoutarel(); + break; + + case 7: + getData()->location = kLocationInsideCompartment; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Boutarel, enterTableWithMmeBoutarel) + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getEntities()->clearSequences(kEntityMmeBoutarel); + getSavePoints()->push(kEntityBoutarel, kEntityTables2, kAction136455232); + getData()->location = kLocationInsideCompartment; + + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityTables2, "008A3"); + getEntities()->drawSequenceRight(kEntityMmeBoutarel, "008A2"); + getEntities()->drawSequenceRight(kEntityBoutarel, "008A1"); + + if (getEntities()->isInSalon(kEntityPlayer)) { + getEntities()->updateFrame(kEntityBoutarel); + getEntityData(kEntityMmeBoutarel)->location = getData()->location; + getEntityData(kEntityTables2)->location = getData()->location; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Boutarel, leaveTableWithMmeBoutarel) + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getSavePoints()->push(kEntityBoutarel, kEntityTables2, kActionDrawTablesWithChairs, "008F"); + getEntities()->clearSequences(kEntityMmeBoutarel); + + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityTables2, "008E3"); + getEntities()->drawSequenceRight(kEntityMmeBoutarel, "008E2"); + getEntities()->drawSequenceRight(kEntityBoutarel, "008E1"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(14, Boutarel, function14, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getSound()->playSound(kEntityBoutarel, "MRB1079"); + + setCallback(2); + setup_leaveTableWithMmeBoutarel(); + break; + + case 2: + getSavePoints()->push(kEntityBoutarel, kEntityServers1, kAction326144276); + getEntities()->drawSequenceRight(kEntityBoutarel, "812DS"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityBoutarel); + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case 3: + getEntityData(kEntityFrancois)->entityPosition = kPosition_540; + getEntityData(kEntityMmeBoutarel)->entityPosition = kPosition_540; + getData()->entityPosition = kPosition_540; + + getEntityData(kEntityFrancois)->location = kLocationOutsideCompartment; + getEntityData(kEntityMmeBoutarel)->location = kLocationOutsideCompartment; + + getEntities()->clearSequences(kEntityBoutarel); + getSavePoints()->push(kEntityBoutarel, kEntityMmeBoutarel, kAction100901266); + + setCallback(4); + setup_updateFromTime(450); + break; + + case 4: + getSavePoints()->push(kEntityBoutarel, kEntityFrancois, kAction100901266); + + setCallback(5); + setup_updateFromTime(450); + break; + + case 5: + setCallback(6); + setup_updateEntity(kCarRedSleeping, kPosition_6470); + break; + + case 6: + setCallback(params->param1 ? 7: 8); + setup_enterExitCompartment2(params->param1 ? "607Gc" : "607Ac", kObjectCompartmentC); + break; + + case 7: + case 8: + getEntities()->clearSequences(kEntityBoutarel); + getData()->location = kLocationInsideCompartment; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_IS(15, Boutarel, function15, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (params->param1) + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(params->param1 ? 1 : 2); + setup_enterExitCompartment(params->param1 ? "607Dc" : "607Bc", kObjectCompartmentC); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + setCallback(3); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + setCallback(3); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 3: + setCallback(4); + setup_callbackActionRestaurantOrSalon(); + break; + + case 4: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(5); + setup_updatePosition(params->seq, kCarRestaurant, 52); + break; + + case 5: + getData()->location = kLocationInsideCompartment; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +// Parameters: +// bool +// const char * +IMPLEMENT_FUNCTION_IS(16, Boutarel, function16, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_updatePosition((const char *)¶ms->seq, kCarRestaurant, 52); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_6470); + break; + + case 3: + setCallback(params->param1 ? 4 : 5); + setup_enterExitCompartment2(params->param1 ? "607Gc" : "607Ac", kObjectCompartmentC); + break; + + case 4: + case 5: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityBoutarel); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_IS(17, Boutarel, function17, TimeValue) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK_ACTION(params->param1, params->param6); + + if (params->param5) { + UPDATE_PARAM(params->param7, getState()->timeTicks, 90) + getScenes()->loadSceneFromPosition(kCarRestaurant, 51); + } else { + params->param7 = 0; + } + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityBoutarel, (char *)¶ms->seq); + break; + + case kActionDrawScene: + params->param5 = (getEntities()->isPlayerPosition(kCarRestaurant, 52) ? 1 : 0); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(18, Boutarel, function18, TimeValue) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 < getState()->time && !params->param4) { + params->param4 = 1; + + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + } + + if (params->param2) { + UPDATE_PARAM(params->param5, getState()->timeTicks, 75); + + params->param2 = 0; + params->param3 = 1; + + getObjects()->update(kObjectCompartmentC, kEntityBoutarel, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject50, kEntityBoutarel, kObjectLocation1, kCursorNormal, kCursorNormal); + } + + params->param5 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update(kObjectCompartmentC, kEntityBoutarel, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject50, kEntityBoutarel, kObjectLocation1, kCursorNormal, kCursorNormal); + + if (params->param2) { + if (savepoint.param.intValue == 50) { + setCallback(4); + setup_playSound(getSound()->justAMinuteCath()); + } else if (getInventory()->hasItem(kItemPassengerList)) { + setCallback(5); + setup_playSound(rnd(2) ? "CAT1511" : getSound()->wrongDoorCath()); + } else { + setCallback(6); + setup_playSound(getSound()->wrongDoorCath()); + } + } else { + setCallback(savepoint.action == kActionKnock ? 1 : 2); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentC, kEntityBoutarel, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityBoutarel, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kActionDrawScene: + if (params->param3 || params->param2) { + getObjects()->update(kObjectCompartmentC, kEntityBoutarel, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityBoutarel, kObjectLocation1, kCursorHandKnock, kCursorHand); + + params->param2 = 0; + params->param3 = 0; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + setCallback(3); + setup_playSound(rnd(2) ? "MRB1001" : "MRB1001A"); + break; + + case 3: + getObjects()->update(kObjectCompartmentC, kEntityBoutarel, kObjectLocation1, kCursorTalk, kCursorNormal); + getObjects()->update(kObject50, kEntityBoutarel, kObjectLocation1, kCursorTalk, kCursorNormal); + + params->param2 = 1; + break; + + case 4: + case 5: + case 6: + params->param2 = 0; + params->param3 = 1; + break; + + case 7: + getSavePoints()->push(kEntityBoutarel, kEntityCoudert, kAction123199584); + break; + + } + break; + + case kAction122865568: + getSavePoints()->push(kEntityBoutarel, kEntityCoudert, kAction88652208); + break; + + case kAction221683008: + setCallback(7); + setup_playSound("MRB1001"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Boutarel, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getSavePoints()->addData(kEntityBoutarel, kAction203520448, 0); + getSavePoints()->addData(kEntityBoutarel, kAction237889408, 1); + + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject42, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + getData()->entityPosition = kPosition_1750; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Boutarel, function20) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) + break; + + if (!params->param2) { + UPDATE_PARAM_PROC(params->param3, getState()->time, 4500) + setCallback(3); + setup_playSound("MRB1078A"); + break; + UPDATE_PARAM_PROC_END + } + + TIME_CHECK_CALLBACK_1(kTime1138500, params->param4, 4, setup_function14, false); + break; + + case kActionDefault: + setCallback(1); + setup_function11(false); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityBoutarel, "008B"); + + setCallback(2); + setup_playSound("MRB1076"); + break; + + case 2: + getSavePoints()->push(kEntityBoutarel, kEntityServers1, kAction256200848); + break; + + case 3: + TIME_CHECK_CALLBACK_1(kTime1138500, params->param4, 4, setup_function14, false); + break; + + case 4: + getSavePoints()->push(kEntityBoutarel, kEntityCooks, kAction224849280); + + CALLBACK_ACTION(); + break; + } + break; + + case kAction134466544: + params->param2 = 0; + break; + + case kAction135854206: + params->param2 = 1; + break; + + case kAction168717392: + params->param1 = 1; + getEntities()->drawSequenceLeft(kEntityBoutarel, "008D"); + + if (!params->param2) { + setCallback(5); + setup_playSound("MRB1078"); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Boutarel, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function17(kTime1071000, "101A"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function16(false, "101B"); + break; + + case 2: + setCallback(3); + setup_function18(kTime1102500); + break; + + case 3: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorNormal); + + if (getEntities()->isPlayerPosition(kCarRedSleeping, 54) || getEntities()->isPlayerPosition(kCarRedSleeping, 44)) + getScenes()->loadSceneFromPosition(kCarRedSleeping, 10); + + getEntities()->updatePositionExit(kEntityBoutarel, kCarRedSleeping, 54); + getEntities()->updatePositionExit(kEntityBoutarel, kCarRedSleeping, 44); + + setCallback(4); + setup_playSound("MRB1074"); + break; + + case 4: + getEntities()->updatePositionExit(kEntityBoutarel, kCarRedSleeping, 54); + getEntities()->updatePositionExit(kEntityBoutarel, kCarRedSleeping, 44); + + setCallback(5); + setup_function20(); + break; + + case 5: + setCallback(6); + setup_function18(kTimeEnterChalons); + break; + + case 6: + setCallback(7); + setup_function15(false, "102A"); + break; + + case 7: + setCallback(8); + setup_function17(kTime1183500, "102B"); + break; + + case 8: + setCallback(9); + setup_function16(false, "102C"); + break; + + case 9: + setCallback(10); + setup_function18(kTime1215000); + break; + + case 10: + setup_function22(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Boutarel, function22) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getEntities()->clearSequences(kEntityBoutarel); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Boutarel, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityBoutarel); + + getData()->entityPosition = kPosition_4689; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Boutarel, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK_1(kTime1759500, params->param2, 1, setup_function14, false); + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityBoutarel, "008D"); + break; + + case kActionDrawScene: + if (getEntities()->isInRestaurant(kEntityPlayer) && !params->param1) { + getSound()->playSound(kEntityBoutarel, "MRB2001"); + params->param1 = 1; + } + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function25(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Boutarel, function25) + if (savepoint.action == kActionDefault) { + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getEntities()->drawSequenceLeft(kEntityBoutarel, "510"); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Boutarel, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityBoutarel); + + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Boutarel, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getEntities()->drawSequenceLeft(kEntityBoutarel, "510"); + break; + + case kAction122288808: + setup_function28(); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityBoutarel, "BLANK"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Boutarel, function28) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function11(true); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function29(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Boutarel, function29) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM_PROC(params->param2, getState()->time, 450) + getSavePoints()->push(kEntityBoutarel, kEntityServers1, kAction256200848); + UPDATE_PARAM_PROC_END + + if (!params->param1) + break; + + if (getEntities()->isInRestaurant(kEntityAnna) + && getEntities()->isInRestaurant(kEntityAugust) + && !getSound()->isBuffered(kEntityBoutarel) + && params->param3 != kTimeInvalid) { + + if (getState()->time <= kTime1998000) + if (!getEntities()->isInRestaurant(kEntityPlayer) || !params->param3) + params->param3 = getState()->time + 450; + + if (params->param3 < getState()->time || getState()->time > kTime1998000) { + params->param3 = kTimeInvalid; + + setCallback(1); + setup_playSound("MRB3102"); + break; + } + } + + TIME_CHECK_CALLBACK_1(kTime2002500, params->param4, 1, setup_function14, true); + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityBoutarel, "008B"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + TIME_CHECK_CALLBACK_1(kTime2002500, params->param4, 1, setup_function14, true); + break; + + case 2: + setup_function30(); + break; + } + break; + + case kAction122288808: + getEntities()->drawSequenceLeft(kEntityBoutarel, "008D"); + params->param1 = 1; + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityBoutarel, "BLANK"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Boutarel, function30) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getEntities()->drawSequenceLeft(kEntityBoutarel, "510"); + break; + + case kAction122288808: + getEntities()->drawSequenceLeft(kEntityBoutarel, "510"); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityBoutarel, "BLANK"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, Boutarel, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityBoutarel); + + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Boutarel, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK(kTime2367000, params->param1, setup_function33); + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getEntities()->drawSequenceLeft(kEntityBoutarel, "510"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Boutarel, function33) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) + TIME_CHECK_CALLBACK_1(kTime2389500, params->param2, 3, setup_function14, false); + break; + + case kActionDefault: + setCallback(1); + setup_function11(true); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityBoutarel, "008B"); + + setCallback(2); + setup_updateFromTime(450); + break; + + case 2: + getSavePoints()->push(kEntityBoutarel, kEntityServers1, kAction256200848); + break; + + case 3: + setup_function34(); + break; + } + break; + + case kAction122288808: + params->param1 = 1; + getEntities()->drawSequenceLeft(kEntityBoutarel, "008D"); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityBoutarel, "BLANK"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(34, Boutarel, function34) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK(kTime2470500, params->param1, setup_function35); + + if (getState()->time > kTime2457000 && getEvent(kEventAugustDrink)) { + getSavePoints()->push(kEntityBoutarel, kEntityAbbot, kAction159003408); + + setCallback(1); + setup_function15(false, "102A"); + } + break; + + case kActionDefault: + getSavePoints()->push(kEntityBoutarel, kEntityAbbot, kAction101687594); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function17(kTime2479500, "102B"); + break; + + case 2: + setCallback(3); + setup_function16(false, "102C"); + break; + + case 3: + case 7: + setup_function35(); + break; + + case 4: + case 8: + if (getState()->time >= kTime2470500) { + setup_function35(); + break; + } + + if (getEvent(kEventAugustDrink)) { + setCallback(5); + setup_function15(false, "102A"); + } else { + setCallback(8); + setup_function18((TimeValue)(getState()->time + 900)); + } + break; + + case 5: + setCallback(6); + setup_function17(kTime2479500, "102B"); + break; + + case 6: + setCallback(7); + setup_function16(false, "102C"); + break; + + case 9: + getSavePoints()->push(kEntityBoutarel, kEntityCoudert, kAction123199584); + break; + } + break; + + case kAction122865568: + getSavePoints()->push(kEntityBoutarel, kEntityCoudert, kAction88652208); + break; + + case kAction125039808: + setCallback(4); + setup_function18(kTime2457000); + break; + + case kAction221683008: + setCallback(9); + setup_playSound("Mrb1001"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(35, Boutarel, function35) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getEntities()->clearSequences(kEntityBoutarel); + + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(36, Boutarel, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityBoutarel); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(37, Boutarel, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_function38(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(38, Boutarel, function38) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + break; + + case kAction135800432: + setup_nullfunction(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(39, Boutarel) + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/boutarel.h b/engines/lastexpress/entities/boutarel.h new file mode 100644 index 0000000000..22f8e62b62 --- /dev/null +++ b/engines/lastexpress/entities/boutarel.h @@ -0,0 +1,188 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_BOUTAREL_H +#define LASTEXPRESS_BOUTAREL_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Boutarel : public Entity { +public: + Boutarel(LastExpressEngine *engine); + ~Boutarel() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Updates the position + * + * @param sequence1 The sequence to draw + * @param car The car + * @param position The position + */ + DECLARE_FUNCTION_3(updatePosition, const char* sequence1, CarIndex car, Position position) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment and updates position/play animation + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment2, const char* sequence, ObjectIndex compartment) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Process callback action when somebody is standing in the restaurant or salon. + */ + DECLARE_FUNCTION(callbackActionRestaurantOrSalon) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION_1(function11, bool) + DECLARE_FUNCTION(enterTableWithMmeBoutarel) + DECLARE_FUNCTION(leaveTableWithMmeBoutarel) + DECLARE_FUNCTION_1(function14, bool) + DECLARE_FUNCTION_2(function15, bool, const char *sequence) + DECLARE_FUNCTION_2(function16, bool, const char *sequence) + DECLARE_FUNCTION_2(function17, TimeValue timeValue, const char *sequence) + DECLARE_FUNCTION_1(function18, TimeValue timeValue) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + DECLARE_FUNCTION(function20) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function22) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + DECLARE_FUNCTION(function25) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + DECLARE_FUNCTION(function28) + DECLARE_FUNCTION(function29) + DECLARE_FUNCTION(function30) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function33) + DECLARE_FUNCTION(function34) + DECLARE_FUNCTION(function35) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function38) + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_BOUTAREL_H diff --git a/engines/lastexpress/entities/chapters.cpp b/engines/lastexpress/entities/chapters.cpp new file mode 100644 index 0000000000..1bf3ee3f71 --- /dev/null +++ b/engines/lastexpress/entities/chapters.cpp @@ -0,0 +1,1810 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/chapters.h" + +#include "lastexpress/entities/abbot.h" +#include "lastexpress/entities/alexei.h" +#include "lastexpress/entities/alouan.h" +#include "lastexpress/entities/anna.h" +#include "lastexpress/entities/august.h" +#include "lastexpress/entities/boutarel.h" +#include "lastexpress/entities/coudert.h" +#include "lastexpress/entities/cooks.h" +#include "lastexpress/entities/francois.h" +#include "lastexpress/entities/gendarmes.h" +#include "lastexpress/entities/hadija.h" +#include "lastexpress/entities/ivo.h" +#include "lastexpress/entities/kahina.h" +#include "lastexpress/entities/kronos.h" +#include "lastexpress/entities/mahmud.h" +#include "lastexpress/entities/max.h" +#include "lastexpress/entities/mertens.h" +#include "lastexpress/entities/milos.h" +#include "lastexpress/entities/mmeboutarel.h" +#include "lastexpress/entities/pascale.h" +#include "lastexpress/entities/rebecca.h" +#include "lastexpress/entities/salko.h" +#include "lastexpress/entities/servers0.h" +#include "lastexpress/entities/servers1.h" +#include "lastexpress/entities/sophie.h" +#include "lastexpress/entities/tatiana.h" +#include "lastexpress/entities/vassili.h" +#include "lastexpress/entities/verges.h" +#include "lastexpress/entities/vesna.h" +#include "lastexpress/entities/yasmin.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/menu.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +Chapters::Chapters(LastExpressEngine *engine) : Entity(engine, kEntityChapters) { + ADD_CALLBACK_FUNCTION(Chapters, savegame); + ADD_CALLBACK_FUNCTION(Chapters, enterStation); + ADD_CALLBACK_FUNCTION(Chapters, exitStation); + ADD_CALLBACK_FUNCTION(Chapters, chapter1); + ADD_CALLBACK_FUNCTION(Chapters, resetMainEntities); + ADD_CALLBACK_FUNCTION(Chapters, chapter1End); + ADD_CALLBACK_FUNCTION(Chapters, chapter1Init); + ADD_CALLBACK_FUNCTION(Chapters, chapter1Handler); + ADD_CALLBACK_FUNCTION(Chapters, chapter1Next); + ADD_CALLBACK_FUNCTION(Chapters, chapter2); + ADD_CALLBACK_FUNCTION(Chapters, chapter2Init); + ADD_CALLBACK_FUNCTION(Chapters, chapter2Handler); + ADD_CALLBACK_FUNCTION(Chapters, chapter3); + ADD_CALLBACK_FUNCTION(Chapters, chapter3Init); + ADD_CALLBACK_FUNCTION(Chapters, chapter3Handler); + ADD_CALLBACK_FUNCTION(Chapters, viennaEvents); + ADD_CALLBACK_FUNCTION(Chapters, chapter4); + ADD_CALLBACK_FUNCTION(Chapters, chapter4Init); + ADD_CALLBACK_FUNCTION(Chapters, chapter4Handler); + ADD_CALLBACK_FUNCTION(Chapters, chapter5); + ADD_CALLBACK_FUNCTION(Chapters, chapter4Init); + ADD_CALLBACK_FUNCTION(Chapters, chapter4Handler); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(1, Chapters, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(2, Chapters, enterStation, CityIndex) + enterExitStation(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(3, Chapters, exitStation) + enterExitStation(savepoint, false); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(4, Chapters, chapter1) + if (savepoint.action == kActionDefault) { + getSavePoints()->addData(kEntityChapters, kAction171843264, 0); + setup_chapter1Init(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(5, Chapters, resetMainEntities) + if (savepoint.action != kActionDefault) + return; + + RESET_ENTITY_STATE(kEntityAbbot, Abbot, setup_reset); + RESET_ENTITY_STATE(kEntityAlexei, Alexei, setup_reset); + RESET_ENTITY_STATE(kEntityAlouan, Alouan, setup_reset); + RESET_ENTITY_STATE(kEntityAnna, Anna, setup_reset); + RESET_ENTITY_STATE(kEntityAugust, August, setup_reset); + RESET_ENTITY_STATE(kEntityMertens, Mertens, setup_reset); + RESET_ENTITY_STATE(kEntityCoudert, Coudert, setup_reset); + RESET_ENTITY_STATE(kEntityFrancois, Francois, setup_reset); + RESET_ENTITY_STATE(kEntityHadija, Hadija, setup_reset); + RESET_ENTITY_STATE(kEntityIvo, Ivo, setup_reset); + RESET_ENTITY_STATE(kEntityKahina, Kahina, setup_reset); + RESET_ENTITY_STATE(kEntityMmeBoutarel, MmeBoutarel, setup_reset); + RESET_ENTITY_STATE(kEntityMahmud, Mahmud, setup_reset); + RESET_ENTITY_STATE(kEntityMax, Max, setup_reset); + RESET_ENTITY_STATE(kEntityMilos, Milos, setup_reset); + RESET_ENTITY_STATE(kEntityBoutarel, Boutarel, setup_reset); + RESET_ENTITY_STATE(kEntityGendarmes, Gendarmes, setup_reset); + RESET_ENTITY_STATE(kEntityRebecca, Rebecca, setup_reset); + RESET_ENTITY_STATE(kEntitySalko, Salko, setup_reset); + RESET_ENTITY_STATE(kEntitySophie, Sophie, setup_reset); + RESET_ENTITY_STATE(kEntityTatiana, Tatiana, setup_reset); + RESET_ENTITY_STATE(kEntityVerges, Verges, setup_reset); + RESET_ENTITY_STATE(kEntityVassili, Vassili, setup_reset); + RESET_ENTITY_STATE(kEntityVesna, Vesna, setup_reset); + RESET_ENTITY_STATE(kEntityYasmin, Yasmin, setup_reset); + + CALLBACK_ACTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6,Chapters, chapter1End) + switch (savepoint.action) { + default: + break; + + case kActionEndSound: + getSound()->playSound(kEntityChapters, "MUS0009", SoundManager::kFlagDefault); + break; + + case kActionKnock: + if (!getSound()->isBuffered("LIB012", true)) + getSound()->playSound(kEntityPlayer, "LIB012"); + break; + + case kActionOpenDoor: + if (params->param1) { + getEntities()->clearSequences(kEntityChapters); + getSound()->processEntry(kEntityChapters); + getSound()->playSound(kEntityPlayer, "LIB014"); + getSound()->resetState(); + + ENTITY_PARAM(0, 4) = 7; + + getSound()->playSteam(kCityPolice); + + getAction()->playAnimation(kEventCathDream); + + getState()->timeDelta = 3; + getProgress().field_18 = 1; + getProgress().field_84 = 0; + + getObjects()->update(kObject63, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + getScenes()->loadScene(kScene41); + + CALLBACK_ACTION(); + } else { + getSound()->playSound(kEntityPlayer, "LIB014"); + getSound()->playSound(kEntityPlayer, "LIB015", SoundManager::kFlagDefault, 15); + + if (!getSound()->isBuffered(kEntityChapters)) + getSound()->playSound(kEntityChapters, "MUS009", SoundManager::kFlagDefault); + + getScenes()->loadSceneFromPosition(kCarLocomotive, 38); + + getObjects()->update(kObject63, kEntityChapters, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + params->param1 = 1; + } + break; + + case kActionDefault: + RESET_ENTITY_STATE(kEntityPascale, Pascale, setup_function19); + RESET_ENTITY_STATE(kEntityServers0, Servers0, setup_function22); + RESET_ENTITY_STATE(kEntityServers1, Servers1, setup_function16); + RESET_ENTITY_STATE(kEntityCooks, Cooks, setup_function7); + + RESET_ENTITY_STATE(kEntityMertens, Mertens, setup_function42); + RESET_ENTITY_STATE(kEntityCoudert, Coudert, setup_chapter1Handler); + RESET_ENTITY_STATE(kEntityVerges, Verges, setup_chapter1Handler); + + getSavePoints()->push(kEntityChapters, kEntityMertens, kAction201431954); + getSavePoints()->push(kEntityChapters, kEntityCoudert, kAction201431954); + getSavePoints()->push(kEntityChapters, kEntityVerges, kAction201431954); + + RESET_ENTITY_STATE(kEntityKronos, Kronos, setup_function10); + RESET_ENTITY_STATE(kEntityKahina, Kahina, setup_function13); + RESET_ENTITY_STATE(kEntityAnna, Anna, setup_function34); + RESET_ENTITY_STATE(kEntityAugust, August, setup_function34); + RESET_ENTITY_STATE(kEntityTatiana, Tatiana, setup_function24); + RESET_ENTITY_STATE(kEntityVassili, Vassili, setup_function7); + RESET_ENTITY_STATE(kEntityAlexei, Alexei, setup_function26); + RESET_ENTITY_STATE(kEntityMilos, Milos, setup_function18); + RESET_ENTITY_STATE(kEntityVesna, Vesna, setup_function15); + RESET_ENTITY_STATE(kEntityIvo, Ivo, setup_function17); + RESET_ENTITY_STATE(kEntitySalko, Salko, setup_function11); + RESET_ENTITY_STATE(kEntityFrancois, Francois, setup_function20); + RESET_ENTITY_STATE(kEntityMmeBoutarel, MmeBoutarel, setup_function16); + RESET_ENTITY_STATE(kEntityBoutarel, Boutarel, setup_function22); + RESET_ENTITY_STATE(kEntityRebecca, Rebecca, setup_function27); + RESET_ENTITY_STATE(kEntitySophie, Sophie, setup_function5); + RESET_ENTITY_STATE(kEntityMahmud, Mahmud, setup_resetChapter); + RESET_ENTITY_STATE(kEntityYasmin, Yasmin, setup_function10); + RESET_ENTITY_STATE(kEntityHadija, Hadija, setup_function12); + RESET_ENTITY_STATE(kEntityHadija, Alouan, setup_function12); + + if (ENTITY_PARAM(0, 2) || ENTITY_PARAM(0, 3)) { + getSound()->removeFromQueue(kEntityChapters); + + ENTITY_PARAM(0, 2) = 0; + ENTITY_PARAM(0, 3) = 0; + } + + getSound()->processEntries(); + + if (getSound()->isBuffered("CON1505")) + getSound()->processEntry("CON1505"); + + if (getSound()->isBuffered("AUG1057")) + getSound()->processEntry("AUG1057"); + + if (getSound()->isBuffered("ZFX1005")) + getSound()->processEntry("ZFX1005"); + + if (getSound()->isBuffered("ZFX1006")) + getSound()->processEntry("ZFX1006"); + + if (getSound()->isBuffered("ZFX1007")) + getSound()->processEntry("ZFX1007"); + + if (getSound()->isBuffered("ZFX1007A")) + getSound()->processEntry("ZFX1007A"); + + if (getSound()->isBuffered("ZFX1007B")) + getSound()->processEntry("ZFX1007B"); + + + getSound()->playSound(kEntityPlayer, "MUS008", SoundManager::kFlagDefault); + getInventory()->unselectItem(); + + // FIXME add event pump ? + while (getSound()->isBuffered("MUS008")) + getSound()->updateQueue(); + + getProgress().field_84 = true; + + getScenes()->loadSceneFromPosition(kCarLocomotive, 75); + getInventory()->show(); + + getState()->time = kTime1492200; + getProgress().field_18 = 4; + getState()->timeDelta = 0; + + getObjects()->update(kObject63, kEntityChapters, kObjectLocationNone, kCursorNormal, kCursorHand); + getSavePoints()->push(kEntityChapters, kEntityTrain, kActionTrainStopRunning); + + getProgress().isTrainRunning = false; + + setCallback(1); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case kAction225358684: + params->param2++; + + if (params->param2 >= 3) { + + if (!getSound()->isBuffered("LIB031", true)) + getSound()->playSound(kEntityPlayer, "LIB031"); + + if (params->param2 == 3) { + getData()->car = kCarGreenSleeping; + getEntities()->drawSequenceLeft(kEntityChapters, "JUGL"); + } + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Chapters, chapter1Init) + if (savepoint.action != kActionDefault) + return; + + getProgress().chapter = kChapter1; + getSound()->resetState(); + + getState()->time = kTimeChapter1; + getState()->timeDelta = 0; + getProgress().isTrainRunning = true; + getProgress().portrait = kPortraitOriginal; + getProgress().field_18 = 1; + + // Setup inventory & items location + getInventory()->addItem(kItemTelegram); + getInventory()->addItem(kItemArticle); + + getInventory()->setLocationAndProcess(kItemScarf, kObjectLocation1); + getInventory()->setLocationAndProcess(kItemParchemin, kObjectLocation1); + getInventory()->setLocationAndProcess(kItemGreenJacket, kObjectLocation1); + getInventory()->setLocationAndProcess(kItemCorpse, kObjectLocation1); + getInventory()->setLocationAndProcess(kItemPassengerList, kObjectLocation1); + getInventory()->setLocationAndProcess(kItem5, kObjectLocation1); + getInventory()->setLocationAndProcess(kItem7, kObjectLocation1); + getInventory()->setLocationAndProcess(kItem3, kObjectLocation1); + getInventory()->setLocationAndProcess(kItemMatch, kObjectLocation1); + getInventory()->setLocationAndProcess(kItem22, kObjectLocation1); + getInventory()->setLocationAndProcess(kItemPaper, kObjectLocation1); + + getProgress().field_7C = 1; + + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectOutsideTylerCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + for (uint i = kObjectCompartment1; i <= kObjectCompartment8; i++) { + getObjects()->updateLocation2((ObjectIndex)i, kObjectLocation2); + } + + for (uint i = kObjectCompartmentA; i <= kObjectCompartmentH; i++) { + getObjects()->updateLocation2((ObjectIndex)i, kObjectLocation2); + } + + params->param1 = 40; + + getObjects()->updateLocation2(kObject25, kObjectLocation1); + getObjects()->updateLocation2(kObjectTrainTimeTable, kObjectLocation1); + getObjects()->updateLocation2(kObject98, kObjectLocation1); + getObjects()->updateLocation2(kObjectRestaurantCar, kObjectLocation1); + + getObjects()->update(kObject25, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObjectTrainTimeTable, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObjectRedSleepingCar, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObject28, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObject56, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObject54, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObjectRestaurantCar, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObject59, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObject66, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObject64, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObject65, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObject69, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObject98, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObjectHandleOutsideLeft, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorHandKnock); + getObjects()->update(kObjectHandleOutsideRight, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorHandKnock); + getObjects()->update(kObject101, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + setup_chapter1Handler(); +} + +////////////////////////////////////////////////////////////////////////// +#define PLAY_STEAM() { \ + getSound()->resetState(); \ + getSound()->playSteam((CityIndex)ENTITY_PARAM(0, 4)); \ + ENTITY_PARAM(0, 2) = 0; \ + } + +IMPLEMENT_FUNCTION(8, Chapters, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!getProgress().isTrainRunning || getState()->time >= kTime1458000) + goto label_processStations; + + UPDATE_PARAM_PROC(params->param6, getState()->timeTicks, params->param2) + // Play sound FX + getSound()->playLocomotiveSound(); + + params->param2 = 225 * (4 * rnd(5) + 20); + params->param6 = 0; + UPDATE_PARAM_PROC_END + +label_processStations: + // Process stations + TIME_CHECK_SAVEGAME(kTime1039500, params->param7, 1, kSavegameTypeTime, kTimeNone); + +label_enter_epernay: + // Entering Epernay station + TIME_CHECK_ENTERSTATION(kTimeEnterEpernay, params->param8, 1, "Epernay", kCityEpernay); + +label_exit_epernay: + // Exiting Epernay station + TIME_CHECK_EXITSTATION_2(kTimeExitEpernay, CURRENT_PARAMS(1, 1), params->param4, 3, "Epernay"); + +label_epernay_police: + TIME_CHECK_EXITSTATION_0(params->param5, ENTITY_PARAM(0, 2), 4, "Unschedu"); + +label_enter_chalons: + if (getState()->time > kTimeEnterChalons && !CURRENT_PARAMS(1, 2)) { + CURRENT_PARAMS(1, 2) = 1; + getProgress().field_18 = 2; + } + + // Skip to callback 18 checks + if (params->param1) + goto label_exit_strasbourg; + + // Entering Chalons station + TIME_CHECK_ENTERSTATION(kTimeEnterChalons, CURRENT_PARAMS(1, 3), 5, "Chalons", kCityChalons); + +label_exit_chalons: + // Exiting Chalons station + TIME_CHECK_EXITSTATION(kTimeExitChalons, CURRENT_PARAMS(1, 4), 6, "Chalons"); + +label_enter_barleduc: + // Entering Bar-Le-Duc station + TIME_CHECK_ENTERSTATION(kTimeCityBarLeDuc, CURRENT_PARAMS(1, 5), 7, "BarLeDuc", kCityBarleduc); + +label_exit_barleduc: + // Exiting Bar-Le-Duc station + TIME_CHECK_EXITSTATION(kTimeExitBarLeDuc, CURRENT_PARAMS(1, 6), 8, "BarLeDuc"); + +label_enter_nancy: + if (getState()->time > kTime1260000 && !CURRENT_PARAMS(1, 7)) { + CURRENT_PARAMS(1, 7) = 1; + getState()->timeDelta = 1; + } + + // Entering Nancy station + TIME_CHECK_ENTERSTATION(kTimeCityNancy, CURRENT_PARAMS(1, 8), 9, "Nancy", kCityNancy); + +label_exit_nancy: + // Exiting Nancy station + TIME_CHECK_EXITSTATION(kTimeExitNancy, CURRENT_PARAMS(2, 1), 10, "Nancy"); + +label_enter_luneville: + // Entering Luneville station + TIME_CHECK_ENTERSTATION(kTimeCityLuneville, CURRENT_PARAMS(2, 2), 11, "Luneville", kCityLuneville); + +label_exit_luneville: + // Exiting Luneville station + TIME_CHECK_EXITSTATION(kTimeExitLuneville, CURRENT_PARAMS(2, 3), 12, "Luneville"); + +label_enter_avricourt: + // Entering Avricourt station + TIME_CHECK_ENTERSTATION(kTimeCityAvricourt, CURRENT_PARAMS(2, 4), 13, "Avricourt", kCityAvricourt); + +label_exit_avricourt: + // Exiting Avricourt station + TIME_CHECK_EXITSTATION(kTimeExitAvricourt, CURRENT_PARAMS(2, 5), 14, "Avricourt"); + +label_enter_deutschavricourt: + // Entering Deutsch-Avricourt station + TIME_CHECK_ENTERSTATION(kTimeCityDeutschAvricourt, CURRENT_PARAMS(2, 6), 15, "DeutschA", kCityDeutschAvricourt); + +label_exit_deutschavricourt: + // Exiting Avricourt station + TIME_CHECK_EXITSTATION(kTimeExitDeutschAvricourt, CURRENT_PARAMS(2, 7), 16, "DeutschA"); + +label_enter_strasbourg: + TIME_CHECK_SAVEGAME(kTimeCityStrasbourg, CURRENT_PARAMS(2, 8), 17, kSavegameTypeTime, kTimeNone); + +label_exit_strasbourg: + // Exiting Strasbourg station + TIME_CHECK_EXITSTATION(kTimeExitStrasbourg, CURRENT_PARAMS(3, 1), 19, "Strasbou"); + +label_enter_badenoos: + // Entering Baden Oos station + TIME_CHECK_ENTERSTATION(kTimeCityBadenOos, CURRENT_PARAMS(3, 2), 20, "BadenOos", kCityBadenOos); + +label_exit_badenoos: + // Exiting Baden Oos station + TIME_CHECK_EXITSTATION(kTimeExitBadenOos, CURRENT_PARAMS(3, 3), 21, "BadenOos"); + +label_chapter1_next: + if (getState()->time > kTimeChapter1End3 && ! CURRENT_PARAMS(3, 4)) { + CURRENT_PARAMS(3, 4) = 1; + setup_chapter1Next(); + } + break; + + case kActionEndSound: + if (ENTITY_PARAM(0, 2)) { + + getSavePoints()->push(kEntityChapters, kEntityTrain, kActionTrainStopRunning); + + if (getEntityData(kEntityPlayer)->location != kLocationOutsideTrain) { + PLAY_STEAM(); + break; + } + + if (getEntities()->isOutsideAlexeiWindow()) { + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + PLAY_STEAM(); + break; + } + + if (getEntities()->isOutsideAnnaWindow()) { + getScenes()->loadSceneFromPosition(kCarRedSleeping, 49); + PLAY_STEAM(); + break; + } + + CarIndex car = getEntityData(kEntityPlayer)->car; + if (car < kCarRedSleeping || car > kCarCoalTender) { + if (car < kCarBaggageRear || car > kCarGreenSleeping) { + PLAY_STEAM(); + break; + } + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 98)) { + getSound()->playSound(kEntityPlayer, "LIB015"); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 71); + PLAY_STEAM(); + break; + } + + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 82); + PLAY_STEAM(); + break; + } + + getScenes()->loadSceneFromPosition(kCarRestaurant, 82); + PLAY_STEAM(); + break; + } + + if (ENTITY_PARAM(0, 3)) { + getSound()->resetState(); + ENTITY_PARAM(0, 3) = 0; + + if (params->param4) { + getSavePoints()->push(kEntityChapters, getProgress().field_24 ? kEntityVerges : kEntityMertens, getProgress().field_24 ? kAction168187490 : kAction224122407); + params->param4 = 0; + } + } + break; + + case kActionDefault: + params->param2 = 225 * (4 * rnd(5) + 20); + break; + + case kActionDrawScene: + if (!params->param3) { + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 1)) { + getState()->time = kTimeChapter1; + getState()->timeDelta = 3; + params->param3 = 1; + } + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_enter_epernay; + + case 2: + goto label_exit_epernay; + + case 3: + goto label_epernay_police; + + case 4: + params->param5 = 0; + goto label_enter_chalons; + + case 5: + goto label_exit_chalons; + + case 6: + goto label_enter_barleduc; + + case 7: + goto label_exit_barleduc; + + case 8: + goto label_enter_nancy; + + case 9: + goto label_exit_nancy; + + case 10: + goto label_enter_luneville; + + case 11: + goto label_exit_luneville; + + case 12: + goto label_enter_avricourt; + + case 13: + goto label_exit_avricourt; + + case 14: + goto label_enter_deutschavricourt; + + case 15: + goto label_exit_deutschavricourt; + + case 16: + getState()->time = kTimeEnterStrasbourg; + goto label_enter_strasbourg; + + case 17: + getProgress().field_18 = 1; + setCallback(18); + setup_enterStation("Strasbou", kCityStrasbourg); + break; + + case 18: + goto label_exit_strasbourg; + + case 19: + getState()->timeDelta = 1; + goto label_enter_badenoos; + + case 20: + goto label_exit_badenoos; + + case 21: + goto label_chapter1_next; + + case 22: + params->param5 = 1; + break; + + case 23: + params->param1 = 1; + break; + } + break; + + case kAction169629818: + setCallback(22); + setup_enterStation("Unschedu", kCityPolice); + break; + + case kActionEndChapter: + getProgress().field_18 = 3; + + if (getState()->time >= kTimeChapter1End) { + setup_chapter1Next(); + } else { + setCallback(23); + setup_chapter1End(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Chapters, chapter1Next) + if (savepoint.action == kActionDefault) { + // Reset sound cache + if (ENTITY_PARAM(0, 2) || ENTITY_PARAM(0, 3)) { + getSound()->removeFromQueue(kEntityChapters); + ENTITY_PARAM(0, 2) = 0; + ENTITY_PARAM(0, 3) = 0; + } + + getSound()->playSound(kEntityPlayer, "MUS008", SoundManager::kFlagDefault); + getInventory()->unselectItem(); + + while (getSound()->isBuffered("MUS008")) + getSound()->updateQueue(); + + setup_chapter2(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Chapters, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + // Setup for chapter 2 in case it hasn't been done before + if (getProgress().chapter != kChapter2) { + getProgress().chapter = kChapter2; + getEntities()->setupChapter(kChapter2); + } + + // Set game time & delta + getState()->time = kTimeChapter2; + getState()->timeDelta = 5; + + // Save game + setCallback(1); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case kActionCallback: + if (getCallback() == 1) { + if (!_engine->getResourceManager()->loadArchive(kArchiveCd2)) { + getMenu()->show(false, kSavegameTypeIndex, 0); + return; + } + + // Load scene data + getScenes()->loadSceneDataFile(kArchiveCd2); + setup_chapter2Init(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Chapters, chapter2Init) + if (savepoint.action != kActionDefault) + return; + + getProgress().eventCorpseMovedFromFloor = true; + getProgress().field_18 = 1; + getProgress().isTrainRunning = true; + getProgress().eventCorpseFound = true; + + // Switch to green jacket/portrait + getProgress().jacket = kJacketGreen; + getProgress().portrait = kPortraitGreen; + + // Setup inventory & items location + getInventory()->addItem(kItemGreenJacket); + + getObjects()->update(kObjectHandleOutsideLeft, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorHand); + getObjects()->update(kObjectHandleOutsideRight, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorHand); + + getInventory()->setLocationAndProcess(kItemBeetle, kObjectLocation3); + getInventory()->setLocationAndProcess(kItem3, kObjectLocation1); + + for (uint i = 1; i < 9; i++) { + getObjects()->updateLocation2((ObjectIndex)i, kObjectLocation2); + } + + for (uint i = 33; i < 40; i++) { + getObjects()->updateLocation2((ObjectIndex)i, kObjectLocation2); + } + + params->param1 = 40; + + getSavePoints()->push(kEntityChapters, kEntityTables0, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables1, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables2, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables3, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables4, kActionDrawTablesWithChairs); + + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectOutsideTylerCompartment, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + // Reset sound cache + if (ENTITY_PARAM(0, 2) || ENTITY_PARAM(0, 3)) { + getSound()->removeFromQueue(kEntityChapters); + ENTITY_PARAM(0, 2) = 0; + ENTITY_PARAM(0, 3) = 0; + } + + getAction()->playAnimation(kEventTrainPassing); + + if (getInventory()->hasItem(kItemScarf)) + getScenes()->loadScene(kScene41); + else + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 79); + + setup_chapter2Handler(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Chapters, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!getProgress().isTrainRunning) + break; + + UPDATE_PARAM(params->param2, getState()->timeTicks, params->param1); + + getSound()->playLocomotiveSound(); + + params->param1 = 225 * (4 * rnd(5) + 20); + params->param2 = 0; + break; + + case kActionDefault: + params->param1 = 225 * (4 * rnd(5) + 20); + break; + + case kActionChapter3: + setup_chapter3(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Chapters, chapter3) + if (savepoint.action == kActionDefault) { + // Setup for chapter 3 in case it hasn't been done before + if (getProgress().chapter != kChapter3) { + getProgress().chapter = kChapter3; + getEntities()->setupChapter(kChapter3); + } + + // Set game time & delta + getState()->time = kTimeChapter3; + getState()->timeDelta = 5; + + setup_chapter3Init(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Chapters, chapter3Init) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getSavePoints()->push(kEntityChapters, kEntityTables0, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables1, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables2, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables3, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables4, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables5, kActionDrawTablesWithChairs); + + getProgress().isTrainRunning = true; + + getObjects()->update(kObjectHandleOutsideLeft, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorHand); + getObjects()->update(kObjectHandleOutsideRight, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorHand); + getInventory()->setLocationAndProcess(kItemBriefcase, kObjectLocation1); + getInventory()->setLocationAndProcess(kItem3, kObjectLocation1); + getObjects()->updateLocation2(kObjectCompartment1, kObjectLocation2); + getObjects()->update(kObject107, kEntityPlayer, kObjectLocation3, kCursorKeepValue, kCursorKeepValue); + + if (ENTITY_PARAM(0, 2) || ENTITY_PARAM(0, 3)) { + getSound()->removeFromQueue(kEntityChapters); + ENTITY_PARAM(0, 2) = 0; + ENTITY_PARAM(0, 3) = 0; + } + + getScenes()->loadSceneFromPosition(kCarRestaurant, 60); + getInventory()->show(); + + setCallback(1); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_chapter3Handler(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Chapters, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getProgress().isTrainRunning) { + UPDATE_PARAM_PROC(params->param4, getState()->timeTicks, params->param1) + getSound()->playLocomotiveSound(); + + params->param1 = 225 * (4 * rnd(5) + 20); + params->param4 = 0; + UPDATE_PARAM_PROC_END + } + + UPDATE_PARAM_PROC(params->param5, getState()->timeTicks, params->param2) + switch (rnd(2)) { + default: + break; + + case 0: + getSound()->playSound(kEntityPlayer, "ZFX1008", (SoundManager::FlagType)(rnd(15) + 2)); + break; + + case 1: + getSound()->playSound(kEntityPlayer, "ZFX1009", (SoundManager::FlagType)(rnd(15) + 2)); + break; + } + + params->param2 = 225 * (4 * rnd(6) + 8); + params->param5 = 0; + UPDATE_PARAM_PROC_END + + TIME_CHECK_ENTERSTATION(kTimeEnterSalzbourg, params->param6, 1, "Salzburg", kCitySalzbourg); + +label_callback_1: + TIME_CHECK_EXITSTATION(kTimeExitSalzbourg, params->param7, 2, "Salzburg"); + +label_callback_2: + TIME_CHECK_ENTERSTATION(kTimeEnterAttnangPuchheim, params->param8, 3, "Attnang", kCityAttnangPuchheim); + +label_callback_3: + TIME_CHECK_EXITSTATION(kTimeExitAttnangPuchheim, CURRENT_PARAMS(1, 1), 4, "Attnang"); + +label_callback_4: + TIME_CHECK_ENTERSTATION(kTimeEnterWels, CURRENT_PARAMS(1, 2), 5, "Wels", kCityWels); + +label_callback_5: + TIME_CHECK_EXITSTATION(kTimeEnterWels, CURRENT_PARAMS(1, 3), 6, "Wels"); + +label_callback_6: + TIME_CHECK_ENTERSTATION(kTimeEnterLinz, CURRENT_PARAMS(1, 4), 7, "Linz", kCityLinz); + +label_callback_7: + TIME_CHECK_EXITSTATION(kTimeCityLinz, CURRENT_PARAMS(1, 5), 8, "Linz"); + +label_callback_8: + if (getState()->time > kTime2187000 && !CURRENT_PARAMS(1, 6)) { + CURRENT_PARAMS(1, 6) = 1; + getState()->timeDelta = 5; + } + + TIME_CHECK_ENTERSTATION(kTimeCityVienna, CURRENT_PARAMS(1, 7), 9, "Vienna", kCityVienna); + break; + + case kActionEndSound: + if (ENTITY_PARAM(0, 2)) { + getSavePoints()->push(kEntityChapters, kEntityTrain, kActionTrainStopRunning); + + if (getEntityData(kEntityPlayer)->location == kLocationOutsideTrain) { + + if (getEntities()->isOutsideAlexeiWindow()) { + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + } else if (getEntities()->isOutsideAnnaWindow()) { + getScenes()->loadSceneFromPosition(kCarRedSleeping, 49); + } else { + CarIndex car = getEntityData(kEntityPlayer)->car; + + if (car < kCarRedSleeping || car > kCarCoalTender) { + if (car >= kCarBaggageRear && car <= kCarGreenSleeping) { + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 98)) { + getSound()->playSound(kEntityPlayer, "LIB015"); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 71); + } else { + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 82); + } + } + } else { + getScenes()->loadSceneFromPosition(kCarRestaurant, 82); + } + } + } + + getSound()->resetState(); + getSound()->playSteam((CityIndex)ENTITY_PARAM(0, 4)); + + ENTITY_PARAM(0, 2) = 0; + if (params->param1) + setup_viennaEvents(); + + break; + } + + if (ENTITY_PARAM(0, 3)) { + getSound()->resetState(); + ENTITY_PARAM(0, 3) = 0; + } + break; + + case kActionDefault: + params->param1 = 225 * (4 * rnd(5) + 20); + params->param2 = 225 * (4 * rnd(6) + 8); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback_1; + + case 2: + goto label_callback_2; + + case 3: + goto label_callback_3; + + case 4: + goto label_callback_4; + + case 5: + goto label_callback_5; + + case 6: + goto label_callback_6; + + case 7: + goto label_callback_7; + + case 8: + goto label_callback_8; + + case 9: + params->param3 = 1; + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Chapters, viennaEvents) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventViennaAugustUnloadGuns); + if (getEvent(kEventConcertLeaveWithBriefcase)) + getLogic()->gameOver(kSavegameTypeTime, kTime2187000, kSceneNone, true); + else if (getEvent(kEventCathJumpDownCeiling)) + getLogic()->gameOver(kSavegameTypeEvent, kEventCathJumpDownCeiling, kSceneNone, true); + else + getLogic()->gameOver(kSavegameTypeTime, kTime2155500, kSceneNone, true); + break; + + case 2: + getAction()->playAnimation(kEventViennaKronosFirebird); + if (getEvent(kEventKronosBringEggCeiling)) + getLogic()->gameOver(kSavegameTypeEvent2, kEventKronosBringEggCeiling, kSceneGameOverVienna1, true); + else if (getEvent(kEventKronosBringEgg)) { + if (getEvent(kEventKronosBringEggCeiling)) + getLogic()->gameOver(kSavegameTypeEvent2, kEventKronosBringEggCeiling, kSceneGameOverVienna1, true); + else + getLogic()->gameOver(kSavegameTypeTime, kTime2155500, kSceneGameOverVienna1, true); + } else { + if (getProgress().field_C0) { + if (getEvent(kEventKronosReturnBriefcase)) + getLogic()->gameOver(kSavegameTypeTime, getProgress().field_C0, kSceneGameOverVienna2, true); + else + getLogic()->gameOver(kSavegameTypeTime, kTime2155500, kSceneGameOverVienna2, true); + } else { + if (getEvent(kEventKronosReturnBriefcase)) + getLogic()->gameOver(kSavegameTypeEvent, kEventKronosReturnBriefcase, kSceneGameOverVienna, true); + else + getLogic()->gameOver(kSavegameTypeTime, kTime2155500, kSceneGameOverVienna, true); + } + } + break; + + case 3: + getAction()->playAnimation(kEventVergesAnnaDead); + getLogic()->gameOver(kSavegameTypeTime, kTime2250000, kSceneGameOverAnnaDied, true); + break; + + case 4: + getAction()->playAnimation(kEventViennaContinueGame); + setup_chapter4(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Chapters, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + // Setup for chapter 4 in case it hasn't been done before + if (getProgress().chapter != kChapter4) { + getProgress().chapter = kChapter4; + getEntities()->setupChapter(kChapter4); + } + + // Set game time & delta + getState()->time = kTimeChapter4; + getState()->timeDelta = 5; + + // Save game + setCallback(1); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case kActionCallback: + if (getCallback() == 1) { + if (!_engine->getResourceManager()->loadArchive(kArchiveCd3)) { + getMenu()->show(false, kSavegameTypeIndex, 0); + return; + } + + // Load scene data + getScenes()->loadSceneDataFile(kArchiveCd3); + setup_chapter4Init(); + } + break; + + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Chapters, chapter4Init) + if (savepoint.action != kActionDefault) + return; + + getSound()->processEntries(); + getSound()->resetState(); + + getProgress().isTrainRunning = true; + + getObjects()->update(kObjectHandleOutsideLeft, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorHand); + getObjects()->update(kObjectHandleOutsideRight, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorHand); + + getSavePoints()->push(kEntityChapters, kEntityTrain, kActionTrainStartRunning); + getSavePoints()->push(kEntityChapters, kEntityTables0, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables1, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables2, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables3, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables4, kActionDrawTablesWithChairs); + getSavePoints()->push(kEntityChapters, kEntityTables5, kActionDrawTablesWithChairs); + + getScenes()->loadSceneFromItemPosition(kItem3); + + getInventory()->setLocationAndProcess(kItemBomb, kObjectLocation1); + + if (getInventory()->get(kItemBeetle)->location == kObjectLocation3) + getScenes()->loadSceneFromItemPosition(kItemBeetle); + + getObjects()->updateLocation2(kObject25, kObjectLocation2); + getObjects()->update(kObject107, kEntityPlayer, kObjectLocation3, kCursorKeepValue, kCursorKeepValue); + + if (ENTITY_PARAM(0, 2) || ENTITY_PARAM(0, 3)) { + getSound()->removeFromQueue(kEntityChapters); + ENTITY_PARAM(0, 2) = 0; + ENTITY_PARAM(0, 3) = 0; + } + + if (getInventory()->hasItem(kItemFirebird)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 76); + else + getScenes()->loadSceneFromPosition(kCarRestaurant, 69); + + getInventory()->show(); + setup_chapter4Handler(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Chapters, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getProgress().isTrainRunning) { + UPDATE_PARAM_PROC(params->param6, getState()->timeTicks, params->param4); + getSound()->playLocomotiveSound(); + + params->param4 = 225 * (4 * rnd(5) + 20); + params->param6 = 0; + UPDATE_PARAM_PROC_END + } + + UPDATE_PARAM_PROC(params->param7, getState()->timeTicks, params->param5) + switch (rnd(2)) { + default: + break; + + case 0: + getSound()->playSound(kEntityPlayer, "ZFX1008", (SoundManager::FlagType)(rnd(15) + 2)); + break; + + case 1: + getSound()->playSound(kEntityPlayer, "ZFX1009", (SoundManager::FlagType)(rnd(15) + 2)); + break; + } + + params->param5 = 225 * (4 * rnd(6) + 8); + params->param7 = 0; + UPDATE_PARAM_PROC_END + + TIME_CHECK_ENTERSTATION(kTimeEnterPoszony, params->param8, 1, "Pozsony", kCityPoszony); + +label_exitPozsony: + TIME_CHECK_EXITSTATION(kTimeExitPoszony, CURRENT_PARAMS(1, 1), 2, "Pozsony"); + +label_enterGalanta: + if (getObjects()->get(kObjectCompartment1).location2 == kObjectLocation1) { + if (getState()->time > kTime2403000 && !CURRENT_PARAMS(1, 2)) { + CURRENT_PARAMS(1, 2) = 1; + getProgress().field_18 = 2; + } + } + + if (params->param1) + goto label_callback_4; + + TIME_CHECK_ENTERSTATION(kTimeEnterGalanta, CURRENT_PARAMS(1, 3), 3, "Galanta", kCityGalanta); + +label_exitGalanta: + TIME_CHECK_EXITSTATION(kTimeExitGalanta, CURRENT_PARAMS(1, 4), 4, "Galanta"); + +label_callback_4: + if (getState()->time > kTime2470500 && !CURRENT_PARAMS(1, 5)) { + CURRENT_PARAMS(1, 5) = 1; + + if (getProgress().field_18 == 2) + getState()->timeDelta = 1; + } + + if (getState()->time > kTime2506500 && !CURRENT_PARAMS(1, 6)) { + CURRENT_PARAMS(1, 6) = 1; + + if (getProgress().field_18 == 2) + getProgress().field_18 = 1; + } + + if (getState()->time > kTime2520000 && !CURRENT_PARAMS(1, 7)) { + CURRENT_PARAMS(1, 7) = 1; + + if (!params->param2 && !params->param3) { + setCallback(5); + setup_savegame(kSavegameTypeEvent, kEventTrainExplosionBridge); + } + } + break; + + case kActionEndSound: + if (ENTITY_PARAM(0, 2)) { + + getSavePoints()->push(kEntityChapters, kEntityTrain, kActionTrainStopRunning); + + if (getEntityData(kEntityPlayer)->location != kLocationOutsideTrain) { + PLAY_STEAM(); + break; + } + + if (getEntities()->isOutsideAlexeiWindow()) { + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + PLAY_STEAM(); + break; + } + + if (getEntities()->isOutsideAnnaWindow()) { + getScenes()->loadSceneFromPosition(kCarRedSleeping, 49); + PLAY_STEAM(); + break; + } + + CarIndex car = getEntityData(kEntityPlayer)->car; + if (car < kCarRedSleeping || car > kCarCoalTender) { + if (car < kCarBaggageRear || car > kCarGreenSleeping) { + PLAY_STEAM(); + break; + } + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 98)) { + getSound()->playSound(kEntityPlayer, "LIB015"); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 71); + PLAY_STEAM(); + break; + } + + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 82); + PLAY_STEAM(); + break; + } + + getScenes()->loadSceneFromPosition(kCarRestaurant, 82); + PLAY_STEAM(); + break; + } + + if (ENTITY_PARAM(0, 3)) { + getSound()->resetState(); + ENTITY_PARAM(0, 3) = 0; + } else if (!params->param2 && !params->param3) { + getSound()->playSound(kEntityChapters, "ZFX1001"); + } + break; + + case kActionExitCompartment: + getEntities()->clearSequences(kEntityChapters); + + setCallback(11); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case kActionDefault: + params->param4 = 225 * (4 * rnd(5) + 20); + params->param5 = 225 * (4 * rnd(6) + 8); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_exitPozsony; + + case 2: + goto label_enterGalanta; + + case 3: + goto label_exitGalanta; + + case 4: + goto label_callback_4; + + case 5: + if (getSound()->isBuffered(kEntityChapters)) + getSound()->removeFromQueue(kEntityChapters); + + getAction()->playAnimation(kEventTrainExplosionBridge); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneNone, true); + break; + + case 6: + getSound()->processEntries(); + getAction()->playAnimation(kEventTylerCastleDream); + getSound()->resetState(); + + getProgress().field_18 = 1; + + getScenes()->loadScene(kScene41); + getSavePoints()->push(kEntityChapters, kEntityTatiana, kAction169360385); + + getState()->timeDelta = 1; + getState()->time = kTime2511900; + + getInventory()->setLocationAndProcess(kItem2, kObjectLocation1); + getScenes()->loadSceneFromItemPosition(kItem22); + + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_1500; + getData()->location = kLocationInsideCompartment; + + getSound()->playSound(kEntityChapters, "ZFX1001"); + break; + + case 7: + getAction()->playAnimation(kEventTrainExplosionBridge); + getLogic()->gameOver(kSavegameTypeTime, kTime2430000, kSceneNone, true); + break; + + case 8: + getSound()->playSound(kEntityPlayer, "MUS022"); + + if (getState()->time < kTime2517300) + getState()->time = kTime2517300; + break; + + case 9: + getAction()->playAnimation(kEventCathDefusingBomb); + getScenes()->loadSceneFromPosition(kCarRedSleeping, 73); + break; + + case 10: + getAction()->playAnimation(kEventDefuseBomb); + RESET_ENTITY_STATE(kEntityAbbot, Abbot, setup_function48); + getSavePoints()->push(kEntityChapters, kEntityAnna, kAction191001984); + getSavePoints()->push(kEntityChapters, kEntityCoudert, kAction191001984); + getScenes()->loadSceneFromItemPosition(kItem2); + + getInventory()->get(kItem2)->location = kObjectLocationNone; + params->param2 = 1; + + getScenes()->loadSceneFromPosition(kCarRedSleeping, 2); + break; + + case 11: + getScenes()->loadSceneFromPosition(kCarRedSleeping, 74); + getSound()->playSound(kEntityTrain, "ZFX4001", SoundManager::kFlagDefault); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneNone, true); + break; + } + break; + + case kActionChapter5: + setup_chapter5(); + break; + + case kAction156435676: + getSavePoints()->push(kEntityChapters, kEntityTatiana, kAction169360385); + getSavePoints()->push(kEntityChapters, kEntityCoudert, kAction201431954); + getSavePoints()->push(kEntityChapters, kEntityVerges, kAction201431954); + + getState()->timeDelta = 1; + getState()->time = kTime2511900; + + getInventory()->setLocationAndProcess(kItem2, kObjectLocation1); + + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_1500; + getData()->location = kLocationInsideCompartment; + + getSound()->playSound(kEntityChapters, "ZFX1001"); + break; + + case kAction158610240: + setCallback(8); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case kAction169300225: + if (getState()->time < kTime2519100) + getState()->time = kTime2519100; + + params->param3 = 1; + + getEntities()->drawSequenceRight(kEntityChapters, "BOMB"); + break; + + case kAction190346110: + getProgress().field_18 = 3; + + params->param1 = 1; + + if (ENTITY_PARAM(0, 2) || ENTITY_PARAM(0, 3)) { + getSound()->removeFromQueue(kEntityChapters); + + ENTITY_PARAM(0, 2) = 0; + ENTITY_PARAM(0, 3) = 0; + } + + getSound()->playSound(kEntityPlayer, "MUS008", SoundManager::kFlagDefault); + getInventory()->unselectItem(); + + while (getSound()->isBuffered("MUS008")) + getSound()->updateQueue(); + + if (getInventory()->hasItem(kItemBomb)) { + RESET_ENTITY_STATE(kEntityAlexei, Alexei, setup_function47); + RESET_ENTITY_STATE(kEntityAnna, Anna, setup_function68); + RESET_ENTITY_STATE(kEntityAugust, August, setup_function65); + RESET_ENTITY_STATE(kEntityMertens, Mertens, setup_function48); + RESET_ENTITY_STATE(kEntityCoudert, Coudert, setup_function53); + RESET_ENTITY_STATE(kEntityServers0, Servers0, setup_chapter4Handler); + RESET_ENTITY_STATE(kEntityServers1, Servers1, setup_chapter4Handler); + RESET_ENTITY_STATE(kEntityPascale, Pascale, setup_chapter4Handler); + RESET_ENTITY_STATE(kEntityVerges, Verges, setup_chapter4Handler); + RESET_ENTITY_STATE(kEntityTatiana, Tatiana, setup_function49); + RESET_ENTITY_STATE(kEntityAbbot, Abbot, setup_function44); + RESET_ENTITY_STATE(kEntityMilos, Milos, setup_function32); + RESET_ENTITY_STATE(kEntityVesna, Vesna, setup_function27); + RESET_ENTITY_STATE(kEntityIvo, Ivo, setup_function29); + RESET_ENTITY_STATE(kEntitySalko, Salko, setup_function22); + RESET_ENTITY_STATE(kEntityMmeBoutarel, MmeBoutarel, setup_function25); + RESET_ENTITY_STATE(kEntityBoutarel, Boutarel, setup_function35); + RESET_ENTITY_STATE(kEntityRebecca, Rebecca, setup_function45); + RESET_ENTITY_STATE(kEntitySophie, Sophie, setup_function9); + RESET_ENTITY_STATE(kEntityYasmin, Yasmin, setup_function17); + RESET_ENTITY_STATE(kEntityHadija, Hadija, setup_function19); + RESET_ENTITY_STATE(kEntityAlouan, Alouan, setup_function19); + RESET_ENTITY_STATE(kEntityMax, Max, setup_chapter4Handler); + getSavePoints()->push(kEntityChapters, kEntityAnna, kAction201431954); + getSavePoints()->push(kEntityChapters, kEntityMertens, kAction201431954); + getSavePoints()->push(kEntityChapters, kEntityCoudert, kAction201431954); + getSavePoints()->push(kEntityChapters, kEntityServers0, kAction201431954); + getSavePoints()->push(kEntityChapters, kEntityServers1, kAction201431954); + getSavePoints()->push(kEntityChapters, kEntityPascale, kAction201431954); + getSavePoints()->push(kEntityChapters, kEntityVerges, kAction201431954); + + setCallback(6); + setup_savegame(kSavegameTypeEvent, kEventTylerCastleDream); + } else { + getState()->time = kTime2520000; + + setCallback(7); + setup_savegame(kSavegameTypeEvent, kEventTrainExplosionBridge); + } + break; + + case kAction191001984: + getState()->time = kTime2520000; + + if (getSound()->isBuffered(kEntityChapters)) + getSound()->removeFromQueue(kEntityChapters); + + getEntities()->clearSequences(kEntityChapters); + getInventory()->removeItem(kItemTelegram); + + getState()->timeDelta = 5; + + setCallback(10); + setup_savegame(kSavegameTypeEvent, kEventDefuseBomb); + break; + + case kAction201959744: + if (getSound()->isBuffered(kEntityChapters)) + getSound()->removeFromQueue(kEntityChapters); + + getSound()->playSound(kEntityTrain, "ZFX4001", SoundManager::kFlagDefault); + + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneNone, true); + break; + + case kAction225367984: + setCallback(9); + setup_savegame(kSavegameTypeEvent, kEventCathDefusingBomb); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Chapters, chapter5) + if (savepoint.action == kActionDefault) { + // Setup for chapter 5 in case it hasn't been done before + if (getProgress().chapter != kChapter5) { + getProgress().chapter = kChapter5; + getEntities()->setupChapter(kChapter5); + } + + // Set game time & delta + getState()->time = kTimeChapter5; + getState()->timeDelta = 2; + + setup_chapter5Init(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Chapters, chapter5Init) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityTables0); + getEntities()->clearSequences(kEntityTables1); + getEntities()->clearSequences(kEntityTables2); + getEntities()->clearSequences(kEntityTables3); + getEntities()->clearSequences(kEntityTables4); + getEntities()->clearSequences(kEntityTables5); + + getProgress().isTrainRunning = true; + + getObjects()->update(kObjectHandleOutsideLeft, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorHand); + getObjects()->update(kObjectHandleOutsideRight, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorHand); + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment4, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment5, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment6, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment7, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment8, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartmentC, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartmentG, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectHandleBathroom, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectHandleInsideBathroom, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectKitchen, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject20, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject21, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject22, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject48, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject50, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject51, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectHandleOutsideLeft, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectHandleOutsideRight, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + getProgress().field_18 = 1; + getProgress().field_84 = 1; + getProgress().portrait = kCursorPortraitYellow; + + getInventory()->unselectItem(); + + getInventory()->removeItem(kItemKey); + getInventory()->removeItem(kItemBomb); + getInventory()->removeItem(kItemMatch); + + if (getInventory()->hasItem(kItemFirebird)) { + getInventory()->removeItem(kItemFirebird); + getInventory()->setLocationAndProcess(kItemFirebird, kObjectLocation3); + + if (getInventory()->hasItem(kItemWhistle)) { + getInventory()->removeItem(kItemWhistle); + getInventory()->setLocationAndProcess(kItemWhistle, kObjectLocation3); + } + } + + getObjects()->update(kObject93, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getObjects()->update(kObject94, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + getObjects()->update(kObject101, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + getObjects()->updateLocation2(kObject98, kObjectLocation2); + getObjects()->updateLocation2(kObjectRestaurantCar, kObjectLocation2); + + if (ENTITY_PARAM(0, 2) || ENTITY_PARAM(0, 3)) { + getSound()->removeFromQueue(kEntityChapters); + ENTITY_PARAM(0, 2) = 0; + ENTITY_PARAM(0, 3) = 0; + } + + getScenes()->loadSceneFromPosition(kCarBaggageRear, 95); + getInventory()->show(); + + setCallback(1); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_chapter5Handler(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Chapters, chapter5Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime2907000 && !params->param2) { + params->param2 = 1; + + if (!getProgress().isNightTime) { + getSound()->playSound(kEntityChapters, "ARRIVE", SoundManager::kFlag8); + getSound()->processEntries(); + } + } + + if (getState()->time > kTimeTrainStopped2 && !params->param3) { + params->param3 = 1; + + if (!getEvent(kEventLocomotiveMilos) && !getEvent(kEventLocomotiveMilosNight)) { + getSound()->playSound(kEntityChapters, "ARRIVE", SoundManager::kFlag8); + getSound()->processEntries(); + } + } + break; + + case kActionEndSound: + if (getState()->time <= kTimeTrainStopped2) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventTrainStopped); + } else { + getLogic()->gameOver(kSavegameTypeTime, kTimeTrainStopped2, kSceneGameOverTrainStopped, true); + } + break; + + case kActionDefault: + params->param1 = 225 * (4 * rnd(10) + 20); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventTrainStopped); + getLogic()->gameOver(kSavegameTypeTime, kTimeTrainStopped, kSceneGameOverTrainStopped, true); + } + break; + + case kAction135800432: + getProgress().isNightTime = true; + getState()->time = kTime2916000; + + if (getSound()->isBuffered(kEntityChapters)) + getSound()->removeFromQueue(kEntityChapters); + break; + } +} + + +////////////////////////////////////////////////////////////////////////// +// Private functions +////////////////////////////////////////////////////////////////////////// +void Chapters::enterExitStation(const SavePoint &savepoint, bool isEnteringStation) { + if (savepoint.action == kActionDefault) { + if (!ENTITY_PARAM(0, 2) && !ENTITY_PARAM(0, 3)) { + enterExitHelper(isEnteringStation); + return; + } + + getSound()->removeFromQueue(kEntityChapters); + + if (!ENTITY_PARAM(0, 2)) { + if (ENTITY_PARAM(0, 3)) + ENTITY_PARAM(0, 3) = 0; + + enterExitHelper(isEnteringStation); + return; + } + + getSavePoints()->push(kEntityChapters, kEntityTrain, kActionTrainStopRunning); + + if (getEntityData(kEntityPlayer)->location != kLocationOutsideTrain) { + ENTITY_PARAM(0, 2) = 0; + enterExitHelper(isEnteringStation); + return; + } + + // Green sleeping car + if (getEntities()->isOutsideAlexeiWindow()) { + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + ENTITY_PARAM(0, 2) = 0; + enterExitHelper(isEnteringStation); + return; + } + + // Red sleeping car + if (getEntities()->isOutsideAnnaWindow()) { + getScenes()->loadSceneFromPosition(kCarRedSleeping, 49); + ENTITY_PARAM(0, 2) = 0; + enterExitHelper(isEnteringStation); + return; + } + + // Other cars + if (getEntityData(kEntityPlayer)->car < kCarRedSleeping || getEntityData(kEntityPlayer)->car > kCarCoalTender) { + + if (getEntityData(kEntityPlayer)->car < kCarBaggageRear || getEntityData(kEntityPlayer)->car > kCarGreenSleeping) { + ENTITY_PARAM(0, 2) = 0; + enterExitHelper(isEnteringStation); + return; + } + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 98)) { + getSound()->playSound(kEntityPlayer, "LIB015"); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 71); + ENTITY_PARAM(0, 2) = 0; + enterExitHelper(isEnteringStation); + return; + } + + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 82); + ENTITY_PARAM(0, 2) = 0; + enterExitHelper(isEnteringStation); + return; + } + + getScenes()->loadSceneFromPosition(kCarRestaurant, 82); + ENTITY_PARAM(0, 2) = 0; + enterExitHelper(isEnteringStation); + } +} + +////////////////////////////////////////////////////////////////////////// +void Chapters::enterExitHelper(bool isEnteringStation) { + EXPOSE_PARAMS(EntityData::EntityParametersSIIS); + + getSound()->playSound(kEntityChapters, isEnteringStation ? "ARRIVE" : "DEPART", SoundManager::kFlag8); + getSound()->processEntries(); + + getObjects()->update(kObjectHandleOutsideLeft, kEntityPlayer, kObjectLocation1, kCursorNormal, isEnteringStation ? kCursorNormal : kCursorHand); + getObjects()->update(kObjectHandleOutsideRight, kEntityPlayer, kObjectLocation1, kCursorNormal, isEnteringStation ? kCursorNormal : kCursorHand); + + getProgress().isTrainRunning = isEnteringStation ? false : true; + + if (isEnteringStation) { + ENTITY_PARAM(0, 2) = 1; + ENTITY_PARAM(0, 4) = params->param4; + } else { + getSavePoints()->push(kEntityChapters, kEntityTrain, kActionTrainStartRunning); + ENTITY_PARAM(0, 3) = 1; + } + + CALLBACK_ACTION(); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/chapters.h b/engines/lastexpress/entities/chapters.h new file mode 100644 index 0000000000..845fe8b727 --- /dev/null +++ b/engines/lastexpress/entities/chapters.h @@ -0,0 +1,166 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_CHAPTERS_H +#define LASTEXPRESS_CHAPTERS_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Chapters : public Entity { +public: + Chapters(LastExpressEngine *engine); + ~Chapters() {}; + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Exit a train station + * + * @param stationName The name of the train station + * @param index The index of the train station + */ + DECLARE_FUNCTION_2(enterStation, const char *stationName, CityIndex index) + + /** + * Exit a train station + * + * @param stationName The name of the train station + */ + DECLARE_FUNCTION_1(exitStation, const char *stationName) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Reset main entities + */ + DECLARE_FUNCTION(resetMainEntities) + + /** + * Handle end of Chapter 1 events + */ + DECLARE_FUNCTION(chapter1End) + + /** + * Init Chapter 1 data + */ + DECLARE_FUNCTION(chapter1Init) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + /** + * Handle switching to Chapter 2 after the end of Chapter 1 + */ + DECLARE_FUNCTION(chapter1Next) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Init Chapter 2 data + */ + DECLARE_FUNCTION(chapter2Init) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Init Chapter 3 data + */ + DECLARE_FUNCTION(chapter3Init) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + /** + * Handle Vienna events + */ + DECLARE_FUNCTION(viennaEvents) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Init Chapter 4 data + */ + DECLARE_FUNCTION(chapter4Init) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Init Chapter 5 data + */ + DECLARE_FUNCTION(chapter5Init) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + +private: + void enterExitStation(const SavePoint &savepoint, bool isEnteringStation); + void enterExitHelper(bool isEnteringStation); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_CHAPTERS_H diff --git a/engines/lastexpress/entities/cooks.cpp b/engines/lastexpress/entities/cooks.cpp new file mode 100644 index 0000000000..2bc787b495 --- /dev/null +++ b/engines/lastexpress/entities/cooks.cpp @@ -0,0 +1,571 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/cooks.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +Cooks::Cooks(LastExpressEngine *engine) : Entity(engine, kEntityCooks) { + ADD_CALLBACK_FUNCTION(Cooks, draw); + ADD_CALLBACK_FUNCTION(Cooks, playSound); + ADD_CALLBACK_FUNCTION(Cooks, function3); + ADD_CALLBACK_FUNCTION(Cooks, function4); + ADD_CALLBACK_FUNCTION(Cooks, chapter1); + ADD_CALLBACK_FUNCTION(Cooks, chapter1Handler); + ADD_CALLBACK_FUNCTION(Cooks, function7); + ADD_CALLBACK_FUNCTION(Cooks, chapter2); + ADD_CALLBACK_FUNCTION(Cooks, chapter2Handler); + ADD_CALLBACK_FUNCTION(Cooks, chapter3); + ADD_CALLBACK_FUNCTION(Cooks, chapter3Handler); + ADD_CALLBACK_FUNCTION(Cooks, chapter4); + ADD_CALLBACK_FUNCTION(Cooks, chapter4Handler); + ADD_CALLBACK_FUNCTION(Cooks, chapter5); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(1, Cooks, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Cooks, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(3, Cooks, function3) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityCooks, "308A"); + getEntities()->updatePositionEnter(kEntityCooks, kCarRestaurant, 75); + getEntities()->updatePositionEnter(kEntityCooks, kCarRestaurant, 78); + + switch (getProgress().chapter) { + default: + getSound()->playSound(kEntityCooks, "KIT1011"); + setCallback(3); + setup_draw("308B"); + break; + + case kChapter1: + setCallback(1); + setup_playSound("KIT1010"); + break; + + case kChapter3: + setCallback(2); + setup_playSound("KIT1012"); + break; + } + break; + + case kActionDrawScene: + if (!getEntities()->isInKitchen(kEntityPlayer)) { + getEntities()->clearSequences(kEntityCooks); + CALLBACK_ACTION(); + break; + } + + if (getEntities()->isPlayerPosition(kCarRestaurant, 46)) { + getEntities()->drawSequenceLeft(kEntityCooks, "308D"); + + if (!getSound()->isBuffered(kEntityCooks)) { + if (params->param1) { + if (!getEntities()->hasValidFrame(kEntityCooks)) { + getSound()->playSound(kEntityCooks, "LIB015"); + getEntities()->clearSequences(kEntityCooks); + CALLBACK_ACTION(); + } + break; + } + + // Kitchen apprentice getting a lesson :D + getSound()->playSound(kEntityCooks, "KIT1011A"); + params->param1 = 1; + } + } + + if (params->param1 && !getEntities()->hasValidFrame(kEntityCooks)) { + getSound()->playSound(kEntityCooks, "LIB015"); + getEntities()->clearSequences(kEntityCooks); + CALLBACK_ACTION(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + getSound()->playSound(kEntityCooks, "KIT1011"); + setCallback(3); + setup_draw("308B"); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityCooks, "308C"); + getEntities()->updatePositionExit(kEntityCooks, kCarRestaurant, 75); + getEntities()->updatePositionExit(kEntityCooks, kCarRestaurant, 78); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(4, Cooks, function4) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityCooks, "308A"); + getEntities()->updatePositionEnter(kEntityCooks, kCarRestaurant, 75); + getEntities()->updatePositionEnter(kEntityCooks, kCarRestaurant, 78); + + switch (getProgress().chapter) { + default: + break; + + case kChapter1: + setCallback(2); + setup_playSound("ZFX1011"); + break; + + case kChapter3: + setCallback(2); + setup_playSound("ZFX1011"); + break; + } + + getSound()->playSound(kEntityCooks, "KIT1011"); + setCallback(3); + setup_draw("308B"); + break; + + case kActionDrawScene: + if (!getEntities()->isInKitchen(kEntityPlayer)) { + getEntities()->clearSequences(kEntityCooks); + CALLBACK_ACTION(); + break; + } + + if (getEntities()->isPlayerPosition(kCarRestaurant, 80)) { + getEntities()->drawSequenceLeft(kEntityCooks, "308D"); + + if (!getSound()->isBuffered(kEntityCooks)) { + if (params->param1) { + if (!getEntities()->hasValidFrame(kEntityCooks)) { + getSound()->playSound(kEntityCooks, "LIB015"); + getEntities()->clearSequences(kEntityCooks); + CALLBACK_ACTION(); + } + break; + } + + // Kitchen apprentice getting a lesson :D + getSound()->playSound(kEntityCooks, "KIT1011A"); + params->param1 = 1; + } + } + + if (params->param1 && !getEntities()->hasValidFrame(kEntityCooks)) { + getSound()->playSound(kEntityCooks, "LIB015"); + getEntities()->clearSequences(kEntityCooks); + CALLBACK_ACTION(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + getSound()->playSound(kEntityCooks, "KIT1011"); + setCallback(3); + setup_draw("308B"); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityCooks, "308C"); + getEntities()->updatePositionExit(kEntityCooks, kCarRestaurant, 75); + getEntities()->updatePositionEnter(kEntityCooks, kCarRestaurant, 78); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(5, Cooks, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + + getProgress().field_4C = 0; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6, Cooks, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param4, getState()->time, params->param2); + + // Broken plate sound + getSound()->playSound(kEntityPlayer, "LIB122", getSound()->getSoundFlag(kEntityCooks)); + params->param2 = 225 * (4 * rnd(30) + 120); + params->param4 = 0; + break; + + case kActionDefault: + params->param1 = 1; + params->param2 = 225 * (4 * rnd(30) + 120); + break; + + case kActionDrawScene: + if (!getEntities()->isInKitchen(kEntityPlayer)) + break; + + if (params->param1) { + if (getEntities()->isPlayerPosition(kCarRestaurant, 73)) { + setCallback(1); + setup_function3(); + } + } else { + if (params->param3) { + setCallback(2); + setup_playSound("ZFX1011"); + } else { + setCallback(3); + setup_playSound("ZFX1012"); + } + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + params->param1 = 0; + break; + + case 2: + case 3: + params->param3 = !params->param3; + break; + } + break; + + case kAction101632192: + setup_function7(); + break; + + case kAction224849280: + getProgress().field_4C = 1; + params->param1 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Cooks, function7) + switch (savepoint.action) { + default: + break; + + case kActionNone: + // Snoring... + setCallback(1); + setup_playSound("WAT1200"); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_3650; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + + getEntities()->clearSequences(kEntityCooks); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Cooks, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityCooks); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + + getProgress().field_4C = 1; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Cooks, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param3, getState()->time, params->param1); + + // Broken plate sound + getSound()->playSound(kEntityPlayer, "LIB122", getSound()->getSoundFlag(kEntityCooks)); + params->param1 = 225 * (4 * rnd(30) + 120); + params->param3 = 0; + break; + + case kActionDefault: + params->param1 = 225 * (4 * rnd(30) + 120); + break; + + case kActionDrawScene: + if (params->param2) { + setCallback(1); + setup_playSound("ZFX1011"); + } else { + setCallback(2); + setup_playSound("ZFX1012"); + } + break; + + case kActionCallback: + if (getCallback() == 1 || getCallback() == 2) + params->param2 = !params->param2; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Cooks, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityCooks); + + getData()->entityPosition = kPosition_5900; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + + getProgress().field_4C = 0; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Cooks, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM_PROC(params->param4, getState()->time, params->param2) + // Broken plate sound + getSound()->playSound(kEntityPlayer, "LIB122", getSound()->getSoundFlag(kEntityCooks)); + params->param2 = 225 * (4 * rnd(30) + 120); + params->param4 = 0; + UPDATE_PARAM_PROC_END + + if (getState()->time > kTime2079000 && !params->param5) { + params->param1 = 0; + params->param5 = 1; + } + break; + + case kActionDefault: + params->param1 = 1; + params->param2 = 225 * (4 * rnd(30) + 120); + break; + + case kActionDrawScene: + if (!getEntities()->isInKitchen(kEntityPlayer)) + break; + + if (params->param1) { + if (getEntities()->isPlayerPosition(kCarRestaurant, 80)) { + setCallback(1); + setup_function4(); + } + } else { + if (params->param3) { + setCallback(2); + setup_playSound("ZFX1011"); + } else { + setCallback(3); + setup_playSound("ZFX1012"); + } + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + params->param1 = 0; + break; + + case 2: + case 3: + params->param3 = !params->param3; + break; + } + break; + + case kAction236976550: + getProgress().field_4C = 1; + break; + + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Cooks, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityCooks); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + + getProgress().field_4C = 1; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Cooks, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param3, getState()->time, params->param1) + + // Broken plate sound + getSound()->playSound(kEntityPlayer, "LIB122", getSound()->getSoundFlag(kEntityCooks)); + params->param1 = 225 * (4 * rnd(30) + 120); + params->param3 = 0; + break; + + case kActionDefault: + params->param1 = 225 * (4 * rnd(30) + 120); + break; + + case kActionDrawScene: + if (!getEntities()->isInKitchen(kEntityPlayer)) + break; + + // Kitchen background sound + if (params->param2) { + setCallback(1); + setup_playSound("ZFX1011"); + } else { + setCallback(2); + setup_playSound("ZFX1012"); + } + break; + + + case kActionCallback: + // Play the next part of background sound + if (getCallback() == 1 || getCallback() == 2) { + params->param2 = !params->param2; + } + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Cooks, chapter5) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityCooks); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/cooks.h b/engines/lastexpress/entities/cooks.h new file mode 100644 index 0000000000..7f1a70fb8a --- /dev/null +++ b/engines/lastexpress/entities/cooks.h @@ -0,0 +1,109 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_COOKS_H +#define LASTEXPRESS_COOKS_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Cooks : public Entity { +public: + Cooks(LastExpressEngine *engine); + ~Cooks() {}; + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + DECLARE_FUNCTION(function3) + + DECLARE_FUNCTION(function4) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function7) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_COOKS_H diff --git a/engines/lastexpress/entities/coudert.cpp b/engines/lastexpress/entities/coudert.cpp new file mode 100644 index 0000000000..6249ea5487 --- /dev/null +++ b/engines/lastexpress/entities/coudert.cpp @@ -0,0 +1,3611 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/coudert.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +#define SAVEGAME_BLOOD_JACKET() \ + if (getProgress().jacket == kJacketBlood \ + && getEntities()->isDistanceBetweenEntities(kEntityCoudert, kEntityPlayer, 1000) \ + && !getEntities()->isInsideCompartments(kEntityPlayer) \ + && !getEntities()->checkFields10(kEntityPlayer)) { \ + setCallback(1); \ + setup_savegame(kSavegameTypeEvent, kEventMertensBloodJacket); \ + } + +Coudert::Coudert(LastExpressEngine *engine) : Entity(engine, kEntityCoudert) { + ADD_CALLBACK_FUNCTION(Coudert, reset); + ADD_CALLBACK_FUNCTION(Coudert, bloodJacket); + ADD_CALLBACK_FUNCTION(Coudert, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Coudert, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Coudert, enterExitCompartment2); + ADD_CALLBACK_FUNCTION(Coudert, playSound); + ADD_CALLBACK_FUNCTION(Coudert, playSound16); + ADD_CALLBACK_FUNCTION(Coudert, savegame); + ADD_CALLBACK_FUNCTION(Coudert, updateEntity); + ADD_CALLBACK_FUNCTION(Coudert, updateFromTime); + ADD_CALLBACK_FUNCTION(Coudert, updateFromTicks); + ADD_CALLBACK_FUNCTION(Coudert, excuseMe); + ADD_CALLBACK_FUNCTION(Coudert, function13); + ADD_CALLBACK_FUNCTION(Coudert, function14); + ADD_CALLBACK_FUNCTION(Coudert, function15); + ADD_CALLBACK_FUNCTION(Coudert, function16); + ADD_CALLBACK_FUNCTION(Coudert, function17); + ADD_CALLBACK_FUNCTION(Coudert, function18); + ADD_CALLBACK_FUNCTION(Coudert, function19); + ADD_CALLBACK_FUNCTION(Coudert, function20); + ADD_CALLBACK_FUNCTION(Coudert, function21); + ADD_CALLBACK_FUNCTION(Coudert, function22); + ADD_CALLBACK_FUNCTION(Coudert, function23); + ADD_CALLBACK_FUNCTION(Coudert, visitCompartmentF); + ADD_CALLBACK_FUNCTION(Coudert, function25); + ADD_CALLBACK_FUNCTION(Coudert, function26); + ADD_CALLBACK_FUNCTION(Coudert, function27); + ADD_CALLBACK_FUNCTION(Coudert, visitCompartmentB); + ADD_CALLBACK_FUNCTION(Coudert, visitCompartmentA); + ADD_CALLBACK_FUNCTION(Coudert, function30); + ADD_CALLBACK_FUNCTION(Coudert, function31); + ADD_CALLBACK_FUNCTION(Coudert, function32); + ADD_CALLBACK_FUNCTION(Coudert, function33); + ADD_CALLBACK_FUNCTION(Coudert, function34); + ADD_CALLBACK_FUNCTION(Coudert, function35); + ADD_CALLBACK_FUNCTION(Coudert, chapter1); + ADD_CALLBACK_FUNCTION(Coudert, function37); + ADD_CALLBACK_FUNCTION(Coudert, function38); + ADD_CALLBACK_FUNCTION(Coudert, function39); + ADD_CALLBACK_FUNCTION(Coudert, chapter1Handler); + ADD_CALLBACK_FUNCTION(Coudert, function41); + ADD_CALLBACK_FUNCTION(Coudert, chapter2); + ADD_CALLBACK_FUNCTION(Coudert, function43); + ADD_CALLBACK_FUNCTION(Coudert, chapter3); + ADD_CALLBACK_FUNCTION(Coudert, function45); + ADD_CALLBACK_FUNCTION(Coudert, function46); + ADD_CALLBACK_FUNCTION(Coudert, function47); + ADD_CALLBACK_FUNCTION(Coudert, function48); + ADD_CALLBACK_FUNCTION(Coudert, function49); + ADD_CALLBACK_FUNCTION(Coudert, function50); + ADD_CALLBACK_FUNCTION(Coudert, function51); + ADD_CALLBACK_FUNCTION(Coudert, chapter4); + ADD_CALLBACK_FUNCTION(Coudert, function53); + ADD_CALLBACK_FUNCTION(Coudert, function54); + ADD_CALLBACK_FUNCTION(Coudert, function55); + ADD_CALLBACK_FUNCTION(Coudert, function56); + ADD_CALLBACK_FUNCTION(Coudert, chapter5); + ADD_CALLBACK_FUNCTION(Coudert, chapter5Handler); + ADD_CALLBACK_FUNCTION(Coudert, function59); + ADD_CALLBACK_FUNCTION(Coudert, function60); + ADD_CALLBACK_FUNCTION(Coudert, function61); + ADD_CALLBACK_FUNCTION(Coudert, function62); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Coudert, reset) + Entity::reset(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Coudert, bloodJacket) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + break; + + case kActionExitCompartment: + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityCoudert, (char *)¶ms->seq1); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(3, Coudert, enterExitCompartment, ObjectIndex) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + return; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + return; + } + + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(4, Coudert, callbackActionOnDirection) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getData()->direction != kDirectionRight) { + CALLBACK_ACTION(); + break; + } + + SAVEGAME_BLOOD_JACKET(); + break; + + case kActionExitCompartment: + CALLBACK_ACTION(); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SIII(5, Coudert, enterExitCompartment2, ObjectIndex, EntityPosition, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + return; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + return; + } + + Entity::enterExitCompartment(savepoint, (EntityPosition)params->param5, (EntityPosition)params->param6, kCarRedSleeping, (ObjectIndex)params->param4, false); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(6, Coudert, playSound) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + break; + + case kActionEndSound: + CALLBACK_ACTION(); + break; + + case kActionDefault: + getSound()->playSound(kEntityCoudert, (char *)¶ms->seq1); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(7, Coudert, playSound16) + EXPOSE_PARAMS(EntityData::EntityParametersSIIS); + + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + break; + + case kActionEndSound: + CALLBACK_ACTION(); + break; + + case kActionDefault: + getSound()->playSound(kEntityCoudert, (char *)¶ms->seq1, SoundManager::kFlagDefault); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(8, Coudert, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(9, Coudert, updateEntity, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param3 && getEntities()->isDistanceBetweenEntities(kEntityCoudert, kEntityPlayer, 2000)) + getData()->inventoryItem = kItemInvalid; + else + getData()->inventoryItem = kItemNone; + + if (getProgress().jacket != kJacketBlood + || !getEntities()->isDistanceBetweenEntities(kEntityCoudert, kEntityPlayer, 1000) + || getEntities()->isInsideCompartments(kEntityPlayer) + || getEntities()->checkFields10(kEntityPlayer)) { + if (getEntities()->updateEntity(kEntityCoudert, (CarIndex)params->param1, (EntityPosition)params->param2)) { + getData()->inventoryItem = kItemNone; + + CALLBACK_ACTION(); + } + break; + } + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventMertensBloodJacket); + break; + + case kAction1: + params->param3 = 0; + getData()->inventoryItem = kItemNone; + + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventCoudertAskTylerCompartment); + break; + + case kActionExcuseMeCath: + if (getData()->clothes == kClothes1) + getSound()->playSound(kEntityPlayer, "ZFX1003", getSound()->getSoundFlag(kEntityCoudert)); + else if (!getSound()->isBuffered(kEntityCoudert)) + getSound()->playSound(kEntityPlayer, "JAC1112", getSound()->getSoundFlag(kEntityCoudert)); + break; + + case kActionExcuseMe: + if (getData()->clothes == kClothes1) + getSound()->playSound(kEntityPlayer, "ZFX1003", getSound()->getSoundFlag(kEntityCoudert)); + else + getSound()->excuseMe(kEntityCoudert); + break; + + case kActionDefault: + if (!getProgress().eventCorpseFound && !getEvent(kEventCoudertAskTylerCompartment)) + params->param3 = kItemInvalid; + + if (getEntities()->updateEntity(kEntityCoudert, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + break; + + case 2: + getAction()->playAnimation(kEventCoudertAskTylerCompartment); + + if (getData()->direction != kDirectionUp) + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + 750)); + else + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition - 750), true); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(10, Coudert, updateFromTime, uint32) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + + UPDATE_PARAM(params->param2, getState()->time, params->param1); + + CALLBACK_ACTION(); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(11, Coudert, updateFromTicks, uint32) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + + UPDATE_PARAM(params->param2, getState()->timeTicks, params->param1); + + CALLBACK_ACTION(); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +// Parameters +// - EntityIndex +IMPLEMENT_FUNCTION_I(12, Coudert, excuseMe, EntityIndex) + if (savepoint.action != kActionDefault) + return; + + if (getSound()->isBuffered(kEntityCoudert)) { + CALLBACK_ACTION(); + return; + } + + if (isNight()) { + if (Entities::isFemale((EntityIndex)params->param1)) { + getSound()->playSound(kEntityCoudert, Entities::isMarried((EntityIndex)params->param1) ? "JAC1112C" : "JAC1112F"); + } else { + if (!params->param1 && getProgress().field_18 == 2) { + switch (rnd(4)) { + default: + break; + + case 0: + getSound()->playSound(kEntityCoudert, "JAC1013"); + break; + + case 1: + getSound()->playSound(kEntityCoudert, "JAC1013A"); + break; + + case 2: + getSound()->playSound(kEntityCoudert, "JAC1113"); + break; + + case 3: + getSound()->playSound(kEntityCoudert, "JAC1113A"); + break; + } + } else { + getSound()->playSound(kEntityCoudert, "JAC1112D"); + } + } + } else { + if (Entities::isFemale((EntityIndex)params->param1)) + getSound()->playSound(kEntityCoudert, Entities::isMarried((EntityIndex)params->param1) ? "JAC1112B" : "JAC1112G"); + else + getSound()->playSound(kEntityCoudert, "JAC1112E"); + } + + CALLBACK_ACTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(13, Coudert, function13, bool, EntityIndex) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + + if (!params->param2 && !params->param3) { + + if (!params->param4) { + params->param4 = getState()->timeTicks + 75; + + if (!params->param4) { + getData()->inventoryItem = kItemNone; + setCallback(4); + setup_function19(true); + break; + } + } + + if (params->param4 < getState()->timeTicks) { + params->param4 = kTimeInvalid; + + getData()->inventoryItem = kItemNone; + setCallback(4); + setup_function19(true); + break; + } + } + + UPDATE_PARAM(params->param5, getState()->timeTicks, 225); + + getData()->inventoryItem = kItemNone; + setCallback(5); + setup_function19(true); + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + + setCallback(9); + setup_savegame(kSavegameTypeEvent, kEventCoudertAskTylerCompartment); + break; + + case kAction11: + ++params->param3; + + setCallback(8); + setup_excuseMe(savepoint.entity2); + break; + + case kActionDefault: + if (params->param2) + params->param3 = 1; + + setCallback(1); + setup_excuseMe((EntityIndex)params->param2); + break; + + case kAction16: + --params->param3; + + if (params->param2 && !params->param3) { + getData()->inventoryItem = kItemNone; + setCallback(7); + setup_function19(true); + } + break; + + case kActionDrawScene: + if (!params->param3) { + getData()->inventoryItem = kItemNone; + setCallback(6); + setup_function19(true); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function17(true); + break; + + case 2: + if (getProgress().chapter == kChapter1 && !getProgress().eventCorpseFound && !getEvent(kEventCoudertAskTylerCompartment)) + getData()->inventoryItem = kItemInvalid; + + getEntities()->drawSequenceLeft(kEntityCoudert, params->param1 ? "667I" : "667H"); + break; + + case 3: + getAction()->playAnimation(kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + // BUG: the original game continues executing code here + break; + + case 4: + case 5: + case 6: + case 7: + CALLBACK_ACTION(); + break; + + case 9: + getAction()->playAnimation(kEventCoudertAskTylerCompartment); + getScenes()->loadSceneFromPosition(kCarRedSleeping, 25); + break; + } + break; + + case kAction201439712: + getEntities()->drawSequenceLeft(kEntityCoudert, "627K"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(14, Coudert, function14, EntityIndex) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + break; + + case kActionDefault: + if (ENTITY_PARAM(2, 1)) { + ENTITY_PARAM(2, 1) = 0; + + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_1500); + } else { + setCallback(1); + setup_updateFromTime(15); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityCoudert, (EntityIndex)params->param1, kAction202558662); + + setCallback(2); + setup_function17(false); + break; + + case 2: + getSavePoints()->push(kEntityCoudert, (EntityIndex)params->param1, kAction155853632); + getEntities()->drawSequenceLeft(kEntityCoudert, "627K"); + break; + + case 3: + getSavePoints()->push(kEntityCoudert, (EntityIndex)params->param1, kAction202558662); + getSavePoints()->push(kEntityCoudert, (EntityIndex)params->param1, kAction155853632); + getEntities()->drawSequenceLeft(kEntityCoudert, "627K"); + getScenes()->loadSceneFromItemPosition(kItem5); + break; + + case 4: + getAction()->playAnimation(kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + break; + + case 5: + CALLBACK_ACTION(); + break; + } + break; + + case kAction125499160: + switch (params->param1) { + default: + break; + + case kEntityVerges: + ENTITY_PARAM(0, 3) = 0; + break; + + case kEntityMmeBoutarel: + ENTITY_PARAM(0, 4) = 0; + break; + + case kEntityMertens: + ENTITY_PARAM(0, 5) = 0; + break; + } + + setCallback(5); + setup_function19(false); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(15, Coudert, function15, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + ENTITY_PARAM(0, 8) = 0; + ENTITY_PARAM(1, 1) = 0; + + setCallback(1); + setup_function16(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_7500); + break; + + case 2: + if (params->param1) + getSound()->playSound(kEntityCoudert, "Tat3163"); + else + getSound()->playSound(kEntityCoudert, (getProgress().chapter != kChapter3 || getState()->time > kTime1449000) ? "Tat3162A" : "Tat3161A"); + + setCallback(3); + setup_enterExitCompartment("627Xb", kObjectCompartmentB); + break; + + case 3: + getSavePoints()->push(kEntityCoudert, kEntityTatiana, kAction69239528); + getData()->entityPosition = kPosition_7250; + + setCallback(4); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 4: + setCallback(5); + setup_function18(); + break; + + case 5: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Coudert, function16) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (ENTITY_PARAM(2, 1)) { + ENTITY_PARAM(2, 1) = 0; + getInventory()->setLocationAndProcess(kItem5, kObjectLocation1); + + CALLBACK_ACTION(); + break; + } + + setCallback(ENTITY_PARAM(0, 2) ? 1 : 2); + setup_bloodJacket(ENTITY_PARAM(0, 2) ? "627C" : "627F"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + getInventory()->setLocationAndProcess(kItem5, kObjectLocation1); + if (!getEntities()->isPlayerPosition(kCarRedSleeping, 2)) + getData()->entityPosition = kPosition_2088; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(17, Coudert, function17, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getScenes()->loadSceneFromItemPosition(kItem5); + + if (ENTITY_PARAM(2, 1)) { + ENTITY_PARAM(2, 1) = 0; + CALLBACK_ACTION(); + break; + } + + if (params->param1) { + setCallback(1); + setup_bloodJacket("627H"); + break; + } + + if (params->param2) { + setCallback(2); + setup_bloodJacket("627C"); + break; + } + + setCallback(3); + setup_bloodJacket("627F"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + case 3: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Coudert, function18) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (ENTITY_PARAM(0, 6) || ENTITY_PARAM(0, 8) + || ENTITY_PARAM(1, 1) || ENTITY_PARAM(1, 2) || ENTITY_PARAM(1, 3) || ENTITY_PARAM(1, 5) || ENTITY_PARAM(1, 6) || ENTITY_PARAM(1, 7) || ENTITY_PARAM(1, 8) + || ENTITY_PARAM(2, 4) || ENTITY_PARAM(2, 6)) { + getInventory()->setLocationAndProcess(kItem5, kObjectLocation1); + + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_540); + break; + } + + if (ENTITY_PARAM(0, 3) || ENTITY_PARAM(0, 5) || ENTITY_PARAM(0, 4)) { + getEntities()->drawSequenceLeft(kEntityCoudert, "627K"); + getScenes()->loadSceneFromItemPosition(kItem5); + + CALLBACK_ACTION(); + break; + } + + getEntities()->drawSequenceRight(kEntityCoudert, ENTITY_PARAM(0, 2) ? "627A" : "627D"); + getScenes()->loadSceneFromItemPosition(kItem5); + + if (getEntities()->isPlayerPosition(kCarRedSleeping, 68)) { + if (!getSound()->isBuffered(kEntityCoudert)) + getSound()->playSound(kEntityCoudert, "JAC1111"); + + getScenes()->loadSceneFromPosition(kCarRedSleeping, 25); + } + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->clearSequences(kEntityCoudert); + ENTITY_PARAM(2, 1) = 1; + + setCallback(2); + setup_updateFromTime(75); + break; + + case 2: + CALLBACK_ACTION(); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityCoudert, ENTITY_PARAM(0, 2) ? "627B" : "627E"); + ENTITY_PARAM(0, 1) = 0; + getSavePoints()->push(kEntityCoudert, kEntityCoudert, kActionDrawScene); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(19, Coudert, function19, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (ENTITY_PARAM(0, 6) || ENTITY_PARAM(0, 8) + || ENTITY_PARAM(1, 1) || ENTITY_PARAM(1, 2) || ENTITY_PARAM(1, 3) || ENTITY_PARAM(1, 5) || ENTITY_PARAM(1, 6) || ENTITY_PARAM(1, 7) || ENTITY_PARAM(1, 8) + || ENTITY_PARAM(2, 4) || ENTITY_PARAM(2, 6)) { + getInventory()->setLocationAndProcess(kItem5, kObjectLocation1); + ENTITY_PARAM(2, 1) = 1; + CALLBACK_ACTION(); + break; + } + + if (ENTITY_PARAM(0, 3) || ENTITY_PARAM(0, 5) || ENTITY_PARAM(0, 4)) { + getScenes()->loadSceneFromItemPosition(kItem5); + ENTITY_PARAM(2, 1) = 1; + CALLBACK_ACTION(); + break; + } + + if (params->param1) + getEntities()->drawSequenceRight(kEntityCoudert, "697H"); + else + getEntities()->drawSequenceRight(kEntityCoudert, ENTITY_PARAM(0, 2) ? "627A" : "627D"); + + getScenes()->loadSceneFromItemPosition(kItem5); + + setCallback(1); + setup_callbackActionOnDirection(); + break; + + case kActionCallback: + if (getCallback() == 1) { + getEntities()->drawSequenceLeft(kEntityCoudert, ENTITY_PARAM(0, 2) ? "627B" : "627E"); + ENTITY_PARAM(0, 1) = 0; + + CALLBACK_ACTION(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(20, Coudert, function20, ObjectIndex, ObjectIndex) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM_PROC(CURRENT_PARAMS(1, 3), getState()->time, 300) + getSound()->playSound(kEntityPlayer, "ZFX1004", getSound()->getSoundFlag(kEntityCoudert)); + UPDATE_PARAM_PROC_END + + UPDATE_PARAM(CURRENT_PARAMS(1, 4), getState()->time, 900); + + getObjects()->updateLocation2((ObjectIndex)params->param1, kObjectLocation1); + + if (params->param4 != kObjectLocation2) + getObjects()->update((ObjectIndex)params->param1, (EntityIndex)params->param3, (ObjectLocation)params->param4, (CursorStyle)params->param5, (CursorStyle)params->param6); + + if (params->param2) + getObjects()->update((ObjectIndex)params->param2, (EntityIndex)params->param7, (ObjectLocation)params->param8, (CursorStyle)CURRENT_PARAMS(1, 1), (CursorStyle)CURRENT_PARAMS(1, 2)); + + CALLBACK_ACTION(); + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update((ObjectIndex)params->param1, kEntityCoudert, kObjectLocation1, kCursorNormal, kCursorNormal); + if (params->param2) + getObjects()->update((ObjectIndex)params->param2, kEntityCoudert, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(savepoint.action == kActionKnock ? 1 : 2); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + break; + + case kActionDefault: + params->param3 = getObjects()->get((ObjectIndex)params->param1).entity; + params->param4 = getObjects()->get((ObjectIndex)params->param1).location; + params->param5 = getObjects()->get((ObjectIndex)params->param1).cursor; + params->param6 = getObjects()->get((ObjectIndex)params->param1).cursor2; + + if (params->param2) { + params->param7 = getObjects()->get((ObjectIndex)params->param2).entity; + params->param8 = getObjects()->get((ObjectIndex)params->param2).location; + CURRENT_PARAMS(1, 1) = getObjects()->get((ObjectIndex)params->param2).cursor; + CURRENT_PARAMS(1, 2) = getObjects()->get((ObjectIndex)params->param2).cursor2; + + getObjects()->update((ObjectIndex)params->param2, kEntityCoudert, kObjectLocation1, kCursorHandKnock, kCursorHand); + } + + if (params->param4 != kObjectLocation2) + getObjects()->update((ObjectIndex)params->param1, kEntityCoudert, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + if (params->param1 == kObjectCompartmentA || params->param1 == kObjectCompartmentC + || params->param1 == kObjectCompartmentG || params->param1 == kObjectCompartmentH) { + setCallback(3); + setup_playSound("Jac1001B"); + } else { + setCallback(4); + setup_playSound("Jac1001A"); + } + break; + + case 3: + case 4: + getObjects()->update((ObjectIndex)params->param1, kEntityCoudert, kObjectLocation1, kCursorHandKnock, kCursorHand); + + if (params->param2) + getObjects()->update((ObjectIndex)params->param2, kEntityCoudert, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Coudert, function21) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) { + UPDATE_PARAM(params->param2, getState()->timeTicks, 75); + + setCallback(3); + setup_enterExitCompartment("627Zh", kObjectCompartmentH); + } + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_2740); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("627Vh", kObjectCompartmentH); + break; + + case 2: + getSavePoints()->push(kEntityCoudert, kEntityIvo, kAction221683008); + getEntities()->drawSequenceLeft(kEntityCoudert, "627Wh"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentH, true); + break; + + case 3: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentH, true); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityCoudert); + + setCallback(4); + setup_function20(kObjectCompartmentH, kObjectNone); + break; + + case 4: + setCallback(5); + setup_enterExitCompartment("697Ah", kObjectCompartmentH); + break; + + case 5: + getData()->location = kLocationOutsideCompartment; + + CALLBACK_ACTION(); + break; + + case 6: + getSavePoints()->push(kEntityCoudert, kEntityIvo, kAction122865568); + break; + + case 7: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentH, true); + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityCoudert); + + setCallback(8); + setup_function20(kObjectCompartmentH, kObjectNone); + break; + + case 8: + getSound()->playSound(kEntityCoudert, "JAC1013A"); + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(9); + setup_enterExitCompartment("667Uh", kObjectCompartmentH); + break; + + case 9: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityCoudert, kEntityIvo, kAction123852928); + + CALLBACK_ACTION(); + break; + } + break; + + case kAction88652208: + setCallback(7); + setup_enterExitCompartment("667Th", kObjectCompartmentH); + break; + + case kAction123199584: + params->param1 = 1; + + setCallback(6); + setup_playSound("JAC1012"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Coudert, function22) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) { + UPDATE_PARAM(params->param2, getState()->timeTicks, 75); + + setCallback(3); + setup_enterExitCompartment("627Rg", kObjectCompartmentG); + } + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_3050); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("627Mg", kObjectCompartmentG); + break; + + case 2: + getSavePoints()->push(kEntityCoudert, kEntityMilos, kAction221683008); + getEntities()->drawSequenceLeft(kEntityCoudert, "627Ng"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentG, true); + break; + + case 3: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentG, true); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityCoudert); + + setCallback(4); + setup_function20(kObjectCompartmentG, kObjectNone); + break; + + case 4: + setCallback(5); + setup_enterExitCompartment("627Sg", kObjectCompartmentG); + break; + + case 5: + getData()->location = kLocationOutsideCompartment; + + CALLBACK_ACTION(); + break; + + case 6: + getSavePoints()->push(kEntityCoudert, kEntityMilos, kAction122865568); + break; + + case 7: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentG, true); + getObjects()->update(kObjectCompartmentG, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityCoudert); + + setCallback(8); + setup_function20(kObjectCompartmentG, kObjectNone); + break; + + case 8: + getSound()->playSound(kEntityCoudert, "JAC1013A"); + getObjects()->update(kObjectCompartmentG, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(9); + setup_enterExitCompartment("627Ug", kObjectCompartmentG); + break; + + case 9: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityCoudert, kEntityMilos, kAction123852928); + + CALLBACK_ACTION(); + break; + } + break; + + case kAction88652208: + setCallback(7); + setup_enterExitCompartment("627Tg", kObjectCompartmentG); + break; + + case kAction123199584: + params->param1 = 1; + + setCallback(6); + setup_playSound("JAC1030"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Coudert, function23) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_4070); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("627Vf", kObjectCompartmentF); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityCoudert, "627Wf"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentF, true); + getSavePoints()->push(kEntityCoudert, kEntityMax, kAction158007856); + + setCallback(3); + setup_updateFromTime(150); + break; + + case 3: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentF, true); + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Coudert, visitCompartmentF) + visitCompartment(savepoint, kPosition_4070, "627Vf", kObjectCompartmentF, "627Wf", "627Zf", kPosition_4455, kObject53, "697Af"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Coudert, function25) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_4840); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("627Me", kObjectCompartmentE); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityCoudert, "627Ne"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentE, true); + + setCallback(3); + setup_updateFromTime(45); + break; + + case 3: + getSavePoints()->push(kEntityCoudert, kEntityRebecca, kAction254915200); + + setCallback(4); + setup_updateFromTime(450); + break; + + case 4: + setCallback(5); + setup_enterExitCompartment2("627Re", kObjectCompartmentE, kPosition_4840, kPosition_4455); + break; + + case 5: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentE, true); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityCoudert); + + setCallback(6); + setup_function20(kObjectCompartmentE, kObject52); + break; + + case 6: + setCallback(7); + setup_enterExitCompartment("627Se", kObjectCompartmentE); + break; + + case 7: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityCoudert, kEntityRebecca, kAction123852928); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Coudert, function26) + error("Coudert: callback function 26 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Coudert, function27) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) { + UPDATE_PARAM(params->param2, getState()->timeTicks, 75); + + setCallback(3); + setup_enterExitCompartment2("627Rc", kObjectCompartmentC, kPosition_6470, kPosition_6130); + } + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_6470); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("627Mc", kObjectCompartmentC); + break; + + case 2: + getSavePoints()->push(kEntityCoudert, kEntityBoutarel, kAction221683008); + getEntities()->drawSequenceLeft(kEntityCoudert, "627Nc"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentC, true); + break; + + case 3: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentC, true); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityCoudert); + + setCallback(4); + setup_function20(kObjectCompartmentC, kObject50); + break; + + case 4: + setCallback(5); + setup_enterExitCompartment("627Sc", kObjectCompartmentC); + break; + + case 5: + getData()->location = kLocationOutsideCompartment; + CALLBACK_ACTION(); + break; + + case 6: + getSavePoints()->push(kEntityCoudert, kEntityBoutarel, kAction122865568); + break; + + case 7: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentC, true); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityCoudert); + + setCallback(8); + setup_function20(kObjectCompartmentC, kObject50); + break; + + case 8: + getSound()->playSound(kEntityCoudert, "JAC1013"); + + setCallback(9); + setup_enterExitCompartment("627Uc", kObjectCompartmentC); + break; + + case 9: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityCoudert, kEntityBoutarel, kAction123852928); + + CALLBACK_ACTION(); + break; + } + break; + + case kAction88652208: + setCallback(7); + setup_enterExitCompartment2("627Rc", kObjectCompartmentC, kPosition_6470, kPosition_6130); + break; + + case kAction123199584: + params->param1 = 1; + + setCallback(6); + setup_playSound("JAC1012"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Coudert, visitCompartmentB) + visitCompartment(savepoint, kPosition_7500, "627Vb", kObjectCompartmentB, "627Wb", "627Zb", kPosition_7850, kObject49, "697Ab"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Coudert, visitCompartmentA) + visitCompartment(savepoint, kPosition_8200, "627Ma", kObjectCompartmentA, "627Na", "627Ra", kPosition_7850, kObject48, "627Sa"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(30, Coudert, function30, ObjectIndex) + // Expose parameters as IIIIIS and ignore the default exposed parameters + EntityData::EntityParametersI5S *parameters = (EntityData::EntityParametersI5S*)_data->getCurrentParameters(); + EntityData::EntityParametersSIIS *parameters1 = (EntityData::EntityParametersSIIS*)_data->getCurrentParameters(1); + + switch (savepoint.action) { + default: + break; + + case kActionDefault: + switch (parameters->param1) { + default: + CALLBACK_ACTION(); + // Stop processing here + return; + + case kObjectCompartmentA: + parameters->param2 = kPosition_8200; + parameters->param3 = kPosition_7850; + strcpy((char *)¶meters->seq, "627Ma"); + strcpy((char *)¶meters1->seq1, "627Na"); + break; + + case kObjectCompartmentB: + parameters->param2 = kPosition_7500; + parameters->param3 = kPosition_7850; + parameters->param4 = true; + strcpy((char *)¶meters->seq, "627Vb"); + strcpy((char *)¶meters1->seq1, "627Wb"); + break; + + case kObjectCompartmentC: + parameters->param2 = kPosition_6470; + parameters->param3 = kPosition_6130; + strcpy((char *)¶meters->seq, "627Mc"); + strcpy((char *)¶meters1->seq1, "627Nc"); + break; + + case kObjectCompartmentD: + parameters->param2 = kPosition_5790; + parameters->param3 = kPosition_6130; + parameters->param4 = true; + strcpy((char *)¶meters->seq, "627Vd"); + strcpy((char *)¶meters1->seq1, "627Wd"); + break; + + case kObjectCompartmentE: + parameters->param2 = kPosition_4840; + parameters->param3 = kPosition_4455; + parameters->param4 = true; + strcpy((char *)¶meters->seq, "627Me"); + strcpy((char *)¶meters1->seq1, "627Ne"); + break; + + case kObjectCompartmentF: + parameters->param2 = kPosition_4070; + parameters->param3 = kPosition_4455; + parameters->param4 = true; + strcpy((char *)¶meters->seq, "627Vf"); + strcpy((char *)¶meters1->seq1, "627Wf"); + break; + } + + setCallback(1); + setup_function16(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, (EntityPosition)parameters->param2); + break; + + case 2: + if (getEntities()->checkFields19(kEntityPlayer, kCarRedSleeping, (EntityPosition)parameters->param3) + || ((parameters->param1 == kObjectCompartmentE || parameters->param1 == kObjectCompartmentF) && getEntities()->isOutsideAnnaWindow())) { + getObjects()->update((ObjectIndex)parameters->param1, kEntityPlayer, getObjects()->get((ObjectIndex)parameters->param1).location, kCursorNormal, kCursorNormal); + parameters->param5 = true; + } + + setCallback(3); + setup_enterExitCompartment((char *)¶meters->seq, (ObjectIndex)parameters->param1); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityCoudert, (char *)¶meters1->seq1); + getEntities()->enterCompartment(kEntityCoudert, (ObjectIndex)parameters->param1, true); + + setCallback(4); + setup_playSound(parameters->param4 ? "JAC3020" : "JAC3021"); + break; + + case 4: + if (parameters->param5) + getObjects()->update((ObjectIndex)parameters->param1, kEntityPlayer, getObjects()->get((ObjectIndex)parameters->param1).location, kCursorHandKnock, kCursorHand); + + getEntities()->exitCompartment(kEntityCoudert, (ObjectIndex)parameters->param1, true); + + setCallback(5); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 5: + setCallback(6); + setup_function18(); + break; + + case 6: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(31, Coudert, function31, uint32) + switch (savepoint.action) { + default: + break; + + case kActionEndSound: + setCallback(3); + setup_function19(true); + break; + + case kActionDefault: + setCallback(1); + setup_bloodJacket("627G"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getSound()->isBuffered(kEntityCoudert)) { + getEntities()->drawSequenceLeft(kEntityCoudert, "627K"); + } else { + setCallback(2); + setup_function19(true); + } + break; + + case 2: + case 3: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Coudert, function32) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_9460); + break; + + case 2: + getEntities()->clearSequences(kEntityCoudert); + setCallback(3); + setup_updateFromTime(900); + break; + + case 3: + setCallback(4); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 4: + setCallback(5); + setup_function18(); + break; + + case 5: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Coudert, function33) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (ENTITY_PARAM(0, 3) || ENTITY_PARAM(0, 4) || ENTITY_PARAM(0, 5) || ENTITY_PARAM(0, 6) || ENTITY_PARAM(0, 7) + || ENTITY_PARAM(1, 2) || ENTITY_PARAM(1, 7) + || ENTITY_PARAM(2, 2)) { + ENTITY_PARAM(2, 6) = 1; + + if (ENTITY_PARAM(0, 3) || ENTITY_PARAM(0, 4) || ENTITY_PARAM(0, 5)) { + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_1500); + } else { + setCallback(5); + setup_updateEntity(kCarRedSleeping, kPosition_540); + } + } else { + CALLBACK_ACTION(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + ENTITY_PARAM(2, 1) = 1; + if (ENTITY_PARAM(0, 3)) { + setCallback(2); + setup_function14(kEntityVerges); + break; + } + // Fallback to next case + + case 2: + if (ENTITY_PARAM(0, 5)) { + setCallback(3); + setup_function14(kEntityMertens); + break; + } + // Fallback to next case + + case 3: + if (ENTITY_PARAM(0, 4)) { + setCallback(4); + setup_function14(kEntityMmeBoutarel); + break; + } + // Fallback to next case + + case 4: + ENTITY_PARAM(2, 6) = 0; + + CALLBACK_ACTION(); + break; + + case 5: + getEntities()->clearSequences(kEntityCoudert); + + setCallback(6); + setup_updateFromTime(75); + break; + + case 6: + if (ENTITY_PARAM(0, 6) || ENTITY_PARAM(0, 7)) { + setCallback(7); + setup_function37(); + break; + } + // Fallback to next case + + case 7: + if (ENTITY_PARAM(2, 2)) { + setCallback(8); + setup_function39(); + break; + } + // Fallback to next case + + case 8: + if (ENTITY_PARAM(1, 2)) { + setCallback(9); + setup_function55(); + break; + } + // Fallback to next case + + case 9: + if (ENTITY_PARAM(1, 7)) { + setCallback(10); + setup_function34(false); + break; + } + // Fallback to next case + + case 10: + ENTITY_PARAM(2, 6) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(34, Coudert, function34, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_4070); + break; + + case 2: + if (!params->param1) { + getSound()->playSound(kEntityCoudert, "Ann3124"); + + ENTITY_PARAM(1, 7) = 0; + ENTITY_PARAM(1, 4) = 0; + + setCallback(7); + setup_function35((bool)params->param1); + } else { + getEntities()->drawSequenceLeft(kEntityCoudert, "627Vf"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentF, true); + + setCallback(3); + setup_playSound("LIB012"); + } + break; + + case 3: + setCallback(4); + setup_playSound("Jac1001"); + break; + + case 4: + getSound()->playSound(kEntityCoudert, "Ann3125"); + + setCallback(5); + setup_enterExitCompartment("629Bf", kObjectCompartmentF); + break; + + case 5: + setCallback(6); + setup_enterExitCompartment("629Ff", kObjectCompartmentF); + break; + + case 6: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentF, true); + + ENTITY_PARAM(1, 7) = 0; + ENTITY_PARAM(1, 4) = 0; + + setCallback(7); + setup_function35((bool)params->param1); + break; + + case 7: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(35, Coudert, function35, bool) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInsideTrainCar(kEntityPlayer, kCarBaggage)) { + getAction()->playAnimation(kEventCoudertBaggageCar); + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromPosition(kCarRestaurant, 65); + } + + UPDATE_PARAM(params->param2, getState()->time, 2700); + + getSavePoints()->push(kEntityCoudert, kEntityMax, kActionMaxFreeFromCage); + + getData()->clothes = kClothesDefault; + + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case kActionDefault: + if (params->param1) + getSavePoints()->push(kEntityCoudert, kEntityAnna, kAction156049968); + + getSavePoints()->push(kEntityCoudert, kEntityMax, kAction122358304); + + getData()->clothes = kClothes1; + getData()->entityPosition = kPosition_4370; + + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_8200); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (!getSound()->isBuffered(kEntityCoudert)) + getSound()->playSound(kEntityCoudert, "Ann3124"); + + if (params->param1) + getSavePoints()->push(kEntityCoudert, kEntityAnna, kAction123733488); + + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_9460); + break; + + case 2: + getEntities()->clearSequences(kEntityCoudert); + break; + + case 3: + setCallback(4); + setup_function18(); + break; + + case 4: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(36, Coudert, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK(kTimeChapter1, params->param1, 1, setup_chapter1Handler) + break; + + case kActionDefault: + getSavePoints()->addData(kEntityCoudert, kAction292048641, 7); + getSavePoints()->addData(kEntityCoudert, kAction326348944, 8); + getSavePoints()->addData(kEntityCoudert, kAction171394341, 2); + getSavePoints()->addData(kEntityCoudert, kAction154005632, 4); + getSavePoints()->addData(kEntityCoudert, kAction169557824, 3); + getSavePoints()->addData(kEntityCoudert, kAction226031488, 5); + getSavePoints()->addData(kEntityCoudert, kAction339669520, 6); + getSavePoints()->addData(kEntityCoudert, kAction189750912, 10); + getSavePoints()->addData(kEntityCoudert, kAction185737168, 12); + getSavePoints()->addData(kEntityCoudert, kAction185671840, 13); + getSavePoints()->addData(kEntityCoudert, kAction205033696, 15); + getSavePoints()->addData(kEntityCoudert, kAction157026693, 14); + getSavePoints()->addData(kEntityCoudert, kAction189026624, 11); + getSavePoints()->addData(kEntityCoudert, kAction168254872, 17); + getSavePoints()->addData(kEntityCoudert, kAction201431954, 18); + getSavePoints()->addData(kEntityCoudert, kAction188570113, 19); + + ENTITY_PARAM(0, 1) = 0; + ENTITY_PARAM(0, 2) = 1; + + getData()->entityPosition = kPosition_1500; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->updateLocation2(kObject111, kObjectLocation1); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_chapter1Handler(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(37, Coudert, function37) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (getSound()->isBuffered(kEntityCoudert)) + getSound()->processEntry(kEntityCoudert); + + if (ENTITY_PARAM(0, 7)) { + getData()->entityPosition = kPosition_8200; + + setCallback(4); + setup_enterExitCompartment2("698Ha", kObjectCompartmentA, kPosition_8200, kPosition_7850); + } else { + setCallback(1); + setup_function16(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_5790); + break; + + case 2: + getSavePoints()->push(kEntityCoudert, kEntityAnna, kAction238358920); + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_8200); + break; + + case 3: + setCallback(4); + setup_enterExitCompartment2("698Ha", kObjectCompartmentA, kPosition_8200, kPosition_7850); + break; + + case 4: + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityCoudert); + setup_function38(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(38, Coudert, function38) +switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationInsideCompartment; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 7) = 0; + + setCallback(2); + setup_function18(); + break; + + case 2: + setup_chapter1Handler(); + break; + } + break; + + case kAction191477936: + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationOutsideCompartment; + getObjects()->update(kObjectCompartment4, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(39, Coudert, function39) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_playSound("LIB070"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function16(); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_5790); + break; + + case 3: + setCallback(4); + setup_enterExitCompartment("627Vd", kObjectCompartmentD); + break; + + case 4: + getEntities()->drawSequenceLeft(kEntityCoudert, "627Wd"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentD, true); + + setCallback(5); + setup_playSound("MME1151A"); + break; + + case 5: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentD, true); + + setCallback(6); + setup_enterExitCompartment("627Zd", kObjectCompartmentD); + break; + + case 6: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityCoudert); + + setCallback(7); + setup_playSound("MME1151"); + break; + + case 7: + setCallback(8); + setup_enterExitCompartment("MME1151", kObjectCompartmentD); + break; + + case 8: + getSavePoints()->push(kEntityCoudert, kEntityMmeBoutarel, kAction223068211); + getData()->location = kLocationOutsideCompartment; + + setCallback(9); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 9: + setCallback(10); + setup_function18(); + break; + + case 10: + getSavePoints()->push(kEntityCoudert, kEntityVerges, kAction167854368); + ENTITY_PARAM(2, 2) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(40, Coudert, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (ENTITY_PARAM(2, 3)) { + ENTITY_PARAM(0, 1) = 1; + ENTITY_PARAM(0, 3) = 0; + ENTITY_PARAM(0, 4) = 0; + ENTITY_PARAM(0, 5) = 0; + ENTITY_PARAM(0, 8) = 0; + + ENTITY_PARAM(1, 1) = 0; + + ENTITY_PARAM(2, 1) = 0; + ENTITY_PARAM(2, 2) = 0; + + getEntities()->drawSequenceLeft(kEntityCoudert, "697F"); + + params->param1 = 1; + params->param2 = 1; + + ENTITY_PARAM(2, 3) = 0; + } + + getData()->inventoryItem = (getProgress().eventCorpseFound || getEvent(kEventCoudertAskTylerCompartment)) ? kItemNone : kItemInvalid; + + if (ENTITY_PARAM(0, 8)) { + getData()->inventoryItem = kItemNone; + + setCallback(4); + setup_function15(true); + break; + } + +label_callback_4: + if (ENTITY_PARAM(1, 1)) { + getData()->inventoryItem = kItemNone; + + setCallback(5); + setup_function15(false); + break; + } + +label_callback_5: + if (ENTITY_PARAM(0, 6) || ENTITY_PARAM(0, 7)) { + getData()->inventoryItem = kItemNone; + setup_function37(); + break; + } + + if (ENTITY_PARAM(0, 3)) { + getData()->inventoryItem = kItemNone; + + setCallback(6); + setup_function14(kEntityVerges); + break; + } + +label_callback_6: + if (ENTITY_PARAM(0, 5)) { + getData()->inventoryItem = kItemNone; + + setCallback(7); + setup_function14(kEntityMertens); + break; + } + +label_callback_7: + if (ENTITY_PARAM(0, 4)) { + getData()->inventoryItem = kItemNone; + + setCallback(8); + setup_function14(kEntityMmeBoutarel); + break; + } + +label_callback_8: + if (ENTITY_PARAM(2, 2)) { + getData()->inventoryItem = kItemNone; + + setCallback(9); + setup_function39(); + break; + } + +label_callback_9: + if (ENTITY_PARAM(0, 1) && !getSound()->isBuffered(kEntityCoudert)) + getSound()->playSound(kEntityCoudert, rnd(2) ? "JAC1065" : "JAC1065A"); + + if (getState()->time > kTime1107000 && !ENTITY_PARAM(0, 1) && !getEvent(kEventVassiliSeizure)) { + getData()->inventoryItem = kItemNone; + + setCallback(10); + setup_function41(); + break; + } + +label_callback_10: + if (getState()->time > kTime1189800 && !ENTITY_PARAM(0, 1) && !ENTITY_PARAM(2, 1)) { + UPDATE_PARAM_PROC(params->param3, getState()->time, 2700); + ENTITY_PARAM(0, 2) = 1; + ENTITY_PARAM(0, 1) = 1; + + getEntities()->drawSequenceLeft(kEntityCoudert, "697F"); + + params->param3 = 0; + UPDATE_PARAM_PROC_END + } + + if (!ENTITY_PARAM(0, 2)) + break; + + TIME_CHECK_OBJECT(kTime1107000, params->param4, kObject111, kObjectLocation2); + TIME_CHECK_OBJECT(kTime1161000, params->param5, kObject111, kObjectLocation3); + TIME_CHECK_OBJECT(kTime1206000, params->param6, kObject111, kObjectLocation4); + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + + setCallback(11); + setup_savegame(kSavegameTypeEvent, kEventCoudertAskTylerCompartment); + break; + + case kAction11: + if (!ENTITY_PARAM(0, 1) && !ENTITY_PARAM(2, 1)) { + getData()->inventoryItem = kItemNone; + + setCallback(13); + setup_function13((bool)savepoint.param.intValue, savepoint.entity2); + } + break; + + case kActionDefault: + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_1500; + getData()->location = kLocationOutsideCompartment; + + getScenes()->loadSceneFromItemPosition(kItem5); + break; + + case kActionDrawScene: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + + if (!getEntities()->isPlayerPosition(kCarRedSleeping, 1) && !getEntities()->isPlayerPosition(kCarRedSleeping, 23)) + break; + + if (getProgress().jacket == kJacketBlood) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventCoudertBloodJacket); + } else { + setCallback(getEntities()->isPlayerPosition(kCarRedSleeping, 1) ? 2 : 3); + setup_function13(true, kEntityPlayer); + } + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + break; + + case 4: + goto label_callback_4; + + case 5: + goto label_callback_5; + + case 6: + goto label_callback_6; + + case 7: + goto label_callback_7; + + case 8: + goto label_callback_8; + + case 9: + goto label_callback_9; + + case 10: + params->param1 = 1; + goto label_callback_10; + + case 11: + getAction()->playAnimation(kEventCoudertAskTylerCompartment); + getEntities()->drawSequenceRight(kEntityCoudert, ENTITY_PARAM(0, 2) ? "627A" : "627D"); + getScenes()->loadSceneFromItemPosition(kItem5); + + ENTITY_PARAM(0, 1) = 0; + + getScenes()->loadSceneFromPosition(kCarRedSleeping, 25); + + setCallback(12); + setup_callbackActionOnDirection(); + break; + + case 12: + getEntities()->drawSequenceLeft(kEntityCoudert, ENTITY_PARAM(0, 2) ? "627B" : "627E"); + break; + + case 14: + setCallback(15); + setup_function18(); + break; + } + break; + + case kAction168253822: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + getData()->inventoryItem = kItemNone; + getSound()->playSound(kEntityCoudert, "JAC1120"); + + setCallback(14); + setup_bloodJacket("697D"); + } + break; + + case kAction225358684: + if (!ENTITY_PARAM(0, 1)) { + getData()->inventoryItem = kItemNone; + setCallback(16); + setup_function30((ObjectIndex)savepoint.param.intValue); + } + break; + + case kAction225932896: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) + getSavePoints()->push(kEntityCoudert, kEntityFrancois, kAction205346192); + break; + + case kAction305159806: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + getData()->inventoryItem = kItemNone; + setCallback(17); + setup_function31(savepoint.param.intValue); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(41, Coudert, function41) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_visitCompartmentA(); + break; + + case 2: + setCallback(3); + setup_function33(); + break; + + case 3: + setCallback(4); + setup_visitCompartmentB(); + break; + + case 4: + setCallback(5); + setup_function33(); + break; + + case 5: + setCallback(6); + setup_function27(); + break; + + case 6: + getSavePoints()->push(kEntityCoudert, kEntityRebecca, kAction285528346); + + setCallback(7); + setup_function33(); + break; + + case 7: + setCallback(8); + setup_function26(); + break; + + case 8: + setCallback(9); + setup_function33(); + break; + + case 9: + setCallback(10); + setup_function25(); + break; + + case 10: + setCallback(11); + setup_function33(); + break; + + case 11: + setCallback(12); + setup_function23(); + break; + + case 12: + setCallback(13); + setup_function33(); + break; + + case 13: + setCallback(14); + setup_function22(); + break; + + case 14: + setCallback(15); + setup_function33(); + break; + + case 15: + setCallback(16); + setup_function21(); + break; + + case 16: + setCallback(17); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 17: + setCallback(18); + setup_function18(); + break; + + case 18: + getSavePoints()->push(kEntityCoudert, kEntityMilos, kAction208228224); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(42, Coudert, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setCallback(1); + setup_function18(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityCoudert); + + getData()->entityPosition = kPosition_1500; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 2) = 0; + ENTITY_PARAM(0, 3) = 0; + ENTITY_PARAM(0, 4) = 0; + ENTITY_PARAM(0, 5) = 0; + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 8) = 0; + + ENTITY_PARAM(1, 1) = 0; + ENTITY_PARAM(1, 2) = 0; + ENTITY_PARAM(1, 3) = 0; + ENTITY_PARAM(1, 5) = 0; + ENTITY_PARAM(1, 6) = 0; + ENTITY_PARAM(1, 7) = 0; + ENTITY_PARAM(1, 8) = 0; + + ENTITY_PARAM(2, 4) = 0; + + getObjects()->updateLocation2(kObject111, kObjectLocation5); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function43(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(43, Coudert, function43) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (ENTITY_PARAM(0, 8)) { + setCallback(1); + setup_function15(true); + break; + } + +label_callback1: + if (!ENTITY_PARAM(1, 1)) { + setCallback(2); + setup_function15(false); + break; + } + +label_callback2: + if (ENTITY_PARAM(0, 3)) { + setCallback(3); + setup_function14(kEntityVerges); + } + break; + + case kAction11: + if (!ENTITY_PARAM(2, 1)) { + setCallback(4); + setup_function13((bool)savepoint.param.intValue, savepoint.entity2); + } + break; + + case kActionDrawScene: + if (ENTITY_PARAM(2, 1)) + break; + + if (getEntities()->isPlayerPosition(kCarRedSleeping, 1)) { + setCallback(5); + setup_function13(true, kEntityPlayer); + + } else if (getEntities()->isPlayerPosition(kCarRedSleeping, 23)) { + setCallback(6); + setup_function13(false, kEntityPlayer); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback1; + + case 2: + goto label_callback2; + + case 7: + setCallback(8); + setup_function18(); + break; + } + break; + + case kAction225358684: + if (!ENTITY_PARAM(0, 1)) { + setCallback(9); + setup_function30((ObjectIndex)savepoint.param.intValue); + } + break; + + case kAction226078300: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + getSound()->playSound(kEntityCoudert, "JAC2020"); + + setCallback(7); + setup_bloodJacket("697D"); + } + break; + + case kAction305159806: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + setCallback(10); + setup_function31(savepoint.param.intValue); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(44, Coudert, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setCallback(1); + setup_function18(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityCoudert); + + getData()->entityPosition = kPosition_1500; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 2) = 0; + ENTITY_PARAM(0, 3) = 0; + ENTITY_PARAM(0, 4) = 0; + ENTITY_PARAM(0, 5) = 0; + ENTITY_PARAM(0, 8) = 0; + + ENTITY_PARAM(1, 1) = 0; + ENTITY_PARAM(1, 2) = 0; + ENTITY_PARAM(1, 3) = 0; + ENTITY_PARAM(1, 4) = 0; + ENTITY_PARAM(1, 5) = 0; + ENTITY_PARAM(1, 6) = 0; + ENTITY_PARAM(1, 7) = 0; + ENTITY_PARAM(1, 8) = 0; + + ENTITY_PARAM(2, 4) = 0; + ENTITY_PARAM(2, 5) = 0; + + getObjects()->updateLocation2(kObject111, kObjectLocation6); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function45(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(45, Coudert, function45) + error("Coudert: callback function 45 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(46, Coudert, function46) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_4070); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityCoudert, "627Vf"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentF, true); + getSavePoints()->push(kEntityCoudert, kEntityAnna, kAction253868128); + + setCallback(3); + setup_playSound("LIB012"); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityCoudert, "627Wf"); + + setCallback(4); + setup_playSound("Ann1016A"); + break; + + case 4: + setCallback(5); + setup_playSound("Ann4150"); + break; + + case 5: + getSound()->playSound(kEntityCoudert, "Ann3121"); + + setCallback(6); + setup_enterExitCompartment("629Bf", kObjectCompartmentF); + break; + + case 6: + getEntities()->drawSequenceLeft(kEntityCoudert, "629Cf"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentF, true); + // Fallback to next case + + case 7: + if (getSound()->isBuffered(kEntityCoudert)) { + setCallback(7); + setup_updateFromTime(75); + } else { + setCallback(8); + setup_playSound("Ann3122"); + } + break; + + case 8: + getSound()->playSound(kEntityCoudert, "Ann3123"); + + setCallback(9); + setup_updateFromTicks(75); + break; + + case 9: + setCallback(10); + setup_enterExitCompartment("629Ff", kObjectCompartmentF); + break; + + case 10: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentF, true); + ENTITY_PARAM(1, 3) = 0; + + setCallback(11); + setup_function35(true); + break; + + case 11: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(47, Coudert, function47, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_4070); + break; + + case 2: + setCallback(3); + setup_enterExitCompartment("627Xf", kObjectCompartmentF); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityCoudert, "627Wf"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentF); + // Fallback to next case + + case 4: + if (getSound()->isBuffered(kEntityCoudert)) { + setCallback(4); + setup_updateFromTime(225); + } else { + setCallback(5); + setup_playSound(params->param1 ? "Ann3149" : "Ann3147a"); + } + break; + + case 5: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentF, true); + getSavePoints()->push(kEntityCoudert, kEntityAnna, kAction157894320); + + setCallback(6); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 6: + ENTITY_PARAM(1, 5) = 0; + ENTITY_PARAM(1, 6) = 0; + + setCallback(7); + setup_function18(); + break; + + case 7: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(48, Coudert, function48) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSound()->playSound(kEntityCoudert, "Ann3148A"); + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_4070); + break; + + case 2: + getSound()->playSound(kEntityCoudert, rnd(2) ? "Ann3148B" : "Ann3148"); + setCallback(3); + setup_enterExitCompartment("627Xf", kObjectCompartmentF); + break; + + case 3: + getSavePoints()->push(kEntityCoudert, kEntityAnna, kAction192063264); + setCallback(4); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 4: + ENTITY_PARAM(1, 8) = 0; + setCallback(5); + setup_function18(); + break; + + case 5: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(49, Coudert, function49) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_7500); + break; + + case 2: + setCallback(3); + setup_enterExitCompartment("627Vb", kObjectCompartmentB); + break; + + case 3: + if (getEntities()->isInsideCompartment(kEntityTatiana, kCarRedSleeping, kPosition_7500)) { + getEntities()->drawSequenceLeft(kEntityCoudert, "627Wb"); + + setCallback(4); + setup_playSound("Jac3006"); + } else { + getEntities()->drawSequenceLeft(kEntityCoudert, "627Wb"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentB, true); + + setCallback(8); + setup_playSound("LIB012"); + } + break; + + case 4: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentB, true); + + setCallback(5); + setup_enterExitCompartment("627Zb", kObjectCompartmentB); + break; + + case 5: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityCoudert); + + setCallback(6); + setup_playSound("Jac3006A"); + break; + + case 6: + setCallback(7); + setup_enterExitCompartment("697Ab", kObjectCompartmentB); + break; + + case 7: + getData()->location = kLocationOutsideCompartment; + + setCallback(10); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 8: + setCallback(9); + setup_updateFromTime(150); + break; + + case 9: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentB, true); + + setCallback(10); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 10: + getSavePoints()->push(kEntityCoudert, kEntityMmeBoutarel, kAction242526416); + ENTITY_PARAM(2, 4) = 0; + ENTITY_PARAM(2, 5) = 1; + + setCallback(11); + setup_function18(); + break; + + case 11: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(50, Coudert, function50) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_4840); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityCoudert, "627Me"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentE, true); + + setCallback(3); + setup_playSound("LIB012"); + break; + + case 3: + if (!getEntities()->isInsideCompartment(kEntityRebecca, kCarRedSleeping, kPosition_4840)) { + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentE, true); + + setCallback(8); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + } else { + getEntities()->drawSequenceLeft(kEntityCoudert, "627Ne"); + + setCallback(4); + setup_playSound("Jac3005"); + } + break; + + case 4: + setCallback(5); + setup_enterExitCompartment("627Re", kObjectCompartmentE); + break; + + case 5: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentE, true); + + getData()->location = kLocationInsideCompartment; + + getEntities()->clearSequences(kEntityCoudert); + + setCallback(6); + setup_playSound("Jac3005A"); + break; + + case 6: + setCallback(7); + setup_enterExitCompartment("627Se", kObjectCompartmentE); + break; + + case 7: + getData()->location = kLocationOutsideCompartment; + + setCallback(8); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 8: + ENTITY_PARAM(2, 5) = 0; + + setCallback(9); + setup_function18(); + break; + + case 9: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(51, Coudert, function51) + error("Coudert: callback function 51 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(52, Coudert, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setCallback(1); + setup_function18(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityCoudert); + + getData()->entityPosition = kPosition_1500; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 2) = 0; + ENTITY_PARAM(0, 3) = 0; + ENTITY_PARAM(0, 4) = 0; + ENTITY_PARAM(0, 5) = 0; + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 8) = 0; + + ENTITY_PARAM(1, 1) = 0; + ENTITY_PARAM(1, 3) = 0; + ENTITY_PARAM(1, 5) = 0; + ENTITY_PARAM(1, 6) = 0; + ENTITY_PARAM(1, 7) = 0; + ENTITY_PARAM(1, 8) = 0; + + ENTITY_PARAM(2, 3) = 0; + ENTITY_PARAM(2, 4) = 0; + + getObjects()->updateLocation2(kObject111, kObjectLocation10); + break; + + case kActionCallback: + if (getCallback() == 1) { + ENTITY_PARAM(1, 2) = 1; + setup_function53(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(53, Coudert, function53) + error("Coudert: callback function 53 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(54, Coudert, function54) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (getEntities()->hasValidFrame(kEntityCoudert)) { + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_540); + } else { + getData()->car = kCarLocomotive; + getData()->entityPosition = kPosition_540; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->clearSequences(kEntityCoudert); + getData()->car = kCarLocomotive; + break; + + case 2: + setCallback(3); + setup_function18(); + break; + + case 3: + CALLBACK_ACTION(); + break; + } + break; + + case kAction191001984: + getData()->car = kCarRedSleeping; + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_1500); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(55, Coudert, function55) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_playSound("LIB070"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function16(); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_4070); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityCoudert, "627Wf"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentF, true); + + setCallback(4); + setup_playSound("Ann4150A"); + break; + + case 4: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentF, true); + getSavePoints()->push(kEntityCoudert, kEntityAnna, kAction219971920); + getSavePoints()->push(kEntityCoudert, kEntityPascale, kAction101824388); + + setCallback(5); + setup_updateEntity(kCarRedSleeping, kPosition_9460); + break; + + case 5: + getEntities()->clearSequences(kEntityCoudert); + getSavePoints()->push(kEntityCoudert, kEntityPascale, kAction136059947); + break; + + case 6: + ENTITY_PARAM(1, 2) = 0; + + setCallback(7); + setup_function18(); + break; + + case 7: + CALLBACK_ACTION(); + break; + } + break; + + case kAction123712592: + setCallback(6); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(56, Coudert, function56) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function21(); + break; + + case 2: + setCallback(3); + setup_function33(); + break; + + case 3: + setCallback(4); + setup_function22(); + break; + + case 4: + setCallback(5); + setup_function33(); + break; + + case 5: + setCallback(6); + setup_visitCompartmentF(); + break; + + case 6: + setCallback(7); + setup_function33(); + break; + + case 7: + setCallback(8); + setup_function25(); + break; + + case 8: + setCallback(9); + setup_function33(); + break; + + case 9: + setCallback(10); + setup_function26(); + break; + + case 10: + setCallback(11); + setup_function33(); + break; + + case 11: + setCallback(12); + setup_function27(); + break; + + case 12: + setCallback(13); + setup_function33(); + break; + + case 13: + setCallback(14); + setup_visitCompartmentB(); + break; + + case 14: + setCallback(15); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 15: + setCallback(16); + setup_function18(); + break; + + case 16: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(57, Coudert, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityCoudert); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(58, Coudert, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_function59(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(59, Coudert, function59) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_7500; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + + getSound()->playSound(kEntityCoudert, "Jac5010"); // Situation is under control, please remain in your compartment + + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case kActionCallback: + if (getCallback() == 1) { + getEntities()->drawSequenceLeft(kEntityCoudert, "627K"); + setup_function60(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(60, Coudert, function60) + switch (savepoint.action) { + default: + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function61(); + break; + + case kAction155991520: + setCallback(1); + setup_updateFromTime(225); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(61, Coudert, function61) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_2088; + + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_4840); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("627Me", kObjectCompartmentE); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityCoudert, "627Ne"); + getEntities()->enterCompartment(kEntityCoudert, kObjectCompartmentE, true); + + setCallback(3); + setup_updateFromTime(75); + break; + + case 3: + getEntities()->exitCompartment(kEntityCoudert, kObjectCompartmentE, true); + + setCallback(4); + setup_enterExitCompartment("627Re", kObjectCompartmentE); + break; + + case 4: + getData()->location = kLocationInsideCompartment; + + getEntities()->clearSequences(kEntityCoudert); + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + setCallback(5); + setup_playSound("Reb5010"); + break; + + case 5: + setCallback(6); + setup_enterExitCompartment("627Se", kObjectCompartmentE); + break; + + case 6: + getSavePoints()->push(kEntityCoudert, kEntityRebecca, kAction155604840); + + getData()->location = kLocationOutsideCompartment; + + setCallback(7); + setup_updateEntity(kCarRedSleeping, kPosition_2740); + break; + + case 7: + setCallback(8); + setup_enterExitCompartment("627Zh", kObjectCompartmentH); + break; + + case 8: + getData()->location = kLocationInsideCompartment; + + getEntities()->clearSequences(kEntityCoudert); + getSavePoints()->push(kEntityCoudert, kEntityPascale, kAction169750080); + + setup_function62(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(62, Coudert, function62) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) { + UPDATE_PARAM(params->param4, getState()->timeTicks, 75); + + params->param1 = 0; + params->param2 = 1; + + getObjects()->update(kObjectCompartmentH, kEntityCoudert, kObjectLocation1, kCursorNormal, kCursorNormal); + } + + params->param4 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + if (params->param1) { + getObjects()->update(kObjectCompartmentH, kEntityCoudert, kObjectLocation1, kCursorNormal, kCursorNormal); + params->param1 = 0; + + setCallback(1); + setup_playSound(getSound()->justCheckingCath()); + } else { + setCallback(savepoint.action == kActionKnock ? 2 : 3); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentH, kEntityCoudert, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kActionDrawScene: + if (params->param1 || params->param2) { + params->param1 = 0; + params->param2 = 0; + params->param3 = 0; + + getObjects()->update(kObjectCompartmentH, kEntityCoudert, kObjectLocation1, kCursorHandKnock, kCursorHand); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentH, kEntityCoudert, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case 2: + case 3: + ++params->param3; + + if (params->param3 == 1 || params->param2) { + getObjects()->update(kObjectCompartmentH, kEntityCoudert, kObjectLocation1, kCursorNormal, kCursorNormal); + setCallback(params->param3 == 1 ? 4 : 5); + setup_playSound(params->param3 == 1 ? "Jac5002" : "Jac5002A"); + } + break; + + case 4: + params->param1 = 1; + getObjects()->update(kObjectCompartmentH, kEntityCoudert, kObjectLocation1, kCursorTalk, kCursorNormal); + break; + + case 5: + params->param2 = 1; + break; + } + break; + + case kAction135800432: + setup_nullfunction(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(63, Coudert) + + +////////////////////////////////////////////////////////////////////////// +// Private functions +////////////////////////////////////////////////////////////////////////// +void Coudert::visitCompartment(const SavePoint &savepoint, EntityPosition position, const char* seq1, ObjectIndex compartment, const char* seq2, const char* seq3, EntityPosition sittingPosition, ObjectIndex object, const char* seq4) { + EXPOSE_PARAMS(EntityData::EntityParametersIIII) + + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRedSleeping, position); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment(seq1, compartment); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityCoudert, seq2); + getEntities()->enterCompartment(kEntityCoudert, compartment, true); + + setCallback(3); + setup_updateFromTime(150); + break; + + case 3: + setCallback(4); + setup_enterExitCompartment2(seq3, compartment, position, sittingPosition); + break; + + case 4: + getEntities()->exitCompartment(kEntityCoudert, compartment, true); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityCoudert); + + setCallback(5); + setup_function20(compartment, object); + break; + + case 5: + setCallback(6); + setup_enterExitCompartment(seq4, compartment); + break; + + case 6: + getData()->location = kLocationOutsideCompartment; + CALLBACK_ACTION(); + break; + } + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/coudert.h b/engines/lastexpress/entities/coudert.h new file mode 100644 index 0000000000..e86e519385 --- /dev/null +++ b/engines/lastexpress/entities/coudert.h @@ -0,0 +1,229 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_COUDERT_H +#define LASTEXPRESS_COUDERT_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Coudert : public Entity { +public: + Coudert(LastExpressEngine *engine); + ~Coudert() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Handle meeting Coudert with the blooded jacket + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(bloodJacket, const char *sequence) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + * @param entityPosition1 The entity position 1 + * @param entityPosition2 The entity position 2 + */ + DECLARE_FUNCTION_4(enterExitCompartment2, const char* sequence, ObjectIndex compartment, EntityPosition entityPosition1, EntityPosition entityPosition2) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Plays sound + * + * @param savepoint The savepoint + * - the sound filename + */ + DECLARE_FUNCTION_NOSETUP(playSound16) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Updates parameter 2 using ticks value + * + * @param ticks The number of ticks to add + */ + DECLARE_FUNCTION_1(updateFromTicks, uint32 ticks) + + DECLARE_FUNCTION_1(excuseMe, EntityIndex entity) + DECLARE_FUNCTION_2(function13, bool, EntityIndex entity) + DECLARE_FUNCTION_1(function14, EntityIndex entity) + DECLARE_FUNCTION_1(function15, bool) + DECLARE_FUNCTION(function16) + DECLARE_FUNCTION_1(function17, bool) + DECLARE_FUNCTION(function18) + DECLARE_FUNCTION_1(function19, bool) + + /** + * ??? + * + * @param object1 The first object index + * @param object2 The second object index + */ + DECLARE_FUNCTION_2(function20, ObjectIndex object1, ObjectIndex object2) + + DECLARE_FUNCTION(function21) + DECLARE_FUNCTION(function22) + DECLARE_FUNCTION(function23) + DECLARE_FUNCTION(visitCompartmentF) + DECLARE_FUNCTION(function25) + DECLARE_FUNCTION(function26) + DECLARE_FUNCTION(function27) + DECLARE_FUNCTION(visitCompartmentB) + DECLARE_FUNCTION(visitCompartmentA) + + /** + * ??? + * + * @param compartment The compartment + */ + DECLARE_FUNCTION_1(function30, ObjectIndex compartment) + + DECLARE_FUNCTION_1(function31, uint32) + DECLARE_FUNCTION(function32) + DECLARE_FUNCTION(function33) + DECLARE_FUNCTION_1(function34, bool) + DECLARE_FUNCTION_1(function35, bool) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + DECLARE_FUNCTION(function37) + DECLARE_FUNCTION(function38) + DECLARE_FUNCTION(function39) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function41) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + DECLARE_FUNCTION(function43) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + DECLARE_FUNCTION(function45) + DECLARE_FUNCTION(function46) + DECLARE_FUNCTION_1(function47, bool) + DECLARE_FUNCTION(function48) + DECLARE_FUNCTION(function49) + DECLARE_FUNCTION(function50) + DECLARE_FUNCTION(function51) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + DECLARE_FUNCTION(function53) + DECLARE_FUNCTION(function54) + DECLARE_FUNCTION(function55) + DECLARE_FUNCTION(function56) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function59) + DECLARE_FUNCTION(function60) + DECLARE_FUNCTION(function61) + DECLARE_FUNCTION(function62) + + DECLARE_NULL_FUNCTION() + +private: + void visitCompartment(const SavePoint &savepoint, EntityPosition position, const char* seq1, ObjectIndex compartment, const char* seq2, const char* seq3, EntityPosition sittingPosition, ObjectIndex object, const char* seq4); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_COUDERT_H diff --git a/engines/lastexpress/entities/entity.cpp b/engines/lastexpress/entities/entity.cpp new file mode 100644 index 0000000000..8400c38737 --- /dev/null +++ b/engines/lastexpress/entities/entity.cpp @@ -0,0 +1,434 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/entity.h" + +#include "lastexpress/entities/entity_intern.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/state.h" +#include "lastexpress/game/savegame.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +////////////////////////////////////////////////////////////////////////// +// EntityData +////////////////////////////////////////////////////////////////////////// +EntityData::EntityParameters *EntityData::getParameters(uint callback, byte index) const { + if (callback >= 9) + error("EntityData::getParameters: invalid callback value (was: %d, max: 9)", callback); + + if (index >= 4) + error("EntityData::getParameters: invalid index value (was: %d, max: 4)", index); + + return _parameters[callback].parameters[index]; +} + +int EntityData::getCallback(uint callback) const { + if (callback >= 16) + error("EntityData::getParameters: invalid callback value (was: %d, max: 16)", callback); + + return _data.callbacks[callback]; +} + +void EntityData::setCallback(uint callback, byte index) { + if (callback >= 16) + error("EntityData::getParameters: invalid callback value (was: %d, max: 16)", callback); + + _data.callbacks[callback] = index; +} + +void EntityData::updateParameters(uint32 index) const { + if (index < 8) + getParameters(8, 0)->update(index); + else if (index < 16) + getParameters(8, 1)->update(index - 8); + else if (index < 24) + getParameters(8, 2)->update(index - 16); + else if (index < 32) + getParameters(8, 3)->update(index - 24); + else + error("EntityData::updateParameters: invalid param index to update (was:%d, max:32)!", index); +} + +void EntityData::saveLoadWithSerializer(Common::Serializer &) { + error("EntityData::saveLoadWithSerializer: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// Entity +////////////////////////////////////////////////////////////////////////// +Entity::Entity(LastExpressEngine *engine, EntityIndex index) : _engine(engine), _entityIndex(index) { + _data = new EntityData(); + + // Add first empty entry to callbacks array + _callbacks.push_back(NULL); +} + +Entity::~Entity() { + for (uint i = 0; i < _callbacks.size(); i++) + delete _callbacks[i]; + + delete _data; + + // Zero-out passed pointers + _engine = NULL; +} + +void Entity::setup(ChapterIndex index) { + switch(index) { + case kChapterAll: + getSavePoints()->setCallback(_entityIndex, _callbacks[_data->getCurrentCallback()]); + break; + + case kChapter1: + setup_chapter1(); + break; + + case kChapter2: + setup_chapter2(); + break; + + case kChapter3: + setup_chapter3(); + break; + + case kChapter4: + setup_chapter4(); + break; + + case kChapter5: + setup_chapter5(); + break; + + default: + break; + } +} + +////////////////////////////////////////////////////////////////////////// +// Shared functions +////////////////////////////////////////////////////////////////////////// + +void Entity::reset(const SavePoint &savepoint, bool resetClothes, bool resetItem) { + EXPOSE_PARAMS(EntityData::EntityParametersIIII) + + switch (savepoint.action) { + default: + break; + + case kAction1: + if (resetClothes) { + // Select next available clothes + getData()->clothes = (ClothesIndex)(getData()->clothes + 1); + if (getData()->clothes > kClothes3) + getData()->clothes = kClothesDefault; + } + break; + + case kActionNone: + if (getEntities()->updateEntity(_entityIndex, kCarGreenSleeping, (EntityPosition)params->param1)) + params->param1 = (params->param1 == 10000) ? 0 : 10000; + break; + + case kActionDefault: + getData()->entityPosition = kPositionNone; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + + if (resetItem) + getData()->inventoryItem = kItemInvalid; + + params->param1 = 10000; + break; + } +} + +void Entity::savegame(const SavePoint &savepoint) { + EXPOSE_PARAMS(EntityData::EntityParametersIIII) + + switch (savepoint.action) { + default: + break; + + case kActionNone: + CALLBACK_ACTION(); + break; + + case kActionDefault: + getSaveLoad()->saveGame((SavegameType)params->param1, _entityIndex, (EventIndex)params->param2); + CALLBACK_ACTION(); + break; + } +} + +void Entity::playSound(const SavePoint &savepoint, bool resetItem, SoundManager::FlagType flag) { + EXPOSE_PARAMS(EntityData::EntityParametersSIIS) + + switch (savepoint.action) { + default: + break; + + case kActionEndSound: + CALLBACK_ACTION(); + break; + + case kActionDefault: + if (resetItem) + getData()->inventoryItem = kItemNone; + + getSound()->playSound(_entityIndex, (char *)¶ms->seq1, flag); + break; + } +} + +void Entity::draw(const SavePoint &savepoint, bool handleExcuseMe) { + EXPOSE_PARAMS(EntityData::EntityParametersSIIS) + + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + CALLBACK_ACTION(); + break; + + case kActionExcuseMeCath: + if (handleExcuseMe && !params->param4) { + getSound()->excuseMe(_entityIndex); + params->param4 = 1; + } + break; + + case kActionDefault: + getEntities()->drawSequenceRight(_entityIndex, (char *)¶ms->seq1); + break; + } +} + +void Entity::draw2(const SavePoint &savepoint) { + EXPOSE_PARAMS(EntityData::EntityParametersSSII) + + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(_entityIndex, (char *)¶ms->seq1); + getEntities()->drawSequenceRight((EntityIndex)params->param7, (char *)¶ms->seq2); + break; + } +} + +void Entity::updateFromTicks(const SavePoint &savepoint) { + EXPOSE_PARAMS(EntityData::EntityParametersIIII) + + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param2, getState()->timeTicks, params->param1) + CALLBACK_ACTION(); + break; + } +} + +void Entity::updateFromTime(const SavePoint &savepoint) { + EXPOSE_PARAMS(EntityData::EntityParametersIIII) + + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param2, getState()->time, params->param1) + CALLBACK_ACTION(); + break; + } +} + +void Entity::callbackActionOnDirection(const SavePoint &savepoint) { + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + CALLBACK_ACTION(); + break; + + case kActionDefault: + if (getData()->direction != kDirectionRight) + CALLBACK_ACTION(); + break; + } +} + +void Entity::callbackActionRestaurantOrSalon(const SavePoint &savepoint) { + switch (savepoint.action) { + default: + break; + + case kActionNone: + case kActionDefault: + if (getEntities()->isSomebodyInsideRestaurantOrSalon()) + CALLBACK_ACTION(); + break; + } +} + +void Entity::updateEntity(const SavePoint &savepoint, bool handleExcuseMe) { + EXPOSE_PARAMS(EntityData::EntityParametersIIII) + + switch (savepoint.action) { + default: + break; + + case kActionExcuseMeCath: + if (handleExcuseMe) + getSound()->excuseMeCath(); + break; + + case kActionExcuseMe: + if (handleExcuseMe) + getSound()->excuseMe(_entityIndex); + break; + + case kActionNone: + case kActionDefault: + if (getEntities()->updateEntity(_entityIndex, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + } +} + +void Entity::callSavepoint(const SavePoint &savepoint, bool handleExcuseMe) { + EXPOSE_PARAMS(EntityData::EntityParametersSIIS) + + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + if (!CURRENT_PARAMS(1, 1)) + getSavePoints()->call(_entityIndex, (EntityIndex)params->param4, (ActionIndex)params->param5, (char *)¶ms->seq2); + CALLBACK_ACTION(); + break; + + case kActionExcuseMeCath: + if (handleExcuseMe && !CURRENT_PARAMS(1, 2)) { + getSound()->excuseMe(_entityIndex); + CURRENT_PARAMS(1, 2) = 1; + } + break; + + case kAction10: + if (!CURRENT_PARAMS(1, 1)) { + getSavePoints()->call(_entityIndex, (EntityIndex)params->param4, (ActionIndex)params->param5, (char *)¶ms->seq2); + CURRENT_PARAMS(1, 1) = 1; + } + break; + + case kActionDefault: + getEntities()->drawSequenceRight(_entityIndex, (char *)¶ms->seq1); + break; + } +} + +void Entity::enterExitCompartment(const SavePoint &savepoint, EntityPosition position1, EntityPosition position2, CarIndex car, ObjectIndex compartment, bool alternate, bool updateLocation) { + EXPOSE_PARAMS(EntityData::EntityParametersSIIS) + + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getEntities()->exitCompartment(_entityIndex, (ObjectIndex)params->param4); + if (position1) + getData()->entityPosition = position1; + + if (updateLocation) + getData()->location = kLocationInsideCompartment; + + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(_entityIndex, (char *)¶ms->seq1); + getEntities()->enterCompartment(_entityIndex, (ObjectIndex)params->param4); + + if (position1) { + getData()->location = kLocationInsideCompartment; + + if (getEntities()->isInsideCompartment(kEntityPlayer, car, position1) || getEntities()->isInsideCompartment(kEntityPlayer, car, position2)) { + getAction()->playAnimation(isNight() ? kEventCathTurningNight : kEventCathTurningDay); + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromObject(compartment, alternate); + } + } + break; + } +} + +void Entity::updatePosition(const SavePoint &savepoint, bool handleExcuseMe) { + EXPOSE_PARAMS(EntityData::EntityParametersSIII) + + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getEntities()->updatePositionExit(_entityIndex, (CarIndex)params->param4, (Position)params->param5); + CALLBACK_ACTION(); + break; + + case kActionExcuseMeCath: + if (handleExcuseMe && !params->param6) { + getSound()->excuseMe(_entityIndex); + params->param6 = 1; + } + break; + + case kActionDefault: + getEntities()->drawSequenceRight(_entityIndex, (char *)¶ms->seq); + getEntities()->updatePositionEnter(_entityIndex, (CarIndex)params->param4, (Position)params->param5); + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/entity.h b/engines/lastexpress/entities/entity.h new file mode 100644 index 0000000000..b0a1ab68ea --- /dev/null +++ b/engines/lastexpress/entities/entity.h @@ -0,0 +1,681 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ENTITY_H +#define LASTEXPRESS_ENTITY_H + +#include "lastexpress/shared.h" + +#include "lastexpress/game/sound.h" + +#include "common/array.h" +#include "common/func.h" +#include "common/serializer.h" + +namespace LastExpress { + +class LastExpressEngine; +class Sequence; +class SequenceFrame; +struct SavePoint; + +class EntityData : Common::Serializable { +public: + + struct EntityParameters { + virtual ~EntityParameters() {} + virtual Common::String toString() = 0; + + virtual void update(uint32 index) = 0; + }; + + struct EntityParametersIIII : EntityParameters { + uint param1; + uint param2; + uint param3; + uint param4; + uint param5; + uint param6; + uint param7; + uint param8; + + EntityParametersIIII() { + param1 = 0; + param2 = 0; + param3 = 0; + param4 = 0; + param5 = 0; + param6 = 0; + param7 = 0; + param8 = 0; + } + + bool hasNonNullParameter() { + return param1 || param2 || param3 || param4 || param5 || param6 || param7 || param8; + } + + Common::String toString() { + return Common::String::printf("IIII: %d %d %d %d %d %d %d %d\n", param1, param2, param3, param4, param5, param6, param7, param8); + } + + void update(uint32 index) { + switch (index) { + default: + error("EntityParametersIIII::update: invalid index (was: %d)", index); + + case 0: param1 = 1; break; + case 1: param2 = 1; break; + case 2: param3 = 1; break; + case 3: param4 = 1; break; + case 4: param5 = 1; break; + case 5: param6 = 1; break; + case 6: param7 = 1; break; + case 7: param8 = 1; break; + } + } + }; + + struct EntityParametersSIII : EntityParameters { + char seq[12]; + uint param4; + uint param5; + uint param6; + uint param7; + uint param8; + + EntityParametersSIII() { + memset(&seq, 0, 12); + param4 = 0; + param5 = 0; + param6 = 0; + param7 = 0; + param8 = 0; + } + + Common::String toString() { + return Common::String::printf("SIII: %s %d %d %d %d %d\n", seq, param4, param5, param6, param7, param8); + } + + void update(uint32 index) { + switch (index) { + default: + error("EntityParametersSIII::update: invalid index (was: %d)", index); + + case 3: param4 = 1; break; + case 4: param5 = 1; break; + case 5: param6 = 1; break; + case 6: param7 = 1; break; + case 7: param8 = 1; break; + } + } + }; + + struct EntityParametersSIIS : EntityParameters { + char seq1[12]; + uint param4; + uint param5; + char seq2[12]; + + EntityParametersSIIS() { + memset(&seq1, 0, 12); + param4 = 0; + param5 = 0; + memset(&seq2, 0, 12); + } + + Common::String toString() { + return Common::String::printf("SIIS: %s %d %d %s\n", seq1, param4, param5, seq2); + } + + void update(uint32 index) { + switch (index) { + default: + error("EntityParametersSIIS::update: invalid index (was: %d)", index); + + case 3: param4 = 1; break; + case 4: param5 = 1; break; + } + } + }; + + struct EntityParametersISSI : EntityParameters { + uint param1; + char seq1[12]; + char seq2[12]; + uint param8; + + EntityParametersISSI() { + param1 = 0; + memset(&seq1, 0, 12); + memset(&seq2, 0, 12); + param8 = 0; + } + + Common::String toString() { + return Common::String::printf("ISSI: %d %s %s %d\n", param1, seq1, seq2, param8); + } + + void update(uint32 index) { + switch (index) { + default: + error("EntityParametersISSI::update: invalid index (was: %d)", index); + + case 0: param1 = 1; break; + case 7: param8 = 1; break; + } + } + }; + + struct EntityParametersISII : EntityParameters { + uint param1; + char seq[12]; + uint param5; + uint param6; + uint param7; + uint param8; + + EntityParametersISII() { + param1 = 0; + memset(&seq, 0, 12); + param5 = 0; + param6 = 0; + param7 = 0; + param8 = 0; + } + + Common::String toString() { + return Common::String::printf("ISII: %d %s %d %d %d %d\n", param1, seq, param5, param6, param7, param8); + } + + void update(uint32 index) { + switch (index) { + default: + error("EntityParametersISII::update: invalid index (was: %d)", index); + + case 0: param1 = 1; break; + case 4: param5 = 1; break; + case 5: param6 = 1; break; + case 6: param7 = 1; break; + case 7: param8 = 1; break; + } + } + }; + + struct EntityParametersSSII : EntityParameters { + char seq1[12]; + char seq2[12]; + uint param7; + uint param8; + + EntityParametersSSII() { + memset(&seq1, 0, 12); + memset(&seq2, 0, 12); + param7 = 0; + param8 = 0; + } + + Common::String toString() { + return Common::String::printf("SSII: %s %s %d %d\n", seq1, seq2, param7, param8); + } + + void update(uint32 index) { + switch (index) { + default: + error("EntityParametersSSII::update: invalid index (was: %d)", index); + + case 6: param7 = 1; break; + case 7: param8 = 1; break; + } + } + }; + + struct EntityParametersIISS : EntityParameters { + uint param1; + uint param2; + char seq1[12]; + char seq2[12]; + + EntityParametersIISS() { + param1 = 0; + param2 = 0; + memset(&seq1, 0, 12); + memset(&seq2, 0, 12); + } + + Common::String toString() { + return Common::String::printf("IISS: %d %d %s %s\n", param1, param2, seq1, seq2); + } + + void update(uint32 index) { + switch (index) { + default: + error("EntityParametersIISS::update: invalid index (was: %d)", index); + + case 0: param1 = 1; break; + case 1: param2 = 1; break; + } + } + }; + + struct EntityParametersIISI : EntityParameters { + uint param1; + uint param2; + char seq[12]; + uint param6; + uint param7; + uint param8; + + EntityParametersIISI() { + param1 = 0; + param2 = 0; + memset(&seq, 0, 12); + param6 = 0; + param7 = 0; + param8 = 0; + } + + Common::String toString() { + return Common::String::printf("IISI: %d %d %s %d %d %d\n", param1, param2, seq, param6, param7, param8); + } + + void update(uint32 index) { + switch (index) { + default: + error("EntityParametersIISI::update: invalid index (was: %d)", index); + + case 0: param1 = 1; break; + case 1: param2 = 1; break; + case 5: param6 = 1; break; + case 6: param7 = 1; break; + case 7: param8 = 1; break; + } + } + }; + + struct EntityParametersIIIS : EntityParameters { + uint param1; + uint param2; + uint param3; + char seq[12]; + uint param7; + uint param8; + + EntityParametersIIIS() { + param1 = 0; + param2 = 0; + param3 = 0; + memset(&seq, 0, 12); + param7 = 0; + param8 = 0; + } + + Common::String toString() { + return Common::String::printf("IIIS: %d %d %d %s %d %d\n", param1, param2, param3, seq, param7, param8); + } + + void update(uint32 index) { + switch (index) { + default: + error("EntityParametersIIIS::update: invalid index (was: %d)", index); + + case 0: param1 = 1; break; + case 1: param2 = 1; break; + case 2: param3 = 1; break; + case 6: param7 = 1; break; + case 7: param8 = 1; break; + } + } + }; + + struct EntityParametersI5S : EntityParameters { + uint param1; + uint param2; + uint param3; + uint param4; + uint param5; + char seq[12]; + + EntityParametersI5S() { + param1 = 0; + param2 = 0; + param3 = 0; + param4 = 0; + param5 = 0; + memset(&seq, 0, 12); + } + }; + + struct EntityCallParameters { + EntityParameters* parameters[4]; + + EntityCallParameters() { + // We default to int parameters + create<EntityParametersIIII>(); + } + + ~EntityCallParameters() { + clear(); + } + + template <class parameter> + void create() { + for (int i = 0; i < 4; i++) + parameters[i] = new parameter(); + } + + void clear() { + for (int i = 0; i < 4; i++) { + if (parameters[i]) + delete parameters[i]; + parameters[i] = NULL; + } + } + }; + + struct EntityCallData { + byte callbacks[16]; + byte currentCall; + EntityPosition entityPosition; // word + Location location; // word + CarIndex car; // word + byte field_497; + EntityIndex entity; // byte + InventoryItem inventoryItem; // byte + EntityDirection direction; // byte + int16 field_49B; + int16 currentFrame; + int16 currentFrame2; + int16 field_4A1; + int16 field_4A3; + ClothesIndex clothes; // byte + Position position; + CarIndex car2; // byte + bool doProcessEntity; // byte + bool field_4A9; // byte + bool field_4AA; // byte + EntityDirection directionSwitch; + Common::String sequenceName; // char[13] + Common::String sequenceName2; // char[13] + Common::String sequenceNamePrefix; // char[7] + Common::String sequenceNameCopy; // char[13] + SequenceFrame *frame; + SequenceFrame *frame1; + Sequence *sequence; + Sequence *sequence2; + Sequence *sequence3; + + /** + * Default constructor. + */ + EntityCallData() { + memset(&callbacks, 0, 16 * sizeof(byte)); + currentCall = 0; + entityPosition = kPositionNone; + location = kLocationOutsideCompartment; + car = kCarNone; + field_497 = 0; + entity = kEntityPlayer; + inventoryItem = kItemNone; + direction = kDirectionNone; + field_49B = 0; + currentFrame = 0; + currentFrame2 = 0; + field_4A1 = 0; + field_4A3 = 30; + clothes = kClothesDefault; + position = 0; + car2 = kCarNone; + doProcessEntity = false; + field_4A9 = false; + field_4AA = false; + directionSwitch = kDirectionNone; + frame = NULL; + frame1 = NULL; + sequence = NULL; + sequence2 = NULL; + sequence3 = NULL; + } + + /** + * Convert this object into a string representation. + * + * @return A string representation of this object. + */ + Common::String toString() { + Common::String str = ""; + + str += Common::String::printf("Entity position: %d - Location: %d - Car: %d\n", entityPosition, location, car); + str += Common::String::printf("Entity: %d - Item: %d - Direction: %d\n", entity, inventoryItem, direction); + str += Common::String::printf("Clothes: %d - Position: %d - Direction switch: %d\n", clothes, position, directionSwitch); + str += "\n"; + str += Common::String::printf("field_497: %02d - field_49B: %i - field_4A1: %i\n", field_497, field_49B, field_4A1); + str += Common::String::printf("field_4A9: %02d - field_4AA: %i - Car 2: %d\n", field_4A9, field_4AA, car2); + str += "\n"; + str += "Sequence: " + sequenceName + " - Sequence 2: " + sequenceName2 + "\n"; + str += "Sequence prefix: " + sequenceNamePrefix + " - Sequence copy: " + sequenceNameCopy + "\n"; + str += Common::String::printf("Current frame: %i - Current frame 2: %i - Process entity: %d\n", currentFrame, currentFrame2, doProcessEntity); + str += "\n"; + str += Common::String::printf("Current call: %d\n", currentCall); + str += Common::String::printf("Functions: %d %d %d %d %d %d %d %d\n", callbacks[0], callbacks[1], callbacks[2], callbacks[3], callbacks[4], callbacks[5], callbacks[6], callbacks[7]); + str += Common::String::printf("Callbacks: %d %d %d %d %d %d %d %d\n", callbacks[8], callbacks[9], callbacks[10], callbacks[11], callbacks[12], callbacks[13], callbacks[14], callbacks[15]); + + return str; + } + }; + + EntityData() {} + + template <class parameter> + void resetCurrentParameters() { + _parameters[_data.currentCall].clear(); + _parameters[_data.currentCall].create<parameter>(); + } + + EntityCallData *getCallData() { return &_data; } + + EntityParameters *getParameters(uint callback, byte index) const; + EntityParameters *getCurrentParameters(byte index = 0) { return getParameters(_data.currentCall, index); } + + int getCallback(uint callback) const; + int getCurrentCallback() { return getCallback(_data.currentCall); } + void setCallback(uint callback, byte index); + void setCurrentCallback(uint index) { setCallback(_data.currentCall, index); } + + void updateParameters(uint32 index) const; + + // Serializable + void saveLoadWithSerializer(Common::Serializer &ser); + +private: + + EntityCallData _data; + EntityCallParameters _parameters[9]; +}; + +class Entity : Common::Serializable { +public: + + typedef Common::Functor1<const SavePoint&, void> Callback; + + Entity(LastExpressEngine *engine, EntityIndex index); + virtual ~Entity(); + + // Accessors + EntityData *getParamData() { return _data; } + EntityData::EntityCallData *getData() { return _data->getCallData(); } + + // Callbacks + int getCallback() { return _data->getCallback(_data->getCallData()->currentCall + 8); } + void setCallback(byte index) { _data->setCallback(_data->getCallData()->currentCall + 8, index); getData()->currentCall++; } + + // Setup + void setup(ChapterIndex index); + + virtual void setup_chapter1() = 0; + virtual void setup_chapter2() = 0; + virtual void setup_chapter3() = 0; + virtual void setup_chapter4() = 0; + virtual void setup_chapter5() = 0; + + // Serializable + void saveLoadWithSerializer(Common::Serializer &ser) { _data->saveLoadWithSerializer(ser); } + + void nullfunction(const SavePoint &savepoint) {} + +protected: + LastExpressEngine* _engine; + + EntityIndex _entityIndex; + EntityData *_data; + Common::Array<Callback *> _callbacks; + + /** + * Saves the game + * + * @param savepoint The savepoint + * - SavegameType + * - EventIndex + */ + void savegame(const SavePoint &savepoint); + + /** + * Play sound + * + * @param savepoint The savepoint + * - Sound filename + * @param resetItem true to reset item. + * @param flag sound flag + */ + void playSound(const SavePoint &savepoint, bool resetItem = false, SoundManager::FlagType flag = SoundManager::kFlagInvalid); + + /** + * Draws the entity + * + * @param savepoint The savepoint + * - Sequence + * - ExcuseMe flag + * @param handleExcuseMe true to handle excuseMeCath action + */ + void draw(const SavePoint &savepoint, bool handleExcuseMe = false); + + /** + * Draws the entity along with another one + * + * @param savepoint The savepoint. + * - Sequence 1 + * - Sequence 2 + * - EntityIndex + */ + void draw2(const SavePoint &savepoint); + + /** + * Updates parameter 2 using ticks value + * + * @param savepoint The savepoint + * - Number of ticks to add + */ + void updateFromTicks(const SavePoint &savepoint); + + /** + * Updates parameter 2 using time value + * + * @param savepoint The savepoint. + * - Time to add + */ + void updateFromTime(const SavePoint &savepoint); + + /** + * Resets an entity + * + * @param savepoint The savepoint. + * @param resetClothes true to reset clothes. + * @param resetItem true to reset inventoryItem to kItemInvalid + */ + void reset(const SavePoint &savepoint, bool resetClothes = false, bool resetItem = false); + + /** + * Process callback action when the entity direction is not kDirectionRight + * + * @param savepoint The savepoint. + */ + void callbackActionOnDirection(const SavePoint &savepoint); + + /** + * Process callback action when somebody is standing in the restaurant or salon. + * + * @param savepoint The savepoint. + */ + void callbackActionRestaurantOrSalon(const SavePoint &savepoint); + + /** + * Updates the entity + * + * @param savepoint The savepoint. + * - CarIndex + * - EntityPosition + * @param handleExcuseMe true to handle the kActionExcuseMe/kActionExcuseMeCath actions. + */ + void updateEntity(const SavePoint &savepoint, bool handleExcuseMe = false); + + /** + * Call a specific savepoint (or draw sequence in default case) + * + * @param savepoint The savepoint. + * - Sequence to draw in default case + * - EntityIndex + * - ActionIndex + * - Sequence for the savepoint + * @param handleExcuseMe true to handle excuse me. + */ + void callSavepoint(const SavePoint &savepoint, bool handleExcuseMe = false); + + /** + * Handles entering/exiting a compartment. + * + * @param savepoint The savepoint. + * @param position1 The first position. + * @param position2 The second position. + * @param car The car. + * @param compartment The compartment. + * @param alternate true to use the alternate version of SceneManager::loadSceneFromObject() + */ + void enterExitCompartment(const SavePoint &savepoint, EntityPosition position1 = kPositionNone, EntityPosition position2 = kPositionNone, CarIndex car = kCarNone, ObjectIndex compartment = kObjectNone, bool alternate = false, bool updateLocation = false); + + /** + * Updates the position + * + * @param savepoint The savepoint + * - Sequence name + * - CarIndex + * - Position + * @param handleExcuseMe true to handle excuseMe actions + */ + void updatePosition(const SavePoint &savepoint, bool handleExcuseMe = false); +}; + + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_ENTITY_H diff --git a/engines/lastexpress/entities/entity39.cpp b/engines/lastexpress/entities/entity39.cpp new file mode 100644 index 0000000000..6d139094c7 --- /dev/null +++ b/engines/lastexpress/entities/entity39.cpp @@ -0,0 +1,103 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/entity39.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +Entity39::Entity39(LastExpressEngine *engine) : Entity(engine, kEntity39) { + ADD_CALLBACK_FUNCTION(Entity39, chapter1); + ADD_CALLBACK_FUNCTION(Entity39, chapter2); + ADD_CALLBACK_FUNCTION(Entity39, chapter3); + ADD_CALLBACK_FUNCTION(Entity39, chapter4); + ADD_CALLBACK_FUNCTION(Entity39, chapter5); + ADD_CALLBACK_FUNCTION(Entity39, process); + + memset(&_sequence, 0, 12); + _counter = 0; +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Entity39, chapter1) + if (savepoint.action == kActionDefault) + setup_process(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(2, Entity39, chapter2) + if (savepoint.action == kActionDefault) + setup_process(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(3, Entity39, chapter3) + if (savepoint.action == kActionDefault) + setup_process(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(4, Entity39, chapter4) + if (savepoint.action == kActionDefault) + setup_process(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(5, Entity39, chapter5) + if (savepoint.action == kActionDefault) + setup_process(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6, Entity39, process) +// TODO: _sequence & counter do not seem to be touched anywhere else in the code :( + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getEntities()->drawSequenceRight(kEntity39, (char *)&_sequence); + break; + + case kActionNone: + getData()->car = getEntityData(kEntityPlayer)->car; + + if (*_sequence && !_counter) { + _counter++; + getEntities()->drawSequenceRight(kEntity39, (char *)&_sequence); + } + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/entity39.h b/engines/lastexpress/entities/entity39.h new file mode 100644 index 0000000000..652dbd139e --- /dev/null +++ b/engines/lastexpress/entities/entity39.h @@ -0,0 +1,78 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ENTITY39_H +#define LASTEXPRESS_ENTITY39_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Entity39 : public Entity { +public: + Entity39(LastExpressEngine *engine); + ~Entity39() {}; + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Process function + */ + DECLARE_FUNCTION(process) + +private: + char _sequence[12]; + int _counter; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_##define##_H diff --git a/engines/lastexpress/entities/entity_intern.h b/engines/lastexpress/entities/entity_intern.h new file mode 100644 index 0000000000..6442f76b3c --- /dev/null +++ b/engines/lastexpress/entities/entity_intern.h @@ -0,0 +1,577 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ENTITY_INTERN_H +#define LASTEXPRESS_ENTITY_INTERN_H + +namespace LastExpress { + +#define LOBYTE(w) ((unsigned char)(((unsigned long)(w)) & 0xff)) + +////////////////////////////////////////////////////////////////////////// +// Callbacks +#define ENTITY_CALLBACK(class, name, pointer) \ + Common::Functor1Mem<const SavePoint&, void, class>(pointer, &class::name) + +#define ADD_CALLBACK_FUNCTION(class, name) \ + _callbacks.push_back(new ENTITY_CALLBACK(class, name, this)); + +#define ADD_NULL_FUNCTION() \ + _callbacks.push_back(new ENTITY_CALLBACK(Entity, nullfunction, this)); + +////////////////////////////////////////////////////////////////////////// +// Declaration +////////////////////////////////////////////////////////////////////////// + +#define DECLARE_FUNCTION(name) \ + void setup_##name(); \ + void name(const SavePoint &savepoint); + +#define DECLARE_FUNCTION_1(name, param1) \ + void setup_##name(param1); \ + void name(const SavePoint &savepoint); + +#define DECLARE_FUNCTION_2(name, param1, param2) \ + void setup_##name(param1, param2); \ + void name(const SavePoint &savepoint); + +#define DECLARE_FUNCTION_3(name, param1, param2, param3) \ + void setup_##name(param1, param2, param3); \ + void name(const SavePoint &savepoint); + +#define DECLARE_FUNCTION_4(name, param1, param2, param3, param4) \ + void setup_##name(param1, param2, param3, param4); \ + void name(const SavePoint &savepoint); + +#define DECLARE_FUNCTION_NOSETUP(name) \ + void name(const SavePoint &savepoint); + +#define DECLARE_NULL_FUNCTION() \ + void setup_nullfunction(); + +////////////////////////////////////////////////////////////////////////// +// Setup +////////////////////////////////////////////////////////////////////////// + +#define IMPLEMENT_SETUP(class, callback_class, name, index) \ +void class::setup_##name() { \ + BEGIN_SETUP(callback_class, name, index, EntityData::EntityParametersIIII) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::setup_" #name "()"); \ + END_SETUP() \ +} + +#define BEGIN_SETUP(class, name, index, type) \ + _engine->getGameLogic()->getGameState()->getGameSavePoints()->setCallback(_entityIndex, new ENTITY_CALLBACK(class, name, this)); \ + _data->setCurrentCallback(index); \ + _data->resetCurrentParameters<type>(); + +#define END_SETUP() \ + _engine->getGameLogic()->getGameState()->getGameSavePoints()->call(_entityIndex, _entityIndex, kActionDefault); + + +////////////////////////////////////////////////////////////////////////// +// Implementation +////////////////////////////////////////////////////////////////////////// + +// Expose parameters and check validity +#define EXPOSE_PARAMS(type) \ + type *params = (type*)_data->getCurrentParameters(); \ + if (!params) \ + error("Trying to call an entity function with invalid parameters!"); \ + + +// function signature without setup (we keep the index for consistency but never use it) +#define IMPLEMENT_FUNCTION_NOSETUP(index, class, name) \ + void class::name(const SavePoint &savepoint) { \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(index=" #index ")"); + +// simple setup with no parameters +#define IMPLEMENT_FUNCTION(index, class, name) \ + IMPLEMENT_SETUP(class, class, name, index) \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersIIII) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "() - action: %s", ACTION_NAME(savepoint.action)); + +// nullfunction call +#define IMPLEMENT_NULL_FUNCTION(index, class) \ + IMPLEMENT_SETUP(class, Entity, nullfunction, index) + +// setup with one uint parameter +#define IMPLEMENT_FUNCTION_I(index, class, name, paramType) \ + void class::setup_##name(paramType param1) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersIIII) \ + EntityData::EntityParametersIIII *params = (EntityData::EntityParametersIIII*)_data->getCurrentParameters(); \ + params->param1 = (unsigned int)param1; \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersIIII) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%d) - action: %s", params->param1, ACTION_NAME(savepoint.action)); + +// setup with two uint parameters +#define IMPLEMENT_FUNCTION_II(index, class, name, paramType1, paramType2) \ + void class::setup_##name(paramType1 param1, paramType2 param2) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersIIII) \ + EntityData::EntityParametersIIII *params = (EntityData::EntityParametersIIII*)_data->getCurrentParameters(); \ + params->param1 = param1; \ + params->param2 = param2; \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersIIII) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%d, %d) - action: %s", params->param1, params->param2, ACTION_NAME(savepoint.action)); + +// setup with three uint parameters +#define IMPLEMENT_FUNCTION_III(index, class, name, paramType1, paramType2, paramType3) \ + void class::setup_##name(paramType1 param1, paramType2 param2, paramType3 param3) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersIIII) \ + EntityData::EntityParametersIIII *params = (EntityData::EntityParametersIIII*)_data->getCurrentParameters(); \ + params->param1 = param1; \ + params->param2 = param2; \ + params->param3 = param3; \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersIIII) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%d, %d, %d) - action: %s", params->param1, params->param2, params->param3, ACTION_NAME(savepoint.action)); + +// setup with on char* parameter +#define IMPLEMENT_FUNCTION_S(index, class, name) \ + void class::setup_##name(const char* seq1) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersSIIS) \ + EntityData::EntityParametersSIIS *params = (EntityData::EntityParametersSIIS*)_data->getCurrentParameters(); \ + strncpy((char *)¶ms->seq1, seq1, 12); \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersSIIS) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%s) - action: %s", (char *)¶ms->seq1, ACTION_NAME(savepoint.action)); + +// setup with on char* parameter and one uint +#define IMPLEMENT_FUNCTION_SI(index, class, name, paramType2) \ + void class::setup_##name(const char* seq1, paramType2 param4) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersSIIS) \ + EntityData::EntityParametersSIIS *params = (EntityData::EntityParametersSIIS*)_data->getCurrentParameters(); \ + strncpy((char *)¶ms->seq1, seq1, 12); \ + params->param4 = param4; \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersSIIS) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%s, %d) - action: %s", (char *)¶ms->seq1, params->param4, ACTION_NAME(savepoint.action)); + +// setup with on char* parameter and two uints +#define IMPLEMENT_FUNCTION_SII(index, class, name, paramType2, paramType3) \ + void class::setup_##name(const char* seq1, paramType2 param4, paramType3 param5) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersSIIS) \ + EntityData::EntityParametersSIIS *params = (EntityData::EntityParametersSIIS*)_data->getCurrentParameters(); \ + strncpy((char *)¶ms->seq1, seq1, 12); \ + params->param4 = param4; \ + params->param5 = param5; \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersSIIS) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%s, %d, %d) - action: %s", (char *)¶ms->seq1, params->param4, params->param5, ACTION_NAME(savepoint.action)); + +// setup with on char* parameter and three uints +#define IMPLEMENT_FUNCTION_SIII(index, class, name, paramType2, paramType3, paramType4) \ + void class::setup_##name(const char* seq, paramType2 param4, paramType3 param5, paramType4 param6) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersSIII) \ + EntityData::EntityParametersSIII *params = (EntityData::EntityParametersSIII*)_data->getCurrentParameters(); \ + strncpy((char *)¶ms->seq, seq, 12); \ + params->param4 = param4; \ + params->param5 = param5; \ + params->param6 = param6; \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersSIII) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%s, %d, %d, %d) - action: %s", (char *)¶ms->seq, params->param4, params->param5, params->param6, ACTION_NAME(savepoint.action)); + +#define IMPLEMENT_FUNCTION_SIIS(index, class, name, paramType2, paramType3) \ + void class::setup_##name(const char* seq1, paramType2 param4, paramType3 param5, const char* seq2) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersSIIS) \ + EntityData::EntityParametersSIIS *params = (EntityData::EntityParametersSIIS*)_data->getCurrentParameters(); \ + strncpy((char *)¶ms->seq1, seq1, 12); \ + params->param4 = param4; \ + params->param5 = param5; \ + strncpy((char *)¶ms->seq2, seq2, 12); \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersSIIS) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%s, %d, %d, %s) - action: %s", (char *)¶ms->seq1, params->param4, params->param5, (char *)¶ms->seq2, ACTION_NAME(savepoint.action)); + +#define IMPLEMENT_FUNCTION_SS(index, class, name) \ + void class::setup_##name(const char* seq1, const char* seq2) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersSSII) \ + EntityData::EntityParametersSSII *params = (EntityData::EntityParametersSSII*)_data->getCurrentParameters(); \ + strncpy((char *)¶ms->seq1, seq1, 12); \ + strncpy((char *)¶ms->seq2, seq2, 12); \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersSSII) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%s, %s) - action: %s", (char *)¶ms->seq1, (char *)¶ms->seq2, ACTION_NAME(savepoint.action)); + +#define IMPLEMENT_FUNCTION_SSI(index, class, name, paramType3) \ + void class::setup_##name(const char* seq1, const char* seq2, paramType3 param7) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersSSII) \ + EntityData::EntityParametersSSII *params = (EntityData::EntityParametersSSII*)_data->getCurrentParameters(); \ + strncpy((char *)¶ms->seq1, seq1, 12); \ + strncpy((char *)¶ms->seq2, seq2, 12); \ + params->param7 = param7; \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersSSII) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%s, %s, %d) - action: %s", (char *)¶ms->seq1, (char *)¶ms->seq2, params->param7, ACTION_NAME(savepoint.action)); + +#define IMPLEMENT_FUNCTION_IS(index, class, name, paramType) \ + void class::setup_##name(paramType param1, const char* seq) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersISII) \ + EntityData::EntityParametersISII *params = (EntityData::EntityParametersISII*)_data->getCurrentParameters(); \ + params->param1 = (unsigned int)param1; \ + strncpy((char *)¶ms->seq, seq, 12); \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersISII) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%d, %s) - action: %s", params->param1, (char *)¶ms->seq, ACTION_NAME(savepoint.action)); + +#define IMPLEMENT_FUNCTION_ISS(index, class, name, paramType) \ + void class::setup_##name(paramType param1, const char* seq1, const char* seq2) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersISSI) \ + EntityData::EntityParametersISSI *params = (EntityData::EntityParametersISSI*)_data->getCurrentParameters(); \ + params->param1 = param1; \ + strncpy((char *)¶ms->seq1, seq1, 12); \ + strncpy((char *)¶ms->seq2, seq2, 12); \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersISSI) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%d, %s, %s) - action: %s", params->param1, (char *)¶ms->seq1, (char *)¶ms->seq2, ACTION_NAME(savepoint.action)); + +#define IMPLEMENT_FUNCTION_IIS(index, class, name, paramType1, paramType2) \ + void class::setup_##name(paramType1 param1, paramType2 param2, const char* seq) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersIISI) \ + EntityData::EntityParametersIISI *params = (EntityData::EntityParametersIISI*)_data->getCurrentParameters(); \ + params->param1 = param1; \ + params->param2 = param2; \ + strncpy((char *)¶ms->seq, seq, 12); \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersIISI) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%d, %d, %s) - action: %s", params->param1, params->param2, (char *)¶ms->seq, ACTION_NAME(savepoint.action)); + +#define IMPLEMENT_FUNCTION_IISS(index, class, name, paramType1, paramType2) \ + void class::setup_##name(paramType1 param1, paramType2 param2, const char* seq1, const char* seq2) { \ + BEGIN_SETUP(class, name, index, EntityData::EntityParametersIISS) \ + EntityData::EntityParametersIISS *params = (EntityData::EntityParametersIISS*)_data->getCurrentParameters(); \ + params->param1 = param1; \ + params->param2 = param2; \ + strncpy((char *)¶ms->seq1, seq1, 12); \ + strncpy((char *)¶ms->seq2, seq2, 12); \ + END_SETUP() \ + } \ + void class::name(const SavePoint &savepoint) { \ + EXPOSE_PARAMS(EntityData::EntityParametersIISS) \ + debugC(6, kLastExpressDebugLogic, "Entity: " #class "::" #name "(%d, %d, %s, %s) - action: %s", params->param1, params->param2, (char *)¶ms->seq1, (char *)¶ms->seq2, ACTION_NAME(savepoint.action)); + + +////////////////////////////////////////////////////////////////////////// +// Misc +////////////////////////////////////////////////////////////////////////// +#define RESET_ENTITY_STATE(entity, class, function) \ + getEntities()->resetState(entity); \ + ((class*)getEntities()->get(entity))->function(); + +////////////////////////////////////////////////////////////////////////// +// Parameters macros (for default IIII parameters) +////////////////////////////////////////////////////////////////////////// +#define CURRENT_PARAMS(index, id) \ + ((EntityData::EntityParametersIIII*)_data->getCurrentParameters(index))->param##id + +#define ENTITY_PARAM(index, id) \ + ((EntityData::EntityParametersIIII*)_data->getParameters(8, index))->param##id + +////////////////////////////////////////////////////////////////////////// +// Time check macros +////////////////////////////////////////////////////////////////////////// +#define TIME_CHECK_CHAPTER1(function) \ + TIME_CHECK(kTimeChapter1, params->param1, function) + +#define TIME_CHECK(timeValue, parameter, function) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + function(); \ + break; \ + } + +#define TIME_CHECK_SAVEPOINT(timeValue, parameter, entity1, entity2, action) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + getSavePoints()->push(entity1, entity2, action); \ + } + +#define TIME_CHECK_CALLBACK(timeValue, parameter, callback, function) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + setCallback(callback); \ + function(); \ + break; \ + } + +#define TIME_CHECK_CALLBACK_1(timeValue, parameter, callback, function, param1) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + setCallback(callback); \ + function(param1); \ + break; \ + } + +#define TIME_CHECK_CALLBACK_2(timeValue, parameter, callback, function, param1, param2) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + setCallback(callback); \ + function(param1, param2); \ + break; \ + } + +#define TIME_CHECK_CALLBACK_3(timeValue, parameter, callback, function, param1, param2, param3) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + setCallback(callback); \ + function(param1, param2, param3); \ + break; \ + } + +#define TIME_CHECK_CALLBACK_INVENTORY(timeValue, parameter, callback, function) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + getData()->inventoryItem = kItemNone; \ + setCallback(callback); \ + function(); \ + break; \ + } + +#define TIME_CHECK_CALLBACK_ACTION(timeValue, parameter) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + CALLBACK_ACTION(); \ + break; \ + } + +#define TIME_CHECK_SAVEGAME(timeValue, parameter, callback, type, event) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + setCallback(callback); \ + setup_savegame(type, event); \ + break; \ + } + +#define TIME_CHECK_ENTERSTATION(timeValue, parameter, callback, name, param2) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + setCallback(callback); \ + setup_enterStation(name, param2); \ + break; \ + } + +#define TIME_CHECK_EXITSTATION(timeValue, parameter, callback, name) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + setCallback(callback); \ + setup_exitStation(name); \ + break; \ + } + +#define TIME_CHECK_EXITSTATION_2(timeValue, parameter1, parameter2, callback, name) \ + if (getState()->time > timeValue && !parameter1) { \ + parameter1 = 1; \ + parameter2 = 1; \ + setCallback(callback); \ + setup_exitStation(name); \ + break; \ + } + +#define TIME_CHECK_EXITSTATION_0(parameter1, parameter2, callback, name) \ + if (parameter1 && !parameter2) { \ + setCallback(callback); \ + setup_exitStation(name); \ + break; \ + } + +#define TIME_CHECK_PLAYSOUND(timeValue, parameter, callback, sound) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + setCallback(callback); \ + setup_playSound(sound); \ + break; \ + } + +#define TIME_CHECK_PLAYSOUND_UPDATEPOSITION(timeValue, parameter, callback, sound, position) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + getData()->entityPosition = position; \ + setCallback(callback); \ + setup_playSound(sound); \ + break; \ + } + +#define TIME_CHECK_OBJECT(timeValue, parameter, object, location) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + getObjects()->updateLocation2(object, location); \ + } + +#define TIME_CHECK_POSITION(timeValue, parameter, position) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + getData()->entityPosition = position; \ + } + +#define TIME_CHECK_CAR(timeValue, parameter, callback, function) {\ + if ((getState()->time <= timeValue && !getEntities()->isPlayerInCar(kCarGreenSleeping)) || !parameter) \ + parameter = getState()->time + 75; \ + if (getState()->time > timeValue || parameter < getState()->time) { \ + parameter = kTimeInvalid; \ + setCallback(callback); \ + function(); \ + break; \ + } \ +} + +////////////////////////////////////////////////////////////////////////// +// Callback action +////////////////////////////////////////////////////////////////////////// +#define CALLBACK_ACTION() { \ + if (getData()->currentCall == 0) \ + error("CALLBACK_ACTION: currentCall is already 0, cannot proceed!"); \ + getData()->currentCall--; \ + getSavePoints()->setCallback(_entityIndex, _callbacks[_data->getCurrentCallback()]); \ + getSavePoints()->call(_entityIndex, _entityIndex, kActionCallback); \ + } + +////////////////////////////////////////////////////////////////////////// +// Param update +////////////////////////////////////////////////////////////////////////// +#define UPDATE_PARAM(parameter, type, value) { \ + if (!parameter) \ + parameter = type + value; \ + if (parameter >= type) \ + break; \ + parameter = kTimeInvalid; \ +} + +// Todo: replace with UPDATE_PARAM_PROC as appropriate +#define UPDATE_PARAM_GOTO(parameter, type, value, label) { \ + if (!parameter) \ + parameter = type + value; \ + if (parameter >= type) \ + goto label; \ + parameter = kTimeInvalid; \ +} + +// Updating parameter with code inside the check +#define UPDATE_PARAM_PROC(parameter, type, value) \ + if (!parameter) \ + parameter = type + value; \ + if (parameter < type) { \ + parameter = kTimeInvalid; + +#define UPDATE_PARAM_PROC_END } + +// Updating parameter with an added check (and code inside the check) +#define UPDATE_PARAM_CHECK(parameter, type, value) \ + if (!parameter || parameter < type) { \ + if (!parameter) \ + parameter = type + value; + +////////////////////////////////////////////////////////////////////////// +// Compartments +////////////////////////////////////////////////////////////////////////// +// Go from one compartment to another (or the same one if no optional args are passed +#define COMPARTMENT_TO(class, compartmentFrom, positionFrom, sequenceFrom, sequenceTo) \ + switch (savepoint.action) { \ + default: \ + break; \ + case kActionDefault: \ + getData()->entityPosition = positionFrom; \ + setCallback(1); \ + setup_enterExitCompartment(sequenceFrom, compartmentFrom); \ + break; \ + case kActionCallback: \ + switch (getCallback()) { \ + default: \ + break; \ + case 1: \ + setCallback(2); \ + setup_enterExitCompartment(sequenceTo, compartmentFrom); \ + break; \ + case 2: \ + getData()->entityPosition = positionFrom; \ + getEntities()->clearSequences(_entityIndex); \ + CALLBACK_ACTION(); \ + } \ + break; \ + } + +#define COMPARTMENT_FROM_TO(class, compartmentFrom, positionFrom, sequenceFrom, compartmentTo, positionTo, sequenceTo) \ + switch (savepoint.action) { \ + default: \ + break; \ + case kActionDefault: \ + getData()->entityPosition = positionFrom; \ + getData()->location = kLocationOutsideCompartment; \ + setCallback(1); \ + setup_enterExitCompartment(sequenceFrom, compartmentFrom); \ + break; \ + case kActionCallback: \ + switch (getCallback()) { \ + default: \ + break; \ + case 1: \ + setCallback(2); \ + setup_updateEntity(kCarGreenSleeping, positionTo); \ + break; \ + case 2: \ + setCallback(3); \ + setup_enterExitCompartment(sequenceTo, compartmentTo); \ + break; \ + case 3: \ + getData()->location = kLocationInsideCompartment; \ + getEntities()->clearSequences(_entityIndex); \ + CALLBACK_ACTION(); \ + break; \ + } \ + break; \ + } + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_ENTITY_INTERN_H diff --git a/engines/lastexpress/entities/francois.cpp b/engines/lastexpress/entities/francois.cpp new file mode 100644 index 0000000000..fd1237f8a3 --- /dev/null +++ b/engines/lastexpress/entities/francois.cpp @@ -0,0 +1,1043 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/francois.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +Francois::Francois(LastExpressEngine *engine) : Entity(engine, kEntityFrancois) { + ADD_CALLBACK_FUNCTION(Francois, reset); + ADD_CALLBACK_FUNCTION(Francois, updateFromTime); + ADD_CALLBACK_FUNCTION(Francois, draw); + ADD_CALLBACK_FUNCTION(Francois, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Francois, enterExitCompartment2); + ADD_CALLBACK_FUNCTION(Francois, playSound); + ADD_CALLBACK_FUNCTION(Francois, savegame); + ADD_CALLBACK_FUNCTION(Francois, updateEntity); + ADD_CALLBACK_FUNCTION(Francois, function9); + ADD_CALLBACK_FUNCTION(Francois, function10); + ADD_CALLBACK_FUNCTION(Francois, function11); + ADD_CALLBACK_FUNCTION(Francois, function12); + ADD_CALLBACK_FUNCTION(Francois, function13); + ADD_CALLBACK_FUNCTION(Francois, function14); + ADD_CALLBACK_FUNCTION(Francois, function15); + ADD_CALLBACK_FUNCTION(Francois, function16); + ADD_CALLBACK_FUNCTION(Francois, chapter1); + ADD_CALLBACK_FUNCTION(Francois, chapter1Handler); + ADD_CALLBACK_FUNCTION(Francois, function19); + ADD_CALLBACK_FUNCTION(Francois, function20); + ADD_CALLBACK_FUNCTION(Francois, chapter2); + ADD_CALLBACK_FUNCTION(Francois, chapter2Handler); + ADD_CALLBACK_FUNCTION(Francois, function23); + ADD_CALLBACK_FUNCTION(Francois, chapter3); + ADD_CALLBACK_FUNCTION(Francois, chapter3Handler); + ADD_CALLBACK_FUNCTION(Francois, chapter4); + ADD_CALLBACK_FUNCTION(Francois, chapter4Handler); + ADD_CALLBACK_FUNCTION(Francois, chapter5); + ADD_CALLBACK_FUNCTION(Francois, chapter5Handler); + ADD_CALLBACK_FUNCTION(Francois, function30); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Francois, reset) + Entity::reset(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(2, Francois, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(3, Francois, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(4, Francois, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(5, Francois, enterExitCompartment2, ObjectIndex) + Entity::enterExitCompartment(savepoint, kPosition_5790, kPosition_6130, kCarRedSleeping, kObjectCompartmentD, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(6, Francois, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(7, Francois, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(8, Francois, updateEntity, CarIndex, EntityPosition) + error("Francois: callback function 8 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Francois, function9) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (getObjects()->get(kObjectCompartmentD).location == kObjectLocation2) { + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + getSavePoints()->push(kEntityFrancois, kEntityMmeBoutarel, kAction134289824); + setCallback(1); + setup_enterExitCompartment("605Cd", kObjectCompartmentD); + } else { + setCallback(2); + setup_enterExitCompartment("605Ed", kObjectCompartmentD); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + break; + + case 2: + getData()->location = kLocationOutsideCompartment; + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Francois, function10) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (getObjects()->get(kObjectCompartmentD).location == kObjectLocation2) { + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + setCallback(1); + setup_enterExitCompartment("605Bd", kObjectCompartmentD); + } else { + setCallback(2); + setup_enterExitCompartment("605Dd", kObjectCompartmentD); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getSavePoints()->push(kEntityFrancois, kEntityMmeBoutarel, kAction102484312); + break; + + case 2: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityFrancois); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(11, Francois, function11, TimeValue) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!getSound()->isBuffered(kEntityFrancois)) { + + UPDATE_PARAM_PROC(CURRENT_PARAMS(1, 1), getState()->timeTicks, params->param6) + switch (rnd(7)) { + default: + break; + + case 0: + getSound()->playSound(kEntityFrancois, "Fra1002A"); + break; + + case 1: + getSound()->playSound(kEntityFrancois, "Fra1002B"); + break; + + case 2: + getSound()->playSound(kEntityFrancois, "Fra1002C"); + break; + + case 3: + getSound()->playSound(kEntityFrancois, "Fra1002D"); + break; + + case 4: + getSound()->playSound(kEntityFrancois, "Fra1002E"); + break; + + case 5: + case 6: + getSound()->playSound(kEntityFrancois, "Fra1002F"); + break; + } + + params->param6 = 15 * rnd(7); + CURRENT_PARAMS(1, 1) = 0; + UPDATE_PARAM_PROC_END + } + + if (!getEntities()->hasValidFrame(kEntityFrancois) || !getEntities()->isWalkingOppositeToPlayer(kEntityFrancois)) + getData()->inventoryItem = kItemNone; + + if (getEntities()->updateEntity(kEntityFrancois, (CarIndex)params->param2, (EntityPosition)params->param3)) { + params->param5 = 0; + + if (params->param3 == kPosition_540) { + params->param2 = (getProgress().chapter == kChapter1) ? kCarRedSleeping : kCarGreenSleeping; + params->param3 = kPosition_9460; + } else { + params->param2 = kCarGreenSleeping; + params->param3 = kPosition_540; + params->param7 = 0; + params->param8 = 0; + + getSavePoints()->push(kEntityFrancois, kEntityCoudert, kAction225932896); + getSavePoints()->push(kEntityFrancois, kEntityMertens, kAction225932896); + } + } + + if (getEntities()->checkDistanceFromPosition(kEntityFrancois, kPosition_2000, 500) && getData()->direction == kDirectionDown) { + + if (getEntities()->isInsideTrainCar(kEntityFrancois, kCarRedSleeping) && params->param8) { + setCallback(2); + setup_draw("605A"); + break; + } + + if (getEntities()->isInsideTrainCar(kEntityFrancois, kCarGreenSleeping) && params->param7) { + setCallback(3); + setup_draw("605A"); + break; + } + } + +label_callback: + if (getProgress().chapter == kChapter1) { + + if (getEntities()->isInsideTrainCar(kEntityFrancois, kCarRedSleeping) + && (getEntities()->hasValidFrame(kEntityFrancois) || params->param1 < getState()->time || params->param4) + && !params->param5 + && getData()->entityPosition < getEntityData(kEntityMmeBoutarel)->entityPosition) { + + if (getData()->direction == kDirectionDown) { + getSavePoints()->push(kEntityFrancois, kEntityMmeBoutarel, kAction202221040); + params->param4 = 1; + params->param5 = 1; + } else if (params->param4 && getEntities()->isDistanceBetweenEntities(kEntityFrancois, kEntityMmeBoutarel, 1000)) { + getSavePoints()->push(kEntityFrancois, kEntityMmeBoutarel, kAction168986720); + params->param5 = 1; + } + } + } else if (params->param1 < getState()->time) { + getData()->clothes = kClothesDefault; + getData()->field_4A3 = 30; + getData()->inventoryItem = kItemNone; + + if (getSound()->isBuffered(kEntityFrancois)) + getSound()->processEntry(kEntityFrancois); + + setCallback(4); + setup_updateEntity(kCarRedSleeping, kPosition_5790); + } + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + + if (getSound()->isBuffered(kEntityFrancois)) + getSound()->processEntry(kEntityFrancois); + + setCallback(6); + setup_savegame(kSavegameTypeEvent, kEventFrancoisWhistle); + break; + + case kActionExcuseMeCath: + if (getProgress().jacket == kJacketGreen + && !getEvent(kEventFrancoisWhistle) + && !getEvent(kEventFrancoisWhistleD) + && !getEvent(kEventFrancoisWhistleNight) + && !getEvent(kEventFrancoisWhistleNightD)) + getData()->inventoryItem = kItemInvalid; + break; + + case kActionDefault: + setCallback(1); + setup_function9(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->clothes = kClothes1; + getData()->field_4A3 = 100; + getData()->inventoryItem = kItemNone; + + params->param2 = kCarGreenSleeping; + params->param3 = kPosition_540; + + getEntities()->updateEntity(kEntityFrancois, kCarGreenSleeping, kPosition_540); + + params->param6 = 15 * rnd(7); + break; + + case 2: + getSavePoints()->push(kEntityFrancois, kEntityCoudert, kAction168253822); + // Fallback to next case + + case 3: + params->param2 = kCarRedSleeping; + params->param3 = kPosition_9460; + params->param5 = 0; + + getData()->entityPosition = kPosition_2088; + + getEntities()->updateEntity(kEntityFrancois, kCarRedSleeping, kPosition_9460); + goto label_callback; + + case 4: + setCallback(5); + setup_function10(); + break; + + case 5: + CALLBACK_ACTION(); + break; + + case 6: + if (getProgress().jacket == kJacketGreen) { + if (isNight()) + getAction()->playAnimation(getData()->entityPosition <= getEntityData(kEntityPlayer)->entityPosition ? kEventFrancoisWhistleNightD : kEventFrancoisWhistleNight); + else + getAction()->playAnimation(getData()->entityPosition <= getEntityData(kEntityPlayer)->entityPosition ? kEventFrancoisWhistleD : kEventFrancoisWhistleD); + } + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + 750 * (getData()->direction == kDirectionUp ? -1 : 1)), getData()->direction == kDirectionUp); + break; + } + break; + + case kAction102752636: + getEntities()->clearSequences(kEntityFrancois); + getData()->location = kLocationInsideCompartment; + getData()->entityPosition = kPosition_5790; + getData()->clothes = kClothesDefault; + getData()->field_4A3 = 30; + getData()->inventoryItem = kItemNone; + + CALLBACK_ACTION(); + break; + + case kAction205346192: + if (savepoint.entity2 == kEntityCoudert) + params->param8 = 1; + else if (savepoint.entity2 == kEntityMertens) + params->param7 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Francois, function12) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function9(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_9460); + break; + + case 2: + setCallback(3); + setup_updateFromTime(675); + break; + + case 3: + setCallback(4); + setup_updateEntity(kCarRedSleeping, kPosition_540); + break; + + case 4: + setCallback(5); + setup_updateFromTime(675); + break; + + case 5: + setCallback(6); + setup_updateEntity(kCarRedSleeping, kPosition_5790); + break; + + case 6: + setCallback(7); + setup_function10(); + break; + + case 7: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Francois, function13) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function9(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_540); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarGreenSleeping, kPosition_4070); + break; + + case 3: + setCallback(4); + setup_enterExitCompartment("605Df", kObjectCompartment6); + break; + + case 4: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityFrancois); + + setCallback(5); + setup_playSound("Har2010"); + break; + + case 5: + getSavePoints()->push(kEntityFrancois, kEntityAlouan, kAction189489753); + break; + + case 6: + getData()->location = kLocationOutsideCompartment; + + setCallback(7); + setup_updateEntity(kCarGreenSleeping, kPosition_4840); + break; + + case 7: + if (getInventory()->hasItem(kItemWhistle) || getInventory()->get(kItemWhistle)->location == kObjectLocation3) { + setCallback(10); + setup_updateEntity(kCarGreenSleeping, kPosition_5790); + break; + } + + getEntities()->drawSequenceLeft(kEntityFrancois, "605He"); + break; + + case 8: + setCallback(9); + setup_updateFromTime(450); + break; + + case 9: + getEntities()->exitCompartment(kEntityFrancois, kObjectCompartmentE, true); + + setCallback(10); + setup_updateEntity(kCarGreenSleeping, kPosition_5790); + break; + + case 10: + setCallback(11); + setup_function10(); + break; + + case 11: + CALLBACK_ACTION(); + break; + } + break; + + case kAction190219584: + setCallback(6); + setup_enterExitCompartment("605Ef", kObjectCompartment6); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_IIS(14, Francois, function14, ObjectIndex, EntityPosition) + error("Francois: callback function 14 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Francois, function15) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function9(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getData()->entityPosition >= getEntityData(kEntityPlayer)->entityPosition) { + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_540); + } else { + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_9460); + } + break; + + case 2: + case 3: + setCallback(4); + setup_updateFromTime(450); + break; + + case 4: + setCallback(5); + setup_updateEntity(kCarRedSleeping, kPosition_5790); + break; + + case 5: + setCallback(6); + setup_function10(); + break; + + case 6: + setCallback(7); + setup_updateFromTime(900); + break; + + case 7: + if (!getEntities()->isInsideCompartment(kEntityMmeBoutarel, kCarRedSleeping, kPosition_5790)) { + CALLBACK_ACTION(); + break; + } + + setCallback(8); + setup_playSound("Fra2012"); + break; + + case 8: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Francois, function16) + switch (savepoint.action) { + default: + break; + + case kActionNone: + getData()->entityPosition = getEntityData(kEntityBoutarel)->entityPosition; + getData()->location = getEntityData(kEntityBoutarel)->location; + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorNormal); + + setCallback(1); + setup_enterExitCompartment("605Cd", kObjectCompartmentD); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getData()->entityPosition = kPosition_5890; + + getSavePoints()->push(kEntityFrancois, kEntityMmeBoutarel, kAction101107728); + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + getEntities()->clearSequences(kEntityFrancois); + getSavePoints()->push(kEntityFrancois, kEntityBoutarel, kAction237889408); + break; + + case 3: + setCallback(4); + setup_enterExitCompartment("605Id", kObjectCompartmentD); + break; + + case 4: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getSavePoints()->push(kEntityFrancois, kEntityMmeBoutarel, kAction100957716); + + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + + CALLBACK_ACTION(); + break; + } + break; + + case kAction100901266: + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_5790); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Francois, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Francois, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK_1(kTimeParisEpernay, params->param1, 1, setup_function11, kTime1093500); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function19(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Francois, function19) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK(kTime1161000, params->param1, 2, setup_function12); + break; + + case kAction101107728: + setCallback(1); + setup_function16(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Francois, function20) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getEntities()->clearSequences(kEntityFrancois); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21 ,Francois, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityFrancois); + + getData()->entityPosition = kPosition_4689; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Francois, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("605Id", kObjectCompartmentD); + break; + + case 2: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getSavePoints()->push(kEntityFrancois, kEntityMmeBoutarel, kAction100957716); + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityFrancois); + setup_function23(); + break; + } + break; + + case kAction100901266: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_5790); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Francois, function23) + error("Francois: callback function 23 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Francois, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityFrancois); + + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Francois, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEvent(kEventFrancoisShowBeetle) || getEvent(kEventFrancoisShowBeetleD)) + if (!getEvent(kEventFrancoisTradeWhistle) && !getEvent(kEventFrancoisTradeWhistleD)) + ENTITY_PARAM(0, 1) = 1; + + if (params->param2 && getEntities()->isInsideCompartment(kEntityMmeBoutarel, kCarRedSleeping, kPosition_5790) && !params->param1) { + + if (ENTITY_PARAM(0, 1) && getEntities()->isPlayerInCar(kCarRedSleeping)) { + setCallback(2); + setup_function15(); + break; + } + +label_callback_2: + TIME_CHECK_CALLBACK(kTime2025000, params->param3, 3, setup_function12); + +label_callback_3: + TIME_CHECK_CALLBACK(kTime2052000, params->param4, 4, setup_function12); + +label_callback_4: + TIME_CHECK_CALLBACK(kTime2079000, params->param5, 5, setup_function12); + +label_callback_5: + TIME_CHECK_CALLBACK(kTime2092500, params->param6, 6, setup_function12); + +label_callback_6: + TIME_CHECK_CALLBACK(kTime2173500, params->param7, 7, setup_function12); + +label_callback_7: + TIME_CHECK_CALLBACK(kTime2182500, params->param8, 8, setup_function12); + +label_callback_8: + TIME_CHECK_CALLBACK(kTime2241000, CURRENT_PARAMS(1, 1), 9, setup_function12); + +label_callback_9: + if (!getInventory()->hasItem(kItemWhistle) && getInventory()->get(kItemWhistle)->location != kObjectLocation3) { + TIME_CHECK_CALLBACK_1(kTime2011500, CURRENT_PARAMS(1, 2), 10, setup_function11, kTime2016000); + +label_callback_10: + TIME_CHECK_CALLBACK_1(kTime2115000, CURRENT_PARAMS(1, 3), 11, setup_function11, kTime2119500); + } + +label_callback_11: + if (getInventory()->get(kItemWhistle)->location == kObjectLocation3) { + if (getState()->time <= kTimeEnd) + if (!getEntities()->isDistanceBetweenEntities(kEntityFrancois, kEntityPlayer, 2000) || !params->param4) + params->param4 = getState()->time + 75; + + if (params->param4 < getState()->time || getState()->time > kTimeEnd) { + params->param4 = kTimeInvalid; + + setCallback(12); + setup_playSound("Fra2010"); + break; + } + +label_callback_12: + TIME_CHECK_CALLBACK_3(kTime2040300, CURRENT_PARAMS(1, 5), 13, setup_function14, kObjectCompartmentE, kPosition_4840, "e"); + +label_callback_13: + TIME_CHECK_CALLBACK_3(kTime2040300, CURRENT_PARAMS(1, 6), 14, setup_function14, kObjectCompartmentF, kPosition_4070, "f"); + +label_callback_14: + TIME_CHECK_CALLBACK_3(kTime2040300, CURRENT_PARAMS(1, 7), 15, setup_function14, kObjectCompartmentB, kPosition_7500, "b"); + } + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + params->param2 = 1; + break; + + case 2: + goto label_callback_2; + + case 3: + goto label_callback_3; + + case 4: + goto label_callback_4; + + case 5: + goto label_callback_5; + + case 6: + goto label_callback_6; + + case 7: + goto label_callback_7; + + case 8: + goto label_callback_8; + + case 9: + goto label_callback_9; + + case 10: + goto label_callback_10; + + case 11: + goto label_callback_11; + + case 12: + getProgress().field_9C = 1; + goto label_callback_12; + + case 13: + goto label_callback_13; + + case 14: + goto label_callback_14; + } + break; + + case kAction101107728: + setCallback(1); + setup_function16(); + break; + + case kAction189872836: + params->param1 = 1; + break; + case kAction190390860: + params->param1 = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Francois, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityFrancois); + + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Francois, chapter4Handler) + if (savepoint.action == kAction101107728) { + setCallback(1); + setup_function16(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Francois, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityFrancois); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Francois, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) { + if (!getInventory()->hasItem(kItemWhistle) + && getInventory()->get(kItemWhistle)->location != kObjectLocation3) + getInventory()->setLocationAndProcess(kItemWhistle, kObjectLocation1); + + setup_function30(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Francois, function30) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + + case kAction135800432: + setup_nullfunction(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(31, Francois) + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/francois.h b/engines/lastexpress/entities/francois.h new file mode 100644 index 0000000000..894a60bc5b --- /dev/null +++ b/engines/lastexpress/entities/francois.h @@ -0,0 +1,170 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_FRANCOIS_H +#define LASTEXPRESS_FRANCOIS_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Francois : public Entity { +public: + Francois(LastExpressEngine *engine); + ~Francois() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment and updates position/play animation + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment2, const char* sequence, ObjectIndex compartment) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION(function9) + DECLARE_FUNCTION(function10) + DECLARE_FUNCTION_1(function11, TimeValue timeValue) + DECLARE_FUNCTION(function12) + DECLARE_FUNCTION(function13) + DECLARE_FUNCTION_3(function14, ObjectIndex compartment, EntityPosition entityPosition, const char *str) + DECLARE_FUNCTION(function15) + DECLARE_FUNCTION(function16) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function19) + DECLARE_FUNCTION(function20) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + DECLARE_FUNCTION(function23) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function30) + + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_FRANCOIS_H diff --git a/engines/lastexpress/entities/gendarmes.cpp b/engines/lastexpress/entities/gendarmes.cpp new file mode 100644 index 0000000000..3af85ffdb9 --- /dev/null +++ b/engines/lastexpress/entities/gendarmes.cpp @@ -0,0 +1,491 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/gendarmes.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Gendarmes::Gendarmes(LastExpressEngine *engine) : Entity(engine, kEntityGendarmes) { + ADD_CALLBACK_FUNCTION(Gendarmes, reset); + ADD_CALLBACK_FUNCTION(Gendarmes, chapter1); + ADD_CALLBACK_FUNCTION(Gendarmes, arrestDraw); + ADD_CALLBACK_FUNCTION(Gendarmes, arrestPlaysound); + ADD_CALLBACK_FUNCTION(Gendarmes, arrestPlaysound16); + ADD_CALLBACK_FUNCTION(Gendarmes, arrestCallback); + ADD_CALLBACK_FUNCTION(Gendarmes, savegame); + ADD_CALLBACK_FUNCTION(Gendarmes, arrestUpdateEntity); + ADD_CALLBACK_FUNCTION(Gendarmes, function9); + ADD_CALLBACK_FUNCTION(Gendarmes, function10); + ADD_CALLBACK_FUNCTION(Gendarmes, chapter1Handler); + ADD_CALLBACK_FUNCTION(Gendarmes, function12); + ADD_CALLBACK_FUNCTION(Gendarmes, function13); + ADD_CALLBACK_FUNCTION(Gendarmes, chapter2); + ADD_CALLBACK_FUNCTION(Gendarmes, chapter3); + ADD_CALLBACK_FUNCTION(Gendarmes, chapter4); + ADD_CALLBACK_FUNCTION(Gendarmes, chapter5); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Gendarmes, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(2, Gendarmes, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getData()->car = kCarNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(3, Gendarmes, arrestDraw) + arrest(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(4, Gendarmes, arrestPlaysound) + arrest(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(5, Gendarmes, arrestPlaysound16) + arrest(savepoint, true, SoundManager::kFlagDefault); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(6, Gendarmes, arrestCallback, TimeValue) + arrest(savepoint, true, SoundManager::kFlagInvalid, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(7, Gendarmes, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(8, Gendarmes, arrestUpdateEntity, CarIndex, EntityPosition) + arrest(savepoint, true, SoundManager::kFlagInvalid, false, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_IISS(9, Gendarmes, function9, CarIndex, EntityPosition) + error("Gendarmes: callback function 9 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_III(10, Gendarmes, function10, CarIndex, EntityPosition, ObjectIndex) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param5 || getState()->timeTicks > (uint32)params->param5) { + if (!params->param5) + params->param5 = getState()->timeTicks + 75; + + if (!getEntities()->isOutsideAlexeiWindow() && getObjects()->get((ObjectIndex)params->param3).location != kObjectLocation1) { + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventGendarmesArrestation); + break; + } + } + + if (!params->param6) + params->param6 = getState()->timeTicks + 150; + + if (params->param6 == 0 || getState()->timeTicks > (uint32)params->param6) { + params->param6 = kTimeInvalid; + + getSound()->playSound(kEntityGendarmes, "POL1046A", SoundManager::kFlagDefault); + } + + UPDATE_PARAM(params->param7, getState()->timeTicks, 300); + + if (!params->param4 && getEntities()->isOutsideAlexeiWindow()) { + getObjects()->update((ObjectIndex)params->param3, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + CALLBACK_ACTION(); + } else { + if (getEntities()->isOutsideAlexeiWindow()) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + + getSound()->playSound(kEntityGendarmes, "LIB017", SoundManager::kFlagDefault); + + setCallback(getProgress().jacket == kJacketBlood ? 3 : 4); + setup_savegame(kSavegameTypeEvent, getProgress().jacket == kJacketBlood ? kEventMertensBloodJacket : kEventGendarmesArrestation); + } + break; + + case kActionKnock: + getObjects()->update((ObjectIndex)params->param3, kEntityGendarmes, getObjects()->get((ObjectIndex)params->param3).location, kCursorNormal, kCursorNormal); + + setCallback(5); + setup_arrestPlaysound16("POL1046B"); + break; + + case kActionOpenDoor: + setCallback(6); + setup_savegame(kSavegameTypeEvent, kEventGendarmesArrestation); + break; + + case kActionDefault: + getObjects()->update((ObjectIndex)params->param3, kEntityGendarmes, getObjects()->get((ObjectIndex)params->param3).location, kCursorNormal, kCursorNormal); + + setCallback(1); + setup_arrestPlaysound16("POL1046"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update((ObjectIndex)params->param3, kEntityGendarmes, getObjects()->get((ObjectIndex)params->param3).location, kCursorTalk, kCursorNormal); + break; + + case 2: + getSound()->playSound(kEntityGendarmes, "LIB014", SoundManager::kFlagDefault); + getAction()->playAnimation(kEventGendarmesArrestation); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverPolice1, true); + break; + + case 3: + getAction()->playAnimation((params->param1 < kCarRedSleeping) ? kEventMertensBloodJacket : kEventCoudertBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + + getObjects()->update((ObjectIndex)params->param3, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + CALLBACK_ACTION(); + break; + + case 4: + getAction()->playAnimation(kEventGendarmesArrestation); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverPolice1, true); + + getObjects()->update((ObjectIndex)params->param3, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + CALLBACK_ACTION(); + break; + + case 5: + getObjects()->update((ObjectIndex)params->param3, kEntityGendarmes, getObjects()->get((ObjectIndex)params->param3).location, kCursorNormal, kCursorHand); + break; + + case 6: + getSound()->playSound(kEntityGendarmes, "LIB014", SoundManager::kFlagDefault); + getAction()->playAnimation(kEventGendarmesArrestation); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverPolice1, true); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Gendarmes, chapter1Handler) + if (savepoint.action == kAction169499649) { + getSavePoints()->push(kEntityGendarmes, kEntityMertens, kAction190082817); + setup_function12(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Gendarmes, function12) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_540; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + + getProgress().field_14 = 29; + + setCallback(1); + setup_arrestUpdateEntity(kCarGreenSleeping, kPosition_5540); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function9(kCarGreenSleeping, kPosition_5790, "d", "A"); + break; + + case 2: + setCallback(3); + setup_arrestUpdateEntity(kCarGreenSleeping, kPosition_6220); + break; + + case 3: + setCallback(4); + setup_function9(kCarGreenSleeping, kPosition_6470, "c", "B"); + break; + + case 4: + setCallback(5); + setup_arrestUpdateEntity(kCarGreenSleeping, kPosition_7250); + break; + + case 5: + setCallback(6); + setup_function9(kCarGreenSleeping, kPosition_7500, "b", "C"); + break; + + case 6: + setCallback(7); + setup_arrestUpdateEntity(kCarGreenSleeping, kPosition_7950); + break; + + case 7: + setCallback(8); + setup_function9(kCarGreenSleeping, kPosition_8200, "a", "NODIALOG"); + break; + + case 8: + setCallback(9); + setup_arrestUpdateEntity(kCarGreenSleeping, kPosition_9460); + break; + + case 9: + if (getEntityData(kEntityPlayer)->car == kCarGreenSleeping) { + getProgress().field_14 = 0; + getEntities()->clearSequences(kEntityGendarmes); + getSavePoints()->push(kEntityGendarmes, kEntityVerges, kAction168710784); + setup_function13(); + break; + } + + setCallback(10); + setup_arrestUpdateEntity(kCarRedSleeping, kPosition_2490); + break; + + case 10: + setCallback(11); + setup_function9(kCarRedSleeping, kPosition_2740, "h", "NODIALOG"); + break; + + case 11: + setCallback(12); + setup_arrestUpdateEntity(kCarRedSleeping, kPosition_3820); + break; + + case 12: + setCallback(13); + setup_function9(kCarRedSleeping, kPosition_4070, "f", "E"); + break; + + case 13: + setCallback(14); + setup_arrestUpdateEntity(kCarRedSleeping, kPosition_4590); + break; + + case 14: + setCallback(15); + setup_function9(kCarRedSleeping, kPosition_4840, "e", "F"); + break; + + case 15: + setCallback(16); + setup_arrestUpdateEntity(kCarRedSleeping, kPosition_5540); + break; + + case 16: + setCallback(17); + setup_function9(kCarRedSleeping, kPosition_5790, "d", "G"); + break; + + case 17: + setCallback(18); + setup_arrestUpdateEntity(kCarRedSleeping, kPosition_6220); + break; + + case 18: + setCallback(19); + setup_function9(kCarRedSleeping, kPosition_6470, "c", "H"); + break; + + case 19: + setCallback(20); + setup_arrestUpdateEntity(kCarRedSleeping, kPosition_7250); + break; + + case 20: + setCallback(21); + setup_function9(kCarRedSleeping, kPosition_7500, "b", "J"); + break; + + case 21: + setCallback(22); + setup_arrestUpdateEntity(kCarRedSleeping, kPosition_7950); + break; + + case 22: + setCallback(23); + setup_function9(kCarRedSleeping, kPosition_8200, "a", "NODIALOG"); + break; + + case 23: + setCallback(24); + setup_arrestUpdateEntity(kCarRedSleeping, kPosition_9460); + break; + + case 24: + getProgress().field_14 = 0; + getEntities()->clearSequences(kEntityGendarmes); + getSavePoints()->push(kEntityGendarmes, kEntityVerges, kAction168710784); + setup_function13(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Gendarmes, function13) + if (savepoint.action == kActionDefault) + getData()->car = kCarNone; +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Gendarmes, chapter2) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityGendarmes); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Gendarmes, chapter3) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityGendarmes); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Gendarmes, chapter4) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityGendarmes); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Gendarmes, chapter5) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityGendarmes); +} + +////////////////////////////////////////////////////////////////////////// +// Private functions +////////////////////////////////////////////////////////////////////////// +void Gendarmes::arrest(const SavePoint &savepoint, bool shouldPlaySound, SoundManager::FlagType flag, bool checkCallback, bool shouldUpdateEntity) { + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (checkCallback) { + EXPOSE_PARAMS(EntityData::EntityParametersIIII); + TIME_CHECK_CALLBACK_ACTION(params->param1, params->param2); + } + + if (shouldUpdateEntity) { + EXPOSE_PARAMS(EntityData::EntityParametersIIII); + if (getEntities()->updateEntity(kEntityGendarmes, (CarIndex)params->param1, (EntityPosition)params->param2)) { + CALLBACK_ACTION(); + break; + } + } + // Fallback to next action + + case kActionDrawScene: + if (!ENTITY_PARAM(0, 1) && getEntities()->hasValidFrame(kEntityGendarmes)) { + getSound()->playSound(kEntityPlayer, "MUS007"); + ENTITY_PARAM(0, 1) = 1; + } + + if (getEntities()->isDistanceBetweenEntities(kEntityGendarmes, kEntityPlayer, 1000) && !getEntityData(kEntityPlayer)->location) { + + if (shouldUpdateEntity) + if (getEntities()->isPlayerPosition(kCarRedSleeping, 22) && !getEntities()->isDistanceBetweenEntities(kEntityGendarmes, kEntityPlayer, 250)) + break; + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventGendarmesArrestation); + } + break; + + case kActionExitCompartment: + CALLBACK_ACTION(); + break; + + case kActionDefault: + // Only handle when passing SIIS params + if (!checkCallback) { + EXPOSE_PARAMS(EntityData::EntityParametersSIIS); + + if (!shouldPlaySound) + getEntities()->drawSequenceRight(kEntityGendarmes, (char *)¶ms->seq1); + else + getSound()->playSound(kEntityGendarmes, (char *)¶ms->seq1, flag); + } + + if (shouldUpdateEntity) { + EXPOSE_PARAMS(EntityData::EntityParametersIIII); + if (getEntities()->updateEntity(kEntityGendarmes, (CarIndex)params->param1, (EntityPosition)params->param2)) { + CALLBACK_ACTION(); + break; + } + } + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventGendarmesArrestation); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverPolice1, true); + } + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/gendarmes.h b/engines/lastexpress/entities/gendarmes.h new file mode 100644 index 0000000000..b0aec28bfb --- /dev/null +++ b/engines/lastexpress/entities/gendarmes.h @@ -0,0 +1,99 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_GENDARMES_H +#define LASTEXPRESS_GENDARMES_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +#include "lastexpress/game/sound.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Gendarmes : public Entity { +public: + Gendarmes(LastExpressEngine *engine); + ~Gendarmes() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + DECLARE_FUNCTION_1(arrestDraw, const char *sequence) + DECLARE_FUNCTION_1(arrestPlaysound, const char *soundName) + DECLARE_FUNCTION_1(arrestPlaysound16, const char *soundName) + DECLARE_FUNCTION_1(arrestCallback, TimeValue timeValue) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + DECLARE_FUNCTION_2(arrestUpdateEntity, CarIndex car, EntityPosition entityPosition) + DECLARE_FUNCTION_4(function9, CarIndex car, EntityPosition entityPosition, const char *sequence1, const char *sequence2) + DECLARE_FUNCTION_3(function10, CarIndex car, EntityPosition entityPosition, ObjectIndex object) + DECLARE_FUNCTION(chapter1Handler) + DECLARE_FUNCTION(function12) + DECLARE_FUNCTION(function13) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + +private: + void arrest(const SavePoint &savepoint, bool playSound = false, SoundManager::FlagType flag = SoundManager::kFlagInvalid, bool checkCallback = false, bool shouldUpdateEntity = false); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_GENDARMES_H diff --git a/engines/lastexpress/entities/hadija.cpp b/engines/lastexpress/entities/hadija.cpp new file mode 100644 index 0000000000..564ac942ca --- /dev/null +++ b/engines/lastexpress/entities/hadija.cpp @@ -0,0 +1,529 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/hadija.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Hadija::Hadija(LastExpressEngine *engine) : Entity(engine, kEntityHadija) { + ADD_CALLBACK_FUNCTION(Hadija, reset); + ADD_CALLBACK_FUNCTION(Hadija, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Hadija, playSound); + ADD_CALLBACK_FUNCTION(Hadija, updateFromTime); + ADD_CALLBACK_FUNCTION(Hadija, updateEntity); + ADD_CALLBACK_FUNCTION(Hadija, compartment6); + ADD_CALLBACK_FUNCTION(Hadija, compartment8); + ADD_CALLBACK_FUNCTION(Hadija, compartment6to8); + ADD_CALLBACK_FUNCTION(Hadija, compartment8to6); + ADD_CALLBACK_FUNCTION(Hadija, chapter1); + ADD_CALLBACK_FUNCTION(Hadija, chapter1Handler); + ADD_CALLBACK_FUNCTION(Hadija, function12); + ADD_CALLBACK_FUNCTION(Hadija, chapter2); + ADD_CALLBACK_FUNCTION(Hadija, chapter2Handler); + ADD_CALLBACK_FUNCTION(Hadija, chapter3); + ADD_CALLBACK_FUNCTION(Hadija, chapter3Handler); + ADD_CALLBACK_FUNCTION(Hadija, chapter4); + ADD_CALLBACK_FUNCTION(Hadija, chapter4Handler); + ADD_CALLBACK_FUNCTION(Hadija, function19); + ADD_CALLBACK_FUNCTION(Hadija, chapter5); + ADD_CALLBACK_FUNCTION(Hadija, chapter5Handler); + ADD_CALLBACK_FUNCTION(Hadija, function22); + ADD_CALLBACK_FUNCTION(Hadija, function23); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Hadija, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(2, Hadija, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(3, Hadija, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(4, Hadija, updateFromTime) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(5, Hadija, updateEntity, CarIndex, EntityPosition) + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6, Hadija, compartment6) + COMPARTMENT_TO(Hadija, kObjectCompartment6, kPosition_4070, "619Cf", "619Df"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Hadija, compartment8) + COMPARTMENT_TO(Hadija, kObjectCompartment8, kPosition_2740, "619Ch", "619Dh"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Hadija, compartment6to8) + COMPARTMENT_FROM_TO(Hadija, kObjectCompartment6, kPosition_4070, "619Bf", kObjectCompartment8, kPosition_2740, "619Ah"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Hadija, compartment8to6) + COMPARTMENT_FROM_TO(Hadija, kObjectCompartment8, kPosition_2740, "619Bh", kObjectCompartment6, kPosition_4070, "619Af"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Hadija, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Hadija, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_PLAYSOUND_UPDATEPOSITION(kTimeParisEpernay, params->param1, 1, "Har1100", kPosition_4840); + +label_callback1: + TIME_CHECK_CALLBACK(kTime1084500, params->param2, 2, setup_compartment6to8); + +label_callback2: + if (params->param3 != kTimeInvalid && getState()->time > kTime1093500) { + + if (getState()->time <= kTime1134000) { + + if (!getEntities()->isPlayerInCar(kCarGreenSleeping) || !getEntities()->isInsideCompartment(kEntityMahmud, kCarGreenSleeping, kPosition_5790) || !params->param3) { + params->param3 = getState()->time + 75; + + if (!params->param3) { + setCallback(3); + setup_compartment8(); + return; + } + } + + if (params->param3 >= getState()->time) + return; + } + + params->param3 = kTimeInvalid; + + setCallback(3); + setup_compartment8(); + } + +label_callback3: + TIME_CHECK_CALLBACK(kTime1156500, params->param4, 4, setup_compartment8to6); + +label_callback4: + if (params->param5 != kTimeInvalid && getState()->time > kTime1165500) { + if (getState()->time <= kTime1188000) { + + if (!getEntities()->isPlayerInCar(kCarGreenSleeping) || !getEntities()->isInsideCompartment(kEntityMahmud, kCarGreenSleeping, kPosition_5790) || !params->param5) { + params->param5 = getState()->time + 75; + + if (!params->param5) { + setCallback(5); + setup_compartment6(); + return; + } + } + + if (params->param5 >= getState()->time) + return; + } + + params->param5 = kTimeInvalid; + + setCallback(5); + setup_compartment6(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback1; + + case 2: + goto label_callback2; + + case 3: + goto label_callback3; + + case 4: + goto label_callback4; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Hadija, function12) + if (savepoint.action == kActionDefault) { + getObjects()->update(kObjectCompartment8, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + getEntities()->clearSequences(kEntityHadija); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Hadija, chapter2) + if (savepoint.action == kActionDefault) { + + getEntities()->clearSequences(kEntityHadija); + + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + setup_chapter2Handler(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Hadija, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_POSITION(kTime1782000, params->param1, kPosition_2740); + + if (params->param2 == kTimeInvalid || getState()->time <= kTime1786500) { + TIME_CHECK_CALLBACK(kTime1822500, params->param3, 2, setup_compartment8to6); + break; + } + + if (getState()->time <= kTime1818000) { + + if (!getEntities()->isPlayerInCar(kCarGreenSleeping) || !params->param2) + params->param2 = getState()->time + 75; + + if (params->param2 >= getState()->time) { + TIME_CHECK_CALLBACK(kTime1822500, params->param3, 2, setup_compartment8to6); + break; + } + } + + params->param2 = kTimeInvalid; + + setCallback(1); + setup_compartment8(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + TIME_CHECK_CALLBACK(kTime1822500, params->param3, 2, setup_compartment8to6); + break; + + case 2: + setCallback(3); + setup_playSound("Har2012"); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Hadija, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityHadija); + + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Hadija, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK(kTime1998000, params->param1, 1, setup_compartment6to8); + +label_callback1: + TIME_CHECK_CALLBACK(kTime2020500, params->param2, 2, setup_compartment8to6); + +label_callback2: + TIME_CHECK_CALLBACK(kTime2079000, params->param3, 3, setup_compartment6to8); + +label_callback3: + TIME_CHECK_CALLBACK(kTime2187000, params->param4, 4, setup_compartment8to6); + +label_callback4: + if (params->param5 != kTimeInvalid && getState()->time > kTime2196000) + TIME_CHECK_CAR(kTime2254500, params->param5, 5, setup_compartment6); + break; + + case kActionDefault: + getSavePoints()->push(kEntityAlouan, kEntityTrain, kAction191070912, kPosition_4840); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback1; + + case 2: + goto label_callback2; + + case 3: + goto label_callback3; + + case 4: + goto label_callback4; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Hadija, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Hadija, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 != kTimeInvalid) + TIME_CHECK_CAR(kTime1714500, params->param1, 1, setup_compartment6); + +label_callback1: + TIME_CHECK_CALLBACK(kTime2367000, params->param2, 2, setup_compartment6to8); + +label_callback2: + TIME_CHECK_CALLBACK(kTime2421000, params->param3, 3, setup_compartment8to6); + +label_callback3: + if (params->param4 != kTimeInvalid && getState()->time > kTime2425500) + TIME_CHECK_CAR(kTime2484000, params->param4, 4, setup_compartment6); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback1; + + case 2: + goto label_callback2; + + case 3: + goto label_callback3; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Hadija, function19) + if (savepoint.action == kActionDefault) { + getObjects()->update(kObjectCompartment8, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + getEntities()->clearSequences(kEntityHadija); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Hadija, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityHadija); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Hadija, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_function22(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Hadija, function22) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param1, getState()->time, 2700); + setup_function23(); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5000; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + break; + + case kActionDrawScene: + if (getEntities()->isInsideTrainCar(kEntityPlayer, kCarGreenSleeping)) { + setup_function23(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Hadija, function23) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_4070); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("619AF", kObjectCompartment6); + break; + + case 2: + getEntities()->clearSequences(kEntityHadija); + + getData()->entityPosition = kPosition_4840; + getData()->location = kLocationInsideCompartment; + + getObjects()->update(kObjectCompartment5, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } + break; + + case kAction135800432: + setup_nullfunction(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(24, Hadija) + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/hadija.h b/engines/lastexpress/entities/hadija.h new file mode 100644 index 0000000000..4792f97cad --- /dev/null +++ b/engines/lastexpress/entities/hadija.h @@ -0,0 +1,144 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_HADIJA_H +#define LASTEXPRESS_HADIJA_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Hadija : public Entity { +public: + Hadija(LastExpressEngine *engine); + ~Hadija() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Updates parameter 2 using time value + * + * @param savepoint The savepoint + * - Time to add + */ + DECLARE_FUNCTION_NOSETUP(updateFromTime) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION(compartment6) + DECLARE_FUNCTION(compartment8) + DECLARE_FUNCTION(compartment6to8) + DECLARE_FUNCTION(compartment8to6) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function12) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function19) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function22) + DECLARE_FUNCTION(function23) + + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_HADIJA_H diff --git a/engines/lastexpress/entities/ivo.cpp b/engines/lastexpress/entities/ivo.cpp new file mode 100644 index 0000000000..d56c184c15 --- /dev/null +++ b/engines/lastexpress/entities/ivo.cpp @@ -0,0 +1,829 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/ivo.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/fight.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Ivo::Ivo(LastExpressEngine *engine) : Entity(engine, kEntityIvo) { + ADD_CALLBACK_FUNCTION(Ivo, reset); + ADD_CALLBACK_FUNCTION(Ivo, draw); + ADD_CALLBACK_FUNCTION(Ivo, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Ivo, updateFromTime); + ADD_CALLBACK_FUNCTION(Ivo, updateFromTicks); + ADD_CALLBACK_FUNCTION(Ivo, updateEntity); + ADD_CALLBACK_FUNCTION(Ivo, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Ivo, playSound); + ADD_CALLBACK_FUNCTION(Ivo, callbackActionRestaurantOrSalon); + ADD_CALLBACK_FUNCTION(Ivo, savegame); + ADD_CALLBACK_FUNCTION(Ivo, function11); + ADD_CALLBACK_FUNCTION(Ivo, sitAtTableWithSalko); + ADD_CALLBACK_FUNCTION(Ivo, leaveTableWithSalko); + ADD_CALLBACK_FUNCTION(Ivo, chapter1); + ADD_CALLBACK_FUNCTION(Ivo, chapter1Handler); + ADD_CALLBACK_FUNCTION(Ivo, function16); + ADD_CALLBACK_FUNCTION(Ivo, function17); + ADD_CALLBACK_FUNCTION(Ivo, chapter2); + ADD_CALLBACK_FUNCTION(Ivo, function19); + ADD_CALLBACK_FUNCTION(Ivo, function20); + ADD_CALLBACK_FUNCTION(Ivo, function21); + ADD_CALLBACK_FUNCTION(Ivo, chapter3); + ADD_CALLBACK_FUNCTION(Ivo, chapter3Handler); + ADD_CALLBACK_FUNCTION(Ivo, chapter4); + ADD_CALLBACK_FUNCTION(Ivo, chapter4Handler); + ADD_CALLBACK_FUNCTION(Ivo, function26); + ADD_CALLBACK_FUNCTION(Ivo, function27); + ADD_CALLBACK_FUNCTION(Ivo, function28); + ADD_CALLBACK_FUNCTION(Ivo, function29); + ADD_CALLBACK_FUNCTION(Ivo, chapter5); + ADD_CALLBACK_FUNCTION(Ivo, chapter5Handler); + ADD_CALLBACK_FUNCTION(Ivo, fight); + ADD_CALLBACK_FUNCTION(Ivo, function33); + ADD_CALLBACK_FUNCTION(Ivo, function34); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Ivo, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Ivo, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(3, Ivo, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(4, Ivo, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(5, Ivo, updateFromTicks, uint32) + Entity::updateFromTicks(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(6, Ivo, updateEntity, CarIndex, EntityPosition) + if (savepoint.action == kActionExcuseMeCath || savepoint.action == kActionExcuseMe) { + getSound()->playSound(kEntityPlayer, "CAT1127A"); + return; + } + + Entity::updateEntity(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Ivo, callbackActionOnDirection) + Entity::callbackActionOnDirection(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(8, Ivo, playSound) + Entity::playSound(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Ivo, callbackActionRestaurantOrSalon) + Entity::callbackActionRestaurantOrSalon(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(10, Ivo, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Ivo, function11) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isDistanceBetweenEntities(kEntityIvo, kEntitySalko, 750) || getEntities()->checkDistanceFromPosition(kEntitySalko, kPosition_2740, 500)) { + getSavePoints()->push(kEntityIvo, kEntitySalko, kAction123668192); + + setCallback(4); + setup_enterExitCompartment("613Ah", kObjectCompartmentH); + } + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityIvo, "809DS"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityIvo); + + setCallback(1); + setup_callbackActionOnDirection(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityIvo, kEntitySalko, kAction125242096); + + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_2740); + break; + + case 2: + if (getEntities()->isDistanceBetweenEntities(kEntityIvo, kEntitySalko, 750) || getEntities()->checkDistanceFromPosition(kEntitySalko, kPosition_2740, 500)) { + getSavePoints()->push(kEntityIvo, kEntitySalko, kAction123668192); + + setCallback(3); + setup_enterExitCompartment("613Ah", kObjectCompartmentH); + } else { + getEntities()->drawSequenceLeft(kEntityIvo, "613Hh"); + getEntities()->enterCompartment(kEntityIvo, kObjectCompartmentH, true); + } + break; + + case 3: + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityIvo); + + CALLBACK_ACTION(); + break; + + case 4: + getEntities()->exitCompartment(kEntityIvo, kObjectCompartmentH, true); + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityIvo); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Ivo, sitAtTableWithSalko) + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getEntities()->clearSequences(kEntitySalko); + getSavePoints()->push(kEntityIvo, kEntityTables2, kAction136455232); + + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityIvo, "023A1"); + getEntities()->drawSequenceRight(kEntitySalko, "023A2"); + getEntities()->drawSequenceRight(kEntityTables2, "023A3"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Ivo, leaveTableWithSalko) + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getSavePoints()->push(kEntityIvo, kEntityTables2, kActionDrawTablesWithChairs, "009E"); + getEntities()->clearSequences(kEntitySalko); + + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityIvo, "023D1"); + getEntities()->drawSequenceRight(kEntitySalko, "023D2"); + getEntities()->drawSequenceRight(kEntityTables2, "023D3"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Ivo, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject47, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + getData()->entityPosition = kPosition_4691; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Ivo, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + getData()->entityPosition = getEntityData(kEntityMilos)->entityPosition; + getData()->location = getEntityData(kEntityMilos)->location; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function11(); + break; + + case 2: + getSavePoints()->push(kEntityIvo, kEntityMilos, kAction135024800); + setup_function16(); + break; + } + break; + + case kAction125242096: + setCallback(1); + setup_updateFromTicks(75); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Ivo, function16) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getEntities()->clearSequences(kEntityIvo); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityIvo, "613Ch"); + getEntities()->enterCompartment(kEntityIvo, kObjectCompartmentH); + getSavePoints()->push(kEntityIvo, kEntityCoudert, kAction88652208); + break; + + case 2: + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + + getEntities()->clearSequences(kEntityIvo); + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + } + break; + + case kAction122865568: + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_enterExitCompartment("613Bh", kObjectCompartmentH); + break; + + case kAction123852928: + getEntities()->exitCompartment(kEntityIvo, kObjectCompartmentH, true); + + setCallback(2); + setup_enterExitCompartment("613Dh", kObjectCompartmentH); + break; + + case kAction221683008: + getSavePoints()->push(kEntityIvo, kEntityCoudert, kAction123199584); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Ivo, function17) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getEntities()->clearSequences(kEntityIvo); + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Ivo, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK(kTime1777500, params->param1, setup_function19); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityIvo); + + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject47, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Ivo, function19) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("613FH", kObjectCompartmentH); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + if (getData()->entityPosition < kPosition_2087) + getData()->entityPosition = kPosition_2088; + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + getSavePoints()->push(kEntityIvo, kEntitySalko, kAction136184016); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_draw("809US"); + break; + + case 4: + setCallback(5); + setup_sitAtTableWithSalko(); + break; + + case 5: + getData()->location = kLocationInsideCompartment; + setup_function20(); + break; + } + break; + + case kAction102675536: + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Ivo, function20) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime1809000 && params->param1) { + if (getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_leaveTableWithSalko(); + } + } + break; + + case kActionDefault: + getSavePoints()->push(kEntityIvo, kEntityServers1, kAction189688608); + getEntities()->drawSequenceLeft(kEntityIvo, "023B"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityIvo, kEntityServers1, kAction101106391); + getEntities()->drawSequenceLeft(kEntityIvo, "023B"); + params->param1 = 1; + break; + + case 2: + setCallback(3); + setup_function11(); + break; + + case 3: + getSavePoints()->push(kEntityIvo, kEntityServers1, kAction236237423); + setup_function21(); + break; + } + break; + + case kAction123712592: + getEntities()->drawSequenceLeft(kEntityIvo, "023C2"); + + setCallback(1); + setup_updateFromTime(450); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Ivo, function21) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Ivo, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityIvo); + + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Ivo, chapter3Handler) + if (savepoint.action == kActionDefault) + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Ivo, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Ivo, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime2361600 && getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getData()->location = kLocationOutsideCompartment; + setup_function26(); + } + break; + + case kActionDefault: + getSavePoints()->push(kEntityIvo, kEntityTables2, kAction136455232); + getEntities()->drawSequenceLeft(kEntityIvo, "023B"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Ivo, function26) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_leaveTableWithSalko(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function11(); + break; + + case 2: + setup_function27(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Ivo, function27) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + + if (getData()->entityPosition < kPosition_2087) + getData()->entityPosition = kPosition_2088; + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + getEntities()->clearSequences(kEntityIvo); + setup_function28(); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityIvo, "613Ch"); + getEntities()->enterCompartment(kEntityIvo, kObjectCompartmentH, true); + getSavePoints()->push(kEntityIvo, kEntityCoudert, kAction88652208); + break; + + case 4: + getEntities()->exitCompartment(kEntityIvo, kObjectCompartmentH, true); + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityIvo); + break; + } + break; + + case kAction55996766: + setCallback(1); + setup_enterExitCompartment("613FH", kObjectCompartmentH); + break; + + case kAction122865568: + getData()->location = kLocationOutsideCompartment; + + setCallback(3); + setup_enterExitCompartment("613Bh", kObjectCompartmentH); + break; + + case kAction123852928: + setCallback(4); + setup_enterExitCompartment("613Dh", kObjectCompartmentH); + break; + + case kAction221683008: + getSavePoints()->push(kEntityIvo, kEntityCoudert, kAction123199584); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Ivo, function28) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime2425500 && !params->param1) { + params->param1 = 1; + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_2740); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("613EH", kObjectCompartmentH); + break; + + case 2: + setup_function29(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Ivo, function29) + if (savepoint.action == kActionDefault) { + getEntities()->clearSequences(kEntityIvo); + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->inventoryItem = kItemNone; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Ivo, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityIvo); + + getData()->entityPosition = kPosition_540; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarBaggageRear; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, Ivo, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_fight(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Ivo, fight) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->location = kLocationOutsideCompartment; + getData()->entityPosition = kPosition_540; + getData()->car = kCarBaggageRear; + getData()->inventoryItem = kItemNone; + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventCathIvoFight); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSound()->playSound(kEntityPlayer, "LIB090"); + getAction()->playAnimation(kEventCathIvoFight); + + setCallback(2); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case 2: + params->param1 = getFight()->setup(kFightIvo); + if (params->param1) { + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneNone, true); + } else { + getScenes()->loadSceneFromPosition(kCarBaggageRear, 96); + setup_function33(); + } + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Ivo, function33) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getState()->time += 1800; + + setCallback(1); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case kActionCallback: + if (getCallback() == 1) + getObjects()->update(kObject94, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + + break; + + case kAction135800432: + setup_function34(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(34, Ivo, function34) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityIvo); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/ivo.h b/engines/lastexpress/entities/ivo.h new file mode 100644 index 0000000000..65bf9de165 --- /dev/null +++ b/engines/lastexpress/entities/ivo.h @@ -0,0 +1,177 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_IVO_H +#define LASTEXPRESS_IVO_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Ivo : public Entity { +public: + Ivo(LastExpressEngine *engine); + ~Ivo() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Updates parameter 2 using ticks value + * + * @param ticks The number of ticks to add + */ + DECLARE_FUNCTION_1(updateFromTicks, uint32 ticks) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Plays sound + * + * @param savepoint The savepoint + * - the sound filename + */ + DECLARE_FUNCTION_NOSETUP(playSound) + + /** + * Process callback action when somebody is standing in the restaurant or salon. + */ + DECLARE_FUNCTION(callbackActionRestaurantOrSalon) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + DECLARE_FUNCTION(function11) + DECLARE_FUNCTION(sitAtTableWithSalko) + DECLARE_FUNCTION(leaveTableWithSalko) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function16) + DECLARE_FUNCTION(function17) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + DECLARE_FUNCTION(function19) + DECLARE_FUNCTION(function20) + DECLARE_FUNCTION(function21) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function26) + DECLARE_FUNCTION(function27) + DECLARE_FUNCTION(function28) + DECLARE_FUNCTION(function29) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(fight) + DECLARE_FUNCTION(function33) + DECLARE_FUNCTION(function34) +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_IVO_H diff --git a/engines/lastexpress/entities/kahina.cpp b/engines/lastexpress/entities/kahina.cpp new file mode 100644 index 0000000000..b10b60476b --- /dev/null +++ b/engines/lastexpress/entities/kahina.cpp @@ -0,0 +1,944 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/kahina.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Kahina::Kahina(LastExpressEngine *engine) : Entity(engine, kEntityKahina) { + ADD_CALLBACK_FUNCTION(Kahina, reset); + ADD_CALLBACK_FUNCTION(Kahina, playSound); + ADD_CALLBACK_FUNCTION(Kahina, savegame); + ADD_CALLBACK_FUNCTION(Kahina, updateFromTime); + ADD_CALLBACK_FUNCTION(Kahina, updateFromTicks); + ADD_CALLBACK_FUNCTION(Kahina, function6); + ADD_CALLBACK_FUNCTION(Kahina, updateEntity2); + ADD_CALLBACK_FUNCTION(Kahina, updateEntity); + ADD_CALLBACK_FUNCTION(Kahina, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Kahina, chapter1); + ADD_CALLBACK_FUNCTION(Kahina, chapter1Handler); + ADD_CALLBACK_FUNCTION(Kahina, function12); + ADD_CALLBACK_FUNCTION(Kahina, function13); + ADD_CALLBACK_FUNCTION(Kahina, function14); + ADD_CALLBACK_FUNCTION(Kahina, function15); + ADD_CALLBACK_FUNCTION(Kahina, chapter2); + ADD_CALLBACK_FUNCTION(Kahina, chapter2Handler); + ADD_CALLBACK_FUNCTION(Kahina, chapter3); + ADD_CALLBACK_FUNCTION(Kahina, function19); + ADD_CALLBACK_FUNCTION(Kahina, chapter3Handler); + ADD_CALLBACK_FUNCTION(Kahina, function21); + ADD_CALLBACK_FUNCTION(Kahina, function22); + ADD_CALLBACK_FUNCTION(Kahina, function23); + ADD_CALLBACK_FUNCTION(Kahina, function24); + ADD_CALLBACK_FUNCTION(Kahina, function25); + ADD_CALLBACK_FUNCTION(Kahina, function26); + ADD_CALLBACK_FUNCTION(Kahina, function27); + ADD_CALLBACK_FUNCTION(Kahina, chapter4); + ADD_CALLBACK_FUNCTION(Kahina, chapter5); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Kahina, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Kahina, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(3, Kahina, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(4, Kahina, updateFromTime, uint32) + if (savepoint.action == kAction137503360) { + ENTITY_PARAM(0, 2) = 1; + CALLBACK_ACTION(); + } + + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(5, Kahina, updateFromTicks) + Entity::updateFromTicks(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(6, Kahina, function6, TimeValue) + error("Kahina: callback function 6 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(7 ,Kahina, updateEntity2, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->updateEntity(_entityIndex, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + + case kActionDefault: + if (getEntities()->updateEntity(_entityIndex, (CarIndex)params->param1, (EntityPosition)params->param2)) { + CALLBACK_ACTION(); + } else if (getEntities()->isDistanceBetweenEntities(kEntityKahina, kEntityPlayer, 1000) + && !getEntities()->isInGreenCarEntrance(kEntityPlayer) + && !getEntities()->isInsideCompartments(kEntityPlayer) + && !getEntities()->checkFields10(kEntityPlayer)) { + + if (getData()->car == kCarGreenSleeping || getData()->car == kCarRedSleeping) { + ENTITY_PARAM(0, 1) = 1; + CALLBACK_ACTION(); + } + } + break; + + case kAction137503360: + ENTITY_PARAM(0, 2) = 1; + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(8, Kahina, updateEntity, CarIndex, EntityPosition) + if (savepoint.action == kActionExcuseMeCath) { + if (getEvent(kEventKronosConversation) || getEvent(kEventKronosConversationFirebird)) { + getSound()->playSound(kEntityPlayer, rnd(2) ? "CAT1019" : "CAT1019A"); + } else { + getSound()->excuseMeCath(); + } + return; + } + + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(9, Kahina, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Kahina, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentKronos, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_5000; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarKronos; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Kahina, chapter1Handler) + if (savepoint.action != kActionNone) + return; + + if (getProgress().jacket != kJacketOriginal) + TIME_CHECK_SAVEPOINT(kTime1107000, params->param1, kEntityKahina, kEntityMertens, kAction238732837); + + if (getProgress().eventMertensKronosInvitation) + setup_function12(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Kahina, function12) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK(kTime1485000, params->param2, setup_function13); + break; + + case kActionKnock: + getSound()->playSound(kEntityPlayer, "LIB012"); + // Fallback to next action + + case kActionOpenDoor: + if (!getEvent(kEventKronosGoingToInvitation)) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventKronosGoingToInvitation); + break; + } + + if (savepoint.action == kActionOpenDoor) + getSound()->playSound(kEntityPlayer, "LIB014"); + + getScenes()->loadSceneFromPosition(kCarKronos, 80); + getSavePoints()->push(kEntityKahina, kEntityKronos, kAction171849314); + params->param1 = 1; + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentKronos, kEntityKahina, kObjectLocationNone, kCursorHandKnock, kCursorHand); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventKronosGoingToInvitation); + getScenes()->loadSceneFromPosition(kCarKronos, 80); + getSavePoints()->push(kEntityKahina, kEntityKronos, kAction171849314); + params->param1 = 1; + } + break; + + case kAction137685712: + setup_function13(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Kahina, function13) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getProgress().field_14 || getState()->time >= kTime1201500 || params->param2 == kTimeInvalid || params->param1 >= getState()->time) + break; + + if (getState()->time <= kTime1197000) { + if (!getEntities()->isPlayerInCar(kCarGreenSleeping) || !params->param2) { + params->param2 = getState()->time; + + if (!getState()->time) + goto label_callback; + } + + if (params->param2 >= getState()->time) + break; + } + + params->param2 = kTimeInvalid; + +label_callback: + setCallback(1); + setup_function15(); + break; + + case kActionDefault: + getData()->car = kCarKronos; + getData()->entityPosition = kPosition_5000; + getData()->location = kLocationOutsideCompartment; + + getObjects()->update(kObjectCompartmentKronos, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + params->param1 = getState()->time + 1800; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Kahina, function14) + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getEntities()->exitCompartment(kEntityKahina, kObjectCompartmentF); + CALLBACK_ACTION(); + break; + + case kAction4: + getEntities()->exitCompartment(kEntityKahina, kObjectCompartmentF); + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityKahina, "616Cf"); + getEntities()->enterCompartment(kEntityKahina, kObjectCompartmentF); + getSavePoints()->push(kEntityKahina, kEntityMax, kAction158007856); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Kahina, function15) + error("Kahina: callback function 15 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Kahina, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityKahina); + + getData()->entityPosition = kPosition_6000; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarKronos; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentKronos, kEntityKahina, kObjectLocation1, kCursorHandKnock, kCursorHand); + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Kahina, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) { + UPDATE_PARAM_PROC(params->param2, getState()->time, 9000) + params->param1 = 1; + params->param2 = 0; + UPDATE_PARAM_PROC_END + } + + if (getEvent(kEventKahinaAskSpeakFirebird) && getEvent(kEventKronosConversationFirebird) && getEntities()->isInsideTrainCar(kEntityPlayer, kCarKronos)) { + UPDATE_PARAM_PROC(params->param3, getState()->time, 900) + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventKronosConversationFirebird); + break; + UPDATE_PARAM_PROC_END + } + +label_callback_3: + if (getState()->time > kTime1845000 && getEvent(kEventKronosConversationFirebird) && getEntities()->isInKronosSalon(kEntityPlayer)) { + getObjects()->update(kObjectCompartmentKronos, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getScenes()->loadSceneFromPosition(kCarKronos, 87); + } + break; + + case kActionKnock: + case kActionOpenDoor: + if (getEvent(kEventKronosConversationFirebird)) + break; + + if (getEvent(kEventKahinaAskSpeakFirebird)) { + if (getSound()->isBuffered(kEntityKahina)) + getSound()->processEntry(kEntityKahina); + + if (savepoint.action == kActionKnock) + getSound()->playSound(kEntityPlayer, "LIB012"); + + setCallback(4); + setup_savegame(kSavegameTypeEvent, kEventKronosConversationFirebird); + break; + } + + if (getEvent(kEventMilosCompartmentVisitAugust) || getEvent(kEventTatianaGivePoem) || getEvent(kEventTatianaBreakfastGivePoem)) { + if (savepoint.action == kActionKnock) + getSound()->playSound(kEntityPlayer, "LIB012"); + + setCallback(7); + setup_savegame(kSavegameTypeEvent, kEventKahinaAskSpeakFirebird); + break; + } + + if (params->param1) { + if (savepoint.action == kActionKnock) + getSound()->playSound(kEntityPlayer, "LIB012"); + + getAction()->playAnimation(kEventKahinaAskSpeak); + getScenes()->processScene(); + + getObjects()->update(kObjectCompartmentKronos, kEntityKahina, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(8); + setup_playSound("KRO3003"); + } else { + getObjects()->update(kObjectCompartmentKronos, kEntityKahina, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(savepoint.action == kActionKnock ? 9 : 10); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + break; + + case kActionDefault: + params->param1 = 1; + getObjects()->update(kObjectCompartmentKronos, kEntityKahina, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 4: + getAction()->playAnimation(kEventKronosConversationFirebird); + getObjects()->update(kObjectCompartmentKronos, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getScenes()->loadSceneFromPosition(kCarKronos, 80, 1); + + setCallback(getCallback() == 1 ? 2 : 5); + setup_updateFromTime(900); + break; + + case 2: + case 5: + setCallback(getCallback() == 2 ? 3 : 6); + setup_playSound("KRO3005"); + break; + + case 3: + goto label_callback_3; + + case 7: + getAction()->playAnimation(kEventKahinaAskSpeakFirebird); + getScenes()->loadSceneFromPosition(kCarKronos, 81); + getSound()->playSound(kEntityKahina, "KRO3004"); + break; + + case 8: + case 9: + case 10: + getObjects()->update(kObjectCompartmentKronos, kEntityKahina, kObjectLocation1, kCursorHandKnock, kCursorHand); + if (getCallback() == 8) + params->param1 = 0; + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Kahina, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityKahina); + + getData()->entityPosition = kPosition_5000; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarKronos; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(19, Kahina, function19, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEvent(kEventAnnaBaggageArgument)) + RESET_ENTITY_STATE(kEntityKahina, Kahina, setup_function22); + + if (getEntities()->updateEntity(kEntityKahina, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + + case kActionExcuseMeCath: + if (getEvent(kEventKronosConversation) || getEvent(kEventKronosConversationFirebird)) + getSound()->playSound(kEntityPlayer, rnd(2) ? "CAT1019" : "CAT1019A"); + else + getSound()->excuseMeCath(); + break; + + case kActionExcuseMe: + getSound()->excuseMe(kEntityKahina); + break; + + case kActionDefault: + if (getEntities()->updateEntity(kEntityKahina, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Kahina, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEvent(kEventKronosVisit)) + getObjects()->update(kObjectCompartmentKronos, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + if (getEntities()->isInKronosSanctum(kEntityPlayer)) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventKahinaPunchSuite4); + break; + } + +label_callback_1: + if (getState()->time > kTime2079000 && !params->param2) { + params->param2 = 1; + + if (getEvent(kEventKahinaAskSpeakFirebird) + && !getEvent(kEventKronosConversationFirebird) + && getEntities()->isInsideTrainCar(kEntityPlayer, kCarKronos)) { + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventKronosConversationFirebird); + break; + } + +label_callback_2: + if (getEntities()->isInKronosSalon(kEntityPlayer)) + getScenes()->loadSceneFromPosition(kCarKronos, 87); + + setup_function21(); + break; + } + + if (!params->param1) { + UPDATE_PARAM_PROC(params->param3, getState()->time, 9000) + params->param1 = 1; + params->param3 = 0; + UPDATE_PARAM_PROC_END + } + + if (getEvent(kEventKahinaAskSpeakFirebird) + && !getEvent(kEventKronosConversationFirebird) + && getEntities()->isInsideTrainCar(kEntityPlayer, kCarKronos)) { + UPDATE_PARAM(params->param4, getState()->time, 900); + + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventKronosConversationFirebird); + } + break; + + case kActionKnock: + case kActionOpenDoor: + if (!getEvent(kEventKronosConversationFirebird)) { + + if (getEvent(kEventKahinaAskSpeakFirebird)) { + if (savepoint.action == kActionKnock) + getSound()->playSound(kEntityPlayer, "LIB012"); + + setCallback(6); + setup_savegame(kSavegameTypeEvent, kEventKronosConversationFirebird); + break; + } + + if (getEvent(kEventMilosCompartmentVisitAugust) || getEvent(kEventTatianaGivePoem) || getEvent(kEventTatianaBreakfastGivePoem)) { + if (savepoint.action == kActionKnock) + getSound()->playSound(kEntityPlayer, "LIB012"); + + setCallback(9); + setup_savegame(kSavegameTypeEvent, kEventKahinaAskSpeakFirebird); + break; + } + + if (params->param1) { + if (savepoint.action == kActionKnock) + getSound()->playSound(kEntityPlayer, "LIB012"); + + getAction()->playAnimation(kEventKahinaAskSpeak); + getScenes()->processScene(); + getObjects()->update(kObjectCompartmentKronos, kEntityKahina, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(10); + setup_playSound("KRO3003"); + break; + } + + getObjects()->update(kObjectCompartmentKronos, kEntityKahina, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(savepoint.action == kActionKnock ? 11 : 12); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + break; + + case kActionDefault: + if (getEvent(kEventKronosConversationFirebird)) { + getObjects()->update(kObjectCompartmentKronos, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + } else { + getObjects()->update(kObjectCompartmentKronos, kEntityKahina, kObjectLocation1, kCursorHandKnock, kCursorHand); + params->param1 = 1; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventKahinaPunchSuite4); + getLogic()->gameOver(kSavegameTypeEvent2, kEventCathJumpDownCeiling, kSceneNone, false); + goto label_callback_1; + + case 2: + getAction()->playAnimation(kEventKronosConversationFirebird); + getScenes()->loadSceneFromPosition(kCarKronos, 87); + goto label_callback_2; + + case 3: + getAction()->playAnimation(kEventKronosConversationFirebird); + getObjects()->update(kObjectCompartmentKronos, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getScenes()->loadSceneFromPosition(kCarKronos, 80, 1); + + setCallback(4); + setup_updateFromTime(900); + break; + + case 4: + setCallback(5); + setup_playSound("KRO3005"); + break; + + case 6: + getAction()->playAnimation(kEventKronosConversationFirebird); + getObjects()->update(kObjectCompartmentKronos, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getScenes()->loadSceneFromPosition(kCarKronos, 80, 1); + + setCallback(7); + setup_updateFromTime(900); + break; + + case 7: + setCallback(8); + setup_playSound("KRO3005"); + break; + + case 9: + getAction()->playAnimation(kEventKahinaAskSpeakFirebird); + getScenes()->loadSceneFromPosition(kCarKronos, 81); + getSound()->playSound(kEntityKahina, "KRO3004"); + break; + + case 10: + params->param1 = 0; + // Fallback to next case + + case 11: + case 12: + getObjects()->update(kObjectCompartmentKronos, kEntityKahina, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Kahina, function21) + error("Kahina: callback function 21 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Kahina, function22) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) { + ObjectLocation location = getInventory()->get(kItemFirebird)->location; + + if (ENTITY_PARAM(0, 3) || location == kObjectLocation3 || location == kObjectLocation7) { + setCallback(1); + setup_function25(); + } else if (location == kObjectLocation2 || location == kObjectLocation1) { + setCallback(2); + setup_function26(); + } + } + break; + + case kActionDefault: + getData()->car = kCarKronos; + getData()->entityPosition = kPosition_5000; + getData()->location = kLocationOutsideCompartment; + break; + + case kActionDrawScene: + if (getData()->car > kCarGreenSleeping || (getData()->car == kCarGreenSleeping && getData()->entityPosition > kPosition_2740)) + params->param1 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Kahina, function23) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getSound()->playSound(kEntityPlayer, "LIB014", getSound()->getSoundFlag(kEntityKahina)); + getSound()->playSound(kEntityPlayer, "LIB015", getSound()->getSoundFlag(kEntityKahina), 15); + + getEntities()->clearSequences(kEntityKahina); + + getData()->car = kCarGreenSleeping; + getData()->entityPosition = kPosition_540; + + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_4070); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getEntities()->checkFields19(kEntityPlayer, kCarRedSleeping, kPosition_4455) || getEntities()->isOutsideAnnaWindow()) { + setCallback(5); + setup_updateEntity(kCarRedSleeping, kPosition_9460); + break; + } else { + setCallback(2); + setup_enterExitCompartment("616Cf", kObjectCompartmentF); + } + break; + + case 2: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityKahina); + + getObjects()->update(kObjectCompartmentF, kEntityPlayer, getObjects()->get(kObjectCompartmentF).location, kCursorNormal, kCursorNormal); + getObjects()->update(kObject53, kEntityPlayer, getObjects()->get(kObject53).location, kCursorNormal, kCursorNormal); + + setCallback(3); + setup_updateFromTime(900); + break; + + case 3: + getObjects()->update(kObjectCompartmentF, kEntityPlayer, getObjects()->get(kObjectCompartmentF).location, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityPlayer, getObjects()->get(kObject53).location, kCursorHandKnock, kCursorHand); + + setCallback(4); + setup_enterExitCompartment("616Df", kObjectCompartmentF); + break; + + case 4: + getData()->location = kLocationOutsideCompartment; + + setCallback(5); + setup_updateEntity(kCarRedSleeping, kPosition_9460); + break; + + case 5: + getEntities()->clearSequences(kEntityKahina); + + setCallback(6); + setup_updateFromTime(900); + break; + + case 6: + setCallback(7); + setup_updateEntity(kCarKronos, kPosition_9270); + break; + + case 7: + getEntities()->clearSequences(kEntityKahina); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Kahina, function24) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 && getEntities()->updateEntity(kEntityKahina, (CarIndex)params->param2, (EntityPosition)params->param3)) { + getEntities()->clearSequences(kEntityKahina); + params->param1 = 0; + } + break; + + case kActionEndSound: + if (getEntities()->isInsideTrainCar(kEntityPlayer, kCarKronos)) + getSavePoints()->push(kEntityKahina, kEntityKronos, kActionOpenDoor); + else + setup_function27(); + break; + + case kActionDefault: + setCallback(1); + setup_function6(kTime2241000); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (ENTITY_PARAM(0, 2)) { + getEntities()->clearSequences(kEntityKahina); + if (getSound()->isBuffered(kEntityKahina)) + getSound()->processEntry(kEntityKahina); + + getProgress().field_44 = 0; + + setup_function22(); + } else if (ENTITY_PARAM(0, 1)) { + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventKahinaGunYellow); + } else { + setup_function27(); + } + break; + + case 2: + if (getEntityData(kEntityPlayer)->entityPosition >= getData()->entityPosition) + getAction()->playAnimation(getData()->car < kCarRedSleeping ? kEventKahinaGunYellow : kEventKahinaGunBlue); + else + getAction()->playAnimation(kEventKahinaGun); + + getEntities()->updateEntity(kEntityKahina, kCarKronos, kPosition_9270); + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + 750)); + getSavePoints()->push(kEntityKahina, kEntityKronos, kAction235599361); + getSound()->playSound(kEntityKahina, "MUS016", SoundManager::kFlagDefault); + getProgress().field_44 = 1; + + params->param1 = true; + params->param2 = kCarKronos; + params->param3 = kPosition_9270; + break; + } + break; + + case kAction137503360: + getEntities()->clearSequences(kEntityKahina); + if (getSound()->isBuffered(kEntityKahina)) + getSound()->processEntry(kEntityKahina); + + getProgress().field_44 = 0; + + setup_function22(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Kahina, function25) + error("Kahina: callback function 25 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Kahina, function26) + error("Kahina: callback function 26 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Kahina, function27) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInGreenCarEntrance(kEntityPlayer)) + params->param1 = kEventKahinaPunchCar; + else if (getEntities()->isPlayerInCar(kCarGreenSleeping)) + params->param1 = kEventKahinaPunchBlue; + else if (getEntities()->isPlayerInCar(kCarRedSleeping)) + params->param1 = kEventKahinaPunchYellow; + else if (getEntities()->isInSalon(kEntityPlayer)) + params->param1 = kEventKahinaPunchSalon; + else if (getEntities()->isInRestaurant(kEntityPlayer)) + params->param1 = kEventKahinaPunchRestaurant; + else if (getEntities()->isInKitchen(kEntityPlayer)) + params->param1 = kEventKahinaPunchKitchen; + else if (getEntities()->isInBaggageCarEntrance(kEntityPlayer)) + params->param1 = kEventKahinaPunchBaggageCarEntrance; + else if (getEntities()->isInsideTrainCar(kEntityPlayer, kCarBaggage)) + params->param1 = kEventKahinaPunchBaggageCar; + + if (params->param1) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kSceneGameOverAlarm2); + } + break; + + case kActionDefault: + getState()->timeDelta = 0; + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation((EventIndex)params->param1); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneNone, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Kahina, chapter4) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityKahina); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Kahina, chapter5) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityKahina); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/kahina.h b/engines/lastexpress/entities/kahina.h new file mode 100644 index 0000000000..1eba9c62d9 --- /dev/null +++ b/engines/lastexpress/entities/kahina.h @@ -0,0 +1,166 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_KAHINA_H +#define LASTEXPRESS_KAHINA_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Kahina : public Entity { +public: + Kahina(LastExpressEngine *engine); + ~Kahina() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Updates parameter 2 using ticks value + * + * @param savepoint The savepoint + * - ticks to add + */ + DECLARE_FUNCTION_NOSETUP(updateFromTicks) + + DECLARE_FUNCTION_1(function6, TimeValue timeValue) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity2, CarIndex car, EntityPosition entityPosition) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function12) + DECLARE_FUNCTION(function13) + DECLARE_FUNCTION(function14) + DECLARE_FUNCTION(function15) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Update the entity, handling excuse me events and resetting the entity state after the argument with Anna in the baggage car + * + * @param car The car index + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(function19, CarIndex car, EntityPosition entityPosition) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + DECLARE_FUNCTION(function21) + DECLARE_FUNCTION(function22) + DECLARE_FUNCTION(function23) + DECLARE_FUNCTION(function24) + DECLARE_FUNCTION(function25) + DECLARE_FUNCTION(function26) + DECLARE_FUNCTION(function27) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_KAHINA_H diff --git a/engines/lastexpress/entities/kronos.cpp b/engines/lastexpress/entities/kronos.cpp new file mode 100644 index 0000000000..f45f18bbd2 --- /dev/null +++ b/engines/lastexpress/entities/kronos.cpp @@ -0,0 +1,666 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/kronos.h" + +#include "lastexpress/entities/anna.h" +#include "lastexpress/entities/august.h" +#include "lastexpress/entities/rebecca.h" +#include "lastexpress/entities/sophie.h" +#include "lastexpress/entities/tatiana.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Kronos::Kronos(LastExpressEngine *engine) : Entity(engine, kEntityKronos) { + ADD_CALLBACK_FUNCTION(Kronos, reset); + ADD_CALLBACK_FUNCTION(Kronos, savegame); + ADD_CALLBACK_FUNCTION(Kronos, updateEntity); + ADD_CALLBACK_FUNCTION(Kronos, playSound); + ADD_CALLBACK_FUNCTION(Kronos, updateFromTime); + ADD_CALLBACK_FUNCTION(Kronos, updateFromTicks); + ADD_CALLBACK_FUNCTION(Kronos, chapter1); + ADD_CALLBACK_FUNCTION(Kronos, chapter1Handler); + ADD_CALLBACK_FUNCTION(Kronos, function9); + ADD_CALLBACK_FUNCTION(Kronos, function10); + ADD_CALLBACK_FUNCTION(Kronos, function11); + ADD_CALLBACK_FUNCTION(Kronos, chapter2); + ADD_CALLBACK_FUNCTION(Kronos, chapter3); + ADD_CALLBACK_FUNCTION(Kronos, chapter3Handler); + ADD_CALLBACK_FUNCTION(Kronos, function15); + ADD_CALLBACK_FUNCTION(Kronos, function16); + ADD_CALLBACK_FUNCTION(Kronos, function17); + ADD_CALLBACK_FUNCTION(Kronos, function18); + ADD_CALLBACK_FUNCTION(Kronos, function19); + ADD_CALLBACK_FUNCTION(Kronos, function20); + ADD_CALLBACK_FUNCTION(Kronos, function21); + ADD_CALLBACK_FUNCTION(Kronos, function22); + ADD_CALLBACK_FUNCTION(Kronos, function23); + ADD_CALLBACK_FUNCTION(Kronos, chapter4); + ADD_CALLBACK_FUNCTION(Kronos, chapter5); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Kronos, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(2, Kronos, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(3, Kronos, updateEntity, CarIndex, EntityPosition) + Entity::updateEntity(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(4, Kronos, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(5, Kronos, updateFromTime) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(6, Kronos, updateFromTicks) + Entity::updateFromTicks(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Kronos, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_6000; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarKronos; + + getObjects()->update(kObjectCeiling, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Kronos, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK(kTime1489500, params->param2, setup_function11); + break; + + case kAction171849314: + params->param1 = 1; + break; + + case kAction202621266: + setup_function9(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Kronos, function9) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventKronosConversation); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventKronosConversation); + getScenes()->loadSceneFromPosition(kCarKronos, 87); + getSavePoints()->push(kEntityKronos, kEntityKahina, kAction137685712); + setup_function10(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Kronos, function10) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK(kTime1489500, params->param1, setup_function11); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_6000; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarKronos; + + getEntities()->clearSequences(kEntityKronos); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Kronos, function11) + switch (savepoint.action) { + default: + break; + + case kActionEndSound: + params->param1++; + getSound()->playSound(kEntityKronos, (params->param1 & 1) ? "KRO1001" : "KRO1002"); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_7000; + + if (!getSound()->isBuffered(kEntityKronos)) + getSound()->playSound(kEntityKronos, "KRO1001"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Kronos, chapter2) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityKronos); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Kronos, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityKronos); + + getData()->entityPosition = kPosition_6000; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarKronos; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCeiling, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Kronos, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime1993500 && !params->param1 && !params->param2 && !params->param3) + setup_function15(); + break; + + case kAction157159392: + switch (savepoint.entity2) { + case kEntityAnna: + params->param1 = 1; + break; + + case kEntityTatiana: + params->param2 = 1; + break; + + case kEntityAbbot: + params->param3 = 1; + break; + + default: + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Kronos, function15) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 && !getEntities()->isInSalon(kEntityBoutarel)) { + UPDATE_PARAM_PROC(params->param2, getState()->timeTicks, 75) + setup_function16(); + break; + UPDATE_PARAM_PROC_END + } + + if (params->param3 != kTimeInvalid && getState()->time > kTime2002500) { + if (getState()->time <= kTime2052000) { + if (!getEntities()->isInSalon(kEntityPlayer) || getEntities()->isInSalon(kEntityPlayer) || !params->param3) + params->param3 = getState()->time + 900; + + if (params->param3 >= getState()->time) + break; + } + + params->param3 = kTimeInvalid; + + if (getEntities()->isInSalon(kEntityPlayer)) { + setup_function16(); + } else { + getSavePoints()->push(kEntityKronos, kEntityAnna, kAction101169422); + getSavePoints()->push(kEntityKronos, kEntityTatiana, kAction101169422); + getSavePoints()->push(kEntityKronos, kEntityAbbot, kAction101169422); + + setup_function18(); + } + } + break; + + case kActionDefault: + if (getEntities()->isPlayerPosition(kCarRestaurant, 60) + || getEntities()->isPlayerPosition(kCarRestaurant, 59) + || getEntities()->isPlayerPosition(kCarRestaurant, 83) + || getEntities()->isPlayerPosition(kCarRestaurant, 81) + || getEntities()->isPlayerPosition(kCarRestaurant, 87)) + params->param1 = 1; + break; + + case kActionDrawScene: + if (params->param1 && getEntities()->isPlayerPosition(kCarRestaurant, 51) && !getEntities()->isInSalon(kEntityBoutarel)) + setup_function16(); + else + params->param1 = getEntities()->isPlayerPosition(kCarRestaurant, 60) + || getEntities()->isPlayerPosition(kCarRestaurant, 59) + || getEntities()->isPlayerPosition(kCarRestaurant, 83) + || getEntities()->isPlayerPosition(kCarRestaurant, 81) + || getEntities()->isPlayerPosition(kCarRestaurant, 87); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16 ,Kronos, function16) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventKronosVisit); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventKronosVisit); + getSavePoints()->push(kEntityKronos, kEntityAnna, kAction101169422); + getSavePoints()->push(kEntityKronos, kEntityTatiana, kAction101169422); + getSavePoints()->push(kEntityKronos, kEntityAbbot, kAction101169422); + getScenes()->loadSceneFromPosition(kCarRestaurant, 60); + + setup_function17(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Kronos, function17) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_7500; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_9270); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function18(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Kronos, function18) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime2079000 && !params->param2) { + getObjects()->updateLocation2(kObjectCompartmentKronos, kObjectLocation3); + getObjects()->update(kObjectCompartmentKronos, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + params->param1 = 1; + params->param2 = 1; + } + + TIME_CHECK(kTime2106000, params->param3, setup_function19) + else { + if (params->param1 && getEntities()->isInKronosSanctum(kEntityPlayer)) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventKahinaPunchSuite4); + } + } + break; + + case kActionDefault: + getData()->entityPosition = kPosition_6000; + getData()->car = kCarKronos; + getData()->location = kLocationOutsideCompartment; + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventKahinaPunchSuite4); + getLogic()->gameOver(kSavegameTypeEvent2, kEventCathJumpDownCeiling, kSceneNone, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Kronos, function19) + switch (savepoint.action) { + default: + break; + + case kActionNone: + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentKronos, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorNormal); + break; + + case kActionDrawScene: + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventKahinaPunchSuite4); + getLogic()->gameOver(kSavegameTypeEvent2, kEventCathJumpDownCeiling, kSceneNone, true); + break; + + case 2: + getAction()->playAnimation(kEventConcertStart); + getSound()->setupEntry(SoundManager::kSoundType7, kEntityKronos); + getScenes()->loadSceneFromPosition(kCarKronos, 83); + + RESET_ENTITY_STATE(kEntityRebecca, Rebecca, setup_function39); + RESET_ENTITY_STATE(kEntitySophie, Sophie, setup_chaptersHandler); + RESET_ENTITY_STATE(kEntityAugust, August, setup_function50); + RESET_ENTITY_STATE(kEntityAnna, Anna, setup_function56); + RESET_ENTITY_STATE(kEntityTatiana, Tatiana, setup_function35); + + setup_function20(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Kronos, function20) + error("Kronos: callback function 20 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Kronos, function21) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInKronosSanctum(kEntityPlayer)) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventKahinaWrongDoor); + } + break; + + case kActionDefault: + getProgress().field_40 = 0; + getObjects()->update(kObjectCompartmentKronos, kEntityPlayer, kObjectLocation3, kCursorNormal, kCursorNormal); + getSavePoints()->push(kEntityKronos, kEntityRebecca, kAction191668032); + if (!getEvent(kEventConcertLeaveWithBriefcase)) + setup_function22(); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventKahinaWrongDoor); + + if (getInventory()->hasItem(kItemBriefcase)) + getInventory()->removeItem(kItemBriefcase); + + getSound()->playSound(kEntityPlayer, "BUMP"); + + getScenes()->loadSceneFromPosition(kCarKronos, 81); + + getSound()->playSound(kEntityPlayer, "LIB015"); + } + break; + + case kAction235599361: + setup_function22(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Kronos, function22) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getProgress().field_44) { + setCallback(5); + setup_savegame(kSavegameTypeEvent, kEventKahinaPunchBaggageCarEntrance); + } else { + setCallback(6); + setup_savegame(kSavegameTypeEvent, kEventKahinaWrongDoor); + } + break; + + case kActionKnock: + case kActionOpenDoor: + if (!getSound()->isBuffered(savepoint.action == kActionKnock ? "LIB012" : "LIB013", true)) + getSound()->playSound(kEntityPlayer, savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + + if (getEvent(kEventConcertLeaveWithBriefcase)) + getSavePoints()->call(kEntityKronos, kEntityKahina, kAction137503360); + + if (getInventory()->hasItem(kItemBriefcase)) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventKronosReturnBriefcase); + break; + } + + if (getInventory()->hasItem(kItemFirebird) && getEvent(kEventConcertLeaveWithBriefcase)) { + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventKronosBringEggCeiling); + break; + } + + if (getInventory()->hasItem(kItemFirebird)) { + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventKronosBringEggCeiling); + break; + } + + if (getEvent(kEventConcertLeaveWithBriefcase)) { + setCallback(4); + setup_savegame(kSavegameTypeEvent, kEventKronosBringNothing); + break; + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentKronos, kEntityKronos, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventKronosReturnBriefcase); + getScenes()->loadSceneFromPosition(kCarKronos, 87); + getInventory()->removeItem(kItemFirebird); + getInventory()->removeItem(kItemScarf); + + setup_function23(); + break; + + case 2: + getAction()->playAnimation(kEventKronosBringEggCeiling); + getScenes()->loadSceneFromPosition(kCarKronos, 87); + getInventory()->removeItem(kItemFirebird); + getInventory()->get(kItemFirebird)->location = kObjectLocation5; + + setup_function23(); + break; + + case 3: + getInventory()->removeItem(kItemFirebird); + getInventory()->get(kItemFirebird)->location = kObjectLocation5; + getAction()->playAnimation(kEventKronosBringEgg); + getScenes()->loadSceneFromPosition(kCarKronos, 87); + getInventory()->addItem(kItemBriefcase); + setup_function23(); + break; + + case 4: + getAction()->playAnimation(kEventKronosBringNothing); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneNone, true); + break; + + case 5: + getAction()->playAnimation(kEventKahinaPunchSuite4); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneNone, true); + break; + + case 6: + getAction()->playAnimation(kEventKahinaWrongDoor); + if (getInventory()->hasItem(kItemBriefcase)) + getInventory()->removeItem(kItemBriefcase); + + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromPosition(kCarKronos, 81); + getSound()->playSound(kEntityPlayer, "LIB015"); + break; + } + break; + + case kAction138085344: + setup_function23(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Kronos, function23) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInKronosSanctum(kEntityPlayer)) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventKahinaWrongDoor); + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentKronos, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventKahinaWrongDoor); + + if (getInventory()->hasItem(kItemBriefcase)) + getInventory()->removeItem(kItemBriefcase); + + getSound()->playSound(kEntityPlayer, "BUMP"); + + getScenes()->loadSceneFromPosition(kCarKronos, 81); + + getSound()->playSound(kEntityPlayer, "LIB015"); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Kronos, chapter4) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityKronos); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Kronos, chapter5) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityKronos); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/kronos.h b/engines/lastexpress/entities/kronos.h new file mode 100644 index 0000000000..5d8efbd4c2 --- /dev/null +++ b/engines/lastexpress/entities/kronos.h @@ -0,0 +1,138 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_KRONOS_H +#define LASTEXPRESS_KRONOS_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Kronos : public Entity { +public: + Kronos(LastExpressEngine *engine); + ~Kronos() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + /** + * Plays sound + * + * @param savepoint The savepoint + * - the sound filename + */ + DECLARE_FUNCTION_NOSETUP(playSound) + + /** + * Updates parameter 2 using time value + * + * @param savepoint The savepoint + * - Time to add + */ + DECLARE_FUNCTION_NOSETUP(updateFromTime) + + /** + * Updates parameter 2 using ticks value + * + * @param savepoint The savepoint + * - ticks to add + */ + DECLARE_FUNCTION_NOSETUP(updateFromTicks) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function9) + DECLARE_FUNCTION(function10) + DECLARE_FUNCTION(function11) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + DECLARE_FUNCTION(function15) + DECLARE_FUNCTION(function16) + DECLARE_FUNCTION(function17) + DECLARE_FUNCTION(function18) + DECLARE_FUNCTION(function19) + DECLARE_FUNCTION(function20) + DECLARE_FUNCTION(function21) + DECLARE_FUNCTION(function22) + DECLARE_FUNCTION(function23) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_KRONOS_H diff --git a/engines/lastexpress/entities/mahmud.cpp b/engines/lastexpress/entities/mahmud.cpp new file mode 100644 index 0000000000..0200f0b554 --- /dev/null +++ b/engines/lastexpress/entities/mahmud.cpp @@ -0,0 +1,839 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/data/scene.h" + +#include "lastexpress/entities/mahmud.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Mahmud::Mahmud(LastExpressEngine *engine) : Entity(engine, kEntityMahmud) { + ADD_CALLBACK_FUNCTION(Mahmud, reset); + ADD_CALLBACK_FUNCTION(Mahmud, draw); + ADD_CALLBACK_FUNCTION(Mahmud, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Mahmud, enterExitCompartment2); + ADD_CALLBACK_FUNCTION(Mahmud, playSound); + ADD_CALLBACK_FUNCTION(Mahmud, playSoundMertens); + ADD_CALLBACK_FUNCTION(Mahmud, updateFromTime); + ADD_CALLBACK_FUNCTION(Mahmud, savegame); + ADD_CALLBACK_FUNCTION(Mahmud, updateEntity); + ADD_CALLBACK_FUNCTION(Mahmud, function10); + ADD_CALLBACK_FUNCTION(Mahmud, function11); + ADD_CALLBACK_FUNCTION(Mahmud, function12); + ADD_CALLBACK_FUNCTION(Mahmud, function13); + ADD_CALLBACK_FUNCTION(Mahmud, chaptersHandler); + ADD_CALLBACK_FUNCTION(Mahmud, chapter1); + ADD_CALLBACK_FUNCTION(Mahmud, resetChapter); + ADD_CALLBACK_FUNCTION(Mahmud, chapter2); + ADD_CALLBACK_FUNCTION(Mahmud, chapter3); + ADD_CALLBACK_FUNCTION(Mahmud, chapter4); + ADD_CALLBACK_FUNCTION(Mahmud, chapter5); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Mahmud, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(2, Mahmud, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(3, Mahmud, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SIII(4, Mahmud, enterExitCompartment2, ObjectIndex, uint32, ObjectIndex) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param7, getState()->timeTicks, params->param5); + + if (!getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp)) + getScenes()->loadSceneFromObject((ObjectIndex)params->param6, true); + break; + + case kActionExitCompartment: + getEntities()->exitCompartment(kEntityMahmud, (ObjectIndex)params->param4); + + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityMahmud, (char *)¶ms->seq); + getEntities()->enterCompartment(kEntityMahmud, (ObjectIndex)params->param4); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(5, Mahmud, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(6, Mahmud, playSoundMertens) + Entity::playSound(savepoint, false, getSound()->getSoundFlag(kEntityMertens)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(7, Mahmud, updateFromTime) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(8, Mahmud, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(9, Mahmud, updateEntity, CarIndex, EntityPosition) + if (savepoint.action == kActionExcuseMeCath) { + if (getInventory()->hasItem(kItemPassengerList)) + getSound()->playSound(kEntityPlayer, rnd(2) ? "CAT1025" : "CAT1025Q"); + else + getSound()->excuseMeCath(); + + return; + } + + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(10, Mahmud, function10, ObjectIndex, bool) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param6, getState()->time, 13500); + + getObjects()->update(kObjectCompartment5, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment6, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment7, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment8, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + + setCallback(2); + setup_enterExitCompartment("614Ed", kObjectCompartment4); + break; + + case kActionEndSound: + case kActionDrawScene: + if (!getSound()->isBuffered(kEntityMahmud)) { + EntityPosition position = getEntityData(kEntityPlayer)->entityPosition; + if (position < kPosition_1500 || position >= kPosition_5790 || (position > kPosition_4455 && params->param5 != 5)) { + getObjects()->update(kObjectCompartment5, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment6, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment7, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment8, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + + setCallback(3); + setup_enterExitCompartment("614Ed", kObjectCompartment4); + } + } + break; + + case kActionKnock: + case kActionOpenDoor: + if (!getSound()->isBuffered((savepoint.action == kActionKnock) ? "LIB012" : "LIB013", true)) + getSound()->playSound(kEntityPlayer, (savepoint.action == kActionKnock) ? "LIB012" : "LIB013"); + + params->param5 = savepoint.param.intValue; + + if (!getSound()->isBuffered(kEntityMahmud)) { + params->param3++; + + switch(params->param3) { + default: + params->param4 = 1; + break; + + case 1: + getSound()->playSound(kEntityMahmud, "MAH1174"); + break; + + case 2: + getSound()->playSound(kEntityMahmud, "MAH1173B"); + break; + + case 3: + getSound()->playSound(kEntityMahmud, params->param2 ? "MAH1170E" : "MAH1173A"); + break; + } + } + + if (params->param4) { + if (getState()->time >= kTimeCityGalanta) { + params->param3 = 0; + } else { + getSound()->playSound(kEntityTrain, "LIB050", SoundManager::kFlagDefault); + getLogic()->gameOver(kSavegameTypeIndex, 0, (getProgress().chapter == kChapter1) ? kSceneGameOverPolice1 : kSceneGameOverPolice2, true); + } + break; + } + + getAction()->handleOtherCompartment((ObjectIndex)savepoint.param.intValue, false, false); + + switch (getScenes()->get(getState()->scene)->position) { + default: + break; + + case 55: + getScenes()->loadSceneFromObject(kObjectCompartment5, true); + break; + + case 56: + getScenes()->loadSceneFromObject(kObjectCompartment6, true); + break; + + case 57: + getScenes()->loadSceneFromObject(kObjectCompartment7, true); + break; + + case 58: + getScenes()->loadSceneFromObject(kObjectCompartment8, true); + break; + } + break; + + case kActionDefault: + getSound()->playSound(kEntityMahmud, params->param2 ? "MAH1170A" : "MAH1173", SoundManager::kFlagInvalid, 45); + getProgress().field_C4 = 1; + + setCallback(1); + setup_enterExitCompartment2("614Dd", kObjectCompartment4, 30, (ObjectIndex)params->param1); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartment5, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment6, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment7, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment8, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + + getData()->location = kLocationOutsideCompartment; + + getEntities()->drawSequenceLeft(kEntityMahmud, "614Md"); + getEntities()->enterCompartment(kEntityMahmud, kObjectCompartment4, true); + break; + + case 2: + case 3: + getEntities()->exitCompartment(kEntityMahmud, kObjectCompartment4, true); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMahmud); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Mahmud, function11) + switch (savepoint.action) { + default: + break; + + case kActionKnock: + case kActionOpenDoor: { + getSound()->playSound(kEntityPlayer, (savepoint.action == kActionKnock ? "LIB012" : "LIB013")); + + if (!getSound()->isBuffered(kEntityMahmud)) { + params->param1++; + + getSound()->playSound(kEntityMahmud, (params->param1 == 1 ? "MAH1170E" : (params->param1 == 2 ? "MAH1173B" : "MAH1174"))); + } + + switch (getScenes()->get(getState()->scene)->position) { + default: + break; + + case 55: + getScenes()->loadSceneFromObject(kObjectCompartment5, true); + break; + + case 56: + getScenes()->loadSceneFromObject(kObjectCompartment6, true); + break; + + case 57: + getScenes()->loadSceneFromObject(kObjectCompartment7, true); + break; + + case 58: + getScenes()->loadSceneFromObject(kObjectCompartment8, true); + break; + } + break; + } + + case kActionDefault: + getSavePoints()->push(kEntityMahmud, kEntityMertens, kAction102227384); + setCallback(1); + setup_enterExitCompartment("614Ad", kObjectCompartment4); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getObjects()->update(kObjectCompartment4, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getEntities()->drawSequenceLeft(kEntityMahmud, "614Kd"); + getEntities()->enterCompartment(kEntityMahmud, kObjectCompartment4, true); + + setCallback(2); + setup_playSound("MAH1170A"); + break; + + case 2: + setCallback(3); + setup_playSoundMertens("MAH1170B"); + break; + + case 3: + setCallback(4); + setup_playSound("MAH1170C"); + break; + + case 4: + setCallback(5); + setup_playSoundMertens("MAH1170D"); + break; + + case 5: + setCallback(6); + setup_playSound("MAH1170E"); + break; + + case 6: + setCallback(7); + setup_playSoundMertens("MAH1170F"); + break; + + case 7: + setCallback(8); + setup_enterExitCompartment("614Ld", kObjectCompartment4); + break; + + case 8: + getSavePoints()->push(kEntityMahmud, kEntityMertens, kAction156567128); + getEntities()->drawSequenceLeft(kEntityMahmud, "614Bd"); + getEntities()->enterCompartment(kEntityMahmud, kObjectCompartment4, true); + + setCallback(9); + setup_playSound("MAH1170G"); + break; + + case 9: + setCallback(10); + setup_playSoundMertens("MAH1170H"); + break; + + case 10: + getObjects()->update(kObjectCompartment5, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment6, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment7, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment8, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + + case 11: + getEntities()->exitCompartment(kEntityMahmud, kObjectCompartment4, true); + getData()->location = kLocationInsideCompartment; + + getEntities()->clearSequences(kEntityMahmud); + getObjects()->update(kObjectCompartment4, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + } + break; + + case kAction123852928: + if (getSound()->isBuffered(kEntityMahmud)) + getSound()->processEntry(kEntityMahmud); + + getObjects()->update(kObjectCompartment5, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment6, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment7, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment8, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + + setCallback(11); + setup_enterExitCompartment("614Cd", kObjectCompartment4); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +// TODO: factorize code between function12 & function13 +IMPLEMENT_FUNCTION(12, Mahmud, function12) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("614Gd", kObjectCompartment4); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getObjects()->update(kObjectCompartment4, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_4070); + break; + + case 2: + setCallback(3); + setup_enterExitCompartment("614Ff", kObjectCompartment6); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMahmud); + + setCallback(4); + setup_playSound("Har1105"); + break; + + case 4: + setCallback(5); + setup_enterExitCompartment("614Gf", kObjectCompartment6); + break; + + case 5: + getData()->location = kLocationOutsideCompartment; + + setCallback(6); + setup_updateEntity(kCarGreenSleeping, kPosition_5790); + break; + + case 6: + setCallback(7); + setup_enterExitCompartment("614Fd", kObjectCompartment4); + break; + + case 7: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMahmud); + + CALLBACK_ACTION(); + break; + + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Mahmud, function13) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("614Gd", kObjectCompartment4); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getObjects()->update(kObjectCompartment4, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_2740); + break; + + case 2: + setCallback(3); + setup_enterExitCompartment("614Fh", kObjectCompartment8); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMahmud); + + setCallback(4); + setup_playSound("Har1107"); + break; + + case 4: + setCallback(5); + setup_enterExitCompartment("614Gh", kObjectCompartment8); + break; + + case 5: + getData()->location = kLocationOutsideCompartment; + + setCallback(6); + setup_updateEntity(kCarGreenSleeping, kPosition_5790); + break; + + case 6: + setCallback(7); + setup_enterExitCompartment("614Fd", kObjectCompartment4); + break; + + case 7: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMahmud); + + CALLBACK_ACTION(); + break; + + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Mahmud, chaptersHandler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (ENTITY_PARAM(0, 1)) { + params->param2 = 1; + getSavePoints()->push(kEntityMahmud, kEntityMertens, kAction204379649); + ENTITY_PARAM(0, 1) = 0; + } + + if (!params->param2 && getProgress().chapter == kChapter1) { + + TIME_CHECK_CALLBACK(kTime1098000, params->param6, 1, setup_function13); + + if (!getSound()->isBuffered("HAR1104") && getState()->time > kTime1167300 && !params->param7) { + params->param7 = 1; + + setCallback(2); + setup_function12(); + break; + } + } + + if (params->param5) { + UPDATE_PARAM(params->param8, getState()->timeTicks, 75); + + params->param4 = 1; + params->param5 = 0; + + getObjects()->update(kObjectCompartment4, kEntityMahmud, kObjectLocation3, kCursorNormal, kCursorNormal); + } + + params->param8 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + if (params->param5) { + getObjects()->update(kObjectCompartment4, kEntityMahmud, kObjectLocation3, kCursorNormal, kCursorNormal); + + if (getProgress().jacket == kJacketBlood || getEvent(kEventMahmudWrongDoor) || getEvent(kEventMahmudWrongDoorOriginalJacket) || getEvent(kEventMahmudWrongDoorDay)) { + // Check if we have the passenger list + if (getInventory()->hasItem(kItemPassengerList)) { + setCallback(6); + setup_playSound(rnd(2) == 0 ? "CAT1501" : getSound()->wrongDoorCath()); + } else { + setCallback(7); + setup_playSound(getSound()->wrongDoorCath()); + } + } else { + setCallback(savepoint.action == kActionKnock ? 8 : 9); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + } else { + getObjects()->update(kObjectCompartment4, kEntityMahmud, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(savepoint.action == kActionKnock ? 3 : 4); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + getEntities()->clearSequences(kEntityMahmud); + params->param3 = 1; + + getObjects()->update(kObjectCompartment4, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + + case kActionDrawScene: + if (params->param4 || params->param5) { + getObjects()->update(kObjectCompartment4, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + params->param4 = 0; + params->param5 = 0; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + return; + + case 1: + getObjects()->update(kObjectCompartment4, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + params->param4 = 0; + params->param5 = 0; + + if (!getSound()->isBuffered("HAR1104") && getState()->time > kTime1167300 && !params->param7) { + params->param7 = 1; + setCallback(2); + setup_function12(); + break; + } + + params->param8 = 0; + break; + + case 2: + getObjects()->update(kObjectCompartment4, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + params->param4 = 0; + params->param5 = 0; + params->param8 = 0; + break; + + case 3: + case 4: + setCallback(5); + setup_playSound("MAH1175"); + break; + + case 5: { + CursorStyle cursor = kCursorHand; + CursorStyle cursor2 = kCursorHandKnock; + + if (getProgress().jacket == kJacketBlood + || getEvent(kEventMahmudWrongDoor) + || getEvent(kEventMahmudWrongDoorOriginalJacket) + || getEvent(kEventMahmudWrongDoorDay)) { + cursor = kCursorNormal; + cursor2 = kCursorTalk; + } + + getObjects()->update(kObjectCompartment4, kEntityMahmud, kObjectLocation1, cursor, cursor2); + params->param5 = 1; + break; + } + + case 6: + case 7: + params->param4 = 1; + break; + + case 8: + case 9: + setCallback(10); + setup_savegame(kSavegameTypeEvent, kEventMahmudWrongDoor); + return; + + case 10: + getAction()->playAnimation((getProgress().jacket == kJacketGreen) ? (isNight() ? kEventMahmudWrongDoor : kEventMahmudWrongDoorDay) : kEventMahmudWrongDoorOriginalJacket); + getSound()->playSound(kEntityPlayer, "LIB015"); + getScenes()->processScene(); + + params->param4 = 1; + break; + + case 11: + getObjects()->update(kObjectCompartment4, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + params->param4 = 0; + params->param5 = 0; + break; + + case 12: + getObjects()->update(kObjectCompartment4, kEntityMahmud, kObjectLocation3, kCursorHandKnock, kCursorHand); + params->param2 = 0; + params->param4 = 0; + params->param5 = 0; + break; + } + break; + + case kAction225563840: + setCallback(12); + setup_function11(); + break; + + case kAction290410610: + params->param3 = (params->param3 < 1) ? 1 : 0; + setCallback(11); + setup_function10((ObjectIndex)savepoint.param.intValue, (bool)params->param3); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Mahmud, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chaptersHandler); + break; + + case kActionDefault: + getSavePoints()->addData(kEntityMahmud, kAction170483072, 0); + + getData()->entityPosition = kPosition_540; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + + getObjects()->update(kObjectCompartment4, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject20, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Mahmud, resetChapter) + if (savepoint.action != kActionDefault) + return; + + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + getObjects()->update(kObjectCompartment4, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getEntities()->clearSequences(kEntityMahmud); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Mahmud, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chaptersHandler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMahmud); + + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Mahmud, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chaptersHandler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMahmud); + + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Mahmud, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chaptersHandler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMahmud); + + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Mahmud, chapter5) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityMahmud); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/mahmud.h b/engines/lastexpress/entities/mahmud.h new file mode 100644 index 0000000000..0b65a8bcff --- /dev/null +++ b/engines/lastexpress/entities/mahmud.h @@ -0,0 +1,153 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_MAHMUD_H +#define LASTEXPRESS_MAHMUD_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Mahmud : public Entity { +public: + Mahmud(LastExpressEngine *engine); + ~Mahmud() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Draws the entity + * + * @param savepoint The savepoint + * - The sequence to draw + */ + DECLARE_FUNCTION_NOSETUP(draw) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + * @param ticks The time ticks + * @param object The object for loading the scene + */ + DECLARE_FUNCTION_4(enterExitCompartment2, const char* sequence, ObjectIndex compartment, uint32 ticks, ObjectIndex object) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSoundMertens, const char* filename) + + /** + * Updates parameter 2 using time value + * + * @param savepoint The savepoint + * - Time to add + */ + DECLARE_FUNCTION_NOSETUP(updateFromTime) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION_2(function10, ObjectIndex, bool) + DECLARE_FUNCTION(function11) + DECLARE_FUNCTION(function12) + DECLARE_FUNCTION(function13) + + /** + * Handle chapters events + */ + DECLARE_FUNCTION(chaptersHandler) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Reset chapter data + */ + DECLARE_FUNCTION(resetChapter) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_MAHMUD_H diff --git a/engines/lastexpress/entities/max.cpp b/engines/lastexpress/entities/max.cpp new file mode 100644 index 0000000000..5bc1c1f357 --- /dev/null +++ b/engines/lastexpress/entities/max.cpp @@ -0,0 +1,628 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/max.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Max::Max(LastExpressEngine *engine) : Entity(engine, kEntityMax) { + ADD_CALLBACK_FUNCTION(Max, reset); + ADD_CALLBACK_FUNCTION(Max, playSound); + ADD_CALLBACK_FUNCTION(Max, draw); + ADD_CALLBACK_FUNCTION(Max, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Max, savegame); + ADD_CALLBACK_FUNCTION(Max, chapter12_handler); + ADD_CALLBACK_FUNCTION(Max, function7); + ADD_CALLBACK_FUNCTION(Max, chapter4Handler); + ADD_CALLBACK_FUNCTION(Max, function9); + ADD_CALLBACK_FUNCTION(Max, chapter1); + ADD_CALLBACK_FUNCTION(Max, chapter2); + ADD_CALLBACK_FUNCTION(Max, chapter3); + ADD_CALLBACK_FUNCTION(Max, chapter3Handler); + ADD_CALLBACK_FUNCTION(Max, freeFromCage); + ADD_CALLBACK_FUNCTION(Max, function15); + ADD_CALLBACK_FUNCTION(Max, chapter4); + ADD_CALLBACK_FUNCTION(Max, function17); + ADD_CALLBACK_FUNCTION(Max, chapter5); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Max, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Max, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(3, Max, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(4, Max, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(5, Max, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6, Max, chapter12_handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param2, getState()->time, params->param1); + + if (!getSound()->isBuffered(kEntityMax)) + getSound()->playSound(kEntityMax, "Max1122"); + + params->param1 = 255 * (4 * rnd(20) + 40); + params->param2 = 0; + break; + + case kActionDefault: + params->param1 = 255 * (4 * rnd(20) + 40); + break; + + case kAction71277948: + setCallback(1); + setup_function7(); + break; + + case kAction158007856: + if (!getSound()->isBuffered(kEntityMax)) { + getSound()->playSound(kEntityMax, "Max1122"); + params->param1 = 255 * (4 * rnd(20) + 40); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Max, function7) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param2, getState()->time, params->param1) + + if (!getSound()->isBuffered(kEntityMax)) + getSound()->playSound(kEntityMax, "Max1122"); + + params->param1 = 255 * (4 * rnd(20) + 40); + params->param2 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update(kObjectCompartmentF, kEntityMax, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject53, kEntityMax, kObjectLocation1, kCursorNormal, kCursorNormal); + + if (getSound()->isBuffered(kEntityMax)) + getSound()->processEntry(kEntityMax); + + setCallback((savepoint.action == kActionKnock) ? 1 : 2); + setup_playSound((savepoint.action == kActionKnock) ? "LIB012" : "LIB013"); + break; + + case kActionDefault: + params->param1 = 255 * (4 * rnd(20) + 40); + + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->update(kObjectCompartmentF, kEntityMax, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityMax, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kActionDrawScene: + if (!getSound()->isBuffered(kEntityMax)) { + if (getEntities()->isPlayerPosition(kCarRedSleeping, 56) || getEntities()->isPlayerPosition(kCarRedSleeping, 78)) + getSound()->playSound(kEntityMax, "Max1120"); + } + break; + + case kActionCallback: + switch (getCallback()) { + case 0: + default: + break; + + case 1: + case 2: + setCallback(3); + setup_playSound("Max1122"); + break; + + case 3: + getObjects()->update(kObjectCompartmentF, kEntityMax, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityMax, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } + break; + + case kAction101687594: + getEntities()->clearSequences(kEntityMax); + + CALLBACK_ACTION(); + break; + + case kAction122358304: + case kActionMaxFreeFromCage: + getSavePoints()->push(kEntityMax, kEntityMax, kActionMaxFreeFromCage); + getObjects()->update(kObjectCompartmentF, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject53, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + + case kAction158007856: + if (!getSound()->isBuffered(kEntityMax)) { + getSound()->playSound(kEntityMax, "Max1122"); + params->param1 = 255 * (4 * rnd(20) + 40); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Max, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param3, getState()->time, params->param2); + + if (!getSound()->isBuffered(kEntityMax)) + getSound()->playSound(kEntityMax, "Max3101"); + + params->param2 = 255 * (4 * rnd(20) + 40); + params->param3 = 0; + break; + + case kActionOpenDoor: + if (params->param1) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventCathMaxLickHand); + break; + } + + if (getSound()->isBuffered(kEntityMax)) + getSound()->processEntry(kEntityMax); + + getAction()->playAnimation(kEventCathMaxLickHand); + getScenes()->processScene(); + + params->param1 = 1; + break; + + case kActionDefault: + params->param2 = 255 * (4 * rnd(20) + 40); + + getObjects()->update(kObjectCageMax, kEntityMax, kObjectLocationNone, kCursorNormal, kCursorHand); + getEntities()->clearSequences(kEntityMax); + + getData()->entityPosition = kPosition_8000; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarBaggage; + + if (!getSound()->isBuffered(kEntityMax)) + getSound()->playSound(kEntityMax, "Max3101"); + break; + + case kActionCallback: + if (getCallback() != 1) + break; + + if (getSound()->isBuffered(kEntityMax)) + getSound()->processEntry(kEntityMax); + + getSound()->playSound(kEntityPlayer, "LIB026"); + getAction()->playAnimation(kEventCathMaxFree); + getScenes()->loadSceneFromPosition(kCarBaggage, 92); + getObjects()->update(kObjectCageMax, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + setup_function9(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Max, function9) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param2 == kTimeInvalid || !getState()->time) + break; + + if (params->param1 >= getState()->time) { + if (!getEntities()->hasValidFrame(kEntityMax) || !params->param2) { + + params->param2 = getState()->time; + if (!params->param2) + goto setup_functions; + } + + if (params->param2 >= getState()->time) + break; + } + + params->param2 = kTimeInvalid; + +setup_functions: + if (getProgress().chapter == kChapter3) + setup_function15(); + + if (getProgress().chapter == kChapter4) + setup_function17(); + break; + + ////////////////////////////////////////////////////////////////////////// + // Draw Max outside of cage + case kActionDefault: + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + + getEntities()->drawSequenceLeft(kEntityMax, "630Af"); + getEntities()->enterCompartment(kEntityMax, kObjectCompartmentF, true); + + params->param1 = getState()->time + 2700; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Max, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter12_handler); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Max, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter12_handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMax); + + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Max, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMax); + + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Max, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param2) { + getData()->entityPosition = getEntityData(kEntityCoudert)->entityPosition; + break; + } + + UPDATE_PARAM(params->param3, getState()->time, params->param1); + + if (!getSound()->isBuffered(kEntityMax)) + getSound()->playSound(kEntityMax, "Max1122"); + + params->param1 = 255 * (4 * rnd(20) + 40); + params->param3 = 0; + break; + + case kActionDefault: + params->param1 = 255 * (4 * rnd(20) + 40); + + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + break; + + case kAction71277948: + setCallback(1); + setup_function7(); + break; + + case kAction122358304: + params->param2 = 1; + break; + + case kActionMaxFreeFromCage: + setup_freeFromCage(); + break; + + case kAction158007856: + if (params->param2) + break; + + if (!getSound()->isBuffered(kEntityMax)) { + getSound()->playSound(kEntityMax, "Max1122"); + params->param1 = 255 * (4 * rnd(20) + 40); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Max, freeFromCage) + switch (savepoint.action) { + default: + break; + + case kActionNone: + break; + + case kActionEndSound: + getSound()->playSound(kEntityMax, "Max1122"); + break; + + ////////////////////////////////////////////////////////////////////////// + // Save game after freeing Max from his cage + case kActionOpenDoor: + if (getEvent(kEventCathMaxCage)) { + if (getEvent(kEventCathMaxFree)) { + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventCathMaxFree); + } + + } else { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventCathMaxCage); + } + break; + + case kActionDefault: + getObjects()->update(kObjectCageMax, kEntityMax, kObjectLocationNone, kCursorNormal, kCursorHand); + + getData()->entityPosition = kPosition_8000; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarBaggage; + + if (!getSound()->isBuffered(kEntityMax)) + getSound()->playSound(kEntityMax, "Max1122"); + break; + + ////////////////////////////////////////////////////////////////////////// + // Play animation for Max in the cage and after opening it + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getSound()->isBuffered(kEntityMax)) + getSound()->removeFromQueue(kEntityMax); + + getAction()->playAnimation(kEventCathMaxCage); + getSound()->setupEntry(SoundManager::kSoundType7, kEntityMax); + getScenes()->processScene(); + break; + + case 2: + if (getSound()->isBuffered(kEntityMax)) + getSound()->processEntry(kEntityMax); + + getSound()->playSound(kEntityPlayer, "LIB026"); + getAction()->playAnimation(kEventCathMaxFree); + getScenes()->loadSceneFromPosition(kCarBaggage, 92); + getObjects()->update(kObjectCageMax, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + setup_function9(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Max, function15) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param2) { + getData()->entityPosition = getEntityData(kEntityCoudert)->entityPosition; + getData()->car = getEntityData(kEntityCoudert)->car; + } + + if (!params->param1) { + UPDATE_PARAM(params->param3, getState()->time, 900); + + getSavePoints()->push(kEntityMax, kEntityCoudert, kAction157026693); + } + break; + + case kActionDefault: + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + + if (!getSound()->isBuffered(kEntityMax)) + getSound()->playSound(kEntityMax, "Max3010"); + + setCallback(1); + setup_enterExitCompartment("630Bf", kObjectCompartment4); + break; + + case kActionCallback: + if (getCallback() == 1) { + getEntities()->drawSequenceLeft(kEntityMax, "630Af"); + getEntities()->enterCompartment(kEntityMax, kObjectCompartmentF, true); + getSavePoints()->push(kEntityMax, kEntityAnna, kAction156622016); + } + break; + + case kAction122358304: + (savepoint.entity2 == kEntityAnna) ? (params->param1 = 1) : (params->param2 = 1); + getEntities()->exitCompartment(kEntityMax, kObjectCompartmentF, true); + getEntities()->drawSequenceLeft(kEntityMax, "BLANK"); + break; + + case kActionMaxFreeFromCage: + getEntities()->exitCompartment(kEntityMax, kObjectCompartmentF, true); + setup_chapter4Handler(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Max, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMax); + + getData()->entityPosition = kPosition_8000; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarBaggage; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Max, function17) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) { + getData()->entityPosition = getEntityData(kEntityCoudert)->entityPosition; + getData()->car = getEntityData(kEntityCoudert)->car; + } + break; + + case kActionDefault: + getData()->entityPosition = kPosition_4070; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + + getEntities()->drawSequenceLeft(kEntityMax, "630Af"); + getSavePoints()->push(kEntityMax, kEntityCoudert, kAction157026693); + break; + + case kAction122358304: + params->param1 = 1; + getEntities()->exitCompartment(kEntityMax, kObjectCompartmentF, true); + getEntities()->drawSequenceLeft(kEntityMax, "BLANK"); + break; + + case kActionMaxFreeFromCage: + getEntities()->exitCompartment(kEntityMax, kObjectCompartmentF, true); + setup_chapter4Handler(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Max, chapter5) + if (savepoint.action == kActionDefault) { + getEntities()->clearSequences(kEntityMax); + + getData()->entityPosition = kPositionNone; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarNone; + + getObjects()->update(kObjectCageMax, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/max.h b/engines/lastexpress/entities/max.h new file mode 100644 index 0000000000..404ee42f5f --- /dev/null +++ b/engines/lastexpress/entities/max.h @@ -0,0 +1,129 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_MAX_H +#define LASTEXPRESS_MAX_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Max : public Entity { +public: + Max(LastExpressEngine *engine); + ~Max() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Draws the entity + * + * @param savepoint The savepoint + * - The sequence to draw + */ + DECLARE_FUNCTION_NOSETUP(draw) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Handle Chapter 1 & 2 events + */ + DECLARE_FUNCTION(chapter12_handler) + + DECLARE_FUNCTION(function7) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function9) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + DECLARE_FUNCTION(freeFromCage) + DECLARE_FUNCTION(function15) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + DECLARE_FUNCTION(function17) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_MAX_H diff --git a/engines/lastexpress/entities/mertens.cpp b/engines/lastexpress/entities/mertens.cpp new file mode 100644 index 0000000000..a59a8eebe8 --- /dev/null +++ b/engines/lastexpress/entities/mertens.cpp @@ -0,0 +1,4113 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/mertens.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/state.h" +#include "lastexpress/game/sound.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +#define SAVEGAME_BLOOD_JACKET() \ + if (getProgress().jacket == kJacketBlood \ + && getEntities()->isDistanceBetweenEntities(kEntityMertens, kEntityPlayer, 1000) \ + && !getEntities()->isInsideCompartments(kEntityPlayer) \ + && !getEntities()->checkFields10(kEntityPlayer)) { \ + setCallback(1); \ + setup_savegame(kSavegameTypeEvent, kEventMertensBloodJacket); \ + } + +Mertens::Mertens(LastExpressEngine *engine) : Entity(engine, kEntityMertens) { + ADD_CALLBACK_FUNCTION(Mertens, reset); + ADD_CALLBACK_FUNCTION(Mertens, bloodJacket); + ADD_CALLBACK_FUNCTION(Mertens, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Mertens, enterExitCompartment2); + ADD_CALLBACK_FUNCTION(Mertens, enterExitCompartment3); + ADD_CALLBACK_FUNCTION(Mertens, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Mertens, playSound); + ADD_CALLBACK_FUNCTION(Mertens, playSound16); + ADD_CALLBACK_FUNCTION(Mertens, savegame); + ADD_CALLBACK_FUNCTION(Mertens, updateEntity); + ADD_CALLBACK_FUNCTION(Mertens, function11); + ADD_CALLBACK_FUNCTION(Mertens, bonsoir); + ADD_CALLBACK_FUNCTION(Mertens, function13); + ADD_CALLBACK_FUNCTION(Mertens, function14); + ADD_CALLBACK_FUNCTION(Mertens, function15); + ADD_CALLBACK_FUNCTION(Mertens, function16); + ADD_CALLBACK_FUNCTION(Mertens, function17); + ADD_CALLBACK_FUNCTION(Mertens, function18); + ADD_CALLBACK_FUNCTION(Mertens, function19); + ADD_CALLBACK_FUNCTION(Mertens, function20); + ADD_CALLBACK_FUNCTION(Mertens, function21); + ADD_CALLBACK_FUNCTION(Mertens, function22); + ADD_CALLBACK_FUNCTION(Mertens, function23); + ADD_CALLBACK_FUNCTION(Mertens, function24); + ADD_CALLBACK_FUNCTION(Mertens, function25); + ADD_CALLBACK_FUNCTION(Mertens, function26); + ADD_CALLBACK_FUNCTION(Mertens, tylerCompartment); + ADD_CALLBACK_FUNCTION(Mertens, function28); + ADD_CALLBACK_FUNCTION(Mertens, function29); + ADD_CALLBACK_FUNCTION(Mertens, function30); + ADD_CALLBACK_FUNCTION(Mertens, function31); + ADD_CALLBACK_FUNCTION(Mertens, function32); + ADD_CALLBACK_FUNCTION(Mertens, function33); + ADD_CALLBACK_FUNCTION(Mertens, chapter1); + ADD_CALLBACK_FUNCTION(Mertens, function35); + ADD_CALLBACK_FUNCTION(Mertens, function36); + ADD_CALLBACK_FUNCTION(Mertens, function37); + ADD_CALLBACK_FUNCTION(Mertens, function38); + ADD_CALLBACK_FUNCTION(Mertens, function39); + ADD_CALLBACK_FUNCTION(Mertens, function40); + ADD_CALLBACK_FUNCTION(Mertens, chapter1Handler); + ADD_CALLBACK_FUNCTION(Mertens, function42); + ADD_CALLBACK_FUNCTION(Mertens, chapter2); + ADD_CALLBACK_FUNCTION(Mertens, function44); + ADD_CALLBACK_FUNCTION(Mertens, chapter3); + ADD_CALLBACK_FUNCTION(Mertens, function46); + ADD_CALLBACK_FUNCTION(Mertens, chapter4); + ADD_CALLBACK_FUNCTION(Mertens, function48); + ADD_CALLBACK_FUNCTION(Mertens, function49); + ADD_CALLBACK_FUNCTION(Mertens, chapter5); + ADD_CALLBACK_FUNCTION(Mertens, chapter5Handler); + ADD_CALLBACK_FUNCTION(Mertens, function52); + ADD_CALLBACK_FUNCTION(Mertens, function53); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Mertens, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Mertens, bloodJacket) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + break; + + case kActionExitCompartment: + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityMertens, (char *)¶ms->seq1); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(3, Mertens, enterExitCompartment, ObjectIndex) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + return; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + return; + } + + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(4, Mertens, enterExitCompartment2, ObjectIndex) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + return; + + case kAction4: + getEntities()->exitCompartment(kEntityMertens, (ObjectIndex)params->param4); + CALLBACK_ACTION(); + return; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + return; + } + + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SIII(5, Mertens, enterExitCompartment3, ObjectIndex, EntityPosition, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + break; + + case kActionExitCompartment: + getEntities()->exitCompartment(_entityIndex, (ObjectIndex)params->param4); + getData()->entityPosition = (EntityPosition)params->param5; + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(_entityIndex, (char *)¶ms->seq); + getEntities()->enterCompartment(_entityIndex, (ObjectIndex)params->param4); + getData()->entityPosition = (EntityPosition)params->param5; + + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, (EntityPosition)params->param5) || getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, (EntityPosition)params->param6)) { + getAction()->playAnimation(isNight() ? kEventCathTurningNight : kEventCathTurningDay); + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromObject((ObjectIndex)params->param4); + } + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6, Mertens, callbackActionOnDirection) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getData()->direction != kDirectionRight) { + CALLBACK_ACTION(); + break; + } + + SAVEGAME_BLOOD_JACKET(); + break; + + case kActionExitCompartment: + CALLBACK_ACTION(); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(7, Mertens, playSound) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + break; + + case kActionEndSound: + CALLBACK_ACTION(); + break; + + case kActionDefault: + getSound()->playSound(kEntityMertens, (char *)¶ms->seq1); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(8, Mertens, playSound16) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + break; + + case kActionEndSound: + CALLBACK_ACTION(); + break; + + case kActionDefault: + getSound()->playSound(kEntityMertens, (char *)¶ms->seq1, SoundManager::kFlagDefault); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(9, Mertens, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(10, Mertens, updateEntity, CarIndex, EntityPosition) + +#define LOADSCENE_FROM_POSITION() \ + if (getData()->direction != kDirectionUp) { \ + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + 750)); \ + } else { \ + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition - 750), true); \ + } + + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param3 && getEntities()->isDistanceBetweenEntities(kEntityMertens, kEntityPlayer, 2000)) + getData()->inventoryItem = (InventoryItem)(getData()->inventoryItem | kItemInvalid); + else + getData()->inventoryItem = (InventoryItem)(getData()->inventoryItem & kItemToggleHigh); + + if (!getEntities()->isDistanceBetweenEntities(kEntityMertens, kEntityPlayer, 1000) + || getEntities()->isInsideCompartments(kEntityPlayer) + || getEntities()->checkFields10(kEntityPlayer)) { + if (getEntities()->updateEntity(kEntityMertens, (CarIndex)params->param1, (EntityPosition)params->param2)) { + getData()->inventoryItem = kItemNone; + CALLBACK_ACTION(); + } + break; + } + + if (getProgress().jacket == kJacketBlood) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventMertensBloodJacket); + break; + } + + if ((ENTITY_PARAM(0, 6) || ENTITY_PARAM(0, 7)) && (!getEvent(kEventKronosConversation) && getProgress().jacket == kJacketGreen)) { + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventMertensKronosInvitation); + break; + } + + if (ENTITY_PARAM(1, 2) && getProgress().jacket == kJacketGreen && !getProgress().eventMetAugust) { + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventMertensAugustWaiting); + break; + } + + if (ENTITY_PARAM(2, 4) && getState()->time < kTime2133000) { + setCallback(4); + setup_savegame(kSavegameTypeEvent, kEventMertensKronosConcertInvitation); + break; + } + + if (getEntities()->updateEntity(kEntityMertens, (CarIndex)params->param1, (EntityPosition)params->param2)) { + getData()->inventoryItem = kItemNone; + CALLBACK_ACTION(); + } + break; + + case kAction1: + params->param3 = 0; + if (getProgress().eventCorpseFound || getEvent(kEventMertensAskTylerCompartment) || getEvent(kEventMertensAskTylerCompartmentD)) { + if (ENTITY_PARAM(0, 4) && getProgress().jacket == kJacketGreen && !getEvent(kEventMertensDontMakeBed) && !getProgress().eventCorpseThrown) { + setCallback(6); + setup_savegame(kSavegameTypeEvent, kEventMertensDontMakeBed); + } + } else { + setCallback(5); + setup_savegame(kSavegameTypeEvent, kEventMertensAskTylerCompartment); + } + break; + + case kActionExcuseMeCath: + getSound()->playSound(kEntityMertens, "CON1110B"); + break; + + case kActionExcuseMe: + getSound()->excuseMe(kEntityMertens); + break; + + case kActionDefault: + if ((!getProgress().eventCorpseFound && !getEvent(kEventMertensAskTylerCompartment) && !getEvent(kEventMertensAskTylerCompartment)) + || (ENTITY_PARAM(0, 4) && getProgress().jacket == kJacketGreen && !getEvent(kEventMertensDontMakeBed) && !getProgress().eventCorpseThrown)) + params->param3 = 1; + + if (getEntities()->updateEntity(kEntityMertens, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + break; + + case 2: + getAction()->playAnimation(getData()->entityPosition < getEntityData(kEntityPlayer)->entityPosition ? kEventMertensKronosInvitation : kEventMertensKronosInvitationClosedWindows); + getProgress().eventMertensKronosInvitation = true; + + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 7) = 0; + + if (params->param1 != 3 || (params->param2 != kPosition_8200 && params->param2 != kPosition_9510)) { + LOADSCENE_FROM_POSITION(); + break; + } + + getData()->inventoryItem = kItemNone; + + if (getData()->car == kCarGreenSleeping && getEntities()->checkDistanceFromPosition(kEntityMertens, kPosition_2000, 500)) + getData()->entityPosition = kPosition_2500; + + getEntities()->updateEntity(kEntityMertens, kCarGreenSleeping, kPosition_2000); + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + 750)); + + CALLBACK_ACTION(); + break; + + case 3: + getAction()->playAnimation(kEventMertensAugustWaiting); + getProgress().eventMertensAugustWaiting = true; + + ENTITY_PARAM(1, 2) = 0; + + if (params->param1 == 3 && params->param2 == kPosition_8200) { + if (getData()->car == kCarGreenSleeping && getEntities()->checkDistanceFromPosition(kEntityMertens, kPosition_2000, 500)) + getData()->entityPosition = kPosition_2500; + + getEntities()->updateEntity(kEntityMertens, kCarGreenSleeping, kPosition_2000); + getEntities()->loadSceneFromEntityPosition(getData()->car, (EntityPosition)(getData()->entityPosition + 750)); + + CALLBACK_ACTION(); + break; + } + + LOADSCENE_FROM_POSITION(); + break; + + case 4: + getAction()->playAnimation(kEventMertensKronosConcertInvitation); + ENTITY_PARAM(2, 4) = 0; + + LOADSCENE_FROM_POSITION(); + break; + + case 5: + getAction()->playAnimation(getData()->entityPosition < getEntityData(kEntityPlayer)->entityPosition ? kEventMertensAskTylerCompartmentD : kEventMertensAskTylerCompartment); + LOADSCENE_FROM_POSITION(); + break; + + case 6: + getAction()->playAnimation(kEventMertensDontMakeBed); + LOADSCENE_FROM_POSITION(); + ENTITY_PARAM(0, 4) = 0; + break; + } + break; + } + +#undef LOADSCENE_FROM_POSITION +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(11, Mertens, function11, uint32) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + + UPDATE_PARAM(params->param2, getState()->time, params->param1) + + CALLBACK_ACTION(); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(12, Mertens, bonsoir, EntityIndex) + EntityIndex entity = (EntityIndex)params->param1; + + if (savepoint.action == kActionDefault) + return; + + if (getSound()->isBuffered(kEntityMertens)) { + CALLBACK_ACTION(); + return; + } + + if (isNight()) { + if (Entities::isFemale(entity)) { + getSound()->playSound(kEntityMertens, rnd(2) ? "CON1112" : "CON1112A"); + } else { + if (entity || getProgress().field_18 != 2) { + getSound()->playSound(kEntityMertens, "CON1112F"); + } else { + switch (rnd(3)) { + default: + case 0: + getSound()->playSound(kEntityMertens, "CON1061"); + break; + + case 1: + getSound()->playSound(kEntityMertens, "CON1110G"); + break; + + case 2: + getSound()->playSound(kEntityMertens, "CON1110H"); + break; + } + } + } + } else { + if (Entities::isFemale(entity)) + getSound()->playSound(kEntityMertens, rnd(2) ? "CON1112B" : "CON1112C"); + else + getSound()->playSound(kEntityMertens, "CON1112G"); + } + + CALLBACK_ACTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(13, Mertens, function13, bool, bool) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + + if (!params->param2 && !params->param3) { + UPDATE_PARAM_PROC(params->param4, getState()->timeTicks, 75) + getData()->inventoryItem = kItemNone; + setCallback(5); + setup_function18(); + break; + UPDATE_PARAM_PROC_END + } + + UPDATE_PARAM_PROC(params->param5, getState()->timeTicks, 225) + getData()->inventoryItem = kItemNone; + setCallback(6); + setup_function18(); + break; + UPDATE_PARAM_PROC_END + + getData()->inventoryItem = (getProgress().chapter == kChapter1 + && !ENTITY_PARAM(2, 1) + && !getProgress().eventCorpseFound + && !getEvent(kEventMertensAskTylerCompartment) + && !getEvent(kEventMertensAskTylerCompartmentD)) ? kItemMatchBox : kItemNone; + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + setCallback(7); + setup_savegame(kSavegameTypeEvent, kEventMertensAskTylerCompartmentD); + break; + + case kAction11: + params->param3++; + setCallback(11); + setup_bonsoir(savepoint.entity2); + break; + + case kActionDefault: + if (params->param2) + params->param3 = 1; + + if (!getSound()->isBuffered(kEntityMertens)) { + + } + + setCallback(3); + setup_function20(); + break; + + case kAction16: + params->param3--; + + if (params->param2 && !params->param3) { + getData()->inventoryItem = kItemNone; + setCallback(10); + setup_function18(); + } + break; + + case kActionDrawScene: + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 23) && ENTITY_PARAM(0, 7) && !getEvent(kEventKronosConversation)) { + setCallback(8); + setup_savegame(kSavegameTypeEvent, kEventMertensKronosInvitation); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + setCallback(3); + setup_function20(); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntityMertens, params->param1 ? "601I" : "601H"); + break; + + case 4: + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + break; + + case 5: + case 6: + case 9: + case 10: + CALLBACK_ACTION(); + break; + + case 7: + getAction()->playAnimation(kEventMertensAskTylerCompartmentD); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 25); + break; + + case 8: + getAction()->playAnimation(kEventMertensKronosInvitation); + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 7) = 0; + getScenes()->processScene(); + + if (!params->param3) { + getData()->inventoryItem = kItemNone; + setCallback(10); + setup_function18(); + } + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(14, Mertens, function14, EntityIndex) + switch (savepoint.action) { + default: + break; + + case kActionNone: + SAVEGAME_BLOOD_JACKET(); + break; + + case kActionDefault: + getData()->inventoryItem = kItemNone; + + if (ENTITY_PARAM(2, 1)) { + ENTITY_PARAM(2, 1) = 0; + + setCallback(3); + setup_updateEntity(kCarGreenSleeping, kPosition_1500); + } else { + setCallback(1); + setup_function11(15); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityMertens, (EntityIndex)params->param1, kAction202558662); + + setCallback(2); + setup_function20(); + break; + + case 2: + getSavePoints()->push(kEntityMertens, (EntityIndex)params->param1, kAction155853632); + getEntities()->drawSequenceLeft(kEntityMertens, "601K"); + break; + + case 3: + getSavePoints()->push(kEntityMertens, (EntityIndex)params->param1, kAction202558662); + getSavePoints()->push(kEntityMertens, (EntityIndex)params->param1, kAction155853632); + getEntities()->drawSequenceLeft(kEntityMertens, "601K"); + getScenes()->loadSceneFromItemPosition(kItem7); + break; + + case 4: + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + break; + + case 5: + CALLBACK_ACTION(); + break; + } + break; + + case kAction125499160: + if (params->param1 == kEntityVerges) + ENTITY_PARAM(0, 8) = 0; + + setCallback(5); + setup_function18(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(15, Mertens, function15, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + ENTITY_PARAM(1, 4) = 0; + ENTITY_PARAM(1, 5) = 0; + + setCallback(1); + setup_function19(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_4070); + break; + + case 2: + getSound()->playSound(kEntityMertens, params->param1 ? "CON1059A" : "CON1059"); + + setCallback(3); + setup_updateEntity(kCarGreenSleeping, kPosition_7500); + break; + + case 3: + setCallback(4); + setup_enterExitCompartment("601Xb", kObjectCompartment2); + break; + + case 4: + getSavePoints()->push(kEntityMertens, kEntityAlexei, kAction135664192); + + setCallback(5); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 5: + setCallback(6); + setup_function17(); + break; + + case 6: + CALLBACK_ACTION();; + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(16, Mertens, function16, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + ENTITY_PARAM(1, 6) = 0; + ENTITY_PARAM(1, 7) = 0; + + setCallback(1); + setup_function19(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_4070); + break; + + case 2: + switch (rnd(4)) { + default: + break; + + case 0: + getSound()->playSound(kEntityMertens, "AUG2095A"); + break; + + case 1: + getSound()->playSound(kEntityMertens, "AUG2096A"); + break; + + case 2: + getSound()->playSound(kEntityMertens, "AUG2094B"); + break; + + case 3: + getSound()->playSound(kEntityMertens, "AUG2094C"); + break; + } + + setCallback(3); + setup_updateEntity(kCarGreenSleeping, kPosition_6470); + break; + + case 3: + getSound()->playSound(kEntityMertens, params->param1 ? "AUG2097" : "AUG2098"); + + setCallback(4); + setup_enterExitCompartment("601Xc", kObjectCompartment3); + break; + + case 4: + getSavePoints()->push(kEntityMertens, kEntityAugust, kAction69239528); + + setCallback(5); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 5: + setCallback(6); + setup_function17(); + break; + + case 6: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Mertens, function17) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + // FIXME: Check that we are using the correct parameter struct + if (ENTITY_PARAM(0, 6) || ((EntityData::EntityParametersIIII*)_data->getParameters(8, 1))->hasNonNullParameter()) { + getInventory()->setLocationAndProcess(kItem7, kObjectLocation1); + + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_540); + break; + } + + if (ENTITY_PARAM(0, 8)) { + getEntities()->drawSequenceLeft(kEntityMertens, "601K"); + getScenes()->loadSceneFromItemPosition(kItem7); + ENTITY_PARAM(2, 1) = 1; + + CALLBACK_ACTION(); + break; + } + + // Mertens sits on his chair at the back of the train + if (!getInventory()->hasItem(kItemPassengerList) || ENTITY_PARAM(0, 2)) { + getEntities()->drawSequenceRight(kEntityMertens, "601A"); + } else { + // Got the passenger list, Mertens is looking for it before sitting + ENTITY_PARAM(0, 2) = 1; + getSound()->playSound(kEntityMertens, "CON1058", SoundManager::kFlagInvalid, 75); + getEntities()->drawSequenceRight(kEntityMertens, "601D"); + } + + getScenes()->loadSceneFromItemPosition(kItem7); + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 68)) { + getSound()->playSound(kEntityPlayer, "CON1110"); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 25); + } + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->clearSequences(kEntityMertens); + ENTITY_PARAM(2, 1) = 1; + setCallback(2); + setup_function11(75); + break; + + case 2: + CALLBACK_ACTION(); + break; + + case 3: + if (!ENTITY_PARAM(0, 3) + && !getInventory()->hasItem(kItemPassengerList) + && ENTITY_PARAM(0, 2)) { + getSavePoints()->push(kEntityMertens, kEntityVerges, kAction158617345); + ENTITY_PARAM(0, 3) = 1; + } + + getEntities()->drawSequenceLeft(kEntityMertens, "601B"); + + ENTITY_PARAM(0, 1) = 0; + getData()->inventoryItem = kItemNone; + + getSavePoints()->push(kEntityMertens, kEntityMertens, kActionDrawScene); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Mertens, function18) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (ENTITY_PARAM(0, 6) + || ENTITY_PARAM(1, 1) + || ENTITY_PARAM(1, 2) + || ENTITY_PARAM(1, 3) + || ENTITY_PARAM(1, 4) + || ENTITY_PARAM(1, 5) + || ENTITY_PARAM(1, 6) + || ENTITY_PARAM(1, 7) + || ENTITY_PARAM(1, 8)) { + getInventory()->setLocationAndProcess(kItem7, kObjectLocation1); + ENTITY_PARAM(2, 1) = 1; + + CALLBACK_ACTION(); + break; + } + + if (ENTITY_PARAM(0, 8)) { + getScenes()->loadSceneFromItemPosition(kItem7); + ENTITY_PARAM(2, 1) = 1; + + CALLBACK_ACTION(); + break; + } + + if (!getInventory()->hasItem(kItemPassengerList) || ENTITY_PARAM(0, 2)) { + getEntities()->drawSequenceRight(kEntityMertens, "601A"); + } else { + ENTITY_PARAM(0, 2) = 1; + getSound()->playSound(kEntityMertens, "CON1058", SoundManager::kFlagInvalid, 75); + getEntities()->drawSequenceRight(kEntityMertens, "601D"); + } + + getScenes()->loadSceneFromItemPosition(kItem7); + + setCallback(1); + setup_callbackActionOnDirection(); + break; + + case kActionCallback: + if (getCallback() == 1) { + if (!ENTITY_PARAM(0, 3) + && !getInventory()->hasItem(kItemPassengerList) + && ENTITY_PARAM(0, 2)) { + getSavePoints()->push(kEntityMertens, kEntityVerges, kAction158617345); + ENTITY_PARAM(0, 3) = 1; + } + + getEntities()->drawSequenceLeft(kEntityMertens, "601B"); + ENTITY_PARAM(0, 1) = 0; + getData()->inventoryItem = kItemNone; + + CALLBACK_ACTION(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Mertens, function19) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (ENTITY_PARAM(2, 1)) { + getInventory()->setLocationAndProcess(kItem7, kObjectLocation1); + ENTITY_PARAM(2, 1) = 0; + CALLBACK_ACTION(); + } else { + setCallback(1); + setup_bloodJacket("601C"); + } + break; + + case kActionCallback: + if (getCallback() == 1) { + getInventory()->setLocationAndProcess(kItem7, kObjectLocation1); + + if (!getEntities()->isPlayerPosition(kCarGreenSleeping, 2)) + getData()->entityPosition = kPosition_2088; + + CALLBACK_ACTION(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Mertens, function20) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getScenes()->loadSceneFromItemPosition(kItem7); + + if (ENTITY_PARAM(2, 1)) { + ENTITY_PARAM(2, 1) = 0; + + CALLBACK_ACTION(); + } else { + setCallback(1); + setup_bloodJacket("601C"); + } + break; + + case kActionCallback: + if (getCallback() == 1) + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(21, Mertens, function21, ObjectIndex, ObjectIndex) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM_PROC(CURRENT_PARAMS(1, 4), getState()->time, 300) + getSound()->playSound(kEntityPlayer, "ZFX1004", getSound()->getSoundFlag(kEntityMertens)); + UPDATE_PARAM_PROC_END + + UPDATE_PARAM(CURRENT_PARAMS(1, 5), getState()->time, 900); + + // Update objects + getObjects()->updateLocation2((ObjectIndex)params->param1, kObjectLocation1); + if (params->param5 != kObjectLocation2) + getObjects()->update((ObjectIndex)params->param1, (EntityIndex)params->param4, (ObjectLocation)params->param5, (CursorStyle)params->param6, (CursorStyle)params->param7); + + if (params->param2) + getObjects()->update((ObjectIndex)params->param2, (EntityIndex)params->param8, (ObjectLocation)CURRENT_PARAMS(1, 1), (CursorStyle)CURRENT_PARAMS(1, 2), (CursorStyle)CURRENT_PARAMS(1, 3)); + + CALLBACK_ACTION(); + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update((ObjectIndex)params->param1, kEntityMertens, kObjectLocation1, kCursorNormal, kCursorNormal); + if (params->param2) + getObjects()->update((ObjectIndex)params->param2, kEntityMertens, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(savepoint.action == kActionKnock ? 1 : 2); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + break; + + case kActionDefault: + params->param3 = 1; + params->param4 = getObjects()->get((ObjectIndex)params->param1).entity; + params->param5 = getObjects()->get((ObjectIndex)params->param1).location; + params->param6 = getObjects()->get((ObjectIndex)params->param1).cursor; + params->param7 = getObjects()->get((ObjectIndex)params->param1).cursor2; + + if (params->param2) { + params->param8 = getObjects()->get((ObjectIndex)params->param2).entity; + CURRENT_PARAMS(1, 1) = getObjects()->get((ObjectIndex)params->param2).location; + CURRENT_PARAMS(1, 2) = getObjects()->get((ObjectIndex)params->param2).cursor; + CURRENT_PARAMS(1, 3) = getObjects()->get((ObjectIndex)params->param2).cursor2; + + getObjects()->update((ObjectIndex)params->param2, kEntityMertens, kObjectLocation1, kCursorHandKnock, kCursorHand); + } + + if (params->param5 != kObjectLocation2) + getObjects()->update((ObjectIndex)params->param1, kEntityMertens, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + setCallback(params->param3 ? 3 : 4); + setup_playSound(params->param3 ? "Con1017" : "Con1017A"); + break; + + case 3: + case 4: + params->param3 = 0; + getObjects()->update((ObjectIndex)params->param1, kEntityMertens, kObjectLocation1, kCursorHandKnock, kCursorHand); + + if (params->param2) + getObjects()->update((ObjectIndex)params->param2, kEntityMertens, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Mertens, function22) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_2740); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("601Mh", kObjectCompartment8); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityMertens, "601Nh"); + getEntities()->enterCompartment(kEntityMertens, kObjectCompartment8, true); + + setCallback(3); + setup_function11(150); + break; + + case 3: + setCallback(4); + setup_enterExitCompartment("601Mh", kObjectCompartment8); + break; + + case 4: + getEntities()->drawSequenceLeft(kEntityMertens, "601Nh"); + getEntities()->enterCompartment(kEntityMertens, kObjectCompartment8); + getSavePoints()->push(kEntityMertens, kEntityMahmud, kAction225563840); + break; + + case 5: + if (!getSound()->isBuffered(kEntityMertens)) + getSound()->playSound(kEntityMertens, "MAH1170I"); + + setCallback(6); + setup_enterExitCompartment("601Zd", kObjectCompartment4); + break; + + case 6: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMertens); + if (!getSound()->isBuffered(kEntityMertens)) + getSound()->playSound(kEntityMertens, "MAH1172", SoundManager::kFlagInvalid, 225); + + setCallback(7); + setup_function21(kObjectCompartment4, kObject20); + break; + + case 7: + setCallback(8); + setup_enterExitCompartment("671Ad", kObjectCompartment4); + break; + + case 8: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityMertens, kEntityMahmud, kAction123852928); + + setCallback(9); + setup_updateEntity(kCarGreenSleeping, kPosition_540); + + break; + + case 9: + CALLBACK_ACTION(); + break; + } + break; + + case kAction102227384: + getEntities()->drawSequenceLeft(kEntityMertens, "671Dh"); + break; + + case kAction156567128: + getEntities()->exitCompartment(kEntityMertens, kObjectCompartment8, true); + + setCallback(5); + setup_updateEntity(kCarGreenSleeping, kPosition_5790); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Mertens, function23) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_5790); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("601Vd", kObjectCompartment4); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityMertens, "601Wd"); + getEntities()->enterCompartment(kEntityMertens, kObjectCompartment4, true); + + setCallback(3); + setup_function11(150); + break; + + case 3: + setCallback(4); + setup_enterExitCompartment("601Zd", kObjectCompartment4); + break; + + case 4: + getEntities()->exitCompartment(kEntityMertens, kObjectCompartment4); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMertens); + + setCallback(5); + setup_function21(kObjectCompartment4, kObject20); + break; + + case 5: + setCallback(6); + setup_enterExitCompartment("671Ad", kObjectCompartment4); + break; + + case 6: + getData()->location = kLocationOutsideCompartment; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Mertens, function24) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) { + UPDATE_PARAM(params->param2, getState()->timeTicks, 75); + + setCallback(3); + setup_enterExitCompartment3("601Rc", kObjectCompartment3, kPosition_6470, kPosition_6130); + } + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_6470); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("601Mc", kObjectCompartment3); + break; + + case 2: + getSavePoints()->push(kEntityMertens, kEntityAugust, kAction221617184); + getEntities()->drawSequenceLeft(kEntityMertens, "601Nc"); + getEntities()->enterCompartment(kEntityMertens, kObjectCompartment3, true); + break; + + case 3: + getEntities()->exitCompartment(kEntityMertens, kObjectCompartment3, true); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMertens); + + setCallback(4); + setup_function21(kObjectCompartment3, kObjectKitchen); + break; + + case 4: + setCallback(5); + setup_enterExitCompartment("601Sc", kObjectCompartment3); + break; + + case 5: + getData()->location = kLocationOutsideCompartment; + + CALLBACK_ACTION(); + break; + + case 6: + getEntities()->exitCompartment(kEntityMertens, kObjectCompartment3, true); + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMertens); + + setCallback(7); + setup_function21(kObjectCompartment3, kObjectKitchen); + break; + + case 7: + getObjects()->update(kObjectCompartment3, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(8); + setup_enterExitCompartment("601Uc", kObjectCompartment3); + break; + + case 8: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityMertens, kEntityAugust, kAction124697504); + + setCallback(9); + setup_updateEntity(kCarGreenSleeping, kPosition_540); + break; + + case 9: + CALLBACK_ACTION(); + break; + } + break; + + case kAction100906246: + getSavePoints()->push(kEntityMertens, kEntityAugust, kAction192849856); + getEntities()->drawSequenceLeft(kEntityMertens, "601Qc"); + break; + + case kAction102675536: + params->param1 = 1; + break; + + case kAction156567128: + setCallback(6); + setup_enterExitCompartment("601Tc", kObjectCompartment3); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Mertens, function25) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) { + UPDATE_PARAM(params->param2, getState()->timeTicks, 75); + + setCallback(3); + setup_enterExitCompartment3("601Zb", kObjectCompartment2, kPosition_7500, kPositionNone); + } + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_7500); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("601Vb", kObjectCompartment2); + break; + + case 2: + getSavePoints()->push(kEntityMertens, kEntityAlexei, kAction221617184); + getEntities()->drawSequenceLeft(kEntityMertens, "601Wb"); + getEntities()->enterCompartment(kEntityMertens, kObjectCompartment2, true); + break; + + case 3: + getEntities()->exitCompartment(kEntityMertens, kObjectCompartment2, true); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMertens); + + if (getProgress().chapter == kChapter1 && ENTITY_PARAM(0, 4)) + if (getProgress().field_14 != 29) + getProgress().field_14 = 3; + + setCallback(4); + setup_function21(kObjectCompartment2, kObjectHandleInsideBathroom); + break; + + case 4: + setCallback(5); + setup_enterExitCompartment("671Ab", kObjectCompartment2); + break; + + case 5: + getData()->location = kLocationOutsideCompartment; + + CALLBACK_ACTION(); + break; + + case 6: + getEntities()->exitCompartment(kEntityMertens, kObjectCompartment2, true); + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMertens); + + if (getProgress().chapter == kChapter1 && ENTITY_PARAM(0, 4)) + if (getProgress().field_14 != 29) + getProgress().field_14 = 3; + + setCallback(7); + setup_function21(kObjectCompartment2, kObjectHandleInsideBathroom); + break; + + case 7: + getSound()->playSound(kEntityMertens, "CON1024A"); + getObjects()->update(kObjectCompartment2, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(8); + setup_enterExitCompartment("641Ub", kObjectCompartment2); + break; + + case 8: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityMertens, kEntityAlexei, kAction124697504); + + setCallback(9); + setup_updateEntity(kCarGreenSleeping, kPosition_9460); + break; + + case 9: + CALLBACK_ACTION(); + break; + } + break; + + case kAction100906246: + params->param1 = 1; + break; + + case kAction156567128: + setCallback(6); + setup_enterExitCompartment("641Tb", kObjectCompartment2); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(26, Mertens, function26, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (getProgress().eventCorpseThrown + || !params->param1 + || getProgress().chapter != kChapter1 + || getProgress().jacket != kJacketGreen) { + + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMertens); + getObjects()->update(kObjectCompartment1, kEntityPlayer, getObjects()->get(kObjectCompartment1).location, kCursorNormal, kCursorNormal); + + setCallback(3); + setup_playSound16("ZNU1001"); + } else { + setCallback(1); + setup_savegame(kSavegameTypeTime, kTimeNone); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartment1, kEntityPlayer, getObjects()->get(kObjectCompartment1).location, kCursorNormal, kCursorNormal); + + setCallback(2); + setup_playSound16("CON1062"); + break; + + case 2: + getObjects()->update(kObjectCompartment1, kEntityPlayer, getObjects()->get(kObjectCompartment1).location, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + + case 3: + if (getProgress().jacket == kJacketBlood) { + setCallback(4); + setup_savegame(kSavegameTypeEvent, kEventMertensBloodJacket); + } else if (getProgress().eventCorpseMovedFromFloor) { + getEntities()->enterCompartment(kEntityMertens, kObjectCompartment1); + getEntities()->drawSequenceRight(kEntityMertens, "601Ra"); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 16); + + setCallback(6); + setup_callbackActionOnDirection(); + } else { + setCallback(5); + setup_savegame(kSavegameTypeEvent, kEventMertensCorpseFloor); + } + break; + + case 4: + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + break; + + case 5: + getAction()->playAnimation(kEventMertensCorpseFloor); + getLogic()->gameOver(kSavegameTypeIndex, 1, getProgress().eventCorpseFound ? kSceneGameOverStopPolice : kSceneGameOverPolice, true); + break; + + case 6: + getEntities()->exitCompartment(kEntityMertens, kObjectCompartment1); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMertens); + + setCallback(7); + setup_function21(kObjectCompartment1, kObjectHandleBathroom); + break; + + case 7: + if (getProgress().eventCorpseThrown || getProgress().chapter != kChapter1) { + if (getEntities()->isDistanceBetweenEntities(kEntityMertens, kEntityPlayer, 1000)) { + if (!getEntities()->checkFields10(kEntityPlayer)) + getSound()->playSound(kEntityMertens, "CON1061"); + } + + setCallback(9); + setup_enterExitCompartment("601Sa", kObjectCompartment1); + } else { + if (!getEntities()->isInsideTrainCar(kEntityPlayer, kCarGreenSleeping)) + getScenes()->loadSceneFromPosition(kCarNone, 1); + + setCallback(8); + setup_savegame(kSavegameTypeEvent, kEventMertensCorpseBed); + } + break; + + case 8: + getAction()->playAnimation(kEventMertensCorpseBed); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverPolice1, true); + break; + + case 9: + getData()->location = kLocationOutsideCompartment; + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(27, Mertens, tylerCompartment, MertensActionType) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getProgress().field_14 == 29) { + CALLBACK_ACTION(); + break; + } + + UPDATE_PARAM_PROC(params->param2, getState()->timeTicks, 150) + getObjects()->update(kObjectCompartment1, kEntityPlayer, getObjects()->get(kObjectCompartment1).location, kCursorNormal, kCursorNormal); + + setCallback(10); + setup_playSound16("CON1018A"); + break; + UPDATE_PARAM_PROC_END + +label_callback10: + if (!params->param3) + params->param3 = getState()->timeTicks + 300; + + if (params->param3 >= getState()->timeTicks) { +label_callback11: + UPDATE_PARAM(params->param4, getState()->timeTicks, 375); + + getSound()->playSound(kEntityPlayer, "LIB033"); + + if (getProgress().eventCorpseMovedFromFloor) { + + if (getProgress().jacket == kJacketBlood) { + setCallback(18); + setup_savegame(kSavegameTypeEvent, kEventMertensBloodJacket); + break; + } + + if (params->param1) { + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + switch (params->param1) { + case 1: + setCallback(20); + setup_savegame(kSavegameTypeEvent, kEventMertensAugustWaitingCompartment); + break; + + case 2: + setCallback(21); + setup_savegame(kSavegameTypeEvent, kEventMertensKronosInvitationCompartment); + break; + + case 3: + getAction()->playAnimation(isNight() ? kEventMertensPushCallNight : kEventMertensPushCall); + // fallback to default case + + default: + getSound()->playSound(kEntityPlayer, "LIB015"); + getScenes()->loadScene(kScene41); + + CALLBACK_ACTION(); + break; + } + } else { + setCallback(26); + setup_function26(false); + } + + } else { + if (!getEntities()->isInsideTrainCar(kEntityPlayer, kCarGreenSleeping)) + getScenes()->loadSceneFromPosition(kCarNone, 1); + + setCallback(17); + setup_savegame(kSavegameTypeEvent, kEventMertensCorpseFloor); + } + } else { + params->param3 = kTimeInvalid; + + if (getObjects()->get(kObjectCompartment1).location == kObjectLocation1) { + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(11); + setup_playSound16("CON1018B"); + break; + } + + getSound()->playSound(kEntityPlayer, "LIB014"); + + if (getProgress().eventCorpseMovedFromFloor) { + + if (getProgress().jacket == kJacketBlood) { + setCallback(13); + setup_savegame(kSavegameTypeEvent, kEventMertensBloodJacket); + break; + } + + if (params->param1) { + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + switch (params->param1) { + case 1: + setCallback(15); + setup_savegame(kSavegameTypeEvent, kEventMertensAugustWaitingCompartment); + break; + + case 2: + setCallback(16); + setup_savegame(kSavegameTypeEvent, kEventMertensKronosInvitationCompartment); + break; + + case 3: + getAction()->playAnimation(isNight() ? kEventMertensPushCallNight : kEventMertensPushCall); + // fallback to default case + + default: + getSound()->playSound(kEntityPlayer, "LIB015"); + getScenes()->loadScene(kScene41); + + CALLBACK_ACTION(); + break; + } + } else { + setCallback(14); + setup_function26(false); + } + } else { + if (!getEntities()->isInsideTrainCar(kEntityPlayer, kCarGreenSleeping)) + getScenes()->loadSceneFromPosition(kCarNone, 1); + + setCallback(12); + setup_savegame(kSavegameTypeEvent, kEventMertensCorpseFloor); + } + } + break; + + case kActionKnock: + if (params->param1) { + getObjects()->update(kObjectCompartment1, kEntityMertens, getObjects()->get(kObjectCompartment1).location, kCursorNormal, kCursorNormal); + + switch (params->param1) { + default: + getObjects()->update(kObjectCompartment1, kEntityPlayer, getObjects()->get(kObjectCompartment1).location, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + + case 1: + setCallback(23); + setup_playSound16("CON1018D"); + break; + + case 2: + setCallback(24); + setup_playSound16("CON1018E"); + break; + + case 3: + setCallback(25); + setup_playSound16("CON1025"); + break; + } + + } else { + setCallback(22); + setup_function26(true); + } + break; + + case kActionOpenDoor: + getSound()->playSound(kEntityPlayer, getObjects()->get(kObjectCompartment1).location == kObjectLocation1 ? "LIB012" : "LIB014"); + + if (getProgress().eventCorpseMovedFromFloor) { + + if (getProgress().jacket == kJacketBlood) { + setCallback(27); + setup_savegame(kSavegameTypeEvent, kEventMertensBloodJacket); + break; + } + + if (params->param1) { + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + switch (params->param1) { + case 1: + setCallback(29); + setup_savegame(kSavegameTypeEvent, kEventMertensAugustWaitingCompartment); + break; + + case 2: + setCallback(30); + setup_savegame(kSavegameTypeEvent, kEventMertensKronosInvitationCompartment); + break; + + case 3: + getAction()->playAnimation(isNight() ? kEventMertensPushCallNight : kEventMertensPushCall); + // fallback to default case + + default: + getSound()->playSound(kEntityPlayer, "LIB015"); + getScenes()->loadScene(kScene41); + + CALLBACK_ACTION(); + break; + } + } else { + setCallback(28); + setup_function26(false); + } + } else { + if (!getEntities()->isInsideTrainCar(kEntityPlayer, kCarGreenSleeping)) + getScenes()->loadSceneFromPosition(kCarNone, 1); + + setCallback(26); + setup_savegame(kSavegameTypeEvent, kEventMertensCorpseFloor); + } + break; + + case kActionDefault: + getData()->inventoryItem = kItemNone; + + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_8200) + || getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_7850) + || getEntities()->isOutsideAlexeiWindow()) { + getObjects()->update(kObjectCompartment1, kEntityPlayer, getObjects()->get(kObjectCompartment1).location, kCursorNormal, kCursorNormal); + + if (getEntities()->isOutsideAlexeiWindow()) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 49); + + setCallback(params->param1 ? 9 : 8); + setup_playSound16(params->param1 ? "CON1018" : "CON1060"); + } else { + getSound()->playSound(kEntityMertens, "CON1019"); + + setCallback(1); + setup_enterExitCompartment("601Ma", kObjectCompartment1); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getProgress().eventCorpseMovedFromFloor) { + setCallback(4); + setup_enterExitCompartment("601Ra", kObjectCompartment1); + } else { + if (getEntities()->isInsideTrainCar(kEntityPlayer, kCarGreenSleeping)) { + setCallback(2); + setup_enterExitCompartment("601Ra", kObjectCompartment1); + } else { + getScenes()->loadSceneFromPosition(kCarNone, 1); + + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventMertensCorpseFloor); + } + } + break; + + case 2: + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventMertensCorpseFloor); + break; + + case 3: + case 12: + case 17: + case 26: + getAction()->playAnimation(kEventMertensCorpseFloor); + getLogic()->gameOver(kSavegameTypeIndex, 1, getProgress().eventCorpseFound ? kSceneGameOverStopPolice : kSceneGameOverPolice, true); + break; + + case 4: + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorNormal); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMertens); + + if (params->param1) { + setCallback(7); + setup_enterExitCompartment("601Sa", kObjectCompartment1); + break; + } + + if (getProgress().eventCorpseThrown || getProgress().chapter != kChapter1) { + setCallback(6); + setup_function21(kObjectCompartment1, kObjectHandleBathroom); + } else { + if (!getEntities()->isInsideTrainCar(kEntityPlayer, kCarGreenSleeping)) + getScenes()->loadSceneFromPosition(kCarNone, 1); + + setCallback(5); + setup_savegame(kSavegameTypeEvent, kEventMertensCorpseBed); + } + break; + + case 5: + getAction()->playAnimation(kEventMertensCorpseBed); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverPolice1, true); + break; + + case 6: + setCallback(7); + setup_enterExitCompartment("601Sa", kObjectCompartment1); + break; + + case 7: + getData()->location = kLocationOutsideCompartment; + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + + case 8: + case 9: + getObjects()->update(kObjectCompartment1, kEntityMertens, getObjects()->get(kObjectCompartment1).location, kCursorTalk, kCursorHand); + break; + + case 10: + getObjects()->update(kObjectCompartment1, kEntityMertens, getObjects()->get(kObjectCompartment1).location, kCursorTalk, kCursorHand); + goto label_callback10; + + case 11: + getObjects()->update(kObjectCompartment1, kEntityMertens, getObjects()->get(kObjectCompartment1).location, kCursorTalk, kCursorHand); + goto label_callback11; + + case 13: + case 18: + case 27: + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + break; + + case 14: + case 19: + case 22: + case 28: + CALLBACK_ACTION(); + break; + + case 15: + case 20: + case 29: + getAction()->playAnimation(kEventMertensAugustWaitingCompartment); + getProgress().eventMertensAugustWaiting = true; + + getSound()->playSound(kEntityPlayer, "LIB015"); + getScenes()->loadScene(kScene41); + + CALLBACK_ACTION(); + break; + + case 16: + case 21: + case 30: + getAction()->playAnimation(kEventMertensKronosInvitationCompartment); + getProgress().eventMertensKronosInvitation = true; + + getSound()->playSound(kEntityPlayer, "LIB015"); + getScenes()->loadScene(kScene41); + + CALLBACK_ACTION(); + break; + + case 23: + getProgress().eventMertensAugustWaiting = true; + getObjects()->update(kObjectCompartment1, kEntityPlayer, getObjects()->get(kObjectCompartment1).location, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + + case 24: + getProgress().eventMertensKronosInvitation = true; + getObjects()->update(kObjectCompartment1, kEntityPlayer, getObjects()->get(kObjectCompartment1).location, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + + case 25: + getObjects()->update(kObjectCompartment1, kEntityPlayer, getObjects()->get(kObjectCompartment1).location, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(28, Mertens, function28) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param4 && params->param5) { + getSavePoints()->push(kEntityMertens, kEntityCoudert, kAction125499160); + + setCallback(3); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + } + break; + + case kActionEndSound: + params->param4 = 1; + break; + + case kActionDefault: + setCallback(1); + setup_function19(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_1500); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityMertens, "601O"); + getSavePoints()->push(kEntityMertens, kEntityCoudert, kAction154005632); + break; + + case 3: + setCallback(4); + setup_function17(); + break; + + case 4: + CALLBACK_ACTION(); + break; + } + break; + + case kAction155853632: + params->param5 = 1; + break; + + case kAction202558662: + getEntities()->drawSequenceLeft(kEntityMertens, "601L"); + getSound()->playSound(kEntityMertens, (char *)¶ms->seq1); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SS(29, Mertens, function29) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param7 > 1 && params->param8) { + getSavePoints()->push(kEntityMertens, kEntityCoudert, kAction125499160); + + setCallback(3); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + } + break; + + case kActionEndSound: + params->param7++; + if (params->param7 == 1) + getSound()->playSound(kEntityMertens, (char *)¶ms->seq2); + break; + + case kActionDefault: + setCallback(1); + setup_function19(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_1500); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityMertens, "601O"); + getSavePoints()->push(kEntityMertens, kEntityCoudert, kAction154005632); + break; + + case 3: + setCallback(4); + setup_function17(); + break; + + case 4: + CALLBACK_ACTION(); + break; + } + break; + + case kAction155853632: + params->param8 = 1; + break; + + case kAction202558662: + getEntities()->drawSequenceLeft(kEntityMertens, "601L"); + getSound()->playSound(kEntityMertens, (char *)¶ms->seq1); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(30, Mertens, function30, MertensActionType) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + switch (params->param1) { + default: + CALLBACK_ACTION(); + return; + + case 1: + params->param2 = kPosition_8200; + + if (getProgress().field_14) { + CALLBACK_ACTION(); + return; + } + + getProgress().field_14 = 3; + break; + + case 2: + params->param2 = kPosition_7500; + break; + + case 3: + params->param2 = kPosition_6470; + break; + } + + setCallback(1); + setup_function19(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, (EntityPosition)params->param2); + break; + + case 2: + switch (params->param1) { + default: + if (getProgress().field_14 == 3) + getProgress().field_14 = 0; + + setCallback(8); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 1: + if (getProgress().chapter == kChapter4) + getSavePoints()->push(kEntityMertens, kEntityTatiana, kAction238790488); + + setCallback(3); + setup_tylerCompartment(kMertensAction3); + break; + + case 2: + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_7500)) { + getObjects()->update(kObjectCompartment2, kEntityPlayer, getObjects()->get(kObjectCompartment2).location, kCursorNormal, kCursorNormal); + params->param3 = 1; + } + + setCallback(4); + setup_enterExitCompartment("601Vb", kObjectCompartment2); + break; + + case 3: + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_6470)) { + getObjects()->update(kObjectCompartment3, kEntityPlayer, getObjects()->get(kObjectCompartment3).location, kCursorNormal, kCursorNormal); + params->param3 = 1; + } + + setCallback(6); + setup_enterExitCompartment("601Mc", kObjectCompartment3); + break; + } + break; + + case 3: + if (getProgress().field_14 == 3) + getProgress().field_14 = 0; + + setCallback(8); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 4: + getEntities()->drawSequenceLeft(kEntityMertens, "601Wb"); + getEntities()->enterCompartment(kEntityMertens, kObjectCompartment2, true); + + setCallback(5); + setup_playSound("CON3020"); + break; + + case 5: + if (params->param3) + getObjects()->update(kObjectCompartment2, kEntityPlayer, getObjects()->get(kObjectCompartment2).location, kCursorHandKnock, kCursorHand); + + getEntities()->exitCompartment(kEntityMertens, kObjectCompartment2); + + if (getProgress().field_14 == 3) + getProgress().field_14 = 0; + + setCallback(8); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 6: + getEntities()->drawSequenceLeft(kEntityMertens, "601Nc"); + getEntities()->enterCompartment(kEntityMertens, kObjectCompartment3, true); + + setCallback(7); + setup_playSound("CON3020"); + break; + + case 7: + if (params->param3) + getObjects()->update(kObjectCompartment3, kEntityPlayer, getObjects()->get(kObjectCompartment3).location, kCursorHandKnock, kCursorHand); + + getEntities()->exitCompartment(kEntityMertens, kObjectCompartment3); + + if (getProgress().field_14 == 3) + getProgress().field_14 = 0; + + setCallback(8); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 8: + setCallback(9); + setup_function17(); + break; + + case 9: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(31, Mertens, function31, MertensActionType) + switch (savepoint.action) { + default: + break; + + case kActionEndSound: + setCallback(3); + setup_function17(); + break; + + case kActionDefault: + setCallback(1); + setup_bloodJacket("601G"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getSound()->isBuffered(kEntityMertens)) { + getEntities()->drawSequenceLeft(kEntityMertens, "601J"); + } else { + setCallback(2); + setup_function17(); + } + break; + + case 2: + case 3: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Mertens, function32) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function19(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_9510); + break; + + case 2: + if (getData()->entityPosition >= kPosition_9460) { + getEntities()->clearSequences(kEntityMertens); + setCallback(3); + setup_function11(900); + break; + } + // Fallback to next case + + case 3: + setCallback(4); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 4: + setCallback(5); + setup_function17(); + break; + + case 5: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Mertens, function33) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (ENTITY_PARAM(0, 8) || ENTITY_PARAM(0, 6) + || ENTITY_PARAM(1, 1) || ENTITY_PARAM(1, 2) || ENTITY_PARAM(1, 3) || ENTITY_PARAM(1, 4) || ENTITY_PARAM(1, 5) || ENTITY_PARAM(1, 6) || ENTITY_PARAM(1, 7) + || ENTITY_PARAM(2, 2)) { + ENTITY_PARAM(1, 8) = 1; + + setCallback(ENTITY_PARAM(0, 8) ? 1 : 3); + setup_updateEntity(kCarGreenSleeping, ENTITY_PARAM(0, 8) ? kPosition_1500 : kPosition_540); + } else { + CALLBACK_ACTION(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + ENTITY_PARAM(2, 1) = 1; + + setCallback(2); + setup_function14(kEntityVerges); + break; + + case 2: + ENTITY_PARAM(1, 8) = 0; + + CALLBACK_ACTION(); + break; + + case 3: + getEntities()->clearSequences(kEntityMertens); + + setCallback(4); + setup_function11(75); + break; + + case 4: + if (ENTITY_PARAM(1, 6)) { + setCallback(5); + setup_function16(true); + break; + } + // Fallback to next case + + case 5: + if (ENTITY_PARAM(1, 7)) { + setCallback(6); + setup_function16(false); + break; + } + // Fallback to next case + + case 6: + if (ENTITY_PARAM(1, 5)) { + setCallback(7); + setup_function15(true); + break; + } + // Fallback to next case + + case 7: + if (ENTITY_PARAM(1, 4)) { + setCallback(8); + setup_function15(false); + break; + } + // Fallback to next case + + case 8: + if (ENTITY_PARAM(1, 2)) { + setCallback(9); + setup_function35(); + break; + } + // Fallback to next case + + case 9: + if (ENTITY_PARAM(0, 6)) { + setCallback(10); + setup_function36(); + break; + } + // Fallback to next case + + case 10: + if (ENTITY_PARAM(1, 3)) { + setCallback(11); + setup_function40(); + break; + } + // Fallback to next case + + case 11: + if (ENTITY_PARAM(1, 1)) { + setCallback(12); + setup_function28("CON1200"); + break; + } + + if (ENTITY_PARAM(2, 2)) { + setCallback(13); + setup_function37(); + break; + } + + CALLBACK_ACTION(); + break; + + case 12: + getSavePoints()->push(kEntityMertens, kEntityCoudert, kAction168254872); + ENTITY_PARAM(1, 1) = 0; + + if (ENTITY_PARAM(2, 2)) { + setCallback(13); + setup_function37(); + break; + } + + CALLBACK_ACTION(); + break; + + case 13: + ENTITY_PARAM(2, 2) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(34, Mertens, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getSavePoints()->addData(kEntityMertens, kAction171394341, 7); + getSavePoints()->addData(kEntityMertens, kAction169633856, 9); + getSavePoints()->addData(kEntityMertens, kAction238732837, 10); + getSavePoints()->addData(kEntityMertens, kAction269624833, 12); + getSavePoints()->addData(kEntityMertens, kAction302614416, 11); + getSavePoints()->addData(kEntityMertens, kAction190082817, 8); + getSavePoints()->addData(kEntityMertens, kAction269436673, 13); + getSavePoints()->addData(kEntityMertens, kAction303343617, 14); + getSavePoints()->addData(kEntityMertens, kAction224122407, 17); + getSavePoints()->addData(kEntityMertens, kAction201431954, 18); + getSavePoints()->addData(kEntityMertens, kAction188635520, 19); + getSavePoints()->addData(kEntityMertens, kAction204379649, 4); + + ENTITY_PARAM(0, 1) = 0; + + getData()->entityPosition = kPosition_9460; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + + break; + } + +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(35, Mertens, function35) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (getProgress().field_14 == 29) { + CALLBACK_ACTION(); + break; + } else { + getProgress().field_14 = 3; + + setCallback(1); + setup_function19(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_8200); + break; + + case 2: + if (!ENTITY_PARAM(1, 2) || getProgress().eventMetAugust) { + ENTITY_PARAM(1, 2) = 0; + + if (getProgress().field_14 == 3) + getProgress().field_14 = 0; + + setCallback(3); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + } else { + setCallback(5); + setup_tylerCompartment(kMertensAction1); + } + break; + + case 3: + setCallback(4); + setup_function17(); + break; + + case 4: + CALLBACK_ACTION(); + break; + + case 5: + if (getProgress().field_14 == 3) + getProgress().field_14 = 0; + + if (getProgress().eventMertensAugustWaiting) + ENTITY_PARAM(1, 2) = 0; + + setCallback(6); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 6: + ENTITY_PARAM(1, 2) = 0; + + setCallback(7); + setup_function17(); + break; + + case 7: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(36, Mertens, function36) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (getProgress().field_14 == 29) { + CALLBACK_ACTION(); + } else { + getProgress().field_14 = 3; + + setCallback(1); + setup_function19(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_8200); + break; + + case 2: + if (ENTITY_PARAM(0, 6)) { + if (getEntities()->isPlayerInCar(kCarGreenSleeping) && getData()->entityPosition < getEntityData(kEntityPlayer)->entityPosition) { + setCallback(3); + setup_updateEntity(kCarGreenSleeping, kPosition_9460); + } else { + setCallback(7); + setup_tylerCompartment(kMertensAction2); + } + } else { + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 7) = 0; + + if (getProgress().field_14 == 3) + getProgress().field_14 = 0; + + setCallback(5); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + } + break; + + case 3: + setCallback(4); + setup_updateEntity(kCarGreenSleeping, kPosition_8200); + break; + + case 4: + if (ENTITY_PARAM(0, 6)) { + setCallback(7); + setup_tylerCompartment(kMertensAction2); + } else { + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 7) = 0; + + if (getProgress().field_14 == 3) + getProgress().field_14 = 0; + + setCallback(5); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + } + break; + + case 5: + setCallback(6); + setup_function17(); + break; + + case 7: + if (getProgress().field_14 == 3) + getProgress().field_14 = 0; + + if (!getProgress().eventMertensKronosInvitation) + ENTITY_PARAM(0, 7) = 1; + + ENTITY_PARAM(0, 6) = 0; + + setCallback(8); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 8: + setCallback(9); + setup_function17(); + break; + + case 6: + case 9: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(37, Mertens, function37) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 >= 2 && params->param2) { + getSavePoints()->push(kEntityMertens, kEntityCoudert, kAction125499160); + + setCallback(3); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + } + break; + + case kActionEndSound: + ++params->param6; + + if (params->param6 == 1) + getSound()->playSound(kEntityMertens, getEntities()->isDistanceBetweenEntities(kEntityMertens, kEntityPlayer, 2000) ? "CON1152" : "CON1151"); + break; + + case kActionDefault: + setCallback(1); + setup_function19(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_1500); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityMertens, "601O"); + getSavePoints()->push(kEntityMertens, kEntityCoudert, kAction154005632); + break; + + case 3: + setCallback(4); + setup_function17(); + break; + + case 4: + CALLBACK_ACTION(); + break; + } + break; + + case kAction155853632: + params->param2 = 1; + break; + + case kAction202558662: + getEntities()->drawSequenceLeft(kEntityMertens, "601L"); + getSound()->playSound(kEntityMertens, "CON1150"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(38, Mertens, function38) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (!ENTITY_PARAM(0, 4)) { + CALLBACK_ACTION(); + break; + } + + if (getProgress().field_14 == 29) { + CALLBACK_ACTION(); + } else { + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_8200); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (!ENTITY_PARAM(0, 4)) { + CALLBACK_ACTION(); + break; + } + + setCallback(2); + setup_tylerCompartment(kMertensActionNone); + break; + + case 2: + ENTITY_PARAM(0, 4) = 0; + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(39, Mertens, function39) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + ENTITY_PARAM(0, 4) = 1; + + setCallback(1); + setup_function19(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function22(); + break; + + case 2: + setCallback(3); + setup_function33(); + break; + + case 3: + setCallback(4); + setup_function24(); + break; + + case 4: + setCallback(5); + setup_function33(); + break; + + case 5: + setCallback(6); + setup_function25(); + break; + + case 6: + setCallback(7); + setup_function33(); + break; + + case 7: + setCallback(8); + setup_function38(); + break; + + case 8: + if (getProgress().field_14 == 3) + getProgress().field_14 = 0; + + setCallback(9); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 9: + setCallback(10); + setup_function17(); + break; + + case 10: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(40, Mertens, function40) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + ENTITY_PARAM(1, 3) = 0; + setCallback(1); + setup_function19(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarKronos, kPosition_9460); + break; + + case 2: + setCallback(3); + setup_function11(1800); + break; + + case 3: + setCallback(4); + setup_updateEntity(kCarGreenSleeping, kPosition_1500); + break; + + case 4: + setCallback(5); + setup_function17(); + break; + + case 5: + ENTITY_PARAM(0, 6) = 1; + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(41, Mertens, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function17(); + break; + + case 2: + setup_function42(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(42, Mertens, function42) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (ENTITY_PARAM(2, 3)) { + ENTITY_PARAM(0, 1) = 1; + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 7) = 0; + ENTITY_PARAM(0, 8) = 0; + + ENTITY_PARAM(1, 1) = 0; + ENTITY_PARAM(1, 2) = 0; + ENTITY_PARAM(1, 3) = 0; + ENTITY_PARAM(1, 4) = 0; + ENTITY_PARAM(1, 5) = 0; + ENTITY_PARAM(1, 6) = 0; + ENTITY_PARAM(1, 7) = 0; + + ENTITY_PARAM(2, 1) = 0; // BUG: is set twice. Maybe a bug? + ENTITY_PARAM(2, 2) = 0; + ENTITY_PARAM(2, 3) = 0; + + params->param1 = 1; + params->param2 = 1; + + getEntities()->drawSequenceLeft(kEntityMertens, "601E"); + } + + if (ENTITY_PARAM(2, 1) || getProgress().eventCorpseFound || getEvent(kEventMertensAskTylerCompartmentD) || getEvent(kEventMertensAskTylerCompartment)) + getData()->inventoryItem = kItemNone; + else + getData()->inventoryItem = kItemInvalid; + + if (!params->param2) { + TIME_CHECK_SAVEPOINT(kTime1125000, params->param3, kEntityMertens, kEntityMahmud, kAction170483072); + + if (params->param4 != kTimeInvalid && getState()->time > kTimeCityChalons) { + + if (getState()->time <= kTime1188000) { + if ((!getEntities()->isPlayerInCar(kCarGreenSleeping) && !getEntities()->isPlayerInCar(kCarRedSleeping)) + || getSound()->isBuffered("REB1205") + || !getEntities()->isInsideCompartment(kEntityMmeBoutarel, kCarRedSleeping, kPosition_5790) + || !params->param4) { + params->param4 = getState()->time; + } + + if (params->param4 >= getState()->time) + break; + } + + ENTITY_PARAM(0, 4) = kTimeInvalid; + getData()->inventoryItem = kItemNone; + + setCallback(8); + setup_function29("CON1210", "CON1210A"); + break; + } + } + +label_callback_8: + if (getState()->time > kTime1215000 && !ENTITY_PARAM(0, 1) && !ENTITY_PARAM(2, 1)) { + UPDATE_PARAM_PROC(params->param5, getState()->time, 2700) + getEntities()->drawSequenceLeft(kEntityMertens, "601E"); + ENTITY_PARAM(0, 1) = 1; + params->param5 = 0; + UPDATE_PARAM_PROC_END + } + + if (ENTITY_PARAM(0, 8)) { + getData()->inventoryItem = kItemNone; + setCallback(9); + setup_function14(kEntityVerges); + break; + } + + if (getProgress().field_14 == 29) + goto label_callback_13; + +label_callback_9: + if (ENTITY_PARAM(1, 6)) { + getData()->inventoryItem = kItemNone; + setCallback(10); + setup_function16(true); + break; + } + +label_callback_10: + if (ENTITY_PARAM(1, 7)) { + getData()->inventoryItem = kItemNone; + setCallback(11); + setup_function16(false); + break; + } + +label_callback_11: + if (ENTITY_PARAM(1, 5)) { + getData()->inventoryItem = kItemNone; + setCallback(12); + setup_function15(true); + break; + } + +label_callback_12: + if (ENTITY_PARAM(1, 4)) { + getData()->inventoryItem = kItemNone; + setCallback(13); + setup_function15(false); + break; + } + +label_callback_13: + if (ENTITY_PARAM(1, 2)) { + getData()->inventoryItem = kItemNone; + setCallback(14); + setup_function35(); + break; + } + +label_callback_14: + if (ENTITY_PARAM(0, 6)) { + getData()->inventoryItem = kItemNone; + setCallback(15); + setup_function36(); + break; + } + +label_callback_15: + if (ENTITY_PARAM(1, 3)) { + getData()->inventoryItem = kItemNone; + setCallback(16); + setup_function40(); + break; + } + +label_callback_16: + if (ENTITY_PARAM(1, 1)) { + ENTITY_PARAM(1, 1) = 0; + getData()->inventoryItem = kItemNone; + setCallback(17); + setup_function28("CON1200"); + break; + } + +label_callback_17: + if (ENTITY_PARAM(2, 2)) { + ENTITY_PARAM(2, 2) = 0; + getData()->inventoryItem = kItemNone; + setCallback(18); + setup_function37(); + break; + } + +label_callback_18: + if (!params->param1 && ENTITY_PARAM(0, 5)) { + getData()->inventoryItem = kItemNone; + setCallback(19); + setup_function39(); + break; + } + +label_callback_19: + if (ENTITY_PARAM(0, 1) && !getSound()->isBuffered(kEntityMertens)) { + if (getProgress().field_18 != 4) + getSound()->playSound(kEntityMertens, "CON1505"); + } + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + setCallback(21); + setup_savegame(kSavegameTypeEvent, kEventMertensAskTylerCompartmentD); + break; + + case kAction11: + if (!ENTITY_PARAM(0, 1) && !ENTITY_PARAM(2, 1)) { + getData()->inventoryItem = kItemNone; + setCallback(20); + setup_function13((bool)savepoint.param.intValue, (bool)savepoint.entity2); + } + break; + + case kActionDefault: + getData()->car = kCarGreenSleeping; + getData()->entityPosition = kPosition_1500; + getData()->location = kLocationOutsideCompartment; + getScenes()->loadSceneFromItemPosition(kItem7); + break; + + case kActionDrawScene: + if (ENTITY_PARAM(2, 1)) + break; + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 23) && ENTITY_PARAM(0, 7) && !getEvent(kEventKronosConversation)) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventMertensKronosInvitation); + break; + } + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 23) && !getProgress().eventMertensKronosInvitation && !getEvent(kEventMertensLastCar) && !getEvent(kEventMertensLastCarOriginalJacket)) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventMertensLastCar); + break; + } + +label_callback_2_4: + if ((getEntities()->isPlayerPosition(kCarGreenSleeping, 1) || getEntities()->isPlayerPosition(kCarGreenSleeping, 23)) && !ENTITY_PARAM(0, 1) && !ENTITY_PARAM(2, 1)) { + getData()->inventoryItem = kItemNone; + setCallback(getEntities()->isPlayerPosition(kCarGreenSleeping, 1) ? 5 : 6); + setup_function13(getEntities()->isPlayerPosition(kCarGreenSleeping, 1), false); + break; + } + +label_callback_5_6: + if (getEntities()->isPlayerInCar(kCarGreenSleeping) && getData()->entityPosition < getEntityData(kEntityPlayer)->entityPosition) { + if (getProgress().jacket == kJacketOriginal || ENTITY_PARAM(0, 7)) { + getData()->inventoryItem = kItemNone; + setCallback(7); + setup_function32(); + } + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getAction()->playAnimation(kEventMertensKronosInvitation); + getProgress().eventMertensKronosInvitation = true; + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 7) = 0; + getEntities()->drawSequenceRight(kEntityMertens, "601A"); + getScenes()->loadSceneFromItemPosition(kItem7); + ENTITY_PARAM(0, 1) = 0; + getData()->inventoryItem = kItemNone; + + setCallback(2); + setup_callbackActionOnDirection(); + break; + + case 2: + case 4: + getEntities()->drawSequenceLeft(kEntityMertens, "601B"); + goto label_callback_2_4; + + case 3: + getAction()->playAnimation(getProgress().jacket == kJacketOriginal ? kEventMertensLastCarOriginalJacket : kEventMertensLastCar); + getEntities()->drawSequenceRight(kEntityMertens, "601A"); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 6); + getScenes()->loadSceneFromItemPosition(kItem7); + ENTITY_PARAM(0, 1) = 0; + getData()->inventoryItem = kItemNone; + + setCallback(4); + setup_callbackActionOnDirection(); + break; + + case 5: + case 6: + goto label_callback_5_6; + + case 8: + goto label_callback_8; + + case 9: + goto label_callback_9; + + case 10: + goto label_callback_10; + + case 11: + goto label_callback_11; + + case 12: + goto label_callback_12; + + case 13: + goto label_callback_13; + + case 14: + goto label_callback_14; + + case 15: + goto label_callback_15; + + case 16: + goto label_callback_16; + + case 17: + goto label_callback_17; + + case 18: + goto label_callback_18; + + case 19: + params->param1 = 1; + goto label_callback_19; + + case 21: + getAction()->playAnimation(kEventMertensAskTylerCompartmentD); + getEntities()->drawSequenceRight(kEntityMertens, "601A"); + getInventory()->get(kItem7)->location = kObjectLocationNone; + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 25); + + setCallback(22); + setup_callbackActionOnDirection(); + break; + + case 22: + getEntities()->drawSequenceLeft(kEntityMertens, "601B"); + break; + } + break; + + case kAction225358684: + if (!ENTITY_PARAM(0, 1)) { + getData()->inventoryItem = kItemNone; + setCallback(23); + setup_function30((MertensActionType)savepoint.param.intValue); + } + break; + + case kAction225932896: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) + getSavePoints()->push(kEntityMertens, kEntityFrancois, kAction205346192); + break; + + case kAction305159806: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + getData()->inventoryItem = kItemNone; + setCallback(24); + setup_function31((MertensActionType)savepoint.param.intValue); + } + break; + + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(43, Mertens, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setCallback(1); + setup_function17(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMertens); + + getData()->entityPosition = kPosition_1500; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 8) = 0; + + ENTITY_PARAM(0, 1) = 0; + ENTITY_PARAM(0, 2) = 0; + ENTITY_PARAM(0, 3) = 0; + ENTITY_PARAM(0, 4) = 0; + ENTITY_PARAM(0, 5) = 0; + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 7) = 0; + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function44(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(44, Mertens, function44) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (ENTITY_PARAM(1, 6)) { + setCallback(1); + setup_function16(true); + break; + } + +label_callback1: + if (ENTITY_PARAM(1, 7)) { + setCallback(2); + setup_function16(false); + break; + } + +label_callback2: + if (ENTITY_PARAM(1, 5)) { + setCallback(3); + setup_function15(true); + break; + } + +label_callback3: + if (ENTITY_PARAM(1, 4)) { + setCallback(4); + setup_function15(false); + break; + } + + break; + + case kAction11: + if (!ENTITY_PARAM(2, 1)) { + setCallback(5); + setup_function13((bool)savepoint.param.intValue, (bool)savepoint.entity2); + } + break; + + case kActionDrawScene: + if (ENTITY_PARAM(2, 1)) + break; + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 1)) { + setCallback(6); + setup_function13(true, false); + + } else if (getEntities()->isPlayerPosition(kCarGreenSleeping, 23)) { + setCallback(7); + setup_function13(false, false); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback1; + + case 2: + goto label_callback2; + + case 3: + goto label_callback3; + } + break; + + case kAction225358684: + if (!ENTITY_PARAM(0, 1)) { + setCallback(9); + setup_function30((MertensActionType)savepoint.param.intValue); + } + break; + + case kAction225932896: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) + getSavePoints()->push(kEntityMertens, kEntityFrancois, kAction205346192); + break; + + case kAction226078300: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + setCallback(8); + setup_playSound("CON2020"); + } + break; + + case kAction305159806: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + setCallback(10); + setup_function31((MertensActionType)savepoint.param.intValue); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(45, Mertens, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setCallback(1); + setup_function17(); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_1500; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 8) = 0; + + ENTITY_PARAM(1, 1) = 0; + ENTITY_PARAM(1, 2) = 0; + ENTITY_PARAM(1, 3) = 0; + ENTITY_PARAM(1, 4) = 0; + ENTITY_PARAM(1, 5) = 0; + ENTITY_PARAM(1, 6) = 0; + ENTITY_PARAM(1, 7) = 0; + + ENTITY_PARAM(2, 3) = 0; + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function46(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(46, Mertens, function46) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (ENTITY_PARAM(1, 6)) { + setCallback(1); + setup_function16(true); + break; + } + +label_callback_1: + if (ENTITY_PARAM(1, 7)) { + setCallback(2); + setup_function16(false); + break; + } + +label_callback_2: + if (ENTITY_PARAM(1, 5)) { + setCallback(3); + setup_function15(true); + break; + } + +label_callback_3: + if (ENTITY_PARAM(1, 4)) { + setCallback(4); + setup_function15(false); + break; + } + +label_callback_4: + if (ENTITY_PARAM(0, 8)) { + setCallback(5); + setup_function14(kEntityVerges); + break; + } + +label_callback_5: + if (ENTITY_PARAM(2, 4) + && (getEvent(kEventKronosVisit) || getState()->time > kTime2052000) + && getState()->time < kTime2133000 + && getEntities()->isPlayerInCar(kCarGreenSleeping)) { + setCallback(6); + setup_function32(); + break; + } + +label_callback_6: + TIME_CHECK_CALLBACK_1(kTime1971000, params->param1, 7, setup_function28, "CON3012"); + +label_callback_7: + TIME_CHECK_CALLBACK(kTime2117700, params->param2, 8, setup_function32); + +label_callback_8: + TIME_CHECK_CALLBACK_1(kTime2124000, params->param3, 9, setup_function28, "CON2010"); + +label_callback_9: + TIME_CHECK_CALLBACK(kTime2146500, params->param4, 10, setup_function32); + +label_callback_10: + TIME_CHECK_CALLBACK(kTime2169000, params->param5, 11, setup_function32); + break; + + case kAction11: + if (!ENTITY_PARAM(2, 1)) { + setCallback(12); + setup_function13((bool)savepoint.param.intValue, savepoint.entity2 != kEntityPlayer); + } + break; + + case kActionDefault: + break; + + case kActionDrawScene: + if (!ENTITY_PARAM(2, 1)) { + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 1)) { + setCallback(13); + setup_function13(true, false); + } else if (getEntities()->isPlayerPosition(kCarGreenSleeping, 23)) { + setCallback(14); + setup_function13(false, false); + } + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback_1; + + case 2: + goto label_callback_2; + + case 3: + goto label_callback_3; + + case 4: + goto label_callback_4; + + case 5: + goto label_callback_5; + + case 6: + goto label_callback_6; + + case 7: + goto label_callback_7; + + case 8: + goto label_callback_8; + + case 9: + goto label_callback_9; + + case 10: + goto label_callback_10; + } + break; + + case kAction225358684: + if (!ENTITY_PARAM(0, 1)) { + setCallback(16); + setup_function30((MertensActionType)savepoint.param.intValue); + } + break; + + case kAction225932896: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) + getSavePoints()->push(kEntityMertens, kEntityFrancois, kAction205346192); + break; + + case kAction226078300: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + setCallback(15); + setup_playSound("CON2020"); + } + break; + + case kAction305159806: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + setCallback(17); + setup_function31((MertensActionType)savepoint.param.intValue); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(47, Mertens, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setCallback(1); + setup_function17(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMertens); + + getData()->entityPosition = kPosition_1500; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 8) = 0; + + ENTITY_PARAM(1, 1) = 0; + ENTITY_PARAM(1, 2) = 0; + ENTITY_PARAM(1, 3) = 0; + ENTITY_PARAM(1, 4) = 0; + ENTITY_PARAM(1, 5) = 0; + ENTITY_PARAM(1, 6) = 0; + ENTITY_PARAM(1, 7) = 0; + + ENTITY_PARAM(2, 4) = 0; + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function48(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(48, Mertens, function48) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (ENTITY_PARAM(2, 3)) { + params->param1 = 1; + + getObjects()->updateLocation2(kObjectCompartment2, kObjectLocation1); + getObjects()->updateLocation2(kObjectCompartment3, kObjectLocation1); + getObjects()->updateLocation2(kObjectCompartment4, kObjectLocation1); + + ENTITY_PARAM(1, 4) = 0; + ENTITY_PARAM(1, 5) = 0; + ENTITY_PARAM(1, 6) = 0; + ENTITY_PARAM(1, 7) = 0; + + getEntities()->drawSequenceLeft(kEntityMertens, "601E"); + + ENTITY_PARAM(2, 3) = 0; + } + + if (ENTITY_PARAM(1, 6)) { + setCallback(1); + setup_function16(true); + break; + } + +label_callback_1: + if (ENTITY_PARAM(1, 7)) { + setCallback(2); + setup_function16(false); + break; + } + +label_callback_2: + if (ENTITY_PARAM(1, 5)) { + setCallback(3); + setup_function15(true); + break; + } + +label_callback_3: + if (ENTITY_PARAM(1, 4)) { + setCallback(4); + setup_function15(false); + break; + } + +label_callback_4: + if (!params->param1) { + TIME_CHECK_CALLBACK(kTime2403000, params->param2, 5, setup_function49); + +label_callback_5: + TIME_CHECK_CALLBACK(kTime2430000, params->param3, 6, setup_function32); + +label_callback_6: + TIME_CHECK_CALLBACK(kTime2439000, params->param4, 7, setup_function32); + +label_callback_7: + TIME_CHECK_CALLBACK(kTime2448000, params->param5, 8, setup_function32); + } + +label_callback_8: + if (getState()->time > kTime2538000 && !ENTITY_PARAM(0, 1) && !ENTITY_PARAM(2, 1)) { + UPDATE_PARAM(params->param6, getState()->time, 2700); + + getEntities()->drawSequenceLeft(kEntityMertens, "601E"); + + ENTITY_PARAM(0, 1) = 1; + params->param6 = 0; + } + break; + + case kAction11: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + setCallback(9); + setup_function13((bool)savepoint.param.intValue, savepoint.entity2 != kEntityPlayer); + } + break; + + case kActionDefault: + getData()->car = kCarGreenSleeping; + getData()->entityPosition = kPosition_1500; + getData()->location = kLocationOutsideCompartment; + + getScenes()->loadSceneFromItemPosition(kItem7); + break; + + case kActionDrawScene: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 1)) { + setCallback(10); + setup_function13(true, false); + } else if (getEntities()->isPlayerPosition(kCarGreenSleeping, 23)) { + setCallback(11); + setup_function13(false, false); + } + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback_1; + + case 2: + goto label_callback_2; + + case 3: + goto label_callback_3; + + case 4: + goto label_callback_4; + + case 5: + goto label_callback_5; + + case 6: + goto label_callback_6; + + case 7: + goto label_callback_7; + + case 8: + goto label_callback_8; + } + break; + + case kAction225358684: + if (!ENTITY_PARAM(0, 1)) { + setCallback(13); + setup_function30((MertensActionType)savepoint.param.intValue); + } + break; + + case kAction226078300: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + setCallback(12); + setup_playSound("CON2020"); + } + break; + + case kAction305159806: + if (!ENTITY_PARAM(2, 1) && !ENTITY_PARAM(0, 1)) { + setCallback(14); + setup_function31((MertensActionType)savepoint.param.intValue); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(49, Mertens, function49) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function19(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_8200); + break; + + case 2: + setCallback(3); + setup_tylerCompartment(kMertensActionNone); + break; + + case 3: + setCallback(4); + setup_function33(); + break; + + case 4: + setCallback(5); + setup_function25(); + break; + + case 5: + setCallback(6); + setup_function33(); + break; + + case 6: + setCallback(7); + setup_function24(); + break; + + case 7: + setCallback(8); + setup_function33(); + break; + + case 8: + setCallback(9); + setup_function23(); + break; + + case 9: + setCallback(10); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 10: + setCallback(11); + setup_function17(); + break; + + case 11: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(50, Mertens, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMertens); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(51, Mertens, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_function52(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(52, Mertens, function52) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param2 == kTimeInvalid) + break; + + if (params->param1 >= getState()->time) { + + if (!getEntities()->isPlayerInCar(kCarRedSleeping) || !params->param2) + params->param2 = getState()->time; + + if (params->param2 >= getState()->time) + break; + } + + params->param2 = kTimeInvalid; + + setCallback(1); + setup_playSound("Mme5010"); + break; + + case kActionDefault: + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + params->param1 = getState()->time + 4500; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("671Ad", kObjectCompartmentD); + break; + + case 2: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityMertens, kEntityMmeBoutarel, kAction155604840); + setup_function53(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(53, Mertens, function53) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) { + UPDATE_PARAM(params->param4, getState()->timeTicks, 75); + + params->param1 = 0; + params->param2 = 0; + + getObjects()->update(kObjectCompartment4, kEntityMertens, kObjectLocation1, kCursorNormal, kCursorNormal); + } + + params->param4 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + if (params->param1) { + getObjects()->update(kObjectCompartment4, kEntityMertens, kObjectLocation1, kCursorNormal, kCursorNormal); + params->param1 = 0; + + setCallback(3); + setup_playSound(getSound()->justCheckingCath()); + } + + setCallback(savepoint.action == kActionKnock ? 4 : 5); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_5790); + break; + + case kActionDrawScene: + if (params->param2 || params->param1) { + params->param1 = 0; + params->param2 = 0; + params->param3 = 0; + + getObjects()->update(kObjectCompartment4, kEntityMertens, kObjectLocation1, kCursorHandKnock, kCursorHand); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("601ZD", kObjectCompartment4); + break; + + case 2: + getEntities()->clearSequences(kEntityMertens); + getData()->location = kLocationInsideCompartment; + getData()->entityPosition = kPosition_5790; + // Fallback to next case + + case 3: + getObjects()->update(kObjectCompartment4, kEntityMertens, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case 4: + case 5: + params->param3++; + + if (params->param3 == 1) { + getObjects()->update(kObjectCompartment4, kEntityMertens, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(6); + setup_playSound("Con5002"); + + } else if (params->param3 == 2) { + getObjects()->update(kObjectCompartment4, kEntityMertens, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(7); + setup_playSound("Con5002A"); + } + break; + + case 6: + params->param1 = 1; + getObjects()->update(kObjectCompartment4, kEntityMertens, kObjectLocation1, kCursorTalk, kCursorNormal); + break; + + case 7: + params->param2 = 1; + break; + } + break; + + case kAction135800432: + setup_nullfunction(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(54, Mertens) + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/mertens.h b/engines/lastexpress/entities/mertens.h new file mode 100644 index 0000000000..c6b800f1ff --- /dev/null +++ b/engines/lastexpress/entities/mertens.h @@ -0,0 +1,220 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_MERTENS_H +#define LASTEXPRESS_MERTENS_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Mertens : public Entity { +private: + // The type of action when entering Tyler compartment + enum MertensActionType { + kMertensActionNone = 0, + kMertensAction1 = 1, + kMertensAction2 = 2, + kMertensAction3 = 3 + }; + +public: + Mertens(LastExpressEngine *engine); + ~Mertens() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Handle meeting Coudert with the blooded jacket + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(bloodJacket, const char *sequence) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment and updates position/play animation + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment2, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + * @param entityPosition1 The entity position + * @param entityPosition1 The entity position to check + * + * @note We are not using the shared function due to too many differences + */ + DECLARE_FUNCTION_4(enterExitCompartment3, const char* sequence, ObjectIndex compartment, EntityPosition entityPosition1, EntityPosition entityPosition2) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound16, const char* filename) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION_1(function11, uint32 time) + + /** + * Says "Bonsoir" to another character + * + * @param entity The entity + */ + DECLARE_FUNCTION_1(bonsoir, EntityIndex entity) + DECLARE_FUNCTION_2(function13, bool, bool) + DECLARE_FUNCTION_1(function14, EntityIndex entity) + DECLARE_FUNCTION_1(function15, bool) + DECLARE_FUNCTION_1(function16, bool) + DECLARE_FUNCTION(function17) + DECLARE_FUNCTION(function18) + DECLARE_FUNCTION(function19) + DECLARE_FUNCTION(function20) + + /** + * ??? + * + * @param object1 First object index + * @param object2 Second object index + */ + DECLARE_FUNCTION_2(function21, ObjectIndex object1, ObjectIndex object2) + DECLARE_FUNCTION(function22) + DECLARE_FUNCTION(function23) + DECLARE_FUNCTION(function24) + DECLARE_FUNCTION(function25) + DECLARE_FUNCTION_1(function26, bool) + DECLARE_FUNCTION_1(tylerCompartment, MertensActionType action) + DECLARE_FUNCTION_1(function28, const char *soundName) + DECLARE_FUNCTION_2(function29, const char *soundName1, const char *soundName2) + DECLARE_FUNCTION_1(function30, MertensActionType action) + DECLARE_FUNCTION_1(function31, MertensActionType action) + DECLARE_FUNCTION(function32) + DECLARE_FUNCTION(function33) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + DECLARE_FUNCTION(function35) + DECLARE_FUNCTION(function36) + DECLARE_FUNCTION(function37) + DECLARE_FUNCTION(function38) + DECLARE_FUNCTION(function39) + DECLARE_FUNCTION(function40) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function42) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + DECLARE_FUNCTION(function44) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + DECLARE_FUNCTION(function46) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + DECLARE_FUNCTION(function48) + DECLARE_FUNCTION(function49) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function52) + DECLARE_FUNCTION(function53) + + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_MERTENS_H diff --git a/engines/lastexpress/entities/milos.cpp b/engines/lastexpress/entities/milos.cpp new file mode 100644 index 0000000000..30ed546106 --- /dev/null +++ b/engines/lastexpress/entities/milos.cpp @@ -0,0 +1,1077 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/milos.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Milos::Milos(LastExpressEngine *engine) : Entity(engine, kEntityMilos) { + ADD_CALLBACK_FUNCTION(Milos, reset); + ADD_CALLBACK_FUNCTION(Milos, draw); + ADD_CALLBACK_FUNCTION(Milos, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Milos, enterExitCompartment2); + ADD_CALLBACK_FUNCTION(Milos, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Milos, playSound); + ADD_CALLBACK_FUNCTION(Milos, playSound16); + ADD_CALLBACK_FUNCTION(Milos, savegame); + ADD_CALLBACK_FUNCTION(Milos, updateFromTime); + ADD_CALLBACK_FUNCTION(Milos, enterCompartmentDialog); + ADD_CALLBACK_FUNCTION(Milos, function11); + ADD_CALLBACK_FUNCTION(Milos, chapter1); + ADD_CALLBACK_FUNCTION(Milos, function13); + ADD_CALLBACK_FUNCTION(Milos, function14); + ADD_CALLBACK_FUNCTION(Milos, chapter1Handler); + ADD_CALLBACK_FUNCTION(Milos, function16); + ADD_CALLBACK_FUNCTION(Milos, function17); + ADD_CALLBACK_FUNCTION(Milos, function18); + ADD_CALLBACK_FUNCTION(Milos, chapter2); + ADD_CALLBACK_FUNCTION(Milos, chapter2Handler); + ADD_CALLBACK_FUNCTION(Milos, function21); + ADD_CALLBACK_FUNCTION(Milos, chapter3); + ADD_CALLBACK_FUNCTION(Milos, function23); + ADD_CALLBACK_FUNCTION(Milos, function24); + ADD_CALLBACK_FUNCTION(Milos, function25); + ADD_CALLBACK_FUNCTION(Milos, function26); + ADD_CALLBACK_FUNCTION(Milos, function27); + ADD_CALLBACK_FUNCTION(Milos, chapter4); + ADD_CALLBACK_FUNCTION(Milos, chapter4Handler); + ADD_CALLBACK_FUNCTION(Milos, function30); + ADD_CALLBACK_FUNCTION(Milos, function31); + ADD_CALLBACK_FUNCTION(Milos, function32); + ADD_CALLBACK_FUNCTION(Milos, chapter5); + ADD_CALLBACK_FUNCTION(Milos, chapter5Handler); + ADD_CALLBACK_FUNCTION(Milos, function35); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Milos, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Milos, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(3, Milos, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(4, Milos, enterExitCompartment2, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(5, Milos, callbackActionOnDirection) + Entity::callbackActionOnDirection(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(6, Milos, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(7, Milos, playSound16) + Entity::playSound(savepoint, false, SoundManager::kFlagDefault); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(8, Milos, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(9, Milos, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(10, Milos, enterCompartmentDialog, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + case kActionDefault: + if (getEntities()->updateEntity(kEntityMilos, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + + case kActionExcuseMeCath: + case kActionExcuseMe: + if (getEvent(kEventMilosTylerCompartmentDefeat)) { + // Robert saying: "Milos" + switch(rnd(3)) { + default: + case 0: + getSound()->playSound(kEntityPlayer, "CAT1014"); + break; + + case 1: + getSound()->playSound(kEntityPlayer, "CAT1014A"); + break; + + case 2: + getSound()->playSound(kEntityPlayer, "CAT1014B"); + break; + } + } else { + getSound()->excuseMeCath(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(11, Milos, function11, TimeValue) + switch (savepoint.action) { + default: + break; + + case kActionNone: + error("Milos: callback function 11 not implemented!"); + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update(kObjectCompartmentG, kEntityMilos, kObjectLocation3, kCursorNormal, kCursorNormal); + + if (params->param2) { + if (getInventory()->hasItem(kItemPassengerList)) { + setCallback(10); + setup_playSound((rnd(2) ? "CAT1504" : getSound()->wrongDoorCath())); + } else { + setCallback(11); + setup_playSound(getSound()->wrongDoorCath()); + } + } else { + if (savepoint.action == kActionKnock) { + setCallback(7); + setup_playSound("LIB012"); + } else { + setCallback(8); + setup_playSound("LIB013"); + } + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentG, kEntityMilos, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + + case kActionDrawScene: + if (params->param3 || params->param2) { + getObjects()->update(kObjectCompartmentG, kEntityMilos, kObjectLocation3, kCursorHandKnock, kCursorHand); + params->param3 = 0; + params->param2 = 0; + } + break; + + case kActionCallback: + switch(getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + setCallback(2); + setup_enterCompartmentDialog(kCarGreenSleeping, kPosition_8200); + break; + + case 2: + setCallback(3); + setup_function14(); + break; + + case 3: + if (getProgress().field_14 == 14) + getProgress().field_14 = 0; + + params->param6 = 1; + setCallback(4); + setup_enterCompartmentDialog(kCarRedSleeping, kPosition_3050); + break; + + case 4: + setCallback(5); + setup_enterExitCompartment("609Bg", kObjectCompartmentG); + break; + + case 5: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMilos); + getSavePoints()->push(kEntityMilos, kEntityVesna, kAction101687594); + getObjects()->update(kObjectCompartmentG, kEntityMilos, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + + case 6: + getObjects()->update(kObjectCompartmentG, kEntityMilos, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + + case 7: + case 8: + setCallback(9); + // Milos asking: "Yeah? Who is it?" + setup_playSound("MIL1117A"); + break; + + case 9: + getObjects()->update(kObjectCompartmentG, kEntityMilos, kObjectLocation3, kCursorTalk, kCursorNormal); + params->param2 = 1; + break; + + case 10: + case 11: + params->param2 = 0; + params->param3 = 1; + break; + + case 12: + getEntities()->drawSequenceLeft(kEntityMilos, "611Cg"); + getEntities()->enterCompartment(kEntityMilos, kObjectCompartmentG, true); + getSavePoints()->push(kEntityMilos, kEntityCoudert, kAction88652208); + break; + + case 13: + getEntities()->exitCompartment(kEntityMilos, kObjectCompartmentG, true); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMilos); + getObjects()->update(kObjectCompartmentG, kEntityMilos, kObjectLocation3, kCursorHandKnock, kCursorHand); + params->param5 = 0; + break; + + } + break; + + case kAction122865568: + getData()->location = kLocationOutsideCompartment; + setCallback(12); + setup_enterExitCompartment("611Bg", kObjectCompartmentG); + break; + + case kAction123852928: + params->param1 = 13; + setup_enterExitCompartment("611Dg", kObjectCompartmentG); + break; + + case kAction221683008: + params->param5 = 1; + getSavePoints()->push(kEntityMilos, kEntityCoudert, kAction123199584); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Milos, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentG, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject46, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + getData()->entityPosition = kPosition_4689; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + + getSavePoints()->addData(kEntityMilos, kAction157691176, 0); + getSavePoints()->addData(kEntityMilos, kAction208228224, 2); + getSavePoints()->addData(kEntityMilos, kAction259125998, 3); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Milos, function13) + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getSavePoints()->push(kEntityMilos, kEntityTables2, kActionDrawTablesWithChairs, "009E"); + getEntities()->clearSequences(kEntityVesna); + getEntities()->clearSequences(kEntityIvo); + getEntities()->clearSequences(kEntitySalko); + + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntitySalko, "009D5"); + getEntities()->drawSequenceRight(kEntityTables2, "009D4"); + getEntities()->drawSequenceRight(kEntityIvo, "009D3"); + getEntities()->drawSequenceRight(kEntityVesna, "009D2"); + getEntities()->drawSequenceRight(kEntityMilos, "009D1"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Milos, function14) + error("Milos: callback function 14 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Milos, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_SAVEPOINT(kTime1071000, params->param3, kEntityMilos, kEntityServers1, kAction223002560); + + if (getState()->time > kTime1089000 && getEntities()->isSomebodyInsideRestaurantOrSalon()) { + setup_function16(); + break; + } + + if (getEntities()->isPlayerPosition(kCarRestaurant, 61) && !params->param1) { + UPDATE_PARAM_PROC(params->param4, getState()->timeTicks, 45) + setCallback(1); + setup_draw("009C"); + break; + UPDATE_PARAM_PROC_END + } + + if (getEntities()->isPlayerPosition(kCarRestaurant, 70) && !params->param2) { + UPDATE_PARAM(params->param5, getState()->timeTicks, 45); + + setCallback(2); + setup_draw("009C"); + } + break; + + case kActionDefault: + getSavePoints()->push(kEntityMilos, kEntityTables2, kAction136455232); + getEntities()->drawSequenceLeft(kEntityMilos, "009A"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityMilos, "009A"); + params->param1 = 1; + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityMilos, "009A"); + params->param2 = 1; + break; + } + break; + } + +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Milos, function16) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) { + if (getEntities()->isDistanceBetweenEntities(kEntityMilos, kEntityVesna, 750) + || getEntities()->checkDistanceFromPosition(kEntityVesna, kPosition_3050, 500)) { + getSavePoints()->push(kEntityMilos, kEntityVesna, kAction123668192); + + setCallback(5); + setup_enterExitCompartment("611Ag", kObjectCompartmentG); + } + } + break; + + case kActionDefault: + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_function13(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityMilos, kEntityServers1, kAction269485588); + getSavePoints()->push(kEntityMilos, kEntityIvo, kAction125242096); + getEntities()->drawSequenceRight(kEntityMilos, "807DS"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityMilos); + + setCallback(2); + setup_callbackActionOnDirection(); + break; + + case 2: + getEntities()->clearSequences(kEntityMilos); + break; + + case 3: + if (getEntities()->isDistanceBetweenEntities(kEntityMilos, kEntityVesna, 750) + || getEntities()->checkDistanceFromPosition(kEntityVesna, kPosition_3050, 500)) { + getSavePoints()->push(kEntityMilos, kEntityVesna, kAction123668192); + + setCallback(4); + setup_enterExitCompartment("611Ag", kObjectCompartmentG); + } else { + params->param1 = 1; + + getEntities()->drawSequenceLeft(kEntityMilos, "609Dg"); + getEntities()->enterCompartment(kEntityMilos, kObjectCompartmentG, true); + } + break; + + case 4: + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMilos); + + setup_function17(); + break; + + case 5: + getEntities()->exitCompartment(kEntityMilos, kObjectCompartmentG, true); + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMilos); + + setup_function17(); + break; + } + break; + + case kAction135024800: + getSavePoints()->push(kEntityMilos, kEntityVesna, kAction204832737); + + setCallback(3); + setup_enterCompartmentDialog(kCarRedSleeping, kPosition_3050); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Milos, function17) + if (savepoint.action == kActionDefault) { + setCallback(1); + setup_function11(kTimeBedTime); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Milos, function18) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getEntities()->clearSequences(kEntityMilos); + getObjects()->update(kObjectCompartmentG, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Milos, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMilos); + + getData()->entityPosition = kPosition_4689; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + + getObjects()->update(kObjectCompartmentG, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject46, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Milos, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_540; + getData()->location = kLocationOutsideCompartment; + + getSavePoints()->push(kEntityMilos, kEntityVesna, kAction137165825); + break; + + case kActionDrawScene: + if (getEntities()->isPlayerInCar(kCarRedSleeping) && !getEntities()->isPlayerPosition(kCarRedSleeping, 1)) { + setCallback(1); + setup_enterCompartmentDialog(kCarRedSleeping, kPosition_3050); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("609Bg", kObjectCompartmentG); + break; + + case 2: + getEntities()->clearSequences(kEntityMilos); + + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + + getSavePoints()->push(kEntityMilos, kEntityVesna, kAction101687594); + + setup_function21(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Milos, function21) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param2, getState()->time, 4500); + + params->param1 = 1; + break; + + case kActionKnock: + getObjects()->update(kObjectCompartmentG, kEntityMilos, kObjectLocation3, kCursorNormal, kCursorNormal); + + setCallback(1); + setup_playSound("LIB012"); + break; + + case kActionOpenDoor: + getObjects()->update(kObjectCompartmentG, kEntityMilos, kObjectLocation3, kCursorNormal, kCursorNormal); + + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventMilosCompartmentVisitAugust); + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentG, kEntityMilos, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + + case kActionDrawScene: + if (!getEvent(kEventMilosCompartmentVisitAugust) + && !getEntities()->isInsideTrainCar(kEntityPlayer, kCarRedSleeping) + && params->param1) + setup_chapter2Handler(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_playSound("Mil1118"); + break; + + case 2: + getObjects()->update(kObjectCompartmentG, kEntityMilos, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + + case 3: + getAction()->playAnimation(kEventMilosCompartmentVisitAugust); + getScenes()->loadSceneFromPosition(kCarRedSleeping, 5); + getSavePoints()->push(kEntityMilos, kEntityVesna, kAction135024800); + + setCallback(4); + setup_function11(kTimeEnd); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Milos, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->events[kEventMilosCompartmentVisitAugust]) + setup_function24(); + else + setup_function23(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMilos); + + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentG, kEntityMilos, kObjectLocation3, kCursorHandKnock, kCursorHand); + + ENTITY_PARAM(0, 1) = 0; + ENTITY_PARAM(0, 4) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Milos, function23) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime2106000 && !params->param1) { + params->param1 = 1; + + setCallback(1); + setup_enterCompartmentDialog(kCarRedSleeping, kPosition_3050); + } + break; + + case kActionDefault: + getData()->entityPosition = kPosition_540; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + + getSavePoints()->push(kEntityMilos, kEntityVesna, kAction137165825); + break; + + case kActionDrawScene: + if (getEntities()->isPlayerInCar(kCarRedSleeping) + && !getEntities()->isPlayerPosition(kCarRedSleeping, 1)) { + setCallback(3); + setup_enterCompartmentDialog(kCarRedSleeping, kPosition_3050); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("609Bg", kObjectCompartmentG); + break; + + case 2: + case 4: + getEntities()->clearSequences(kEntityMilos); + getData()->location = kLocationInsideCompartment; + getSavePoints()->push(kEntityMilos, kEntityVesna, kAction101687594); + + setup_function24(); + break; + + case 3: + setCallback(4); + setup_enterExitCompartment("609Bg", kObjectCompartmentG); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Milos, function24) + error("Milos: callback function 24 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Milos, function25) + error("Milos: callback function 25 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(26, Milos, function26, TimeValue) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 < getState()->time && !params->param2) { + CALLBACK_ACTION(); + break; + } + + if (getEntities()->isPlayerInCar(kCarGreenSleeping) || getEntities()->isPlayerInCar(kCarRedSleeping)) { + if (getEntities()->isInsideTrainCar(kEntityPlayer, kCarGreenSleeping)) { + setCallback(2); + setup_function27(kCarGreenSleeping, kPosition_540); + } else { + setCallback(3); + setup_function27(kCarRedSleeping, kPosition_9460); + } + } + break; + + case kActionDefault: + ENTITY_PARAM(0, 2) = 0; + + setCallback(1); + setup_function27(kCarRedSleeping, kPosition_540); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (ENTITY_PARAM(0, 2)) { + CALLBACK_ACTION(); + break; + } + + getEntities()->clearSequences(kEntityMilos); + break; + + case 2: + case 3: + if (ENTITY_PARAM(0, 2)) { + CALLBACK_ACTION(); + break; + } + + getEntities()->clearSequences(kEntityMilos); + + setCallback(4); + setup_updateFromTime(450); + break; + + case 4: + setCallback(5); + setup_function27(kCarRedSleeping, kPosition_540); + break; + + case 5: + if (ENTITY_PARAM(0, 2)) { + CALLBACK_ACTION(); + break; + } + + getEntities()->clearSequences(kEntityMilos); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(27, Milos, function27, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->updateEntity(kEntityMilos, (CarIndex)params->param1, (EntityPosition)params->param2)) { + CALLBACK_ACTION(); + break; + } + + if (getEntities()->isDistanceBetweenEntities(kEntityMilos, kEntityPlayer, 1000) + && !getEntities()->isInGreenCarEntrance(kEntityPlayer) + && !getEntities()->isInsideCompartments(kEntityPlayer) + && !getEntities()->checkFields10(kEntityPlayer)) { + if (getData()->car == kCarRedSleeping || getData()->car == kCarGreenSleeping) { + ENTITY_PARAM(0, 2) = 1; + + CALLBACK_ACTION(); + } + } + break; + + case kActionDefault: + if (getEntities()->updateEntity(kEntityMilos, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Milos, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMilos); + + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Milos, chapter4Handler) +#define TIME_CHECK_PLAYSOUND_MILOS(timeValue, parameter, sound) \ + if (getState()->time > timeValue && !parameter) { \ + parameter = 1; \ + getSound()->playSound(kEntityMilos, sound); \ + if (getEntities()->isDistanceBetweenEntities(kEntityMilos, kEntityPlayer, 2000)) \ + getProgress().field_94 = 1; \ + break; \ + } + + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) + break; + + if (params->param2) { + setup_function30(); + break; + } + + TIME_CHECK_PLAYSOUND_MILOS(kTime2356200, params->param3, "Mil4013"); + + TIME_CHECK_PLAYSOUND_MILOS(kTime2360700, params->param4, "Mil4014"); + + TIME_CHECK_PLAYSOUND_MILOS(kTime2370600, params->param5, "Mil4015"); + + TIME_CHECK_SAVEPOINT(kTime2407500, params->param6, kEntityMilos, kEntityVesna, kAction55996766); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getEntities()->drawSequenceLeft(kEntityMilos, "611Cg"); + getEntities()->enterCompartment(kEntityMilos, kObjectCompartmentG, true); + getSavePoints()->push(kEntityMilos, kEntityCoudert, kAction88652208); + break; + + case 2: + getEntities()->exitCompartment(kEntityMilos, kObjectCompartmentG); + + getData()->location = kLocationInsideCompartment; + getData()->entityPosition = kPosition_3050; + + getEntities()->clearSequences(kEntityMilos); + + params->param1 = 0; + break; + } + break; + + case kAction122865568: + setCallback(1); + setup_enterExitCompartment("611Bg", kObjectCompartmentG); + break; + + case kAction123852928: + setCallback(2); + setup_enterExitCompartment("611Dg", kObjectCompartmentG); + break; + + case kAction135600432: + params->param2 = 1; + break; + + case kAction221683008: + if (getSound()->isBuffered(kEntityMilos)) + getSound()->processEntry(kEntityMilos); + + params->param1 = 1; + getSavePoints()->push(kEntityMilos, kEntityCoudert, kAction123199584); + break; + } + +#undef TIME_CHECK_PLAYSOUND_MILOS +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Milos, function30) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setCallback(1); + setup_function11(kTime2410200); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityMilos, kEntityIvo, kAction55996766); + + setCallback(2); + setup_function11(kTime2412000); + break; + + case 2: + getSavePoints()->push(kEntityMilos, kEntitySalko, kAction55996766); + + setCallback(3); + setup_function11(kTime2415600); + break; + + case 3: + setup_function31(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, Milos, function31) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setCallback(1); + setup_enterExitCompartment("609CG", kObjectCompartmentG); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getObjects()->update(kObjectCompartmentG, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + setCallback(2); + setup_enterCompartmentDialog(kCarGreenSleeping, kPosition_540); + break; + + case 2: + setup_function32(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Milos, function32) + if (savepoint.action == kActionDefault) { + getEntities()->clearSequences(kEntityMilos); + getObjects()->update(kObjectCompartmentG, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_540; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarCoalTender; + getData()->inventoryItem = kItemNone; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Milos, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMilos); + + getData()->entityPosition = kPosition_540; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarCoalTender; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(34, Milos, chapter5Handler) + error("Milos: callback function 34 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(35, Milos, function35) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityMilos); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/milos.h b/engines/lastexpress/entities/milos.h new file mode 100644 index 0000000000..4bbafaaaa7 --- /dev/null +++ b/engines/lastexpress/entities/milos.h @@ -0,0 +1,175 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_MILOS_H +#define LASTEXPRESS_MILOS_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Milos : public Entity { +public: + Milos(LastExpressEngine *engine); + ~Milos() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment and updates position/play animation + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment2, const char* sequence, ObjectIndex compartment) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound16, const char* filename) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + DECLARE_FUNCTION_2(enterCompartmentDialog, CarIndex car, EntityPosition entityPosition) + DECLARE_FUNCTION_1(function11, TimeValue timeValue) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + DECLARE_FUNCTION(function13) + DECLARE_FUNCTION(function14) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function16) + DECLARE_FUNCTION(function17) + DECLARE_FUNCTION(function18) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + DECLARE_FUNCTION(function21) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + DECLARE_FUNCTION(function23) + DECLARE_FUNCTION(function24) + DECLARE_FUNCTION(function25) + DECLARE_FUNCTION_1(function26, TimeValue timeValue) + DECLARE_FUNCTION_2(function27, CarIndex car, EntityPosition entityPosition) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function30) + DECLARE_FUNCTION(function31) + DECLARE_FUNCTION(function32) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function35) +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_MILOS_H diff --git a/engines/lastexpress/entities/mmeboutarel.cpp b/engines/lastexpress/entities/mmeboutarel.cpp new file mode 100644 index 0000000000..ce9ebf3abd --- /dev/null +++ b/engines/lastexpress/entities/mmeboutarel.cpp @@ -0,0 +1,939 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/mmeboutarel.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +MmeBoutarel::MmeBoutarel(LastExpressEngine *engine) : Entity(engine, kEntityMmeBoutarel) { + ADD_CALLBACK_FUNCTION(MmeBoutarel, reset); + ADD_CALLBACK_FUNCTION(MmeBoutarel, playSound); + ADD_CALLBACK_FUNCTION(MmeBoutarel, draw); + ADD_CALLBACK_FUNCTION(MmeBoutarel, updateFromTime); + ADD_CALLBACK_FUNCTION(MmeBoutarel, enterExitCompartment); + ADD_CALLBACK_FUNCTION(MmeBoutarel, enterExitCompartment2); + ADD_CALLBACK_FUNCTION(MmeBoutarel, updateEntity); + ADD_CALLBACK_FUNCTION(MmeBoutarel, function8); + ADD_CALLBACK_FUNCTION(MmeBoutarel, function9); + ADD_CALLBACK_FUNCTION(MmeBoutarel, chapter1); + ADD_CALLBACK_FUNCTION(MmeBoutarel, function11); + ADD_CALLBACK_FUNCTION(MmeBoutarel, chapter1Handler); + ADD_CALLBACK_FUNCTION(MmeBoutarel, function13); + ADD_CALLBACK_FUNCTION(MmeBoutarel, function14); + ADD_CALLBACK_FUNCTION(MmeBoutarel, function15); + ADD_CALLBACK_FUNCTION(MmeBoutarel, function16); + ADD_CALLBACK_FUNCTION(MmeBoutarel, chapter2); + ADD_CALLBACK_FUNCTION(MmeBoutarel, chapter2Handler); + ADD_CALLBACK_FUNCTION(MmeBoutarel, function19); + ADD_CALLBACK_FUNCTION(MmeBoutarel, chapter3); + ADD_CALLBACK_FUNCTION(MmeBoutarel, chapter3Handler); + ADD_CALLBACK_FUNCTION(MmeBoutarel, chapter4); + ADD_CALLBACK_FUNCTION(MmeBoutarel, chapter4Handler); + ADD_CALLBACK_FUNCTION(MmeBoutarel, function24); + ADD_CALLBACK_FUNCTION(MmeBoutarel, function25); + ADD_CALLBACK_FUNCTION(MmeBoutarel, chapter5); + ADD_CALLBACK_FUNCTION(MmeBoutarel, chapter5Handler); + ADD_CALLBACK_FUNCTION(MmeBoutarel, function28); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, MmeBoutarel, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, MmeBoutarel, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(3, MmeBoutarel, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(4, MmeBoutarel, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(5, MmeBoutarel, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(6, MmeBoutarel, enterExitCompartment2, ObjectIndex) + Entity::enterExitCompartment(savepoint, kPosition_5790, kPosition_6130, kCarRedSleeping, kObjectCompartmentD, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(7, MmeBoutarel, updateEntity, CarIndex, EntityPosition) + if (savepoint.action == kActionExcuseMeCath) { + getInventory()->hasItem(kItemPassengerList) ? getSound()->playSound(kEntityPlayer, "CAT1021") : getSound()->excuseMeCath(); + + return; + } + + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(8, MmeBoutarel, function8) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param4 && params->param5) { + getSavePoints()->push(kEntityMmeBoutarel, kEntityCoudert, kAction125499160); + + if (!getEntities()->isPlayerPosition(kCarRedSleeping, 2)) + getData()->entityPosition = kPosition_2088; + + CALLBACK_ACTION(); + } + break; + + case kActionEndSound: + params->param5 = 1; + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "606U"); + getSavePoints()->push(kEntityMmeBoutarel, kEntityCoudert, kAction169557824); + break; + + case kAction155853632: + params->param4 = 1; + break; + + case kAction202558662: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "606L"); + getSound()->playSound(kEntityMmeBoutarel, (char *)¶ms->seq1); + + if (getEntities()->hasValidFrame(kEntityMmeBoutarel) || getEntities()->isDistanceBetweenEntities(kEntityMmeBoutarel, kEntityPlayer, 2000)) { + if (getProgress().chapter == kChapter1) + getProgress().field_A8 = 1; + else if (getProgress().chapter == kChapter3) + getProgress().field_A4 = 1; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, MmeBoutarel, function9) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) { + getData()->entityPosition = getEntityData(kEntityBoutarel)->entityPosition; + getData()->location = getEntityData(kEntityBoutarel)->location; + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject51, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + setCallback(1); + setup_enterExitCompartment("606Rd", kObjectCompartmentD); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + getEntities()->clearSequences(kEntityMmeBoutarel); + getSavePoints()->push(kEntityMmeBoutarel, kEntityBoutarel, kAction203520448); + break; + + case 3: + if (getEntities()->isInsideCompartment(kEntityFrancois, kCarRedSleeping, kPosition_5790)) { + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorNormal); + + setCallback(4); + setup_enterExitCompartment2("606Ad", kObjectCompartmentD); + } else { + params->param1 = 1; + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "606Md"); + getEntities()->enterCompartment(kEntityMmeBoutarel, kObjectCompartmentD, true); + } + break; + + case 4: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMmeBoutarel); + + CALLBACK_ACTION(); + break; + + case 5: + getEntities()->exitCompartment(kEntityMmeBoutarel, kObjectCompartmentD, true); + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityMmeBoutarel); + + CALLBACK_ACTION(); + break; + } + break; + + case kAction100901266: + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_5790); + break; + + case kAction100957716: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorNormal); + + setCallback(5); + setup_enterExitCompartment2("606Ad", kObjectCompartmentD); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, MmeBoutarel, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getSavePoints()->addData(kEntityMmeBoutarel, kAction242526416, 0); + + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getObjects()->update(kObject51, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, MmeBoutarel, function11) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param2 == kTimeInvalid) + break; + + if (params->param1 >= getState()->time) { + if (!getEntities()->isDistanceBetweenEntities(kEntityMmeBoutarel, kEntityPlayer, 1000) || !params->param2) + params->param2 = getState()->time + 150; + + if (params->param2 >= getState()->time) + break; + } + + params->param2 = kTimeInvalid; + + setCallback(1); + setup_playSound("MME1040"); + break; + + case kActionDefault: + params->param1 = getState()->time + 1800; + getObjects()->update(kObjectCompartmentD, kEntityMmeBoutarel, kObjectLocation1, kCursorNormal, kCursorNormal); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_playSound("MME1040A"); + break; + + case 2: + setCallback(3); + setup_playSound("MME1041"); + break; + + case 3: + setCallback(4); + setup_updateFromTime(900); + break; + + case 4: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, MmeBoutarel, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + params->param1 = 1; + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "501"); + break; + + case kActionDrawScene: + if (getEntities()->isPlayerPosition(kCarRedSleeping, 44)) { + setCallback(1); + setup_draw("502B"); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "502A"); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "606Qd"); + getEntities()->enterCompartment(kEntityMmeBoutarel, kObjectCompartmentD, true); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + params->param1 = 1; + getEntities()->clearSequences(kEntityMmeBoutarel); + setup_function13(); + break; + } + break; + + case kAction102484312: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "501"); + params->param1 = 1; + break; + + case kAction134289824: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "502A"); + params->param1 = 0; + break; + + case kAction168986720: + getSavePoints()->push(kEntityMmeBoutarel, kEntityFrancois, kAction102752636); + getSound()->playSound(kEntityMmeBoutarel, "MME1036"); + getEntities()->exitCompartment(kEntityMmeBoutarel, kObjectCompartmentD, true); + + setCallback(3); + setup_enterExitCompartment("606Fd", kObjectCompartmentD); + break; + + case kAction202221040: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + getData()->location = kLocationOutsideCompartment; + + getSound()->playSound(kEntityMmeBoutarel, "MME1035A"); + + if (getEntities()->hasValidFrame(kEntityMmeBoutarel) || getEntities()->isDistanceBetweenEntities(kEntityMmeBoutarel, kEntityPlayer, 2000) ) + getProgress().field_AC = 1; + + setCallback(2); + setup_enterExitCompartment("606Ed", kObjectCompartmentD); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, MmeBoutarel, function13) + error("MmeBoutarel: callback function 13 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, MmeBoutarel, function14) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("606Dd", kObjectCompartmentD); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getObjects()->update(kObject51, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "503"); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "503"); + + setCallback(3); + setup_playSound("MRB1080"); + break; + + case 3: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(4); + setup_enterExitCompartment("606Cd", kObjectCompartmentD); + break; + + case 4: + getEntities()->clearSequences(kEntityMmeBoutarel); + + setup_function15(); + break; + } + break; + + case kAction101107728: + setCallback(2); + setup_function9(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, MmeBoutarel, function15) + error("MmeBoutarel: callback function 15 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, MmeBoutarel, function16) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject51, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getEntities()->clearSequences(kEntityMmeBoutarel); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, MmeBoutarel, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMmeBoutarel); + + getData()->entityPosition = kPosition_4689; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject51, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject43, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, MmeBoutarel, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getEntities()->isInsideCompartment(kEntityFrancois, kCarRedSleeping, kPosition_5790)) { + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorNormal); + + setCallback(2); + setup_enterExitCompartment2("606Ad", kObjectCompartmentD); + } else { + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "606Md"); + getEntities()->enterCompartment(kEntityMmeBoutarel, kObjectCompartmentD, true); + } + break; + + case 2: + case 3: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getData()->location = kLocationInsideCompartment; + setup_function19(); + break; + } + break; + + case kAction100901266: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_5790); + break; + + case kAction100957716: + getEntities()->exitCompartment(kEntityMmeBoutarel, kObjectCompartmentD, true); + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorNormal); + + setCallback(3); + setup_enterExitCompartment2("606Ad", kObjectCompartmentD); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, MmeBoutarel, function19) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isPlayerPosition(kCarRedSleeping, 44) && !params->param2) { + if (params->param1) { + setCallback(1); + setup_draw("502B"); + } else { + params->param1 = 1; + } + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getObjects()->update(kObject51, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + params->param2 = 1; + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "501"); + break; + + case kActionCallback: + if (getCallback() == 1) { + if (getEntities()->isPlayerPosition(kCarRedSleeping , 44)) + getScenes()->loadSceneFromPosition(kCarRedSleeping, 11); + } + break; + + case kAction102484312: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "501"); + params->param2 = 1; + break; + + case kAction134289824: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "502A"); + params->param2 = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, MmeBoutarel, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMmeBoutarel); + + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, MmeBoutarel, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (ENTITY_PARAM(0, 1) && params->param2 != kTimeInvalid) { + + if (getState()->time <= kTime2038500) { + if (!getEntities()->isPlayerInCar(kCarRedSleeping) + || !params->param1 + || getSound()->isBuffered("FRA2012") + || getSound()->isBuffered("FRA2010") + ||!params->param2) + params->param2 = getState()->time; + + if (params->param2 >= getState()->time) + break; + } + + params->param2 = kTimeInvalid; + + getSavePoints()->push(kEntityMmeBoutarel, kEntityFrancois, kAction189872836); + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + setCallback(1); + setup_enterExitCompartment("606Cd", kObjectCompartmentD); + } + break; + + case kActionDefault: + params->param1 = 1; + + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getObjects()->update(kObject51, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject43, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "501"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("606Rd", kObjectCompartmentD); + break; + + case 2: + getData()->location = kLocationOutsideCompartment; + + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 3: + setCallback(4); + setup_function8("MME3001"); + break; + + case 4: + setCallback(5); + setup_updateEntity(kCarRedSleeping, kPosition_5790); + break; + + case 5: + setCallback(6); + setup_enterExitCompartment2("606Td", kObjectCompartmentD); + break; + + case 6: + getEntities()->clearSequences(kEntityMmeBoutarel); + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + setCallback(7); + setup_updateFromTime(150); + break; + + case 7: + setCallback(8); + setup_enterExitCompartment("606Dd", kObjectCompartmentD); + break; + + case 8: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "501"); + getSavePoints()->push(kEntityMmeBoutarel, kEntityFrancois, kAction190390860); + break; + + case 9: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "501"); + params->param1 = 1; + break; + } + break; + + case kAction101107728: + setCallback(9); + setup_function9(); + break; + + case kAction102484312: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "501"); + params->param1 = 1; + break; + + case kAction134289824: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "502A"); + params->param1 = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, MmeBoutarel, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMmeBoutarel); + + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, MmeBoutarel, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) { + UPDATE_PARAM(params->param2, getState()->time, 900); + + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation1, kCursorKeepValue, kCursorKeepValue); + + setCallback(1); + setup_enterExitCompartment("606Cd", kObjectCompartmentD); + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getObjects()->update(kObject51, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "501"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->clearSequences(kEntityMmeBoutarel); + setup_function24(); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityMmeBoutarel, "501"); + params->param1 = 1; + break; + } + break; + + case kAction101107728: + setCallback(2); + setup_function9(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, MmeBoutarel, function24) + error("MmeBoutarel: callback function 24 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, MmeBoutarel, function25) + if (savepoint.action == kActionDefault) { + getEntities()->clearSequences(kEntityMmeBoutarel); + + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->update(kObjectCompartmentD, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject51, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, MmeBoutarel, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityMmeBoutarel); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, MmeBoutarel, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_function28(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, MmeBoutarel, function28) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) { + UPDATE_PARAM(params->param3, getState()->timeTicks, 75); + + params->param1 = 0; + params->param2 = 1; + + getObjects()->update(kObjectCompartmentD, kEntityMmeBoutarel, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject51, kEntityMmeBoutarel, kObjectLocation1, kCursorNormal, kCursorNormal); + } + + params->param3 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + if (params->param1) { + getObjects()->update(kObjectCompartmentD, kEntityMmeBoutarel, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject51, kEntityMmeBoutarel, kObjectLocation1, kCursorNormal, kCursorNormal); + params->param1 = 0; + + setCallback(1); + setup_playSound(getSound()->justCheckingCath()); + break; + } + + setCallback(savepoint.action == kActionKnock ? 2 : 3); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + break; + + case kActionDefault: + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_5790; + getData()->location = kLocationInsideCompartment; + + getEntities()->clearSequences(kEntityMmeBoutarel); + break; + + case kActionDrawScene: + if (params->param1 || params->param2) { + params->param1 = 0; + params->param2 = 0; + + getObjects()->update(kObjectCompartmentD, kEntityMmeBoutarel, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject51, kEntityMmeBoutarel, kObjectLocation1, kCursorHandKnock, kCursorHand); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentD, kEntityMmeBoutarel, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject51, kEntityMmeBoutarel, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case 2: + case 3: + getObjects()->update(kObjectCompartmentD, kEntityMmeBoutarel, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject51, kEntityMmeBoutarel, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(4); + setup_playSound("Mme5001"); + break; + + case 4: + params->param1 = 1; + getObjects()->update(kObjectCompartmentD, kEntityMmeBoutarel, kObjectLocation1, kCursorTalk, kCursorNormal); + getObjects()->update(kObject51, kEntityMmeBoutarel, kObjectLocation1, kCursorTalk, kCursorNormal); + break; + } + break; + + case kAction135800432: + setup_nullfunction(); + break; + + case kAction155604840: + getObjects()->update(kObjectCompartmentD, kEntityMmeBoutarel, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject51, kEntityMmeBoutarel, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(29, MmeBoutarel) + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/mmeboutarel.h b/engines/lastexpress/entities/mmeboutarel.h new file mode 100644 index 0000000000..548a0635cb --- /dev/null +++ b/engines/lastexpress/entities/mmeboutarel.h @@ -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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_MMEBOUTAREL_H +#define LASTEXPRESS_MMEBOUTAREL_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class MmeBoutarel : public Entity { +public: + MmeBoutarel(LastExpressEngine *engine); + ~MmeBoutarel() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment and updates position/play animation + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment2, const char* sequence, ObjectIndex compartment) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION_1(function8, const char *soundName) + + DECLARE_FUNCTION(function9) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + DECLARE_FUNCTION(function11) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function13) + DECLARE_FUNCTION(function14) + DECLARE_FUNCTION(function15) + DECLARE_FUNCTION(function16) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + DECLARE_FUNCTION(function19) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function24) + DECLARE_FUNCTION(function25) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function28) + + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_MMEBOUTAREL_H diff --git a/engines/lastexpress/entities/pascale.cpp b/engines/lastexpress/entities/pascale.cpp new file mode 100644 index 0000000000..dfb0854eda --- /dev/null +++ b/engines/lastexpress/entities/pascale.cpp @@ -0,0 +1,1232 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/pascale.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Pascale::Pascale(LastExpressEngine *engine) : Entity(engine, kEntityPascale) { + ADD_CALLBACK_FUNCTION(Pascale, draw); + ADD_CALLBACK_FUNCTION(Pascale, callbackActionRestaurantOrSalon); + ADD_CALLBACK_FUNCTION(Pascale, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Pascale, updateFromTime); + ADD_CALLBACK_FUNCTION(Pascale, updatePosition); + ADD_CALLBACK_FUNCTION(Pascale, playSound); + ADD_CALLBACK_FUNCTION(Pascale, draw2); + ADD_CALLBACK_FUNCTION(Pascale, welcomeSophieAndRebecca); + ADD_CALLBACK_FUNCTION(Pascale, sitSophieAndRebecca); + ADD_CALLBACK_FUNCTION(Pascale, welcomeCath); + ADD_CALLBACK_FUNCTION(Pascale, function11); + ADD_CALLBACK_FUNCTION(Pascale, chapter1); + ADD_CALLBACK_FUNCTION(Pascale, getMessageFromAugustToTyler); + ADD_CALLBACK_FUNCTION(Pascale, sitAnna); + ADD_CALLBACK_FUNCTION(Pascale, welcomeAnna); + ADD_CALLBACK_FUNCTION(Pascale, serveTatianaVassili); + ADD_CALLBACK_FUNCTION(Pascale, chapter1Handler); + ADD_CALLBACK_FUNCTION(Pascale, function18); + ADD_CALLBACK_FUNCTION(Pascale, function19); + ADD_CALLBACK_FUNCTION(Pascale, chapter2); + ADD_CALLBACK_FUNCTION(Pascale, chapter3); + ADD_CALLBACK_FUNCTION(Pascale, chapter3Handler); + ADD_CALLBACK_FUNCTION(Pascale, function23); + ADD_CALLBACK_FUNCTION(Pascale, welcomeAbbot); + ADD_CALLBACK_FUNCTION(Pascale, chapter4); + ADD_CALLBACK_FUNCTION(Pascale, chapter4Handler); + ADD_CALLBACK_FUNCTION(Pascale, function27); + ADD_CALLBACK_FUNCTION(Pascale, messageFromAnna); + ADD_CALLBACK_FUNCTION(Pascale, function29); + ADD_CALLBACK_FUNCTION(Pascale, function30); + ADD_CALLBACK_FUNCTION(Pascale, chapter5); + ADD_CALLBACK_FUNCTION(Pascale, chapter5Handler); + ADD_CALLBACK_FUNCTION(Pascale, function33); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(1, Pascale, draw) + Entity::draw(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(2, Pascale, callbackActionRestaurantOrSalon) + Entity::callbackActionRestaurantOrSalon(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(3, Pascale, callbackActionOnDirection) + if (savepoint.action == kActionExcuseMeCath) { + if (!params->param1) { + getSound()->excuseMe(kEntityPascale); + params->param1 = 1; + } + + return; + } + + Entity::callbackActionOnDirection(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(4, Pascale, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(5, Pascale, updatePosition) + Entity::updatePosition(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(6, Pascale, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(7, Pascale, draw2) + Entity::draw2(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Pascale, welcomeSophieAndRebecca) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_850; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("901"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + switch (getProgress().chapter) { + default: + break; + + case kChapter1: + getSound()->playSound(kEntityPascale, "REB1198", SoundManager::kFlagInvalid, 30); + break; + + case kChapter3: + getSound()->playSound(kEntityPascale, "REB3001", SoundManager::kFlagInvalid, 30); + break; + + case kChapter4: + getSound()->playSound(kEntityPascale, "REB4001", SoundManager::kFlagInvalid, 30); + break; + } + + setCallback(2); + setup_sitSophieAndRebecca(); + break; + + case 2: + getSavePoints()->push(kEntityPascale, kEntityRebecca, kAction157370960); + + setCallback(3); + setup_draw("905"); + break; + + case 3: + getEntities()->clearSequences(kEntityPascale); + getData()->entityPosition = kPosition_5900; + ENTITY_PARAM(0, 4) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Pascale, sitSophieAndRebecca) + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityPascale, "012C1"); + getEntities()->drawSequenceLeft(kEntityRebecca, "012C2"); + getEntities()->drawSequenceLeft(kEntityTables3, "012C3"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Pascale, welcomeCath) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 && !getSound()->isBuffered(kEntityPascale)) + getEntities()->updatePositionExit(kEntityPascale, kCarRestaurant, 64); + break; + + case kActionExitCompartment: + if (!params->param2) { + params->param2 = 1; + + getSound()->playSound(kEntityPascale, "HED1001A"); + getSound()->playSound(kEntityPlayer, "LIB004"); + + getScenes()->loadSceneFromPosition(kCarRestaurant, 69); + } + + CALLBACK_ACTION(); + break; + + case kAction4: + if (!params->param1) { + params->param1 = 1; + getSound()->playSound(kEntityPascale, "HED1001"); + } + break; + + case kActionDefault: + getEntities()->updatePositionEnter(kEntityPascale, kCarRestaurant, 64); + getEntities()->drawSequenceRight(kEntityPascale, "035A"); + break; + + case kActionDrawScene: + if (params->param1 && getEntities()->isPlayerPosition(kCarRestaurant, 64)) { + getSound()->playSound(kEntityPascale, "HED1001A"); + getSound()->playSound(kEntityPlayer, "LIB004"); + + getScenes()->loadSceneFromPosition(kCarRestaurant, 69); + + CALLBACK_ACTION(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Pascale, function11) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + getSavePoints()->push(kEntityPascale, kEntityAugust, kAction168046720); + getSavePoints()->push(kEntityPascale, kEntityAnna, kAction168046720); + getSavePoints()->push(kEntityPascale, kEntityAlexei, kAction168046720); + getEntities()->updatePositionEnter(kEntityPascale, kCarRestaurant, 55); + + setCallback(1); + setup_welcomeCath(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityPascale, kEntityAugust, kAction168627977); + getSavePoints()->push(kEntityPascale, kEntityAnna, kAction168627977); + getSavePoints()->push(kEntityPascale, kEntityAlexei, kAction168627977); + getEntities()->updatePositionExit(kEntityPascale, kCarRestaurant, 55); + + setCallback(2); + setup_draw("905"); + break; + + case 2: + getEntities()->clearSequences(kEntityPascale); + getData()->entityPosition = kPosition_5900; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Pascale, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter1Handler(); + break; + + case kActionDefault: + getSavePoints()->addData(kEntityPascale, kAction239072064, 0); + getSavePoints()->addData(kEntityPascale, kAction257489762, 2); + getSavePoints()->addData(kEntityPascale, kAction207769280, 6); + getSavePoints()->addData(kEntityPascale, kAction101824388, 7); + getSavePoints()->addData(kEntityPascale, kAction136059947, 8); + getSavePoints()->addData(kEntityPascale, kAction223262556, 1); + getSavePoints()->addData(kEntityPascale, kAction269479296, 3); + getSavePoints()->addData(kEntityPascale, kAction352703104, 4); + getSavePoints()->addData(kEntityPascale, kAction352768896, 5); + getSavePoints()->addData(kEntityPascale, kAction191604416, 10); + getSavePoints()->addData(kEntityPascale, kAction190605184, 11); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Pascale, getMessageFromAugustToTyler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("902"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (!ENTITY_PARAM(1, 3)) { + getEntities()->drawSequenceLeft(kEntityPascale, "010E"); + getEntities()->drawSequenceLeft(kEntityAugust, "BLANK"); + + setCallback(2); + setup_playSound("AUG1001"); + break; + } + + setCallback(3); + setup_draw("905"); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityPascale, "010B"); + + setCallback(3); + setup_draw("905"); + break; + + case 3: + getData()->entityPosition = kPosition_5900; + getEntities()->clearSequences(kEntityPascale); + getSavePoints()->push(kEntityPascale, kEntityVerges, kActionDeliverMessageToTyler); + ENTITY_PARAM(0, 1) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Pascale, sitAnna) + switch (savepoint.action) { + default: + break; + + case kActionExitCompartment: + getEntities()->updatePositionExit(kEntityPascale, kCarRestaurant, 62); + + CALLBACK_ACTION(); + break; + + case kActionDefault: + getEntities()->drawSequenceRight(kEntityTables0, "001C3"); + getEntities()->drawSequenceRight(kEntityAnna, "001C2"); + getEntities()->drawSequenceRight(kEntityPascale, "001C1"); + + getEntities()->updatePositionEnter(kEntityPascale, kCarRestaurant, 62); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Pascale, welcomeAnna) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("901"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSound()->playSound(kEntityPascale, "ANN1047"); + + setCallback(2); + setup_sitAnna(); + break; + + case 2: + getSavePoints()->push(kEntityPascale, kEntityAnna, kAction157370960); + + setCallback(3); + setup_draw("904"); + break; + + case 3: + getEntities()->clearSequences(kEntityPascale); + getData()->entityPosition = kPosition_5900; + ENTITY_PARAM(0, 2) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Pascale, serveTatianaVassili) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("903"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityPascale, kEntityTatiana, kAction122358304); + getEntities()->drawSequenceLeft(kEntityPascale, "014B"); + getEntities()->updatePositionEnter(kEntityPascale, kCarRestaurant, 67); + + if (getSound()->isBuffered("TAT1069A")) + getSound()->processEntry("TAT1069A"); + else if (getSound()->isBuffered("TAT1069B")) + getSound()->processEntry("TAT1069B"); + + setCallback(2); + setup_playSound("TAT1066"); + break; + + case 2: + getEntities()->updatePositionExit(kEntityPascale, kCarRestaurant, 67); + getSavePoints()->push(kEntityPascale, kEntityTatiana, kAction122288808); + + setCallback(3); + setup_draw("906"); + break; + + case 3: + getEntities()->clearSequences(kEntityPascale); + getData()->entityPosition = kPosition_5900; + ENTITY_PARAM(0, 3) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Pascale, chapter1Handler) +switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param2) { + if (getEntities()->isPlayerPosition(kCarRestaurant, 69) + || getEntities()->isPlayerPosition(kCarRestaurant, 70) + || getEntities()->isPlayerPosition(kCarRestaurant, 71)) + params->param2 = 1; + + if (!params->param2 && getEntities()->isPlayerPosition(kCarRestaurant, 61)) + params->param1 = 1; + } + + if (!getEntities()->isInKitchen(kEntityPascale)) + break; + + if (ENTITY_PARAM(0, 5) && ENTITY_PARAM(0, 6)) { + setup_function18(); + break; + } + + if (!getEntities()->isSomebodyInsideRestaurantOrSalon()) + goto label_callback3; + + if (params->param1 && !params->param2 && getEntities()->isPlayerPosition(kCarRestaurant, 61)) { + setCallback(1); + setup_function11(); + break; + } + +label_callback1: + if (ENTITY_PARAM(0, 1) && !ENTITY_PARAM(1, 3)) { + setCallback(2); + setup_getMessageFromAugustToTyler(); + break; + } + +label_callback2: + if (ENTITY_PARAM(0, 3)) { + setCallback(3); + setup_serveTatianaVassili(); + break; + } + +label_callback3: + if (ENTITY_PARAM(0, 2)) { + setCallback(4); + setup_welcomeAnna(); + break; + } + +label_callback4: + if (ENTITY_PARAM(0, 4)) { + setCallback(5); + setup_welcomeSophieAndRebecca(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + params->param1 = 0; + params->param2 = 1; + goto label_callback1; + + case 2: + goto label_callback2; + + case 3: + goto label_callback3; + + case 4: + goto label_callback4; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Pascale, function18) + if (savepoint.action != kActionNone) + return; + + if (getState()->time > kTime1242000 && !params->param1) { + params->param1 = 1; + + getSavePoints()->push(kEntityPascale, kEntityServers0, kAction101632192); + getSavePoints()->push(kEntityPascale, kEntityServers1, kAction101632192); + getSavePoints()->push(kEntityPascale, kEntityCooks, kAction101632192); + getSavePoints()->push(kEntityPascale, kEntityVerges, kAction101632192); + + setup_function19(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Pascale, function19) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1 && getEntityData(kEntityPlayer)->entityPosition < kPosition_3650) { + getObjects()->update(kObject65, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getSavePoints()->push(kEntityPascale, kEntityTables0, kActionDrawTablesWithChairs, "001P"); + getSavePoints()->push(kEntityPascale, kEntityTables1, kActionDrawTablesWithChairs, "005J"); + getSavePoints()->push(kEntityPascale, kEntityTables2, kActionDrawTablesWithChairs, "009G"); + getSavePoints()->push(kEntityPascale, kEntityTables3, kActionDrawTablesWithChairs, "010M"); + getSavePoints()->push(kEntityPascale, kEntityTables4, kActionDrawTablesWithChairs, "014F"); + getSavePoints()->push(kEntityPascale, kEntityTables5, kActionDrawTablesWithChairs, "024D"); + + params->param1 = 1; + } + break; + + case kActionDefault: + getData()->car = kCarRestaurant; + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + + getEntities()->clearSequences(kEntityPascale); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Pascale, chapter2) + if (savepoint.action == kActionDefault) { + getEntities()->clearSequences(kEntityPascale); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothes1; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObject65, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Pascale, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityPascale); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 4) = 0; + ENTITY_PARAM(0, 7) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Pascale, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!getEntities()->isInKitchen(kEntityPascale)) + break; + + if (ENTITY_PARAM(0, 7)) { + setCallback(1); + setup_function23(); + break; + } + +label_callback: + if (ENTITY_PARAM(0, 4)) { + setCallback(2); + setup_welcomeSophieAndRebecca(); + } + break; + + case kActionCallback: + if (getCallback() == 1) + goto label_callback; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Pascale, function23) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + getEntities()->updatePositionEnter(kEntityPascale, kCarRestaurant, 67); + + setCallback(1); + setup_welcomeAbbot(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->updatePositionExit(kEntityPascale, kCarRestaurant, 67); + getSavePoints()->push(kEntityPascale, kEntityAbbot, kAction122288808); + + setCallback(2); + setup_draw("906"); + break; + + case 2: + getData()->entityPosition = kPosition_5900; + ENTITY_PARAM(0, 7) = 0; + getEntities()->clearSequences(kEntityPascale); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Pascale, welcomeAbbot) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) { + getSound()->playSound(kEntityPascale, "ABB3015A"); + params->param1 = 1; + } + break; + + case kActionExitCompartment: + CALLBACK_ACTION(); + break; + + case kAction10: + getSavePoints()->push(kEntityPascale, kEntityTables4, kAction136455232); + break; + + case kActionDefault: + getSound()->playSound(kEntityPascale, "ABB3015", SoundManager::kFlagInvalid, 105); + getEntities()->drawSequenceRight(kEntityPascale, "029A1"); + getEntities()->drawSequenceRight(kEntityAbbot, "029A2"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Pascale, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityPascale); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 4) = 0; + ENTITY_PARAM(0, 8) = 0; + + ENTITY_PARAM(1, 1) = 0; + ENTITY_PARAM(1, 2) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Pascale, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime2511000 && !params->param4) { + params->param2 = 1; + params->param4 = 1; + } + + if (!getEntities()->isInKitchen(kEntityPascale)) + break; + + if (getEntities()->isSomebodyInsideRestaurantOrSalon()) { + if (ENTITY_PARAM(0, 8)) { + setCallback(1); + setup_function27(); + break; + } + +label_callback1: + if (ENTITY_PARAM(1, 2) && ENTITY_PARAM(1, 4)) { + if (!params->param3) + params->param3 = getState()->time + 9000; + + if (params->param5 != kTimeInvalid) { + + if (params->param3 < getState()->time) { + params->param5 = kTimeInvalid; + setCallback(2); + setup_messageFromAnna(); + break; + } + + if (!getEntities()->isInRestaurant(kEntityPlayer) || !params->param5) + params->param5 = getState()->time; + + if (params->param5 < getState()->time) { + params->param5 = kTimeInvalid; + setCallback(2); + setup_messageFromAnna(); + break; + } + } + } + +label_callback2: + if (params->param1 && !params->param2 && getEntities()->isPlayerPosition(kCarRestaurant, 61)) { + setCallback(3); + setup_function11(); + break; + } + } + +label_callback3: + if (ENTITY_PARAM(0, 4)) { + setCallback(4); + setup_welcomeSophieAndRebecca(); + } + break; + + case kActionDefault: + if (getEntities()->isPlayerPosition(kCarRestaurant, 69) + || getEntities()->isPlayerPosition(kCarRestaurant, 70) + || getEntities()->isPlayerPosition(kCarRestaurant, 71)) + params->param2 = 1; + break; + + case kActionDrawScene: + if (!params->param2) { + if (getEntities()->isPlayerPosition(kCarRestaurant, 69) + || getEntities()->isPlayerPosition(kCarRestaurant, 70) + || getEntities()->isPlayerPosition(kCarRestaurant, 71)) + params->param2 = 1; + + if (!params->param2 && getEntities()->isPlayerPosition(kCarRestaurant, 61)) + params->param1 = 1; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback1; + + case 2: + goto label_callback2; + + case 3: + params->param1 = 0; + params->param2 = 1; + goto label_callback3; + } + break; + + case kAction201431954: + ENTITY_PARAM(0, 4) = 0; + ENTITY_PARAM(0, 8) = 0; + + getSavePoints()->push(kEntityPascale, kEntityTables0, kActionDrawTablesWithChairs, "001P"); + getSavePoints()->push(kEntityPascale, kEntityTables1, kActionDrawTablesWithChairs, "005J"); + getSavePoints()->push(kEntityPascale, kEntityTables2, kActionDrawTablesWithChairs, "009G"); + getSavePoints()->push(kEntityPascale, kEntityTables3, kActionDrawTablesWithChairs, "010M"); + getSavePoints()->push(kEntityPascale, kEntityTables4, kActionDrawTablesWithChairs, "014F"); + getSavePoints()->push(kEntityPascale, kEntityTables5, kActionDrawTablesWithChairs, "024D"); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Pascale, function27) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (ENTITY_PARAM(1, 1)) { + setCallback(2); + setup_updateFromTime(450); + } + break; + + case kActionDefault: + setCallback(1); + setup_function29(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->clearSequences(kEntityPascale); + break; + + case 2: + getSavePoints()->push(kEntityPascale, kEntityCoudert, kAction123712592); + + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + setCallback(4); + setup_function30(); + break; + + case 4: + getEntities()->clearSequences(kEntityPascale); + getData()->entityPosition = kPosition_5900; + ENTITY_PARAM(0, 8) = 0; + ENTITY_PARAM(1, 1) = 0; + ENTITY_PARAM(1, 2) = 1; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Pascale, messageFromAnna) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("902"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityPascale, kEntityAugust, kAction122358304); + getEntities()->drawSequenceLeft(kEntityPascale, "010E2"); + + setCallback(2); + setup_playSound("Aug4001"); + break; + + case 2: + getSavePoints()->push(kEntityPascale, kEntityAugust, kAction123793792); + + setCallback(3); + setup_draw("905"); + break; + + case 3: + getEntities()->clearSequences(kEntityPascale); + getData()->entityPosition = kPosition_5900; + ENTITY_PARAM(1, 2) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Pascale, function29) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("817DD"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceRight(kEntityPascale, "817DS"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityPascale); + + setCallback(2); + setup_callbackActionOnDirection(); + break; + + case 2: + getData()->entityPosition = kPosition_850; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Pascale, function30) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_9270; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("817US"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceRight(kEntityPascale, "817UD"); + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityPascale); + + setCallback(2); + setup_callbackActionOnDirection(); + break; + + case 2: + getData()->entityPosition = kPosition_5900; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, Pascale, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityPascale); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Pascale, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_function33(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Pascale, function33) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param4) { + UPDATE_PARAM_PROC(params->param5, getState()->time, 4500) + getObjects()->update(kObjectCompartmentG, kEntityPascale, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(1); + setup_playSound("Wat5010"); + break; + UPDATE_PARAM_PROC_END + } + +label_callback1: + if (params->param1) { + UPDATE_PARAM(params->param6, getState()->timeTicks, 75); + + params->param1 = 0; + params->param2 = 2; + + getObjects()->update(kObjectCompartmentG, kEntityPascale, kObjectLocation1, kCursorNormal, kCursorNormal); + } + + params->param6 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + if (params->param1) { + getObjects()->update(kObjectCompartmentG, kEntityPascale, kObjectLocation1, kCursorNormal, kCursorNormal); + params->param1 = 0; + + setCallback(2); + setup_playSound(getSound()->justCheckingCath()); + } else { + setCallback(savepoint.action == kActionKnock ? 3 : 4); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + break; + + case kActionDefault: + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + + getObjects()->update(kObjectCompartmentG, kEntityPascale, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kActionDrawScene: + if (params->param2 || params->param1) { + params->param1 = 0; + params->param2 = 0; + params->param3 = 0; + + getObjects()->update(kObjectCompartmentG, kEntityPascale, kObjectLocation1, kCursorHandKnock, kCursorHand); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentG, kEntityPascale, kObjectLocation1, kCursorHandKnock, kCursorHand); + goto label_callback1; + + case 2: + getObjects()->update(kObjectCompartmentG, kEntityPascale, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case 3: + case 4: + params->param3++; + + if (params->param3 == 1 || params->param3 == 2) { + getObjects()->update(kObjectCompartmentG, kEntityPascale, kObjectLocation1, kCursorNormal, kCursorNormal); + setCallback(params->param3 == 1 ? 5 : 6); + setup_playSound(params->param3 == 1 ? "Wat5001" : "Wat5002"); + } + break; + + case 5: + params->param1 = 1; + getObjects()->update(kObjectCompartmentG, kEntityPascale, kObjectLocation1, kCursorTalk, kCursorNormal); + break; + + case 6: + params->param2 = 1; + break; + + case 7: + params->param4 = 1; + break; + } + break; + + case kAction135800432: + setup_nullfunction(); + break; + + case kAction169750080: + if (getSound()->isBuffered(kEntityPascale)) { + params->param4 = 1; + } else { + setCallback(7); + setup_playSound("Wat5002"); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(34, Pascale) + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/pascale.h b/engines/lastexpress/entities/pascale.h new file mode 100644 index 0000000000..d899037c0b --- /dev/null +++ b/engines/lastexpress/entities/pascale.h @@ -0,0 +1,166 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_PASCALE_H +#define LASTEXPRESS_PASCALE_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Pascale : public Entity { +public: + Pascale(LastExpressEngine *engine); + ~Pascale() {}; + + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Process callback action when somebody is standing in the restaurant or salon. + */ + DECLARE_FUNCTION(callbackActionRestaurantOrSalon) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Updates the position + * + * @param savepoint The savepoint + * - The sequence to draw + * - The car + * - The position + */ + DECLARE_FUNCTION_NOSETUP(updatePosition) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Draws the entity along with another one + * + * @param savepoint The savepoint + * - The sequence to draw + * - The sequence to draw for the second entity + * - The EntityIndex of the second entity + */ + DECLARE_FUNCTION_NOSETUP(draw2) + + DECLARE_FUNCTION(welcomeSophieAndRebecca) + DECLARE_FUNCTION(sitSophieAndRebecca) + DECLARE_FUNCTION(welcomeCath) + DECLARE_FUNCTION(function11) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + DECLARE_FUNCTION(getMessageFromAugustToTyler) + DECLARE_FUNCTION(sitAnna) + DECLARE_FUNCTION(welcomeAnna) + DECLARE_FUNCTION(serveTatianaVassili) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function18) + DECLARE_FUNCTION(function19) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + DECLARE_FUNCTION(function23) + DECLARE_FUNCTION(welcomeAbbot) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function27) + DECLARE_FUNCTION(messageFromAnna) + DECLARE_FUNCTION(function29) + DECLARE_FUNCTION(function30) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function33) + + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_PASCALE_H diff --git a/engines/lastexpress/entities/rebecca.cpp b/engines/lastexpress/entities/rebecca.cpp new file mode 100644 index 0000000000..b9c3f23401 --- /dev/null +++ b/engines/lastexpress/entities/rebecca.cpp @@ -0,0 +1,1732 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/rebecca.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Rebecca::Rebecca(LastExpressEngine *engine) : Entity(engine, kEntityRebecca) { + ADD_CALLBACK_FUNCTION(Rebecca, reset); + ADD_CALLBACK_FUNCTION(Rebecca, updateFromTime); + ADD_CALLBACK_FUNCTION(Rebecca, playSound); + ADD_CALLBACK_FUNCTION(Rebecca, playSound16); + ADD_CALLBACK_FUNCTION(Rebecca, callSavepoint); + ADD_CALLBACK_FUNCTION(Rebecca, draw); + ADD_CALLBACK_FUNCTION(Rebecca, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Rebecca, enterExitCompartment2); + ADD_CALLBACK_FUNCTION(Rebecca, enterExitCompartment3); + ADD_CALLBACK_FUNCTION(Rebecca, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Rebecca, callbackActionRestaurantOrSalon); + ADD_CALLBACK_FUNCTION(Rebecca, updateEntity); + ADD_CALLBACK_FUNCTION(Rebecca, updatePosition); + ADD_CALLBACK_FUNCTION(Rebecca, draw2); + ADD_CALLBACK_FUNCTION(Rebecca, function15); + ADD_CALLBACK_FUNCTION(Rebecca, function16); + ADD_CALLBACK_FUNCTION(Rebecca, function17); + ADD_CALLBACK_FUNCTION(Rebecca, function18); + ADD_CALLBACK_FUNCTION(Rebecca, function19); + ADD_CALLBACK_FUNCTION(Rebecca, function20); + ADD_CALLBACK_FUNCTION(Rebecca, chapter1); + ADD_CALLBACK_FUNCTION(Rebecca, chapter1Handler); + ADD_CALLBACK_FUNCTION(Rebecca, function23); + ADD_CALLBACK_FUNCTION(Rebecca, function24); + ADD_CALLBACK_FUNCTION(Rebecca, function25); + ADD_CALLBACK_FUNCTION(Rebecca, function26); + ADD_CALLBACK_FUNCTION(Rebecca, function27); + ADD_CALLBACK_FUNCTION(Rebecca, chapter2); + ADD_CALLBACK_FUNCTION(Rebecca, chapter2Handler); + ADD_CALLBACK_FUNCTION(Rebecca, function30); + ADD_CALLBACK_FUNCTION(Rebecca, function31); + ADD_CALLBACK_FUNCTION(Rebecca, chapter3); + ADD_CALLBACK_FUNCTION(Rebecca, chapter3Handler); + ADD_CALLBACK_FUNCTION(Rebecca, function34); + ADD_CALLBACK_FUNCTION(Rebecca, function35); + ADD_CALLBACK_FUNCTION(Rebecca, function36); + ADD_CALLBACK_FUNCTION(Rebecca, function37); + ADD_CALLBACK_FUNCTION(Rebecca, function38); + ADD_CALLBACK_FUNCTION(Rebecca, function39); + ADD_CALLBACK_FUNCTION(Rebecca, function40); + ADD_CALLBACK_FUNCTION(Rebecca, function41); + ADD_CALLBACK_FUNCTION(Rebecca, chapter4); + ADD_CALLBACK_FUNCTION(Rebecca, chapter4Handler); + ADD_CALLBACK_FUNCTION(Rebecca, function44); + ADD_CALLBACK_FUNCTION(Rebecca, function45); + ADD_CALLBACK_FUNCTION(Rebecca, chapter5); + ADD_CALLBACK_FUNCTION(Rebecca, chapter5Handler); + ADD_CALLBACK_FUNCTION(Rebecca, function48); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Rebecca, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(2, Rebecca, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(3, Rebecca, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(4, Rebecca, playSound16) + Entity::playSound(savepoint, false, getSound()->getSoundFlag(kEntityCoudert)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SIIS(5, Rebecca, callSavepoint, EntityIndex, ActionIndex) + Entity::callSavepoint(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(6, Rebecca, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(7, Rebecca, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(8, Rebecca, enterExitCompartment2, ObjectIndex) + Entity::enterExitCompartment(savepoint, kPosition_4840, kPosition_4455, kCarRedSleeping, kObjectCompartmentE, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(9, Rebecca, enterExitCompartment3, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Rebecca, callbackActionOnDirection) + Entity::callbackActionOnDirection(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Rebecca, callbackActionRestaurantOrSalon) + Entity::callbackActionRestaurantOrSalon(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(12, Rebecca, updateEntity, CarIndex, EntityPosition) + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SII(13, Rebecca, updatePosition, CarIndex, Position) + Entity::updatePosition(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SSI(14, Rebecca, draw2, EntityIndex) + Entity::draw2(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Rebecca, function15) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (getEntities()->isOutsideAnnaWindow()) + getScenes()->loadSceneFromPosition(kCarRedSleeping, 49); + + setCallback(1); + setup_enterExitCompartment2("624Ae", kObjectCompartmentE); + break; + + case kActionCallback: + if (getCallback() == 1) { + getObjects()->update(kObjectOutsideBetweenCompartments, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityRebecca); + + CALLBACK_ACTION(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(16, Rebecca, function16, bool) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param2) { + if (getEntities()->isDistanceBetweenEntities(kEntityRebecca, kEntitySophie, 750)) { + if (!getEntities()->hasValidFrame(kEntitySophie)) { + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction123668192); + + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + } + } + } + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment(params->param1 ? "624Be" : "623Ee", kObjectCompartmentE); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction125242096); + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + getEntities()->clearSequences(kEntityRebecca); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_draw("810US"); + break; + + case 4: + getEntities()->drawSequenceRight(kEntityRebecca, "012B"); + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityRebecca); + + setCallback(4); + setup_callbackActionOnDirection(); + break; + + case 5: + getEntities()->drawSequenceLeft(kEntityRebecca, "012A"); + if (getProgress().chapter == kChapter3) + getSound()->playSound(kEntityRebecca, "REB3000"); + + getSavePoints()->push(kEntityRebecca, kEntityPascale, kAction269479296); + + params->param2 = 1; + break; + } + break; + + case kAction157370960: + getSavePoints()->push(kEntityRebecca, kEntityTables3, kAction136455232); + getData()->location = kLocationInsideCompartment; + + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(17, Rebecca, function17, bool) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isDistanceBetweenEntities(kEntityRebecca, kEntitySophie, 750) + && !getEntities()->hasValidFrame(kEntitySophie)) { + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction123668192); + + setCallback(3); + setup_updateFromTime(0); + } + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("624Be", kObjectCompartmentE); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + getData()->location = kLocationOutsideCompartment; + + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction125242096); + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + getEntities()->clearSequences(kEntitySophie); + break; + + case 3: + setCallback(4); + setup_callbackActionRestaurantOrSalon(); + break; + + case 4: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + if (getProgress().chapter == kChapter3) + getSound()->playSound(kEntityRebecca, "Reb3005", SoundManager::kFlagInvalid, 75); + + if (params->param1) { + setCallback(5); + setup_updatePosition("118A", kCarRestaurant, 52); + } else { + getEntities()->updatePositionEnter(kEntityRebecca, kCarRestaurant, 57); + + setCallback(6); + setup_draw2("107A1", "107A2", kEntitySophie); + } + break; + + case 5: + getData()->location = kLocationInsideCompartment; + + CALLBACK_ACTION(); + break; + + case 6: + getEntities()->updatePositionExit(kEntityRebecca, kCarRestaurant, 57); + getEntities()->clearSequences(kEntitySophie); + + getData()->location = kLocationInsideCompartment; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Rebecca, function18) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isDistanceBetweenEntities(kEntityRebecca, kEntitySophie, 750) + || getEntities()->checkDistanceFromPosition(kEntitySophie, kPosition_4840, 500)) { + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction123668192); + getEntities()->exitCompartment(kEntityRebecca, kObjectCompartmentE, true); + + setCallback(3); + setup_function15(); + } + break; + + case kActionDefault: + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_9270; + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction136654208); + + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_4840); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getEntities()->isDistanceBetweenEntities(kEntityRebecca, kEntitySophie, 750) + || getEntities()->checkDistanceFromPosition(kEntitySophie, kPosition_4840, 500)) { + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction123668192); + + setCallback(2); + setup_function15(); + } else { + getEntities()->drawSequenceLeft(kEntityRebecca, "623Ge"); + getEntities()->enterCompartment(kEntityRebecca, kObjectCompartmentE, true); + } + break; + + case 2: + case 3: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Rebecca, function19) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isDistanceBetweenEntities(kEntityRebecca, kEntitySophie, 750) + || getEntities()->checkDistanceFromPosition(kEntitySophie, kPosition_4840, 500)) { + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction123668192); + getEntities()->exitCompartment(kEntityRebecca, kObjectCompartmentE, true); + + setCallback(6); + setup_function15(); + } + break; + + case kActionDefault: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_callSavepoint("012H", kEntityTables3, kActionDrawTablesWithChairs, "010M"); + break; + + case 2: + getSavePoints()->push(kEntityRebecca, kEntityServers0, kAction337548856); + getEntities()->drawSequenceRight(kEntityRebecca, "810DS"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityRebecca); + + setCallback(4); + setup_callbackActionOnDirection(); + break; + + case 3: + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_9270; + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction136654208); + + setCallback(4); + setup_updateEntity(kCarRedSleeping, kPosition_4840); + break; + + case 4: + if (getEntities()->isDistanceBetweenEntities(kEntityRebecca, kEntitySophie, 750) + || getEntities()->checkDistanceFromPosition(kEntitySophie, kPosition_4840, 500)) { + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction123668192); + + setCallback(5); + setup_function15(); + } else { + getEntities()->drawSequenceLeft(kEntityRebecca, "623Ge"); + getEntities()->enterCompartment(kEntityRebecca, kObjectCompartmentE, true); + } + break; + + case 5: + case 6: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(20, Rebecca, function20, TimeValue) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 < getState()->time && !params->param5) { + params->param5 = 1; + + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + } + + if (!params->param2) { + params->param6 = 0; + } else { + UPDATE_PARAM_PROC(params->param6, getState()->timeTicks, 75) + params->param2 = 0; + params->param3 = 1; + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject52, kEntityRebecca, kObjectLocation1, kCursorNormal, kCursorNormal); + + params->param6 = 0; + UPDATE_PARAM_PROC_END + } + + if (getProgress().chapter == kChapter1 && !ENTITY_PARAM(0, 3)) { + if (params->param7 != kTimeInvalid && getState()->time > kTime1174500) { + if (getState()->time <= kTime1183500) { + if (!getEntities()->isDistanceBetweenEntities(kEntityRebecca, kEntityPlayer, 2000) || getSound()->isBuffered("CON1210") || !params->param7) + params->param7 = getState()->time; + + if (params->param7 >= getState()->time) + goto label_callback; + } + + params->param7 = kTimeInvalid; + ENTITY_PARAM(0, 3) = 1; + + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject52, kEntityRebecca, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(1); + setup_playSound("REB1205"); + break; + } + goto label_callback; + } + + if (getProgress().chapter == kChapter3 && !ENTITY_PARAM(0, 4) && params->param8 != kTimeInvalid && getState()->time > kTime2097000) { + if (getState()->time <= kTime2106000) { + if (!getEntities()->isDistanceBetweenEntities(kEntityRebecca, kEntityPlayer, 1000) || !params->param8) + params->param8 = getState()->time; + + if (params->param8 >= getState()->time) + goto label_callback; + } + + params->param8 = kTimeInvalid; + ENTITY_PARAM(0, 4) = 1; + + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject52, kEntityRebecca, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(2); + setup_playSound("REB3010"); + break; + } + +label_callback: + if (ENTITY_PARAM(0, 2) && getEntities()->isDistanceBetweenEntities(kEntityRebecca, kEntityPlayer, 1000)) { + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject52, kEntityRebecca, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(3); + setup_playSound("REB1040"); + } + break; + + case kActionKnock: + case kActionOpenDoor: + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityRebecca, kObjectLocation1, kCursorHandKnock, kCursorHand); + + break; + + case kActionDrawScene: + if (params->param3 || params->param2) { + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityRebecca, kObjectLocation1, kCursorHandKnock, kCursorHand); + + params->param2 = 0; + params->param3 = 0; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + case 3: + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityRebecca, kObjectLocation1, kCursorHandKnock, kCursorHand); + + if (getCallback() != 2) + ENTITY_PARAM(0, 2) = 0; + + if (getCallback() != 3) + goto label_callback; + break; + + case 4: + case 5: + if (rnd(2)) { + setCallback(6); + setup_playSound("REB1039"); + } else { + setCallback(7); + setup_playSound(rnd(2) ? "SOP1039" : "SOP1039A"); + } + break; + + case 6: + case 7: + params->param4 = (getCallback() == 6 ? 0 : 1); + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorTalk, kCursorNormal); + getObjects()->update(kObject52, kEntityRebecca, kObjectLocation1, kCursorTalk, kCursorNormal); + params->param2 = 1; + break; + + case 8: + case 9: + case 10: + case 11: + params->param2 = 0; + params->param3 = 1; + break; + + case 12: + setCallback(13); + setup_playSound16("JAC1012B"); + break; + + case 13: + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityRebecca, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } + break; + + case kAction254915200: + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject52, kEntityRebecca, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(12); + setup_playSound("REB1039A"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Rebecca, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getSavePoints()->addData(kEntityRebecca, kAction224253538, 0); + + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectOutsideBetweenCompartments, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + getObjects()->updateLocation2(kObject110, kObjectLocation1); + + getData()->entityPosition = kPosition_2830; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + + ENTITY_PARAM(0, 2) = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Rebecca, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_PLAYSOUND(kTime1084500, params->param3, 1, "REB1015"); + + if (params->param4 == kTimeInvalid) + goto label_callback_4; + + if (getState()->time > kTime1080000) + goto label_playConversation; + + if (!getEntities()->isInSalon(kEntityPlayer) || !params->param4) + params->param4 = getState()->time + 150; + + if (params->param4 >= getState()->time) { +label_callback_4: + if (params->param1) { + UPDATE_PARAM_CHECK(params->param5, getState()->time, 900) + if (getEntities()->isInSalon(kEntityPlayer)) { + setCallback(5); + setup_playSound("REB1013"); + break; + } + } + } + +label_callback_5: + if (params->param2) { + UPDATE_PARAM(params->param6, getState()->timeTicks, 90); + getScenes()->loadSceneFromPosition(kCarRestaurant, 55); + } else { + params->param6 = 0; + } + } else { +label_playConversation: + params->param4 = kTimeInvalid; + + if (getEntities()->isInSalon(kEntityPlayer)) + getProgress().field_B8 = 1; + + setCallback(4); + setup_playSound("REB1012"); + } + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityRebecca, "107B"); + break; + + case kActionDrawScene: + params->param2 = (getEntities()->isPlayerPosition(kCarRestaurant, 57) ? 1 : 0); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updatePosition("107C", kCarRestaurant, 57); + break; + + case 2: + setCallback(3); + setup_function18(); + break; + + case 3: + setup_function23(); + break; + + case 4: + params->param1 = 1; + goto label_callback_4; + + case 5: + getProgress().field_B4 = 1; + params->param1 = 0; + goto label_callback_5; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Rebecca, function23) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK_2(kTime1111500, params->param2, 3, setup_enterExitCompartment, "623De", kObjectCompartmentE); + break; + + case kActionDefault: + setCallback(1); + setup_updateFromTime(900); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("623Ce", kObjectCompartmentE); + break; + + case 2: + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getEntities()->drawSequenceLeft(kEntityRebecca, "504"); + break; + + case 3: + case 6: + getEntities()->clearSequences(kEntityRebecca); + getData()->entityPosition = kPosition_4840; + getData()->location = kLocationInsideCompartment; + + setCallback((byte)(getCallback() + 1)); + setup_function20(kTime1120500); + break; + + case 4: + case 5: + if (ENTITY_PARAM(0, 1)) { + setup_function24(); + } else { + setCallback(5); + setup_function20((TimeValue)(getState()->time + 900)); + } + break; + + case 7: + case 8: + if (ENTITY_PARAM(0, 1)) { + setup_function24(); + } else { + setCallback(8); + setup_function20((TimeValue)(getState()->time + 900)); + } + break; + } + break; + + case kAction285528346: + setCallback(6); + setup_enterExitCompartment("623De", kObjectCompartmentE); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Rebecca, function24) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_SAVEPOINT(kTime1134000, params->param2, kEntityRebecca, kEntityServers0, kAction223712416); + + if (!params->param1) + break; + + TIME_CHECK_CALLBACK(kTime1165500, params->param3, 6, setup_function19); + + if (params->param4 != kTimeInvalid) { + if (getState()->time <= kTime1161000) { + if (!getEntities()->isInRestaurant(kEntityPlayer) || !params->param4) + params->param4 = getState()->time + 150; + + if (params->param4 >= getState()->time) + break; + } + + params->param4 = kTimeInvalid; + + setCallback(7); + setup_playSound("REB1200A"); + } + break; + + case kActionDefault: + setCallback(1); + setup_function16(true); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityRebecca, "012D"); + + setCallback(2); + setup_playSound("REB1199"); + break; + + case 2: + if (getEntities()->isInRestaurant(kEntityPlayer)) { + setCallback(3); + setup_playSound("REB1199A"); + break; + } + // Fallback to next case + + case 3: + if (getCallback() == 3) + getProgress().field_BC = 1; + + if (getEntities()->isInRestaurant(kEntityAnna)) { + setCallback(4); + setup_playSound("REB1199B"); + break; + } + // Fallback to next case + + case 4: + setCallback(5); + setup_playSound("REB1199C"); + break; + + case 6: + setup_function25(); + break; + + case 8: + getSavePoints()->push(kEntityRebecca, kEntityServers0, kAction136702400); + getEntities()->drawSequenceLeft(kEntityRebecca, "012G"); + params->param1 = 1; + break; + } + break; + + case kAction123712592: + getEntities()->drawSequenceLeft(kEntityServers0, "BLANK"); + getEntities()->drawSequenceLeft(kEntityRebecca, "012E"); + + setCallback(8); + setup_playSound("REB1200"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Rebecca, function25) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function20(kTime1184400); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function17(false); + break; + + case 2: + setup_function26(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Rebecca, function26) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK_3(kTime1224000, params->param2, 1, setup_updatePosition, "118H", kCarRestaurant, 52); + + if (params->param1) { + UPDATE_PARAM(params->param3, getState()->timeTicks, 90); + + getScenes()->loadSceneFromPosition(kCarRestaurant, 51); + } + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityRebecca, "118D"); + break; + + case kActionDrawScene: + params->param1 = getEntities()->isPlayerPosition(kCarRestaurant, 52); + params->param3 = 0; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function18(); + break; + + case 2: + setup_function27(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Rebecca, function27) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_4840; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getEntities()->clearSequences(kEntityRebecca); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Rebecca, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityRebecca); + + getData()->entityPosition = kPosition_4840; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getObjects()->updateLocation2(kObject110, kObjectLocation2); + + ENTITY_PARAM(0, 2) = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Rebecca, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function20(kTime1764000); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function17(false); + break; + + case 2: + setup_function30(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Rebecca, function30) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1 && params->param4 != kTimeInvalid) { + + if (getState()->time <= kTimeEnd) + if (!getEntities()->isInSalon(kEntityPlayer) || !params->param4) + params->param4 = getState()->time + 450; + + if (params->param4 < getState()->time || getState()->time > kTimeEnd) { + params->param4 = kTimeInvalid; + + getSound()->playSound(kEntityRebecca, "Reb2001"); + getProgress().field_B0 = 1; + params->param2 = 1; + } + } + + if (!params->param3 && !params->param2 && params->param5 != kTimeInvalid) { + + if (getState()->time <= kTime10881000) { + if (!getEntities()->isInSalon(kEntityPlayer) || !params->param5) + params->param5 = getState()->time + 450; + + if (params->param5 >= getState()->time) + break; + } + + params->param5 = kTimeInvalid; + + getSavePoints()->push(kEntityRebecca, kEntityAugust, kAction169358379); + } + break; + + case kActionEndSound: + params->param2 = 0; + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityRebecca, "107B"); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function31(); + break; + + case kAction125496184: + setCallback(1); + setup_function18(); + break; + + case kAction155465152: + getEntities()->drawSequenceLeft(kEntityRebecca, "BLANK"); + break; + + case kAction155980128: + params->param1 = 1; + params->param3 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, Rebecca, function31) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateFromTime(900); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("623CE", kObjectCompartmentE); + break; + + case 2: + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocation2, kCursorNormal, kCursorNormal); + getEntities()->drawSequenceLeft(kEntityRebecca, "504"); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Rebecca, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityRebecca); + + getData()->entityPosition = kPosition_4840; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Rebecca, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function20(kTime2016000); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function34(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(34, Rebecca, function34) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param2 == kTimeInvalid) { + if (getState()->time <= kTime1386000) { + if (!getEntities()->isInRestaurant(kEntityPlayer) || !params->param2) + params->param2 = getState()->time; + + if (params->param2 >= getState()->time) { + TIME_CHECK_CALLBACK(kTime2052000, params->param3, 1, setup_function19); + break; + } + } + + params->param2 = kTimeInvalid; + + getSavePoints()->push(kEntityRebecca, kEntityServers0, kAction223712416); + } + + TIME_CHECK_CALLBACK(kTime2052000, params->param3, 1, setup_function19); + break; + + case kActionEndSound: + setCallback(5); + setup_playSound("Reb3004"); + break; + + case kActionDefault: + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_function16(true); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityRebecca, "012D"); + getData()->location = kLocationInsideCompartment; + + setCallback(2); + setup_playSound("Reb3002"); + break; + + case 3: + setup_function35(); + break; + + case 4: + getSavePoints()->push(kEntityRebecca, kEntityServers0, kAction136702400); + getEntities()->drawSequenceLeft(kEntityRebecca, "012G"); + params->param1 = 1; + break; + } + break; + + case kAction123712592: + getEntities()->drawSequenceLeft(kEntityServers0, "BLANK"); + getSound()->playSound(kEntityRebecca, "Reb3003"); + + setCallback(4); + setup_draw("012E"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(35, Rebecca, function35) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function20(kTime2070000); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function36(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(36, Rebecca, function36) + error("Rebecca: callback function 36 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(37, Rebecca, function37) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function20(kTime2110500); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function38(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(38, Rebecca, function38) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_enterExitCompartment3("624Be", kObjectCompartmentE); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction259921280); + + setCallback(2); + setup_updateEntity(kCarKronos, kPosition_9270); + break; + + case 2: + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction123668192); + setup_function39(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(39, Rebecca, function39) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityRebecca); + + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_6000; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarKronos; + break; + + case kAction191668032: + setup_function40(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(40, Rebecca, function40) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_9270; + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_2740); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction292775040); + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_2740); + break; + + case 2: + getSavePoints()->push(kEntityRebecca, kEntityAnna, kAction191668032); + setCallback(3); + setup_updateEntity(kCarRedSleeping, kPosition_4840); + break; + + case 3: + getSavePoints()->push(kEntityRebecca, kEntitySophie, kAction123668192); + setCallback(4); + setup_function15(); + break; + + case 4: + setup_function41(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(41, Rebecca, function41) + if (savepoint.action == kActionDefault) { + ENTITY_PARAM(0, 2) = 1; + + setCallback(1); + setup_function20(kTimeEnd); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(42, Rebecca, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityRebecca); + + getData()->entityPosition = kPosition_4840; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->updateLocation2(kObject110, kObjectLocation3); + + ENTITY_PARAM(0, 1) = 0; + ENTITY_PARAM(0, 2) = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(43, Rebecca, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function20(kTime2385000); + break; + + case kActionCallback: + if (getCallback() == 1 || getCallback() == 2) { + if (ENTITY_PARAM(0, 1)) { + setup_function44(); + } else { + setCallback(2); + setup_function20((TimeValue)(getState()->time + 900)); + } + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(44, Rebecca, function44) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param3 != kTimeInvalid) { + if (getState()->time <= kTime2412000) { + if (!getEntities()->isInRestaurant(kEntityPlayer) || !params->param3) + params->param3 = getState()->time; + + if (params->param3 >= getState()->time) + goto label_next; + } + + params->param3 = kTimeInvalid; + + getSavePoints()->push(kEntityRebecca, kEntityServers0, kAction223712416); + } + +label_next: + if (params->param1 && params->param4 != kTimeInvalid) { + if (getState()->time <= kTime2430000) { + if (!getEntities()->isInRestaurant(kEntityPlayer) || !params->param4) + params->param4 = getState()->time + 150; + + if (params->param4 >= getState()->time) + goto label_callback_2; + } + + params->param4 = kTimeInvalid; + + setCallback(2); + setup_playSound("Reb4004"); + break; + } + +label_callback_2: + if (params->param2) + TIME_CHECK_CALLBACK(kTime2443500, params->param5, 3, setup_function19); + break; + + case kActionEndSound: + if (getEntities()->isInRestaurant(kEntityPlayer)) { + setCallback(5); + setup_playSound("Reb4004"); + break; + } + + params->param1 = 1; + break; + + case kActionDefault: + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_function16(true); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityRebecca, "012D"); + getData()->location = kLocationInsideCompartment; + break; + + case 2: + goto label_callback_2; + + case 3: + setup_function45(); + break; + + case 4: + getSavePoints()->push(kEntityRebecca, kEntityServers0, kAction136702400); + getEntities()->drawSequenceLeft(kEntityRebecca, "012G"); + params->param2 = 1; + break; + } + break; + + case kAction123712592: + getEntities()->drawSequenceLeft(kEntityRebecca, "BLANK"); + getSound()->playSound(kEntityRebecca, "Reb4003"); + + setCallback(4); + setup_draw("012E"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(45, Rebecca, function45) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_4840; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject52, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getEntities()->clearSequences(kEntityRebecca); + break; + + case kActionCallback: + if (getCallback() == 1) + params->param1 = 1; + break; + + case kAction205034665: + if (!params->param1 && getState()->time < kTime2511000) { + setCallback(1); + setup_playSound("Reb6969"); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(46, Rebecca, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityRebecca); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + + getObjects()->updateLocation2(kObject110, kObjectLocation4); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(47, Rebecca, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_function48(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(48, Rebecca, function48) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) { + UPDATE_PARAM(params->param3, getState()->timeTicks, 75); + + params->param1 = 0; + params->param2 = 1; + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorNormal, kCursorNormal); + } + + params->param3 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorNormal, kCursorNormal); + + if (params->param1) { + params->param1 = 0; + + setCallback(2); + setup_playSound(getSound()->justCheckingCath()); + } else { + setCallback(savepoint.action == kActionKnock ? 3 : 4); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + break; + + case kActionDefault: + getData()->car = kCarRedSleeping; + + setCallback(1); + setup_enterExitCompartment("624AE", kObjectCompartmentE); + break; + + case kActionDrawScene: + if (params->param1 || params->param2) { + params->param1 = 0; + params->param2 = 0; + + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorHandKnock, kCursorHand); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->clearSequences(kEntityRebecca); + getData()->location = kLocationInsideCompartment; + getData()->entityPosition = kPosition_4840; + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case 3: + case 4: + setCallback(5); + setup_playSound("Reb5001"); + break; + + case 5: + params->param1 = 1; + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorTalk, kCursorNormal); + break; + } + break; + + case kAction135800432: + setup_nullfunction(); + break; + + case kAction155604840: + getObjects()->update(kObjectCompartmentE, kEntityRebecca, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(49, Rebecca) + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/rebecca.h b/engines/lastexpress/entities/rebecca.h new file mode 100644 index 0000000000..bf836b1fa3 --- /dev/null +++ b/engines/lastexpress/entities/rebecca.h @@ -0,0 +1,230 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_REBECCA_H +#define LASTEXPRESS_REBECCA_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Rebecca : public Entity { +public: + Rebecca(LastExpressEngine *engine); + ~Rebecca() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound16, const char* filename) + + /** + * Call a savepoint (or draw sequence in default case) + * + * @param sequence1 The sequence to draw in the default case + * @param entity The entity + * @param action The action + * @param sequence2 The sequence name for the savepoint + */ + DECLARE_FUNCTION_4(callSavepoint, const char* sequence1, EntityIndex entity, ActionIndex action, const char* sequence2) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment and updates position/play animation + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment2, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment3, const char* sequence, ObjectIndex compartment) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Process callback action when somebody is standing in the restaurant or salon. + */ + DECLARE_FUNCTION(callbackActionRestaurantOrSalon) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + + /** + * Updates the position + * + * @param sequence1 The sequence to draw + * @param car The car + * @param position The position + */ + DECLARE_FUNCTION_3(updatePosition, const char* sequence1, CarIndex car, Position position) + + /** + * Draws the entity along with another one + * + * @param sequence1 The sequence to draw + * @param sequence2 The sequence to draw for the second entity + * @param entity The EntityIndex of the second entity + */ + DECLARE_FUNCTION_3(draw2, const char* sequence1, const char* sequence2, EntityIndex entity) + + DECLARE_FUNCTION(function15) + DECLARE_FUNCTION_1(function16, bool) + DECLARE_FUNCTION_1(function17, bool) + DECLARE_FUNCTION(function18) + DECLARE_FUNCTION(function19) + DECLARE_FUNCTION_1(function20, TimeValue timeValue) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function23) + DECLARE_FUNCTION(function24) + DECLARE_FUNCTION(function25) + DECLARE_FUNCTION(function26) + DECLARE_FUNCTION(function27) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + DECLARE_FUNCTION(function30) + DECLARE_FUNCTION(function31) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + DECLARE_FUNCTION(function34) + DECLARE_FUNCTION(function35) + DECLARE_FUNCTION(function36) + DECLARE_FUNCTION(function37) + DECLARE_FUNCTION(function38) + DECLARE_FUNCTION(function39) + DECLARE_FUNCTION(function40) + DECLARE_FUNCTION(function41) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function44) + DECLARE_FUNCTION(function45) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function48) + + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_REBECCA_H diff --git a/engines/lastexpress/entities/salko.cpp b/engines/lastexpress/entities/salko.cpp new file mode 100644 index 0000000000..688208f30a --- /dev/null +++ b/engines/lastexpress/entities/salko.cpp @@ -0,0 +1,642 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/salko.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/fight.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Salko::Salko(LastExpressEngine *engine) : Entity(engine, kEntitySalko) { + ADD_CALLBACK_FUNCTION(Salko, reset); + ADD_CALLBACK_FUNCTION(Salko, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Salko, draw); + ADD_CALLBACK_FUNCTION(Salko, updateEntity); + ADD_CALLBACK_FUNCTION(Salko, updateFromTime); + ADD_CALLBACK_FUNCTION(Salko, savegame); + ADD_CALLBACK_FUNCTION(Salko, function7); + ADD_CALLBACK_FUNCTION(Salko, function8); + ADD_CALLBACK_FUNCTION(Salko, chapter1); + ADD_CALLBACK_FUNCTION(Salko, chapter1Handler); + ADD_CALLBACK_FUNCTION(Salko, function11); + ADD_CALLBACK_FUNCTION(Salko, chapter2); + ADD_CALLBACK_FUNCTION(Salko, function13); + ADD_CALLBACK_FUNCTION(Salko, chapter3); + ADD_CALLBACK_FUNCTION(Salko, chapter3Handler); + ADD_CALLBACK_FUNCTION(Salko, function16); + ADD_CALLBACK_FUNCTION(Salko, function17); + ADD_CALLBACK_FUNCTION(Salko, chapter4); + ADD_CALLBACK_FUNCTION(Salko, chapter4Handler); + ADD_CALLBACK_FUNCTION(Salko, function20); + ADD_CALLBACK_FUNCTION(Salko, function21); + ADD_CALLBACK_FUNCTION(Salko, function22); + ADD_CALLBACK_FUNCTION(Salko, chapter5); + ADD_CALLBACK_FUNCTION(Salko, chapter5Handler); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Salko, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(2, Salko, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(3, Salko, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(4, Salko, updateEntity, CarIndex, EntityPosition) + Entity::updateEntity(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(5, Salko, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(6, Salko, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(7, Salko, function7, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: { + params->param3 = 0; + + EntityDirection direction = getData()->direction; + CarIndex carSalko = getData()->car; + CarIndex carIvo = getEntityData(kEntityIvo)->car; + EntityPosition positionSalko = getData()->entityPosition; + EntityPosition positionIvo = getEntityData(kEntityIvo)->entityPosition; + + if (getEntities()->isDistanceBetweenEntities(kEntitySalko, kEntityIvo, 500) + || (direction == kDirectionUp && (carSalko > carIvo || (carSalko == carIvo && positionSalko > positionIvo))) + || (direction == kDirectionDown && (carSalko < carIvo || (carSalko == carIvo && positionSalko < positionIvo)))) { + getData()->field_49B = 0; + params->param3 = 1; + } + + if (!params->param3) + getEntities()->updateEntity(kEntitySalko, (CarIndex)params->param1, (EntityPosition)params->param2); + + } + break; + + case kActionExcuseMeCath: + case kActionExcuseMe: + getSound()->playSound(kEntityPlayer, "ZFX1002", getSound()->getSoundFlag(kEntitySalko)); + getSound()->playSound(kEntityPlayer, "CAT1127A"); + break; + + case kActionDefault: + getEntities()->updateEntity(kEntitySalko, (CarIndex)params->param1, (EntityPosition)params->param2); + break; + + case kAction123668192: + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Salko, function8) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Salko, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_4691; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Salko, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + getData()->entityPosition = getEntityData(kEntityIvo)->entityPosition; + getData()->location = getEntityData(kEntityIvo)->location; + break; + + case kActionCallback: + if (getCallback() == 1) { + getEntities()->clearSequences(kEntitySalko); + setup_function8(); + } + break; + + case kAction125242096: + setCallback(1); + setup_function7(kCarRedSleeping, kPosition_2740); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Salko, function11) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getEntities()->clearSequences(kEntitySalko); + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Salko, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->clearSequences(kEntitySalko); + + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + + case kAction136184016: + setCallback(1); + setup_function13(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Salko, function13) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("612DH", kObjectCompartmentH); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + getSavePoints()->push(kEntitySalko, kEntityIvo, kAction102675536); + getEntities()->clearSequences(kEntitySalko); + break; + + case 3: + getEntities()->drawSequenceLeft(kEntitySalko, "BLANK"); + getData()->location = kLocationInsideCompartment; + + setup_function8(); + break; + } + break; + + case kAction125242096: + setCallback(3); + setup_function7(kCarRedSleeping, kPosition_2740); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Salko, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntitySalko); + + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Salko, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time < kTime2200500) { + UPDATE_PARAM(params->param1, getState()->time, 81000); + + setCallback(1); + setup_function16(); + } + break; + + case kActionCallback: + if (getCallback() == 1) + params->param1 = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Salko, function16) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->hasValidFrame(kEntitySalko) && getEntities()->isDistanceBetweenEntities(kEntitySalko, kEntityPlayer, 5000)) { + getSavePoints()->push(kEntitySalko, kEntityMax, kAction158007856); + + setCallback(3); + setup_updateFromTime(75); + break; + } + +label_callback3: + UPDATE_PARAM(params->param1, getState()->time, 4500); + + getSavePoints()->push(kEntitySalko, kEntitySalko, kAction101169464); + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("612DH", kObjectCompartmentH); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + + if (getData()->entityPosition < kPosition_2087) + getData()->entityPosition = kPosition_2088; + + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_4070); + break; + + case 2: + break; + + case 3: + getSavePoints()->push(kEntitySalko, kEntitySalko, kAction101169464); + goto label_callback3; + + case 4: + getEntities()->exitCompartment(kEntitySalko, kObjectCompartmentF, true); + + setCallback(5); + setup_updateEntity(kCarRedSleeping, kPosition_9460); + break; + + case 5: + setCallback(6); + setup_updateFromTime(4500); + break; + + case 6: + setCallback(7); + setup_updateEntity(kCarRedSleeping, kPosition_2740); + break; + + case 7: + setCallback(8); + setup_enterExitCompartment("612Ch", kObjectCompartmentH); + break; + + case 8: + getData()->location = kLocationInsideCompartment; + getData()->entityPosition = kPosition_2740; + getEntities()->clearSequences(kEntitySalko); + + CALLBACK_ACTION(); + break; + } + break; + + case kAction101169464: + setCallback(4); + setup_enterExitCompartment("612Bf", kObjectCompartmentF); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Salko, function17) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_6470; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_2740); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("612Ch", kObjectCompartmentH); + break; + + case 2: + getEntities()->clearSequences(kEntitySalko); + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getSavePoints()->push(kEntitySalko, kEntityMilos, kAction157691176); + + setup_chapter3Handler(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Salko, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntitySalko); + + getData()->entityPosition = kPosition_5420; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Salko, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionCallback: + if (getCallback() == 1) { + getEntities()->drawSequenceLeft(kEntitySalko, "BLANK"); + + getData()->location = kLocationInsideCompartment; + + setup_function20(); + } + break; + + case kAction125242096: + setCallback(1); + setup_function7(kCarRedSleeping, kPosition_2740); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Salko, function20) + switch (savepoint.action) { + default: + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + if (getData()->entityPosition < kPosition_2087) + getData()->entityPosition = kPosition_2088; + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + getEntities()->clearSequences(kEntitySalko); + setup_function21(); + break; + } + break; + + case kAction55996766: + setCallback(1); + setup_enterExitCompartment("612Dh", kObjectCompartmentH); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Salko, function21) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime2422800 && !params->param1) { + params->param1 = 1; + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_2740); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment("612Ch", kObjectCompartmentH); + break; + + case 2: + setup_function22(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Salko, function22) + if (savepoint.action == kActionDefault) { + getEntities()->clearSequences(kEntitySalko); + getObjects()->update(kObjectCompartmentH, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_2740; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->inventoryItem = kItemNone; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Salko, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntitySalko); + + getData()->entityPosition = kPosition_9460; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Salko, chapter5Handler) + switch (savepoint.action) { + default: + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (getSound()->isBuffered("MUS050")) + getSound()->processEntry("MUS050"); + + getAction()->playAnimation(kEventCathSalkoTrainTopFight); + + setCallback(2); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case 2: + params->param1 = getFight()->setup(kFightSalko); + + if (params->param1 == Fight::kFightEndWin) { + getState()->time += 1800; + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventCathSalkoTrainTopWin); + } else { + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneNone, params->param1 == Fight::kFightEndLost); + } + break; + + case 3: + getAction()->playAnimation(kEventCathSalkoTrainTopWin); + getSavePoints()->push(kEntitySalko, kEntityVesna, kAction134427424); + + getScenes()->loadSceneFromPosition(kCarRestaurant, 10); + setup_nullfunction(); + break; + } + break; + + case kAction167992577: + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventCathSalkoTrainTopFight); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(25, Salko) + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/salko.h b/engines/lastexpress/entities/salko.h new file mode 100644 index 0000000000..49059c4b40 --- /dev/null +++ b/engines/lastexpress/entities/salko.h @@ -0,0 +1,149 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SALKO_H +#define LASTEXPRESS_SALKO_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Salko : public Entity { +public: + Salko(LastExpressEngine *engine); + ~Salko() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Draws the entity + * + * @param savepoint The savepoint + * - The sequence to draw + */ + DECLARE_FUNCTION_NOSETUP(draw) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + DECLARE_FUNCTION_2(function7, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION(function8) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function11) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + DECLARE_FUNCTION(function13) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + DECLARE_FUNCTION(function16) + DECLARE_FUNCTION(function17) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function20) + DECLARE_FUNCTION(function21) + DECLARE_FUNCTION(function22) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SALKO_H diff --git a/engines/lastexpress/entities/servers0.cpp b/engines/lastexpress/entities/servers0.cpp new file mode 100644 index 0000000000..33f2943c43 --- /dev/null +++ b/engines/lastexpress/entities/servers0.cpp @@ -0,0 +1,1039 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/servers0.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +#define HANDLE_TABLE(index, param, callback, function) \ + if (ENTITY_PARAM(index, param)) { \ + setCallback(callback); \ + function(); \ + break; \ + } + +Servers0::Servers0(LastExpressEngine *engine) : Entity(engine, kEntityServers0) { + ADD_CALLBACK_FUNCTION(Servers0, callSavepoint); + ADD_CALLBACK_FUNCTION(Servers0, updateFromTime); + ADD_CALLBACK_FUNCTION(Servers0, draw); + ADD_CALLBACK_FUNCTION(Servers0, updatePosition); + ADD_CALLBACK_FUNCTION(Servers0, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Servers0, playSound); + ADD_CALLBACK_FUNCTION(Servers0, function7); + ADD_CALLBACK_FUNCTION(Servers0, function8); + ADD_CALLBACK_FUNCTION(Servers0, function9); + ADD_CALLBACK_FUNCTION(Servers0, function10); + ADD_CALLBACK_FUNCTION(Servers0, chapter1); + ADD_CALLBACK_FUNCTION(Servers0, function12); + ADD_CALLBACK_FUNCTION(Servers0, function13); + ADD_CALLBACK_FUNCTION(Servers0, function14); + ADD_CALLBACK_FUNCTION(Servers0, function15); + ADD_CALLBACK_FUNCTION(Servers0, function16); + ADD_CALLBACK_FUNCTION(Servers0, function17); + ADD_CALLBACK_FUNCTION(Servers0, function18); + ADD_CALLBACK_FUNCTION(Servers0, function19); + ADD_CALLBACK_FUNCTION(Servers0, chapter1Handler); + ADD_CALLBACK_FUNCTION(Servers0, function21); + ADD_CALLBACK_FUNCTION(Servers0, function22); + ADD_CALLBACK_FUNCTION(Servers0, chapter2); + ADD_CALLBACK_FUNCTION(Servers0, chapter2Handler); + ADD_CALLBACK_FUNCTION(Servers0, function25); + ADD_CALLBACK_FUNCTION(Servers0, function26); + ADD_CALLBACK_FUNCTION(Servers0, chapter3); + ADD_CALLBACK_FUNCTION(Servers0, chapter3Handler); + ADD_CALLBACK_FUNCTION(Servers0, augustAnnaDateOrder); + ADD_CALLBACK_FUNCTION(Servers0, function30); + ADD_CALLBACK_FUNCTION(Servers0, chapter4); + ADD_CALLBACK_FUNCTION(Servers0, chapter4Handler); + ADD_CALLBACK_FUNCTION(Servers0, augustOrderSteak); + ADD_CALLBACK_FUNCTION(Servers0, augustServeDuck); + ADD_CALLBACK_FUNCTION(Servers0, function35); + ADD_CALLBACK_FUNCTION(Servers0, chapter5); + ADD_CALLBACK_FUNCTION(Servers0, chapter5Handler); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SIIS(1, Servers0, callSavepoint, EntityIndex, ActionIndex) + Entity::callSavepoint(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(2, Servers0, updateFromTime) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(3, Servers0, draw) + Entity::draw(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(4, Servers0, updatePosition) + Entity::updatePosition(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(5, Servers0, callbackActionOnDirection) + EXPOSE_PARAMS(EntityData::EntityParametersIIII); + + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getData()->direction != kDirectionRight) + CALLBACK_ACTION(); + break; + + case kActionExitCompartment: + CALLBACK_ACTION(); + break; + + case kActionExcuseMeCath: + if (!params->param1) { + getSound()->excuseMe(kEntityServers0); + params->param1 = 1; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(6, Servers0, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Servers0, function7) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + ENTITY_PARAM(0, 3) = 0; + + setCallback(1); + setup_draw("911"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->clearSequences(kEntityServers0); + getSavePoints()->push(kEntityServers0, kEntityRebecca, kAction123712592); + break; + + case 2: + getEntities()->clearSequences(kEntityServers0); + getData()->entityPosition = kPosition_5900; + CALLBACK_ACTION(); + break; + } + break; + + case kAction136702400: + setCallback(2); + setup_draw("913"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Servers0, function8) + serveTable(savepoint, "911", kEntityTables3, "010L", "010M", "913", &ENTITY_PARAM(1, 2)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Servers0, function9) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("915"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityServers0, kEntityAbbot, kAction122358304); + getEntities()->drawSequenceLeft(kEntityServers0, "029D"); + + setCallback(2); + setup_playSound(getProgress().chapter == kChapter3 ? "Abb3016" : "Abb4001"); + break; + + case 2: + getSavePoints()->push(kEntityServers0, kEntityAbbot, kAction122288808); + + setCallback(3); + setup_draw("917"); + break; + + case 3: + getData()->entityPosition = kPosition_5900; + getEntities()->clearSequences(kEntityServers0); + ENTITY_PARAM(2, 2) = 0; + ENTITY_PARAM(1, 6) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Servers0, function10) + serveTable(savepoint, "916", kEntityTables4, "014E", "014F", "918", &ENTITY_PARAM(2, 3), false); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Servers0, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter1Handler(); + break; + + case kActionDefault: + getSavePoints()->addData(kEntityServers0, kAction270410280, 0); + getSavePoints()->addData(kEntityServers0, kAction304061224, 1); + getSavePoints()->addData(kEntityServers0, kAction252568704, 10); + getSavePoints()->addData(kEntityServers0, kAction286534136, 11); + getSavePoints()->addData(kEntityServers0, kAction218983616, 12); + getSavePoints()->addData(kEntityServers0, kAction218586752, 13); + getSavePoints()->addData(kEntityServers0, kAction207330561, 14); + getSavePoints()->addData(kEntityServers0, kAction286403504, 16); + getSavePoints()->addData(kEntityServers0, kAction218128129, 17); + getSavePoints()->addData(kEntityServers0, kAction270068760, 18); + getSavePoints()->addData(kEntityServers0, kAction223712416, 2); + getSavePoints()->addData(kEntityServers0, kAction237485916, 5); + getSavePoints()->addData(kEntityServers0, kAction188893625, 8); + getSavePoints()->addData(kEntityServers0, kAction204704037, 6); + getSavePoints()->addData(kEntityServers0, kAction292758554, 7); + getSavePoints()->addData(kEntityServers0, kAction337548856, 9); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Servers0, function12) + handleServer(savepoint, "907", kEntityAnna, kAction268773672, &ENTITY_PARAM(0, 1)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Servers0, function13) + handleServer(savepoint, "911", kEntityAugust, kAction268773672, &ENTITY_PARAM(0, 2), "010F"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Servers0, function14) + handleServer(savepoint, "908", kEntityAnna, kAction170016384, &ENTITY_PARAM(0, 4)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Servers0, function15) + handleServer(savepoint, "912", kEntityAugust, kAction170016384, &ENTITY_PARAM(0, 5)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Servers0, function16) + serveTable(savepoint, "907", kEntityTables0, "001N", "001P", "909", &ENTITY_PARAM(0, 6)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Servers0, function17) + serveTable(savepoint, "915", kEntityTables4, "014E", "014F", "917", &ENTITY_PARAM(1, 1), true, false, 67); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Servers0, function18) + serveTable(savepoint, "911", kEntityTables3, "010L", "010H", "913", &ENTITY_PARAM(0, 7)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Servers0, function19) + serveTable(savepoint, "911", kEntityTables3, "010L", "010M", "913", &ENTITY_PARAM(0, 8), true, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Servers0, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param2) { + UPDATE_PARAM_PROC(params->param3, getState()->time, 2700); + ENTITY_PARAM(0, 4) = 1; + params->param2 = 0; + UPDATE_PARAM_PROC_END + } + + if (params->param1) { + UPDATE_PARAM_PROC(params->param4, getState()->time, 4500) + ENTITY_PARAM(0, 5) = 1; + params->param1 = 0; + UPDATE_PARAM_PROC_END + } + + if (!getEntities()->isInKitchen(kEntityServers0) && !getEntities()->isSomebodyInsideRestaurantOrSalon()) + break; + + HANDLE_TABLE(0, 1, 1, setup_function12); + HANDLE_TABLE(0, 2, 2, setup_function13); + HANDLE_TABLE(0, 3, 3, setup_function7); + HANDLE_TABLE(0, 4, 4, setup_function14); + HANDLE_TABLE(0, 5, 5, setup_function15); + HANDLE_TABLE(0, 6, 6, setup_function16); + HANDLE_TABLE(1, 1, 7, setup_function17); + HANDLE_TABLE(0, 7, 8, setup_function18); + HANDLE_TABLE(0, 8, 9, setup_function19); + HANDLE_TABLE(1, 2, 10, setup_function8); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 10: + getSavePoints()->push(kEntityServers0, kEntityPascale, kAction352703104); + setup_function21(); + break; + + case 11: + case 12: + getEntities()->clearSequences(kEntityServers0); + getData()->entityPosition = kPosition_5900; + + if (getCallback() == 11) + params->param2 = 1; + else + params->param1 = 1; + break; + + case 13: + case 14: + getEntities()->clearSequences(kEntityServers0); + getData()->entityPosition = kPosition_5900; + break; + } + break; + + case kAction136702400: + setCallback(savepoint.entity2 == kEntityAnna ? 13 : 14); + setup_draw(savepoint.entity2 == kEntityAnna ? "909" : "913"); + break; + + case kAction203859488: + setCallback(savepoint.entity2 == kEntityAnna ? 11 : 12); + setup_draw(savepoint.entity2 == kEntityAnna ? "910" : "913"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Servers0, function21) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5900; + break; + + case kAction101632192: + setup_function22(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Servers0, function22) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + + getEntities()->clearSequences(kEntityServers0); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Servers0, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityServers0); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothes1; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(1, 3) = 0; + ENTITY_PARAM(1, 4) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Servers0, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!getEntities()->isInKitchen(kEntityServers0) || !getEntities()->isSomebodyInsideRestaurantOrSalon()) + break; + + HANDLE_TABLE(1, 3, 1, setup_function25); + HANDLE_TABLE(1, 4, 2, setup_function26); + break; + + case kActionCallback: + if (getCallback() == 1) + HANDLE_TABLE(1, 4, 2, setup_function26); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Servers0, function25) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("957"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityServers0, kEntityAugust, kAction123712592); + getEntities()->drawSequenceLeft(kEntityServers0, "BLANK"); + break; + + case 2: + getData()->entityPosition = kPosition_5900; + getEntities()->clearSequences(kEntityServers0); + ENTITY_PARAM(1, 3) = 0; + + CALLBACK_ACTION(); + break; + } + break; + + case kAction219522616: + setCallback(2); + setup_draw("959"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Servers0, function26) + serveTable(savepoint, "957", kEntityTables0, "016E", "016D", "959", &ENTITY_PARAM(1, 4)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Servers0, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityServers0); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothes1; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 3) = 0; + ENTITY_PARAM(1, 2) = 0; + ENTITY_PARAM(1, 5) = 0; + ENTITY_PARAM(1, 6) = 0; + ENTITY_PARAM(2, 3) = 0; + ENTITY_PARAM(2, 4) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Servers0, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!getEntities()->isInKitchen(kEntityServers1) || !getEntities()->isSomebodyInsideRestaurantOrSalon()) + break; + + if (ENTITY_PARAM(1, 5)) { + setCallback(1); + setup_augustAnnaDateOrder(); + break; + } + +label_callback_1: + if (ENTITY_PARAM(1, 6)) { + setCallback(2); + setup_function9(); + break; + } + +label_callback_2: + if (ENTITY_PARAM(2, 4)) { + setCallback(3); + setup_function30(); + break; + } + +label_callback_3: + if (ENTITY_PARAM(2, 3)) { + setCallback(4); + setup_function10(); + break; + } + +label_callback_4: + if (ENTITY_PARAM(0, 3)) { + setCallback(5); + setup_function7(); + break; + } + +label_callback_5: + if (ENTITY_PARAM(1, 2)) { + setCallback(6); + setup_function8(); + break; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback_1; + + case 2: + goto label_callback_2; + + case 3: + goto label_callback_3; + + case 4: + goto label_callback_4; + + case 5: + goto label_callback_5; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Servers0, augustAnnaDateOrder) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("911"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityServers0, kEntityAnna, kAction122358304); + getEntities()->drawSequenceLeft(kEntityServers0, "026D"); + + setCallback(2); + setup_playSound("Ann3138"); + break; + + case 2: + getSavePoints()->push(kEntityServers0, kEntityAnna, kAction122288808); + + setCallback(3); + setup_draw("913"); + break; + + case 3: + getData()->entityPosition = kPosition_5900; + getEntities()->clearSequences(kEntityServers0); + ENTITY_PARAM(1, 5) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Servers0, function30) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("916"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityServers0, kEntityAbbot, kAction122358304); + getEntities()->drawSequenceLeft(kEntityServers0, "029D"); + + setCallback(2); + setup_playSound("Abb3016a"); + break; + + case 2: + getSavePoints()->push(kEntityServers0, kEntityAbbot, kAction122288808); + + setCallback(3); + setup_draw("918"); + break; + + case 3: + getData()->entityPosition = kPosition_5900; + getEntities()->clearSequences(kEntityServers0); + ENTITY_PARAM(2, 4) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, Servers0, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityServers0); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 3) = 0; + ENTITY_PARAM(1, 7) = 0; + ENTITY_PARAM(1, 8) = 0; + ENTITY_PARAM(2, 1) = 0; + ENTITY_PARAM(2, 2) = 0; + ENTITY_PARAM(2, 3) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Servers0, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM_PROC(params->param2, getState()->time, 3600) + ENTITY_PARAM(1, 8) = 1; + params->param1 = 0; + UPDATE_PARAM_PROC_END + + if (!getEntities()->isInKitchen(kEntityServers1) || !getEntities()->isSomebodyInsideRestaurantOrSalon()) + break; + + if (ENTITY_PARAM(1, 7)) { + setCallback(1); + setup_augustOrderSteak(); + break; + } + +label_callback_1: + if (ENTITY_PARAM(1, 8)) { + setCallback(2); + setup_augustServeDuck(); + break; + } + +label_callback_2: + if (ENTITY_PARAM(2, 1)) { + setCallback(3); + setup_function35(); + break; + } + +label_callback_3: + if (ENTITY_PARAM(2, 2)) { + setCallback(4); + setup_function9(); + break; + } + +label_callback_4: + if (ENTITY_PARAM(2, 3)) { + setCallback(5); + setup_function10(); + break; + } + +label_callback_5: + if (ENTITY_PARAM(0, 3)) { + setCallback(6); + setup_function7(); + break; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + params->param1 = 1; + goto label_callback_1; + + case 2: + goto label_callback_2; + + case 3: + goto label_callback_3; + + case 4: + goto label_callback_4; + + case 5: + goto label_callback_5; + } + break; + + case kAction201431954: + ENTITY_PARAM(0, 3) = 0; + ENTITY_PARAM(1, 7) = 0; + ENTITY_PARAM(1, 8) = 0; + ENTITY_PARAM(2, 1) = 0; + ENTITY_PARAM(2, 3) = 0; + params->param1 = 0; + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Servers0, augustOrderSteak) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_draw("911"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityServers0, "010F3"); + getEntities()->drawSequenceLeft(kEntityAugust, "010D3"); + + setCallback(2); + setup_playSound("AUG4002"); + break; + + case 2: + getSavePoints()->push(kEntityServers0, kEntityAugust, kAction122288808); + + setCallback(3); + setup_draw("913"); + break; + + case 3: + getData()->entityPosition = kPosition_5900; + getEntities()->clearSequences(kEntityServers0); + ENTITY_PARAM(1, 7) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(34, Servers0, augustServeDuck) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_draw("912"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityServers0, kEntityAugust, kAction122358304); + getSound()->playSound(kEntityServers0, "AUG1053"); + + setCallback(2); + setup_draw("010G3"); + break; + + case 2: + getSavePoints()->push(kEntityServers0, kEntityAugust, kAction201964801); + + setCallback(3); + setup_draw("914"); + break; + + case 3: + getData()->entityPosition = kPosition_5900; + getEntities()->clearSequences(kEntityServers0); + ENTITY_PARAM(1, 8) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(35, Servers0, function35) + serveTable(savepoint, "911", kEntityTables3, "010L", "010M", "914", &ENTITY_PARAM(2, 1), false, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(36, Servers0, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityServers0); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(37, Servers0, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_nullfunction(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(38, Servers0) + + +////////////////////////////////////////////////////////////////////////// +// Private functions +////////////////////////////////////////////////////////////////////////// +void Servers0::handleServer(const SavePoint &savepoint, const char* name, EntityIndex entity, ActionIndex action, uint *parameter, const char* name2) { + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw(name); + break; + + case kActionCallback: + if (getCallback() == 1) { + // Prepare or draw sequences depending of value of string + if (strcmp(name2, "")) + getEntities()->clearSequences(kEntityServers0); + else + getEntities()->drawSequenceLeft(kEntityServers0, name2); + + getSavePoints()->push(kEntityServers0, entity, action); + *parameter = 0; + + CALLBACK_ACTION(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +void Servers0::serveTable(const SavePoint &savepoint, const char* seq1, EntityIndex entity, const char* seq2, const char* seq3, const char* seq4, uint *parameter, bool shouldUpdatePosition, bool pushSavepoint, Position position) { + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (shouldUpdatePosition) { + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + } + + setCallback(1); + setup_draw(seq1); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (position) + getEntities()->updatePositionEnter(kEntityServers0, kCarRestaurant, position); + + getSavePoints()->push(kEntityServers0, entity, kAction136455232); + + setCallback(2); + setup_callSavepoint(seq2, entity, kActionDrawTablesWithChairs, seq3); + break; + + case 2: + if (position) + getEntities()->updatePositionExit(kEntityServers0, kCarRestaurant, position); + + setCallback(3); + setup_draw(seq4); + break; + + case 3: + getData()->entityPosition = kPosition_5900; + + // Special case for functions 19 & 35 + if (pushSavepoint) + getSavePoints()->push(kEntityServers0, kEntityRebecca, kAction224253538); + + getEntities()->clearSequences(kEntityServers0); + *parameter = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/servers0.h b/engines/lastexpress/entities/servers0.h new file mode 100644 index 0000000000..61a060f0c2 --- /dev/null +++ b/engines/lastexpress/entities/servers0.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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SERVERS0_H +#define LASTEXPRESS_SERVERS0_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Servers0 : public Entity { +public: + Servers0(LastExpressEngine *engine); + ~Servers0() {}; + + + /** + * Call a savepoint (or draw sequence in default case) + * + * @param sequence1 The sequence to draw in the default case + * @param entity The entity + * @param action The action + * @param sequence2 The sequence name for the savepoint + */ + DECLARE_FUNCTION_4(callSavepoint, const char* sequence1, EntityIndex entity, ActionIndex action, const char* sequence2) + + /** + * Updates parameter 2 using time value + * + * @param savepoint The savepoint + * - Time to add + */ + DECLARE_FUNCTION_NOSETUP(updateFromTime) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Updates the position + * + * @param sequence1 The sequence to draw + * @param car The car + * @param position The position + */ + DECLARE_FUNCTION_3(updatePosition, const char* sequence1, CarIndex car, Position position) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION_NOSETUP(callbackActionOnDirection) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + DECLARE_FUNCTION(function7) + DECLARE_FUNCTION(function8) + DECLARE_FUNCTION(function9) + DECLARE_FUNCTION(function10) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + DECLARE_FUNCTION(function12) + DECLARE_FUNCTION(function13) + DECLARE_FUNCTION(function14) + DECLARE_FUNCTION(function15) + DECLARE_FUNCTION(function16) + DECLARE_FUNCTION(function17) + DECLARE_FUNCTION(function18) + DECLARE_FUNCTION(function19) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function21) + DECLARE_FUNCTION(function22) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + DECLARE_FUNCTION(function25) + DECLARE_FUNCTION(function26) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + DECLARE_FUNCTION(augustAnnaDateOrder) + DECLARE_FUNCTION(function30) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(augustOrderSteak) + DECLARE_FUNCTION(augustServeDuck) + DECLARE_FUNCTION(function35) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_NULL_FUNCTION() + +private: + void handleServer(const SavePoint &savepoint, const char* name, EntityIndex entity, ActionIndex action, uint *parameter, const char* name2 = ""); + void serveTable(const SavePoint &savepoint, const char* seq1, EntityIndex entity, const char* seq2, const char* seq3, const char* seq4, uint *parameter, bool shouldUpdatePosition = true, bool pushSavepoint = false, Position position = 0); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SERVERS0_H diff --git a/engines/lastexpress/entities/servers1.cpp b/engines/lastexpress/entities/servers1.cpp new file mode 100644 index 0000000000..ea383dbacb --- /dev/null +++ b/engines/lastexpress/entities/servers1.cpp @@ -0,0 +1,787 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/servers1.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Servers1::Servers1(LastExpressEngine *engine) : Entity(engine, kEntityServers1) { + ADD_CALLBACK_FUNCTION(Servers1, updateFromTime); + ADD_CALLBACK_FUNCTION(Servers1, draw); + ADD_CALLBACK_FUNCTION(Servers1, updatePosition); + ADD_CALLBACK_FUNCTION(Servers1, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Servers1, callSavepoint); + ADD_CALLBACK_FUNCTION(Servers1, playSound); + ADD_CALLBACK_FUNCTION(Servers1, function7); + ADD_CALLBACK_FUNCTION(Servers1, chapter1); + ADD_CALLBACK_FUNCTION(Servers1, function9); + ADD_CALLBACK_FUNCTION(Servers1, function10); + ADD_CALLBACK_FUNCTION(Servers1, function11); + ADD_CALLBACK_FUNCTION(Servers1, function12); + ADD_CALLBACK_FUNCTION(Servers1, function13); + ADD_CALLBACK_FUNCTION(Servers1, chapter1Handler); + ADD_CALLBACK_FUNCTION(Servers1, function15); + ADD_CALLBACK_FUNCTION(Servers1, function16); + ADD_CALLBACK_FUNCTION(Servers1, chapter2); + ADD_CALLBACK_FUNCTION(Servers1, chapter2Handler); + ADD_CALLBACK_FUNCTION(Servers1, function19); + ADD_CALLBACK_FUNCTION(Servers1, function20); + ADD_CALLBACK_FUNCTION(Servers1, function21); + ADD_CALLBACK_FUNCTION(Servers1, chapter3); + ADD_CALLBACK_FUNCTION(Servers1, chapter3Handler); + ADD_CALLBACK_FUNCTION(Servers1, function24); + ADD_CALLBACK_FUNCTION(Servers1, chapter4); + ADD_CALLBACK_FUNCTION(Servers1, chapter4Handler); + ADD_CALLBACK_FUNCTION(Servers1, function27); + ADD_CALLBACK_FUNCTION(Servers1, function28); + ADD_CALLBACK_FUNCTION(Servers1, function29); + ADD_CALLBACK_FUNCTION(Servers1, chapter5); + ADD_CALLBACK_FUNCTION(Servers1, chapter5Handler); + ADD_NULL_FUNCTION() +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(1, Servers1, updateFromTime) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Servers1, draw) + Entity::draw(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SII(3, Servers1, updatePosition, CarIndex, Position) + Entity::updatePosition(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(4, Servers1, callbackActionOnDirection) + if (savepoint.action == kActionExcuseMeCath) { + if (!params->param1) { + getSound()->excuseMe(kEntityServers1); + params->param1 = 1; + } + } + + Entity::callbackActionOnDirection(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SIIS(5, Servers1, callSavepoint, EntityIndex, ActionIndex) + Entity::callSavepoint(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(6, Servers1, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Servers1, function7) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("924"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getSavePoints()->push(kEntityServers1, kEntityBoutarel, kAction122358304); + setCallback(2); + setup_draw("008C"); + break; + + case 2: + getSavePoints()->push(kEntityServers1, kEntityBoutarel, kAction122288808); + setCallback(2); + setup_draw("926"); + break; + + case 3: + getEntities()->clearSequences(kEntityServers1); + getData()->entityPosition = kPosition_5900; + ENTITY_PARAM(1, 2) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Servers1, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter1Handler(); + break; + + case kActionDefault: + getSavePoints()->addData(kEntityServers1, kAction223002560, 0); + getSavePoints()->addData(kEntityServers1, kAction302996448, 2); + getSavePoints()->addData(kEntityServers1, kAction269485588, 3); + getSavePoints()->addData(kEntityServers1, kAction326144276, 4); + getSavePoints()->addData(kEntityServers1, kAction302203328, 5); + getSavePoints()->addData(kEntityServers1, kAction189688608, 6); + getSavePoints()->addData(kEntityServers1, kAction236237423, 7); + getSavePoints()->addData(kEntityServers1, kAction219377792, 8); + getSavePoints()->addData(kEntityServers1, kAction256200848, 9); + getSavePoints()->addData(kEntityServers1, kAction291721418, 10); + getSavePoints()->addData(kEntityServers1, kAction258136010, 11); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Servers1, function9) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("924"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityMilos, "BLANK"); + getEntities()->drawSequenceLeft(kEntityServers1, "009B"); + + setCallback(2); + setup_playSound("WAT1001"); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityMilos, "009A"); + + setCallback(3); + setup_draw("926"); + break; + + case 3: + getEntities()->clearSequences(kEntityServers1); + getData()->entityPosition = kPosition_5900; + ENTITY_PARAM(0, 1) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Servers1, function10) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("924"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityBoutarel, "BLANK"); + getEntities()->drawSequenceLeft(kEntityServers1, "008C"); + + setCallback(2); + setup_playSound("MRB1077"); + break; + + case 2: + getSavePoints()->push(kEntityServers1, kEntityBoutarel, kAction168717392); + + setCallback(3); + setup_draw("926"); + break; + + case 3: + getEntities()->clearSequences(kEntityServers1); + getData()->entityPosition = kPosition_5900; + ENTITY_PARAM(0, 2) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Servers1, function11) + serveTable(savepoint, "919", kEntityTables1, "005H", "005J", "921", &ENTITY_PARAM(0, 3), 63); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Servers1, function12) + serveTable(savepoint, "923", kEntityTables2, "009F", "009G", "926", &ENTITY_PARAM(0, 4)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Servers1, function13) + serveTable(savepoint, "923", kEntityTables2, "009F", "009G", "926", &ENTITY_PARAM(0, 5)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Servers1, chapter1Handler) +switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (!getEntities()->isInKitchen(kEntityServers1) || !getEntities()->isSomebodyInsideRestaurantOrSalon()) + break; + + if (ENTITY_PARAM(0, 1)) { + setCallback(1); + setup_function9(); + break; + } + + if (ENTITY_PARAM(1, 2)) { + setCallback(2); + setup_function10(); + break; + } + + if (ENTITY_PARAM(0, 3)) { + setCallback(3); + setup_function11(); + break; + } + + if (ENTITY_PARAM(0, 4)) { + setCallback(4); + setup_function12(); + break; + } + + if (ENTITY_PARAM(0, 5)) { + setCallback(5); + setup_function13(); + } + break; + + case kActionCallback: + if (getCallback() == 5) { + getSavePoints()->push(kEntityServers1, kEntityPascale, kAction352768896); + setup_function15(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Servers1, function15) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5900; + break; + + case kAction101632192: + setup_function16(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Servers1, function16) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + + getEntities()->clearSequences(kEntityServers1); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Servers1, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityServers1); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothes1; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 6) = 0; + ENTITY_PARAM(0, 7) = 0; + ENTITY_PARAM(0, 8) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Servers1, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!getEntities()->isInKitchen(kEntityServers1) || !getEntities()->isSomebodyInsideRestaurantOrSalon()) + break; + + if (ENTITY_PARAM(0, 6)) { + setCallback(1); + setup_function19(); + break; + } + +label_callback_1: + if (ENTITY_PARAM(0, 7)) { + setCallback(2); + setup_function20(); + break; + } + +label_callback_2: + if (ENTITY_PARAM(0, 8) || ENTITY_PARAM(0, 5)) { + setCallback(3); + setup_function21(); + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback_1; + + case 2: + goto label_callback_2; + + case 4: + getEntities()->clearSequences(kEntityServers1); + getData()->entityPosition = kPosition_5900; + break; + } + break; + + case kAction101106391: + setCallback(4); + setup_draw("975"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Servers1, function19) + serveTable(savepoint, "969", kEntityTables1, "005H2", "018A", "971", &ENTITY_PARAM(0, 6), 63); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Servers1, function20) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("973"); + break; + + case kActionCallback: + if (getCallback() == 1) { + getSavePoints()->push(kEntityServers1, kEntityIvo, kAction123712592); + getEntities()->drawSequenceLeft(kEntityServers1, "BLANK"); + ENTITY_PARAM(0, 7) = 0; + + CALLBACK_ACTION(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Servers1, function21) + serveTable(savepoint, "974", kEntityTables2, "009F2", "009G", "976", &ENTITY_PARAM(0, 8), 0, true, &ENTITY_PARAM(0, 5)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Servers1, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityServers1); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothes1; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(1, 1) = 0; + ENTITY_PARAM(1, 2) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Servers1, chapter3Handler) + if (savepoint.action != kActionNone) + return; + + if (!getEntities()->isInKitchen(kEntityServers1) || !getEntities()->isSomebodyInsideRestaurantOrSalon()) + return; + + if (ENTITY_PARAM(1, 1)) { + setCallback(1); + setup_function24(); + return; + } + + if (ENTITY_PARAM(1, 2)) { + setCallback(2); + setup_function7(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Servers1, function24) + serveSalon(savepoint, "927", "Ann3143A", kEntityAnna, "Ann31444", "112C", kAction122288808, "928", &ENTITY_PARAM(1, 1)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Servers1, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityServers1); + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + + getEntities()->clearSequences(kEntityServers1); + + ENTITY_PARAM(1, 2) = 0; + ENTITY_PARAM(1, 3) = 0; + ENTITY_PARAM(1, 4) = 0; + ENTITY_PARAM(1, 5) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Servers1, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param2) { + UPDATE_PARAM_PROC(params->param2, getState()->time, 900) + ENTITY_PARAM(1, 5) = 1; + params->param1 = 0; + UPDATE_PARAM_PROC_END + } + + if (!getEntities()->isInKitchen(kEntityServers1) || !getEntities()->isSomebodyInsideRestaurantOrSalon()) + break; + + if (ENTITY_PARAM(1, 5)) { + setCallback(2); + setup_function28(); + break; + } + + if (ENTITY_PARAM(1, 4)) { + setCallback(3); + setup_function29(); + break; + } + + if (ENTITY_PARAM(1, 2)) { + setCallback(4); + setup_function7(); + } + break; + + case kActionCallback: + if (getCallback() == 1) + params->param1 = 1; + break; + + case kAction201431954: + ENTITY_PARAM(1, 2) = 0; + ENTITY_PARAM(1, 3) = 0; + ENTITY_PARAM(1, 4) = 0; + ENTITY_PARAM(1, 5) = 0; + + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationOutsideCompartment; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Servers1, function27) + serveSalon(savepoint, "929", "", kEntityAugust, "Aug4003", "122D", kAction134486752, "930", &ENTITY_PARAM(1, 3)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Servers1, function28) + serveSalon(savepoint, "931", "", kEntityAugust, "Aug4004", "122E", kAction125826561, "930", &ENTITY_PARAM(1, 5)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Servers1, function29) + serveSalon(savepoint, "932", "", kEntityAnna, "Ann4151", "127D", kAction122288808, "930", &ENTITY_PARAM(1, 4)); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Servers1, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityServers1); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, Servers1, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_nullfunction(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(32, Servers1) + + +////////////////////////////////////////////////////////////////////////// +// Private functions +////////////////////////////////////////////////////////////////////////// +void Servers1::serveTable(const SavePoint &savepoint, const char* seq1, EntityIndex entity, const char* seq2, const char* seq3, const char* seq4, uint *parameter, Position position, bool shouldUpdatePosition, uint* parameter2) { + switch (savepoint.action) { + default: + break; + + case kActionDefault: + if (shouldUpdatePosition) { + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + } + + setCallback(1); + setup_draw(seq1); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (position) + getEntities()->updatePositionEnter(kEntityServers1, kCarRestaurant, position); + + getSavePoints()->push(kEntityServers1, entity, kAction136455232); + + setCallback(2); + setup_callSavepoint(seq2, entity, kActionDrawTablesWithChairs, seq3); + break; + + case 2: + if (position) + getEntities()->updatePositionExit(kEntityServers1, kCarRestaurant, position); + + setCallback(3); + setup_draw(seq4); + break; + + case 3: + getData()->entityPosition = kPosition_5900; + getEntities()->clearSequences(kEntityServers1); + *parameter = 0; + + if (parameter2 != NULL) + *parameter2 = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +void Servers1::serveSalon(const SavePoint &savepoint, const char* seq1, const char* snd1, EntityIndex entity, const char* snd2, const char* seq2, ActionIndex action, const char* seq3, uint *parameter) { + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_draw("816DD"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceRight(kEntityServers1, seq1); + + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityServers1); + + if (!strcmp(snd1, "")) + getSound()->playSound(kEntityServers1, snd1); + + setCallback(2); + setup_callbackActionOnDirection(); + break; + + case 2: + getSavePoints()->push(kEntityServers1, entity, kAction122358304); + + getSound()->playSound(kEntityServers1, snd2); + + setCallback(3); + setup_updatePosition(seq2, kCarRestaurant, 57); + break; + + case 3: + getSavePoints()->push(kEntityServers1, entity, action); + + setCallback(4); + setup_draw(seq3); + break; + + case 4: + getEntities()->drawSequenceRight(kEntityServers1, "816UD"); + + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityServers1); + + setCallback(5); + setup_callbackActionOnDirection(); + break; + + case 5: + getEntities()->clearSequences(kEntityServers1); + getData()->entityPosition = kPosition_5900; + *parameter = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/servers1.h b/engines/lastexpress/entities/servers1.h new file mode 100644 index 0000000000..f05d994d21 --- /dev/null +++ b/engines/lastexpress/entities/servers1.h @@ -0,0 +1,167 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SERVERS1_H +#define LASTEXPRESS_SERVERS1_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Servers1 : public Entity { +public: + Servers1(LastExpressEngine *engine); + ~Servers1() {}; + + /** + * Updates parameter 2 using time value + * + * @param savepoint The savepoint + * - Time to add + */ + DECLARE_FUNCTION_NOSETUP(updateFromTime) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Updates the position + * + * @param sequence1 The sequence to draw + * @param car The car + * @param position The position + */ + DECLARE_FUNCTION_3(updatePosition, const char *sequence, CarIndex car, Position position) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Call a savepoint (or draw sequence in default case) + * + * @param sequence1 The sequence to draw in the default case + * @param entity The entity + * @param action The action + * @param sequence2 The sequence name for the savepoint + */ + DECLARE_FUNCTION_4(callSavepoint, const char* sequence1, EntityIndex entity, ActionIndex action, const char* sequence2) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + DECLARE_FUNCTION(function7) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + DECLARE_FUNCTION(function9) + DECLARE_FUNCTION(function10) + DECLARE_FUNCTION(function11) + DECLARE_FUNCTION(function12) + DECLARE_FUNCTION(function13) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function15) + DECLARE_FUNCTION(function16) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + DECLARE_FUNCTION(function19) + DECLARE_FUNCTION(function20) + DECLARE_FUNCTION(function21) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + DECLARE_FUNCTION(function24) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function27) + DECLARE_FUNCTION(function28) + DECLARE_FUNCTION(function29) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_NULL_FUNCTION() + +private: + void serveTable(const SavePoint &savepoint, const char* seq1, EntityIndex entity, const char* seq2, const char* seq3, const char* seq4, uint *parameter, Position position = 0, bool updatePosition = true, uint *parameter2 = NULL); + void serveSalon(const SavePoint &savepoint, const char* seq1, const char* snd1, EntityIndex entity, const char* snd2, const char* seq2, ActionIndex action, const char* seq3, uint *parameter); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SERVERS1_H diff --git a/engines/lastexpress/entities/sophie.cpp b/engines/lastexpress/entities/sophie.cpp new file mode 100644 index 0000000000..bab74ad1f2 --- /dev/null +++ b/engines/lastexpress/entities/sophie.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. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/sophie.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +#define CHAPTER_IMPLEMENTATION() \ + switch (savepoint.action) { \ + default: \ + break; \ + case kActionNone: \ + setup_chaptersHandler(); \ + break; \ + case kActionDefault: \ + getEntities()->clearSequences(kEntitySophie); \ + getData()->entityPosition = kPosition_4840; \ + getData()->location = kLocationInsideCompartment; \ + getData()->car = kCarRedSleeping; \ + getData()->clothes = kClothesDefault; \ + getData()->inventoryItem = kItemNone; \ + break; \ + } + +#define DEFAULT_ACTION_IMPLEMENTATION() \ + if (savepoint.action == kActionDefault) { \ + getData()->entityPosition = kPosition_4840; \ + getData()->location = kLocationInsideCompartment; \ + getData()->car = kCarRedSleeping; \ + getEntities()->clearSequences(kEntitySophie); \ + } + +Sophie::Sophie(LastExpressEngine *engine) : Entity(engine, kEntitySophie) { + ADD_CALLBACK_FUNCTION(Sophie, reset); + ADD_CALLBACK_FUNCTION(Sophie, updateEntity); + ADD_CALLBACK_FUNCTION(Sophie, chaptersHandler); + ADD_CALLBACK_FUNCTION(Sophie, chapter1); + ADD_CALLBACK_FUNCTION(Sophie, function5); + ADD_CALLBACK_FUNCTION(Sophie, chapter2); + ADD_CALLBACK_FUNCTION(Sophie, chapter3); + ADD_CALLBACK_FUNCTION(Sophie, chapter4); + ADD_CALLBACK_FUNCTION(Sophie, function9); + ADD_CALLBACK_FUNCTION(Sophie, chapter5); + ADD_CALLBACK_FUNCTION(Sophie, chapter5Handler); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Sophie, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(2, Sophie, updateEntity, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: { + params->param3 = 0; + + // Sophie + byte direction = getData()->direction; + EntityPosition position = getData()->entityPosition; + CarIndex car = getData()->car; + + // Rebecca + EntityPosition rebecca_position = getEntityData(kEntityRebecca)->entityPosition; + CarIndex rebeccaCar = getEntityData(kEntityRebecca)->car; + + if (getEntities()->isDistanceBetweenEntities(kEntitySophie, kEntityRebecca, 500) + || (direction == kDirectionUp && car >= rebeccaCar && position > rebecca_position) + || (direction == kDirectionDown && car <= rebeccaCar && position < rebecca_position)) { + getData()->field_49B = 0; + params->param3 = 1; + } + + if (!params->param3) + getEntities()->updateEntity(kEntitySophie, (CarIndex)params->param1, (EntityPosition)params->param2); + + break; + } + + case kActionExcuseMeCath: + getSound()->excuseMeCath(); + break; + + case kActionExcuseMe: + getSound()->excuseMe(kEntitySophie); + break; + + case kActionDefault: + getEntities()->updateEntity(kEntitySophie, (CarIndex)params->param1, (EntityPosition)params->param2); + break; + + case kAction123668192: + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(3, Sophie, chaptersHandler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + getData()->entityPosition = getEntityData(kEntityRebecca)->entityPosition; + getData()->car = getEntityData(kEntityRebecca)->car; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->clearSequences(kEntitySophie); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntitySophie, "BLANK"); + break; + + case 3: + getEntities()->clearSequences(kEntitySophie); + break; + + case 4: + getEntities()->drawSequenceLeft(kEntitySophie, "BLANK"); + break; + } + break; + + case kAction125242096: + getData()->entityPosition = (EntityPosition)(getEntityData(kEntityRebecca)->entityPosition - 100); + getData()->location = getEntityData(kEntityRebecca)->location; + getData()->car = getEntityData(kEntityRebecca)->car; + + setCallback(1); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case kAction136654208: + getData()->entityPosition = (EntityPosition)(getEntityData(kEntityRebecca)->entityPosition + 100); + getData()->location = getEntityData(kEntityRebecca)->location; + getData()->car = getEntityData(kEntityRebecca)->car; + + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_4840); + break; + + case kAction259921280: + getData()->entityPosition = (EntityPosition)(getEntityData(kEntityRebecca)->entityPosition + 100); + getData()->location = getEntityData(kEntityRebecca)->location; + getData()->car = getEntityData(kEntityRebecca)->car; + + setCallback(3); + setup_updateEntity(kCarKronos, kPosition_9460); + break; + + case kAction292775040: + getData()->entityPosition = kPosition_9270; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarKronos; + + setCallback(4); + setup_updateEntity(kCarRedSleeping, kPosition_4840); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(4, Sophie, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chaptersHandler); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_4840; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(5, Sophie, function5) + DEFAULT_ACTION_IMPLEMENTATION() +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6, Sophie, chapter2) + CHAPTER_IMPLEMENTATION() +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Sophie, chapter3) + CHAPTER_IMPLEMENTATION() +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Sophie, chapter4) + CHAPTER_IMPLEMENTATION() +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Sophie, function9) + DEFAULT_ACTION_IMPLEMENTATION() +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Sophie, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntitySophie); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Sophie, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_nullfunction(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(12, Sophie) + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/sophie.h b/engines/lastexpress/entities/sophie.h new file mode 100644 index 0000000000..51d3d084b6 --- /dev/null +++ b/engines/lastexpress/entities/sophie.h @@ -0,0 +1,98 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SOPHIE_H +#define LASTEXPRESS_SOPHIE_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Sophie : public Entity { +public: + Sophie(LastExpressEngine *engine); + ~Sophie() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + /** + * Handle chapters events + */ + DECLARE_FUNCTION(chaptersHandler) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + DECLARE_FUNCTION(function5) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + DECLARE_FUNCTION(function9) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SOPHIE_H diff --git a/engines/lastexpress/entities/tables.cpp b/engines/lastexpress/entities/tables.cpp new file mode 100644 index 0000000000..eca60a536b --- /dev/null +++ b/engines/lastexpress/entities/tables.cpp @@ -0,0 +1,221 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/tables.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Tables::Tables(LastExpressEngine *engine, EntityIndex id) : Entity(engine, id) { + _id = id; + + ADD_CALLBACK_FUNCTION(Tables, chapter1); + ADD_CALLBACK_FUNCTION(Tables, chapter2); + ADD_CALLBACK_FUNCTION(Tables, chapter3); + ADD_CALLBACK_FUNCTION(Tables, chapter4); + ADD_CALLBACK_FUNCTION(Tables, chapter5); + ADD_CALLBACK_FUNCTION(Tables, draw); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Tables, chapter1) + if (savepoint.action == kActionDefault) { + if (_id == kEntityTables2) + getSound()->playSoundWithSubtitles("LOOP8A.SND", SoundManager::kFlagLoop, kEntityTables2); + + setup_draw(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(2, Tables, chapter2) + if (savepoint.action == kActionDefault) { + if (_id == kEntityTables2) + getSound()->playSoundWithSubtitles("LOOP8A.SND", SoundManager::kFlagLoop, kEntityTables2); + + setup_draw(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(3, Tables, chapter3) + if (savepoint.action == kActionDefault) { + if (_id == kEntityTables2) + getSound()->playSoundWithSubtitles("LOOP8A.SND", SoundManager::kFlagLoop, kEntityTables2); + + setup_draw(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(4, Tables, chapter4) + if (savepoint.action == kActionDefault) { + if (_id == kEntityTables2) + getSound()->playSoundWithSubtitles("LOOP8A.SND", SoundManager::kFlagLoop, kEntityTables2); + + setup_draw(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(5, Tables, chapter5) + if (savepoint.action == kActionDefault) { + if (_id == kEntityTables2 && getSound()->isBuffered(kEntityTables2)) + getSound()->processEntry(kEntityTables2); + + setup_draw(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6, Tables, draw) + switch (savepoint.action) { + default: + break; + + case kActionNone: + // Only applicable to Tables2 entity + if (_id != kEntityTables2) + break; + + switch (getProgress().chapter) { + default: + break; + + case kChapter1: + if (getState()->time > kTime1165500 && !params->param1) { + params->param1 = 1; + getSound()->processEntry(kEntityTables2); + } + break; + + case kChapter3: + if (getState()->time > kTime2052000 && !params->param2) { + params->param2 = 1; + getSound()->processEntry(kEntityTables2); + } + break; + + case kChapter4: + if (getState()->time > kTime2488500 && !params->param3) { + params->param3 = 1; + getSound()->processEntry(kEntityTables2); + } + break; + + } + break; + + case kActionDefault: + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + switch(_id) { + default: + break; + + case kEntityTables0: + getData()->entityPosition = kPosition_3970; + getEntities()->drawSequenceLeft(_id, "001P"); + break; + + case kEntityTables1: + getData()->entityPosition = kPosition_3970; + getEntities()->drawSequenceLeft(_id, "005J"); + break; + + case kEntityTables2: + getData()->entityPosition = kPosition_4690; + getEntities()->drawSequenceLeft(_id, "009G"); + break; + + case kEntityTables3: + getData()->entityPosition = kPosition_4690; + getEntities()->drawSequenceLeft(_id, "010M"); + break; + + case kEntityTables4: + getData()->entityPosition = kPosition_5420; + getEntities()->drawSequenceLeft(_id, "014F"); + break; + + case kEntityTables5: + getData()->entityPosition = kPosition_5420; + getEntities()->drawSequenceLeft(_id, "024D"); + break; + } + + break; + + case kActionDrawTablesWithChairs: + if (!strcmp(savepoint.param.charValue, "")) { + getEntities()->drawSequenceLeft(_id, savepoint.param.charValue); + } else { + switch(_id) { + default: + break; + + case kEntityTables0: + getEntities()->drawSequenceLeft(_id, "001P"); + break; + + case kEntityTables1: + getEntities()->drawSequenceLeft(_id, "005J"); + break; + + case kEntityTables2: + getEntities()->drawSequenceLeft(_id, "009G"); + break; + + case kEntityTables3: + getEntities()->drawSequenceLeft(_id, "010M"); + break; + + case kEntityTables4: + getEntities()->drawSequenceLeft(_id, "014F"); + break; + + case kEntityTables5: + getEntities()->drawSequenceLeft(_id, "024D"); + break; + } + } + break; + + case kAction136455232: + getEntities()->drawSequenceLeft(_id, "BLANK"); + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/tables.h b/engines/lastexpress/entities/tables.h new file mode 100644 index 0000000000..e7f51da66a --- /dev/null +++ b/engines/lastexpress/entities/tables.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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_TABLES_H +#define LASTEXPRESS_TABLES_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Tables : public Entity { +public: + Tables(LastExpressEngine *engine, EntityIndex id); + ~Tables() {}; + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Draws tables + */ + DECLARE_FUNCTION(draw) + +private: + EntityIndex _id; ///< Table entity id +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_TABLES_H diff --git a/engines/lastexpress/entities/tatiana.cpp b/engines/lastexpress/entities/tatiana.cpp new file mode 100644 index 0000000000..14567f8afa --- /dev/null +++ b/engines/lastexpress/entities/tatiana.cpp @@ -0,0 +1,1711 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/tatiana.h" + +#include "lastexpress/entities/alexei.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Tatiana::Tatiana(LastExpressEngine *engine) : Entity(engine, kEntityTatiana) { + ADD_CALLBACK_FUNCTION(Tatiana, reset); + ADD_CALLBACK_FUNCTION(Tatiana, playSound); + ADD_CALLBACK_FUNCTION(Tatiana, draw); + ADD_CALLBACK_FUNCTION(Tatiana, updatePosition); + ADD_CALLBACK_FUNCTION(Tatiana, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Tatiana, enterExitCompartment2); + ADD_CALLBACK_FUNCTION(Tatiana, callSavepoint); + ADD_CALLBACK_FUNCTION(Tatiana, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Tatiana, updateFromTicks); + ADD_CALLBACK_FUNCTION(Tatiana, updateFromTime); + ADD_CALLBACK_FUNCTION(Tatiana, callbackActionRestaurantOrSalon); + ADD_CALLBACK_FUNCTION(Tatiana, savegame); + ADD_CALLBACK_FUNCTION(Tatiana, updateEntity); + ADD_CALLBACK_FUNCTION(Tatiana, function14); + ADD_CALLBACK_FUNCTION(Tatiana, function15); + ADD_CALLBACK_FUNCTION(Tatiana, function16); + ADD_CALLBACK_FUNCTION(Tatiana, chapter1); + ADD_CALLBACK_FUNCTION(Tatiana, function18); + ADD_CALLBACK_FUNCTION(Tatiana, chapter1Handler); + ADD_CALLBACK_FUNCTION(Tatiana, function20); + ADD_CALLBACK_FUNCTION(Tatiana, function21); + ADD_CALLBACK_FUNCTION(Tatiana, function22); + ADD_CALLBACK_FUNCTION(Tatiana, function23); + ADD_CALLBACK_FUNCTION(Tatiana, function24); + ADD_CALLBACK_FUNCTION(Tatiana, chapter2); + ADD_CALLBACK_FUNCTION(Tatiana, chapter2Handler); + ADD_CALLBACK_FUNCTION(Tatiana, function27); + ADD_CALLBACK_FUNCTION(Tatiana, function28); + ADD_CALLBACK_FUNCTION(Tatiana, function29); + ADD_CALLBACK_FUNCTION(Tatiana, function30); + ADD_CALLBACK_FUNCTION(Tatiana, chapter3); + ADD_CALLBACK_FUNCTION(Tatiana, chapter3Handler); + ADD_CALLBACK_FUNCTION(Tatiana, function33); + ADD_CALLBACK_FUNCTION(Tatiana, function34); + ADD_CALLBACK_FUNCTION(Tatiana, function35); + ADD_CALLBACK_FUNCTION(Tatiana, function36); + ADD_CALLBACK_FUNCTION(Tatiana, function37); + ADD_CALLBACK_FUNCTION(Tatiana, function38); + ADD_CALLBACK_FUNCTION(Tatiana, function39); + ADD_CALLBACK_FUNCTION(Tatiana, function40); + ADD_CALLBACK_FUNCTION(Tatiana, function41); + ADD_CALLBACK_FUNCTION(Tatiana, function42); + ADD_CALLBACK_FUNCTION(Tatiana, chapter4); + ADD_CALLBACK_FUNCTION(Tatiana, chapter4Handler); + ADD_CALLBACK_FUNCTION(Tatiana, function45); + ADD_CALLBACK_FUNCTION(Tatiana, function46); + ADD_CALLBACK_FUNCTION(Tatiana, function47); + ADD_CALLBACK_FUNCTION(Tatiana, function48); + ADD_CALLBACK_FUNCTION(Tatiana, function49); + ADD_CALLBACK_FUNCTION(Tatiana, function50); + ADD_CALLBACK_FUNCTION(Tatiana, function51); + ADD_CALLBACK_FUNCTION(Tatiana, chapter5); + ADD_CALLBACK_FUNCTION(Tatiana, chapter5Handler); + ADD_CALLBACK_FUNCTION(Tatiana, function54); + ADD_CALLBACK_FUNCTION(Tatiana, function55); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Tatiana, reset) + Entity::reset(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Tatiana, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(3, Tatiana, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SII(4, Tatiana, updatePosition, CarIndex, Position) + Entity::updatePosition(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(5, Tatiana, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(6, Tatiana, enterExitCompartment2, ObjectIndex) + Entity::enterExitCompartment(savepoint, kPosition_7500, kPosition_7850, kCarRedSleeping, kObjectCompartmentB); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SIIS(7, Tatiana, callSavepoint, EntityIndex, ActionIndex) + Entity::callSavepoint(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Tatiana, callbackActionOnDirection) + Entity::callbackActionOnDirection(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(9, Tatiana, updateFromTicks) + Entity::updateFromTicks(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(10, Tatiana, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Tatiana, callbackActionRestaurantOrSalon) + Entity::callbackActionRestaurantOrSalon(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(12, Tatiana, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(13, Tatiana, updateEntity, CarIndex, EntityPosition) + if (savepoint.action == kActionExcuseMeCath) { + if (getEvent(kEventTatianaAskMatchSpeakRussian) || getEvent(kEventTatianaAskMatch) || getEvent(kEventVassiliSeizure)) { + getSound()->playSound(kEntityPlayer, rnd(2) ? "CAT1010" : "CAT1010A"); + } else { + getSound()->excuseMeCath(); + } + return; + } + + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Tatiana, function14) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getSavePoints()->push(kEntityTatiana, kEntityCoudert, kAction326348944); + getEntities()->drawSequenceLeft(kEntityTatiana, getProgress().chapter == kChapter1 ? "603Fb" : "673Fb"); + getEntities()->enterCompartment(kEntityTatiana, kObjectCompartmentB, true); + break; + + case kActionCallback: + if (getCallback() == 1 || getCallback() == 2) { + getEntities()->exitCompartment(kEntityTatiana, kObjectCompartmentB, true); + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityTatiana); + + CALLBACK_ACTION(); + } + break; + + case kAction69239528: + setCallback(getProgress().chapter == kChapter1 ? 1 : 2); + setup_enterExitCompartment2(getProgress().chapter == kChapter1 ? "603Db" : "673Db", kObjectCompartmentB); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Tatiana, function15) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(getProgress().chapter == kChapter1 ? 1 : 2); + setup_enterExitCompartment2(getProgress().chapter == kChapter1 ? "603Bb" : "673Bb", kObjectCompartmentB); + break; + + case kActionCallback: + if (getCallback() == 1 || getCallback() == 2) { + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityTatiana, kEntityCoudert, kAction292048641); + + getEntities()->drawSequenceLeft(kEntityTatiana, getProgress().chapter == kChapter1 ? "603Fb" : "673Fb"); + getEntities()->enterCompartment(kEntityTatiana, kObjectCompartmentB, true); + } + break; + + case kAction69239528: + getEntities()->exitCompartment(kEntityTatiana, kObjectCompartmentB, true); + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(16, Tatiana, function16, uint32) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 < getState()->time && !params->param4) { + params->param4 = 1; + + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + CALLBACK_ACTION(); + break; + } + + if (params->param2) { + UPDATE_PARAM(params->param5, getState()->timeTicks, 75); + + params->param2 = 0; + params->param3 = 1; + + getObjects()->update(kObjectCompartmentB, kEntityTatiana, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject49, kEntityTatiana, kObjectLocation1, kCursorNormal, kCursorNormal); + } + + params->param5 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + if (params->param2) { + getObjects()->update(kObjectCompartmentB, kEntityTatiana, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject49, kEntityTatiana, kObjectLocation1, kCursorNormal, kCursorNormal); + + if (savepoint.param.intValue == 49) { + setCallback(4); + setup_playSound(getSound()->justAMinuteCath()); + break; + } + + if (getInventory()->hasItem(kItemPassengerList)) { + setCallback(5); + setup_playSound(rnd(2) ? "CAT1512" : getSound()->wrongDoorCath()); + break; + } + + setCallback(6); + setup_playSound(getSound()->wrongDoorCath()); + } else { + getObjects()->update(kObjectCompartmentB, kEntityTatiana, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject49, kEntityTatiana, kObjectLocation1, kCursorNormal, kCursorNormal); + + setCallback(savepoint.action == kActionKnock ? 1 : 2); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentB, kEntityTatiana, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityTatiana, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kActionDrawScene: + if (params->param2 || params->param3) { + getObjects()->update(kObjectCompartmentB, kEntityTatiana, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityTatiana, kObjectLocation1, kCursorHandKnock, kCursorHand); + + params->param2 = 0; + params->param3 = 0; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + setCallback(3); + setup_playSound(rnd(2) ? "TAT1133A" : "TAT1133B"); + break; + + case 3: + getObjects()->update(kObjectCompartmentB, kEntityTatiana, kObjectLocation1, kCursorTalk, kCursorNormal); + getObjects()->update(kObject49, kEntityTatiana, kObjectLocation1, kCursorTalk, kCursorNormal); + params->param2 = 1; + break; + + case 4: + case 5: + case 6: + params->param2 = 0; + params->param3 = 1; + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Tatiana, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getSavePoints()->addData(kEntityTatiana, kAction191198209, 0); + + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject41, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + getData()->entityPosition = kPosition_5419; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Tatiana, function18) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) { + + if (getState()->time > kTime1143000 && !params->param2) { + params->param2 = 1; + getEntities()->drawSequenceRight(kEntityTatiana, "806DS"); + params->param1 = 1; + } + + if (!params->param1) { + UPDATE_PARAM_PROC(params->param3, getState()->time, 4500) + getEntities()->drawSequenceRight(kEntityTatiana, "806DS"); + params->param1 = 1; + UPDATE_PARAM_PROC_END + } + } + + if (getData()->entityPosition <= kPosition_2330) { + getSavePoints()->push(kEntityTatiana, kEntityAlexei, kAction157159392); + getEntities()->clearSequences(kEntityTatiana); + + CALLBACK_ACTION(); + } + break; + + case kActionExitCompartment: + getSavePoints()->push(kEntityTatiana, kEntityAlexei, kAction188784532); + + CALLBACK_ACTION(); + break; + + case kActionDefault: + if (getEntities()->isInSalon(kEntityPlayer)) { + getEntities()->drawSequenceRight(kEntityTatiana, "806DS"); + params->param1 = 1; + } else { + getEntities()->clearSequences(kEntityTatiana); + } + break; + + case kActionDrawScene: + if (!params->param1 && getEntities()->isInSalon(kEntityPlayer)) { + getEntities()->drawSequenceRight(kEntityTatiana, "806DS"); + getEntities()->updateFrame(kEntityTatiana); + params->param1 = 1; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Tatiana, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getSound()->isBuffered(kEntityTatiana) || !params->param4 || params->param3 == 2 || getSound()->isBuffered("TAT1066")) + goto label_tatiana_chapter1_2; + + UPDATE_PARAM_PROC(params->param5, getState()->timeTicks, 450) + getSound()->playSound(kEntityTatiana, params->param3 ? "TAT1069B" : "TAT1069A"); + getProgress().field_64 = 1; + params->param3++; + params->param5 = 0; + UPDATE_PARAM_PROC_END + + if (getEntities()->isPlayerPosition(kCarRestaurant, 71)) { + UPDATE_PARAM_PROC(params->param6, getState()->timeTicks, 75) + getSound()->playSound(kEntityTatiana, params->param3 ? "TAT1069B" : "TAT1069A"); + getProgress().field_64 = 1; + params->param3++; + params->param6 = 0; + UPDATE_PARAM_PROC_END + } + +label_tatiana_chapter1_2: + TIME_CHECK_SAVEPOINT(kTime1084500, params->param7, kEntityTatiana, kEntityPascale, kAction257489762); + + if (params->param1) { + UPDATE_PARAM(params->param8, getState()->timeTicks, 90); + getScenes()->loadSceneFromPosition(kCarRestaurant, 65); + } else { + params->param8 = 0; + } + break; + + case kActionDefault: + getSavePoints()->push(kEntityTatiana, kEntityTables4, kAction136455232); + getEntities()->drawSequenceLeft(kEntityTatiana, "014A"); + break; + + case kActionDrawScene: + params->param1 = getEntities()->isPlayerPosition(kCarRestaurant, 67) ? 1 : 0; + params->param4 = getEntities()->isPlayerPosition(kCarRestaurant, 69) + || getEntities()->isPlayerPosition(kCarRestaurant, 70) + || getEntities()->isPlayerPosition(kCarRestaurant, 71); + break; + + case kAction122288808: + getEntities()->drawSequenceLeft(kEntityTatiana, "014A"); + break; + + case kAction122358304: + getEntities()->drawSequenceLeft(kEntityTatiana, "BLANK"); + break; + + case kAction124973510: + setup_function20(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Tatiana, function20) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getSavePoints()->push(kEntityTatiana, kEntityAugust, kAction223183000); + getEntities()->updatePositionEnter(kEntityTatiana, kCarRestaurant, 67); + getSound()->playSound(kEntityTatiana, "TAT1070"); + + setCallback(2); + setup_callSavepoint("014C", kEntityTables4, kActionDrawTablesWithChairs, "014D"); + break; + + case 2: + getEntities()->updatePositionExit(kEntityTatiana, kCarRestaurant, 67); + getSavePoints()->push(kEntityTatiana, kEntityServers0, kAction188893625); + + setCallback(3); + setup_function18(); + break; + + case 3: + getSavePoints()->push(kEntityTatiana, kEntityAugust, kAction268620864); + setup_function21(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Tatiana, function21) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->clothes = kClothes1; + + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_8513); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->clothes = kClothesDefault; + + getSound()->playSound(kEntityTatiana, "TAT1071"); + getEntities()->drawSequenceRight(kEntityTatiana, "604Aa"); + getEntities()->enterCompartment(kEntityTatiana, kObjectCompartmentA); + + getData()->location = kLocationInsideCompartment; + + if (getEntities()->checkFields19(kEntityPlayer, kCarRedSleeping, kPosition_7850)) { + getAction()->playAnimation(isNight() ? kEventCathTurningNight : kEventCathTurningDay); + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromObject(kObjectCompartmentA, true); + } + + setCallback(2); + setup_callbackActionOnDirection(); + break; + + case 2: + getEntities()->exitCompartment(kEntityTatiana, kObjectCompartmentA); + + getData()->location = kLocationInsideCompartment; + + getEntities()->clearSequences(kEntityTatiana); + getSavePoints()->push(kEntityTatiana, kEntityAlexei, kAction135854208); + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorNormal); + // Fallback to next case + + case 3: + if (getSound()->isBuffered(kEntityTatiana)) { + setCallback(3); + setup_updateFromTime(75); + } else { + setCallback(4); + setup_playSound("TAT1071A"); + } + break; + + case 4: + getData()->entityPosition = kPosition_7500; + + getSavePoints()->push(kEntityTatiana, kEntityVassili, kAction168459827); + + setCallback(5); + setup_function16(kTime1156500); + break; + + case 5: + case 6: + if (getProgress().field_14 == 29) { + setCallback(6); + setup_function16(getState()->time + 900); + } else { + getObjects()->update(kObject49, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + setup_function22(); + } + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Tatiana, function22) + error("Tatiana: callback function 22 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Tatiana, function23) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_7500); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function14(); + break; + + case 2: + setup_function24(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Tatiana, function24) + if (savepoint.action == kActionDefault) { + + getData()->entityPosition = kPosition_7500; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->update(kObject25, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + getObjects()->update(kObjectTrainTimeTable, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + getEntities()->updatePositionExit(kEntityTatiana, kCarGreenSleeping, 70); + getEntities()->updatePositionExit(kEntityTatiana, kCarGreenSleeping, 71); + getEntities()->clearSequences(kEntityTatiana); + + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject41, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Tatiana, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityTatiana); + + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject41, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + + getData()->entityPosition = kPosition_5420; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothes2; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Tatiana, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime1800000 && params->param1 && getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getData()->inventoryItem = kItemNone; + setup_function28(); + } + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + setup_function28(); + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityTatiana, "024A"); + getSavePoints()->push(kEntityTatiana, kEntityTables5, kAction136455232); + getData()->inventoryItem = kItemInvalid; + break; + + case kActionDrawScene: + if (getEntities()->isPlayerPosition(kCarRestaurant, 64) || getEntities()->isPlayerPosition(kCarRestaurant, 65)) { + getData()->inventoryItem = kItemNone; + setup_function27(); + } + break; + + case kAction290869168: + params->param1 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Tatiana, function27) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(getEvent(kEventTatianaGivePoem) ? 1 : 2); + setup_savegame(kSavegameTypeEvent, getEvent(kEventTatianaGivePoem) ? kEventTatianaBreakfastAlexei : kEventTatianaBreakfast); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + RESET_ENTITY_STATE(kEntityAlexei, Alexei, setup_function30); + getAction()->playAnimation(kEventTatianaBreakfastAlexei); + getInventory()->addItem(kItemParchemin); + getInventory()->setLocationAndProcess(kItem11, kObjectLocation1); + setup_function28(); + break; + + case 2: + RESET_ENTITY_STATE(kEntityAlexei, Alexei, setup_function30); + getAction()->playAnimation(kEventTatianaBreakfast); + if (getInventory()->hasItem(kItemParchemin)) { + getAction()->playAnimation(kEventTatianaBreakfastGivePoem); + getInventory()->removeItem(kItemParchemin); + } else { + getAction()->playAnimation(kEventTatianaAlexei); + } + setup_function28(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Tatiana, function28) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->inventoryItem = kItemNone; + getData()->location = kLocationOutsideCompartment; + + getSavePoints()->push(kEntityTatiana, kEntityTables5, kActionDrawTablesWithChairs, "024D"); + getSavePoints()->push(kEntityTatiana, kEntityAlexei, kAction236053296, (getEvent(kEventTatianaBreakfastAlexei) || getEvent(kEventTatianaBreakfast)) ? 69 : 0); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function29(); + break; + + case kAction123857088: + getEntities()->drawSequenceLeft(kEntityTatiana, "018G"); + + setCallback(1); + setup_updateFromTime(1800); + break; + + case kAction156444784: + getData()->location = kLocationInsideCompartment; + getEntities()->drawSequenceLeft(kEntityTatiana, "BLANK"); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Tatiana, function29) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getEntities()->updatePositionEnter(kEntityTatiana, kCarRestaurant, 63); + + setCallback(2); + setup_callSavepoint("018H", kEntityTables1, kActionDrawTablesWithChairs, "018A"); + break; + + case 2: + getEntities()->updatePositionExit(kEntityTatiana, kCarRestaurant, 63); + getSavePoints()->push(kEntityTatiana, kEntityServers1, kAction302203328); + getEntities()->drawSequenceRight(kEntityTatiana, "805DS"); + + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityTatiana); + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case 3: + setup_function30(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Tatiana, function30) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_7500); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_function14(); + break; + + case 2: + setCallback(3); + setup_function16(kTimeEnd); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, Tatiana, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityTatiana); + + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_1750; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothes2; + getData()->inventoryItem = kItemNone; + + // Update inventory + getInventory()->get(kItemFirebird)->location = kObjectLocation2; + + if (getEvent(kEventTatianaBreakfastGivePoem) || (getEvent(kEventTatianaGivePoem) && !getEvent(kEventTatianaBreakfastAlexei))) + getInventory()->get(kItemParchemin)->location = kObjectLocation2; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Tatiana, chapter3Handler) + EntityData::EntityParametersI5S *parameters = (EntityData::EntityParametersI5S*)_data->getCurrentParameters(); + EntityData::EntityParametersSIII *parameters1 = (EntityData::EntityParametersSIII*)_data->getCurrentParameters(1); + + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!parameters->param2 && !parameters->param5) { + parameters->param1 -= getState()->timeDelta; + + if (getState()->timeDelta > parameters->param1) { + + getEntities()->drawSequenceLeft(kEntityTatiana, (char *)¶meters1->seq); + getSound()->playSound(kEntityTatiana, (char *)¶meters->seq); + + if (parameters->param3 == 4 && getEntities()->isInSalon(kEntityPlayer)) + getProgress().field_90 = 1; + + parameters->param2 = 1; + } + } + + if (parameters->param4 && parameters->param5) { + UPDATE_PARAM_CHECK(parameters->param4, getState()->time, 6300) + if (getEntities()->isSomebodyInsideRestaurantOrSalon()) { + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_updatePosition("110E", kCarRestaurant, 52); + } + } + } + break; + + case kActionEndSound: + parameters->param2 = 0; + ++parameters->param3; + + switch (parameters->param3) { + default: + parameters->param5 = 1; + break; + + case 1: + parameters->param1 = 900; + getEntities()->drawSequenceLeft(kEntityTatiana, "110A"); + strcpy((char *)¶meters->seq, "Tat3160B"); + strcpy((char *)¶meters1->seq, "110A"); + break; + + case 2: + parameters->param1 = 9000; + strcpy((char *)¶meters->seq, "Tat3160C"); + strcpy((char *)¶meters1->seq, "110C"); + break; + + case 3: + parameters->param1 = 13500; + getEntities()->drawSequenceLeft(kEntityTatiana, "110B"); + strcpy((char *)¶meters->seq, "Tat3160D"); + strcpy((char *)¶meters1->seq, "110D"); + break; + + case 4: + parameters->param1 = 9000; + getEntities()->drawSequenceLeft(kEntityTatiana, "110B"); + strcpy((char *)¶meters->seq, "Tat3160E"); + strcpy((char *)¶meters1->seq, "110D"); + break; + + case 5: + parameters->param1 = 4500; + getEntities()->drawSequenceLeft(kEntityTatiana, "110B"); + strcpy((char *)¶meters->seq, "Tat3160G"); + strcpy((char *)¶meters1->seq, "110D"); + break; + + case 6: + parameters->param1 = 4500; + getEntities()->drawSequenceLeft(kEntityTatiana, "110B"); + strcpy((char *)¶meters->seq, "Tat3160B"); + break; + } + break; + + case kActionDefault: + getSavePoints()->push(kEntityTatiana, kEntityAlexei, kAction122358304); + getSavePoints()->push(kEntityTatiana, kEntityKronos, kAction157159392); + getEntities()->drawSequenceLeft(kEntityTatiana, "110C"); + getSound()->playSound(kEntityTatiana, "Tat3160A"); + + parameters->param2 = 1; + break; + + case kActionCallback: + if (getCallback() == 1) { + getSavePoints()->push(kEntityTatiana, kEntityAlexei, kAction122288808); + setup_function33(); + } + break; + + case kAction101169422: + parameters->param4 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Tatiana, function33) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityTatiana); + setCallback(1); + setup_updateFromTime(75); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_7500); + break; + + case 2: + setCallback(3); + setup_function14(); + break; + + case 3: + setup_function34(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(34, Tatiana, function34) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(kTime2097000); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getInventory()->get(kItemFirebird)->location = kObjectLocation1; + if (getEntities()->checkFields19(kEntityPlayer, kCarRedSleeping, kPosition_7850)) + getScenes()->loadSceneFromObject(kObjectCompartmentB); + + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + setup_function15(); + break; + + case 2: + setCallback(3); + setup_updateEntity(kCarKronos, kPosition_9270); + break; + + case 3: + setup_function35(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(35, Tatiana, function35) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1 + && getInventory()->hasItem(kItemFirebird) + && getEntities()->checkFields19(kEntityPlayer, kCarRedSleeping, kPosition_7850) + && (getState()->time < kTime2133000 || getProgress().field_40)) { + setCallback(1); + setup_function41(); + break; + } + +label_callback_1: + if (getState()->time > kTime2133000) { + if (getData()->car >= kCarRedSleeping || (getData()->car == kCarGreenSleeping && getData()->entityPosition > kPosition_5790)) + setup_function36(); + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getEntities()->clearSequences(kEntityTatiana); + + getData()->car = kCarKronos; + getData()->entityPosition = kPosition_6000; + getData()->location = kLocationInsideCompartment; + break; + + case kActionCallback: + if (getCallback() == 1) { + params->param1 = 1; + goto label_callback_1; + } + break; + + case kAction191668032: + setup_function36(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(36, Tatiana, function36) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->car = kCarGreenSleeping; + getData()->entityPosition = kPosition_850; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_7500); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + if (!getEntities()->checkFields19(kEntityPlayer, kCarGreenSleeping, kPosition_7850) || getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_8200)) { + setCallback(2); + setup_function14(); + break; + } + + if (getInventory()->hasItem(kItemFirebird)) { + getAction()->playAnimation(kEventTatianaCompartmentStealEgg); + getInventory()->removeItem(kItemFirebird); + getInventory()->get(kItemFirebird)->location = kObjectLocation2; + } else { + getAction()->playAnimation(kEventTatianaCompartment); + } + + getScenes()->loadSceneFromObject(kObjectCompartmentB); + break; + + case 2: + setup_function37(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(37, Tatiana, function37) + error("Tatiana: callback function 37 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(38, Tatiana, function38) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param1, getState()->time, 450); + + getEntities()->exitCompartment(kEntityTatiana, kObjectCompartmentF, true); + + setCallback(4); + setup_function42(kCarRedSleeping, kPosition_7500); + break; + + case kActionDefault: + getData()->clothes = kClothes3; + + setCallback(1); + setup_enterExitCompartment("673Jb", kObjectCompartmentB); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + setCallback(2); + setup_function42(kCarRedSleeping, kPosition_4070); + break; + + case 2: + getEntities()->drawSequenceLeft(kEntityTatiana, "673Gf"); + getEntities()->enterCompartment(kEntityTatiana, kObjectCompartmentF, true); + + setCallback(3); + setup_playSound("Tat3164"); + break; + + case 3: + getSavePoints()->push(kEntityTatiana, kEntityAnna, kAction236241630); + break; + + case 4: + setCallback(5); + setup_enterExitCompartment2("673Db", kObjectCompartmentB); + break; + + case 5: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityTatiana); + + setup_function39(); + break; + + case 6: + getEntities()->exitCompartment(kEntityTatiana, kObjectCompartmentF, true); + getEntities()->clearSequences(kEntityTatiana); + getData()->location = kLocationInsideCompartment; + + setCallback(7); + setup_playSound("ANN3011"); + break; + + case 7: + setCallback(8); + setup_updateFromTime(900); + break; + + case 8: + setCallback(9); + setup_enterExitCompartment("673Jf", kObjectCompartmentF); + break; + + case 9: + getData()->location = kLocationOutsideCompartment; + + setCallback(10); + setup_function42(kCarRedSleeping, kPosition_7500); + break; + + case 10: + getSavePoints()->push(kEntityTatiana, kEntityAnna, kAction236517970); + + setCallback(11); + setup_enterExitCompartment2("673Db", kObjectCompartmentB); + break; + + case 11: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityTatiana); + + setup_function39(); + break; + } + break; + + case kAction100906246: + setCallback(6); + setup_enterExitCompartment("673Df", kObjectCompartmentF); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(39, Tatiana, function39) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1 && getEntities()->isDistanceBetweenEntities(kEntityTatiana, kEntityPlayer, 1000)) { + params->param1 = 1; + getSound()->playSound(kEntityTatiana, "Tat3164"); // Tatiana weeping + } + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(40, Tatiana, function40) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInsideTrainCar(kEntityPlayer, kCarKronos) + || getData()->car != getEntityData(kEntityPlayer)->car + || getEntities()->updateEntity(kEntityTatiana, kCarKronos, kPosition_9270)) + CALLBACK_ACTION(); + break; + + case kActionExcuseMe: + if (getEvent(kEventTatianaAskMatchSpeakRussian) || getEvent(kEventTatianaAskMatch) || getEvent(kEventVassiliSeizure)) + getSound()->playSound(kEntityPlayer, rnd(2) ? "CAT1001A" : "CAT1010"); + else + getSound()->excuseMeCath(); + break; + + case kActionDefault: + if (getEntities()->updateEntity(kEntityTatiana, kCarKronos, kPosition_9270)) + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(41, Tatiana, function41) + error("Tatiana: callback function 41 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(42, Tatiana, function42, CarIndex, EntityPosition) + if (savepoint.action == kActionExcuseMeCath || savepoint.action == kActionExcuseMe) { + getSound()->playSound(kEntityPlayer, "Tat3124", getSound()->getSoundFlag(kEntityTatiana)); + return; + } + + Entity::updateEntity(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(43, Tatiana, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityTatiana); + + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_7500; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothes2; + getData()->inventoryItem = kItemNone; + + ENTITY_PARAM(0, 1) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(44, Tatiana, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function16(kTime2362500); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function45(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(45, Tatiana, function45) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("673Bb", kObjectCompartmentB); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_540); + break; + + case 2: + if (getEntities()->isInGreenCarEntrance(kEntityPlayer)) { + getSound()->excuseMe(kEntityTatiana); + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 62)) + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 72); + } + + getSavePoints()->push(kEntityTatiana, kEntityAlexei, kAction123712592); + setup_function46(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(46, Tatiana, function46) + error("Tatiana: callback function 46 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(47, Tatiana, function47) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRedSleeping, kPosition_7500); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_enterExitCompartment2("673Db", kObjectCompartmentB); + break; + + case 2: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityTatiana); + + setCallback(3); + setup_function16(kTime2407500); + break; + + case 3: + case 4: + if (ENTITY_PARAM(0, 1) && getObjects()->get(kObjectCompartment1).location2 == kObjectLocation1) { + setup_function48(); + } else { + setCallback(4); + setup_function16(900); + } + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(48, Tatiana, function48) + error("Tatiana: callback function 48 not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(49, Tatiana, function49) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_7500; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kAction169360385: + setup_function50(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(50, Tatiana, function50) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime2520000 && !params->param1) { + params->param1 = 1; + setup_function51(); + } + break; + + case kActionEndSound: + getSound()->playSound(kEntityTatiana, "Tat4166"); + break; + + case kActionKnock: + if (!getSound()->isBuffered("LIB012", true)) + getSound()->playSound(kEntityPlayer, "LIB012"); + break; + + case kActionOpenDoor: + getSound()->playSound(kEntityPlayer, "LIB014"); + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventVassiliDeadAlexei); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->update(kObjectCompartmentB, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject49, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject48, kEntityTatiana, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartmentA, kEntityTatiana, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + if (!getSound()->isBuffered(kEntityTatiana)) + getSound()->playSound(kEntityTatiana, "Tat4166"); + break; + + case kActionCallback: + if (getCallback() == 1) { + if (getSound()->isBuffered("MUS013")) + getSound()->processEntry("MUS013"); + + getAction()->playAnimation(kEventVassiliDeadAlexei); + getSavePoints()->push(kEntityTatiana, kEntityAbbot, kAction104060776); + getScenes()->loadSceneFromPosition(kCarRedSleeping, 38); + + setup_function51(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(51, Tatiana, function51) + if (savepoint.action == kActionDefault) { + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorNormal); + getObjects()->update(kObject48, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorNormal); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(52, Tatiana, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityTatiana); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(53, Tatiana, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_function54(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(54, Tatiana, function54) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param2) { + switch (params->param1) { + default: + break; + + case 0: + getSound()->playSound(kEntityTatiana, "Tat5167A"); + params->param2 = 1; + break; + + case 1: + getSound()->playSound(kEntityTatiana, "Tat5167B"); + params->param2 = 1; + break; + + case 2: + getSound()->playSound(kEntityTatiana, "Tat5167C"); + params->param2 = 1; + break; + + case 3: + getSound()->playSound(kEntityTatiana, "Tat5167D"); + params->param2 = 1; + break; + } + } + + if (params->param1 > 3) { + UPDATE_PARAM(params->param3, getState()->timeTicks, 225); + + params->param1 = 0; + params->param3 = 0; + } + break; + + case kAction1: + getData()->inventoryItem = kItemNone; + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventTatianaVassiliTalk); + break; + + case kActionEndSound: + ++params->param1; + params->param2 = 0; + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityTatiana, "033A"); + getData()->inventoryItem = kItemInvalid; + break; + + case kActionCallback: + if (getCallback() == 1) { + if (getSound()->isBuffered("MUS050")) + getSound()->processEntry("MUS050"); + + if (getSound()->isBuffered(kEntityTatiana)) + getSound()->processEntry(kEntityTatiana); + + getAction()->playAnimation(isNight() ? kEventTatianaVassiliTalkNight : kEventTatianaVassiliTalk); + getScenes()->processScene(); + + params->param1 = 4; + params->param2 = 0; + params->param3 = 0; + } + break; + + case kAction203078272: + getEntities()->drawSequenceLeft(kEntityTatiana, "033E"); + break; + + case kAction236060709: + getData()->inventoryItem = kItemNone; + setup_function55(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(55, Tatiana, function55) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityTatiana); + // fall back to next action + + case kActionDrawScene: + if (getEntities()->isPlayerPosition(kCarRestaurant, 72)) + getScenes()->loadSceneFromPosition(kCarRestaurant, 86); + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/tatiana.h b/engines/lastexpress/entities/tatiana.h new file mode 100644 index 0000000000..8dc5de9b35 --- /dev/null +++ b/engines/lastexpress/entities/tatiana.h @@ -0,0 +1,235 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_TATIANA_H +#define LASTEXPRESS_TATIANA_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Tatiana : public Entity { +public: + Tatiana(LastExpressEngine *engine); + ~Tatiana() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Updates the position + * + * @param sequence1 The sequence to draw + * @param car The car + * @param position The position + */ + DECLARE_FUNCTION_3(updatePosition, const char* sequence1, CarIndex car, Position position) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Handles entering/exiting a compartment and updates position/play animation + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment2, const char* sequence, ObjectIndex compartment) + + /** + * Call a savepoint (or draw sequence in default case) + * + * @param sequence1 The sequence to draw in the default case + * @param entity The entity + * @param action The action + * @param sequence2 The sequence name for the savepoint + */ + DECLARE_FUNCTION_4(callSavepoint, const char* sequence1, EntityIndex entity, ActionIndex action, const char* sequence2) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Updates parameter 2 using ticks value + * + * @param savepoint The savepoint + * - ticks to add + */ + DECLARE_FUNCTION_NOSETUP(updateFromTicks) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Process callback action when somebody is standing in the restaurant or salon. + */ + DECLARE_FUNCTION(callbackActionRestaurantOrSalon) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION(function14) + DECLARE_FUNCTION(function15) + DECLARE_FUNCTION_1(function16, uint32) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + DECLARE_FUNCTION(function18) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function20) + DECLARE_FUNCTION(function21) + DECLARE_FUNCTION(function22) + DECLARE_FUNCTION(function23) + DECLARE_FUNCTION(function24) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + DECLARE_FUNCTION(function27) + DECLARE_FUNCTION(function28) + DECLARE_FUNCTION(function29) + DECLARE_FUNCTION(function30) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + DECLARE_FUNCTION(function33) + DECLARE_FUNCTION(function34) + DECLARE_FUNCTION(function35) + DECLARE_FUNCTION(function36) + DECLARE_FUNCTION(function37) + DECLARE_FUNCTION(function38) + DECLARE_FUNCTION(function39) + DECLARE_FUNCTION(function40) + DECLARE_FUNCTION(function41) + + /** + * ??? + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(function42, CarIndex car, EntityPosition entityPosition) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function45) + DECLARE_FUNCTION(function46) + DECLARE_FUNCTION(function47) + DECLARE_FUNCTION(function48) + DECLARE_FUNCTION(function49) + DECLARE_FUNCTION(function50) + DECLARE_FUNCTION(function51) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function54) + DECLARE_FUNCTION(function55) +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_TATIANA_H diff --git a/engines/lastexpress/entities/train.cpp b/engines/lastexpress/entities/train.cpp new file mode 100644 index 0000000000..53f19b1a30 --- /dev/null +++ b/engines/lastexpress/entities/train.cpp @@ -0,0 +1,575 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/train.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savegame.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/state.h" +#include "lastexpress/game/sound.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Train::Train(LastExpressEngine *engine) : Entity(engine, kEntityTrain) { + ADD_CALLBACK_FUNCTION(Train, savegame); + ADD_CALLBACK_FUNCTION(Train, chapter1); + ADD_CALLBACK_FUNCTION(Train, chapter2); + ADD_CALLBACK_FUNCTION(Train, chapter3); + ADD_CALLBACK_FUNCTION(Train, chapter4); + ADD_CALLBACK_FUNCTION(Train, chapter5); + ADD_CALLBACK_FUNCTION(Train, harem); + ADD_CALLBACK_FUNCTION(Train, process); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(1, Train, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(2, Train, chapter1) + if (savepoint.action == kActionDefault) + setup_process(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(3, Train, chapter2) + if (savepoint.action == kActionDefault) + setup_process(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(4, Train, chapter3) + if (savepoint.action == kActionDefault) + setup_process(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(5, Train, chapter4) + if (savepoint.action == kActionDefault) + setup_process(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6, Train, chapter5) + if (savepoint.action == kActionDefault) + setup_process(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(7, Train, harem, ObjectIndex, uint32) + if (savepoint.action != kActionDefault) + return; + + switch (params->param1) { + default: + error("Train::harem: Invalid value for parameter 1: %d", params->param1); + break; + + case kObjectCompartment5: + params->param3 = kPosition_4840; + break; + + case kObjectCompartment6: + params->param3 = kPosition_4070; + break; + + case kObjectCompartment7: + params->param3 = kPosition_3050; + break; + + case kObjectCompartment8: + params->param3 = kPosition_2740; + break; + } + + params->param4 = getEntities()->isInsideCompartment(kEntityAlouan, kCarGreenSleeping, (EntityPosition)params->param3); + params->param5 = (ENTITY_PARAM(0, 7) - params->param3) < 1 ? true : false; + params->param6 = getEntities()->isInsideCompartment(kEntityYasmin, kCarGreenSleeping, (EntityPosition)params->param3); + params->param7 = getEntities()->isInsideCompartment(kEntityHadija, kCarGreenSleeping, (EntityPosition)params->param3); + + getObjects()->update((ObjectIndex)params->param1, kEntityTrain, kObjectLocation3, kCursorNormal, kCursorNormal); + + // Knock / closed door sound + getSound()->playSound(kEntityTables5, (params->param2 == 8) ? "LIB012" : "LIB013", SoundManager::kFlagDefault); + + if (params->param4 && params->param5) { + + ENTITY_PARAM(0, 5)++; + + switch (ENTITY_PARAM(0, 5)) { + default: + params->param8 = 1; + break; + + case 1: + getSound()->playSound(kEntityTables5, "Har1014", SoundManager::kFlagDefault, 15); + break; + + case 2: + getSound()->playSound(kEntityTables5, "Har1013", SoundManager::kFlagDefault, 15); + getSound()->playSound(kEntityTables5, "Har1016", SoundManager::kFlagDefault, 150); + break; + + case 3: + getSound()->playSound(kEntityTables5, "Har1015A", SoundManager::kFlagDefault, 15); + getSound()->playSound(kEntityTables5, "Har1015", SoundManager::kFlagDefault, 150); + break; + } + + // Update progress + getProgress().field_DC = 1; + getProgress().field_E0 = 1; + + handleCompartmentAction(); + + // Done with it! + return; + } + + if (params->param6 && params->param7) { + + ENTITY_PARAM(0, 6)++; + + switch(ENTITY_PARAM(0, 6)) { + default: + params->param8 = 1; + break; + + case 1: + getSound()->playSound(kEntityTables5, "Har1014", SoundManager::kFlagDefault, 15); + break; + + case 2: + getSound()->playSound(kEntityTables5, "Har1013", SoundManager::kFlagDefault, 15); + break; + + case 3: + getSound()->playSound(kEntityTables5, "Har1013A", SoundManager::kFlagDefault, 15); + break; + } + + handleCompartmentAction(); + return; + } + + if (!params->param5) { + + if (params->param6) { + ENTITY_PARAM(0, 3)++; + + switch(ENTITY_PARAM(0, 3)) { + default: + params->param8 = 1; + break; + + case 1: + getSound()->playSound(kEntityTables5, "Har1012", SoundManager::kFlagDefault, 15); + break; + + case 2: + getSound()->playSound(kEntityTables5, "Har1012A", SoundManager::kFlagDefault, 15); + break; + } + + handleCompartmentAction(); + return; + } else { + + if (params->param4) { + ENTITY_PARAM(0, 1)++; + + if (ENTITY_PARAM(0, 1) <= 1) + getSound()->playSound(kEntityTables5, "Har1014", SoundManager::kFlagDefault, 15); + else + params->param8 = 1; + + getProgress().field_DC = 1; + + handleCompartmentAction(); + return; + } + + if (params->param7) { + ENTITY_PARAM(0, 4)++; + + if (ENTITY_PARAM(0, 4) <= 1) { + getSound()->playSound(kEntityTables5, "Har1011", SoundManager::kFlagDefault, 15); + handleCompartmentAction(); + return; + } + } + } + + params->param8 = 1; + handleCompartmentAction(); + return; + } + + ENTITY_PARAM(0, 2) += 1; + + switch (ENTITY_PARAM(0, 2)) { + default: + params->param8 = 1; + break; + + case 1: + getSound()->playSound(kEntityTables5, "Har1013", SoundManager::kFlagDefault, 15); + break; + + case 2: + getSound()->playSound(kEntityTables5, "Har1013A", SoundManager::kFlagDefault, 15); + break; + } + + getProgress().field_E0 = 1; + + handleCompartmentAction(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Train, process) + EntityData::EntityParametersIIIS *params1 = (EntityData::EntityParametersIIIS*)_data->getCurrentParameters(1); + + switch (savepoint.action) { + default: + break; + + case kActionNone: + // Play smoke animation + if ((getEntities()->isPlayerInCar(kCarGreenSleeping) || getEntities()->isPlayerInCar(kCarRedSleeping)) + && params->param4 && !params->param5) { + + params->param4 -= 1; + + if (!params->param4 && getProgress().jacket == kJacketGreen) { + + getAction()->playAnimation(isNight() ? kEventCathSmokeNight : kEventCathSmokeDay); + params->param5 = 1; + getScenes()->processScene(); + } + } + + if (params->param6) { + UPDATE_PARAM_GOTO(params1->param7, getState()->time, 900, label_process); + getScenes()->loadSceneFromPosition(kCarRestaurant, 58); + } + + params1->param7 = 0; + +label_process: + if (params->param7) { + if (!params1->param8) { + params1->param8 = getState()->time + 4500; + + if (!params1->param8) + params->param7 = 0; + } + + if (params1->param8 && params1->param8 < getState()->time) { + params->param7 = 0; + params1->param8 = 0; + } + } + + // Update object + if (ENTITY_PARAM(0, 8) && !getSound()->isBuffered(kEntityTables5)) { + getObjects()->update((ObjectIndex)ENTITY_PARAM(0, 8), getObjects()->get((ObjectIndex)ENTITY_PARAM(0, 8)).entity, kObjectLocation3, kCursorHandKnock, kCursorHand); + ENTITY_PARAM(0, 8) = 0; + } + + // Play clock sound + if (params->param6 && !getSound()->isBuffered("ZFX1001", true)) + getSound()->playSound(kEntityPlayer, "ZFX1001"); + + break; + + case kActionKnock: + case kActionOpenDoor: { + // Handle opening harem compartments + ObjectIndex compartment = (ObjectIndex)savepoint.param.intValue; + if (compartment == kObjectCompartment5 || compartment == kObjectCompartment6 || compartment == kObjectCompartment7 || compartment == kObjectCompartment8) { + setCallback(savepoint.action == kActionKnock ? 3 : 4); + setup_harem(compartment, savepoint.action); + } + break; + } + + case kActionDefault: + params->param3 = 1; + if (getProgress().chapter < kChapter5) { + getObjects()->update(kObjectCompartment5, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment6, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment7, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment8, kEntityTrain, kObjectLocation3, kCursorHandKnock, kCursorHand); + } + getData()->entityPosition = kPosition_30000; + break; + + case kActionDrawScene: + getData()->car = getEntityData(kEntityPlayer)->car; + + // Play clock sound + if (getEntities()->isPlayerPosition(kCarRestaurant, 81)) { + params->param6 = 1; + if (!getSound()->isBuffered("ZFX1001")) + getSound()->playSound(kEntityPlayer, "ZFX1001"); + } else { + params->param6 = 0; + if (getSound()->isBuffered("ZFX1001", true)) + getSound()->removeFromQueue("ZFX1001"); + } + + // Draw moving background behind windows + if (params->param3) { + if (getEntityData(kEntityPlayer)->car != (CarIndex)params->param1 || isNight() != (bool)(params->param2)) { + switch (getEntityData(kEntityPlayer)->car) { + default: + getEntities()->clearSequences(kEntityTrain); + break; + + case kCarBaggageRear: + case kCarBaggage: + if (getProgress().isNightTime) + getEntities()->drawSequenceLeft(kEntityTrain, "B1WNM"); + else + getEntities()->drawSequenceLeft(kEntityTrain, isNight() ? "B1WNN" : "B1WND"); + break; + + case kCarGreenSleeping: + case kCarRedSleeping: + if (getProgress().isNightTime) + getEntities()->drawSequenceLeft(kEntityTrain, "S1WNM"); + else + getEntities()->drawSequenceLeft(kEntityTrain, isNight() ? "S1WNN" : "S1WND"); + break; + + case kCarRestaurant: + getEntities()->drawSequenceLeft(kEntityTrain, isNight() ? "RCWNN" : "RCWND"); + break; + } + + // Set parameters so we do not get called twice + params->param1 = getEntityData(kEntityPlayer)->car; + params->param2 = isNight(); + } + } + + if (!params->param5) { + params->param4 = 2700; // this is the sound file name + params->param5 = 0; + } + + if (getProgress().jacket == kJacketBlood) { + if (getEntities()->isPlayerPosition(kCarRedSleeping, 18)) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventMertensBloodJacket); + break; + } + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 22)) { + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventMertensBloodJacket); + break; + } + } + + resetParam8(); + break; + + + case kActionCallback: { + int action = getCallback(); + switch(action) { + default: + break; + + case 1: + case 2: + getAction()->playAnimation(action == 1 ? kEventCoudertBloodJacket : kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverBloodJacket, true); + resetParam8(); + break; + + case 5: + getAction()->playAnimation(kEventLocomotiveConductorsDiscovered); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverPolice2, true); + break; + + case 6: + getAction()->playAnimation(kEventCathBreakCeiling); + getObjects()->update(kObjectCeiling, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + getScenes()->processScene(); + break; + + case 7: + getAction()->playAnimation(kEventCathJumpDownCeiling); + getScenes()->loadSceneFromPosition(kCarKronos, 89); + break; + + case 8: + getAction()->playAnimation(kEventCloseMatchbox); + getScenes()->loadSceneFromPosition(kCarRestaurant, 51); + break; + } + break; + } + + case kAction191070912: + ENTITY_PARAM(0, 7) = savepoint.param.intValue; + break; + + case kActionTrainStopRunning: + params->param3 = 0; + getEntities()->clearSequences(kEntityTrain); + break; + + case kActionCatchBeetle: + setCallback(8); + setup_savegame(kSavegameTypeEvent, kEventCloseMatchbox); + break; + + case kAction203339360: + if (params->param7) { + setCallback(5); + setup_savegame(kSavegameTypeEvent, kEventLocomotiveConductorsDiscovered); + } else { + params->param7 = 1; + getAction()->playAnimation(kEventLocomotiveConductorsLook); + getScenes()->loadSceneFromPosition(kCarCoalTender, 2); + } + break; + + case kActionTrainStartRunning: + if (!params->param3) { + params->param1 = 0; + params->param3 = 1; + getSavePoints()->push(kEntityTrain, kEntityTrain, kActionDrawScene); + } + break; + + case kAction203863200: + if (!strcmp(savepoint.param.charValue, "")) { + params->param8 = 1; + strcpy((char *)¶ms1->seq, savepoint.param.charValue); // this is the sound file name + } + break; + + case kAction222746496: + switch(savepoint.param.intValue) { + default: + break; + + case kObjectCompartment1: + case kObjectCompartment2: + case kObjectCompartmentA: + case kObjectCompartmentB: + params1->param1 = (savepoint.param.intValue == kObjectCompartment1 || savepoint.param.intValue == kObjectCompartment2) ? kCarGreenSleeping : kCarRedSleeping; + params1->param2 = (savepoint.param.intValue == kObjectCompartment1 || savepoint.param.intValue == kObjectCompartmentA) ? kPosition_8200 : kPosition_7500; + params1->param3 = kPosition_7850; + break; + + case kObjectCompartment3: + case kObjectCompartment4: + case kObjectCompartmentC: + case kObjectCompartmentD: + params1->param1 = (savepoint.param.intValue == kObjectCompartment1 || savepoint.param.intValue == kObjectCompartment2) ? kCarGreenSleeping : kCarRedSleeping; + params1->param2 = (savepoint.param.intValue == kObjectCompartment3 || savepoint.param.intValue == kObjectCompartmentC) ? kPosition_6470 : kPosition_5790; + params1->param3 = kPosition_6130; + break; + + case kObjectCompartment5: + case kObjectCompartment6: + case kObjectCompartmentE: + case kObjectCompartmentF: + params1->param1 = (savepoint.param.intValue == kObjectCompartment1 || savepoint.param.intValue == kObjectCompartment2) ? kCarGreenSleeping : kCarRedSleeping; + params1->param2 = (savepoint.param.intValue == kObjectCompartment5 || savepoint.param.intValue == kObjectCompartmentE) ? kPosition_4840 : kPosition_4070; + params1->param3 = kPosition_4455; + break; + + case kObjectCompartment7: + case kObjectCompartment8: + case kObjectCompartmentG: + case kObjectCompartmentH: + params1->param1 = (savepoint.param.intValue == kObjectCompartment1 || savepoint.param.intValue == kObjectCompartment2) ? kCarGreenSleeping : kCarRedSleeping; + params1->param2 = (savepoint.param.intValue == kObjectCompartment7 || savepoint.param.intValue == kObjectCompartmentG) ? kPosition_3050 : kPosition_2740; + params1->param3 = kPositionNone; + break; + } + break; + + case kActionBreakCeiling: + setCallback(6); + setup_savegame(kSavegameTypeEvent, kEventCathBreakCeiling); + break; + + case kActionJumpDownCeiling: + setCallback(7); + setup_savegame(kSavegameTypeEvent, kEventCathJumpDownCeiling); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +// Private functions +////////////////////////////////////////////////////////////////////////// +void Train::handleCompartmentAction() { + EXPOSE_PARAMS(EntityData::EntityParametersIIII) + + if (params->param8) + getSavePoints()->push(kEntityTrain, kEntityMahmud, kAction290410610, params->param1); + + getAction()->handleOtherCompartment((ObjectIndex)params->param1, false, !params->param8); + + ENTITY_PARAM(0, 8) = params->param1; + + CALLBACK_ACTION(); +} + +////////////////////////////////////////////////////////////////////////// +void Train::resetParam8() { + EXPOSE_PARAMS(EntityData::EntityParametersIIII) + EntityData::EntityParametersIIIS *params1 = (EntityData::EntityParametersIIIS*)_data->getCurrentParameters(1); + + if (params->param8 + && !getEntities()->isInsideCompartment(kEntityPlayer, (CarIndex)params1->param1, (EntityPosition)params1->param2) + && !getEntities()->isInsideCompartment(kEntityPlayer, (CarIndex)params1->param1, (EntityPosition)params1->param3)) { + + if (getSound()->isBuffered((const char *)¶ms1->seq)) + getSound()->processEntry((const char *)¶ms1->seq); + + params->param8 = 0; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/train.h b/engines/lastexpress/entities/train.h new file mode 100644 index 0000000000..4c6e2989ff --- /dev/null +++ b/engines/lastexpress/entities/train.h @@ -0,0 +1,95 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_TRAIN_H +#define LASTEXPRESS_TRAIN_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Train : public Entity { +public: + Train(LastExpressEngine *engine); + ~Train() {}; + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Harem events + * + * @param compartment The compartment to handle + * @param counter ??? (checked to decide which sound to make when knocking) + */ + DECLARE_FUNCTION_2(harem, ObjectIndex compartment, uint32 counter) + + /** + * Handles Train events + */ + DECLARE_FUNCTION(process) + +private: + // Helper methods + void resetParam8(); + void handleCompartmentAction(); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_TRAIN_H diff --git a/engines/lastexpress/entities/vassili.cpp b/engines/lastexpress/entities/vassili.cpp new file mode 100644 index 0000000000..951c323f91 --- /dev/null +++ b/engines/lastexpress/entities/vassili.cpp @@ -0,0 +1,589 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/vassili.h" + +#include "lastexpress/entities/anna.h" +#include "lastexpress/entities/coudert.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Vassili::Vassili(LastExpressEngine *engine) : Entity(engine, kEntityVassili) { + ADD_CALLBACK_FUNCTION(Vassili, reset); + ADD_CALLBACK_FUNCTION(Vassili, draw); + ADD_CALLBACK_FUNCTION(Vassili, savegame); + ADD_CALLBACK_FUNCTION(Vassili, chapter1); + ADD_CALLBACK_FUNCTION(Vassili, chapter1Handler); + ADD_CALLBACK_FUNCTION(Vassili, function6); + ADD_CALLBACK_FUNCTION(Vassili, function7); + ADD_CALLBACK_FUNCTION(Vassili, function8); + ADD_CALLBACK_FUNCTION(Vassili, function9); + ADD_CALLBACK_FUNCTION(Vassili, seizure); + ADD_CALLBACK_FUNCTION(Vassili, drawInBed); + ADD_CALLBACK_FUNCTION(Vassili, chapter2); + ADD_CALLBACK_FUNCTION(Vassili, sleeping); + ADD_CALLBACK_FUNCTION(Vassili, chapter3); + ADD_CALLBACK_FUNCTION(Vassili, stealEgg); + ADD_CALLBACK_FUNCTION(Vassili, chapter4); + ADD_CALLBACK_FUNCTION(Vassili, chapter4Handler); + ADD_CALLBACK_FUNCTION(Vassili, chapter5); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Vassili, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Vassili, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(3, Vassili, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(4, Vassili, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObject40, kEntityPlayer, kObjectLocationNone, kCursorKeepValue, kCursorKeepValue); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(5, Vassili, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1) { + getData()->entityPosition = getEntityData(kEntityTatiana)->entityPosition; + getData()->location = getEntityData(kEntityTatiana)->location; + } else { + if (params->param3 && params->param3 >= getState()->time) { + break; + }else { + params->param3 = getState()->time + 450; + if (params->param3 == 0) + break; + } + + if (!params->param2 && getObjects()->get(kObjectCompartmentA).location == kObjectLocation1) { + params->param2 = 1; + getEntities()->drawSequenceLeft(kEntityVassili, "303A"); + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + } + break; + } + break; + + case kActionDefault: + params->param1 = 1; + break; + + case kAction122732000: + setup_function6(); + break; + + case kAction168459827: + params->param1 = 0; + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6, Vassili, function6) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_8200)) { + UPDATE_PARAM_GOTO(params->param3, getState()->timeTicks, params->param1, label_function7); + + setCallback(1); + setup_draw("303B"); + break; + } + + params->param3 = 0; + + if (params->param2) + getEntities()->drawSequenceLeft(kEntityVassili, "303A"); + +label_function7: + if (params->param4 != kTimeInvalid && getState()->time > kTime1489500) { + + if (getState()->time <= kTime1503000) { + + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_8200) || !params->param4) { + + params->param4 = getState()->time; + if (!params->param4) { + setup_function7(); + break; + } + } + + if (params->param4 >= getState()->time) + break; + } + + params->param4 = kTimeInvalid; + setup_function7(); + } + break; + + case kActionDefault: + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + params->param1 = 5 * (3 * rnd(25) + 15); + + getEntities()->drawSequenceLeft(kEntityVassili, "303A"); + break; + + case kActionCallback: + if (getCallback() == 1) { + getEntities()->drawSequenceLeft(kEntityVassili, "303C"); + params->param1 = 5 * (3 * rnd(25) + 15); + params->param2 = 1; + + // Shared part with kActionNone + goto label_function7; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Vassili, function7) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param1 != kTimeInvalid && getState()->time > kTime1503000) { + + if (getState()->time <= kTime1512000) { + if (getEntities()->isPlayerInCar(kCarRedSleeping) || !params->param1) { + params->param1 = getState()->time + 150; + if (params->param1) { + setup_function8(); + break; + } + } + + if (params->param1 >= getState()->time) + break; + } + + params->param1 = kTimeInvalid; + setup_function8(); + } + break; + + case kActionDefault: + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getEntities()->clearSequences(kEntityVassili); + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_8200)) + getScenes()->loadSceneFromObject(kObjectCompartmentA); + + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + break; + + case kAction339669520: + setup_function9(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Vassili, function8) + switch (savepoint.action) { + default: + break; + + case kActionEndSound: + setup_function9(); + break; + + case kActionDefault: + if (!getEntities()->isInsideTrainCar(kEntityPlayer, kCarRedSleeping)) { + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromPosition(kCarRedSleeping, (getEntityData(kEntityPlayer)->car <= kCarRedSleeping) ? 1 : 40); + } + + getSavePoints()->push(kEntityVassili, kEntityAnna, kAction226031488); + getSavePoints()->push(kEntityVassili, kEntityVerges, kAction226031488); + getSavePoints()->push(kEntityVassili, kEntityCoudert, kAction226031488); + getSound()->playSound(kEntityVassili, "VAS1027", SoundManager::kFlagDefault); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Vassili, function9) + switch (savepoint.action) { + default: + break; + + case kActionEndSound: + if (!getEntities()->isDistanceBetweenEntities(kEntityVassili, kEntityPlayer, 2500)) + getSound()->playSound(kEntityPlayer, "BUMP"); + + setup_seizure(); + break; + + case kActionDefault: + case kActionDrawScene: + if ((getObjects()->get(kObjectCompartmentA).location == kObjectLocation2 && getEntities()->isPlayerPosition(kCarRedSleeping, 17)) + || getEntities()->isPlayerPosition(kCarRedSleeping, 18) + || getEntities()->isPlayerPosition(kCarRedSleeping, 37) + || getEntities()->isPlayerPosition(kCarRedSleeping, 38) + || getEntities()->isPlayerPosition(kCarRedSleeping, 41)) { + + if (savepoint.action == kActionDrawScene) + getSound()->processEntry(kEntityVassili); + + setup_seizure(); + } else { + if (savepoint.action == kActionDefault) + getSound()->playSound(kEntityVassili, "VAS1028", SoundManager::kFlagDefault); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Vassili, seizure) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + // Check that we have removed the body from the train and changed jacket + if (!getProgress().eventCorpseMovedFromFloor) { + getAction()->playAnimation(kEventMertensCorpseFloor); + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneNone, false); + break; + } + + if (!getProgress().eventCorpseThrown) { + getAction()->playAnimation(kEventMertensCorpseBed); + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneNone, false); + break; + } + + if (getProgress().jacket == kJacketBlood) { + getAction()->playAnimation(kEventMertensBloodJacket); + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneNone, false); + break; + } + + // Setup Anna & Coudert + RESET_ENTITY_STATE(kEntityAnna, Anna, setup_function37); + RESET_ENTITY_STATE(kEntityCoudert, Coudert, setup_function38); + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventVassiliSeizure); + break; + + case kActionCallback: + if (getCallback() != 1) + break; + + getData()->location = kLocationInsideCompartment; + getAction()->playAnimation(kEventVassiliSeizure); + + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getProgress().field_18 = 2; + + getSavePoints()->push(kEntityVassili, kEntityAnna, kAction191477936); + getSavePoints()->push(kEntityVassili, kEntityVerges, kAction191477936); + getSavePoints()->push(kEntityVassili, kEntityCoudert, kAction191477936); + getScenes()->loadSceneFromObject(kObjectCompartmentA); + + setup_drawInBed(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Vassili, drawInBed) + if (savepoint.action == kActionDefault) + getEntities()->drawSequenceLeft(kEntityVassili, "303A"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Vassili, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_sleeping(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityVassili); + + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->updateLocation2(kObjectCompartmentA, kObjectLocation1); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Vassili, sleeping) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_8200)) { + UPDATE_PARAM(params->param3, getState()->timeTicks, params->param1); + + setCallback(1); + setup_draw("303B"); + } else { + params->param3 = 0; + if (params->param2) + getEntities()->drawSequenceLeft(kEntityVassili, "303A"); + } + break; + + case kActionDefault: + params->param5 = 5 * (3 * rnd(25) + 15); + getEntities()->drawSequenceLeft(kEntityVassili, "303A"); + break; + + case kActionCallback: + if (getCallback() != 1) + break; + + getEntities()->drawSequenceLeft(kEntityVassili, "303C"); + params->param1 = 5 * (3 * rnd(25) + 15); + params->param2 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Vassili, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_stealEgg(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityVassili); + + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Vassili, stealEgg) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_8200)) { + UPDATE_PARAM(params->param3, getState()->timeTicks, params->param1); + + setCallback(1); + setup_draw("303B"); + } else { + params->param3 = 0; + if (params->param2) + getEntities()->drawSequenceLeft(kEntityVassili, "303A"); + } + break; + + case kActionOpenDoor: + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventVassiliCompartmentStealEgg); + break; + + case kActionDefault: + params->param5 = 5 * (3 * rnd(25) + 15); + getEntities()->drawSequenceLeft(kEntityVassili, "303A"); + break; + + case kActionDrawScene: + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_7850) + && getInventory()->hasItem(kItemFirebird) + && !getEvent(kEventVassiliCompartmentStealEgg)) + getObjects()->update(kObject48, kEntityVassili, kObjectLocationNone, kCursorNormal, kCursorHand); + else + getObjects()->update(kObject48, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityVassili, "303C"); + params->param1 = 5 * (3 * rnd(25) + 15); + params->param2 = 1; + break; + + case 2: + getAction()->playAnimation(kEventVassiliCompartmentStealEgg); + getScenes()->loadSceneFromPosition(kCarRedSleeping, 67); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Vassili, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityVassili); + + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentA, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getObjects()->updateLocation2(kObjectCompartmentA, kObjectLocation1); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +// Looks identical to sleeping (#13) +IMPLEMENT_FUNCTION(17, Vassili, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_8200)) { + UPDATE_PARAM(params->param3, getState()->timeTicks, params->param1); + + setCallback(1); + setup_draw("303B"); + } else { + params->param3 = 0; + if (params->param2) + getEntities()->drawSequenceLeft(kEntityVassili, "303A"); + } + break; + + case kActionDefault: + params->param5 = 5 * (3 * rnd(25) + 15); + getEntities()->drawSequenceLeft(kEntityVassili, "303A"); + break; + + case kActionCallback: + if (getCallback() != 1) + break; + + getEntities()->drawSequenceLeft(kEntityVassili, "303C"); + params->param1 = 5 * (3 * rnd(25) + 15); + params->param2 = 1; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Vassili, chapter5) + if (savepoint.action == kActionDefault) { + getEntities()->clearSequences(kEntityVassili); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/vassili.h b/engines/lastexpress/entities/vassili.h new file mode 100644 index 0000000000..113bc1d958 --- /dev/null +++ b/engines/lastexpress/entities/vassili.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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_VASSILI_H +#define LASTEXPRESS_VASSILI_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Vassili : public Entity { +public: + Vassili(LastExpressEngine *engine); + ~Vassili() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function6) + DECLARE_FUNCTION(function7) + DECLARE_FUNCTION(function8) + DECLARE_FUNCTION(function9) + DECLARE_FUNCTION(seizure) + DECLARE_FUNCTION(drawInBed) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + DECLARE_FUNCTION(sleeping) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + DECLARE_FUNCTION(stealEgg) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_VASSILI_H diff --git a/engines/lastexpress/entities/verges.cpp b/engines/lastexpress/entities/verges.cpp new file mode 100644 index 0000000000..20979ff9f9 --- /dev/null +++ b/engines/lastexpress/entities/verges.cpp @@ -0,0 +1,1898 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/verges.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Verges::Verges(LastExpressEngine *engine) : Entity(engine, kEntityVerges) { + ADD_CALLBACK_FUNCTION(Verges, reset); + ADD_CALLBACK_FUNCTION(Verges, draw); + ADD_CALLBACK_FUNCTION(Verges, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Verges, playSound); + ADD_CALLBACK_FUNCTION(Verges, playSound16); + ADD_CALLBACK_FUNCTION(Verges, callbackActionRestaurantOrSalon); + ADD_CALLBACK_FUNCTION(Verges, savegame); + ADD_CALLBACK_FUNCTION(Verges, updateEntity); + ADD_CALLBACK_FUNCTION(Verges, function9); + ADD_CALLBACK_FUNCTION(Verges, function10); + ADD_CALLBACK_FUNCTION(Verges, function11); + ADD_CALLBACK_FUNCTION(Verges, function12); + ADD_CALLBACK_FUNCTION(Verges, function13); + ADD_CALLBACK_FUNCTION(Verges, updateFromTime); + ADD_CALLBACK_FUNCTION(Verges, function15); + ADD_CALLBACK_FUNCTION(Verges, function16); + ADD_CALLBACK_FUNCTION(Verges, function17); + ADD_CALLBACK_FUNCTION(Verges, chapter1); + ADD_CALLBACK_FUNCTION(Verges, talkHarem); + ADD_CALLBACK_FUNCTION(Verges, talkPassengerList); + ADD_CALLBACK_FUNCTION(Verges, talkGendarmes); + ADD_CALLBACK_FUNCTION(Verges, function22); + ADD_CALLBACK_FUNCTION(Verges, function23); + ADD_CALLBACK_FUNCTION(Verges, policeGettingOffTrain); + ADD_CALLBACK_FUNCTION(Verges, function25); + ADD_CALLBACK_FUNCTION(Verges, chapter1Handler); + ADD_CALLBACK_FUNCTION(Verges, chapter2); + ADD_CALLBACK_FUNCTION(Verges, chapter2Handler); + ADD_CALLBACK_FUNCTION(Verges, chapter3); + ADD_CALLBACK_FUNCTION(Verges, function30); + ADD_CALLBACK_FUNCTION(Verges, function31); + ADD_CALLBACK_FUNCTION(Verges, function32); + ADD_CALLBACK_FUNCTION(Verges, function33); + ADD_CALLBACK_FUNCTION(Verges, function34); + ADD_CALLBACK_FUNCTION(Verges, function35); + ADD_CALLBACK_FUNCTION(Verges, chapter4); + ADD_CALLBACK_FUNCTION(Verges, chapter4Handler); + ADD_CALLBACK_FUNCTION(Verges, function38); + ADD_CALLBACK_FUNCTION(Verges, chapter5); + ADD_CALLBACK_FUNCTION(Verges, chapter5Handler); + ADD_CALLBACK_FUNCTION(Verges, function41); + ADD_CALLBACK_FUNCTION(Verges, function42); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Verges, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Verges, draw) + Entity::draw(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(3, Verges, callbackActionOnDirection) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getData()->direction != kDirectionRight) + CALLBACK_ACTION(); + break; + + case kActionExitCompartment: + CALLBACK_ACTION(); + break; + + case kActionExcuseMeCath: + if (!params->param1) { + getSound()->excuseMe(kEntityVerges); + params->param1 = 1; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(4, Verges, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(5, Verges, playSound16) + Entity::playSound(savepoint, false, SoundManager::kFlagDefault); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6, Verges, callbackActionRestaurantOrSalon) + Entity::callbackActionRestaurantOrSalon(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(7, Verges, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(8, Verges, updateEntity, CarIndex, EntityPosition) + if (savepoint.action == kActionExcuseMeCath) { + if (!getSound()->isBuffered(kEntityVerges)) + getSound()->playSound(kEntityPlayer, "TRA1113", getSound()->getSoundFlag(kEntityVerges)); + + return; + } + + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(9, Verges, function9) +switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObject104, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + getObjects()->update(kObject105, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + + if (getEntities()->isInBaggageCar(kEntityPlayer) || getEntities()->isInKitchen(kEntityPlayer)) { + getAction()->playAnimation(getEntities()->isInBaggageCar(kEntityPlayer) ? kEventVergesBaggageCarOffLimits : kEventVergesCanIHelpYou); + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromPosition(kCarRestaurant, 65); + } + + getScenes()->loadSceneFromItemPosition(kItem9); + getData()->car = kCarRestaurant; + getData()->entityPosition = kPosition_5900; + + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + getSound()->playSound(kEntityVerges, (char *)¶ms->seq1); + + setCallback(2); + setup_draw("813DD"); + break; + + case 2: + if (!getSound()->isBuffered(kEntityVerges)) + getSound()->playSound(kEntityVerges, (char *)¶ms->seq1); + + getEntities()->drawSequenceRight(kEntityVerges, "813DS"); + + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityVerges); + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case 3: + setCallback(4); + setup_function10(kCarGreenSleeping, kPosition_540, (char *)¶ms->seq1); + break; + + case 4: + getEntities()->clearSequences(kEntityVerges); + + setCallback(5); + setup_updateFromTime(225); + break; + + case 5: + setCallback(6); + setup_function11(); + break; + + case 6: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_IIS(10, Verges, function10, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param7) { + if (!getSound()->isBuffered(kEntityVerges)) { + getSound()->playSound(kEntityVerges, (char *)¶ms->seq); + params->param7 = 1; + } + } + + if (getEntities()->updateEntity(kEntityVerges, (CarIndex)params->param1, (EntityPosition)params->param2)) { + CALLBACK_ACTION(); + break; + } + + if (params->param6) { + UPDATE_PARAM(params->param8, getState()->timeTicks, 75); + + getSound()->playSound(kEntityVerges, (char *)¶ms->seq); + + params->param6 = 0; + params->param8 = 0; + } + break; + + case kActionEndSound: + params->param6 = 1; + break; + + case kActionDefault: + if (!getSound()->isBuffered(kEntityVerges)) { + getSound()->playSound(kEntityVerges, (char *)¶ms->seq); + params->param7 = 1; + } + + if (getEntities()->updateEntity(kEntityVerges, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Verges, function11) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_updateEntity(kCarRestaurant, kPosition_540); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_callbackActionRestaurantOrSalon(); + break; + + case 2: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(3); + setup_draw("813US"); + break; + + case 3: + getEntities()->drawSequenceRight(kEntityVerges, "813UD"); + + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityVerges); + + setCallback(4); + setup_callbackActionOnDirection(); + break; + + case 4: { + getEntities()->clearSequences(kEntityVerges); + + bool loadscene = true; + + if (getEntities()->isInBaggageCarEntrance(kEntityPlayer)) + getAction()->playAnimation(kEventVergesEscortToDiningCar); + else if (getEntities()->isInBaggageCar(kEntityPlayer)) + getAction()->playAnimation(kEventVergesBaggageCarOffLimits); + else if (getEntities()->isInKitchen(kEntityPlayer)) + getAction()->playAnimation(kEventVergesCanIHelpYou); + else + loadscene = false; + + if (loadscene) { + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromPosition(kCarRestaurant, 65); + } + + getInventory()->setLocationAndProcess(kItem9, kObjectLocation1); + + getData()->car = kCarBaggage; + getData()->entityPosition = kPosition_5000; + + getObjects()->update(kObject104, kEntityVerges, kObjectLocationNone, kCursorNormal, kCursorHand); + getObjects()->update(kObject105, kEntityVerges, kObjectLocationNone, kCursorNormal, kCursorHand); + + CALLBACK_ACTION(); + break; + } + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Verges, function12) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObject104, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + getObjects()->update(kObject105, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + + if (getEntities()->isInBaggageCar(kEntityPlayer) || getEntities()->isInKitchen(kEntityPlayer)) { + getAction()->playAnimation(getEntities()->isInBaggageCar(kEntityPlayer) ? kEventVergesBaggageCarOffLimits : kEventVergesCanIHelpYou); + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromPosition(kCarRestaurant, 65); + } + + getScenes()->loadSceneFromItemPosition(kItem9); + + getData()->car = kCarRestaurant; + getData()->entityPosition = kPosition_5900; + + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_draw("813DD"); + break; + + case 2: + getEntities()->drawSequenceRight(kEntityVerges, "813DS"); + + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityVerges); + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case 3: + getData()->entityPosition = kPosition_850; + getEntities()->clearSequences(kEntityVerges); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(13, Verges, function13, bool) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventVergesSuitcase); + break; + + case kActionCallback: + if (getCallback() == 1) { + if (getEvent(kEventVergesSuitcase) || getEvent(kEventVergesSuitcaseNight) || getEvent(kEventVergesSuitcaseOtherEntry) || getEvent(kEventVergesSuitcaseNightOtherEntry)) + params->param2 = 1; + + if (isNight() && getProgress().chapter != kChapter1) + params->param2 = 1; + + if (params->param1) { + if (isNight()) + getAction()->playAnimation(params->param2 ? kEventVergesSuitcaseNightOtherEntryStart : kEventVergesSuitcaseNightOtherEntry); + else + getAction()->playAnimation(params->param2 ? kEventVergesSuitcaseOtherEntryStart : kEventVergesSuitcaseOtherEntry); + } else { + if (isNight()) + getAction()->playAnimation(params->param2 ? kEventVergesSuitcaseNightStart : kEventVergesSuitcaseNight); + else + getAction()->playAnimation(params->param2 ? kEventVergesSuitcaseStart : kEventVergesSuitcase); + } + + getEntities()->clearSequences(kEntityVerges); + getScenes()->loadSceneFromPosition(kCarBaggage, 91); + + CALLBACK_ACTION(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(14, Verges, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_IS(15, Verges, function15, EntityIndex) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (params->param5 && params->param6) { + getSavePoints()->push(kEntityVerges, (EntityIndex)params->param1, kAction125499160); + + if (!getEntities()->isPlayerPosition(kCarGreenSleeping, 2) && !getEntities()->isPlayerPosition(kCarRedSleeping, 2)) + getData()->entityPosition = kPosition_2088; + + CALLBACK_ACTION(); + } + break; + + case kActionEndSound: + params->param5 = 1; + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityVerges, "620F"); + getSavePoints()->push(kEntityVerges, (EntityIndex)params->param1, kAction171394341); + break; + + case kAction155853632: + params->param6 = 1; + break; + + case kAction202558662: + getEntities()->drawSequenceLeft(kEntityVerges, "620E"); + getSound()->playSound(kEntityVerges, (char *)¶ms->seq); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_ISS(16, Verges, function16, EntityIndex) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (CURRENT_PARAMS(1, 1) && params->param8) { + getSavePoints()->push(kEntityVerges, (EntityIndex)params->param1, kAction125499160); + + if (!getEntities()->isPlayerPosition(kCarGreenSleeping, 2) && !getEntities()->isPlayerPosition(kCarRedSleeping, 2)) + getData()->entityPosition = kPosition_2088; + + CALLBACK_ACTION(); + } + break; + + case kActionEndSound: + CURRENT_PARAMS(1, 1)++; + + if (CURRENT_PARAMS(1, 1) == 1) + getSound()->playSound(kEntityVerges, (char *)¶ms->seq2); + break; + + case kActionDefault: + getEntities()->drawSequenceLeft(kEntityVerges, "620F"); + getSavePoints()->push(kEntityVerges, (EntityIndex)params->param1, kAction171394341); + break; + + case kAction155853632: + params->param8 = 1; + break; + + case kAction202558662: + getEntities()->drawSequenceLeft(kEntityVerges, "620E"); + getSound()->playSound(kEntityVerges, (char *)¶ms->seq1); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Verges, function17) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function12(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 2: + setCallback(3); + setup_function15(kEntityMertens, "TRA1291"); + break; + + case 3: + setCallback(4); + setup_function11(); + break; + + case 4: + ENTITY_PARAM(0, 3) = 0; + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Verges, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getSavePoints()->addData(kEntityVerges, kActionDeliverMessageToTyler, 0); + getSavePoints()->addData(kEntityVerges, kAction226031488, 1); + getSavePoints()->addData(kEntityVerges, kAction339669520, 1); + getSavePoints()->addData(kEntityVerges, kAction167854368, 4); + getSavePoints()->addData(kEntityVerges, kAction158617345, 2); + getSavePoints()->addData(kEntityVerges, kAction168255788, 3); + getSavePoints()->addData(kEntityVerges, kAction201431954, 5); + getSavePoints()->addData(kEntityVerges, kAction168187490, 6); + + getObjects()->update(kObject104, kEntityVerges, kObjectLocationNone, kCursorNormal, kCursorHand); + getObjects()->update(kObject105, kEntityVerges, kObjectLocationNone, kCursorNormal, kCursorHand); + + getData()->entityPosition = kPosition_5000; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarBaggage; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(19, Verges, talkHarem) + talk(savepoint, "TRA1202", "TRA1201"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Verges, talkPassengerList) + talk(savepoint, "TRA1205", "TRA1206"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Verges, talkGendarmes) + talk(savepoint, "TRA1250", "TRA1251"); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Verges, function22) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function12(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 2: + if (getEvent(kEventMertensAskTylerCompartment) || getEvent(kEventMertensAskTylerCompartmentD) || getEvent(kEventMertensAugustWaiting)) { + setCallback(3); + setup_function16(kEntityMertens, "TRA1200", "TRA1201"); + } else { + setCallback(4); + setup_function16(kEntityMertens, "TRA1200A", "TRA1201"); + } + break; + + case 3: + case 4: + getSavePoints()->push(kEntityVerges, kEntityMertens, kAction169633856); + + setCallback(5); + setup_function11(); + break; + + case 5: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Verges, function23) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getScenes()->loadSceneFromItemPosition(kItem9); + + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + break; + + case kAction191477936: + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRedSleeping; + + setCallback(1); + setup_function11(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Verges, policeGettingOffTrain) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isDistanceBetweenEntities(kEntityVerges, kEntityPlayer, 1000) && getEntityData(kEntityPlayer)->location == kLocationOutsideCompartment) { + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventGendarmesArrestation); + } + break; + + case kActionEndSound: + CALLBACK_ACTION(); + break; + + case kActionDefault: + getSound()->playSound(kEntityVerges, "POL1101", SoundManager::kFlagDefault); + break; + + case kActionCallback: + if (getCallback() == 1) { + getSound()->processEntry(kEntityVerges); + getAction()->playAnimation(kEventGendarmesArrestation); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneGameOverPolice1, true); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Verges, function25) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getScenes()->loadSceneFromItemPosition(kItem9); + + if (!getEntities()->isInKronosSalon(kEntityPlayer)) { + + if (getEntityData(kEntityPlayer)->car > kCarRedSleeping + || (getEntityData(kEntityPlayer)->car == kCarRedSleeping && getEntityData(kEntityPlayer)->entityPosition > kPosition_9270)) { + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromPosition(kCarRedSleeping, 40); + + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_9270; + } else { + if (getEntityData(kEntityPlayer)->car > kCarGreenSleeping + || (getEntityData(kEntityPlayer)->car == kCarGreenSleeping && getEntityData(kEntityPlayer)->entityPosition < kPosition_4840)) { + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromObject(kObjectCompartment5, true); + } + + getData()->car = kCarGreenSleeping; + getData()->entityPosition = kPosition_850; + } + + getData()->location = kLocationOutsideCompartment; + + getObjects()->update(kObjectRestaurantCar, kEntityPlayer, kObjectLocation1, kCursorNormal, kCursorForward); + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + + if (getEntities()->isOutsideAnnaWindow()) + getScenes()->loadSceneFromPosition(kCarRedSleeping, 49); + + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_4840) + || getEntities()->isInsideCompartment(kEntityPlayer, kCarRedSleeping, kPosition_4455)) { + getAction()->playAnimation(isNight() ? kEventCathTurningNight : kEventCathTurningDay); + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromObject(kObjectCompartmentE, true); + } + + getSavePoints()->push(kEntityVerges, kEntityGendarmes, kAction169499649); + + getProgress().field_3C = 1; + getState()->timeDelta = 1; + + if (getData()->car == kCarRedSleeping) { + setCallback(6); + setup_function10(kCarGreenSleeping, kPosition_540, "TRA1005"); + } else { + setCallback(7); + setup_function10(kCarRedSleeping, kPosition_9460, "TRA1006"); + } + break; + } + // Fallback to next case + + case 2: + if (getEvent(kEventKronosConversation)) { + getProgress().field_3C = 1; + getData()->car = kCarGreenSleeping; + getData()->entityPosition = kPosition_540; + getData()->location = kLocationOutsideCompartment; + + getState()->timeDelta = 3; + getSavePoints()->push(kEntityVerges, kEntityChapters, kAction169629818); + + setCallback(3); + setup_policeGettingOffTrain(); + } else { + setCallback(2); + setup_updateFromTime(150); + } + break; + + case 3: + getSavePoints()->push(kEntityVerges, kEntityCoudert, kAction168254872); + + setCallback(4); + setup_function10(kCarRedSleeping, kPosition_9460, "TRA1006"); + break; + + case 4: + setCallback(5); + setup_function11(); + break; + + case 5: + case 11: + ENTITY_PARAM(0, 7) = 0; + + CALLBACK_ACTION(); + break; + + case 6: + case 7: + getEntities()->clearSequences(kEntityVerges); + break; + + case 8: + getSavePoints()->push(kEntityVerges, kEntityChapters, kAction169629818); + + setCallback(9); + setup_policeGettingOffTrain(); + break; + + case 9: + getObjects()->update(kObjectRestaurantCar, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + getObjects()->update(kObjectCompartmentE, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + getSavePoints()->push(kEntityVerges, kEntityCoudert, kAction168254872); + + setCallback(10); + setup_function10(kCarGreenSleeping, kPosition_540, "TRA1006"); + break; + + case 10: + setCallback(11); + setup_function11(); + break; + } + break; + + case kAction168710784: + getData()->car = kCarGreenSleeping; + + if (!(getEntityData(kEntityPlayer)->car == kCarGreenSleeping)) + getData()->car = kCarRedSleeping; + + getData()->entityPosition = kPosition_8200; + getData()->location = kLocationOutsideCompartment; + + getState()->timeDelta = 3; + + setCallback(8); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Verges, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (ENTITY_PARAM(0, 6)) { + params->param1 = 1; + params->param2 = 1; + params->param3 = 1; + params->param4 = 1; + params->param5 = 1; + params->param6 = 1; + + ENTITY_PARAM(0, 6) = 0; + } + + if (ENTITY_PARAM(0, 2)) { + setCallback(1); + setup_function23(); + break; + } + +label_callback1: + if (getEntities()->isInBaggageCarEntrance(kEntityPlayer)) { + setCallback(2); + setup_function13(false); + break; + } + +label_callback2: + if (ENTITY_PARAM(0, 7)) { + setCallback(3); + setup_function25(); + break; + } + +label_callback3: + if (params->param6) + goto label_callback12; + + TIME_CHECK_CALLBACK_1(kTimeChapter1, params->param7, 4, setup_function9, "TRA1001"); + +label_callback4: + TIME_CHECK_CALLBACK(kTime1089000, params->param8, 5, setup_function12); + + params->param8 = 1; + + if (!params->param5) { + setCallback(5); + setup_function12(); + break; + } + +label_callback8: + TIME_CHECK_CALLBACK_1(kTime1107000, CURRENT_PARAMS(1, 1), 9, setup_function9, "TRA1001A"); + +label_callback9: + TIME_CHECK_CALLBACK_1(kTime1134000, CURRENT_PARAMS(1, 2), 10, setup_function9, "TRA1002"); + +label_callback10: + TIME_CHECK_CALLBACK_1(kTime1165500, CURRENT_PARAMS(1, 3), 11, setup_function9, "TRA1003"); + +label_callback11: + TIME_CHECK_CALLBACK_1(kTime1225800, CURRENT_PARAMS(1, 4), 12, setup_function9, "TRA1004"); + +label_callback12: + if (ENTITY_PARAM(0, 5) && !params->param2) { + setCallback(13); + setup_talkGendarmes(); + break; + } + +label_callback13: + if (getInventory()->hasItem(kItemPassengerList) && !params->param3 && (getState()->time < kTime1134000 || getState()->time > kTime1156500)) { + setCallback(14); + setup_talkPassengerList(); + break; + } + +label_callback14: + if (ENTITY_PARAM(0, 3) && !params->param4 && (getState()->time < kTime1134000 || getState()->time > kTime1156500)) { + setCallback(15); + setup_function17(); + break; + } + +label_callback15: + if (ENTITY_PARAM(0, 1) && !params->param5) { + if (getState()->time < kTime1134000 || getState()->time > kTime1156500) { + setCallback(16); + setup_function22(); + } + } + break; + + case kActionOpenDoor: + setCallback(17); + setup_function13(savepoint.param.intValue < 106 ? true : false); + break; + + case kActionDefault: + getData()->car = kCarBaggage; + getData()->entityPosition = kPosition_5000; + getData()->location = kLocationOutsideCompartment; + + getEntities()->clearSequences(kEntityVerges); + getInventory()->setLocationAndProcess(kItem9, kObjectLocation1); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback1; + + case 2: + goto label_callback2; + + case 3: + goto label_callback3; + + case 4: + goto label_callback4; + + case 5: + setCallback(6); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 6: + setCallback(7); + setup_function15(kEntityMertens, "TRA1202"); + break; + + case 7: + setCallback(8); + setup_function11(); + break; + + case 8: + goto label_callback8; + + case 9: + goto label_callback9; + + case 10: + goto label_callback10; + + case 11: + goto label_callback11; + + case 12: + goto label_callback12; + + case 13: + params->param2 = 1; + goto label_callback13; + + case 14: + params->param3 = 1; + goto label_callback14; + + case 15: + params->param4 = 1; + goto label_callback15; + + case 16: + params->param5 = 1; + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Verges, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityVerges); + + getData()->entityPosition = kPosition_5000; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarBaggage; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObject104, kEntityVerges, kObjectLocationNone, kCursorNormal, kCursorHand); + getObjects()->update(kObject105, kEntityVerges, kObjectLocationNone, kCursorNormal, kCursorHand); + + ENTITY_PARAM(0, 3) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Verges, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInBaggageCarEntrance(kEntityPlayer)) { + setCallback(1); + setup_function13(false); + } + +label_callback_1: + TIME_CHECK_CALLBACK_1(kTime1818900, params->param1, 2, setup_function9, "Tra2177"); + +label_callback_2: + if (params->param2 == kTimeInvalid || !getState()->time) + goto label_callback_6; + + if (getState()->time > kTime1836000) { + params->param2 = kTimeInvalid; + setCallback(3); + setup_function12(); + break; + } + + if (!getEntities()->isPlayerInCar(kCarRedSleeping) || !params->param2) { + params->param2 = getState()->time; + + if (!params->param2) { + setCallback(3); + setup_function12(); + break; + } + } + + if (params->param2 >= getState()->time) { +label_callback_6: + + if (ENTITY_PARAM(0, 3)) { + setCallback(7); + setup_function17(); + } + + break; + } + + params->param2 = kTimeInvalid; + setCallback(3); + setup_function12(); + break; + + case kActionOpenDoor: + setCallback(8); + setup_function13(savepoint.param.intValue < 106); + break; + + case kActionDefault: + getInventory()->setLocationAndProcess(kItem9, kObjectLocation1); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback_1; + + case 2: + goto label_callback_2; + + case 3: + setCallback(4); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 4: + setCallback(5); + setup_function15(kEntityCoudert, "TRA2100"); + break; + + case 5: + setCallback(6); + setup_function11(); + break; + + case 6: + goto label_callback_6; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Verges, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_function23(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityVerges); + + getData()->entityPosition = kPosition_540; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObject104, kEntityVerges, kObjectLocationNone, kCursorNormal, kCursorHand); + getObjects()->update(kObject105, kEntityVerges, kObjectLocationNone, kCursorNormal, kCursorHand); + + ENTITY_PARAM(0, 3) = 0; + ENTITY_PARAM(0, 4) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(30, Verges, function30) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function12(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 2: + setCallback(3); + setup_function15(kEntityCoudert, (char *)¶ms->seq1); + break; + + case 3: + setCallback(4); + setup_function11(); + break; + + case 4: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(31, Verges, function31) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function12(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 2: + setCallback(3); + setup_function15(kEntityCoudert, "TRA3015"); + break; + + case 3: + setCallback(4); + setup_function11(); + break; + + case 4: + getProgress().field_48 = 1; + ENTITY_PARAM(0, 4) = 0; + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(32, Verges, function32) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK_3(kTime2263500, params->param1, 5, setup_function10, kCarRedSleeping, kPosition_9460, "TRA3006"); + break; + + case kActionDefault: + getObjects()->update(kObject104, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + getObjects()->update(kObject105, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + + if (getEntities()->isInBaggageCar(kEntityPlayer) || getEntities()->isInKitchen(kEntityPlayer)) { + getAction()->playAnimation(getEntities()->isInBaggageCar(kEntityPlayer) ? kEventVergesBaggageCarOffLimits : kEventVergesCanIHelpYou); + getSound()->playSound(kEntityPlayer, "BUMP"); + getScenes()->loadSceneFromPosition(kCarRestaurant, 65); + } + + getScenes()->loadSceneFromItemPosition(kItem9); + getData()->car = kCarRestaurant; + getData()->entityPosition = kPosition_5900; + + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->entityPosition = kPosition_8500; + getData()->location = kLocationOutsideCompartment; + getSound()->playSound(kEntityVerges, "TRA3004"); + + setCallback(2); + setup_draw("813DD"); + break; + + case 2: + if (!getSound()->isBuffered(kEntityVerges)) + getSound()->playSound(kEntityVerges, "TRA3004"); + + getEntities()->drawSequenceRight(kEntityVerges, "813DS"); + + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityVerges); + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case 3: + setCallback(4); + setup_function10(kCarGreenSleeping, kPosition_540, "TRA3004"); + break; + + case 4: + getEntities()->clearSequences(kEntityVerges); + break; + + case 5: + setCallback(6); + setup_function11(); + break; + + case 6: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(33, Verges, function33) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_draw("813US"); + break; + + case 2: + getEntities()->drawSequenceRight(kEntityVerges, "813UD"); + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityVerges); + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case 3: + getEntities()->clearSequences(kEntityVerges); + getData()->location = kLocationInsideCompartment; + getData()->entityPosition = kPosition_5799; + + setCallback(getProgress().field_3C ? 4 : 5); + setup_playSound(getProgress().field_3C ? "ABB3035A" : "ABB3035"); + break; + + case 4: + setCallback(5); + setup_playSound("ABB3035"); + break; + + case 5: + getSavePoints()->push(kEntityVerges, kEntityAbbot, kAction192054567); + + setCallback(6); + setup_function9("Tra3010"); + break; + + case 6: + setup_function34(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(34, Verges, function34) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInBaggageCarEntrance(kEntityPlayer)) { + setCallback(1); + setup_function13(false); + break; + } + +label_callback_1: + if (ENTITY_PARAM(0, 4)) { + setCallback(2); + setup_function31(); + break; + } + +label_callback_2: + if (ENTITY_PARAM(0, 3)) { + setCallback(3); + setup_function17(); + break; + } + +label_callback_3: + TIME_CHECK_CALLBACK_1(kTime1971000, params->param1, 4, setup_function9, "Tra3001"); + +label_callback_4: + TIME_CHECK_CALLBACK_1(kTime1998000, params->param2, 5, setup_function9, "Tra3010a"); + +label_callback_5: + TIME_CHECK_CALLBACK(kTime2016000, params->param3, 6, setup_function35); + +label_callback_6: + TIME_CHECK_CALLBACK_1(kTime2070000, params->param4, 7, setup_function9, "Tra3002"); + +label_callback_7: + TIME_CHECK_CALLBACK_1(kTime2142000, params->param5, 8, setup_function9, "Tra3003"); + +label_callback_8: + TIME_CHECK_CALLBACK_1(kTime2173500, params->param6, 9, setup_function30, "Tra3012"); + +label_callback_9: + TIME_CHECK_CALLBACK(kTime2218500, params->param7, 10, setup_function32); + break; + + case kActionOpenDoor: + setCallback(11); + setup_function13(savepoint.param.intValue < 106); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback_1; + + case 2: + goto label_callback_2; + + case 3: + goto label_callback_3; + + case 4: + goto label_callback_4; + + case 5: + goto label_callback_5; + + case 6: + goto label_callback_6; + + case 7: + goto label_callback_7; + + case 8: + goto label_callback_8; + + case 9: + goto label_callback_9; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(35, Verges, function35) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function12(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 2: + setCallback(3); + setup_function15(kEntityMertens, "Tra3011A"); + break; + + case 3: + getSavePoints()->push(kEntityVerges, kEntityCoudert, kAction188570113); + + setCallback(4); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 4: + setCallback(5); + setup_function15(kEntityMertens, "Tra3011"); + break; + + case 5: + getSavePoints()->push(kEntityVerges, kEntityMertens, kAction188635520); + + setCallback(6); + setup_function11(); + break; + + case 6: + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(36, Verges, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityVerges); + + getData()->entityPosition = kPosition_5000; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarBaggage; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObject104, kEntityVerges, kObjectLocationNone, kCursorNormal, kCursorHand); + getObjects()->update(kObject105, kEntityVerges, kObjectLocationNone, kCursorNormal, kCursorHand); + + ENTITY_PARAM(0, 3) = 0; + ENTITY_PARAM(0, 6) = 0; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(37, Verges, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInBaggageCarEntrance(kEntityPlayer)) { + setCallback(1); + setup_function13(false); + break; + } + +label_callback_1: + if (ENTITY_PARAM(0, 6)) { + if (ENTITY_PARAM(0, 3)) { + setCallback(2); + setup_function17(); + break; + } + +label_callback_2: + TIME_CHECK_CALLBACK_1(kTime2349000, params->param1, 3, setup_function9, "Tra1001"); + +label_callback_3: + TIME_CHECK_CALLBACK_1(kTime2378700, params->param2, 4, setup_function9, "Tra4001"); + +label_callback_4: + TIME_CHECK_CALLBACK_1(kTime2403000, params->param3, 5, setup_function9, "Tra1001A"); + +label_callback_5: + TIME_CHECK_CALLBACK_1(kTime2414700, params->param4, 6, setup_function9, "Tra4002"); + +label_callback_6: + TIME_CHECK_CALLBACK_1(kTime2484000, params->param5, 7, setup_function9, "Tra4003"); + +label_callback_7: + TIME_CHECK_CALLBACK_1(kTime2511000, params->param6, 8, setup_function9, "Tra4004"); + } + +label_callback_8: + TIME_CHECK_CALLBACK_1(kTime2538000, params->param7, 9, setup_function9, "Tra4005"); + + break; + + case kActionOpenDoor: + setCallback(10); + setup_function13(savepoint.param.intValue < 106); + break; + + case kActionDefault: + getData()->car = kCarBaggage; + getData()->entityPosition = kPosition_5000; + getData()->location = kLocationOutsideCompartment; + + getInventory()->setLocationAndProcess(kItem9, kObjectLocation1); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + goto label_callback_1; + + case 2: + goto label_callback_2; + + case 3: + goto label_callback_3; + + case 4: + goto label_callback_4; + + case 5: + goto label_callback_5; + + case 6: + goto label_callback_6; + + case 7: + goto label_callback_7; + + case 8: + goto label_callback_8; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(38, Verges, function38) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->update(kObject104, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + getObjects()->update(kObject105, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + getScenes()->loadSceneFromItemPosition(kItem9); + getEntities()->clearSequences(kEntityVerges); + + getData()->entityPosition = kPosition_6469; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->clearSequences(kEntityVerges); + setCallback(2); + setup_updateFromTime(1800); + break; + + case 2: + setCallback(3); + setup_function11(); + break; + + case 3: + setup_chapter4Handler(); + break; + } + break; + + case kAction125233040: + getData()->entityPosition = kPosition_5790; + + setCallback(1); + setup_updateEntity(kCarGreenSleeping, kPosition_540); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(39, Verges, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityVerges); + + getData()->entityPosition = kPosition_3650; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObject104, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + getObjects()->update(kObject105, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorHand); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(40, Verges, chapter5Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getEntities()->isInSalon(kEntityPlayer) && !getSound()->isBuffered(kEntityVerges)) + getSound()->playSound(kEntityVerges, "WAT5000"); + break; + + case kActionOpenDoor: + if (getSound()->isBuffered(kEntityVerges)) + getSound()->processEntry(kEntityVerges); + + if (getSound()->isBuffered("MUS050")) + getSound()->processEntry("MUS050"); + + getObjects()->update(kObject65, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventCathFreePassengers); + break; + + case kActionDefault: + getScenes()->loadSceneFromItemPosition(kItem9); + getObjects()->update(kObject65, kEntityVerges, kObjectLocation1, kCursorNormal, kCursorForward); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventCathFreePassengers); + getSavePoints()->pushAll(kEntityVerges, kActionProceedChapter5); + getScenes()->loadSceneFromPosition(kCarRedSleeping, 40); + setup_function41(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(41, Verges, function41) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getObjects()->updateLocation2(kObjectRestaurantCar, kObjectLocation3); + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_9460; + getData()->location = kLocationInsideCompartment; + + setCallback(1); + setup_function10(kCarRedSleeping, kPosition_2000, "Tra5001"); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getEntities()->drawSequenceLeft(kEntityVerges, "620E"); + // Fallback to next case + + case 2: + if (getSound()->isBuffered(kEntityVerges)) { + setCallback(2); + setup_updateFromTime(225); + } else { + setCallback(3); + setup_playSound("Con5001"); + } + break; + + case 3: + getSavePoints()->push(kEntityVerges, kEntityCoudert, kAction155991520); + + setCallback(4); + setup_updateEntity(kCarBaggageRear, kPosition_9460); + break; + + case 4: + setup_function42(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(42, Verges, function42) + if (savepoint.action == kActionDefault) + getEntities()->clearSequences(kEntityVerges); +} + + +////////////////////////////////////////////////////////////////////////// +// Private functions +////////////////////////////////////////////////////////////////////////// +void Verges::talk(const SavePoint &savepoint, const char *sound1, const char *sound2) { + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_function12(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarRedSleeping, kPosition_2000); + break; + + case 2: + setCallback(3); + setup_function15(kEntityCoudert, sound1); + break; + + case 3: + setCallback(4); + setup_updateEntity(kCarGreenSleeping, kPosition_2000); + break; + + case 4: + setCallback(5); + setup_function15(kEntityMertens, sound2); + break; + + case 5: + setup_function11(); + break; + + case 6: + CALLBACK_ACTION(); + break; + } + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/verges.h b/engines/lastexpress/entities/verges.h new file mode 100644 index 0000000000..3162d4e0ea --- /dev/null +++ b/engines/lastexpress/entities/verges.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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_VERGES_H +#define LASTEXPRESS_VERGES_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Verges : public Entity { +public: + Verges(LastExpressEngine *engine); + ~Verges() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Plays sound + * + * @param savepoint The savepoint + * - the sound filename + */ + DECLARE_FUNCTION_NOSETUP(playSound16) + + /** + * Process callback action when somebody is standing in the restaurant or salon. + */ + DECLARE_FUNCTION(callbackActionRestaurantOrSalon) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION_1(function9, const char *soundName) + DECLARE_FUNCTION_3(function10, CarIndex car, EntityPosition entityPosition, const char *soundName) + DECLARE_FUNCTION(function11) + DECLARE_FUNCTION(function12) + DECLARE_FUNCTION_1(function13, bool) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + DECLARE_FUNCTION_2(function15, EntityIndex entity, const char *soundName) + DECLARE_FUNCTION_3(function16, EntityIndex entityIndex, const char *soundName1, const char *soundName2) + DECLARE_FUNCTION(function17) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + DECLARE_FUNCTION_NOSETUP(talkHarem) + DECLARE_FUNCTION(talkPassengerList) + DECLARE_FUNCTION(talkGendarmes) + DECLARE_FUNCTION(function22) + DECLARE_FUNCTION(function23) + DECLARE_FUNCTION(policeGettingOffTrain) + DECLARE_FUNCTION(function25) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + DECLARE_FUNCTION_1(function30, const char *soundName) + DECLARE_FUNCTION(function31) + DECLARE_FUNCTION(function32) + DECLARE_FUNCTION(function33) + DECLARE_FUNCTION(function34) + DECLARE_FUNCTION(function35) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function38) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function41) + DECLARE_FUNCTION(function42) + +private: + void talk(const SavePoint &savepoint, const char* sound1, const char* sound2); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_VERGES_H diff --git a/engines/lastexpress/entities/vesna.cpp b/engines/lastexpress/entities/vesna.cpp new file mode 100644 index 0000000000..9e0ef09a08 --- /dev/null +++ b/engines/lastexpress/entities/vesna.cpp @@ -0,0 +1,1161 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/vesna.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/fight.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Vesna::Vesna(LastExpressEngine *engine) : Entity(engine, kEntityVesna) { + ADD_CALLBACK_FUNCTION(Vesna, reset); + ADD_CALLBACK_FUNCTION(Vesna, playSound); + ADD_CALLBACK_FUNCTION(Vesna, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Vesna, draw); + ADD_CALLBACK_FUNCTION(Vesna, updateEntity); + ADD_CALLBACK_FUNCTION(Vesna, updateFromTime); + ADD_CALLBACK_FUNCTION(Vesna, updateEntity2); + ADD_CALLBACK_FUNCTION(Vesna, callbackActionRestaurantOrSalon); + ADD_CALLBACK_FUNCTION(Vesna, callbackActionOnDirection); + ADD_CALLBACK_FUNCTION(Vesna, savegame); + ADD_CALLBACK_FUNCTION(Vesna, function11); + ADD_CALLBACK_FUNCTION(Vesna, chapter1); + ADD_CALLBACK_FUNCTION(Vesna, chapter1Handler); + ADD_CALLBACK_FUNCTION(Vesna, function14); + ADD_CALLBACK_FUNCTION(Vesna, function15); + ADD_CALLBACK_FUNCTION(Vesna, chapter2); + ADD_CALLBACK_FUNCTION(Vesna, chapter2Handler); + ADD_CALLBACK_FUNCTION(Vesna, function18); + ADD_CALLBACK_FUNCTION(Vesna, chapter3); + ADD_CALLBACK_FUNCTION(Vesna, chapter3Handler); + ADD_CALLBACK_FUNCTION(Vesna, function21); + ADD_CALLBACK_FUNCTION(Vesna, function22); + ADD_CALLBACK_FUNCTION(Vesna, function23); + ADD_CALLBACK_FUNCTION(Vesna, chapter4); + ADD_CALLBACK_FUNCTION(Vesna, function25); + ADD_CALLBACK_FUNCTION(Vesna, function26); + ADD_CALLBACK_FUNCTION(Vesna, function27); + ADD_CALLBACK_FUNCTION(Vesna, chapter5); + ADD_CALLBACK_FUNCTION(Vesna, chapter5Handler); + ADD_CALLBACK_FUNCTION(Vesna, function30); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Vesna, reset) + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(2, Vesna, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(3, Vesna, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(4, Vesna, draw) + Entity::draw(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(5, Vesna, updateEntity, CarIndex, EntityPosition) + if (savepoint.action == kActionExcuseMeCath) { + getSound()->playSound(kEntityPlayer, rnd(2) ? "CAT10150" : "CAT1015A"); + + return; + } + + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_I(6, Vesna, updateFromTime, uint32) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(7, Vesna, updateEntity2, CarIndex, EntityPosition) + switch (savepoint.action) { + default: + break; + + case kActionNone: + params->param3 = 0; + + if (getEntities()->isDistanceBetweenEntities(kEntityVesna, kEntityMilos, 500) + || (((getData()->direction == kDirectionUp && (getData()->car > getEntityData(kEntityMilos)->car)) || (getData()->car == getEntityData(kEntityMilos)->car && getData()->entityPosition > getEntityData(kEntityMilos)->entityPosition))) + || (((getData()->direction == kDirectionDown && (getData()->car < getEntityData(kEntityMilos)->car)) || (getData()->car == getEntityData(kEntityMilos)->car && getData()->entityPosition < getEntityData(kEntityMilos)->entityPosition)))) { + getData()->field_49B = 0; + params->param3 = 1; + } + + if (!params->param3) + getEntities()->updateEntity(kEntityVesna, (CarIndex)params->param1, (EntityPosition)params->param2); + break; + + case kActionDefault: + getEntities()->updateEntity(kEntityVesna, (CarIndex)params->param1, (EntityPosition)params->param2); + break; + + case kAction123668192: + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Vesna, callbackActionRestaurantOrSalon) + Entity::callbackActionRestaurantOrSalon(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Vesna, callbackActionOnDirection) + Entity::callbackActionOnDirection(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(10, Vesna, savegame, SavegameType, uint32) + Entity::savegame(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Vesna, function11) + // Expose parameters as IIIS and ignore the default exposed parameters + EntityData::EntityParametersIIIS *parameters = (EntityData::EntityParametersIIIS*)_data->getCurrentParameters(); + + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (parameters->param3) { + UPDATE_PARAM(parameters->param7, getState()->timeTicks, 75); + + parameters->param2 = 1; + parameters->param3 = 0; + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation1, kCursorNormal, kCursorNormal); + } + + parameters->param7 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + if (parameters->param3) { + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation3, kCursorNormal, kCursorNormal); + + setCallback(4); + setup_playSound(getSound()->wrongDoorCath()); + break; + } + + parameters->param1++; + switch (parameters->param1) { + default: + strcpy((char *)¶meters->seq, "VES1015C"); + parameters->param1 = 0; + break; + + case 1: + strcpy((char *)¶meters->seq, "VES1015A"); + break; + + case 2: + strcpy((char *)¶meters->seq, "VES1015B"); + break; + } + + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation3, kCursorNormal, kCursorNormal); + + setCallback(savepoint.action == kActionKnock ? 2 : 1); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + break; + + case kActionDefault: + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + + case kActionDrawScene: + if (parameters->param2 || parameters->param3) { + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation1, kCursorHandKnock, kCursorHand); + + parameters->param2 = 0; + parameters->param3 = 0; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + setCallback(3); + setup_playSound((char *)¶meters->seq); + break; + + case 3: + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation3, kCursorTalk, kCursorNormal); + parameters->param3 = 1; + break; + + case 4: + parameters->param2 = 1; + parameters->param3 = 0; + break; + } + break; + + case kAction55996766: + case kAction101687594: + CALLBACK_ACTION(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Vesna, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getSavePoints()->addData(kEntityVesna, kAction124190740, 0); + + getData()->entityPosition = kPosition_4689; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Vesna, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + getData()->entityPosition = getEntityData(kEntityMilos)->entityPosition; + getData()->location = getEntityData(kEntityMilos)->location; + break; + + case kActionCallback: + if (getCallback() == 1) { + getEntities()->clearSequences(kEntityVesna); + setup_function14(); + } + break; + + case kAction204832737: + setCallback(1); + setup_updateEntity2(kCarRedSleeping, kPosition_3050); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Vesna, function14) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + break; + + case kAction190412928: + setCallback(1); + setup_function11(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Vesna, function15) + if (savepoint.action == kActionDefault) { + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + + getEntities()->clearSequences(kEntityVesna); + getObjects()->update(kObjectCompartmentG, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Vesna, chapter2) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter2Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityVesna); + + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Vesna, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kAction135024800: + setCallback(2); + setup_function18(); + break; + + case kAction137165825: + setCallback(1); + setup_function11(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Vesna, function18) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("610Bg", kObjectCompartmentG); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + if (getData()->entityPosition < kPosition_2087) + getData()->entityPosition = kPosition_2088; + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_draw("808US"); + break; + + case 4: + getEntities()->drawSequenceRight(kEntityVesna, "808UD"); + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityVesna); + + setCallback(5); + setup_callbackActionOnDirection(); + break; + + case 5: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityVesna); + + setCallback(6); + setup_updateFromTime(4500); + break; + + case 6: + setCallback(7); + setup_callbackActionRestaurantOrSalon(); + break; + + case 7: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(8); + setup_draw("808DD"); + break; + + case 8: + getEntities()->drawSequenceRight(kEntityVesna, "808DS"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityVesna); + + setCallback(9); + setup_callbackActionOnDirection(); + break; + + case 9: + setCallback(10); + setup_updateEntity(kCarRedSleeping, kPosition_3050); + break; + + case 10: + setCallback(11); + setup_enterExitCompartment("610Ag", kObjectCompartmentG); + break; + + case 11: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityVesna); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Vesna, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityVesna); + + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Vesna, chapter3Handler) + EntityData::EntityParametersIIIS *parameters = (EntityData::EntityParametersIIIS*)_data->getCurrentParameters(); + + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getProgress().field_54 && parameters->param7 != kTimeInvalid) { + if (getState()->time > kTime2250000) { + parameters->param7 = kTimeInvalid; + setup_function22(); + break; + } + + if (!getEntities()->isPlayerInCar(kCarRedSleeping) || !parameters->param7) + parameters->param7 = getState()->time; + + if (parameters->param7 < getState()->time) { + parameters->param7 = kTimeInvalid; + setup_function22(); + break; + } + } + + if (parameters->param2) { + UPDATE_PARAM(parameters->param8, getState()->timeTicks, 75); + + parameters->param1 = 1; + parameters->param2 = 0; + + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation1, kCursorNormal, kCursorNormal); + } + + parameters->param8 = 0; + break; + + case kActionKnock: + case kActionOpenDoor: + if (parameters->param2) { + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation3, kCursorNormal, kCursorNormal); + + setCallback(4); + setup_playSound(getSound()->wrongDoorCath()); + break; + } + + ++parameters->param3; + + switch (parameters->param3) { + default: + break; + + case 1: + strcpy((char *)¶meters->seq, "VES1015A"); + break; + + case 2: + strcpy((char *)¶meters->seq, "VES1015B"); + break; + + case 3: + strcpy((char *)¶meters->seq, "VES1015C"); + parameters->param3 = 0; + break; + } + + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation3, kCursorNormal, kCursorNormal); + + setCallback(savepoint.action == kActionKnock ? 2 : 1); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + break; + + case kActionDefault: + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + getEntities()->clearSequences(kEntityVesna); + break; + + case kActionDrawScene: + if (parameters->param1 || parameters->param2) { + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation1, kCursorHandKnock, kCursorHand); + parameters->param1 = 0; + parameters->param2 = 0; + } + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + setCallback(3); + setup_playSound((char *)¶meters->seq); + break; + + case 3: + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation3, kCursorTalk, kCursorNormal); + parameters->param2 = 1; + break; + + case 4: + parameters->param1 = 1; + parameters->param2 = 0; + break; + } + break; + + case kAction137165825: + setCallback(5); + setup_function11(); + break; + + case kAction155913424: + setCallback(6); + setup_function21(); + break; + + case kAction203663744: + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Vesna, function21) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_enterExitCompartment("610Bg", kObjectCompartmentG); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + if (getData()->entityPosition < kPosition_2087) + getData()->entityPosition = kPosition_2088; + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_draw("808US"); + break; + + case 4: + getEntities()->drawSequenceRight(kEntityVesna, "808UD"); + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityVesna); + + setCallback(5); + setup_callbackActionOnDirection(); + break; + + case 5: + getEntities()->clearSequences(kEntityVesna); + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationInsideCompartment; + + setCallback(6); + setup_updateFromTime(4500); + break; + + case 6: + setCallback(7); + setup_callbackActionRestaurantOrSalon(); + break; + + case 7: + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(8); + setup_draw("808DD"); + break; + + case 8: + getEntities()->drawSequenceRight(kEntityVesna, "808DS"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityVesna); + + setCallback(9); + setup_callbackActionOnDirection(); + break; + + case 9: + setCallback(10); + setup_updateEntity(kCarRedSleeping, kPosition_3050); + break; + + case 10: + setCallback(11); + setup_enterExitCompartment("610Ag", kObjectCompartmentG); /* BUG the original engine passes 3050 here */ + break; + + case 11: + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityVesna); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(22, Vesna, function22) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getSavePoints()->push(kEntityVesna, kEntityMilos, kAction259125998); + + setCallback(1); + setup_enterExitCompartment("610Bg", kObjectCompartmentG); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + if (getData()->entityPosition < kPosition_2087) + getData()->entityPosition = kPosition_2088; + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_draw("808US"); + break; + + case 4: + getEntities()->drawSequenceRight(kEntityVesna, "808UD"); + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityVesna); + + setCallback(5); + setup_callbackActionOnDirection(); + break; + + case 5: + getEntities()->clearSequences(kEntityVesna); + getData()->car = kCarBaggage; + getSavePoints()->push(kEntityVesna, kEntityAnna, kAction235856512); + break; + + case 6: + getData()->car = kCarRestaurant; + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(7); + setup_draw("808DD"); + break; + + case 7: + getEntities()->drawSequenceRight(kEntityVesna, "808DS"); + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityVesna); + + setCallback(8); + setup_callbackActionOnDirection(); + break; + + case 8: + setCallback(9); + setup_updateEntity(kCarRedSleeping, kPosition_3050); + break; + + case 9: + setCallback(10); + setup_enterExitCompartment("610Ag", kObjectCompartmentG); /* BUG the original engine passes 3050 here */ + break; + + case 10: + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityVesna); + + setup_function23(); + break; + } + break; + + case kAction189299008: + setCallback(6); + setup_callbackActionRestaurantOrSalon(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(23, Vesna, function23) + switch (savepoint.action) { + default: + break; + + case kActionKnock: + case kActionOpenDoor: + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation3, kCursorNormal, kCursorNormal); + setCallback(savepoint.action == kActionKnock ? 1 : 2); + setup_playSound(savepoint.action == kActionKnock ? "LIB012" : "LIB013"); + break; + + case kActionDefault: + getData()->car = kCarRedSleeping; + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + setCallback(3); + setup_playSound("VES1015A"); + break; + + case 3: + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + } + break; + + case kAction203663744: + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(24, Vesna, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setCallback(1); + setup_function11(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityVesna); + + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->inventoryItem = kItemNone; + + getObjects()->update(kObjectCompartmentG, kEntityVesna, kObjectLocation3, kCursorHandKnock, kCursorHand); + break; + + case kActionCallback: + if (getCallback() == 1) + setup_function25(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(25, Vesna, function25) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (getState()->time > kTime2428200 && !params->param1) { + params->param1 = 1; + setup_function26(); + } + break; + + case kActionDefault: + getSavePoints()->push(kEntityVesna, kEntityMilos, kAction135600432); + + setCallback(1); + setup_enterExitCompartment("610BG", kObjectCompartmentG); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->location = kLocationOutsideCompartment; + if (getData()->entityPosition < kPosition_2087) + getData()->entityPosition = kPosition_2088; + + setCallback(2); + setup_updateEntity(kCarRestaurant, kPosition_850); + break; + + case 2: + setCallback(3); + setup_callbackActionRestaurantOrSalon(); + break; + + case 3: + getData()->entityPosition = kPosition_1540; + getData()->location = kLocationOutsideCompartment; + + setCallback(4); + setup_draw("808US"); + break; + + case 4: + getEntities()->drawSequenceRight(kEntityVesna, "808UD"); + if (getEntities()->isInSalon(kEntityPlayer)) + getEntities()->updateFrame(kEntityVesna); + + setCallback(5); + setup_callbackActionOnDirection(); + break; + + case 5: + getEntities()->clearSequences(kEntityVesna); + getData()->entityPosition = kPosition_5900; + getData()->location = kLocationInsideCompartment; + + // Original game calls clearSequences a second time + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(26, Vesna, function26) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + setCallback(1); + setup_callbackActionRestaurantOrSalon(); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->car = kCarRestaurant; + getData()->entityPosition = kPosition_5800; + getData()->location = kLocationOutsideCompartment; + + setCallback(2); + setup_draw("808DD"); + break; + + case 2: + getEntities()->drawSequenceRight(kEntityVesna, "808DS"); + + if (getEntities()->isInRestaurant(kEntityPlayer)) + getEntities()->updateFrame(kEntityVesna); + + setCallback(3); + setup_callbackActionOnDirection(); + break; + + case 3: + setCallback(4); + setup_updateEntity(kCarRedSleeping, kPosition_3050); + break; + + case 4: + setCallback(5); + setup_enterExitCompartment("610AG", kObjectCompartmentG); + break; + + case 5: + setup_function27(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(27, Vesna, function27) + if (savepoint.action == kActionDefault) { + getEntities()->clearSequences(kEntityVesna); + getObjects()->update(kObjectCompartmentG, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRedSleeping; + getData()->inventoryItem = kItemNone; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(28, Vesna, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityVesna); + + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(29, Vesna, chapter5Handler) + switch (savepoint.action) { + default: + break; + + case kActionOpenDoor: + setCallback(1); + + getData()->currentCall++; + setup_savegame(kSavegameTypeEvent, kEventCathVesnaRestaurantKilled); + break; + + case kActionDefault: + getObjects()->update(kObject64, kEntityVesna, kObjectLocationNone, kCursorNormal, kCursorForward); + break; + + case kActionCallback: + if (getCallback() == 1) { + getAction()->playAnimation(kEventCathVesnaRestaurantKilled); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneNone, true); + } + break; + + case kAction134427424: + getObjects()->update(kObject64, kEntityPlayer, kObjectLocationNone, kCursorNormal, kCursorForward); + setup_function30(); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(30, Vesna, function30) + switch (savepoint.action) { + default: + break; + + case kActionNone: + if (!params->param1) { + UPDATE_PARAM_PROC(params->param3, getState()->timeTicks, 120) + getSound()->playSound(kEntityVesna, "Ves50001", SoundManager::kFlagDefault); + params->param1 = 1; + UPDATE_PARAM_PROC_END + } + + UPDATE_PARAM(params->param4, getState()->timeTicks, 180); + + setCallback(1); + setup_savegame(kSavegameTypeEvent, kEventCathVesnaTrainTopKilled); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + case 2: + getAction()->playAnimation(kEventCathVesnaTrainTopKilled); + getLogic()->gameOver(kSavegameTypeIndex, 1, kSceneNone, true); + break; + + case 3: + getAction()->playAnimation(kEventCathVesnaTrainTopFight); + + setCallback(4); + setup_savegame(kSavegameTypeTime, kTimeNone); + break; + + case 4: + params->param2 = getFight()->setup(kFightVesna); + + if (params->param2) { + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneNone, params->param2 != Fight::kFightEndExit); + } else { + getSound()->playSound(kEntityPlayer, "TUNNEL"); + + getState()->time += 1800; + + setCallback(5); + setup_savegame(kSavegameTypeEvent, kEventCathVesnaTrainTopWin); + } + break; + + case 5: + getAction()->playAnimation(kEventCathVesnaTrainTopWin); + getScenes()->loadSceneFromPosition(kCarRestaurant, 11); + + setup_nullfunction(); + break; + } + break; + + case kAction167992577: + setCallback(3); + setup_savegame(kSavegameTypeEvent, kEventCathVesnaTrainTopFight); + break; + + case kAction202884544: + if (params->param1) { + setCallback(2); + setup_savegame(kSavegameTypeEvent, kEventCathVesnaTrainTopKilled); + } else { + getSound()->playSound(kEntityVesna, "Ves5001", SoundManager::kFlagDefault); + params->param1 = 1; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_NULL_FUNCTION(31, Vesna) + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/vesna.h b/engines/lastexpress/entities/vesna.h new file mode 100644 index 0000000000..4a0a8550b2 --- /dev/null +++ b/engines/lastexpress/entities/vesna.h @@ -0,0 +1,176 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_VESNA_H +#define LASTEXPRESS_VESNA_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Vesna : public Entity { +public: + Vesna(LastExpressEngine *engine); + ~Vesna() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + + /** + * Draws the entity + * + * @param sequence The sequence to draw + */ + DECLARE_FUNCTION_1(draw, const char* sequence) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + /** + * Updates parameter 2 using time value + * + * @param time The time to add + */ + DECLARE_FUNCTION_1(updateFromTime, uint32 time) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity2, CarIndex car, EntityPosition entityPosition) + + /** + * Process callback action when somebody is standing in the restaurant or salon. + */ + DECLARE_FUNCTION(callbackActionRestaurantOrSalon) + + /** + * Process callback action when the entity direction is not kDirectionRight + */ + DECLARE_FUNCTION(callbackActionOnDirection) + + /** + * Saves the game + * + * @param savegameType The type of the savegame + * @param param The param for the savegame (EventIndex or TimeValue) + */ + DECLARE_FUNCTION_2(savegame, SavegameType savegameType, uint32 param) + + DECLARE_FUNCTION(function11) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function14) + DECLARE_FUNCTION(function15) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + DECLARE_FUNCTION(function18) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + DECLARE_FUNCTION(function21) + DECLARE_FUNCTION(function22) + DECLARE_FUNCTION(function23) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + DECLARE_FUNCTION(function25) + DECLARE_FUNCTION(function26) + DECLARE_FUNCTION(function27) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function30) + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_VESNA_H diff --git a/engines/lastexpress/entities/yasmin.cpp b/engines/lastexpress/entities/yasmin.cpp new file mode 100644 index 0000000000..fe7c7fb2bf --- /dev/null +++ b/engines/lastexpress/entities/yasmin.cpp @@ -0,0 +1,490 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/entities/yasmin.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +namespace LastExpress { + +Yasmin::Yasmin(LastExpressEngine *engine) : Entity(engine, kEntityYasmin) { + ADD_CALLBACK_FUNCTION(Yasmin, reset); + ADD_CALLBACK_FUNCTION(Yasmin, enterExitCompartment); + ADD_CALLBACK_FUNCTION(Yasmin, playSound); + ADD_CALLBACK_FUNCTION(Yasmin, updateFromTime); + ADD_CALLBACK_FUNCTION(Yasmin, updateEntity); + ADD_CALLBACK_FUNCTION(Yasmin, function6); + ADD_CALLBACK_FUNCTION(Yasmin, function7); + ADD_CALLBACK_FUNCTION(Yasmin, chapter1); + ADD_CALLBACK_FUNCTION(Yasmin, chapter1Handler); + ADD_CALLBACK_FUNCTION(Yasmin, function10); + ADD_CALLBACK_FUNCTION(Yasmin, chapter2); + ADD_CALLBACK_FUNCTION(Yasmin, chapter2Handler); + ADD_CALLBACK_FUNCTION(Yasmin, chapter3); + ADD_CALLBACK_FUNCTION(Yasmin, chapter3Handler); + ADD_CALLBACK_FUNCTION(Yasmin, chapter4); + ADD_CALLBACK_FUNCTION(Yasmin, chapter4Handler); + ADD_CALLBACK_FUNCTION(Yasmin, function17); + ADD_CALLBACK_FUNCTION(Yasmin, chapter5); + ADD_CALLBACK_FUNCTION(Yasmin, chapter5Handler); + ADD_CALLBACK_FUNCTION(Yasmin, function20); + ADD_CALLBACK_FUNCTION(Yasmin, function21); + ADD_NULL_FUNCTION(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(1, Yasmin, reset) + switch (savepoint.action) { + default: + break; + + case kActionExcuseMeCath: + getSound()->excuseMeCath(); + break; + + case kActionExcuseMe: + getSound()->excuseMe(kEntityYasmin); + break; + } + + // Process default actions + Entity::reset(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_SI(2, Yasmin, enterExitCompartment, ObjectIndex) + Entity::enterExitCompartment(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_S(3, Yasmin, playSound) + Entity::playSound(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_NOSETUP(4, Yasmin, updateFromTime) + Entity::updateFromTime(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION_II(5, Yasmin, updateEntity, CarIndex, EntityPosition) + Entity::updateEntity(savepoint, true); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(6, Yasmin, function6) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_4840; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_enterExitCompartment("615Be", kObjectCompartment5); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_3050); + break; + + case 2: + setCallback(3); + setup_enterExitCompartment("615Ag", kObjectCompartment7); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityYasmin); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(7, Yasmin, function7) + switch (savepoint.action) { + default: + break; + + case kActionDefault: + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationOutsideCompartment; + + setCallback(1); + setup_enterExitCompartment("615Bg", kObjectCompartment7); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + setCallback(2); + setup_updateEntity(kCarGreenSleeping, kPosition_4840); + break; + + case 2: + setCallback(3); + setup_enterExitCompartment("615Ae", kObjectCompartment5); + break; + + case 3: + getData()->location = kLocationInsideCompartment; + getEntities()->clearSequences(kEntityYasmin); + + CALLBACK_ACTION(); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(8, Yasmin, chapter1) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CHAPTER1(setup_chapter1Handler); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_4840; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(9, Yasmin, chapter1Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK(kTime1093500, params->param1, 1, setup_function6); + TIME_CHECK_CALLBACK(kTime1161000, params->param2, 3, setup_function7); + TIME_CHECK_PLAYSOUND_UPDATEPOSITION(kTime1162800, params->param3, 4, "Har1102", kPosition_4070); + TIME_CHECK_PLAYSOUND(kTime1165500, params->param4, 5, "Har1104"); + TIME_CHECK_PLAYSOUND(kTime1174500, params->param5, 6, "Har1106"); + TIME_CHECK_CALLBACK(kTime1183500, params->param6, 7, setup_function6); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->entityPosition = kPosition_2740; + setCallback(2); + setup_playSound("Har1102"); + break; + + case 2: + TIME_CHECK_CALLBACK(kTime1161000, params->param2, 3, setup_function7); + // Fallback to case 3 + + case 3: + TIME_CHECK_PLAYSOUND_UPDATEPOSITION(kTime1162800, params->param3, 4, "Har1102", kPosition_4070); + // Fallback to case 4 + + case 4: + TIME_CHECK_PLAYSOUND(kTime1165500, params->param4, 5, "Har1104"); + // Fallback to case 5 + + case 5: + TIME_CHECK_PLAYSOUND(kTime1174500, params->param5, 6, "Har1106"); + // Fallback to case 6 + + case 6: + TIME_CHECK_CALLBACK(kTime1183500, params->param6, 7, setup_function6); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(10, Yasmin, function10) + if (savepoint.action == kActionDefault) { + getObjects()->update(kObjectCompartment7, kEntityPlayer, kObjectLocation3, kCursorHandKnock, kCursorHand); + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + + getEntities()->clearSequences(kEntityYasmin); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(11, Yasmin, chapter2) + if (savepoint.action == kActionDefault) { + getEntities()->clearSequences(kEntityYasmin); + + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + + setup_chapter2Handler(); + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(12, Yasmin, chapter2Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK(kTime1759500, params->param1, 1, setup_function7); + + if (getState()->time > kTime1800000 && !params->param2) { + params->param2 = 1; + getData()->entityPosition = kPosition_4070; + + getSavePoints()->push(kEntityYasmin, kEntityTrain, kAction191070912, kPosition_4070); + } + break; + + case kActionCallback: + + if (getCallback() != 1) + break; + + if (getState()->time > kTime1800000 && !params->param2) { + params->param2 = 1; + getData()->entityPosition = kPosition_4070; + + getSavePoints()->push(kEntityYasmin, kEntityTrain, kAction191070912, kPosition_4070); + } + + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(13, Yasmin, chapter3) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter3Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityYasmin); + + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(14, Yasmin, chapter3Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK(kTime2062800, params->param1, 1, setup_function6); + TIME_CHECK_CALLBACK(kTime2106000, params->param2, 2, setup_function7); + TIME_CHECK_CALLBACK(kTime2160000, params->param3, 3, setup_function6); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + TIME_CHECK_CALLBACK(kTime2106000, params->param2, 2, setup_function7); + // Fallback to case 2 + + case 2: + TIME_CHECK_CALLBACK(kTime2160000, params->param3, 3, setup_function6); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(15, Yasmin, chapter4) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter4Handler(); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_3050; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarGreenSleeping; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(16, Yasmin, chapter4Handler) + switch (savepoint.action) { + default: + break; + + case kActionNone: + TIME_CHECK_CALLBACK(kTime2457000, params->param1, 1, setup_function7); + TIME_CHECK_CALLBACK(kTime2479500, params->param2, 3, setup_function6); + break; + + case kActionCallback: + switch (getCallback()) { + default: + break; + + case 1: + getData()->entityPosition = kPosition_4070; + setCallback(2); + setup_playSound("Har1110"); + break; + + case 2: + TIME_CHECK_CALLBACK(kTime2479500, params->param2, 3, setup_function6); + break; + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(17, Yasmin, function17) + // Same as existing function 10 ? + function10(savepoint); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(18, Yasmin, chapter5) + switch (savepoint.action) { + default: + break; + + case kActionNone: + setup_chapter5Handler(); + break; + + case kActionDefault: + getEntities()->clearSequences(kEntityYasmin); + + getData()->entityPosition = kPosition_3969; + getData()->location = kLocationInsideCompartment; + getData()->car = kCarRestaurant; + getData()->clothes = kClothesDefault; + getData()->inventoryItem = kItemNone; + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(19, Yasmin, chapter5Handler) + if (savepoint.action == kActionProceedChapter5) + setup_function20(); +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(20, Yasmin, function20) + switch (savepoint.action) { + default: + break; + + case kActionNone: + UPDATE_PARAM(params->param1, getState()->time, 2700); + setup_function21(); + break; + + case kActionDefault: + getData()->entityPosition = kPosition_2500; + getData()->location = kLocationOutsideCompartment; + getData()->car = kCarGreenSleeping; + break; + + case kActionDrawScene: + if (getEntities()->isInsideTrainCar(kEntityPlayer, kCarGreenSleeping)) { + setup_function21(); + } + break; + } +} + +////////////////////////////////////////////////////////////////////////// +IMPLEMENT_FUNCTION(21, Yasmin, function21) + switch (savepoint.action) { + default: + break; + + case kActionNone: + case kActionDefault: + if (getEntities()->updateEntity(kEntityYasmin, (CarIndex)params->param1, (EntityPosition)params->param2)) + CALLBACK_ACTION(); + break; + + case kActionExcuseMeCath: + getSound()->excuseMeCath(); + break; + + case kActionExcuseMe: + getSound()->excuseMe(kEntityYasmin); + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/entities/yasmin.h b/engines/lastexpress/entities/yasmin.h new file mode 100644 index 0000000000..125b11d566 --- /dev/null +++ b/engines/lastexpress/entities/yasmin.h @@ -0,0 +1,142 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_YASMIN_H +#define LASTEXPRESS_YASMIN_H + +#include "lastexpress/entities/entity.h" +#include "lastexpress/entities/entity_intern.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Yasmin : public Entity { +public: + Yasmin(LastExpressEngine *engine); + ~Yasmin() {}; + + /** + * Resets the entity + */ + DECLARE_FUNCTION(reset) + + /** + * Handles entering/exiting a compartment. + * + * @param sequence The sequence to draw + * @param compartment The compartment + */ + DECLARE_FUNCTION_2(enterExitCompartment, const char* sequence, ObjectIndex compartment) + + /** + * Plays sound + * + * @param filename The sound filename + */ + DECLARE_FUNCTION_1(playSound, const char* filename) + + /** + * Updates parameter 2 using time value + * + * @param savepoint The savepoint + * - Time to add + */ + DECLARE_FUNCTION_NOSETUP(updateFromTime) + + /** + * Updates the entity + * + * @param car The car + * @param entityPosition The entity position + */ + DECLARE_FUNCTION_2(updateEntity, CarIndex car, EntityPosition entityPosition) + + DECLARE_FUNCTION(function6) + DECLARE_FUNCTION(function7) + + /** + * Setup Chapter 1 + */ + DECLARE_FUNCTION(chapter1) + + /** + * Handle Chapter 1 events + */ + DECLARE_FUNCTION(chapter1Handler) + + DECLARE_FUNCTION(function10) + + /** + * Setup Chapter 2 + */ + DECLARE_FUNCTION(chapter2) + + /** + * Handle Chapter 2 events + */ + DECLARE_FUNCTION(chapter2Handler) + + /** + * Setup Chapter 3 + */ + DECLARE_FUNCTION(chapter3) + + /** + * Handle Chapter 3 events + */ + DECLARE_FUNCTION(chapter3Handler) + + /** + * Setup Chapter 4 + */ + DECLARE_FUNCTION(chapter4) + + /** + * Handle Chapter 4 events + */ + DECLARE_FUNCTION(chapter4Handler) + + DECLARE_FUNCTION(function17) + + /** + * Setup Chapter 5 + */ + DECLARE_FUNCTION(chapter5) + + /** + * Handle Chapter 5 events + */ + DECLARE_FUNCTION(chapter5Handler) + + DECLARE_FUNCTION(function20) + DECLARE_FUNCTION(function21) + + DECLARE_NULL_FUNCTION() +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_YASMIN_H diff --git a/engines/lastexpress/eventhandler.h b/engines/lastexpress/eventhandler.h new file mode 100644 index 0000000000..8536bba7e7 --- /dev/null +++ b/engines/lastexpress/eventhandler.h @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_EVENTHANDLER_H +#define LASTEXPRESS_EVENTHANDLER_H + +#include "common/EventRecorder.h" +#include "common/func.h" + +namespace LastExpress { + +#define SET_EVENT_HANDLERS(class, inst) \ + _engine->setEventHandlers(new EVENT_HANDLER(class, eventMouse, inst), new EVENT_HANDLER(class, eventTick, inst)); + +#define EVENT_HANDLER(class, name, inst) \ + Common::Functor1Mem<const Common::Event&, void, class>(inst, &class::name) + +class EventHandler { +public: + virtual ~EventHandler() {} + + // Function pointer for event handler + typedef Common::Functor1<const Common::Event&, void> EventFunction; + + virtual void eventMouse(const Common::Event &ev) {}; // Event type 1 + virtual void eventTick(const Common::Event &ev) {}; // Event type 3 +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_EVENTHANDLER_H diff --git a/engines/lastexpress/game/action.cpp b/engines/lastexpress/game/action.cpp new file mode 100644 index 0000000000..b2c7fdef1f --- /dev/null +++ b/engines/lastexpress/game/action.cpp @@ -0,0 +1,1968 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/action.h" + +#include "lastexpress/data/animation.h" +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/snd.h" +#include "lastexpress/data/scene.h" + +#include "lastexpress/entities/abbot.h" +#include "lastexpress/entities/anna.h" +#include "lastexpress/entities/entity.h" + +#include "lastexpress/game/beetle.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savegame.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +static const int _animationListSize = 273; + +// List of animations +static const struct { + const char *filename; + uint16 time; +} _animationList[_animationListSize] = { + {"", 0}, + {"1002", 255}, + {"1002D", 255}, + {"1003", 0}, + {"1005", 195}, + {"1006", 750}, // 5 + {"1006A", 750}, + {"1008", 765}, + {"1008N", 765}, + {"1008A", 750}, + {"1008AN", 750}, // 10 + {"1009", 0}, + {"1011", 1005}, + {"1011A", 780}, + {"1012", 300}, + {"1013", 285}, + {"1017", 870}, // 15 + {"1017A", 0}, // Not in the data files? + {"1019", 120}, + {"1019D", 120}, + {"1020", 120}, // 20 + {"1022", 525}, + {"1022A", 180}, + {"1022AD", 210}, + {"1022B", 210}, + {"1022C", 210}, // 25 + {"1023", 135}, + {"1025", 945}, + {"1028", 300}, + {"1030", 390}, + {"1031", 375}, // 30 + {"1032", 1050}, + {"1033", 945}, + {"1034", 495}, + {"1035", 1230}, + {"1037", 1425}, // 35 + {"1038", 195}, + {"1038A", 405}, + {"1039", 600}, + {"1040", 945}, + {"1041", 510}, // 40 + {"1042", 540}, + {"1043", 855}, + {"1044", 645}, + {"1046", 0}, + {"1047", 0}, // 45 + {"1047A", 0}, + {"1059", 1005}, + {"1060", 255}, + {"1063", 0}, + {"1101", 255}, // 50 + {"1102", 1320}, + {"1103", 210}, + {"1104", 120}, + {"1105", 1350}, + {"1106", 315}, // 55 + {"1106A", 315}, + {"1106D", 315}, + {"1107", 1}, + {"1107A", 660}, + {"1108", 300}, // 60 + {"1109", 1305}, + {"1110", 300}, + {"1112", 0}, + {"1115", 0}, + {"1115A", 0}, // 65 + {"1115B", 0}, + {"1115C", 0}, + {"1115D", 0}, + {"1115E", 0}, + {"1115F", 0}, // 70 + {"1115G", 0}, + {"1115H", 0}, + {"1116", 0}, + {"1117", 0}, + {"1118", 105}, // 75 + {"1202", 510}, + {"1202A", 510}, + {"1203", 720}, + {"1204", 120}, + {"1205", 465}, // 80 + {"1206", 690}, + {"1206A", 450}, + {"1208", 465}, + {"1210", 1020}, + {"1211", 600}, // 85 + {"1212", 435}, + {"1213", 525}, + {"1213A", 150}, + {"1215", 390}, + {"1216", 0}, // 90 + {"1219", 240}, + {"1222", 1095}, + {"1223", 0}, + {"1224", 720}, + {"1225", 1005}, // 95 + {"1227", 840}, + {"1227A", 840}, + {"1303", 450}, + {"1303N", 450}, + {"1304", 450}, // 100 + {"1304N", 450}, + {"1305", 630}, + {"1309", 0}, + {"1311", 1710}, + {"1312", 240}, // 105 + {"1312D", 240}, + {"1313", 930}, + {"1315", 1035}, + {"1315A", 1035}, + {"1401", 540}, // 110 + {"1402", 150}, + {"1402B", 150}, + {"1403", 90}, + {"1404", 885}, + {"1404A", 0}, // 115 + {"1405", 135}, + {"1406", 1665}, + {"1501", 285}, + {"1501A", 285}, + {"1502", 165}, // 120 + {"1502A", 165}, + {"1502D", 165}, + {"1503", 0}, + {"1504", 0}, + {"1505", 0}, // 125 + {"1505A", 0}, + {"1506", 300}, + {"1506A", 180}, + {"1508", 0}, + {"1509", 450}, // 130 + {"1509S", 450}, + {"1509A", 450}, + {"1509AS", 450}, + {"1509N", 450}, + {"1509SN", 450}, // 135 + {"1509AN", 450}, + {"1509BN", 450}, + {"1511", 150}, + {"1511A", 150}, + {"1511B", 90}, // 140 + {"1511BA", 90}, + {"1511C", 135}, + {"1511D", 105}, + {"1930", 0}, + {"1511E", 150}, // 145 + {"1512", 165}, + {"1513", 180}, + {"1517", 0}, + {"1517A", 165}, + {"1518", 165}, // 150 + {"1518A", 165}, + {"1518B", 165}, + {"1591", 450}, + {"1592", 450}, + {"1593", 450}, // 155 + {"1594", 450}, + {"1595", 450}, + {"1596", 450}, + {"1601", 0}, + {"1603", 0}, // 160 + {"1606B", 315}, + {"1607A", 0}, + {"1610", 0}, + {"1611", 0}, + {"1612", 0}, // 165 + {"1615", 0}, + {"1619", 0}, + {"1620", 120}, + {"1621", 105}, + {"1622", 105}, // 170 + {"1629", 450}, + {"1630", 450}, + {"1631", 525}, + {"1632", 0}, + {"1633", 615}, // 175 + {"1634", 180}, + {"1702", 180}, + {"1702DD", 180}, + {"1702NU", 180}, + {"1702ND", 180}, // 180 + {"1704", 300}, + {"1704D", 300}, + {"1705", 195}, + {"1705D", 195}, + {"1706", 195}, // 185 + {"1706DD", 195}, + {"1706ND", 195}, + {"1706NU", 195}, + {"1901", 135}, + {"1902", 1410}, // 190 + {"1903", 0}, + {"1904", 1920}, + {"1908", 600}, + {"1908A", 195}, + {"1908B", 105}, // 195 + {"1908C", 165}, + {"1908CD", 0}, + {"1909A", 150}, + {"1909B", 150}, + {"1909C", 150}, // 200 + {"1910A", 180}, + {"1910B", 180}, + {"1910C", 180}, + {"1911A", 90}, + {"1911B", 90}, // 205 + {"1911C", 90}, + {"1912", 0}, + {"1913", 0}, + {"1917", 0}, + {"1918", 390}, // 210 + {"1919", 360}, + {"1919A", 105}, + {"1920", 75}, + {"1922", 75}, + {"1923", 150}, // 215 + {"8001", 120}, + {"8001A", 120}, + {"8002", 120}, + {"8002A", 120}, + {"8002B", 120}, // 220 + {"8003", 105}, + {"8003A", 105}, + {"8004", 105}, + {"8004A", 105}, + {"8005", 270}, // 225 + {"8005B", 270}, + {"8010", 270}, + {"8013", 120}, + {"8013A", 120}, + {"8014", 165}, // 230 + {"8014A", 165}, + {"8014R", 165}, + {"8014AR", 165}, + {"8015", 150}, + {"8015A", 150}, // 235 + {"8015R", 150}, + {"8015AR", 150}, + {"8017", 120}, + {"8017A", 120}, + {"8017R", 120}, // 240 + {"8017AR", 120}, + {"8017N", 90}, + {"8023", 135}, + {"8023A", 135}, + {"8023M", 135}, // 245 + {"8024", 150}, + {"8024A", 180}, + {"8024M", 180}, + {"8025", 150}, + {"8025A", 150}, // 250 + {"8025M", 150}, + {"8027", 75}, + {"8028", 75}, + {"8029", 120}, + {"8029A", 120}, // 255 + {"8031", 375}, + {"8032", 0}, + {"8032A", 0}, + {"8033", 105}, + {"8035", 195}, // 260 + {"8035A", 120}, + {"8035B", 180}, + {"8035C", 135}, + {"8036", 105}, + {"8037", 195}, // 265 + {"8037A", 195}, + {"8040", 240}, + {"8040A", 240}, + {"8041", 195}, + {"8041A", 195}, // 270 + {"8042", 600}, + {"8042A", 600} +}; + +Action::Action(LastExpressEngine *engine) : _engine(engine) { + ADD_ACTION(dummy); + ADD_ACTION(inventory); + ADD_ACTION(savePoint); + ADD_ACTION(playSound); + ADD_ACTION(playMusic); + ADD_ACTION(knock); + ADD_ACTION(compartment); + ADD_ACTION(playSounds); + ADD_ACTION(playAnimation); + ADD_ACTION(openCloseObject); + ADD_ACTION(updateObjetLocation2); + ADD_ACTION(setItemLocation); + ADD_ACTION(knockNoSound); + ADD_ACTION(pickItem); + ADD_ACTION(dropItem); + ADD_ACTION(dummy); + ADD_ACTION(enterCompartment); + ADD_ACTION(dummy); + ADD_ACTION(getOutsideTrain); + ADD_ACTION(slip); + ADD_ACTION(getInsideTrain); + ADD_ACTION(climbUpTrain); + ADD_ACTION(climbDownTrain); + ADD_ACTION(jumpUpDownTrain); + ADD_ACTION(unbound); + ADD_ACTION(25); + ADD_ACTION(26); + ADD_ACTION(27); + ADD_ACTION(concertSitCough); + ADD_ACTION(29); + ADD_ACTION(catchBeetle); + ADD_ACTION(exitCompartment); + ADD_ACTION(32); + ADD_ACTION(useWhistle); + ADD_ACTION(openMatchBox); + ADD_ACTION(openBed); + ADD_ACTION(dummy); + ADD_ACTION(dialog); + ADD_ACTION(eggBox); + ADD_ACTION(39); + ADD_ACTION(bed); + ADD_ACTION(playMusicChapter); + ADD_ACTION(playMusicChapterSetupTrain); + ADD_ACTION(switchChapter); + ADD_ACTION(44); +} + +Action::~Action() { + for (int i = 0; i < (int)_actions.size(); i++) + delete _actions[i]; + + // Zero-out passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Processing hotspot +////////////////////////////////////////////////////////////////////////// +SceneIndex Action::processHotspot(const SceneHotspot &hotspot) { + if (!hotspot.action || hotspot.action >= (int)_actions.size()) + return kSceneInvalid; + + return (*_actions[hotspot.action])(hotspot); +} + +////////////////////////////////////////////////////////////////////////// +// Actions +////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////// +// Action 0 +IMPLEMENT_ACTION(dummy) + error("Action::action_dummy: Function should never be called (hotspot action: %d)!", hotspot.action); +} + +////////////////////////////////////////////////////////////////////////// +// Action 1 +IMPLEMENT_ACTION(inventory) + if (!getState()->sceneUseBackup) + return kSceneInvalid; + + SceneIndex index = kSceneNone; + if (getState()->sceneBackup2) { + index = getState()->sceneBackup2; + getState()->sceneBackup2 = kSceneNone; + } else { + getState()->sceneUseBackup = false; + index = getState()->sceneBackup; + + Scene *backup = getScenes()->get(getState()->sceneBackup); + if (getEntities()->getPosition(backup->car, backup->position)) + index = getScenes()->processIndex(getState()->sceneBackup); + } + + getScenes()->loadScene(index); + + if (!getInventory()->getSelectedItem()) + return kSceneInvalid; + + if (!getInventory()->getSelectedEntry()->isSelectable || (!getState()->sceneBackup2 && getInventory()->getFirstExaminableItem())) + getInventory()->selectItem(getInventory()->getFirstExaminableItem()); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 2 +IMPLEMENT_ACTION(savePoint) + getSavePoints()->push(kEntityPlayer, (EntityIndex)hotspot.param1, (ActionIndex)hotspot.param2); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 3 +IMPLEMENT_ACTION(playSound) + + // Check that the file is not already buffered + if (hotspot.param2 || !getSound()->isBuffered(Common::String::printf("LIB%03d", hotspot.param1), true)) + getSound()->playSoundEvent(kEntityPlayer, hotspot.param1, hotspot.param2); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 4 +IMPLEMENT_ACTION(playMusic) + // Check that the file is not already buffered + Common::String filename = Common::String::printf("MUS%03d", hotspot.param1); + + if (!getSound()->isBuffered(filename) && (hotspot.param1 != 50 || getProgress().chapter == kChapter5)) + getSound()->playSound(kEntityPlayer, filename, SoundManager::kFlagDefault, hotspot.param2); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 5 +IMPLEMENT_ACTION(knock) + ObjectIndex object = (ObjectIndex)hotspot.param1; + if (object >= kObjectMax) + return kSceneInvalid; + + if (getObjects()->get(object).entity) { + getSavePoints()->push(kEntityPlayer, getObjects()->get(object).entity, kActionKnock, object); + } else { + if (!getSound()->isBuffered("LIB012", true)) + getSound()->playSoundEvent(kEntityPlayer, 12); + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 6 +IMPLEMENT_ACTION(compartment) + ObjectIndex compartment = (ObjectIndex)hotspot.param1; + + if (compartment >= kObjectMax) + return kSceneInvalid; + + if (getObjects()->get(compartment).entity) { + getSavePoints()->push(kEntityPlayer, getObjects()->get(compartment).entity, kActionOpenDoor, compartment); + + // Stop processing further + return kSceneNone; + } + + if (handleOtherCompartment(compartment, true, true)) { + // Stop processing further + return kSceneNone; + } + + ObjectLocation location = getObjects()->get(compartment).location; + if (location == kObjectLocation1 || location == kObjectLocation3 || getEntities()->checkFields2(compartment)) { + + if (location != kObjectLocation1 || getEntities()->checkFields2(compartment) + || (getInventory()->getSelectedItem() != kItemKey + && (compartment != kObjectCompartment1 + || !getInventory()->hasItem(kItemKey) + || (getInventory()->getSelectedItem() != kItemFirebird && getInventory()->getSelectedItem() != kItemBriefcase)))) { + if (!getSound()->isBuffered("LIB13")) + getSound()->playSoundEvent(kEntityPlayer, 13); + + // Stop processing further + return kSceneNone; + } + + getSound()->playSoundEvent(kEntityPlayer, 32); + + if ((compartment >= kObjectCompartment1 && compartment <= kObjectCompartment3) || (compartment >= kObjectCompartmentA && compartment <= kObjectCompartmentF)) + getObjects()->update(compartment, kEntityPlayer, kObjectLocationNone, kCursorHandKnock, kCursorHand); + + getSound()->playSoundEvent(kEntityPlayer, 15, 22); + getInventory()->unselectItem(); + + return kSceneInvalid; + } + + if (hotspot.action != SceneHotspot::kActionEnterCompartment || getInventory()->getSelectedItem() != kItemKey) { + if (compartment == kObjectCageMax) { + getSound()->playSoundEvent(kEntityPlayer, 26); + } else { + getSound()->playSoundEvent(kEntityPlayer, 14); + getSound()->playSoundEvent(kEntityPlayer, 15, 22); + } + return kSceneInvalid; + } + + getObjects()->update(kObjectCompartment1, kEntityPlayer, kObjectLocation1, kCursorHandKnock, kCursorHand); + getSound()->playSoundEvent(kEntityPlayer, 16); + getInventory()->unselectItem(); + + // Stop processing further + return kSceneNone; +} + +////////////////////////////////////////////////////////////////////////// +// Action 7 +IMPLEMENT_ACTION(playSounds) + getSound()->playSoundEvent(kEntityPlayer, hotspot.param1); + getSound()->playSoundEvent(kEntityPlayer, hotspot.param3, hotspot.param2); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 8 +IMPLEMENT_ACTION(playAnimation) + if (getEvent(hotspot.param1)) + return kSceneInvalid; + + playAnimation((EventIndex)hotspot.param1); + + if (!hotspot.scene) + getScenes()->processScene(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 9 +IMPLEMENT_ACTION(openCloseObject) + ObjectIndex object = (ObjectIndex)hotspot.param1; + ObjectLocation location = (ObjectLocation)hotspot.param2; + + if (object >= kObjectMax) + return kSceneInvalid; + + getObjects()->update(object, getObjects()->get(object).entity, location, kCursorKeepValue, kCursorKeepValue); + + bool isNotWindow = ((object <= kObjectCompartment8 || object >= kObjectHandleBathroom) && (object <= kObjectCompartmentH || object >= kObject48)); + + switch (location) { + default: + break; + + case kObjectLocation1: + if (isNotWindow) + getSound()->playSoundEvent(kEntityPlayer, 24); + else + getSound()->playSoundEvent(kEntityPlayer, 21); + break; + + case kObjectLocation2: + if (isNotWindow) + getSound()->playSoundEvent(kEntityPlayer, 36); + else + getSound()->playSoundEvent(kEntityPlayer, 20); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 10 +IMPLEMENT_ACTION(updateObjetLocation2) + ObjectIndex object = (ObjectIndex)hotspot.param1; + ObjectLocation location = (ObjectLocation)hotspot.param2; + + if (object >= kObjectMax) + return kSceneInvalid; + + getObjects()->updateLocation2(object, location); + + if (object != kObject112 || getSound()->isBuffered("LIB096")) { + if (object == 1) + getSound()->playSoundEvent(kEntityPlayer, 73); + } else { + getSound()->playSoundEvent(kEntityPlayer, 96); + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 11 +IMPLEMENT_ACTION(setItemLocation) + InventoryItem item = (InventoryItem)hotspot.param1; + if (item >= kPortraitOriginal) + return kSceneInvalid; + + Inventory::InventoryEntry* entry = getInventory()->get(item); + if (!entry->isPresent) + return kSceneInvalid; + + entry->location = (ObjectLocation)hotspot.param2; + + if (item == kItemCorpse) { + ObjectLocation corpseLocation = getInventory()->get(kItemCorpse)->location; + + if (corpseLocation == kObjectLocation3 || corpseLocation == kObjectLocation4) + getProgress().eventCorpseMovedFromFloor = true; + else + getProgress().eventCorpseMovedFromFloor = false; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 12 +IMPLEMENT_ACTION(knockNoSound) + ObjectIndex object = (ObjectIndex)hotspot.param1; + if (object >= kObjectMax) + return kSceneInvalid; + + if (getObjects()->get(object).entity) + getSavePoints()->push(kEntityPlayer, getObjects()->get(object).entity, kActionKnock, object); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 13 +IMPLEMENT_ACTION(pickItem) + InventoryItem item = (InventoryItem)hotspot.param1; + ObjectLocation location = (ObjectLocation)hotspot.param2; + bool process = (hotspot.scene == 0); + SceneIndex sceneIndex = kSceneInvalid; + + if (item >= kPortraitOriginal) + return kSceneInvalid; + + Inventory::InventoryEntry* entry = getInventory()->get(item); + if (!entry->location) + return kSceneInvalid; + + // Special case for corpse + if (item == kItemCorpse) { + pickCorpse(location, process); + return kSceneInvalid; + } + + // Add and process items + getInventory()->addItem(item); + + switch (item) { + default: + break; + + case kItemGreenJacket: + pickGreenJacket(process); + break; + + case kItemScarf: + pickScarf(process); + + // stop processing + return kSceneInvalid; + + case kItemParchemin: + if (location != kObjectLocation2) + break; + + getInventory()->addItem(kItemParchemin); + getInventory()->get(kItem11)->location = kObjectLocation1; + getSound()->playSoundEvent(kEntityPlayer, 9); + break; + + case kItemBomb: + RESET_ENTITY_STATE(kEntityAbbot, Abbot, setup_pickBomb); + break; + + case kItemBriefcase: + getSound()->playSoundEvent(kEntityPlayer, 83); + break; + } + + // Load item scene + if (getInventory()->get(item)->scene) { + if (!getState()->sceneUseBackup) { + getState()->sceneUseBackup = true; + getState()->sceneBackup = (hotspot.scene ? hotspot.scene : getState()->scene); + } + + getScenes()->loadScene(getInventory()->get(item)->scene); + + // do not process further + sceneIndex = kSceneNone; + } + + // Select item + if (getInventory()->get(item)->isSelectable) { + getInventory()->selectItem(item); + _engine->getCursor()->setStyle(getInventory()->get(item)->cursor); + } + + return sceneIndex; +} + +////////////////////////////////////////////////////////////////////////// +// Action 14 +IMPLEMENT_ACTION(dropItem) + InventoryItem item = (InventoryItem)hotspot.param1; + ObjectLocation location = (ObjectLocation)hotspot.param2; + bool process = (hotspot.scene == kSceneNone); + + if (item >= kPortraitOriginal) + return kSceneInvalid; + + if (!getInventory()->hasItem(item)) + return kSceneInvalid; + + if (location < kObjectLocation1) + return kSceneInvalid; + + // Handle actions + if (item == kItemBriefcase) { + getSound()->playSoundEvent(kEntityPlayer, 82); + + if (location == kObjectLocation2) { + if (!getProgress().field_58) { + getSaveLoad()->saveGame(kSavegameTypeTime, kEntityPlayer, kTimeNone); + getProgress().field_58 = 1; + } + + if (getInventory()->get(kItemParchemin)->location == kObjectLocation2) { + getInventory()->addItem(kItemParchemin); + getInventory()->get(kItem11)->location = kObjectLocation1; + getSound()->playSoundEvent(kEntityPlayer, 9); + } + } + } + + // Update item location + getInventory()->removeItem(item, location); + + if (item == kItemCorpse) + dropCorpse(process); + + // Unselect item + getInventory()->unselectItem(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 15: Dummy action + +////////////////////////////////////////////////////////////////////////// +// Action 16 +IMPLEMENT_ACTION(enterCompartment) + if (getObjects()->get(kObjectCompartment1).location == kObjectLocation1 || getObjects()->get(kObjectCompartment1).location == kObjectLocation3 || getInventory()->getSelectedItem() == kItemKey) + return action_compartment(hotspot); + + if (getProgress().eventCorpseFound) { + if (hotspot.action != SceneHotspot::kActionEnterCompartment || getInventory()->get(kItemBriefcase)->location != kObjectLocation2) + return action_compartment(hotspot); + + getSound()->playSoundEvent(kEntityPlayer, 14); + getSound()->playSoundEvent(kEntityPlayer, 15, 22); + + if (getProgress().field_78 && !getSound()->isBuffered("MUS003")) { + getSound()->playSound(kEntityPlayer, "MUS003", SoundManager::kFlagDefault); + getProgress().field_78 = 0; + } + + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 77); + + return kSceneNone; + } + + getSaveLoad()->saveGame(kSavegameTypeTime, kEntityPlayer, kTimeNone); + getSound()->playSound(kEntityPlayer, "LIB014"); + playAnimation(kEventCathFindCorpse); + getSound()->playSound(kEntityPlayer, "LIB015"); + getProgress().eventCorpseFound = true; + + return kSceneCompartmentCorpse; +} + +////////////////////////////////////////////////////////////////////////// +// Action 17: Dummy action + +////////////////////////////////////////////////////////////////////////// +// Action 18 +IMPLEMENT_ACTION(getOutsideTrain) + ObjectIndex object = (ObjectIndex)hotspot.param1; + + if ((getEvent(kEventCathLookOutsideWindowDay) || getEvent(kEventCathLookOutsideWindowNight) || getObjects()->get(kObjectCompartment1).location2 == kObjectLocation1) + && getProgress().isTrainRunning + && (object != kObjectOutsideAnnaCompartment || (!getEntities()->isInsideCompartment(kEntityRebecca, kCarRedSleeping, kPosition_4840) && getObjects()->get(kObjectOutsideBetweenCompartments).location == kObjectLocation2)) + && getInventory()->getSelectedItem() != kItemFirebird + && getInventory()->getSelectedItem() != kItemBriefcase) { + + switch (object) { + default: + return kSceneInvalid; + + case kObjectOutsideTylerCompartment: + getEvent(kEventCathLookOutsideWindowDay) = 1; + playAnimation(isNight() ? kEventCathGoOutsideTylerCompartmentNight : kEventCathGoOutsideTylerCompartmentDay); + getProgress().field_C8 = 1; + break; + + case kObjectOutsideBetweenCompartments: + getEvent(kEventCathLookOutsideWindowDay) = 1; + playAnimation(isNight() ? kEventCathGoOutsideNight : kEventCathGoOutsideDay); + getProgress().field_C8 = 1; + break; + + case kObjectOutsideAnnaCompartment: + getEvent(kEventCathLookOutsideWindowDay) = 1; + playAnimation(isNight() ? kEventCathGetInsideNight : kEventCathGetInsideDay); + if (!hotspot.scene) + getScenes()->processScene(); + break; + } + } else { + if (object == kObjectOutsideTylerCompartment || object == kObjectOutsideBetweenCompartments || object == kObjectOutsideAnnaCompartment) { + playAnimation(isNight() ? kEventCathLookOutsideWindowNight : kEventCathLookOutsideWindowDay); + getScenes()->processScene(); + return kSceneNone; + } + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 19 +IMPLEMENT_ACTION(slip) + switch((ObjectIndex)hotspot.param1) { + default: + return kSceneInvalid; + + case kObjectOutsideTylerCompartment: + playAnimation(isNight() ? kEventCathSlipTylerCompartmentNight : kEventCathSlipTylerCompartmentDay); + break; + + case kObjectOutsideBetweenCompartments: + playAnimation(isNight() ? kEventCathSlipNight : kEventCathSlipDay); + break; + } + + getProgress().field_C8 = 0; + + if (!hotspot.scene) + getScenes()->processScene(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 20 +IMPLEMENT_ACTION(getInsideTrain) + switch ((ObjectIndex)hotspot.param1) { + default: + return kSceneInvalid; + + case kObjectOutsideTylerCompartment: + playAnimation(isNight() ? kEventCathGetInsideTylerCompartmentNight : kEventCathGetInsideTylerCompartmentDay); + break; + + case kObjectOutsideBetweenCompartments: + playAnimation(isNight() ? kEventCathGetInsideNight : kEventCathGetInsideDay); + break; + + case kObjectOutsideAnnaCompartment: + playAnimation(kEventCathGettingInsideAnnaCompartment); + break; + } + + if (!hotspot.scene) + getScenes()->processScene(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 21 +IMPLEMENT_ACTION(climbUpTrain) + byte action = hotspot.param1; + + if (action != 1 && action != 2) + return kSceneInvalid; + + switch (getProgress().chapter) { + default: + break; + + case kChapter2: + case kChapter3: + if (action == 2) + playAnimation(kEventCathClimbUpTrainGreenJacket); + + playAnimation(kEventCathTopTrainGreenJacket); + break; + + case kChapter5: + if (action == 2) + playAnimation(getProgress().isNightTime ? kEventCathClimbUpTrainNoJacketNight : kEventCathClimbUpTrainNoJacketDay); + + playAnimation(getProgress().isNightTime ? kEventCathTopTrainNoJacketNight : kEventCathTopTrainNoJacketDay); + break; + } + + if (!hotspot.scene) + getScenes()->processScene(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 22 +IMPLEMENT_ACTION(climbDownTrain) + EventIndex evt = kEventNone; + switch (getProgress().chapter) { + default: + return kSceneInvalid; + + case kChapter2: + case kChapter3: + evt = kEventCathClimbDownTrainGreenJacket; + break; + + case kChapter5: + evt = (getProgress().isNightTime ? kEventCathClimbDownTrainNoJacketNight : kEventCathClimbDownTrainNoJacketDay); + break; + } + + playAnimation(evt); + if (evt == kEventCathClimbDownTrainNoJacketDay) + getSound()->playSoundEvent(kEntityPlayer, 37); + + if (!hotspot.scene) + getScenes()->processScene(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 23 +IMPLEMENT_ACTION(jumpUpDownTrain) + switch (hotspot.param1) { + default: + break; + + case 1: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kActionBreakCeiling); + break; + + case 2: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kActionJumpDownCeiling); + break; + + case 3: + if (getInventory()->getSelectedItem() == kItemBriefcase) { + getInventory()->removeItem(kItemBriefcase, kObjectLocation3); + getSound()->playSoundEvent(kEntityPlayer, 82); + getInventory()->unselectItem(); + } + + // Show animation with or without briefcase + playAnimation((getInventory()->get(kItemBriefcase)->location - 3) ? kEventCathJumpUpCeilingBriefcase : kEventCathJumpUpCeiling); + + if (!hotspot.scene) + getScenes()->processScene(); + + break; + + case 4: + if (getProgress().chapter == kChapter1) + getSavePoints()->push(kEntityPlayer, kEntityKronos, kAction202621266); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 24 +IMPLEMENT_ACTION(unbound) + byte action = hotspot.param1; + + switch (action) { + default: + break; + + case 1: + playAnimation(kEventCathStruggleWithBonds); + if (hotspot.scene) + getScenes()->processScene(); + break; + + case 2: + playAnimation(kEventCathBurnRope); + if (hotspot.scene) + getScenes()->processScene(); + break; + + case 3: + if (getEvent(kEventCathBurnRope)) { + playAnimation(kEventCathRemoveBonds); + getProgress().field_84 = 0; + getScenes()->loadSceneFromPosition(kCarBaggageRear, 89); + return kSceneNone; + } + break; + + case 4: + if (!getEvent(kEventCathStruggleWithBonds2)) { + playAnimation(kEventCathStruggleWithBonds2); + getSound()->playSoundEvent(kEntityPlayer, 101); + getInventory()->setLocationAndProcess(kItemMatch, kObjectLocation2); + if (!hotspot.scene) + getScenes()->processScene(); + } + break; + + case 5: + getSavePoints()->push(kEntityPlayer, kEntityIvo, kAction192637492); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 25 +IMPLEMENT_ACTION(25) + switch(hotspot.param1) { + default: + break; + + case 1: + getSavePoints()->push(kEntityPlayer, kEntityAnna, kAction272177921); + break; + + case 2: + if (!getSound()->isBuffered("MUS021")) + getSound()->playSound(kEntityPlayer, "MUS021", SoundManager::kFlagDefault); + break; + + case 3: + getSound()->playSoundEvent(kEntityPlayer, 43); + if (!getInventory()->hasItem(kItemKey) && !getEvent(kEventAnnaBaggageArgument)) { + RESET_ENTITY_STATE(kEntityAnna, Anna, setup_baggage); + return kSceneNone; + } + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 26 +IMPLEMENT_ACTION(26) + switch(hotspot.param1) { + default: + return kSceneInvalid; + + case 1: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kAction158610240); + break; + + case 2: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kAction225367984); + getInventory()->unselectItem(); + return kSceneNone; + + case 3: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kAction191001984); + return kSceneNone; + + case 4: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kAction201959744); + return kSceneNone; + + case 5: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kAction169300225); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 27 +IMPLEMENT_ACTION(27) + if (!getSound()->isBuffered("LIB031", true)) + getSound()->playSoundEvent(kEntityPlayer, 31); + + switch (getEntityData(kEntityPlayer)->car) { + default: + break; + + case kCarGreenSleeping: + getSavePoints()->push(kEntityPlayer, kEntityMertens, kAction225358684, hotspot.param1); + break; + + case kCarRedSleeping: + getSavePoints()->push(kEntityPlayer, kEntityCoudert, kAction225358684, hotspot.param1); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 28 +IMPLEMENT_ACTION(concertSitCough) + switch(hotspot.param1) { + default: + return kSceneInvalid; + + case 1: + playAnimation(kEventConcertSit); + break; + + case 2: + playAnimation(kEventConcertCough); + break; + } + + if (!hotspot.scene) + getScenes()->processScene(); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 29 +IMPLEMENT_ACTION(29) + getProgress().field_C = 1; + getSound()->playSoundEvent(kEntityPlayer, hotspot.param1, hotspot.param2); + + Common::String filename = Common::String::printf("MUS%03d", hotspot.param3); + if (!getSound()->isBuffered(filename)) + getSound()->playSound(kEntityPlayer, filename, SoundManager::kFlagDefault); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 30 +IMPLEMENT_ACTION(catchBeetle) + if (!getBeetle()->isLoaded()) + return kSceneInvalid; + + if (getBeetle()->catchBeetle()) { + getBeetle()->unload(); + getInventory()->get(kItemBeetle)->location = kObjectLocation1; + getSavePoints()->push(kEntityPlayer, kEntityChapters, kActionCatchBeetle); + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 31 +IMPLEMENT_ACTION(exitCompartment) + if (!getProgress().field_30 && getProgress().jacket != kJacketOriginal) { + getSaveLoad()->saveGame(kSavegameTypeTime, kEntityPlayer, kTimeNone); + getProgress().field_30 = 1; + } + + getObjects()->updateLocation2(kObjectCompartment1, (ObjectLocation)hotspot.param2); + + // fall to case enterCompartment action + return action_enterCompartment(hotspot); +} + +////////////////////////////////////////////////////////////////////////// +// Action 32 +IMPLEMENT_ACTION(32) + switch(hotspot.param1) { + default: + break; + + case 1: + getSavePoints()->push(kEntityPlayer, kEntitySalko, kAction167992577); + break; + + case 2: + getSavePoints()->push(kEntityPlayer, kEntityVesna, kAction202884544); + break; + + case 3: + if (getProgress().chapter == kChapter5) { + getSavePoints()->push(kEntityPlayer, kEntityAbbot, kAction168646401); + getSavePoints()->push(kEntityPlayer, kEntityMilos, kAction168646401); + } else { + getSavePoints()->push(kEntityPlayer, kEntityTrain, kAction203339360); + } + // Stop processing further scenes + return kSceneNone; + + case 4: + getSavePoints()->push(kEntityPlayer, kEntityMilos, kAction169773228); + break; + + case 5: + getSavePoints()->push(kEntityPlayer, kEntityVesna, kAction167992577); + break; + + case 6: + getSavePoints()->push(kEntityPlayer, kEntityAugust, kAction203078272); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 33 +IMPLEMENT_ACTION(useWhistle) + EventIndex evt = kEventNone; + SceneIndex sceneIndex = kSceneInvalid; + + switch (hotspot.param1) { + default: + break; + + case 1: + if (getEvent(kEventKronosBringFirebird)) { + getSavePoints()->push(kEntityPlayer, kEntityAnna, kAction205294778); + break; + } + + if (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_8200)) { + evt = kEventCathOpenEgg; + + Scene *scene = getScenes()->get(hotspot.scene); + if (scene->getHotspot()) + sceneIndex = scene->getHotspot()->scene; + + } else { + evt = kEventCathOpenEggNoBackground; + } + getProgress().isEggOpen = true; + break; + + case 2: + if (getEvent(kEventKronosBringFirebird)) { + getSavePoints()->push(kEntityPlayer, kEntityAnna, kAction224309120); + break; + } + + evt = (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_8200)) ? kEventCathCloseEgg : kEventCathCloseEggNoBackground; + getProgress().isEggOpen = false; + break; + + case 3: + if (getEvent(kEventKronosBringFirebird)) { + getSavePoints()->push(kEntityPlayer, kEntityAnna, kActionUseWhistle); + break; + } + + evt = (getEntities()->isInsideCompartment(kEntityPlayer, kCarGreenSleeping, kPosition_8200)) ? kEventCathUseWhistleOpenEgg : kEventCathUseWhistleOpenEggNoBackground; + break; + + } + + if (evt != kEventNone) { + playAnimation(evt); + if (sceneIndex == kSceneNone || !hotspot.scene) + getScenes()->processScene(); + } + + return sceneIndex; +} + +////////////////////////////////////////////////////////////////////////// +// Action 34 +IMPLEMENT_ACTION(openMatchBox) + // If the match is already in the inventory, do nothing + if (!getInventory()->get(kItemMatch)->location + || getInventory()->get(kItemMatch)->isPresent) + return kSceneInvalid; + + getInventory()->addItem(kItemMatch); + getSound()->playSoundEvent(kEntityPlayer, 102); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 35 +IMPLEMENT_ACTION(openBed) + getSound()->playSoundEvent(kEntityPlayer, 59); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 36: Dummy action + +////////////////////////////////////////////////////////////////////////// +// Action 37 +IMPLEMENT_ACTION(dialog) + getSound()->playDialog(kEntityTables4, (EntityIndex)hotspot.param1, SoundManager::kFlagDefault, 0); + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 38 +IMPLEMENT_ACTION(eggBox) + getSound()->playSoundEvent(kEntityPlayer, 43); + if (getProgress().field_7C && !getSound()->isBuffered("MUS003")) { + getSound()->playSound(kEntityPlayer, "MUS003", SoundManager::kFlagDefault); + getProgress().field_7C = 0; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 39 +IMPLEMENT_ACTION(39) + getSound()->playSoundEvent(kEntityPlayer, 24); + if (getProgress().field_80 && !getSound()->isBuffered("MUS003")) { + getSound()->playSound(kEntityPlayer, "MUS003", SoundManager::kFlagDefault); + getProgress().field_80 = 0; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 40 +IMPLEMENT_ACTION(bed) + getSound()->playSoundEvent(kEntityPlayer, 85); + // falls to case knockNoSound + return action_knockNoSound(hotspot); +} + +////////////////////////////////////////////////////////////////////////// +// Action 41 +IMPLEMENT_ACTION(playMusicChapter) + byte id = 0; + switch (getProgress().chapter) { + default: + break; + + case kChapter1: + id = hotspot.param1; + break; + + case kChapter2: + case kChapter3: + id = hotspot.param2; + break; + + case kChapter4: + case kChapter5: + id = hotspot.param3; + break; + } + + if (id) { + Common::String filename = Common::String::printf("MUS%03d", id); + + if (!getSound()->isBuffered(filename)) + getSound()->playSound(kEntityPlayer, filename, SoundManager::kFlagDefault); + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 42 +IMPLEMENT_ACTION(playMusicChapterSetupTrain) + int id = 0; + switch (getProgress().chapter) { + default: + break; + + case kChapter1: + id = 1; + break; + + case kChapter2: + case kChapter3: + id = 2; + break; + + case kChapter4: + case kChapter5: + id = 4; + break; + } + + Common::String filename = Common::String::printf("MUS%03d", hotspot.param1); + + if (!getSound()->isBuffered(filename) && hotspot.param3 & id) { + getSound()->playSound(kEntityPlayer, filename, SoundManager::kFlagDefault); + + getSavePoints()->call(kEntityPlayer, kEntityTrain, kAction203863200, filename.c_str()); + getSavePoints()->push(kEntityPlayer, kEntityTrain, kAction222746496, hotspot.param2); + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// // Action 43 +IMPLEMENT_ACTION(switchChapter) + // Nothing to do here as an hotspot action + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Action 44 +IMPLEMENT_ACTION(44) + switch (hotspot.param1) { + default: + break; + + case 1: + getSavePoints()->push(kEntityPlayer, kEntityRebecca, kAction205034665); + break; + + case 2: + getSavePoints()->push(kEntityPlayer, kEntityChapters, kAction225358684); + break; + } + + return kSceneInvalid; +} + +////////////////////////////////////////////////////////////////////////// +// Helper functions +////////////////////////////////////////////////////////////////////////// +void Action::pickGreenJacket(bool process) const { + getProgress().jacket = kJacketGreen; + getInventory()->addItem(kItemMatchBox); + + getObjects()->update(kObjectOutsideTylerCompartment, kEntityPlayer, kObjectLocation2, kCursorKeepValue, kCursorKeepValue); + playAnimation(kEventPickGreenJacket); + + getInventory()->setPortrait(kPortraitGreen); + + if (process) + getScenes()->processScene(); +} + +void Action::pickScarf(bool process) const { + playAnimation(getProgress().jacket == kJacketGreen ? kEventPickScarfGreen : kEventPickScarfOriginal); + + if (process) + getScenes()->processScene(); +} + +void Action::pickCorpse(ObjectLocation bedPosition, bool process) const { + + if (getProgress().jacket == kJacketOriginal) + getProgress().jacket = kJacketBlood; + + switch(getInventory()->get(kItemCorpse)->location) { + // No way to pick the corpse + default: + break; + + // Floor + case kObjectLocation1: + if (bedPosition != 4) { + playAnimation(getProgress().jacket == kJacketGreen ? kEventCorpsePickFloorGreen : kEventCorpsePickFloorOriginal); + break; + } + + if (getProgress().jacket) + playAnimation(kEventCorpsePickFloorOpenedBedOriginal); + + getInventory()->get(kItemCorpse)->location = kObjectLocation5; + break; + + // Bed + case kObjectLocation2: + playAnimation(getProgress().jacket == kJacketGreen ? kEventCorpsePickFloorGreen : kEventCorpsePickBedOriginal); + break; + } + + if (process) + getScenes()->processScene(); + + // Add corpse to inventory + if (bedPosition != 4) { // bed position + getInventory()->addItem(kItemCorpse); + getInventory()->selectItem(kItemCorpse); + _engine->getCursor()->setStyle(kCursorCorpse); + } +} + +void Action::dropCorpse(bool process) const { + switch(getInventory()->get(kItemCorpse)->location) { + default: + break; + + case kObjectLocation1: // Floor + playAnimation(getProgress().jacket == kJacketGreen ? kEventCorpseDropFloorGreen : kEventCorpseDropFloorOriginal); + break; + + case kObjectLocation2: // Bed + playAnimation(getProgress().jacket == kJacketGreen ? kEventCorpseDropBedGreen : kEventCorpseDropBedOriginal); + break; + + case kObjectLocation4: // Window + // Say goodbye to an old friend + getInventory()->get(kItemCorpse)->location = kObjectLocationNone; + getProgress().eventCorpseThrown = true; + + if (getState()->time <= kTime1138500) { + playAnimation(getProgress().jacket == kJacketGreen ? kEventCorpseDropWindowGreen : kEventCorpseDropWindowOriginal); + + getProgress().field_24 = true; + } else { + playAnimation(kEventCorpseDropBridge); + } + + getProgress().eventCorpseMovedFromFloor = true; + break; + } + + if (process) + getScenes()->processScene(); +} + +bool Action::handleOtherCompartment(ObjectIndex object, bool doPlaySound, bool doLoadScene) const { +#define ENTITY_PARAMS(entity, index, id) \ + ((EntityData::EntityParametersIIII*)getEntities()->get(entity)->getParamData()->getParameters(8, index))->param##id + + // Only handle compartments + if (getEntityData(kEntityPlayer)->location != kLocationOutsideCompartment + || ((object < kObjectCompartment2 || object > kObjectCompartment8) && (object < kObjectCompartmentA || object > kObjectCompartmentH))) + return false; + + ////////////////////////////////////////////////////////////////////////// + // Gendarmes + if (getEntityData(kEntityPlayer)->car == getEntityData(kEntityGendarmes)->car + && getEntityData(kEntityGendarmes)->location == kLocationOutsideCompartment + && !getEntities()->compare(kEntityPlayer, kEntityGendarmes)) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object); + + return true; + } + + ////////////////////////////////////////////////////////////////////////// + // Mertens + if (getEntityData(kEntityPlayer)->car == kCarGreenSleeping + && getEntityData(kEntityMertens)->car == kCarGreenSleeping + && !getEntityData(kEntityMertens)->location + && !ENTITY_PARAMS(kEntityMertens, 0, 1)) { + + if (!getEntities()->compare(kEntityPlayer, kEntityMertens)) { + + if (getEntityData(kEntityMertens)->entityPosition < kPosition_2740 + && getEntityData(kEntityMertens)->entityPosition > kPosition_850 + && (getEntityData(kEntityCoudert)->car != kCarGreenSleeping || getEntityData(kEntityCoudert)->entityPosition > kPosition_2740) + && (getEntityData(kEntityVerges)->car != kCarGreenSleeping || getEntityData(kEntityVerges)->entityPosition > kPosition_2740)) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (!getSound()->isBuffered(kEntityMertens)) + getSound()->playWarningCompartment(kEntityMertens, object); + + getSavePoints()->push(kEntityPlayer, kEntityMertens, kAction305159806); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object); + + return true; + } + + if (getEntityData(kEntityMertens)->direction == kDirectionUp + && getEntityData(kEntityMertens)->entityPosition < getEntityData(kEntityPlayer)->entityPosition) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (!getSound()->isBuffered(kEntityMertens)) + getSound()->playSound(kEntityMertens, (rnd(2)) ? "JAC1000" : "JAC1000A"); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object); + } + + if (getEntityData(kEntityMertens)->direction == kDirectionDown + && getEntityData(kEntityMertens)->entityPosition > getEntityData(kEntityPlayer)->entityPosition) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (!getSound()->isBuffered(kEntityMertens)) + getSound()->playSound(kEntityMertens, (rnd(2)) ? "JAC1000" : "JAC1000A"); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object, true); + } + } + } + + ////////////////////////////////////////////////////////////////////////// + // Coudert + if (getEntityData(kEntityPlayer)->car != kCarRedSleeping + || !getEntityData(kEntityCoudert)->car + || getEntityData(kEntityCoudert)->location != kLocationOutsideCompartment + || ENTITY_PARAMS(kEntityCoudert, 0, 1)) + return false; + + if (!getEntities()->compare(kEntityPlayer, kEntityCoudert)) { + + if (getEntityData(kEntityCoudert)->entityPosition < kPosition_2740 + && getEntityData(kEntityCoudert)->entityPosition > kPosition_850 + && (getEntityData(kEntityMertens)->car != kCarRedSleeping || getEntityData(kEntityMertens)->entityPosition > kPosition_2740) + && (getEntityData(kEntityVerges)->car != kCarRedSleeping || getEntityData(kEntityVerges)->entityPosition > kPosition_2740) + && (getEntityData(kEntityMmeBoutarel)->car != kCarRedSleeping || getEntityData(kEntityMmeBoutarel)->entityPosition > kPosition_2740)) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (!getSound()->isBuffered(kEntityCoudert)) + getSound()->playWarningCompartment(kEntityCoudert, object); + + getSavePoints()->push(kEntityPlayer, kEntityCoudert, kAction305159806); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object); + + return true; + } + + // Direction = Up + if (getEntityData(kEntityCoudert)->direction == kDirectionUp + && getEntityData(kEntityCoudert)->entityPosition < getEntityData(kEntityPlayer)->entityPosition) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (!getSound()->isBuffered(kEntityCoudert)) + getSound()->playSound(kEntityCoudert, (rnd(2)) ? "JAC1000" : "JAC1000A"); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object); + + return true; + } + + // Direction = down + if (getEntityData(kEntityCoudert)->direction == kDirectionDown + && getEntityData(kEntityCoudert)->entityPosition > getEntityData(kEntityPlayer)->entityPosition) { + if (doPlaySound) + playCompartmentSoundEvents(object); + + if (!getSound()->isBuffered(kEntityCoudert)) + getSound()->playSound(kEntityCoudert, (rnd(2)) ? "JAC1000" : "JAC1000A"); + + if (doLoadScene) + getScenes()->loadSceneFromObject(object, true); + } + } + + return false; +} + +void Action::playCompartmentSoundEvents(ObjectIndex object) const { + if (getObjects()->get(object).location == kObjectLocation1 || getObjects()->get(object).location == kObjectLocation3 || getEntities()->checkFields2(object)) { + getSound()->playSoundEvent(kEntityPlayer, 13); + } else { + getSound()->playSoundEvent(kEntityPlayer, 14); + getSound()->playSoundEvent(kEntityPlayer, 15, 3); + } +} + +////////////////////////////////////////////////////////////////////////// +// Cursors +////////////////////////////////////////////////////////////////////////// +CursorStyle Action::getCursor(const SceneHotspot &hotspot) const { + // Simple cursor style + if (hotspot.cursor != kCursorProcess) + return (CursorStyle)hotspot.cursor; + + ObjectIndex object = (ObjectIndex)hotspot.param1; + + switch (hotspot.action) { + default: + return kCursorNormal; + + case SceneHotspot::kActionInventory: + if (!getState()->sceneBackup2 && (getEvent(kEventKronosBringFirebird) || getProgress().isEggOpen)) + return kCursorNormal; + else + return kCursorBackward; + + case SceneHotspot::kActionKnockOnDoor: + warning("================================= DOOR %03d =================================", object); + if (object >= kObjectMax) + return kCursorNormal; + else + return (CursorStyle)getObjects()->get(object).cursor; + + case SceneHotspot::kAction12: + warning("================================= OBJECT %03d =================================", object); + if (object >= kObjectMax) + return kCursorNormal; + + if (getObjects()->get(object).entity) + return (CursorStyle)getObjects()->get(object).cursor; + else + return kCursorNormal; + + case SceneHotspot::kActionPickItem: + warning("================================= ITEM %03d =================================", object); + if (object >= kObjectCompartmentA) + return kCursorNormal; + + if ((!getInventory()->getSelectedItem() || getInventory()->getSelectedEntry()->manualSelect) + && (object != kObject21 || getProgress().eventCorpseMovedFromFloor == 1)) + return kCursorHand; + else + return kCursorNormal; + + case SceneHotspot::kActionDropItem: + warning("================================= ITEM %03d =================================", object); + if (object >= kObjectCompartmentA) + return kCursorNormal; + + if (getInventory()->getSelectedItem() != (InventoryItem)object) + return kCursorNormal; + + if (object == kObject20 && hotspot.param2 == 4 && !getProgress().isTrainRunning) + return kCursorNormal; + + if (object == kObjectHandleInsideBathroom && hotspot.param2 == 1 && getProgress().field_5C) + return kCursorNormal; + + return (CursorStyle)getInventory()->getSelectedEntry()->cursor; + + case SceneHotspot::kAction15: + if (object >= kObjectMax) + return kCursorNormal; + + if (getProgress().isEqual(object, hotspot.param2)) + return (CursorStyle)hotspot.param3; + + return kCursorNormal; + + case SceneHotspot::kActionEnterCompartment: + if ((getInventory()->getSelectedItem() != kItemKey || getObjects()->get(kObjectCompartment1).location) + && (getObjects()->get(kObjectCompartment1).location != 1 || !getInventory()->hasItem(kItemKey) + || (getInventory()->getSelectedItem() != kItemFirebird && getInventory()->getSelectedItem() != kItemBriefcase))) + goto LABEL_KEY; + + return (CursorStyle)getInventory()->get(kItemKey)->cursor; // TODO is that always the same as kCursorKey ? + + case SceneHotspot::kActionGetOutsideTrain: + if (getProgress().jacket != kJacketGreen) + return kCursorNormal; + + if ((getEvent(kEventCathLookOutsideWindowDay) || getEvent(kEventCathLookOutsideWindowDay) || getObjects()->get(kObjectCompartment1).location2 == kObjectLocation1) + && getProgress().isTrainRunning + && (object != kObjectOutsideAnnaCompartment || (getEntities()->isInsideCompartment(kEntityRebecca, kCarRedSleeping, kPosition_4840) && getObjects()->get(kObjectOutsideBetweenCompartments).location == 2)) + && getInventory()->getSelectedItem() != kItemBriefcase && getInventory()->getSelectedItem() != kItemFirebird) + return kCursorForward; + + return (getObjects()->get(kObjectCompartment1).location2 < kObjectLocation2) ? kCursorNormal : kCursorMagnifier; + + case SceneHotspot::kActionSlip: + return (getProgress().field_C8 < 1) ? kCursorNormal : kCursorLeft; + + case SceneHotspot::kActionClimbUpTrain: + if (getProgress().isTrainRunning + && (getProgress().chapter == kChapter2 || getProgress().chapter == kChapter3 || getProgress().chapter == kChapter5) + && getInventory()->getSelectedItem() != kItemFirebird + && getInventory()->getSelectedItem() != kItemBriefcase) + return kCursorUp; + + return kCursorNormal; + + case SceneHotspot::kActionJumpUpDownTrain: + if (object != kObjectCompartment1) + return kCursorNormal; + + return (getObjects()->get(kObjectCeiling).location < kObjectLocation1) ? kCursorHand : kCursorNormal; + + case SceneHotspot::kActionUnbound: + if (hotspot.param2 != 2) + return kCursorNormal; + + if (getEvent(kEventCathBurnRope) || !getEvent(kEventCathStruggleWithBonds2)) + return kCursorNormal; + + return kCursorHand; + + case SceneHotspot::kActionCatchBeetle: + if (!getBeetle()->isLoaded()) + return kCursorNormal; + + if (!getBeetle()->isCatchable()) + return kCursorNormal; + + if (getInventory()->getSelectedItem() == kItemMatchBox && getInventory()->hasItem(kItemMatch)) + return (CursorStyle)getInventory()->get(kItemMatchBox)->cursor; + + return kCursorHandPointer; + + case SceneHotspot::KActionUseWhistle: + if (object != kObjectCompartment3) + return kCursorNormal; + + if (getInventory()->getSelectedItem() == kItemWhistle) + return kCursorWhistle; + else + return kCursorNormal; + + case SceneHotspot::kActionOpenBed: + if (getProgress().chapter < kChapter2) + return kCursorHand; + + return kCursorNormal; + + case SceneHotspot::kActionDialog: + if (getSound()->getDialogName((EntityIndex)object)) + return kCursorHandPointer; + + return kCursorNormal; + + case SceneHotspot::kActionBed: + if (getProgress().field_18 == 2 && !getProgress().field_E4 + && (getState()->time > kTimeBedTime + || (getProgress().eventMetAugust && getProgress().field_CC + && (!getProgress().field_24 || getProgress().field_3C)))) + return kCursorSleep; + + return kCursorNormal; + +LABEL_KEY: + // Handle compartment action + case SceneHotspot::kActionCompartment: + case SceneHotspot::kActionExitCompartment: + warning("================================= DOOR %03d =================================", object); + if (object >= kObjectMax) + return kCursorNormal; + + if (getInventory()->getSelectedItem() != kItemKey + || getObjects()->get(object).entity + || getObjects()->get(object).location != 1 + || !getObjects()->get(object).cursor2 + || getEntities()->isInsideCompartments(kEntityPlayer) + || getEntities()->checkFields2(object)) + return (CursorStyle)getObjects()->get(object).cursor2; + else + return (CursorStyle)getInventory()->get(kItemKey)->cursor; + } +} + +////////////////////////////////////////////////////////////////////////// +// Animation +////////////////////////////////////////////////////////////////////////// + +// Play an animation and add delta time to global game time +void Action::playAnimation(EventIndex index, bool debugMode) const { + if (index >= _animationListSize) + error("Action::playAnimation: invalid event index (value=%i, max=%i)", index, _animationListSize); + + // In debug mode, just show the animation + if (debugMode) { + Animation animation; + if (animation.load(getArchive(Common::String(_animationList[index].filename) + ".nis"))) + animation.play(); + return; + } + + getFlags()->flag_3 = true; + + // Hide cursor + _engine->getCursor()->show(false); + + // Show inventory & hourglass + getInventory()->show(); + getInventory()->showHourGlass(); + + if (!getFlags()->mouseRightClick) { + + if (getGlobalTimer()) { + if (getSound()->isBuffered("TIMER")) { + getSound()->processEntry("TIMER"); + setGlobalTimer(105); + } + } + + bool processSound = false; + if (index >= kEventCorpseDropFloorOriginal + || index == kEventCathWakingUp + || index == kEventConcertCough + || index == kEventConcertSit + || index == kEventConcertLeaveWithBriefcase) + processSound = true; + + Animation animation; + if (animation.load(getArchive(Common::String(_animationList[index].filename) + ".nis") , processSound ? Animation::kFlagDefault : Animation::kFlagProcess)) + animation.play(); + + if (getSound()->isBuffered("TIMER")) + getSound()->removeFromQueue("TIMER"); + } + + // Show cursor + _engine->getCursor()->show(true); + + getEvent(index) = 1; + + // Adjust game time + getState()->timeTicks += _animationList[index].time; + getState()->time += _animationList[index].time * getState()->timeDelta; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/action.h b/engines/lastexpress/game/action.h new file mode 100644 index 0000000000..d8f81abfde --- /dev/null +++ b/engines/lastexpress/game/action.h @@ -0,0 +1,135 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ACTION_H +#define LASTEXPRESS_ACTION_H + +#include "lastexpress/shared.h" + +#include "common/array.h" +#include "common/func.h" +#include "common/system.h" + +namespace LastExpress { + +#define DECLARE_ACTION(name) \ + SceneIndex action_##name(const SceneHotspot &hotspot) const + +#define ADD_ACTION(name) \ + _actions.push_back(new Functor1MemConst<const SceneHotspot &, SceneIndex, Action>(this, &Action::action_##name)); + +#define IMPLEMENT_ACTION(name) \ + SceneIndex Action::action_##name(const SceneHotspot &hotspot) const { \ + debugC(6, kLastExpressDebugLogic, "Hotspot action: " #name "%s", hotspot.toString().c_str()); + +class LastExpressEngine; +class SceneHotspot; + +class Action { +public: + Action(LastExpressEngine *engine); + ~Action(); + + // Hotspot action + SceneIndex processHotspot(const SceneHotspot &hotspot); + + // Cursor + CursorStyle getCursor(const SceneHotspot &hotspot) const; + + // Animation + void playAnimation(EventIndex index, bool debugMode = false) const; + + // Compartment action + bool handleOtherCompartment(ObjectIndex object, bool doPlaySound, bool doLoadScene) const; + +private: + typedef Common::Functor1<const SceneHotspot &, SceneIndex> ActionFunctor; + + LastExpressEngine* _engine; + Common::Array<ActionFunctor *> _actions; + + // Each action is of the form action_<name>(SceneHotspot *hotspot) + // - a pointer to each action is added to the _actions array + // - processHotspot simply calls the proper function given by the hotspot->action value + // + // Note: even though there are 44 actions, only 41 are used in processHotspot + + DECLARE_ACTION(inventory); + DECLARE_ACTION(savePoint); + DECLARE_ACTION(playSound); + DECLARE_ACTION(playMusic); + DECLARE_ACTION(knock); + DECLARE_ACTION(compartment); + DECLARE_ACTION(playSounds); + DECLARE_ACTION(playAnimation); + DECLARE_ACTION(openCloseObject); + DECLARE_ACTION(updateObjetLocation2); + DECLARE_ACTION(setItemLocation); + DECLARE_ACTION(knockNoSound); + DECLARE_ACTION(pickItem); + DECLARE_ACTION(dropItem); + DECLARE_ACTION(enterCompartment); + DECLARE_ACTION(getOutsideTrain); + DECLARE_ACTION(slip); + DECLARE_ACTION(getInsideTrain); + DECLARE_ACTION(climbUpTrain); + DECLARE_ACTION(climbDownTrain); + DECLARE_ACTION(jumpUpDownTrain); + DECLARE_ACTION(unbound); + DECLARE_ACTION(25); + DECLARE_ACTION(26); + DECLARE_ACTION(27); + DECLARE_ACTION(concertSitCough); + DECLARE_ACTION(29); + DECLARE_ACTION(catchBeetle); + DECLARE_ACTION(exitCompartment); + DECLARE_ACTION(32); + DECLARE_ACTION(useWhistle); + DECLARE_ACTION(openMatchBox); + DECLARE_ACTION(openBed); + DECLARE_ACTION(dialog); + DECLARE_ACTION(eggBox); + DECLARE_ACTION(39); + DECLARE_ACTION(bed); + DECLARE_ACTION(playMusicChapter); + DECLARE_ACTION(playMusicChapterSetupTrain); + DECLARE_ACTION(switchChapter); + DECLARE_ACTION(44); + + // Special dummy function + DECLARE_ACTION(dummy); + + // Helpers + void pickGreenJacket(bool process) const; + void pickScarf(bool process) const; + void pickCorpse(ObjectLocation bedPosition, bool process) const; + void dropCorpse(bool process) const; + + void playCompartmentSoundEvents(ObjectIndex object) const; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_ACTION_H diff --git a/engines/lastexpress/game/beetle.cpp b/engines/lastexpress/game/beetle.cpp new file mode 100644 index 0000000000..665edb79a5 --- /dev/null +++ b/engines/lastexpress/game/beetle.cpp @@ -0,0 +1,517 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/beetle.h" + +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +Beetle::Beetle(LastExpressEngine *engine) : _engine(engine), _data(NULL) {} + +Beetle::~Beetle() { + SAFE_DELETE(_data); + + // Free passed pointers + _engine = NULL; +} + +void Beetle::load() { + // Only load in chapter 2 & 3 + if (getProgress().chapter != kChapter2 && getProgress().chapter != kChapter3) + return; + + // Already loaded + if (_data) + return; + + // Do not load if beetle is in the wrong location + if (getInventory()->get(kItemBeetle)->location != kObjectLocation3) + return; + + /////////////////////// + // Load Beetle data + _data = new BeetleData(); + + // Load sequences + _data->sequences.push_back(loadSequence("BW000.seq")); // 0 + _data->sequences.push_back(loadSequence("BT000045.seq")); + _data->sequences.push_back(loadSequence("BT045000.seq")); + _data->sequences.push_back(loadSequence("BW045.seq")); + _data->sequences.push_back(loadSequence("BT045090.seq")); + _data->sequences.push_back(loadSequence("BT090045.seq")); // 5 + _data->sequences.push_back(loadSequence("BW090.seq")); + _data->sequences.push_back(loadSequence("BT090135.seq")); + _data->sequences.push_back(loadSequence("BT135090.seq")); + _data->sequences.push_back(loadSequence("BW135.seq")); + _data->sequences.push_back(loadSequence("BT135180.seq")); // 10 + _data->sequences.push_back(loadSequence("BT180135.seq")); + _data->sequences.push_back(loadSequence("BW180.seq")); + _data->sequences.push_back(loadSequence("BT180225.seq")); + _data->sequences.push_back(loadSequence("BT225180.seq")); + _data->sequences.push_back(loadSequence("BW225.seq")); // 15 + _data->sequences.push_back(loadSequence("BT225270.seq")); + _data->sequences.push_back(loadSequence("BT270225.seq")); + _data->sequences.push_back(loadSequence("BW270.seq")); + _data->sequences.push_back(loadSequence("BT270315.seq")); + _data->sequences.push_back(loadSequence("BT315270.seq")); // 20 + _data->sequences.push_back(loadSequence("BW315.seq")); + _data->sequences.push_back(loadSequence("BT315000.seq")); + _data->sequences.push_back(loadSequence("BT000315.seq")); + _data->sequences.push_back(loadSequence("BA135.seq")); + _data->sequences.push_back(loadSequence("BL045.seq")); // 25 + _data->sequences.push_back(loadSequence("BL000.seq")); + _data->sequences.push_back(loadSequence("BL315.seq")); + _data->sequences.push_back(loadSequence("BL180.seq")); + + // Init fields + _data->field_74 = 0; + + // Check that all sequences are loaded properly + _data->isLoaded = true; + for (int i = 0; i < (int)_data->sequences.size(); i++) { + if (!_data->sequences[i]->isLoaded()) { + _data->isLoaded = false; + break; + } + } + + _data->field_D9 = 10; + _data->coordOffset = 5; + _data->coordY = 178; + _data->currentSequence = 0; + _data->offset = 0; + _data->frame = NULL; + _data->field_D5 = 0; + _data->indexes[0] = 29; + _data->field_DD = 0; +} + +void Beetle::unload() { + // Remove sequences from display list + if (_data) + getScenes()->removeFromQueue(_data->frame); + + // Delete all loaded sequences + SAFE_DELETE(_data); +} + +bool Beetle::isLoaded() const { + if (!_data) + return false; + + return _data->isLoaded; +} + +bool Beetle::catchBeetle() { + if (!_data) + error("Beetle::catchBeetle: sequences have not been loaded!"); + + if (getInventory()->getSelectedItem() == kItemMatchBox + && getInventory()->hasItem(kItemMatch) + && ABS((int16)(getCoords().x - _data->coordX)) < 10 + && ABS((int16)(getCoords().y - _data->coordY)) < 10) { + return true; + } + + _data->field_D5 = 0; + move(); + + return false; +} + +bool Beetle::isCatchable() const { + if (!_data) + error("Beetle::isCatchable: sequences have not been loaded!"); + + return (_data->indexes[_data->offset] >= 30); +} + +void Beetle::update() { + if (!_data) + error("Beetle::update: sequences have not been loaded!"); + + if (!_data->isLoaded) + return; + + move(); + + if (_data->field_D5) + _data->field_D5--; + + if (_data->currentSequence && _data->indexes[_data->offset] != 29) { + drawUpdate(); + return; + } + + if (getInventory()->get(kItemBeetle)->location == kObjectLocation3) { + if ((!_data->field_DD && rnd(10) < 1) + || (_data->field_DD && rnd(30) < 1) + || rnd(100) < 1) { + + _data->field_DD++; + if (_data->field_DD > 3) + _data->field_DD = 0; + + updateData(24); + + _data->coordX = (int16)(rnd(250) + 190); + _data->coordOffset = (int16)(rnd(5) + 5); + + if (_data->field_D9 > 1) + _data->field_D9--; + + drawUpdate(); + } + } +} + +void Beetle::drawUpdate() { + if (!_data) + error("Beetle::drawUpdate: sequences have not been loaded!"); + + if (_data->frame != NULL) { + getScenes()->setCoordinates(_data->frame); + getScenes()->removeFromQueue(_data->frame); + } + + // Update current frame + switch (_data->indexes[_data->offset]) { + default: + _data->currentFrame += 10; + break; + + case 3: + case 6: + case 9: + case 12: + case 15: + case 18: + case 21: + case 24: + case 25: + case 26: + case 27: + case 28: + _data->currentFrame++; + break; + } + + // Update current sequence + if (_data->currentSequence->count() <= _data->currentFrame) { + switch (_data->indexes[_data->offset]) { + default: + _data->offset++; + _data->currentSequence = _data->sequences[_data->indexes[_data->offset]]; + break; + + case 3: + case 6: + case 9: + case 12: + case 15: + case 18: + case 21: + break; + } + + _data->currentFrame = 0; + if (_data->indexes[_data->offset] == 29) { + SAFE_DELETE(_data->frame); + _data->currentSequence = NULL; // pointer to existing sequence + return; + } + } + + // Update coordinates + switch (_data->indexes[_data->offset]) { + default: + break; + + case 0: + _data->coordY -= _data->coordOffset; + break; + + case 3: + _data->coordX += _data->coordOffset; + _data->coordY -= _data->coordOffset; + break; + + case 6: + _data->coordX += _data->coordOffset; + break; + + case 9: + _data->coordX += _data->coordOffset; + _data->coordY += _data->coordOffset; + break; + + case 12: + _data->coordY += _data->coordOffset; + break; + + case 15: + _data->coordX -= _data->coordOffset; + _data->coordY += _data->coordOffset; + break; + + case 18: + _data->coordX -= _data->coordOffset; + break; + + case 21: + _data->coordX -= _data->coordOffset; + _data->coordY -= _data->coordOffset; + break; + } + + // Update beetle data + int rnd = rnd(100); + if (_data->coordX < 165 || _data->coordX > 465) { + uint index = 0; + + if (rnd >= 30) { + if (rnd >= 70) + index = (_data->coordX < 165) ? 9 : 15; + else + index = (_data->coordX < 165) ? 6 : 18; + } else { + index = (_data->coordX < 165) ? 3 : 21; + } + + updateData(index); + } + + if (_data->coordY < 178) { + switch (_data->indexes[_data->offset]) { + default: + updateData(26); + break; + + case 3: + updateData(25); + break; + + case 21: + updateData(27); + break; + } + } + + if (_data->coordY > 354) { + switch (_data->indexes[_data->offset]) { + default: + break; + + case 9: + case 12: + case 15: + updateData(28); + break; + } + } + +#define INVERT_Y() \ + switch (_data->indexes[_data->offset]) { \ + default: \ + break; \ + case 24: \ + case 25: \ + case 26: \ + case 27: \ + case 28: \ + _data->coordY = -_data->coordY; \ + break; \ + } + + // Invert direction + INVERT_Y(); + + SequenceFrame *frame = new SequenceFrame(_data->currentSequence, (uint16)_data->currentFrame); + updateFrame(frame); + + INVERT_Y(); + + getScenes()->addToQueue(frame); + + SAFE_DELETE(_data->frame); + _data->frame = frame; +} + +void Beetle::move() { + if (!_data) + error("Beetle::move: sequences have not been loaded!"); + + if (_data->indexes[_data->offset] >= 24 && _data->indexes[_data->offset] <= 29) + return; + + if (_data->field_D5) + return; + + if (ABS((int)(getCoords().x - _data->coordX)) > 35) + return; + + if (ABS((int)(getCoords().y - _data->coordY)) > 35) + return; + + int32 deltaX = getCoords().x - _data->coordX; + int32 deltaY = -getCoords().y - _data->coordY; + uint32 index = 0; + + // FIXME: check code path + if (deltaX >= 0) { + if (deltaY > 0) { + if (100 * deltaY - 241 * deltaX <= 0) { + if (100 * deltaY - 41 * deltaX <= 0) + index = 18; + else + index = 15; + } else { + index = 12; + } + + goto update_data; + } + } + + if (deltaX < 0) { + + if (deltaY > 0) { + if (100 * deltaY + 241 * deltaX <= 0) { + if (100 * deltaY + 41 * deltaX <= 0) + index = 6; + else + index = 9; + } else { + index = 12; + } + + goto update_data; + } + + if (deltaY <= 0) { + if (100 * deltaY - 41 * deltaX <= 0) { + if (100 * deltaY - 241 * deltaX <= 0) + index = 0; + else + index = 3; + } else { + index = 6; + } + + goto update_data; + } + } + +update_data: + updateData(index); + + if (_data->coordOffset >= 15) { + _data->field_D5 = 0; + return; + } + + _data->coordOffset = _data->coordOffset + (int16)(4 * rnd(100)/100 + _data->field_D9); + _data->field_D5 = 0; +} + +// Update the beetle sequence to show the correct frames in the correct place +void Beetle::updateFrame(SequenceFrame *frame) const { + if (!_data) + error("Beetle::updateSequence: sequences have not been loaded!"); + + if (!frame) + return; + + // Update coordinates + if (_data->coordX > 0) + frame->getInfo()->xPos1 = (uint16)_data->coordX; + + if (_data->coordY > 0) + frame->getInfo()->yPos1 = (uint16)_data->coordY; +} + +void Beetle::updateData(uint32 index) { + if (!_data) + error("Beetle::updateData: sequences have not been loaded!"); + + if (!_data->isLoaded) + return; + + if (index == 25 || index == 26 || index == 27 || index == 28) { + _data->indexes[0] = index; + _data->indexes[1] = 29; + _data->offset = 0; + + _data->currentSequence = _data->sequences[index]; + _data->currentFrame = 0; + _data->index = index; + } else { + if (!_data->sequences[index]) + return; + + if (_data->index == index) + return; + + _data->offset = 0; + + // Special case for sequence 24 + if (index == 24) { + _data->indexes[0] = index; + _data->coordY = 178; + _data->index = _data->indexes[1]; + _data->indexes[1] = (_data->coordX >= 265) ? 15 : 9; + _data->currentFrame = 0; + _data->currentSequence = _data->sequences[index]; + } else { + if (index <= _data->index) { + for (uint32 i = _data->index - 1; i > index; ++_data->offset) { + _data->indexes[_data->offset] = i; + i -= 3; + } + } else { + for (uint32 i = _data->index + 1; i < index; ++_data->offset) { + _data->indexes[_data->offset] = i; + i += 3; + } + } + + _data->index = index; + _data->indexes[_data->offset] = index; + _data->currentFrame = 0; + _data->offset = 0; + _data->currentSequence = _data->sequences[_data->indexes[0]]; + } + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/beetle.h b/engines/lastexpress/game/beetle.h new file mode 100644 index 0000000000..ece5866b85 --- /dev/null +++ b/engines/lastexpress/game/beetle.h @@ -0,0 +1,118 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_BEETLE_H +#define LASTEXPRESS_BEETLE_H + +#include "lastexpress/data/sequence.h" + +#include "common/array.h" +#include "common/system.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Beetle { +public: + + Beetle(LastExpressEngine *engine); + ~Beetle(); + + void update(); + + void load(); + void unload(); + + bool isLoaded() const; + + bool catchBeetle(); + bool isCatchable() const; + +private: + struct BeetleData { + Common::Array<Sequence *> sequences; + + uint32 field_74; + Sequence *currentSequence; + uint32 currentFrame; + uint32 index; + int16 coordOffset; + int16 field_86; + + int16 coordX; + int16 coordY; + + uint32 indexes[16]; + + uint32 offset; + SequenceFrame* frame; + bool isLoaded; + uint32 field_D5; + uint32 field_D9; + uint32 field_DD; + + BeetleData() { + field_74 = 0; + currentSequence = NULL; + currentFrame = 0; + index = 0; + coordOffset = 0; + + field_86 = 0; + + coordX = 0; + coordY = 0; + + memset(indexes, 0, sizeof(indexes)); + offset = 0; + + frame = NULL; + isLoaded = false; + field_D5 = 0; + field_D9 = 0; + field_DD = 0; + } + + ~BeetleData() { + for (int i = 0; i < (int)sequences.size(); i++) + if (sequences[i]) + delete sequences[i]; + } + }; + + LastExpressEngine* _engine; + + BeetleData *_data; + + void move(); + void updateFrame(SequenceFrame *frame) const; + void updateData(uint32 index); + void drawUpdate(); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_BEETLE_H diff --git a/engines/lastexpress/game/entities.cpp b/engines/lastexpress/game/entities.cpp new file mode 100644 index 0000000000..ef21fee0ae --- /dev/null +++ b/engines/lastexpress/game/entities.cpp @@ -0,0 +1,2731 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/entities.h" + +// Data +#include "lastexpress/data/scene.h" +#include "lastexpress/data/sequence.h" + +// Entities +#include "lastexpress/entities/entity.h" + +#include "lastexpress/entities/abbot.h" +#include "lastexpress/entities/alexei.h" +#include "lastexpress/entities/alouan.h" +#include "lastexpress/entities/anna.h" +#include "lastexpress/entities/august.h" +#include "lastexpress/entities/boutarel.h" +#include "lastexpress/entities/chapters.h" +#include "lastexpress/entities/cooks.h" +#include "lastexpress/entities/coudert.h" +#include "lastexpress/entities/entity39.h" +#include "lastexpress/entities/francois.h" +#include "lastexpress/entities/gendarmes.h" +#include "lastexpress/entities/hadija.h" +#include "lastexpress/entities/ivo.h" +#include "lastexpress/entities/kahina.h" +#include "lastexpress/entities/kronos.h" +#include "lastexpress/entities/mahmud.h" +#include "lastexpress/entities/max.h" +#include "lastexpress/entities/mertens.h" +#include "lastexpress/entities/milos.h" +#include "lastexpress/entities/mmeboutarel.h" +#include "lastexpress/entities/pascale.h" +#include "lastexpress/entities/rebecca.h" +#include "lastexpress/entities/salko.h" +#include "lastexpress/entities/servers0.h" +#include "lastexpress/entities/servers1.h" +#include "lastexpress/entities/sophie.h" +#include "lastexpress/entities/tables.h" +#include "lastexpress/entities/tatiana.h" +#include "lastexpress/entities/train.h" +#include "lastexpress/entities/vassili.h" +#include "lastexpress/entities/verges.h" +#include "lastexpress/entities/vesna.h" +#include "lastexpress/entities/yasmin.h" + +// Game +#include "lastexpress/game/logic.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +#define STORE_VALUE(data) ((uint)1 << (uint)data) + +static const EntityPosition objectsPosition[8] = {kPosition_8200, kPosition_7500, + kPosition_6470, kPosition_5790, + kPosition_4840, kPosition_4070, + kPosition_3050, kPosition_2740}; + +static const EntityPosition entityPositions[41] = { + kPositionNone, kPosition_851, kPosition_1430, kPosition_2110, kPositionNone, + kPosition_2410, kPosition_2980, kPosition_3450, kPosition_3760, kPosition_4100, + kPosition_4680, kPosition_5140, kPosition_5440, kPosition_5810, kPosition_6410, + kPosition_6850, kPosition_7160, kPosition_7510, kPosition_8514, kPositionNone, + kPositionNone, kPositionNone, kPosition_2086, kPosition_2690, kPositionNone, + kPosition_3110, kPosition_3390, kPosition_3890, kPosition_4460, kPosition_4770, + kPosition_5090, kPosition_5610, kPosition_6160, kPosition_6460, kPosition_6800, + kPosition_7320, kPosition_7870, kPosition_8160, kPosition_8500, kPosition_9020, + kPosition_9269}; + +#define ADD_ENTITY(class) \ + _entities.push_back(new class(engine)); + +#define COMPUTE_SEQUENCE_NAME(sequenceTo, sequenceFrom) { \ + sequenceTo = sequenceFrom; \ + for (int seqIdx = 0; seqIdx < 7; seqIdx++) \ + sequenceTo.deleteLastChar(); \ + if (isInsideTrainCar(entityIndex, kCarGreenSleeping) || isInsideTrainCar(entityIndex, kCarGreenSleeping)) { \ + if (data->car < getData(kEntityPlayer)->car || (data->car == getData(kEntityPlayer)->car && data->entityPosition < getData(kEntityPlayer)->entityPosition)) \ + sequenceTo += "R.SEQ"; \ + else \ + sequenceTo += "F.SEQ"; \ + } else { \ + sequenceTo += ".SEQ"; \ + } \ +} + +#define TRY_LOAD_SEQUENCE(sequence, name, name1, name2) { \ + if (data->car == getData(kEntityPlayer)->car) \ + sequence = loadSequence1(name1, field30); \ + if (sequence) { \ + name = name1; \ + } else { \ + if (name2 != "") \ + sequence = loadSequence1(name2, field30); \ + name = (sequence ? name2 : ""); \ + } \ +} + +////////////////////////////////////////////////////////////////////////// +// Entities +////////////////////////////////////////////////////////////////////////// +Entities::Entities(LastExpressEngine *engine) : _engine(engine) { + _header = new EntityData(); + + _entities.push_back(NULL); // Header + ADD_ENTITY(Anna); + ADD_ENTITY(August); + ADD_ENTITY(Mertens); + ADD_ENTITY(Coudert); + ADD_ENTITY(Pascale); + ADD_ENTITY(Servers0); + ADD_ENTITY(Servers1); + ADD_ENTITY(Cooks); + ADD_ENTITY(Verges); + ADD_ENTITY(Tatiana); + ADD_ENTITY(Vassili); + ADD_ENTITY(Alexei); + ADD_ENTITY(Abbot); + ADD_ENTITY(Milos); + ADD_ENTITY(Vesna); + ADD_ENTITY(Ivo); + ADD_ENTITY(Salko); + ADD_ENTITY(Kronos); + ADD_ENTITY(Kahina); + ADD_ENTITY(Francois); + ADD_ENTITY(MmeBoutarel); + ADD_ENTITY(Boutarel); + ADD_ENTITY(Rebecca); + ADD_ENTITY(Sophie); + ADD_ENTITY(Mahmud); + ADD_ENTITY(Yasmin); + ADD_ENTITY(Hadija); + ADD_ENTITY(Alouan); + ADD_ENTITY(Gendarmes); + ADD_ENTITY(Max); + ADD_ENTITY(Chapters); + ADD_ENTITY(Train); + + // Special case for tables + _entities.push_back(new Tables(engine, kEntityTables0)); + _entities.push_back(new Tables(engine, kEntityTables1)); + _entities.push_back(new Tables(engine, kEntityTables2)); + _entities.push_back(new Tables(engine, kEntityTables3)); + _entities.push_back(new Tables(engine, kEntityTables4)); + _entities.push_back(new Tables(engine, kEntityTables5)); + + ADD_ENTITY(Entity39); + + // Init compartments & positions + memset(&_compartments, 0, sizeof(_compartments)); + memset(&_compartments1, 0, sizeof(_compartments1)); + memset(&_positions, 0, sizeof(_positions)); +} + +Entities::~Entities() { + delete _header; + + for (int i = 0; i < (int)_entities.size(); i++) + delete _entities[i]; + + // Zero passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Accessors +////////////////////////////////////////////////////////////////////////// +Entity *Entities::get(EntityIndex entity) { + assert((uint)entity < _entities.size()); + + if (entity == kEntityPlayer) + error("Cannot get entity for index == 0!"); + + return _entities[entity]; +} + +EntityData::EntityCallData *Entities::getData(EntityIndex entity) const { + assert((uint)entity < _entities.size()); + + if (entity == kEntityPlayer) + return _header->getCallData(); + + return _entities[entity]->getData(); +} + +int Entities::getPosition(CarIndex car, Position position) { + int index = 100 * car + position; + + if (car < 0 || car > 10) + error("Entities::getPosition: trying to access an invalid car (was: %d, valid:0-9)", car); + + if (position < 0 || position > 100) + error("Entities::getPosition: trying to access an invalid position (was: %d, valid:0-100)", position); + + return _positions[index]; +} + +int Entities::getCompartments(int index) { + if (index >= _compartmentsCount) + error("Entities::getCompartments: trying to access an invalid compartment (was: %d, valid:0-15)", index); + + return _compartments[index]; +} + +int Entities::getCompartments1(int index) { + if (index >= _compartmentsCount) + error("Entities::getCompartments: trying to access an invalid compartment (was: %d, valid:0-15)", index); + + return _compartments1[index]; +} + +////////////////////////////////////////////////////////////////////////// +// Savegame +////////////////////////////////////////////////////////////////////////// +void Entities::saveLoadWithSerializer(Common::Serializer &ser) { + _header->saveLoadWithSerializer(ser); + for (uint i = 1; i < _entities.size(); i++) + _entities[i]->saveLoadWithSerializer(ser); +} + +////////////////////////////////////////////////////////////////////////// +// Setup +////////////////////////////////////////////////////////////////////////// +void Entities::setup(bool isFirstChapter, EntityIndex entityIndex) { + setupChapter(isFirstChapter ? kChapter1 : kChapterAll); + + bool flag_4 = false; + + if (!isFirstChapter) { + getFlags()->flag_4 = false; + + if (entityIndex) { + getSavePoints()->call(kEntityPlayer, entityIndex, kActionNone); + flag_4 = getFlags()->flag_4; + } + } + + getFlags()->flag_4 = flag_4; + if (!getFlags()->flag_4) + getScenes()->loadScene(getState()->scene); +} + +void Entities::setupChapter(ChapterIndex chapter) { + if (chapter) { + // Reset current call, inventory item & draw sequences + for (uint i = 1; i < _entities.size(); i++) { + getData((EntityIndex)i)->currentCall = 0; + getData((EntityIndex)i)->inventoryItem = kItemNone; + + clearSequences((EntityIndex)i); + } + + // Init compartments & positions + memset(&_compartments, 0, sizeof(_compartments)); + memset(&_compartments1, 0, sizeof(_compartments1)); + memset(&_positions, 0, sizeof(_positions)); + + getSound()->resetQueue(SoundManager::kSoundType13); + } + + // we skip the header when doing entity setup + for (uint i = 1; i < _entities.size(); i++) { + // Special case of chapters (prevents infinite loop as we will be called from Chapters functions when changing chapters) + if (i == kEntityChapters && chapter >= 2) + continue; + + _entities[i]->setup(chapter); + } +} + +void Entities::reset() { + // Reset header + delete _header; + _header = new EntityData(); + + for (uint i = 1; i < _entities.size(); i++) + resetSequences((EntityIndex)i); + + getScenes()->resetDoorsAndClock(); +} + +////////////////////////////////////////////////////////////////////////// +// State & Sequences +////////////////////////////////////////////////////////////////////////// + +EntityIndex Entities::canInteractWith(const Common::Point &point) const { + if (!getFlags()->isGameRunning) + return kEntityPlayer; + + EntityIndex index = kEntityPlayer; + int location = 10000; + + // Check if there is an entity we can interact with + for (uint i = 0; i < _entities.size(); i++) { + + // Skip entities with no current frame + if (!getData((EntityIndex)i)->frame) + continue; + + FrameInfo *info = getData((EntityIndex)i)->frame->getInfo(); + + // Check the hotspot + if (info->hotspot.contains(point)) { + + // If closer to us, update with its values + if (location > info->location) { + location = info->location; + index = (EntityIndex)i; + } + } + } + + // Check if we found an entity + if (!index) + return kEntityPlayer; + + // Check that there is an item to interact with + if (!getData(index)->inventoryItem) + return kEntityPlayer; + + return index; +} + +void Entities::resetState(EntityIndex entityIndex) { + getData(entityIndex)->currentCall = 0; + getData(entityIndex)->inventoryItem = kItemNone; + + if (getSound()->isBuffered(entityIndex)) + getSound()->removeFromQueue(entityIndex); + + clearSequences(entityIndex); + + if (entityIndex == kEntity39) + entityIndex = kEntityPlayer; + + if (entityIndex > kEntityChapters) + return; + + // reset compartments and positions for this entity + for (int i = 0; i < _positionsCount; i++) + _positions[i] &= ~STORE_VALUE(entityIndex); + + for (int i = 0; i < _compartmentsCount; i++) { + _compartments[i] &= ~STORE_VALUE(entityIndex); + _compartments1[i] &= ~STORE_VALUE(entityIndex); + } + + getLogic()->updateCursor(); +} + + +void Entities::updateFields() const { + if (!getFlags()->isGameRunning) + return; + + for (int i = 0; i < (int)_entities.size(); i++) { + + if (!getSavePoints()->getCallback((EntityIndex)i)) + continue; + + EntityData::EntityCallData *data = getData((EntityIndex)i); + int positionDelta = data->field_4A3 * 10; + switch (data->direction) { + default: + break; + + case kDirectionUp: + if (data->entityPosition >= 10000 - positionDelta) + data->entityPosition = (EntityPosition)(data->entityPosition + positionDelta); + break; + + case kDirectionDown: + if (data->entityPosition > positionDelta) + data->entityPosition = (EntityPosition)(data->entityPosition - positionDelta); + break; + + case kDirectionLeft: + data->currentFrame++; + break; + + case kDirectionRight: + data->field_4A1 += 9; + break; + + case kDirectionSwitch: + if (data->directionSwitch == kDirectionRight) + data->field_4A1 += 9; + break; + + } + } +} + +void Entities::updateFrame(EntityIndex entityIndex) const { + Sequence *sequence = NULL; + int16 *currentFrame = NULL; + bool found = false; + + if (getData(entityIndex)->direction == kDirectionSwitch) { + sequence = getData(entityIndex)->sequence2; + currentFrame = &getData(entityIndex)->currentFrame2; + } else { + sequence = getData(entityIndex)->sequence; + currentFrame = &getData(entityIndex)->currentFrame; + } + + if (!sequence) + return; + + // Save current values + int16 oldFrame = *currentFrame; + int16 field_4A1 = getData(entityIndex)->field_4A1; + + do { + // Check we do not get past the end + if (*currentFrame >= (int)sequence->count() - 1) + break; + + // Get the proper frame + FrameInfo *info = sequence->getFrameInfo((uint16)*currentFrame); + + if (info->field_33 & 8) { + found = true; + } else { + if (info->soundAction == 35) + found = true; + + getData(entityIndex)->field_4A1 += info->field_30; + + // Progress to the next frame + ++*currentFrame; + } + } while (!found); + + // Restore old values + if (!found) { + *currentFrame = oldFrame; + getData(entityIndex)->field_4A1 = field_4A1; + } +} + +void Entities::updateSequences() const { + if (!getFlags()->isGameRunning) + return; + + // Update the train clock & doors + getScenes()->updateDoorsAndClock(); + + ////////////////////////////////////////////////////////////////////////// + // First pass: Drawing + ////////////////////////////////////////////////////////////////////////// + for (uint i = 1; i < _entities.size(); i++) { + EntityIndex entityIndex = (EntityIndex)i; + + if (!getSavePoints()->getCallback(entityIndex)) + continue; + + EntityData::EntityCallData *data = getData(entityIndex); + + if (data->frame) { + getScenes()->removeFromQueue(data->frame); + SAFE_DELETE(data->frame); + } + + if (data->frame1) { + getScenes()->removeFromQueue(data->frame1); + SAFE_DELETE(data->frame1); + } + + if (data->direction == kDirectionSwitch) { + + // Clear sequence 2 + if (data->sequence) + SAFE_DELETE(data->sequence); + + // Replace by sequence 3 if available + if (data->sequence2) { + data->sequence = data->sequence2; + data->sequenceName = data->sequenceName2; + + data->sequence2 = NULL; + data->sequenceName2 = ""; + } + + data->direction = data->directionSwitch; + data->currentFrame = -1; + data->field_49B = 0; + } + + // Draw sequences + drawSequences(entityIndex, data->direction, false); + } + + ////////////////////////////////////////////////////////////////////////// + // Second pass: Load sequences for next pass + ////////////////////////////////////////////////////////////////////////// + for (uint i = 1; i < _entities.size(); i++) { + EntityIndex entityIndex = (EntityIndex)i; + + if (!getSavePoints()->getCallback(entityIndex)) + continue; + + EntityData::EntityCallData *data = getData(entityIndex); + byte field30 = (data->direction == kDirectionLeft ? entityIndex + 35 : 15); + + if (data->sequenceName != "" && !data->sequence) { + data->sequence = loadSequence1(data->sequenceName, field30); + + // If sequence 2 was loaded correctly, remove the copied name + // otherwise, compute new name + if (data->sequence) { + data->sequenceNameCopy = ""; + } else { + Common::String sequenceName; + + // Left and down directions + if (data->direction == kDirectionLeft || data->direction == kDirectionRight) { + COMPUTE_SEQUENCE_NAME(sequenceName, data->sequenceName); + + // Try loading the sequence + data->sequence = loadSequence1(sequenceName, field30); + } + + // Update sequence names + data->sequenceNameCopy = (data->sequence ? "" : data->sequenceName); + data->sequenceName = (data->sequence ? sequenceName : ""); + } + } + + // Update sequence 3 + if (data->sequenceName2 != "" && !data->sequence2) { + + if (data->car == getData(kEntityPlayer)->car) + data->sequence2 = loadSequence1(data->sequenceName2, field30); + + if (!data->sequence2) { + Common::String sequenceName; + + // Left and down directions + if (data->directionSwitch == kDirectionLeft || data->directionSwitch == kDirectionRight) { + COMPUTE_SEQUENCE_NAME(sequenceName, data->sequenceName2); + + // Try loading the sequence + data->sequence2 = loadSequence1(sequenceName, field30); + } + + // Update sequence names + data->sequenceName2 = (data->sequence2 ? sequenceName : ""); + } + } + } +} + +void Entities::resetSequences(EntityIndex entityIndex) const { + + // Reset direction + if (getData(entityIndex)->direction == kDirectionSwitch) { + getData(entityIndex)->direction = getData(entityIndex)->directionSwitch; + getData(entityIndex)->field_49B = 0; + getData(entityIndex)->currentFrame = -1; + } + + // FIXME: in the original engine, the sequence pointers might just be copies, + // make sure we free the associated memory at some point + getData(entityIndex)->frame = NULL; + getData(entityIndex)->frame1 = NULL; + + SAFE_DELETE(getData(entityIndex)->sequence); + SAFE_DELETE(getData(entityIndex)->sequence2); + SAFE_DELETE(getData(entityIndex)->sequence3); + + getData(entityIndex)->field_4A9 = false; + getData(entityIndex)->field_4AA = false; + + strcpy((char*)&getData(entityIndex)->sequenceNameCopy, ""); + strcpy((char*)&getData(entityIndex)->sequenceName, ""); + strcpy((char*)&getData(entityIndex)->sequenceName2, ""); + + // Original engine resets flag to decompress data on the fly (we don't need to do that) +} + +////////////////////////////////////////////////////////////////////////// +// Callbacks +////////////////////////////////////////////////////////////////////////// +void Entities::updateCallbacks() { + if (!getFlags()->isGameRunning) + return; + + getFlags()->flag_entities_0 = false; + + if (getFlags()->flag_entities_1) { + executeCallbacks(); + getFlags()->flag_entities_0 = true; + } else { + getFlags()->flag_entities_1 = true; + executeCallbacks(); + getFlags()->flag_entities_1 = false; + } +} + +void Entities::executeCallbacks() { + for (uint i = 1; i < _entities.size(); i++) { + if (getFlags()->flag_entities_0) + break; + + if (getSavePoints()->getCallback((EntityIndex)i)) + processEntity((EntityIndex)i); + } + + if (getFlags()->flag_entities_0) + return; + + bool processed = true; + do { + processed = true; + for (int i = 1; i < (int)_entities.size(); i++) { + if (getFlags()->flag_entities_0) + break; + + if (getSavePoints()->getCallback((EntityIndex)i)) { + if (getData((EntityIndex)i)->doProcessEntity) { + processed = false; + processEntity((EntityIndex)i); + } + } + } + } while (!processed); +} + +////////////////////////////////////////////////////////////////////////// +// Processing +////////////////////////////////////////////////////////////////////////// +#define INCREMENT_DIRECTION_COUNTER() { \ + data->doProcessEntity = false; \ + if (data->direction == kDirectionRight || (data->direction == kDirectionSwitch && data->directionSwitch == kDirectionRight)) \ + ++data->field_4A1; \ + } + +void Entities::processEntity(EntityIndex entityIndex) { + EntityData::EntityCallData *data = getData(entityIndex); + bool keepPreviousFrame = false; + + data->doProcessEntity = false; + + if (getData(kEntityPlayer)->car != data->car && data->direction != kDirectionRight && data->direction != kDirectionSwitch) { + + if (data->position) { + updatePositionExit(entityIndex, data->car2, data->position); + data->car2 = kCarNone; + data->position = 0; + } + + getScenes()->removeAndRedraw(&data->frame, false); + getScenes()->removeAndRedraw(&data->frame1, false); + + INCREMENT_DIRECTION_COUNTER(); + return; + } + + if (data->frame1) { + getScenes()->removeAndRedraw(&data->frame1, false); + + if (data->frame && data->frame->getInfo()->subType != kFrameType3) { + data->frame->getInfo()->subType = kFrameTypeNone; + getScenes()->setFlagDrawSequences(); + } + } + + SAFE_DELETE(data->sequence3); + + if (!data->frame || !data->direction) { + if (!data->sequence) +label_nosequence: + drawSequences(entityIndex, data->direction, true); + + data->doProcessEntity = false; + computeCurrentFrame(entityIndex); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + + if (data->sequence && data->currentFrame != -1 && data->currentFrame <= (int16)(data->sequence->count() - 1)) { + processFrame(entityIndex, false, true); + + if (!getFlags()->flag_entities_0 && !data->doProcessEntity) { + INCREMENT_DIRECTION_COUNTER(); + return; + } + } else { + if (data->direction == kDirectionRight && data->field_4A1 > 100) { + getSavePoints()->push(kEntityPlayer, entityIndex, kActionExitCompartment); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + if (data->position) { + updatePositionExit(entityIndex, data->car2, data->position); + data->car2 = kCarNone; + data->position = 0; + } + + INCREMENT_DIRECTION_COUNTER(); + } + return; + } + + if (!data->sequence) + goto label_nosequence; + + if (data->frame->getInfo()->field_30 > data->field_49B + 1 || (data->direction == kDirectionLeft && data->sequence->count() == 1)) { + ++data->field_49B; + INCREMENT_DIRECTION_COUNTER(); + return; + } + + if (data->frame->getInfo()->field_30 > data->field_49B && !data->frame->getInfo()->keepPreviousFrame) { + ++data->field_49B; + INCREMENT_DIRECTION_COUNTER(); + return; + } + + if (data->frame->getInfo()->keepPreviousFrame == 1) + keepPreviousFrame = true; + + // Increment current frame + ++data->currentFrame; + + if (data->currentFrame > (int16)(data->sequence->count() - 1) || (data->field_4A9 && checkSequenceFromPosition(entityIndex))) { + + if (data->direction == kDirectionLeft) { + data->currentFrame = 0; + } else { + keepPreviousFrame = true; + drawNextSequence(entityIndex); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + + if (!data->sequence2) { + updateEntityPosition(entityIndex); + data->doProcessEntity = false; + return; + } + + copySequenceData(entityIndex); + } + + } + + processFrame(entityIndex, keepPreviousFrame, false); + + if (!getFlags()->flag_entities_0 && !data->doProcessEntity) + INCREMENT_DIRECTION_COUNTER(); +} + +void Entities::computeCurrentFrame(EntityIndex entityIndex) const { + EntityData::EntityCallData *data = getData(entityIndex); + int16 originalCurrentFrame = data->currentFrame; + + if (!data->sequence) { + data->currentFrame = -1; + return; + } + + switch (data->direction) { + default: + break; + + case kDirectionNone: + case kDirectionSwitch: + data->currentFrame = -1; + break; + + case kDirectionUp: + case kDirectionDown: { + Scene *scene = getScenes()->get(getState()->scene); + + if (scene->position > 40) + break; + + switch (scene->position) { + default: + case 4: + case 19: + case 20: + case 21: + case 24: + break; + + case 1: + case 18: + case 22: + case 40: + data->currentFrame = getCurrentFrame(entityIndex, data->sequence, kPositionNone, false); + break; + + case 2: + case 3: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + if (data->field_4A9) { + if (getData(kEntityPlayer)->entityPosition >= data->entityPosition) { + data->currentFrame = -1; + } else { + data->currentFrame = getCurrentFrame(entityIndex, data->sequence, getEntityPositionFromCurrentPosition(), true); + + if (data->currentFrame != -1 && originalCurrentFrame == data->currentFrame) + if (data->currentFrame < (int)(data->sequence->count() - 2)) + data->currentFrame += 2; + } + } else { + data->currentFrame = getCurrentFrame(entityIndex, data->sequence, kPositionNone, false); + } + break; + + case 23: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + case 38: + case 39: + if (data->field_4A9) { + if (getData(kEntityPlayer)->entityPosition <= data->entityPosition) { + data->currentFrame = -1; + } else { + data->currentFrame = getCurrentFrame(entityIndex, data->sequence, getEntityPositionFromCurrentPosition(), true); + + if (data->currentFrame != -1 && originalCurrentFrame == data->currentFrame) + if (data->currentFrame < (int)(data->sequence->count() - 2)) + data->currentFrame += 2; + } + } else { + data->currentFrame = getCurrentFrame(entityIndex, data->sequence, kPositionNone, false); + } + break; + } + + } + break; + + + case kDirectionLeft: + if (data->currentFrame == -1 || data->currentFrame >= (int32)data->sequence->count()) { + data->currentFrame = 0; + data->field_49B = 0; + } + break; + + case kDirectionRight: + bool found = false; + bool flag = false; + uint16 frameIndex = 0; + byte field30 = 0; + + int16 currentFrameCopy = (!data->currentFrame && !data->field_4A1) ? -1 : data->currentFrame; + + // Process frames + do { + if (frameIndex >= data->sequence->count()) + break; + + FrameInfo *info = data->sequence->getFrameInfo(frameIndex); + + if (field30 + info->field_30 >= data->field_4A1) { + found = true; + break; + } + + if (field30 > data->field_4A1 - 10) { + if (info->soundAction) + getSound()->playSoundEvent(entityIndex, info->soundAction, (field30 <= data->field_4A1 - info->field_31) ? 0 : (byte)(field30 + info->field_31 - data->field_4A1)); + } + + field30 += info->field_30; + + if (info->field_33 & 4) + flag = true; + + if (info->field_33 & 2) { + flag = false; + + getSavePoints()->push(kEntityPlayer, entityIndex, kAction10); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + if (info->field_33 & 16) { + getSavePoints()->push(kEntityPlayer, entityIndex, kAction4); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + frameIndex++; + + } while (!found); + + if (found) { + + if (flag) { + bool found2 = false; + + do { + if (frameIndex >= data->sequence->count()) + break; + + FrameInfo *info = data->sequence->getFrameInfo(frameIndex); + if (info->field_33 & 2) { + found2 = true; + + getSavePoints()->push(kEntityPlayer, entityIndex, kAction10); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + + } else { + data->field_4A1 += info->field_30; + + byte soundAction = data->sequence->getFrameInfo((uint16)data->currentFrame)->soundAction; + if (soundAction) + getSound()->playSoundEvent(entityIndex, soundAction); + + ++frameIndex; + } + + } while (!found2); + + if (found2) { + data->currentFrame = frameIndex; + data->field_49B = 0; + + byte soundAction = data->sequence->getFrameInfo((uint16)data->currentFrame)->soundAction; + byte field31 = data->sequence->getFrameInfo((uint16)data->currentFrame)->field_31; + if (soundAction && data->currentFrame != currentFrameCopy) + getSound()->playSoundEvent(entityIndex, soundAction, field31); + + } else { + data->currentFrame = (int16)(data->sequence->count() - 1); + data->field_49B = data->sequence->getFrameInfo((uint16)data->currentFrame)->field_30; + } + + } else { + + data->currentFrame = frameIndex; + data->field_49B = data->field_4A1 - field30; + + byte soundAction = data->sequence->getFrameInfo((uint16)data->currentFrame)->soundAction; + byte field31 = data->sequence->getFrameInfo((uint16)data->currentFrame)->field_31; + if (soundAction && data->currentFrame != currentFrameCopy) + getSound()->playSoundEvent(entityIndex, soundAction, field31 <= data->field_49B ? 0 : (byte)(field31 - data->field_49B)); + } + } else { + data->currentFrame = (int16)(data->sequence->count() - 1); + data->field_49B = data->sequence->getFrameInfo((uint16)data->currentFrame)->field_30; + + getSavePoints()->push(kEntityPlayer, entityIndex, kActionExitCompartment); + getSavePoints()->process(); + } + break; + } +} + +int16 Entities::getCurrentFrame(EntityIndex entity, Sequence *sequence, EntityPosition position, bool doProcessing) const { + EntityData::EntityCallData *data = getData(entity); + + EntityPosition firstFramePosition = sequence->getFrameInfo(0)->entityPosition; + EntityPosition lastFramePosition = sequence->getFrameInfo(sequence->count() - 1)->entityPosition; + + bool isGoingForward = (firstFramePosition < lastFramePosition); + + if (!doProcessing) { + if (!isGoingForward) { + if (data->field_4A3 + firstFramePosition < data->entityPosition || lastFramePosition - data->field_4A3 > data->entityPosition) + return -1; + } else { + if (firstFramePosition - data->field_4A3 > data->entityPosition || lastFramePosition + data->field_4A3 < data->entityPosition) + return -1; + } + } + + if (sequence->count() == 0) + return 0; + + // Search for the correct frame + // TODO: looks slightly like some sort of binary search + uint16 frame = 0; + uint16 numFrames = sequence->count() - 1; + + for (;;) { + uint16 currentFrame = (frame + numFrames) / 2; + + if (position + sequence->getFrameInfo(currentFrame)->entityPosition <= data->entityPosition) { + if (!isGoingForward) + numFrames = (frame + numFrames) / 2; + else + frame = (frame + numFrames) / 2; + } else { + if (isGoingForward) + numFrames = (frame + numFrames) / 2; + else + frame = (frame + numFrames) / 2; + } + + if (numFrames - frame == 1) { + uint16 lastFramePos = ABS(position - (sequence->getFrameInfo(numFrames)->entityPosition + data->entityPosition)); + uint16 framePosition = ABS(position - (sequence->getFrameInfo(frame)->entityPosition + data->entityPosition)); + + return (framePosition > lastFramePos) ? numFrames : frame; + } + + if (numFrames <= frame) + return currentFrame; + } +} + +void Entities::processFrame(EntityIndex entityIndex, bool keepPreviousFrame, bool dontPlaySound) { + EntityData::EntityCallData *data = getData(entityIndex); + + // Set frame to be drawn again + if (data->frame && keepPreviousFrame) { + if (data->frame->getInfo()->subType != kFrameType3) + data->frame->getInfo()->subType = kFrameType2; + + getScenes()->setFlagDrawSequences(); + } + + // Remove old frame from queue + if (data->frame && !keepPreviousFrame) + getScenes()->removeFromQueue(data->frame); + + // Stop if nothing else to draw + if (data->currentFrame < 0) + return; + + if (data->currentFrame > (int)data->sequence->count()) + return; + + // Get new frame info + FrameInfo *info = data->sequence->getFrameInfo((uint16)data->currentFrame); + + if (data->frame && data->frame->getInfo()->subType != kFrameType3) + if (!info->field_2E || keepPreviousFrame) + getScenes()->setCoordinates(data->frame); + + // Update position + if (info->entityPosition) { + data->entityPosition = info->entityPosition; + if (data->field_4A9) + data->entityPosition = (EntityPosition)(data->entityPosition + getEntityPositionFromCurrentPosition()); + } + + info->location = entityIndex + ABS(getData(entityIndex)->entityPosition - getData(kEntityPlayer)->entityPosition); + + if (info->subType != kFrameType3) { + info->subType = kFrameType1; + + if (!keepPreviousFrame) + info->subType = kFrameTypeNone; + } + + if (info->field_33 & 1) + getSavePoints()->push(kEntityPlayer, entityIndex, kActionExcuseMeCath); + + if (info->field_33 & 2) { + getSavePoints()->push(kEntityPlayer, entityIndex, kAction10); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + if (info->field_33 & 16) { + getSavePoints()->push(kEntityPlayer, entityIndex, kAction4); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + if (data->position) { + updatePositionExit(entityIndex, data->car2, data->position); + data->car2 = kCarNone; + data->position = 0; + } + + if (info->position) { + data->car2 = data->car; + data->position = info->position; + updatePositionEnter(entityIndex, data->car2, data->position); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + if (info->soundAction && !dontPlaySound) + getSound()->playSoundEvent(entityIndex, info->soundAction, info->field_31); + + // Add the new frame to the queue + SequenceFrame *frame = new SequenceFrame(data->sequence, (uint16)data->currentFrame); + getScenes()->addToQueue(frame); + + // Keep previous frame if needed and store the new frame + if (keepPreviousFrame) + data->frame1 = data->frame; + + data->frame = frame; + + if (!dontPlaySound) + data->field_49B = keepPreviousFrame ? 0 : 1; +} + +void Entities::drawNextSequence(EntityIndex entityIndex) const { + EntityData::EntityCallData *data = getData(entityIndex); + + if (data->direction == kDirectionRight) { + getSavePoints()->push(kEntityPlayer, entityIndex, kActionExitCompartment); + getSavePoints()->process(); + + if (getFlags()->flag_entities_0 || data->doProcessEntity) + return; + } + + if (!isDirectionUpOrDown(entityIndex)) + return; + + if (data->sequence2) + return; + + if (!getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingAtDoors)) + return; + + if (getData(kEntityPlayer)->car != data->car) + return; + + if (!data->field_4A9 || isWalkingOppositeToPlayer(entityIndex)) { + if (!data->field_4A9 && isWalkingOppositeToPlayer(entityIndex)) { + data->entityPosition = kPosition_2088; + + if (data->direction != kDirectionUp) + data->entityPosition = kPosition_8512; + + drawSequences(entityIndex, data->direction, true); + } + } else { + data->entityPosition = kPosition_8514; + + if (data->direction != kDirectionUp) + data->entityPosition = kPosition_2086; + + drawSequences(entityIndex, data->direction, true); + } +} + +void Entities::updateEntityPosition(EntityIndex entityIndex) const { + EntityData::EntityCallData *data = getData(entityIndex); + + getScenes()->removeAndRedraw(&data->frame, false); + + SAFE_DELETE(data->frame1); + data->field_49B = 0; + + if (isDirectionUpOrDown(entityIndex) + && (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) || getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown)) + && data->car == getData(kEntityPlayer)->car) { + + if (isWalkingOppositeToPlayer(entityIndex)) { + data->entityPosition = getData(kEntityPlayer)->entityPosition; + } else if (data->field_4A9) { + data->entityPosition = (data->direction == kDirectionUp) ? kPosition_8514 : kPosition_2086; + } else { + if (isPlayerPosition(kCarGreenSleeping, 1) || isPlayerPosition(kCarGreenSleeping, 40) + || isPlayerPosition(kCarRedSleeping, 1) || isPlayerPosition(kCarRedSleeping, 40)) { + data->entityPosition = (data->direction == kDirectionUp) ? kPosition_2588 : kPosition_8012; + } else { + data->entityPosition = (data->direction == kDirectionUp) ? kPosition_9271 : kPosition_849; + } + } + } + + SAFE_DELETE(data->sequence); + data->sequenceName = ""; + data->field_4A9 = false; + + if (data->directionSwitch) + data->direction = data->directionSwitch; +} + +void Entities::copySequenceData(EntityIndex entityIndex) const { + EntityData::EntityCallData *data = getData(entityIndex); + + if (data->sequence) + data->sequence3 = data->sequence; + + data->sequence = data->sequence2; + data->sequenceName = data->sequenceName2; + data->field_4A9 = data->field_4AA; + + if (data->directionSwitch) + data->direction = data->directionSwitch; + + // Clear sequence 3 + data->sequence2 = NULL; + data->sequenceName2 = ""; + data->field_4AA = false; + data->directionSwitch = kDirectionNone; + + if (data->field_4A9) { + computeCurrentFrame(entityIndex); + + if (data->currentFrame == -1) + data->currentFrame = 0; + } else { + data->currentFrame = data->currentFrame2; + data->currentFrame2 = 0; + + if (data->currentFrame == -1) + data->currentFrame = 0; + } +} + +////////////////////////////////////////////////////////////////////////// +// Drawing +////////////////////////////////////////////////////////////////////////// +void Entities::drawSequenceLeft(EntityIndex index, const char* sequence) const { + drawSequence(index, sequence, kDirectionLeft); +} + +void Entities::drawSequenceRight(EntityIndex index, const char* sequence) const { + drawSequence(index, sequence, kDirectionRight); +} + +void Entities::clearSequences(EntityIndex entityIndex) const { + debugC(8, kLastExpressDebugLogic, "Clear sequences for entity %s", ENTITY_NAME(entityIndex)); + + EntityData::EntityCallData *data = getData(entityIndex); + + getScenes()->removeAndRedraw(&data->frame, false); + getScenes()->removeAndRedraw(&data->frame1, false); + + if (data->sequence2) { + SAFE_DELETE(data->sequence2); + data->sequenceName2 = ""; + data->field_4AA = false; + data->directionSwitch = kDirectionNone; + } + + if (data->sequence) { + SAFE_DELETE(data->sequence); + data->sequenceName = ""; + data->field_4A9 = false; + data->currentFrame = -1; + } + + data->sequenceNamePrefix = ""; + data->direction = kDirectionNone; + data->doProcessEntity = true; +} + +void Entities::drawSequence(EntityIndex index, const char* sequence, EntityDirection direction) const { + debugC(8, kLastExpressDebugLogic, "Drawing sequence %s for entity %s with direction %s", sequence, ENTITY_NAME(index), DIRECTION_NAME(direction)); + + // Copy sequence name + getData(index)->sequenceNamePrefix = sequence; + getData(index)->sequenceNamePrefix.toUppercase(); + getData(index)->sequenceNamePrefix += "-"; + + // Reset fields + getData(index)->field_49B = 0; + getData(index)->currentFrame = 0; + getData(index)->field_4A1 = 0; + + drawSequences(index, direction, true); +} + +void Entities::drawSequences(EntityIndex entityIndex, EntityDirection direction, bool loadSequence) const { + EntityData::EntityCallData *data = getData(entityIndex); + + // Compute value for loading sequence depending on direction + byte field30 = (direction == kDirectionLeft ? entityIndex + 35 : 15); + + data->doProcessEntity = true; + bool field4A9 = data->field_4A9; + + // First case: different car and not going right: cleanup and return + if (data->car != getData(kEntityPlayer)->car && direction != kDirectionRight) { + clearEntitySequenceData(data, direction); + return; + } + + data->directionSwitch = kDirectionNone; + + // Process sequence names + Common::String sequenceName; + Common::String sequenceName1; + Common::String sequenceName2; + Common::String sequenceName3; + + getSequenceName(entityIndex, direction, sequenceName1, sequenceName2); + + // No sequence 1: cleanup and return + if (sequenceName1 == "") { + clearEntitySequenceData(data, direction); + return; + } + + if (sequenceName1 == data->sequenceNameCopy) { + data->direction = direction; + return; + } + + if (direction == kDirectionLeft || direction == kDirectionRight) { + COMPUTE_SEQUENCE_NAME(sequenceName, sequenceName1); + + if (sequenceName3 != "") + COMPUTE_SEQUENCE_NAME(sequenceName3, sequenceName2); + } + + if (!data->frame) { + data->direction = direction; + + if (sequenceName1 == data->sequenceName) { + if (sequenceName2 == "") + return; + + loadSequence2(entityIndex, sequenceName2, sequenceName3, field30, loadSequence); + return; + } + + SAFE_DELETE(data->sequence); + + if (sequenceName1 != data->sequenceName2) { + + if (loadSequence) { + + if (data->car == getData(kEntityPlayer)->car) + data->sequence = loadSequence1(sequenceName1, field30); + + if (data->sequence) { + data->sequenceName = sequenceName1; + data->sequenceNameCopy = ""; + } else { + if (sequenceName != "") + data->sequence = loadSequence1(sequenceName, field30); + + data->sequenceName = (data->sequence ? sequenceName : ""); + data->sequenceNameCopy = (data->sequence ? "" : sequenceName1); + } + } else { + data->sequenceName = sequenceName1; + } + + if (sequenceName2 != "") { + loadSequence2(entityIndex, sequenceName2, sequenceName3, field30, loadSequence); + return; + } + + if (!data->sequence2) { + if (sequenceName2 == "") + return; + + loadSequence2(entityIndex, sequenceName2, sequenceName3, field30, loadSequence); + return; + } + + SAFE_DELETE(data->sequence2); + } else { + data->sequence = data->sequence2; + data->sequenceName = data->sequenceName2; + data->sequence2 = NULL; + } + + data->sequenceName2 = ""; + + if (sequenceName2 == "") + return; + + loadSequence2(entityIndex, sequenceName2, sequenceName3, field30, loadSequence); + return; + } + + if (data->sequenceName != sequenceName1) { + + if (data->sequenceName2 != sequenceName1) { + SAFE_DELETE(data->sequence2); + TRY_LOAD_SEQUENCE(data->sequence2, data->sequenceName2, sequenceName1, sequenceName); + } + + data->field_4AA = data->field_4A9; + if ((direction != kDirectionUp && direction != kDirectionDown) || data->field_4AA || !data->sequence2) { + data->currentFrame2 = 0; + } else { + data->currentFrame2 = getCurrentFrame(entityIndex, data->sequence2, kPositionNone, false); + + if (data->currentFrame2 == -1) { + clearSequences(entityIndex); + return; + } + } + + data->field_4A9 = field4A9; + data->field_49B = data->frame->getInfo()->field_30; + data->currentFrame = (int16)(data->sequence->count() - 1); + data->direction = kDirectionSwitch; + data->directionSwitch = direction; + } else { + SAFE_DELETE(data->sequence2); + + data->sequence2 = loadSequence1(data->sequence->getName(), data->sequence->getField30()); + + data->sequenceName2 = data->sequenceName; + data->field_4AA = data->field_4A9; + data->field_49B = data->frame->getInfo()->field_30; + data->currentFrame = (int16)(data->sequence->count() - 1); + data->direction = kDirectionSwitch; + data->directionSwitch = direction; + + if ((direction != kDirectionUp && direction != kDirectionDown) || data->field_4AA || !data->sequence2) { + data->currentFrame2 = 0; + } else { + data->currentFrame2 = getCurrentFrame(entityIndex, data->sequence2, kPositionNone, false); + + if (data->currentFrame2 == -1) + clearSequences(entityIndex); + } + } +} + +void Entities::loadSequence2(EntityIndex entityIndex, Common::String sequenceName, Common::String sequenceName2, byte field30, bool reloadSequence) const { + EntityData::EntityCallData *data = getData(entityIndex); + + if (data->sequenceName2 == sequenceName) + return; + + if (data->sequence2) + SAFE_DELETE(data->sequence2); + + if (reloadSequence) { + TRY_LOAD_SEQUENCE(data->sequence2, data->sequenceName2, sequenceName, sequenceName2); + } else { + data->sequenceName2 = sequenceName; + } +} + +void Entities::getSequenceName(EntityIndex index, EntityDirection direction, Common::String &sequence1, Common::String &sequence2) const { + EntityData::EntityCallData *data = getData(index); + Position position = getScenes()->get(getState()->scene)->position; + + // reset fields + data->field_4A9 = false; + data->field_4AA = false; + + switch (direction) { + default: + break; + + case kDirectionUp: + switch (position) { + default: + break; + + case 1: + if (data->entityPosition < kPosition_2587) + sequence1 = Common::String::printf("%02d%01d-01u.seq", index, data->clothes); + break; + + case 2: + case 3: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + if (data->entityPosition >= kPosition_9270) + break; + + if (data->entityPosition >= kPosition_8513) { + sequence1 = Common::String::printf("%02d%01d-%02deu.seq", index, data->clothes, position); + } else { + sequence1 = Common::String::printf("%02d%01d-03u.seq", index, data->clothes); + sequence2 = Common::String::printf("%02d%01d-%02deu.seq", index, data->clothes, position); + data->field_4A9 = true; + } + break; + + case 18: + if (data->entityPosition < kPosition_9270) + sequence1 = Common::String::printf("%02d%01d-18u.seq", index, data->clothes); + break; + + case 22: + if (getData(kEntityPlayer)->entityPosition > data->entityPosition) + sequence1 = Common::String::printf("%02d%01d-22u.seq", index, data->clothes); + break; + + case 23: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + case 38: + case 39: + if (getData(kEntityPlayer)->entityPosition <= data->entityPosition) + break; + + if (data->entityPosition >= kPosition_2087) { + sequence1 = Common::String::printf("%02d%01d-38u.seq", index, data->clothes); + data->field_4A9 = true; + } else { + sequence1 = Common::String::printf("%02d%01d-%02deu.seq", index, data->clothes, position); + sequence2 = Common::String::printf("%02d%01d-38u.seq", index, data->clothes); + data->field_4AA = true; + } + break; + + case 40: + if (getData(kEntityPlayer)->entityPosition > data->entityPosition) + sequence1 = Common::String::printf("%02d%01d-40u.seq", index, data->clothes); + break; + } + break; + + case kDirectionDown: + switch (position) { + default: + break; + + case 1: + if (getData(kEntityPlayer)->entityPosition < data->entityPosition) + sequence1 = Common::String::printf("%02d%01d-01d.seq", index, data->clothes); + break; + + case 2: + case 3: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + if (getData(kEntityPlayer)->entityPosition >= data->entityPosition) + break; + + if (data->entityPosition <= kPosition_8513) { + sequence1 = Common::String::printf("%02d%01d-03d.seq", index, data->clothes); + data->field_4A9 = true; + } else { + sequence1 = Common::String::printf("%02d%01d-%02ded.seq", index, data->clothes, position); + sequence2 = Common::String::printf("%02d%01d-03d.seq", index, data->clothes); + data->field_4AA = true; + } + break; + + case 18: + if (getData(kEntityPlayer)->entityPosition < data->entityPosition) + sequence1 = Common::String::printf("%02d%01d-18d.seq", index, data->clothes); + break; + + case 22: + if (data->entityPosition > kPosition_850) + sequence1 = Common::String::printf("%02d%01d-22d.seq", index, data->clothes); + break; + + case 23: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + case 38: + case 39: + if (data->entityPosition <= kPosition_850) + break; + + if (data->entityPosition <= kPosition_2087) { + sequence1 = Common::String::printf("%02d%01d-%02ded.seq", index, data->clothes, position); + } else { + sequence1 = Common::String::printf("%02d%01d-38d.seq", index, data->clothes); + sequence2 = Common::String::printf("%02d%01d-%02ded.seq", index, data->clothes, position); + data->field_4A9 = true; + } + break; + + case 40: + if (getData(kEntityPlayer)->entityPosition > kPosition_8013) + sequence1 = Common::String::printf("%02d%01d-40d.seq", index, data->clothes); + break; + } + break; + + // First part of sequence is already set + case kDirectionLeft: + case kDirectionRight: + sequence1 = Common::String::printf("%s%02d.seq", data->sequenceNamePrefix.c_str(), position); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +/// Compartments +////////////////////////////////////////////////////////////////////////// +void Entities::enterCompartment(EntityIndex entity, ObjectIndex compartment, bool useCompartment1) { + if (entity > kEntityChapters) + return; + + switch (compartment) { + default: + // Return here so we do not update the compartments + return; + + case kObjectCompartment1: + updatePositionsEnter(entity, kCarGreenSleeping, 41, 51, 17, 38); + break; + + case kObjectCompartment2: + updatePositionsEnter(entity, kCarGreenSleeping, 42, 52, 15, 36); + break; + + case kObjectCompartment3: + updatePositionsEnter(entity, kCarGreenSleeping, 43, 53, 13, 34); + break; + + case kObjectCompartment4: + updatePositionsEnter(entity, kCarGreenSleeping, 44, 54, 11, 32); + break; + + case kObjectCompartment5: + updatePositionsEnter(entity, kCarGreenSleeping, 45, 55, 9, 30); + break; + + case kObjectCompartment6: + updatePositionsEnter(entity, kCarGreenSleeping, 46, 56, 7, 28); + break; + + case kObjectCompartment7: + updatePositionsEnter(entity, kCarGreenSleeping, 47, 57, 5, 26); + break; + + case kObjectCompartment8: + updatePositionsEnter(entity, kCarGreenSleeping, 48, 58, 3, 25); + break; + + case kObjectCompartmentA: + updatePositionsEnter(entity, kCarRedSleeping, 41, 51, 17, 38); + break; + + case kObjectCompartmentB: + updatePositionsEnter(entity, kCarRedSleeping, 42, 52, 15, 36); + break; + + case kObjectCompartmentC: + updatePositionsEnter(entity, kCarRedSleeping, 43, 53, 13, 34); + break; + + case kObjectCompartmentD: + updatePositionsEnter(entity, kCarRedSleeping, 44, 54, 11, 32); + break; + + case kObjectCompartmentE: + updatePositionsEnter(entity, kCarRedSleeping, 45, 55, 9, 30); + break; + + case kObjectCompartmentF: + updatePositionsEnter(entity, kCarRedSleeping, 46, 56, 7, 28); + break; + + case kObjectCompartmentG: + updatePositionsEnter(entity, kCarRedSleeping, 47, 57, 5, 26); + break; + + case kObjectCompartmentH: + updatePositionsEnter(entity, kCarRedSleeping, 48, 58, 3, 25); + break; + } + + // Update compartments + int index = (compartment < 32 ? compartment - 1 : compartment - 24); + if (index >= 16) + error("Entities::exitCompartment: invalid compartment index!"); + + if (useCompartment1) + _compartments1[index] |= STORE_VALUE(entity); + else + _compartments[index] |= STORE_VALUE(entity); +} + +void Entities::exitCompartment(EntityIndex entity, ObjectIndex compartment, bool useCompartment1) { + if (entity > kEntityChapters) + return; + + // TODO factorize in one line + switch (compartment) { + default: + // Return here so we do not update the compartments + return; + + case kObjectCompartment1: + updatePositionsExit(entity, kCarGreenSleeping, 41, 51); + break; + + case kObjectCompartment2: + updatePositionsExit(entity, kCarGreenSleeping, 42, 52); + break; + + case kObjectCompartment3: + updatePositionsExit(entity, kCarGreenSleeping, 43, 53); + break; + + case kObjectCompartment4: + updatePositionsExit(entity, kCarGreenSleeping, 44, 54); + break; + + case kObjectCompartment5: + updatePositionsExit(entity, kCarGreenSleeping, 45, 55); + break; + + case kObjectCompartment6: + updatePositionsExit(entity, kCarGreenSleeping, 46, 56); + break; + + case kObjectCompartment7: + updatePositionsExit(entity, kCarGreenSleeping, 47, 57); + break; + + case kObjectCompartment8: + updatePositionsExit(entity, kCarGreenSleeping, 48, 58); + break; + + case kObjectCompartmentA: + updatePositionsExit(entity, kCarRedSleeping, 41, 51); + break; + + case kObjectCompartmentB: + updatePositionsExit(entity, kCarRedSleeping, 42, 52); + break; + + case kObjectCompartmentC: + updatePositionsExit(entity, kCarRedSleeping, 43, 53); + break; + + case kObjectCompartmentD: + updatePositionsExit(entity, kCarRedSleeping, 44, 54); + break; + + case kObjectCompartmentE: + updatePositionsExit(entity, kCarRedSleeping, 45, 55); + break; + + case kObjectCompartmentF: + updatePositionsExit(entity, kCarRedSleeping, 46, 56); + break; + + case kObjectCompartmentG: + updatePositionsExit(entity, kCarRedSleeping, 47, 57); + break; + + case kObjectCompartmentH: + updatePositionsExit(entity, kCarRedSleeping, 48, 58); + break; + } + + // Update compartments + int index = (compartment < 32 ? compartment - 1 : compartment - 24); + if (index >= 16) + error("Entities::exitCompartment: invalid compartment index!"); + + if (useCompartment1) + _compartments1[index] &= ~STORE_VALUE(entity); + else + _compartments[index] &= ~STORE_VALUE(entity); +} + +void Entities::updatePositionEnter(EntityIndex entity, CarIndex car, Position position) { + if (entity == kEntity39) + entity = kEntityPlayer; + + if (entity > kEntityChapters) + return; + + _positions[100 * car + position] |= STORE_VALUE(entity); + + if (isPlayerPosition(car, position) || (car == kCarRestaurant && position == 57 && isPlayerPosition(kCarRestaurant, 50))) { + getSound()->excuseMe(entity); + getScenes()->loadScene(getScenes()->processIndex(getState()->scene)); + getSound()->playSound(kEntityPlayer, "CAT1127A"); + } else { + getLogic()->updateCursor(); + } +} + +void Entities::updatePositionExit(EntityIndex entity, CarIndex car, Position position) { + if (entity == kEntity39) + entity = kEntityPlayer; + + if (entity > kEntityChapters) + return; + + _positions[100 * car + position] &= ~STORE_VALUE(entity); + + getLogic()->updateCursor(); +} + +void Entities::updatePositionsEnter(EntityIndex entity, CarIndex car, Position position1, Position position2, Position position3, Position position4) { + if (entity == kEntity39) + entity = kEntityPlayer; + + if (entity > kEntityChapters) + return; + + _positions[100 * car + position1] |= STORE_VALUE(entity); + _positions[100 * car + position2] |= STORE_VALUE(entity); + + // FIXME: also checking two DWORD values that do not seem to updated anywhere... + if (isPlayerPosition(car, position1) || isPlayerPosition(car, position2) || isPlayerPosition(car, position3) || isPlayerPosition(car, position4)) { + getSound()->excuseMe(entity); + getScenes()->loadScene(getScenes()->processIndex(getState()->scene)); + getSound()->playSound(kEntityPlayer, "CAT1127A"); + } else { + getLogic()->updateCursor(); + } +} + +void Entities::updatePositionsExit(EntityIndex entity, CarIndex car, Position position1, Position position2) { + if (entity == kEntity39) + entity = kEntityPlayer; + + if (entity > kEntityChapters) + return; + + _positions[100 * car + position1] &= ~STORE_VALUE(entity); + _positions[100 * car + position2] &= ~STORE_VALUE(entity); + + getLogic()->updateCursor(); +} + +void Entities::loadSceneFromEntityPosition(CarIndex car, EntityPosition entityPosition, bool alternate) const { + + // Determine position + Position position = (alternate ? 1 : 40); + do { + if (entityPosition > entityPositions[position]) { + if (alternate) + break; + + // For default value, we ignore position 24 + if (position != 24) + break; + } + + alternate ? ++position : --position; + + } while (alternate ? position <= 18 : position >= 22); + + // For position outside bounds, use minimal value + if ((alternate && position > 18) || (alternate && position < 22)) { + getScenes()->loadSceneFromPosition(car, alternate ? 18 : 22); + return; + } + + // Load scene from position + switch (position) { + default: + getScenes()->loadSceneFromPosition(car, (Position)(position + (alternate ? - 1 : 1))); + break; + + // Alternate + case 1: + if (alternate) getScenes()->loadSceneFromPosition(car, 1); + break; + + case 5: + if (alternate) getScenes()->loadSceneFromPosition(car, 3); + break; + + // Default + case 23: + if (!alternate) getScenes()->loadSceneFromPosition(car, 25); + break; + + case 40: + if (!alternate) getScenes()->loadSceneFromPosition(car, 40); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +// Checks +////////////////////////////////////////////////////////////////////////// +bool Entities::hasValidFrame(EntityIndex entity) const { + return (getData(entity)->frame && (getData(entity)->frame->getInfo()->subType != kFrameType3)); +} + +bool Entities::compare(EntityIndex entity1, EntityIndex entity2) { + EntityData::EntityCallData *data1 = getData(entity1); + EntityData::EntityCallData *data2 = getData(entity2); + + if (data2->car != data1->car + || data1->car < kCarGreenSleeping + || data1->car > kCarRedSleeping) + return false; + + EntityPosition position1 = (data1->entityPosition >= data2->entityPosition) ? data1->entityPosition : data2->entityPosition; + EntityPosition position2 = (data1->entityPosition >= data2->entityPosition) ? data2->entityPosition : data1->entityPosition; + + // Compute position + int index1 = 7; + do { + if (objectsPosition[index1] >= position2) + break; + + --index1; + } while (index1 > -1); + + int index2 = 0; + do { + if (objectsPosition[index2] <= position2) + break; + + ++index2; + } while (index2 < 8); + + if (index1 > -1 && index2 < 8 && index2 <= index1) { + while (index2 <= index1) { + if (getCompartments(index2 + (data1->car == kCarGreenSleeping ? 0 : 8))) + return true; + + if (getCompartments1(index2 + (data1->car == kCarGreenSleeping ? 0 : 8))) + return true; + + ++index2; + } + } + + for (EntityIndex entity = kEntityAnna; entity <= kEntity39; entity = (EntityIndex)(entity + 1)) { + + if (entity1 == entity || entity2 == entity) + continue; + + if (!isDirectionUpOrDown(entity)) + continue; + + if (data1->car == getEntityData(entity)->car + && getEntityData(entity)->entityPosition > position2 + && getEntityData(entity)->entityPosition < position1) + return true; + } + + return false; +} + +bool Entities::updateEntity(EntityIndex entity, CarIndex car, EntityPosition position) { + EntityData::EntityCallData *data = getData(entity); + EntityDirection direction = kDirectionNone; + int delta = 0; + bool flag1 = false; + bool flag2 = false; + bool flag3 = false; + + if (position == kPosition_2000 + && getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) + && !isPlayerPosition(kCarGreenSleeping, 1) + && !isPlayerPosition(kCarRedSleeping, 2)) + position = kPosition_1500; + + if (data->direction != kDirectionUp && data->direction != kDirectionDown) + data->field_497 = 0; + + if (data->field_497) { + data->field_497--; + + if (data->field_497 == 128) + data->field_497 = 0; + + if ((data->field_497 & 127) != 8) { + data->field_49B = 0; + return false; + } + + flag1 = true; + + if (data->field_497 & 128) + flag2 = true; + } + + if (data->car != car) + goto label_process_entity; + + // Calculate delta + delta = ABS(data->entityPosition - position); + if (delta < 100 || (position > kPosition_850 && position < kPosition_9270 && delta < 300)) + flag3 = true; + + if (!flag3) { + if ((getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) && data->direction == kDirectionUp) + || (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown) && data->direction == kDirectionDown)) { + if (!checkPosition(position) && isDistanceBetweenEntities(entity, kEntityPlayer, 250)) + flag3 = true; + } + + if (!flag3) + goto label_process_entity; + } + + if (getEntities()->hasValidFrame(entity) + && getEntities()->isWalkingOppositeToPlayer(entity) + && !getEntities()->checkPosition(position)) { + flag3 = false; + position = (EntityPosition)(getData(kEntityPlayer)->entityPosition + 250 * (data->direction == kDirectionUp ? 1 : -1)); + } + + if (!flag3) { +label_process_entity: + + // Calculate direction + if (data->car < car) + direction = kDirectionUp; + else if (data->car > car) + direction = kDirectionDown; + else // same car + direction = (data->entityPosition < position) ? kDirectionUp : kDirectionDown; + + if (data->direction == direction) { + if (!flag1) { + + if (checkDistanceFromPosition(entity, kPosition_1500, 750) && entity != kEntityFrancois) { + + if (data->entity != kEntityPlayer) { + if (data->direction != kDirectionUp || (position <= kPosition_2000 && data->car == car)) { + if (data->direction == kDirectionDown && (position < kPosition_1500 || data->car != car)) { + if (data->entityPosition > kPosition_1500 && (data->car == kCarGreenSleeping || data->car == kCarRedSleeping)) { + data->entity = (data->car == kCarGreenSleeping) ? kEntityMertens : kEntityCoudert; + getSavePoints()->push(entity, data->entity, kAction11); + } + } + } else { + if (data->entityPosition < kPosition_1500 && (data->car == kCarGreenSleeping || data->car == kCarRedSleeping)) { + data->entity = (data->car == kCarGreenSleeping) ? kEntityMertens : kEntityCoudert; + getSavePoints()->push(entity, data->entity, kAction11, 1); + } + } + } + + } else if (data->entity) { + getSavePoints()->push(entity, data->entity, kAction16); + data->entity = kEntityPlayer; + } + + if (hasValidFrame(entity)) { + + if (!data->field_4A9) + return false; + + int compartmentIndex = 0; + if (data->car == kCarGreenSleeping) + compartmentIndex = 0; + else if (data->car == kCarRedSleeping) + compartmentIndex = 8; + + for (int i = 0; i < 8; i++) { + if (getCompartments(compartmentIndex) || getCompartments1(compartmentIndex)) { + if (checkDistanceFromPosition(entity, objectsPosition[i], 750)) { + if (checkPosition(objectsPosition[i])) { + + if ((data->direction == kDirectionUp && data->entityPosition < objectsPosition[i] && (data->car != car || position > objectsPosition[i])) + || (data->direction == kDirectionDown && data->entityPosition > objectsPosition[i] && (data->car != car || position < objectsPosition[i]))) { + + getSound()->excuseMe(entity, (EntityIndex)(State::getPowerOfTwo((uint32)(getCompartments(compartmentIndex) ? getCompartments(compartmentIndex) : getCompartments1(compartmentIndex))))); + + data->field_497 = 144; + + break; + } + } + } + } + + compartmentIndex++; + } + + for (EntityIndex entityIndex = kEntityAnna; entityIndex <= kEntity39; entityIndex = (EntityIndex)(entityIndex + 1)) { + if (getSavePoints()->getCallback(entityIndex) + && hasValidFrame(entityIndex) + && entityIndex != entity + && isDistanceBetweenEntities(entity, entityIndex, 750) + && isDirectionUpOrDown(entityIndex) + && (entity != kEntityRebecca || entityIndex != kEntitySophie) + && (entity != kEntitySophie || entityIndex != kEntityRebecca) + && (entity != kEntityIvo || entityIndex != kEntitySalko) + && (entity != kEntitySalko || entityIndex != kEntityIvo) + && (entity != kEntityMilos || entityIndex != kEntityVesna) + && (entity != kEntityVesna || entityIndex != kEntityMilos)) { + + EntityData::EntityCallData *data2 = getData(entityIndex); + + if (data->direction != data2->direction) { + + if ((data->direction != kDirectionUp || data2->entityPosition <= data->entityPosition) + && (data->direction != kDirectionDown || data2->entityPosition >= data->entityPosition)) + continue; + + data->field_49B = 0; + data2->field_49B = 0; + + data->field_497 = 16; + data2->field_497 = 16; + + getSound()->excuseMe(entity, entityIndex); + getSound()->excuseMe(entityIndex, entity); + + if (entityIndex > entity) + ++data2->field_497; + + break; + } + + if (ABS(data2->entityPosition - getData(kEntityPlayer)->entityPosition) < ABS(data->entityPosition - getData(kEntityPlayer)->entityPosition)) { + + if (!isWalkingOppositeToPlayer(entity)) { + + if (direction == kDirectionUp) { + if (data->entityPosition < kPosition_9500) + data->entityPosition = (EntityPosition)(data->entityPosition + 500); + } else { + if (data->entityPosition > kPosition_500) + data->entityPosition = (EntityPosition)(data->entityPosition - 500); + } + + drawSequences(entity, direction, true); + + return false; + } + data->field_49B = 0; + + break; + } + } + } + + return false; + } + + if (data->direction == kDirectionUp) { + if (data->entityPosition + data->field_4A3 < 10000) + data->entityPosition = (EntityPosition)(data->entityPosition + data->field_4A3); + } else { + if (data->entityPosition > data->field_4A3) + data->entityPosition = (EntityPosition)(data->entityPosition - data->field_4A3); + } + + if (data->entityPosition <= kPosition_9270 || data->direction != kDirectionUp) { + if (data->entityPosition < kPosition_850 && data->direction == kDirectionDown) { + if (changeCar(data, entity, car, position, false, kPosition_9269, kCarKronos)) + return true; + } + } else { + if (changeCar(data, entity, car, position, true, kPosition_851, kCarGreenSleeping)) + return true; + } + + if (getData(kEntityPlayer)->car == data->car && data->location == kLocationOutsideCompartment) { + if (data->direction == kDirectionUp) { + + if (getData(kEntityPlayer)->entityPosition > data->entityPosition + && getData(kEntityPlayer)->entityPosition - data->entityPosition >= 500 + && data->field_4A3 + 500 > getData(kEntityPlayer)->entityPosition - data->entityPosition) { + + if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) || getScenes()->checkCurrentPosition(false)) { + getSavePoints()->push(kEntityPlayer, entity, kActionExcuseMe); + + if (getScenes()->checkCurrentPosition(false)) + getScenes()->loadSceneFromObject((ObjectIndex)getScenes()->get(getState()->scene)->param1, true); + + } else if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown)) { + getSavePoints()->push(kEntityPlayer, entity, kActionExcuseMeCath); + } + } + } else { + if (getData(kEntityPlayer)->entityPosition < data->entityPosition + && data->entityPosition - getData(kEntityPlayer)->entityPosition >= 500 + && data->field_4A3 + 500 > data->entityPosition - getData(kEntityPlayer)->entityPosition) { + + if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp)) { + getSavePoints()->push(kEntityPlayer, entity, kActionExcuseMeCath); + } else if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown) || getScenes()->checkCurrentPosition(false)){ + getSavePoints()->push(kEntityPlayer, entity, kActionExcuseMe); + + if (getScenes()->checkCurrentPosition(false)) + getScenes()->loadSceneFromObject((ObjectIndex)getScenes()->get(getState()->scene)->param1); + } + } + } + return false; + } + } + } else if (!flag1) { + drawSequences(entity, direction, true); + return false; + } + + ////////////////////////////////////////////////////////////////////////// + // Adjust positions + + // Direction Up + if (direction == kDirectionUp) { + if (data->entityPosition < (flag2 ? kPosition_8800 : kPosition_9250)) + data->entityPosition = (EntityPosition)(data->entityPosition + (flag2 ? kPosition_1200 : kPosition_750)); + + if (data->car == car && data->entityPosition >= position) { + data->entityPosition = position; + data->direction = kDirectionNone; + data->entity = kEntityPlayer; + return true; + } + + drawSequences(entity, direction, true); + return false; + } + + // Direction Down + if (data->entityPosition > (flag2 ? kPosition_1200 : kPosition_750)) + data->entityPosition = (EntityPosition)(data->entityPosition - (flag2 ? kPosition_1200 : kPosition_750)); + + if (data->car == car && data->entityPosition <= position) { + data->entityPosition = position; + data->direction = kDirectionNone; + data->entity = kEntityPlayer; + return true; + } + + drawSequences(entity, direction, true); + return false; + } + + data->entityPosition = position; + if (data->direction == kDirectionUp || data->direction == kDirectionDown) + data->direction = kDirectionNone; + data->entity = kEntityPlayer; + + return true; +} + +bool Entities::changeCar(EntityData::EntityCallData * data, EntityIndex entity, CarIndex car, EntityPosition position, bool increment, EntityPosition newPosition, CarIndex newCar) const { + if (getData(kEntityPlayer)->car == data->car) { + getSound()->playSoundEvent(entity, 36); + getSound()->playSoundEvent(entity, 37, 30); + } + + data->car = (CarIndex)(increment ? data->car + 1 : data->car - 1); + data->entityPosition = newPosition; + + if (data->car == newCar) { + if (isInGreenCarEntrance(kEntityPlayer)) { + getSound()->playSoundEvent(kEntityPlayer, 14); + getSound()->excuseMe(entity, kEntityPlayer, SoundManager::kFlagDefault); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 1); + getSound()->playSound(kEntityPlayer, "CAT1127A"); + getSound()->playSoundEvent(kEntityPlayer, 15); + } + } + + if ((increment ? data->car > car : data->car < car) || (data->car == car && (increment ? data->entityPosition >= position : data->entityPosition <= position))) { + data->car = car; + data->entityPosition = position; + data->direction = kDirectionNone; + data->entity = kEntityPlayer; + + return true; + } + + if (data->car == newCar) { + if (isInKronosCarEntrance(kEntityPlayer)) { + getSound()->playSoundEvent(kEntityPlayer, 14); + getSound()->excuseMe(entity, kEntityPlayer, SoundManager::kFlagDefault); + getScenes()->loadSceneFromPosition(kCarGreenSleeping, 62); + getSound()->playSound(kEntityPlayer, "CAT1127A"); + getSound()->playSoundEvent(kEntityPlayer, 15); + } + } + + if (data->car == getData(kEntityPlayer)->car) { + getSound()->playSoundEvent(entity, 36); + getSound()->playSoundEvent(entity, 37, 30); + } + + return false; +} + +////////////////////////////////////////////////////////////////////////// +// CHECKS +////////////////////////////////////////////////////////////////////////// +bool Entities::isInsideCompartment(EntityIndex entity, CarIndex car, EntityPosition position) const { + return (getData(entity)->entityPosition == position + && getData(entity)->location == kLocationInsideCompartment + && getData(entity)->car == car); +} + +bool Entities::checkFields2(ObjectIndex object) const { + + EntityPosition position = kPositionNone; + CarIndex car = kCarNone; + + switch (object) { + default: + return false; + + case kObjectCompartment1: + case kObjectCompartment2: + case kObjectCompartment3: + case kObjectCompartment4: + case kObjectCompartment5: + case kObjectCompartment6: + case kObjectCompartment7: + case kObjectCompartment8: + position = objectsPosition[object - 1]; + car = kCarGreenSleeping; + if (isInsideCompartment(kEntityPlayer, car, position)) + return false; + break; + + case kObjectHandleBathroom: + case kObjectHandleInsideBathroom: + case kObjectKitchen: + case kObject20: + case kObject21: + case kObject22: + position = objectsPosition[object-17]; + car = kCarGreenSleeping; + break; + + case kObjectCompartmentA: + case kObjectCompartmentB: + case kObjectCompartmentC: + case kObjectCompartmentD: + case kObjectCompartmentE: + case kObjectCompartmentF: + case kObjectCompartmentG: + case kObjectCompartmentH: + position = objectsPosition[object-32]; + car = kCarRedSleeping; + if (isInsideCompartment(kEntityPlayer, car, position)) + return false; + break; + + case kObject48: + case kObject49: + case kObject50: + case kObject51: + case kObject52: + case kObject53: + position = objectsPosition[object-48]; + car = kCarRedSleeping; + break; + + } + + uint index = 1; + while (!isInsideCompartment((EntityIndex)index, car, position) || index == kEntityVassili) { + index++; + if (index >= 40) + return false; + } + + return true; +} + +bool Entities::isInsideCompartments(EntityIndex entity) const { + return (getData(entity)->car == kCarGreenSleeping + || getData(entity)->car == kCarRedSleeping) + && getData(entity)->location == kLocationInsideCompartment; +} + +bool Entities::isPlayerPosition(CarIndex car, Position position) const { + return getData(kEntityPlayer)->car == car && getScenes()->get(getState()->scene)->position == position; +} + +bool Entities::isInsideTrainCar(EntityIndex entity, CarIndex car) const { + return getData(entity)->car == car && getData(entity)->location <= kLocationInsideCompartment; +} + +bool Entities::isInGreenCarEntrance(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarGreenSleeping) && getData(entity)->entityPosition < kPosition_850; +} + +bool Entities::isPlayerInCar(CarIndex car) const { + return isInsideTrainCar(kEntityPlayer, car) && getData(kEntityPlayer)->location && !isInGreenCarEntrance(kEntityPlayer); +} + +bool Entities::isDirectionUpOrDown(EntityIndex entity) const { + return getData(entity)->direction == kDirectionUp || getData(entity)->direction == kDirectionDown; +} + +bool Entities::isDistanceBetweenEntities(EntityIndex entity1, EntityIndex entity2, uint distance) const { + return getData(entity1)->car == getData(entity2)->car + && (uint)ABS(getData(entity1)->entityPosition - getData(entity2)->entityPosition) <= distance + && (getData(entity1)->location != kLocationOutsideTrain || getData(entity2)->location != kLocationOutsideTrain); +} + +bool Entities::checkFields10(EntityIndex entity) const { + return getData(entity)->location <= kLocationOutsideTrain; +} + +bool Entities::isSomebodyInsideRestaurantOrSalon() const { + for (uint i = 1; i < _entities.size(); i++) { + EntityIndex index = (EntityIndex)i; + + if (getData(index)->location == kLocationOutsideCompartment && (isInSalon(index) || isInRestaurant(index))) + return false; + } + + return true; +} + +bool Entities::isInSalon(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarRestaurant) + && getData(entity)->entityPosition >= kPosition_1540 + && getData(entity)->entityPosition <= kPosition_3650; +} + +bool Entities::isInRestaurant(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarRestaurant) + && getData(entity)->entityPosition >= kPosition_3650 + && getData(entity)->entityPosition <= kPosition_5800; +} + +bool Entities::isInKronosSalon(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarKronos) + && getData(entity)->entityPosition >= kPosition_5500 + && getData(entity)->entityPosition <= kPosition_7500; +} + +bool Entities::isOutsideAlexeiWindow() const { + return (getData(kEntityPlayer)->entityPosition == kPosition_7500 || getData(kEntityPlayer)->entityPosition == kPosition_8200) + && getData(kEntityPlayer)->location == kLocationOutsideTrain + && getData(kEntityPlayer)->car == kCarGreenSleeping; +} + +bool Entities::isOutsideAnnaWindow() const { + return (getData(kEntityPlayer)->entityPosition == kPosition_4070 || getData(kEntityPlayer)->entityPosition == kPosition_4840) + && getData(kEntityPlayer)->location == kLocationOutsideTrain + && getData(kEntityPlayer)->car == kCarRedSleeping; +} + +bool Entities::isInKitchen(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarRestaurant) && getData(entity)->entityPosition > kPosition_5800; +} + +bool Entities::isNobodyInCompartment(CarIndex car, EntityPosition position) const { + for (uint i = 1; i < _entities.size(); i++) { + if (isInsideCompartment((EntityIndex)i, car, position)) + return false; + } + return true; +} + +bool Entities::checkFields19(EntityIndex entity, CarIndex car, EntityPosition position) const { + + if (getData(entity)->car != car || getData(entity)->location != kLocationInsideCompartment) + return false; + + EntityPosition entityPosition = getData(entity)->entityPosition; + + // Test values + if (position == kPosition_4455) { + if (entityPosition == kPosition_4070 || entityPosition == kPosition_4455 || entityPosition == kPosition_4840) + return true; + + return false; + } + + if (position == kPosition_6130) { + if (entityPosition == kPosition_5790 || entityPosition == kPosition_6130 || entityPosition == kPosition_6470) + return true; + + return false; + } + + if (position != kPosition_7850 + || (entityPosition != kPosition_7500 && entityPosition != kPosition_7850 && entityPosition != kPosition_8200)) + return false; + + return true; +} + +bool Entities::isInBaggageCarEntrance(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarBaggage) + && getData(entity)->entityPosition >= kPosition_4500 + && getData(entity)->entityPosition <= kPosition_5500; +} + +bool Entities::isInBaggageCar(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarBaggage) && getData(entity)->entityPosition < kPosition_4500; +} + +bool Entities::isInKronosSanctum(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarKronos) + && getData(entity)->entityPosition >= kPosition_3500 + && getData(entity)->entityPosition <= kPosition_5500; +} + +bool Entities::isInKronosCarEntrance(EntityIndex entity) const { + return isInsideTrainCar(entity, kCarKronos) && getData(entity)->entityPosition > kPosition_7900; +} + +bool Entities::checkDistanceFromPosition(EntityIndex entity, EntityPosition position, int distance) const { + return distance >= ABS(getData(entity)->entityPosition - position); +} + +bool Entities::isWalkingOppositeToPlayer(EntityIndex entity) const { + if (getData(entity)->direction == kDirectionUp && getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown)) + return true; + + return (getData(entity)->direction == kDirectionDown && getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp)); +} + +bool Entities::isFemale(EntityIndex entity) { + return (entity == kEntityAnna + || entity == kEntityTatiana + || entity == kEntityVesna + || entity == kEntityKahina + || entity == kEntityMmeBoutarel + || entity == kEntityRebecca + || entity == kEntitySophie + || entity == kEntityYasmin + || entity == kEntityHadija + || entity == kEntityAlouan); +} + +bool Entities::isMarried(EntityIndex entity) { + return (entity != kEntityTatiana + && entity != kEntityRebecca + && entity != kEntitySophie); +} + +bool Entities::checkPosition(EntityPosition position) const { + Position position1 = 0; + Position position2 = 0; + + switch (position) { + default: + return true; + + case kPosition_1500: + position1 = 1; + position2 = 23; + break; + + case kPosition_2740: + position1 = 3; + position2 = 25; + break; + + case kPosition_3050: + position1 = 5; + position2 = 26; + break; + + case kPosition_4070: + position1 = 7; + position2 = 28; + break; + + case kPosition_4840: + position1 = 9; + position2 = 30; + break; + + case kPosition_5790: + position1 = 11; + position2 = 32; + break; + + case kPosition_6470: + position1 = 13; + position2 = 34; + break; + + case kPosition_7500: + position1 = 15; + position2 = 36; + break; + + case kPosition_8200: + position1 = 17; + position2 = 38; + break; + } + + if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) && entityPositions[position1] >= getEntityData(kEntityPlayer)->entityPosition) + return true; + else + return (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown) && entityPositions[position2] <= getEntityData(kEntityPlayer)->entityPosition); +} + +bool Entities::checkSequenceFromPosition(EntityIndex entity) const { + FrameInfo *info = getEntityData(entity)->sequence->getFrameInfo((uint16)getEntityData(entity)->currentFrame); + + if (getEntityData(entity)->direction == kDirectionUp) + return (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) + && info->entityPosition + getEntityPositionFromCurrentPosition() > kPosition_8513); + + if (getEntityData(entity)->direction == kDirectionDown) + return (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown) + && info->entityPosition + getEntityPositionFromCurrentPosition() < kPosition_2087); + + return false; +} + +EntityPosition Entities::getEntityPositionFromCurrentPosition() const { + // Get the scene position first + Position position = getScenes()->get(getState()->scene)->position; + + if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp)) + return (EntityPosition)(entityPositions[position] - kPosition_1430); + + if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown)) + return (EntityPosition)(entityPositions[position] - kPosition_9020); + + return kPositionNone; +} + +void Entities::clearEntitySequenceData(EntityData::EntityCallData *data, EntityDirection direction) const { + getScenes()->removeAndRedraw(&data->frame, false); + getScenes()->removeAndRedraw(&data->frame1, false); + + SAFE_DELETE(data->sequence); + SAFE_DELETE(data->sequence2); + + data->sequenceName = ""; + data->sequenceName2 = ""; + + data->field_4A9 = false; + data->field_4AA = false; + data->directionSwitch = kDirectionNone; + + data->currentFrame = -1; + data->currentFrame2 = 0; + + data->direction = direction; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/entities.h b/engines/lastexpress/game/entities.h new file mode 100644 index 0000000000..40d7025b85 --- /dev/null +++ b/engines/lastexpress/game/entities.h @@ -0,0 +1,378 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_ENTITIES_H +#define LASTEXPRESS_ENTITIES_H + +/* + Entities + -------- + + The entities structure contains 40 Entity_t structures for each entity + +*/ + +#include "lastexpress/entities/entity.h" + +#include "lastexpress/shared.h" + +#include "common/rect.h" +#include "common/serializer.h" + +namespace LastExpress { + +class LastExpressEngine; +class Sequence; + +class Entities : Common::Serializable { +public: + Entities(LastExpressEngine *engine); + ~Entities(); + + // Serializable + void saveLoadWithSerializer(Common::Serializer &ser); + + void setup(bool isFirstChapter, EntityIndex entity); + void setupChapter(ChapterIndex chapter); + void reset(); + + // Update & drawing + + /** + * Reset an entity state + * + * @param entity entity index + * @note remember to call the function pointer (we do not pass it our implementation) + */ + void resetState(EntityIndex entity); + void updateFields() const; + void updateSequences() const; + void updateCallbacks(); + + EntityIndex canInteractWith(const Common::Point &point) const; + bool compare(EntityIndex entity1, EntityIndex entity2); + + /** + * Update an entity current sequence frame (and related fields) + * + * @param entity entity index + */ + void updateFrame(EntityIndex entity) const; + void updatePositionEnter(EntityIndex entity, CarIndex car, Position position); + void updatePositionExit(EntityIndex entity, CarIndex car, Position position); + void enterCompartment(EntityIndex entity, ObjectIndex compartment, bool useCompartment1 = false); + void exitCompartment(EntityIndex entity, ObjectIndex compartment, bool useCompartment1 = false); + + // Sequences + void drawSequenceLeft(EntityIndex index, const char* sequence) const; + void drawSequenceRight(EntityIndex index, const char* sequence) const; + void clearSequences(EntityIndex index) const; + + bool updateEntity(EntityIndex entity, CarIndex car, EntityPosition position); + bool hasValidFrame(EntityIndex entity) const; + + // Accessors + Entity *get(EntityIndex entity); + EntityData::EntityCallData *getData(EntityIndex entity) const; + int getPosition(CarIndex car, Position position); + int getCompartments(int index); + int getCompartments1(int index); + + // Scene + void loadSceneFromEntityPosition(CarIndex car, EntityPosition position, bool alternate = false) const; + + ////////////////////////////////////////////////////////////////////////// + // Checks + ////////////////////////////////////////////////////////////////////////// + + /** + * Query if 'entity' is inside a compartment + * + * @param entity The entity. + * @param car The car. + * @param position The position. + * + * @return true if inside the compartment, false if not. + */ + bool isInsideCompartment(EntityIndex entity, CarIndex car, EntityPosition position) const; + + bool checkFields2(ObjectIndex object) const; + + /** + * Query if 'entity' is in compartment cars. + * + * @param entity The entity. + * + * @return true if in compartment cars, false if not. + */ + bool isInsideCompartments(EntityIndex entity) const; + + /** + * Query if the player is in the specified position + * + * @param car The car. + * @param position The position. + * @return true if player is in that position, false if not. + */ + bool isPlayerPosition(CarIndex car, Position position) const; + + /** + * Query if 'entity' is inside a train car + * + * @param entity The entity. + * @param car The car. + * + * @return true if inside a train car, false if not. + */ + bool isInsideTrainCar(EntityIndex entity, CarIndex car) const; + + /** + * Query if 'entity' is in green car entrance. + * + * @param entity The entity. + * + * @return true if in the green car entrance, false if not. + */ + bool isInGreenCarEntrance(EntityIndex entity) const; + + /** + * Query if the player is in a specific car + * + * @param car The car. + * + * @return true if player is in the car, false if not. + */ + bool isPlayerInCar(CarIndex car) const; + + /** + * Query if 'entity' is going in the up or down direction. + * + * @param entity The entity. + * + * @return true if direction is up or down, false if not. + */ + bool isDirectionUpOrDown(EntityIndex entity) const; + + /** + * Query if the distance between the two entities is less 'distance' + * + * @param entity1 The first entity. + * @param entity2 The second entity. + * @param distance The distance. + * + * @return true if the distance between entities is less than 'distance', false if not. + */ + bool isDistanceBetweenEntities(EntityIndex entity1, EntityIndex entity2, uint distance) const; + + bool checkFields10(EntityIndex entity) const; + + /** + * Query if there is somebody in the restaurant or salon. + * + * @return true if somebody is in the restaurant or salon, false if not. + */ + bool isSomebodyInsideRestaurantOrSalon() const; + + /** + * Query if 'entity' is in the salon. + * + * @param entity The entity. + * + * @return true if in the salon, false if not. + */ + bool isInSalon(EntityIndex entity) const; + + /** + * Query if 'entity' is in the restaurant. + * + * @param entity The entity. + * + * @return true if in the restaurant, false if not. + */ + bool isInRestaurant(EntityIndex entity) const; + + /** + * Query if 'entity' is in Kronos salon. + * + * @param entity The entity. + * + * @return true if in Kronos salon, false if not. + */ + bool isInKronosSalon(EntityIndex entity) const; + + /** + * Query if the player is outside Alexei window. + * + * @return true if outside alexei window, false if not. + */ + bool isOutsideAlexeiWindow() const; + + /** + * Query if the player is outside Anna window. + * + * @return true if outside anna window, false if not. + */ + bool isOutsideAnnaWindow() const; + + /** + * Query if 'entity' is in the kitchen. + * + * @param entity The entity. + * + * @return true if in the kitchen, false if not. + */ + bool isInKitchen(EntityIndex entity) const; + + /** + * Query if nobody is in a compartment at that position. + * + * @param car The car. + * @param position The position. + * + * @return true if nobody is in a compartment, false if not. + */ + bool isNobodyInCompartment(CarIndex car, EntityPosition position) const; + + bool checkFields19(EntityIndex entity, CarIndex car, EntityPosition position) const; + + /** + * Query if 'entity' is in the baggage car entrance. + * + * @param entity The entity. + * + * @return true if in the baggage car entrance, false if not. + */ + bool isInBaggageCarEntrance(EntityIndex entity) const; + + /** + * Query if 'entity' is in the baggage car. + * + * @param entity The entity. + * + * @return true if in the baggage car, false if not. + */ + bool isInBaggageCar(EntityIndex entity) const; + + /** + * Query if 'entity' is in Kronos sanctum. + * + * @param entity The entity. + * + * @return true if in Kronos sanctum, false if not. + */ + bool isInKronosSanctum(EntityIndex entity) const; + + /** + * Query if 'entity' is in Kronos car entrance. + * + * @param entity The entity. + * + * @return true if in Kronos car entrance, false if not. + */ + bool isInKronosCarEntrance(EntityIndex entity) const; + + /** + * Check distance from position. + * + * @param entity The entity. + * @param position The position. + * @param distance The distance. + * + * @return true if distance is bigger, false otherwise. + */ + bool checkDistanceFromPosition(EntityIndex entity, EntityPosition position, int distance) const; + + /** + * Query if 'entity' is walking opposite to player. + * + * @param entity The entity. + * + * @return true if walking opposite to player, false if not. + */ + bool isWalkingOppositeToPlayer(EntityIndex entity) const; + + /** + * Query if 'entity' is female. + * + * @param entity The entity. + * + * @return true if female, false if not. + */ + static bool isFemale(EntityIndex entity); + + /** + * Query if 'entity' is married. + * + * @param entity The entity. + * + * @return true if married, false if not. + */ + static bool isMarried(EntityIndex entity); + +private: + static const int _compartmentsCount = 16; + static const int _positionsCount = 100 * 10; // 100 positions per train car + + LastExpressEngine *_engine; + EntityData *_header; + Common::Array<Entity *> _entities; + + // Compartments & positions + uint _compartments[_compartmentsCount]; + uint _compartments1[_compartmentsCount]; + uint _positions[_positionsCount]; + + void executeCallbacks(); + void processEntity(EntityIndex entity); + + void drawSequence(EntityIndex entity, const char* sequence, EntityDirection direction) const; + void drawSequences(EntityIndex entity, EntityDirection direction, bool loadSequence) const; + void loadSequence2(EntityIndex entity, Common::String sequenceName, Common::String sequenceName2, byte field30, bool loadSequence) const; + + void clearEntitySequenceData(EntityData::EntityCallData * data, EntityDirection direction) const; + void computeCurrentFrame(EntityIndex entity) const; + int16 getCurrentFrame(EntityIndex entity, Sequence *sequence, EntityPosition position, bool doProcessing) const; + void processFrame(EntityIndex entity, bool keepPreviousFrame, bool dontPlaySound); + void drawNextSequence(EntityIndex entity) const; + void updateEntityPosition(EntityIndex entity) const; + void copySequenceData(EntityIndex entity) const; + + bool changeCar(EntityData::EntityCallData * data, EntityIndex entity, CarIndex car, EntityPosition position, bool increment, EntityPosition newPosition, CarIndex newCar) const; + + void getSequenceName(EntityIndex entity, EntityDirection direction, Common::String &sequence1, Common::String &sequence2) const; + + void updatePositionsEnter(EntityIndex entity, CarIndex car, Position position1, Position position2, Position position3, Position position4); + void updatePositionsExit(EntityIndex entity, CarIndex car, Position position1, Position position2); + + void resetSequences(EntityIndex entity) const; + + bool checkPosition(EntityPosition position) const; + bool checkSequenceFromPosition(EntityIndex entity) const; + EntityPosition getEntityPositionFromCurrentPosition() const; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_ENTITIES_H diff --git a/engines/lastexpress/game/fight.cpp b/engines/lastexpress/game/fight.cpp new file mode 100644 index 0000000000..8fa711df1c --- /dev/null +++ b/engines/lastexpress/game/fight.cpp @@ -0,0 +1,1587 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/fight.h" + +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/scene.h" +#include "lastexpress/data/sequence.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +#include "common/func.h" + +namespace LastExpress { + +#define CALL_FUNCTION0(fighter, name) \ + (*fighter->name)(fighter) + +#define CALL_FUNCTION1(fighter, name, a) \ + (*fighter->name)(fighter, a) + +#define REGISTER_PLAYER_FUNCTIONS(name) \ + if (!_data) \ + error("Fight::load##namePlayer - invalid data!"); \ + _data->player->handleAction = new Common::Functor2Mem<Fighter *, FightAction, void, Fight>(this, &Fight::handleAction##name); \ + _data->player->update = new Common::Functor1Mem<Fighter *, void, Fight>(this, &Fight::update##name); \ + _data->player->canInteract = new Common::Functor2Mem<Fighter const *, FightAction, bool, Fight>(this, &Fight::canInteract##name); + +#define REGISTER_OPPONENT_FUNCTIONS(name) \ + if (!_data) \ + error("Fight::load##nameOpponent - invalid data!"); \ + _data->opponent->handleAction = new Common::Functor2Mem<Fighter *, FightAction, void, Fight>(this, &Fight::handleOpponentAction##name); \ + _data->opponent->update = new Common::Functor1Mem<Fighter *, void, Fight>(this, &Fight::updateOpponent##name); \ + _data->opponent->canInteract = new Common::Functor2Mem<Fighter const *, FightAction, bool, Fight>(this, &Fight::canInteract); + +#define CHECK_SEQUENCE2(fighter, value) \ + (fighter->frame->getInfo()->field_33 & value) + +Fight::Fight(LastExpressEngine *engine) : _engine(engine), _data(NULL), _endType(kFightEndLost), _state(0), _handleTimer(false) {} + +Fight::~Fight() { + clearData(); + _data = NULL; + + // Zero passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Events +////////////////////////////////////////////////////////////////////////// + +void Fight::eventMouse(const Common::Event &ev) { + if (!_data || _data->index) + return; + + // TODO move all the egg handling to inventory functions + + getFlags()->mouseLeftClick = false; + getFlags()->shouldRedraw = false; + getFlags()->mouseRightClick = false; + + if (ev.mouse.x < 608 || ev.mouse.y < 448 || ev.mouse.x >= 640 || ev.mouse.x >= 480) { + + // Handle right button click + if (ev.type == Common::EVENT_RBUTTONUP) { + getSound()->removeFromQueue(kEntityTables0); + setStopped(); + + getGlobalTimer() ? _state = 0 : ++_state; + + getFlags()->mouseRightClick = true; + } + + if (_handleTimer) { + // Timer expired => show with full brightness + if (!getGlobalTimer()) + getInventory()->drawEgg(); + + _handleTimer = false; + } + + // Check hotspots + Scene *scene = getScenes()->get(getState()->scene); + SceneHotspot *hotspot = NULL; + + if (!scene->checkHotSpot(ev.mouse, &hotspot)) { + _engine->getCursor()->setStyle(kCursorNormal); + } else { + _engine->getCursor()->setStyle((CursorStyle)hotspot->cursor); + + // Call player function + if (CALL_FUNCTION1(_data->player, canInteract, (FightAction)hotspot->action)) { + if (ev.type == Common::EVENT_LBUTTONUP) + CALL_FUNCTION1(_data->player, handleAction, (FightAction)hotspot->action); + } else { + _engine->getCursor()->setStyle(kCursorNormal); + } + } + } else { + // Handle clicks on menu icon + + if (!_handleTimer) { + // Timer expired => show with full brightness + if (!getGlobalTimer()) + getInventory()->drawEgg(); + + _handleTimer = true; + } + + // Stop fight if clicked + if (ev.type == Common::EVENT_LBUTTONUP) { + _handleTimer = false; + getSound()->removeFromQueue(kEntityTables0); + bailout(kFightEndExit); + } + + // Reset timer on right click + if (ev.type == Common::EVENT_RBUTTONUP) { + if (getGlobalTimer()) { + if (getSound()->isBuffered("TIMER")) + getSound()->removeFromQueue("TIMER"); + + setGlobalTimer(900); + } + } + } + + getFlags()->shouldRedraw = true; +} + +void Fight::eventTick(const Common::Event &ev) { + handleTick(ev, true); +} + +void Fight::handleTick(const Common::Event &ev, bool isProcessing) { + // TODO move all the egg handling to inventory functions + + // Blink egg + if (getGlobalTimer()) { + warning("Fight::handleMouseMove - egg blinking not implemented!"); + } + + if (!_data || _data->index) + return; + + SceneHotspot *hotspot = NULL; + if (!getScenes()->get(getState()->scene)->checkHotSpot(ev.mouse, &hotspot) || !CALL_FUNCTION1(_data->player, canInteract, (FightAction)hotspot->action)) { + _engine->getCursor()->setStyle(kCursorNormal); + } else { + _engine->getCursor()->setStyle((CursorStyle)hotspot->cursor); + } + + CALL_FUNCTION0(_data->player, update); + CALL_FUNCTION0(_data->opponent, update); + + // Draw sequences + if (!_data->isRunning) + return; + + if (isProcessing) + getScenes()->drawFrames(true); + + if (_data->index) { + // Set next sequence name index + _data->index--; + _data->sequences[_data->index] = loadSequence(_data->names[_data->index]); + } +} + +////////////////////////////////////////////////////////////////////////// +// Setup +////////////////////////////////////////////////////////////////////////// + +Fight::FightEndType Fight::setup(FightType type) { + if (_data) + error("Fight::setup - calling fight setup again while a fight is already in progress!"); + + ////////////////////////////////////////////////////////////////////////// + // Prepare UI & state + if (_state >= 5 && (type == kFightSalko || type == kFightVesna)) { + _state = 0; + return kFightEndWin; + } + + getInventory()->showHourGlass(); + // TODO events function + getFlags()->flag_0 = false; + getFlags()->mouseRightClick = false; + getEntities()->reset(); + + // Compute scene to use + SceneIndex sceneIndex; + switch(type) { + default: + sceneIndex = kSceneFightDefault; + break; + + case kFightMilos: + sceneIndex = (getObjects()->get(kObjectCompartment1).location2 < kObjectLocation3) ? kSceneFightMilos : kSceneFightMilosBedOpened; + break; + + case kFightAnna: + sceneIndex = kSceneFightAnna; + break; + + case kFightIvo: + sceneIndex = kSceneFightIvo; + break; + + case kFightSalko: + sceneIndex = kSceneFightSalko; + break; + + case kFightVesna: + sceneIndex = kSceneFightVesna; + break; + } + + if (getFlags()->shouldRedraw) { + getFlags()->shouldRedraw = false; + askForRedraw(); + //redrawScreen(); + } + + // Load the scene object + Scene *scene = getScenes()->get(sceneIndex); + + // Update game entities and state + getEntityData(kEntityPlayer)->entityPosition = scene->entityPosition; + getEntityData(kEntityPlayer)->location = scene->location; + + getState()->scene = sceneIndex; + + getFlags()->flag_3 = true; + + // Draw the scene + _engine->getGraphicsManager()->draw(scene, GraphicsManager::kBackgroundC); + // FIXME move to start of fight? + askForRedraw(); + redrawScreen(); + + ////////////////////////////////////////////////////////////////////////// + // Setup the fight + _data = new FightData; + loadData(type); + + // Show opponents & egg button + Common::Event emptyEvent; + handleTick(emptyEvent, false); + getInventory()->drawEgg(); + + // Start fight + _endType = kFightEndLost; + while (_data->isRunning) { + if (_engine->handleEvents()) + continue; + + getSound()->updateQueue(); + } + + // Cleanup after fight is over + clearData(); + + return _endType; +} + +////////////////////////////////////////////////////////////////////////// +// Status +////////////////////////////////////////////////////////////////////////// + +void Fight::setStopped() { + if (_data) + _data->isRunning = false; +} + +void Fight::bailout(FightEndType type) { + _state = 0; + _endType = type; + setStopped(); +} + +////////////////////////////////////////////////////////////////////////// +// Cleanup +////////////////////////////////////////////////////////////////////////// + +void Fight::clearData() { + if (!_data) + return; + + // Clear data + clearSequences(_data->player); + clearSequences(_data->opponent); + + delete _data->player; + delete _data->opponent; + + delete _data; + _data = NULL; + + _engine->restoreEventHandlers(); +} + +void Fight::clearSequences(Fighter *combatant) const { + if (!combatant) + return; + + // The original game resets the function pointers to default values, just before deleting the struct + getScenes()->removeAndRedraw(&combatant->frame, false); + + // Free sequences + for (int i = 0; i < (int)combatant->sequences.size(); i++) + delete combatant->sequences[i]; +} + +////////////////////////////////////////////////////////////////////////// +// Drawing +////////////////////////////////////////////////////////////////////////// + +void Fight::setSequenceAndDraw(Fighter *combatant, uint32 sequenceIndex, FightSequenceType type) const { + if (combatant->sequences.size() < sequenceIndex) + return; + + switch (type) { + default: + break; + + case kFightSequenceType0: + if (combatant->sequenceIndex) + return; + + combatant->sequence = combatant->sequences[sequenceIndex]; + combatant->sequenceIndex = sequenceIndex; + draw(combatant); + break; + + case kFightSequenceType1: + combatant->sequence = combatant->sequences[sequenceIndex]; + combatant->sequenceIndex = sequenceIndex; + combatant->sequenceIndex2 = 0; + draw(combatant); + break; + + case kFightSequenceType2: + combatant->sequenceIndex2 = sequenceIndex; + break; + } +} + +void Fight::draw(Fighter *combatant) const { + getScenes()->removeAndRedraw(&combatant->frame, false); + + combatant->frameIndex = 0; + combatant->field_24 = 0; +} + +////////////////////////////////////////////////////////////////////////// +// Loading +////////////////////////////////////////////////////////////////////////// + +void Fight::loadData(FightType type) { + if (!_data) + error("Fight::loadData - invalid data!"); + + switch (type) { + default: + break; + + case kFightMilos: + loadMilosPlayer(); + loadMilosOpponent(); + break; + + case kFightAnna: + loadAnnaPlayer(); + loadAnnaOpponent(); + break; + + case kFightIvo: + loadIvoPlayer(); + loadIvoOpponent(); + break; + + case kFightSalko: + loadSalkoPlayer(); + loadSalkoOpponent(); + break; + + case kFightVesna: + loadVesnaPlayer(); + loadVesnaOpponent(); + break; + } + + if (!_data->player || !_data->opponent) + error("Fight::loadData - error loading fight data (type=%d)", type); + + ////////////////////////////////////////////////////////////////////////// + // Start running the fight + _data->isRunning = true; + + if (_state < 5) { + setSequenceAndDraw(_data->player, 0, kFightSequenceType0); + setSequenceAndDraw(_data->opponent, 0, kFightSequenceType0); + goto end_load; + } + + switch(type) { + default: + break; + + case kFightMilos: + _data->opponent->countdown = 1; + setSequenceAndDraw(_data->player, 4, kFightSequenceType0); + setSequenceAndDraw(_data->opponent, 0, kFightSequenceType0); + break; + + case kFightIvo: + _data->opponent->countdown = 1; + setSequenceAndDraw(_data->player, 3, kFightSequenceType0); + setSequenceAndDraw(_data->opponent, 6, kFightSequenceType0); + break; + + case kFightVesna: + _data->opponent->countdown = 1; + setSequenceAndDraw(_data->player, 0, kFightSequenceType0); + setSequenceAndDraw(_data->player, 3, kFightSequenceType2); + setSequenceAndDraw(_data->opponent, 5, kFightSequenceType0); + break; + } + +end_load: + // Setup event handlers + _engine->backupEventHandlers(); + SET_EVENT_HANDLERS(Fight, this); +} + +////////////////////////////////////////////////////////////////////////// +// Shared +////////////////////////////////////////////////////////////////////////// +void Fight::processFighter(Fighter *fighter) { + if (!_data) + error("Fight::processFighter - invalid data!"); + + if (!fighter->sequence) { + if (fighter->frame) { + getScenes()->removeFromQueue(fighter->frame); + getScenes()->setCoordinates(fighter->frame); + } + SAFE_DELETE(fighter->frame); + return; + } + + if (fighter->sequence->count() <= fighter->frameIndex) { + switch(fighter->action) { + default: + break; + + case kFightAction101: + setSequenceAndDraw(fighter, fighter->sequenceIndex2, kFightSequenceType1); + fighter->sequenceIndex2 = 0; + break; + + case kFightActionResetFrame: + fighter->frameIndex = 0; + break; + + case kFightAction103: + setSequenceAndDraw(fighter, 0, kFightSequenceType1); + CALL_FUNCTION1(fighter, handleAction, kFightAction101); + setSequenceAndDraw(fighter->opponent, 0, kFightSequenceType1); + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction101); + CALL_FUNCTION0(fighter->opponent, update); + break; + + case kFightActionWin: + bailout(kFightEndWin); + break; + + case kFightActionLost: + bailout(kFightEndLost); + break; + } + } + + if (_data->isRunning) { + + // Get the current sequence frame + SequenceFrame *frame = new SequenceFrame(fighter->sequence, (uint16)fighter->frameIndex); + frame->getInfo()->location = 1; + + if (fighter->frame == frame) { + delete frame; + return; + } + + getSound()->playFightSound(frame->getInfo()->soundAction, frame->getInfo()->field_31); + + // Add current frame to queue and advance + getScenes()->addToQueue(frame); + fighter->frameIndex++; + + if (fighter->frame) { + getScenes()->removeFromQueue(fighter->frame); + + if (!frame->getInfo()->field_2E) + getScenes()->setCoordinates(fighter->frame); + } + + // Replace by new frame + delete fighter->frame; + fighter->frame = frame; + } +} + +void Fight::handleAction(Fighter *fighter, FightAction action) { + switch (action) { + default: + return; + + case kFightAction101: + break; + + case kFightActionResetFrame: + fighter->countdown--; + break; + + case kFightAction103: + CALL_FUNCTION1(fighter->opponent, handleAction, kFightActionResetFrame); + break; + + case kFightActionWin: + _endType = kFightEndWin; + CALL_FUNCTION1(fighter->opponent, handleAction, kFightActionResetFrame); + break; + + case kFightActionLost: + _endType = kFightEndLost; + CALL_FUNCTION1(fighter->opponent, handleAction, kFightActionResetFrame); + break; + } + + // Update action + fighter->action = action; +} + +bool Fight::canInteract(Fighter const *fighter, FightAction /*= (FightAction)0*/ ) { + return (fighter->action == kFightAction101 && !fighter->sequenceIndex); +} + +void Fight::update(Fighter *fighter) { + + processFighter(fighter); + + if (fighter->frame) + fighter->frame->getInfo()->location = (fighter->action == kFightActionResetFrame ? 2 : 0); +} + +void Fight::updateOpponent(Fighter *fighter) { + + // This is an opponent struct! + Opponent *opponent = (Opponent *)fighter; + + processFighter(opponent); + + if (opponent->field_38 && !opponent->sequenceIndex) + opponent->field_38--; + + if (fighter->frame) + fighter->frame->getInfo()->location = 1; +} + +////////////////////////////////////////////////////////////////////////// +// Milos +////////////////////////////////////////////////////////////////////////// + +void Fight::loadMilosPlayer() { + REGISTER_PLAYER_FUNCTIONS(Milos) + + _data->player->sequences.push_back(loadSequence("2001cr.seq")); + _data->player->sequences.push_back(loadSequence("2001cdl.seq")); + _data->player->sequences.push_back(loadSequence("2001cdr.seq")); + _data->player->sequences.push_back(loadSequence("2001cdm.seq")); + _data->player->sequences.push_back(loadSequence("2001csgr.seq")); + _data->player->sequences.push_back(loadSequence("2001csgl.seq")); + _data->player->sequences.push_back(loadSequence("2001dbk.seq")); +} + +void Fight::loadMilosOpponent() { + REGISTER_OPPONENT_FUNCTIONS(Milos) + + _data->opponent->sequences.push_back(loadSequence("2001or.seq")); + _data->opponent->sequences.push_back(loadSequence("2001oal.seq")); + _data->opponent->sequences.push_back(loadSequence("2001oam.seq")); + _data->opponent->sequences.push_back(loadSequence("2001okl.seq")); + _data->opponent->sequences.push_back(loadSequence("2001okm.seq")); + _data->opponent->sequences.push_back(loadSequence("2001dbk.seq")); + _data->opponent->sequences.push_back(loadSequence("2001wbk.seq")); + + getSound()->playSound(kEntityTables0, "MUS027", SoundManager::kFlagDefault); + + _data->opponent->field_38 = 35; +} + +void Fight::handleActionMilos(Fighter *fighter, FightAction action) { + switch (action) { + default: + handleAction(fighter, action); + return; + + case kFightAction1: + if (fighter->sequenceIndex != 1 || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 6, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 3, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction2: + if ((fighter->sequenceIndex != 2 && fighter->sequenceIndex != 3) || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 6, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 4, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction128: + if (fighter->sequenceIndex != 1 || CHECK_SEQUENCE2(fighter, 4) || fighter->opponent->sequenceIndex != 1) { + switch (fighter->opponent->sequenceIndex) { + default: + setSequenceAndDraw(fighter, rnd(3) + 1, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(fighter, 1, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(fighter, 3, kFightSequenceType0); + break; + } + } else { + setSequenceAndDraw(fighter, 4, kFightSequenceType1); + CALL_FUNCTION0(fighter, update); + } + break; + } +} + +void Fight::updateMilos(Fighter *fighter) { + if (fighter->frame && CHECK_SEQUENCE2(fighter, 2)) { + + // Draw sequences + if (fighter->opponent->countdown <= 0) { + setSequenceAndDraw(fighter, 5, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 6, kFightSequenceType1); + + getSound()->removeFromQueue(kEntityTables0); + getSound()->playSound(kEntityTrain, "MUS029", SoundManager::kFlagDefault); + + CALL_FUNCTION1(fighter, handleAction, kFightActionWin); + } + + if (fighter->sequenceIndex == 4) { + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction4); + _endType = kFightEndLost; + } + } + + update(fighter); +} + +bool Fight::canInteractMilos(Fighter const *fighter, FightAction action) { + if (!_data) + error("Fight::canInteractMilos - invalid data!"); + + if (action != kFightAction128 + || _data->player->sequenceIndex != 1 + || !fighter->frame + || CHECK_SEQUENCE2(fighter, 4) + || fighter->opponent->sequenceIndex != 1) { + return canInteract(fighter); + } + + _engine->getCursor()->setStyle(kCursorHand); + + return true; +} + +void Fight::handleOpponentActionMilos(Fighter *fighter, FightAction action) { + if (action == kFightAction4) { + setSequenceAndDraw(fighter, 5, kFightSequenceType1); + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + } else { + if (action != kFightAction131) + handleAction(fighter, action); + } +} + +void Fight::updateOpponentMilos(Fighter *fighter) { + // This is an opponent struct! + Opponent *opponent = (Opponent *)fighter; + + if (!opponent->field_38 && CALL_FUNCTION1(opponent, canInteract, kFightAction1) && !opponent->sequenceIndex2) { + + if (opponent->opponent->field_34 >= 2) { + switch (rnd(5)) { + default: + break; + + case 0: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType1); + break; + + case 3: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 4: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + } + } else { + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + } + + // Update field_38 + if (opponent->opponent->field_34 < 5) + opponent->field_38 = 6 * (5 - opponent->opponent->field_34); + else + opponent->field_38 = 0; + } + + if (opponent->frame && CHECK_SEQUENCE2(opponent, 2)) { + if (opponent->sequenceIndex == 1 || opponent->sequenceIndex == 2) + CALL_FUNCTION1(opponent->opponent, handleAction, (FightAction)opponent->sequenceIndex); + + if (opponent->opponent->countdown <= 0) { + getSound()->removeFromQueue(kEntityTables0); + CALL_FUNCTION1(opponent, handleAction, kFightActionLost); + } + } + + updateOpponent(opponent); +} + +////////////////////////////////////////////////////////////////////////// +// Anna +////////////////////////////////////////////////////////////////////////// + +void Fight::loadAnnaPlayer() { + if (!_data) + error("Fight::loadAnnaPlayer - invalid data!"); + + // Special case: we are using some shared functions directly + _data->player->handleAction = new Common::Functor2Mem<Fighter *, FightAction, void, Fight>(this, &Fight::handleActionAnna); + _data->player->update = new Common::Functor1Mem<Fighter *, void, Fight>(this, &Fight::update); + _data->player->canInteract = new Common::Functor2Mem<Fighter const *, FightAction, bool, Fight>(this, &Fight::canInteract); + + _data->player->sequences.push_back(loadSequence("2002cr.seq")); + _data->player->sequences.push_back(loadSequence("2002cdl.seq")); + _data->player->sequences.push_back(loadSequence("2002cdr.seq")); + _data->player->sequences.push_back(loadSequence("2002cdm.seq")); + _data->player->sequences.push_back(loadSequence("2002lbk.seq")); +} + +void Fight::loadAnnaOpponent() { + if (!_data) + error("Fight::loadAnnaOpponent - invalid data!"); + + // Special case: we are using some shared functions directly + _data->opponent->handleAction = new Common::Functor2Mem<Fighter *, FightAction, void, Fight>(this, &Fight::handleAction); + _data->opponent->update = new Common::Functor1Mem<Fighter *, void, Fight>(this, &Fight::updateOpponentAnna); + _data->opponent->canInteract = new Common::Functor2Mem<Fighter const *, FightAction, bool, Fight>(this, &Fight::canInteract); + + _data->opponent->sequences.push_back(loadSequence("2002or.seq")); + _data->opponent->sequences.push_back(loadSequence("2002oal.seq")); + _data->opponent->sequences.push_back(loadSequence("2002oam.seq")); + _data->opponent->sequences.push_back(loadSequence("2002oar.seq")); + _data->opponent->sequences.push_back(loadSequence("2002okr.seq")); + _data->opponent->sequences.push_back(loadSequence("2002okml.seq")); + _data->opponent->sequences.push_back(loadSequence("2002okm.seq")); + + getSound()->playSound(kEntityTables0, "MUS030", SoundManager::kFlagDefault); + + _data->opponent->field_38 = 30; +} + +void Fight::handleActionAnna(Fighter *fighter, FightAction action) { + switch (action) { + default: + handleAction(fighter, action); + return; + + case kFightAction1: + if ((fighter->sequenceIndex != 1 && fighter->sequenceIndex != 3) || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 4, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 4, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction2: + if ((fighter->sequenceIndex != 2 && fighter->sequenceIndex != 3) || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 4, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 5, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction3: + if ((fighter->sequenceIndex != 2 && fighter->sequenceIndex != 1) || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 4, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 6, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction128: + switch (fighter->opponent->sequenceIndex) { + default: + setSequenceAndDraw(fighter, 3, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(fighter, 1, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(fighter, 3, kFightSequenceType0); + break; + + case 3: + setSequenceAndDraw(fighter, 2, kFightSequenceType0); + break; + } + break; + } + + if (fighter->field_34 > 4) { + getSound()->removeFromQueue(kEntityTables0); + bailout(kFightEndWin); + } +} + +void Fight::updateOpponentAnna(Fighter *fighter) { + // This is an opponent struct! + Opponent *opponent = (Opponent *)fighter; + + if (!opponent->field_38 && CALL_FUNCTION1(opponent, canInteract, kFightAction1) && !opponent->sequenceIndex2) { + + if (opponent->opponent->field_34 >= 2) { + switch (rnd(6)) { + default: + break; + + case 0: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(opponent, 3, kFightSequenceType0); + break; + + case 3: + setSequenceAndDraw(opponent, 3, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 4: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 5: + setSequenceAndDraw(opponent, 3, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + } + } + + // Update field_38 + opponent->field_38 = (int32)rnd(15); + } + + if (opponent->frame && CHECK_SEQUENCE2(opponent, 2)) { + if (opponent->sequenceIndex == 1 || opponent->sequenceIndex == 2 || opponent->sequenceIndex == 3) + CALL_FUNCTION1(opponent->opponent, handleAction, (FightAction)opponent->sequenceIndex); + + if (opponent->opponent->countdown <= 0) { + getSound()->removeFromQueue(kEntityTables0); + CALL_FUNCTION1(opponent, handleAction, kFightActionLost); + } + } + + updateOpponent(opponent); +} + +////////////////////////////////////////////////////////////////////////// +// Ivo +////////////////////////////////////////////////////////////////////////// + +void Fight::loadIvoPlayer() { + REGISTER_PLAYER_FUNCTIONS(Ivo) + + _data->player->sequences.push_back(loadSequence("2003cr.seq")); + _data->player->sequences.push_back(loadSequence("2003car.seq")); + _data->player->sequences.push_back(loadSequence("2003cal.seq")); + _data->player->sequences.push_back(loadSequence("2003cdr.seq")); + _data->player->sequences.push_back(loadSequence("2003cdm.seq")); + _data->player->sequences.push_back(loadSequence("2003chr.seq")); + _data->player->sequences.push_back(loadSequence("2003chl.seq")); + _data->player->sequences.push_back(loadSequence("2003ckr.seq")); + _data->player->sequences.push_back(loadSequence("2003lbk.seq")); + _data->player->sequences.push_back(loadSequence("2003fbk.seq")); + + _data->player->countdown = 5; +} + +void Fight::loadIvoOpponent() { + REGISTER_OPPONENT_FUNCTIONS(Ivo) + + _data->opponent->sequences.push_back(loadSequence("2003or.seq")); + _data->opponent->sequences.push_back(loadSequence("2003oal.seq")); + _data->opponent->sequences.push_back(loadSequence("2003oar.seq")); + _data->opponent->sequences.push_back(loadSequence("2003odm.seq")); + _data->opponent->sequences.push_back(loadSequence("2003okl.seq")); + _data->opponent->sequences.push_back(loadSequence("2003okj.seq")); + _data->opponent->sequences.push_back(loadSequence("blank.seq")); + _data->opponent->sequences.push_back(loadSequence("csdr.seq")); + _data->opponent->sequences.push_back(loadSequence("2003l.seq")); + + getSound()->playSound(kEntityTables0, "MUS032", SoundManager::kFlagDefault); + + _data->opponent->countdown = 5; + _data->opponent->field_38 = 15; +} + +void Fight::handleActionIvo(Fighter *fighter, FightAction action) { + switch (action) { + default: + handleAction(fighter, action); + return; + + case kFightAction1: + if (fighter->sequenceIndex != 1 || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 7, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 4, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } + break; + + case kFightAction2: + if ((fighter->sequenceIndex != 2 && fighter->sequenceIndex != 3) || CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 7, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 5, kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } + break; + + case kFightAction128: + switch (fighter->opponent->sequenceIndex) { + default: + case 1: + setSequenceAndDraw(fighter, 1, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(fighter, 2, kFightSequenceType0); + break; + } + break; + + case kFightAction129: + setSequenceAndDraw(fighter, (fighter->opponent->countdown > 1) ? 4 : 3, fighter->sequenceIndex ? kFightSequenceType2 : kFightSequenceType0); + break; + + case kFightAction130: + setSequenceAndDraw(fighter, 3, fighter->sequenceIndex ? kFightSequenceType2 : kFightSequenceType0); + break; + } +} + +void Fight::updateIvo(Fighter *fighter) { + + if ((fighter->sequenceIndex == 3 || fighter->sequenceIndex == 4) && !fighter->frameIndex) + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction131); + + if (fighter->frame && CHECK_SEQUENCE2(fighter, 2)) { + + // Draw sequences + if (fighter->opponent->countdown <= 0) { + setSequenceAndDraw(fighter, 9, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, 8, kFightSequenceType1); + getSound()->removeFromQueue(kEntityTables0); + + CALL_FUNCTION1(fighter, handleAction, kFightActionWin); + return; + } + + if (fighter->sequenceIndex == 3 || fighter->sequenceIndex == 4) + CALL_FUNCTION1(fighter->opponent, handleAction, (FightAction)fighter->sequenceIndex); + } + + update(fighter); +} + +bool Fight::canInteractIvo(Fighter const *fighter, FightAction action) { + if (action == kFightAction129 || action == kFightAction130) + return (fighter->sequenceIndex >= 8); + + return canInteract(fighter); +} + +void Fight::handleOpponentActionIvo(Fighter *fighter, FightAction action) { + // This is an opponent struct! + Opponent *opponent = (Opponent *)fighter; + + switch (action) { + default: + handleAction(fighter, action); + break; + + case kFightAction3: + if ((opponent->sequenceIndex != 1 && opponent->sequenceIndex != 3) || CHECK_SEQUENCE2(opponent, 4)) { + setSequenceAndDraw(opponent, 6, kFightSequenceType1); + setSequenceAndDraw(opponent->opponent, 6, kFightSequenceType1); + CALL_FUNCTION1(opponent->opponent, handleAction, kFightAction103); + } + break; + + case kFightAction4: + if ((opponent->sequenceIndex != 2 && opponent->sequenceIndex != 3) || CHECK_SEQUENCE2(opponent, 4)) { + setSequenceAndDraw(opponent, 6, kFightSequenceType1); + setSequenceAndDraw(opponent->opponent, 5, kFightSequenceType1); + CALL_FUNCTION1(opponent->opponent, handleAction, kFightAction103); + } + break; + + case kFightAction131: + if (opponent->sequenceIndex) + break; + + if (rnd(100) <= (unsigned int)(opponent->countdown > 2 ? 60 : 75)) { + setSequenceAndDraw(opponent, 3 , kFightSequenceType1); + if (opponent->opponent->sequenceIndex == 4) + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + } + break; + } +} + +void Fight::updateOpponentIvo(Fighter *fighter) { + // This is an opponent struct! + Opponent *opponent = (Opponent *)fighter; + + if (!opponent->field_38 && CALL_FUNCTION1(opponent, canInteract, kFightAction1) && !opponent->sequenceIndex2) { + + if (opponent->opponent->field_34 >= 2) { + switch (rnd(5)) { + default: + break; + + case 0: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 3: + setSequenceAndDraw(opponent, 0, kFightSequenceType2); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + + case 4: + setSequenceAndDraw(opponent, 0, kFightSequenceType1); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + } + } + + // Update field_38 + opponent->field_38 = 3 * opponent->countdown + (int32)rnd(10); + } + + if (opponent->frame && CHECK_SEQUENCE2(opponent, 2)) { + + if (opponent->opponent->countdown <= 0) { + setSequenceAndDraw(opponent, 7, kFightSequenceType1); + setSequenceAndDraw(opponent->opponent, 8, kFightSequenceType1); + getSound()->removeFromQueue(kEntityTables0); + + CALL_FUNCTION1(opponent->opponent, handleAction, kFightActionWin); + + return; + } + + if (opponent->sequenceIndex == 1 || opponent->sequenceIndex == 2) + CALL_FUNCTION1(opponent->opponent, handleAction, (FightAction)opponent->sequenceIndex); + } + + updateOpponent(opponent); +} + +////////////////////////////////////////////////////////////////////////// +// Salko +////////////////////////////////////////////////////////////////////////// + +void Fight::loadSalkoPlayer() { + REGISTER_PLAYER_FUNCTIONS(Salko) + + _data->player->sequences.push_back(loadSequence("2004cr.seq")); + _data->player->sequences.push_back(loadSequence("2004cdr.seq")); + _data->player->sequences.push_back(loadSequence("2004chj.seq")); + _data->player->sequences.push_back(loadSequence("2004bk.seq")); + + _data->player->countdown = 2; +} + +void Fight::loadSalkoOpponent() { + REGISTER_OPPONENT_FUNCTIONS(Salko) + + _data->opponent->sequences.push_back(loadSequence("2004or.seq")); + _data->opponent->sequences.push_back(loadSequence("2004oam.seq")); + _data->opponent->sequences.push_back(loadSequence("2004oar.seq")); + _data->opponent->sequences.push_back(loadSequence("2004okr.seq")); + _data->opponent->sequences.push_back(loadSequence("2004ohm.seq")); + _data->opponent->sequences.push_back(loadSequence("blank.seq")); + + getSound()->playSound(kEntityTables0, "MUS035", SoundManager::kFlagDefault); + + _data->opponent->countdown = 3; + _data->opponent->field_38 = 30; +} + +void Fight::handleActionSalko(Fighter *fighter, FightAction action) { + switch (action) { + default: + handleAction(fighter, action); + return; + + case kFightAction1: + case kFightAction2: + if (fighter->sequenceIndex != 1 && CHECK_SEQUENCE2(fighter, 4)) { + fighter->field_34 = 0; + + setSequenceAndDraw(fighter, 3, kFightSequenceType1); + setSequenceAndDraw(fighter->opponent, (action == kFightAction1 ? 3 : 4), kFightSequenceType1); + + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + + if (action == kFightAction2) + fighter->countdown= 0; + + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction5: + if (fighter->sequenceIndex != 3) { + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } + break; + + case kFightAction128: + setSequenceAndDraw(fighter, 1, kFightSequenceType0); + fighter->field_34 = 0; + break; + + case kFightAction131: + setSequenceAndDraw(fighter, 2, (fighter->sequenceIndex ? kFightSequenceType2 : kFightSequenceType0)); + break; + } +} + +void Fight::updateSalko(Fighter *fighter) { + update(fighter); + + // The original doesn't check for currentSequence2 != NULL (might not happen when everything is working properly, but crashes with our current implementation) + if (fighter->frame && CHECK_SEQUENCE2(fighter, 2)) { + + if (fighter->opponent->countdown <= 0) { + getSound()->removeFromQueue(kEntityTables0); + bailout(kFightEndWin); + + return; + } + + if (fighter->sequenceIndex == 2) + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction2); + } +} + +bool Fight::canInteractSalko(Fighter const *fighter, FightAction action) { + if (action == kFightAction131) { + if (fighter->sequenceIndex == 1) { + if (fighter->opponent->countdown <= 0) + _engine->getCursor()->setStyle(kCursorHand); + + return true; + } + + return false; + } + + return canInteract(fighter); +} + +void Fight::handleOpponentActionSalko(Fighter *fighter, FightAction action) { + if (action == kFightAction2) { + setSequenceAndDraw(fighter, 5, kFightSequenceType1); + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + } else { + handleAction(fighter, action); + } +} + +void Fight::updateOpponentSalko(Fighter *fighter) { + // This is an opponent struct + Opponent *opponent = (Opponent *)fighter; + + if (!opponent->field_38 && CALL_FUNCTION1(opponent, canInteract, kFightAction1) && !opponent->sequenceIndex2) { + + switch (rnd(5)) { + default: + break; + + case 0: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + break; + + case 2: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 3: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + + case 4: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + } + + // Update field_38 + opponent->field_38 = 4 * opponent->countdown; + } + + if (opponent->frame && CHECK_SEQUENCE2(opponent, 2)) { + if (opponent->opponent->countdown <= 0) { + getSound()->removeFromQueue(kEntityTables0); + bailout(kFightEndLost); + + // Stop processing + return; + } + + if (opponent->sequenceIndex == 1 || opponent->sequenceIndex == 2) + CALL_FUNCTION1(opponent->opponent, handleAction, (FightAction)opponent->sequenceIndex); + } + + updateOpponent(opponent); +} + +////////////////////////////////////////////////////////////////////////// +// Vesna +////////////////////////////////////////////////////////////////////////// + +void Fight::loadVesnaPlayer() { + REGISTER_PLAYER_FUNCTIONS(Vesna) + + _data->player->sequences.push_back(loadSequence("2005cr.seq")); + _data->player->sequences.push_back(loadSequence("2005cdr.seq")); + _data->player->sequences.push_back(loadSequence("2005cbr.seq")); + _data->player->sequences.push_back(loadSequence("2005bk.seq")); + _data->player->sequences.push_back(loadSequence("2005cdm1.seq")); + _data->player->sequences.push_back(loadSequence("2005chl.seq")); +} + +void Fight::loadVesnaOpponent() { + REGISTER_OPPONENT_FUNCTIONS(Vesna) + + _data->opponent->sequences.push_back(loadSequence("2005or.seq")); + _data->opponent->sequences.push_back(loadSequence("2005oam.seq")); + _data->opponent->sequences.push_back(loadSequence("2005oar.seq")); + _data->opponent->sequences.push_back(loadSequence("2005okml.seq")); + _data->opponent->sequences.push_back(loadSequence("2005okr.seq")); + _data->opponent->sequences.push_back(loadSequence("2005odm1.seq")); + _data->opponent->sequences.push_back(loadSequence("2005csbm.seq")); + _data->opponent->sequences.push_back(loadSequence("2005oam4.seq")); + + getSound()->playSound(kEntityTables0, "MUS038", SoundManager::kFlagDefault); + + _data->opponent->countdown = 4; + _data->opponent->field_38 = 30; +} + +void Fight::handleActionVesna(Fighter *fighter, FightAction action) { + switch (action) { + default: + handleAction(fighter, action); + return; + + case kFightAction1: + if (fighter->sequenceIndex != 1) { + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction2: + if (fighter->sequenceIndex != 2) { + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } else { + fighter->field_34++; + } + break; + + case kFightAction5: + if (fighter->sequenceIndex != 3) { + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + CALL_FUNCTION0(fighter, update); + } + break; + + case kFightAction128: + if (fighter->sequenceIndex == 1 && fighter->opponent->sequenceIndex == 1 && CHECK_SEQUENCE2(fighter, 4)) { + setSequenceAndDraw(fighter, 5, kFightSequenceType1); + } else { + setSequenceAndDraw(fighter, (fighter->opponent->sequenceIndex == 5) ? 3 : 1, kFightSequenceType0); + } + break; + + case kFightAction132: + setSequenceAndDraw(fighter, 2, kFightSequenceType0); + break; + } + + if (fighter->field_34 > 10) { + setSequenceAndDraw(fighter->opponent, 5, kFightSequenceType2); + fighter->opponent->countdown = 1; + fighter->field_34 = 0; + } +} + +void Fight::updateVesna(Fighter *fighter) { + if (fighter->frame && CHECK_SEQUENCE2(fighter, 2)) { + + if (fighter->sequenceIndex == 3) + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction3); + + if (fighter->opponent->countdown <= 0) { + getSound()->removeFromQueue(kEntityTables0); + bailout(kFightEndWin); + return; + } + + if (fighter->sequenceIndex == 5) + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction5); + } + + update(fighter); +} + +bool Fight::canInteractVesna(Fighter const *fighter, FightAction action) { + if (action != kFightAction128) + return canInteract(fighter); + + if (fighter->sequenceIndex != 1) { + + if (fighter->opponent->sequenceIndex == 5) { + _engine->getCursor()->setStyle(kCursorDown); + return true; + } + + return canInteract(fighter); + } + + if (fighter->opponent->sequenceIndex == 1 && CHECK_SEQUENCE2(fighter, 4)) { + _engine->getCursor()->setStyle(kCursorPunchLeft); + return true; + } + + return false; +} + +void Fight::handleOpponentActionVesna(Fighter *fighter, FightAction action) { + switch (action) { + default: + handleAction(fighter, action); + break; + + case kFightAction3: + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + break; + + case kFightAction5: + setSequenceAndDraw(fighter, 7, kFightSequenceType1); + CALL_FUNCTION1(fighter->opponent, handleAction, kFightAction103); + if (fighter->countdown <= 1) + fighter->countdown = 1; + break; + + case kFightAction131: + break; + } +} + +void Fight::updateOpponentVesna(Fighter *fighter) { + // This is an opponent struct + Opponent *opponent = (Opponent *)fighter; + + if (!opponent->field_38 && CALL_FUNCTION1(opponent, canInteract, kFightAction1) && !opponent->sequenceIndex2) { + + if (opponent->opponent->field_34 == 1) { + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + } else { + switch (rnd(6)) { + default: + break; + + case 0: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + break; + + case 1: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + + case 2: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + break; + + case 3: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 4: + setSequenceAndDraw(opponent, 1, kFightSequenceType0); + setSequenceAndDraw(opponent, 2, kFightSequenceType2); + break; + + case 5: + setSequenceAndDraw(opponent, 2, kFightSequenceType0); + setSequenceAndDraw(opponent, 1, kFightSequenceType2); + break; + } + } + + // Update field_38 + opponent->field_38 = 4 * opponent->countdown; + } + + if (opponent->frame && CHECK_SEQUENCE2(opponent, 2)) { + if (opponent->sequenceIndex == 1 || opponent->sequenceIndex == 2 || opponent->sequenceIndex == 5) + CALL_FUNCTION1(opponent->opponent, handleAction, (FightAction)opponent->sequenceIndex); + + if (opponent->opponent->countdown <= 0) { + + switch (opponent->sequenceIndex) { + default: + break; + + case 1: + setSequenceAndDraw(opponent, 3, kFightSequenceType1); + break; + + case 2: + setSequenceAndDraw(opponent, 4, kFightSequenceType1); + break; + + case 5: + setSequenceAndDraw(opponent, 6, kFightSequenceType1); + break; + } + + setSequenceAndDraw(opponent->opponent, 4, kFightSequenceType1); + + CALL_FUNCTION1(opponent, handleAction, kFightActionLost); + CALL_FUNCTION0(opponent->opponent, update); + CALL_FUNCTION0(opponent, update); + + getSound()->removeFromQueue(kEntityTables0); + + // Stop processing + return; + } + } + + updateOpponent(opponent); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/fight.h b/engines/lastexpress/game/fight.h new file mode 100644 index 0000000000..32d734d57c --- /dev/null +++ b/engines/lastexpress/game/fight.h @@ -0,0 +1,269 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_FIGHT_H +#define LASTEXPRESS_FIGHT_H + +/* + Fight structure + --------------- + uint32 {4} - player struct + uint32 {4} - opponent struct + uint32 {4} - hasLost flag + + byte {1} - isRunning + + Fight participant structure + --------------------------- + uint32 {4} - function pointer + uint32 {4} - pointer to fight structure + uint32 {4} - pointer to opponent (fight participant structure) + uint32 {4} - array of sequences + uint32 {4} - number of sequences + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint16 {2} - ?? + uint16 {2} - ?? - only for opponent structure + uint32 {4} - ?? - only for opponent structure + +*/ + +#include "lastexpress/shared.h" + +#include "lastexpress/eventhandler.h" + +#include "common/array.h" + +namespace LastExpress { + +class LastExpressEngine; +class Sequence; +class SequenceFrame; + +////////////////////////////////////////////////////////////////////////// +// TODO : objectify! +class Fight : public EventHandler { +public: + enum FightEndType { + kFightEndWin = 0, + kFightEndLost = 1, + kFightEndExit = 2 + }; + + Fight(LastExpressEngine *engine); + ~Fight(); + + FightEndType setup(FightType type); + + void eventMouse(const Common::Event &ev); + void eventTick(const Common::Event &ev); + + void setStopped(); + void resetState() { _state = 0; } + +private: + enum FightSequenceType { + kFightSequenceType0 = 0, + kFightSequenceType1 = 1, + kFightSequenceType2 = 2 + }; + + enum FightAction { + kFightAction1 = 1, + kFightAction2 = 2, + kFightAction3 = 3, + kFightAction4 = 4, + kFightAction5 = 5, + kFightAction101 = 101, + kFightActionResetFrame = 102, + kFightAction103 = 103, + kFightActionWin = 104, + kFightActionLost = 105, + kFightAction128 = 128, + kFightAction129 = 129, + kFightAction130 = 130, + kFightAction131 = 131, + kFightAction132 = 132 + }; + + struct Fighter { + Common::Functor2<Fighter *, FightAction, void> *handleAction; + Common::Functor1<Fighter *, void> *update; + Common::Functor2<Fighter const *, FightAction, bool> *canInteract; + Fighter* opponent; + Common::Array<Sequence *> sequences; + uint32 sequenceIndex; + Sequence *sequence; + SequenceFrame *frame; + uint32 frameIndex; + uint32 field_24; + FightAction action; + uint32 sequenceIndex2; + int32 countdown; // countdown before loosing ? + uint32 field_34; + + Fighter() { + handleAction = NULL; + update = NULL; + canInteract = NULL; + + opponent = NULL; + + sequenceIndex = 0; + sequence = NULL; + frame = NULL; + frameIndex = 0; + + field_24 = 0; + + action = kFightAction101; + sequenceIndex2 = 0; + + countdown = 1; + + field_34 = 0; + } + }; + + // Opponent struct + struct Opponent : Fighter { + int32 field_38; + + Opponent() : Fighter() { + field_38 = 0; + } + }; + + struct FightData { + Fighter *player; + Opponent *opponent; + int32 index; + + Sequence *sequences[20]; + Common::String names[20]; + + bool isRunning; + + FightData() { + player = new Fighter(); + opponent = new Opponent(); + + // Set opponents + player->opponent = opponent; + opponent->opponent = player; + + index = 0; + + isRunning = false; + } + }; + + LastExpressEngine* _engine; + FightData *_data; + FightEndType _endType; + int _state; + + bool _handleTimer; + + // Events + void handleTick(const Common::Event &ev, bool unknown); + + // State + void bailout(FightEndType type); + + + // Drawing + void setSequenceAndDraw(Fighter *fighter, uint32 sequenceIndex, FightSequenceType type) const; + void draw(Fighter *fighter) const; + + // Cleanup + void clearData(); + void clearSequences(Fighter *fighter) const; + + ////////////////////////////////////////////////////////////////////////// + // Loading + void loadData(FightType type); + + // Shared + void processFighter(Fighter *fighter); + + // Default functions + void handleAction(Fighter *fighter, FightAction action); + void update(Fighter *fighter); + bool canInteract(Fighter const *fighter, FightAction = (FightAction)0); + void updateOpponent(Fighter *fighter); + + // Milos + void loadMilosPlayer(); + void loadMilosOpponent(); + void handleActionMilos(Fighter *fighter, FightAction action); + void updateMilos(Fighter *fighter); + bool canInteractMilos(Fighter const *fighter, FightAction action); + void handleOpponentActionMilos(Fighter *fighter, FightAction action); + void updateOpponentMilos(Fighter *fighter); + + // Anna + void loadAnnaPlayer(); + void loadAnnaOpponent(); + void handleActionAnna(Fighter *fighter, FightAction action); + void updateOpponentAnna(Fighter *fighter); + + // Ivo + void loadIvoPlayer(); + void loadIvoOpponent(); + void handleActionIvo(Fighter *fighter, FightAction action); + void updateIvo(Fighter *fighter); + bool canInteractIvo(Fighter const *fighter, FightAction action); + void handleOpponentActionIvo(Fighter *fighter, FightAction action); + void updateOpponentIvo(Fighter *fighter); + + // Salko + void loadSalkoPlayer(); + void loadSalkoOpponent(); + void handleActionSalko(Fighter *fighter, FightAction action); + void updateSalko(Fighter *fighter); + bool canInteractSalko(Fighter const *fighter, FightAction action); + void handleOpponentActionSalko(Fighter *fighter, FightAction action); + void updateOpponentSalko(Fighter *fighter); + + // Vesna + void loadVesnaPlayer(); + void loadVesnaOpponent(); + void handleActionVesna(Fighter *fighter, FightAction action); + void updateVesna(Fighter *fighter); + bool canInteractVesna(Fighter const *fighter, FightAction action); + void handleOpponentActionVesna(Fighter *fighter, FightAction action); + void updateOpponentVesna(Fighter *fighter); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_FIGHT_H diff --git a/engines/lastexpress/game/inventory.cpp b/engines/lastexpress/game/inventory.cpp new file mode 100644 index 0000000000..37020604f6 --- /dev/null +++ b/engines/lastexpress/game/inventory.cpp @@ -0,0 +1,594 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/inventory.h" + +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/scene.h" +#include "lastexpress/data/snd.h" + +#include "lastexpress/game/logic.h" +#include "lastexpress/game/menu.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + + +#define drawItem(x, y, index, brightness) { Icon icon((CursorStyle)(index)); icon.setPosition(x, y); icon.setBrightness(brightness); _engine->getGraphicsManager()->draw(&icon, GraphicsManager::kBackgroundInventory); } + +namespace LastExpress { + +Inventory::Inventory(LastExpressEngine *engine) : _engine(engine), _selectedItem(kItemNone), _highlightedItem(kItemNone), _opened(false), _visible(false), + _showingHourGlass(false), _blinkingEgg(false), _blinkingTime(0), _blinkingInterval(_defaultBlinkingInterval), _blinkingBrightness(100), + _flagUseMagnifier(false), _flag1(false), _flag2(false), _flagEggHightlighted(false), _itemScene(NULL) { + + _inventoryRect = Common::Rect(0, 0, 32, 32); + _menuRect = Common::Rect(608, 448, 640, 480); + _selectedRect = Common::Rect(44, 0, 76, 32); + + init(); +} + +Inventory::~Inventory() { + _itemScene = NULL; + + // Zero passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Inventory handling +////////////////////////////////////////////////////////////////////////// + +// Initialize inventory contents +void Inventory::init() { + // ID + _entries[kItemMatchBox].cursor = kCursorMatchBox; + _entries[kItemTelegram].cursor = kCursorTelegram; + _entries[kItemPassengerList].cursor = kCursorPassengerList; + _entries[kItemArticle].cursor = kCursorArticle; + _entries[kItemScarf].cursor = kCursorScarf; + _entries[kItemPaper].cursor = kCursorPaper; + _entries[kItemParchemin].cursor = kCursorParchemin; + _entries[kItemMatch].cursor = kCursorMatch; + _entries[kItemWhistle].cursor = kCursorWhistle; + _entries[kItemKey].cursor = kCursorKey; + _entries[kItemBomb].cursor = kCursorBomb; + _entries[kItemFirebird].cursor = kCursorFirebird; + _entries[kItemBriefcase].cursor = kCursorBriefcase; + _entries[kItemCorpse].cursor = kCursorCorpse; + + // Selectable + _entries[kItemMatchBox].isSelectable = true; + _entries[kItemMatch].isSelectable = true; + _entries[kItemTelegram].isSelectable = true; + _entries[kItemWhistle].isSelectable = true; + _entries[kItemKey].isSelectable = true; + _entries[kItemFirebird].isSelectable = true; + _entries[kItemBriefcase].isSelectable = true; + _entries[kItemCorpse].isSelectable = true; + _entries[kItemPassengerList].isSelectable = true; + + // Auto selection + _entries[kItem2].manualSelect = false; + _entries[kItem3].manualSelect = false; + _entries[kItem5].manualSelect = false; + _entries[kItem7].manualSelect = false; + _entries[kItem9].manualSelect = false; + _entries[kItem11].manualSelect = false; + _entries[kItemBeetle].manualSelect = false; + _entries[kItem17].manualSelect = false; + _entries[kItemFirebird].manualSelect = false; + _entries[kItemBriefcase].manualSelect = false; + _entries[kItemCorpse].manualSelect = false; + _entries[kItemGreenJacket].manualSelect = false; + _entries[kItem22].manualSelect = false; + + // Scene + _entries[kItemMatchBox].scene = kSceneMatchbox; + _entries[kItemTelegram].scene = kSceneTelegram; + _entries[kItemPassengerList].scene = kScenePassengerList; + _entries[kItemScarf].scene = kSceneScarf; + _entries[kItemParchemin].scene = kSceneParchemin; + _entries[kItemArticle].scene = kSceneArticle; + _entries[kItemPaper].scene = kScenePaper; + _entries[kItemFirebird].scene = kSceneFirebird; + _entries[kItemBriefcase].scene = kSceneBriefcase; + + // Has item + _entries[kItemTelegram].isPresent = true; + _entries[kItemArticle].isPresent = true; + + _selectedItem = kItemNone; +} + +// FIXME we need to draw cursors with full background opacity so that whatever is in the background is erased +// this saved us clearing some part of the background when switching between states + +// TODO if we draw inventory objects on screen, we need to load a new scene. +// Signal that the inventory has taken over the screen and stop processing mouse events after we have been called +bool Inventory::handleMouseEvent(const Common::Event &ev) { + + // Do not show inventory when on the menu screen + if (getMenu()->isShown() || !_visible) + return false; + + // Flag to know whether to restore the current cursor or not + bool insideInventory = false; + + // Egg (menu) + if (_menuRect.contains(ev.mouse)) { + insideInventory = true; + _engine->getCursor()->setStyle(kCursorNormal); + + // If clicked, show the menu + if (ev.type == Common::EVENT_LBUTTONUP) { + getSound()->playSound(kEntityPlayer, "LIB039"); + getMenu()->show(false, kSavegameTypeIndex, 0); + + // TODO can we return directly or do we need to make sure the state will be "valid" when we come back from the menu + return true; + } else { + // Highlight if needed + if (_highlightedItem != getMenu()->getGameId() + 39) { + _highlightedItem = (InventoryItem)(getMenu()->getGameId() + 39); + drawItem(608, 448, _highlightedItem, 100) + + askForRedraw(); + } + } + } else { + // remove highlight if needed + if (_highlightedItem == getMenu()->getGameId() + 39) { + drawItem(608, 448, _highlightedItem, 50) + _highlightedItem = kItemNone; + askForRedraw(); + } + } + + // Portrait (inventory) + if (_inventoryRect.contains(ev.mouse)) { + insideInventory = true; + _engine->getCursor()->setStyle(kCursorNormal); + + // If clicked, show pressed state and display inventory + if (ev.type == Common::EVENT_LBUTTONUP) { + open(); + } else { + // Highlight if needed + if (_highlightedItem != (InventoryItem)getProgress().portrait && !_opened) { + _highlightedItem = (InventoryItem)getProgress().portrait; + drawItem(0, 0, getProgress().portrait, 100) + + askForRedraw(); + } + } + } else { + // remove highlight if needed + if (_highlightedItem == (InventoryItem)getProgress().portrait && !_opened) { + drawItem(0, 0, getProgress().portrait, 50) + _highlightedItem = kItemNone; + askForRedraw(); + } + } + + // If the inventory is open, check all items rect to see if we need to highlight one / handle click + if (_opened) { + + // Always show normal cursor when the inventory is opened + insideInventory = true; + _engine->getCursor()->setStyle(kCursorNormal); + + bool selected = false; + + // Iterate over items + int16 y = 44; + for (int i = 1; i < 32; i++) { + if (!hasItem((InventoryItem)i)) + continue; + + if (Common::Rect(0, y, 32, 32 + y).contains(ev.mouse)) { + + // If released with an item highlighted, show this item + if (ev.type == Common::EVENT_LBUTTONUP) { + if (_entries[i].isSelectable) { + selected = true; + _selectedItem = (InventoryItem)i; + drawItem(44, 0, get(_selectedItem)->cursor, 100) + } + + examine((InventoryItem)i); + break; + } else { + if (_highlightedItem != i) { + drawItem(0, y, _entries[i].cursor, 100) + _highlightedItem = (InventoryItem)i; + askForRedraw(); + } + } + } else { + // Remove highlight if necessary + if (_highlightedItem == i) { + drawItem(0, y, _entries[i].cursor, 50) + _highlightedItem = kItemNone; + askForRedraw(); + } + } + + y += 40; + } + + // Right button is released: we need to close the inventory + if (ev.type == Common::EVENT_LBUTTONUP) { + + // Not on a selectable item: unselect the current item + if (!selected) + unselectItem(); + + close(); + } + } + + // Selected item + if (_selectedItem != kItemNone && _selectedRect.contains(ev.mouse)) { + insideInventory = true; + + // Show magnifier icon + _engine->getCursor()->setStyle(kCursorMagnifier); + + if (ev.type == Common::EVENT_LBUTTONUP) { + examine((InventoryItem)_selectedItem); + } + } + + // If the egg is blinking, refresh + if (_blinkingEgg) + drawEgg(); + + // Restore cursor + //if (!insideInventory) + // _engine->getCursor()->setStyle(getLogic()->getCursorStyle()); + + return insideInventory; +} + +////////////////////////////////////////////////////////////////////////// +// UI +////////////////////////////////////////////////////////////////////////// +void Inventory::show() { + clearBg(GraphicsManager::kBackgroundInventory); + askForRedraw(); + + // Show portrait (first draw, cannot be highlighted) + drawItem(0, 0, getProgress().portrait, 50) + + // Show selected item + if (_selectedItem != kItemNone) + drawItem(44, 0, _selectedItem, 100) + + drawEgg(); +} + +void Inventory::setPortrait(InventoryItem item) const { + getProgress().portrait = item; + drawItem(0, 0, getProgress().portrait, 50); +} + +void Inventory::blinkEgg(bool enabled) { + _blinkingEgg = enabled; + + // Reset state + _showingHourGlass = false; + + // Show egg at full brightness for first step if blinking + if (_blinkingEgg) + drawItem(608, 448, getMenu()->getGameId() + 39, _blinkingBrightness) + else { + // Reset values + _blinkingBrightness = 100; + _blinkingInterval = _defaultBlinkingInterval; + drawItem(608, 448, getMenu()->getGameId() + 39, 50) // normal egg state + } + + askForRedraw(); +} + +void Inventory::showHourGlass() const{ + if (!getFlags()->flag_5) { + drawItem(608, 448, kCursorHourGlass, 100); + } + + askForRedraw(); + + getFlags()->shouldDrawEggOrHourGlass = true; +} + +////////////////////////////////////////////////////////////////////////// +// Items +////////////////////////////////////////////////////////////////////////// +Inventory::InventoryEntry *Inventory::get(InventoryItem item) { + if (item >= kPortraitOriginal) + error("Inventory::getEntry: Invalid inventory item!"); + + return &_entries[item]; +} + +void Inventory::addItem(InventoryItem item) { + if (item >= kPortraitOriginal) + return; + + get(item)->isPresent = true; + get(item)->location = kObjectLocationNone; + + // Auto-select item if necessary + if (get(item)->cursor && !get(item)->manualSelect) { + _selectedItem = (InventoryItem)get(item)->cursor; + drawItem(44, 0, _selectedItem, 100) + askForRedraw(); + } +} + +void Inventory::removeItem(InventoryItem item, ObjectLocation newLocation) { + if (item >= kPortraitOriginal) + return; + + get(item)->isPresent = false; + get(item)->location = newLocation; + + if (get(item)->cursor == (CursorStyle)_selectedItem) { + _selectedItem = kItemNone; + _engine->getGraphicsManager()->clear(GraphicsManager::kBackgroundInventory, Common::Rect(44, 0, 44 + 32, 32)); + askForRedraw(); + } +} + +bool Inventory::hasItem(InventoryItem item) { + if (get(item)->isPresent && item < kPortraitOriginal) + return true; + + return false; +} + +void Inventory::selectItem(InventoryItem item) { + _selectedItem = item; + + drawItem(44, 0, get(_selectedItem)->cursor, 100) + askForRedraw(); +} + +void Inventory::unselectItem() { + _selectedItem = kItemNone; + + _engine->getGraphicsManager()->clear(GraphicsManager::kBackgroundInventory, Common::Rect(44, 0, 44 + 32, 32)); + askForRedraw(); +} + +void Inventory::setLocationAndProcess(InventoryItem item, ObjectLocation location) { + if (item >= kPortraitOriginal) + return; + + if (get(item)->location == location) + return; + + get(item)->location = location; + + if (isItemSceneParameter(item) && !getFlags()->flag_0) + getScenes()->processScene(); +} + +////////////////////////////////////////////////////////////////////////// +// Serializable +////////////////////////////////////////////////////////////////////////// +void Inventory::saveLoadWithSerializer(Common::Serializer &) { + error("Inventory::saveLoadWithSerializer: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// toString +////////////////////////////////////////////////////////////////////////// +Common::String Inventory::toString() { + Common::String ret = ""; + + for (int i = 0; i < kPortraitOriginal; i++) + ret += Common::String::printf("%d : %s\n", i, _entries[i].toString().c_str()); + + return ret; +} + +////////////////////////////////////////////////////////////////////////// +// Private methods +////////////////////////////////////////////////////////////////////////// +InventoryItem Inventory::getFirstExaminableItem() const { + + int index = 0; + InventoryEntry entry = _entries[index]; + while (!entry.isPresent || !entry.cursor || entry.manualSelect) { + index++; + entry = _entries[index]; + + if (index >= kPortraitOriginal) + return kItemNone; + } + + return (InventoryItem)index; +} + +bool Inventory::isItemSceneParameter(InventoryItem item) const { + Scene *scene = getScenes()->get(getState()->scene); + + switch(scene->type) { + default: + return false; + + case Scene::kTypeItem: + if (scene->param1 == item) + return true; + break; + + case Scene::kTypeItem2: + if (scene->param1 == item || scene->param2 == item) + return true; + break; + + case Scene::kTypeObjectItem: + if (scene->param2 == item) + return true; + break; + + case Scene::kTypeItem3: + if (scene->param1 == item || scene->param2 == item || scene->param3 == item) + return true; + break; + + case Scene::kTypeCompartmentsItem: + if (scene->param2 == item) + return true; + break; + } + + return false; +} + +// Examine an inventory item +void Inventory::examine(InventoryItem item) { + SceneIndex index = get(item)->scene; + if (!index) + return; + + /*if (!getState()->sceneUseBackup || + (getState()->sceneBackup2 && getFirstExaminableItem() == _selectedItem)) + flag = 1;*/ + + if (!getState()->sceneUseBackup) { + getState()->sceneBackup = getState()->scene; + getState()->sceneUseBackup = true; + + getScenes()->loadScene(index); + } else { + + if (!getState()->sceneBackup2) + return; + + if (getFirstExaminableItem() == _selectedItem) { + index = getState()->sceneBackup2; + getState()->sceneBackup2 = kSceneNone; + getScenes()->loadScene(index); + } + } +} + +// FIXME: see different callers and adjust +// - draw with different brightness if mousing over +void Inventory::drawEgg() const { + if (!getFlags()->flag_5) + drawItem(608, 448, getMenu()->getGameId() + 39, 50) + + getFlags()->shouldDrawEggOrHourGlass = false; +} + +// Blinking egg: we need to blink the egg for delta time, with the blinking getting faster until it's always lit. +void Inventory::drawBlinkingEgg() { + + warning("Inventory::drawEgg - blinking not implemented!"); + + //// TODO show egg (with or without mouseover) + + //// Play timer sound + //if (getGlobalTimer() < 90) { + // if (getGlobalTimer() + ticks >= 90) + // getSound()->playSoundWithSubtitles("TIMER.SND", 50331664, kEntityPlayer); + + // if (getSound()->isBuffered("TIMER")) + // setGlobalTimer(0); + //} + + //// Restore egg to standard brightness + //if (!getGlobalTimer()) { + // + //} + + + //drawItem(608, 448, getMenu()->getGameId() + 39, _blinkingBrightness) + + //// TODO if delta time > _blinkingInterval, update egg & ask for redraw then adjust blinking time and remaining time + // + + //// Reset values and stop blinking + //if (_blinkingTime == 0) + // blinkEgg(false); + + askForRedraw(); +} + +// Close inventory: clear items and reset icon +void Inventory::open() { + _opened = true; + + // Show selected state + drawItem(0, 0, getProgress().portrait + 1, 100) + + int16 y = 44; + + // Iterate over items + for (uint i = 1; i < 32; i++) { + if (_entries[i].isPresent) { + drawItem(0, y, _entries[i].cursor, 50) + y += 40; + } + } + + askForRedraw(); +} + +// Close inventory: clear items and reset icon +void Inventory::close() { + _opened = false; + + // Fallback to unselected state + drawItem(0, 0, getProgress().portrait, 100) + + // Erase rectangle for all inventory items + int count = 0; + for (uint i = 1; i < 32; i++) { + if (_entries[i].isPresent) { + count++; + } + } + + _engine->getGraphicsManager()->clear(GraphicsManager::kBackgroundInventory, Common::Rect(0, 44, 32, (int16)(44 + 44 * count))); + + askForRedraw(); +} + +Common::Rect Inventory::getItemRect(int16 index) const{ + return Common::Rect(0, (int16)((32 + 12) * (index + 1)), 32, (int16)((32 + 12) * (index + 2))); // space between items = 12px +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/inventory.h b/engines/lastexpress/game/inventory.h new file mode 100644 index 0000000000..6babe3e60e --- /dev/null +++ b/engines/lastexpress/game/inventory.h @@ -0,0 +1,169 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_INVENTORY_H +#define LASTEXPRESS_INVENTORY_H + +/* + Inventory entry (32 entries) + ---------------------------- + + byte {1} - Item ID (set to 0 for "undefined" items) + byte {1} - Scene ID + byte {1} - ?? + byte {1} - Selectable (1 if item is selectable, 0 otherwise) + byte {1} - Is item in inventory (set to 1 for telegram and article) + byte {1} - Auto selection (1 for no auto selection, 0 otherwise) + byte {1} - Location + +*/ + +#include "lastexpress/shared.h" + +#include "lastexpress/eventhandler.h" + +#include "common/events.h" +#include "common/serializer.h" + +namespace LastExpress { + +class LastExpressEngine; +class Scene; + +class Inventory : Common::Serializable, public EventHandler { +public: + + // Entry + struct InventoryEntry { + CursorStyle cursor; + SceneIndex scene; + byte field_2; + bool isSelectable; + bool isPresent; + bool manualSelect; + ObjectLocation location; + + InventoryEntry() { + cursor = kCursorNormal; + scene = kSceneNone; + field_2 = 0; + isSelectable = false; + isPresent = false; + manualSelect = true; + location = kObjectLocationNone; + } + + Common::String toString() { + return Common::String::printf("{ %d - %d - %d - %d - %d - %d - %d }", cursor, scene, field_2, isSelectable, isPresent, manualSelect, location); + } + }; + + Inventory(LastExpressEngine *engine); + ~Inventory(); + + // Inventory contents + void addItem(InventoryItem item); + void removeItem(InventoryItem item, ObjectLocation newLocation = kObjectLocationNone); + bool hasItem(InventoryItem item); + void selectItem(InventoryItem item); + void unselectItem(); + InventoryItem getSelectedItem() { return _selectedItem; } + + InventoryEntry *get(InventoryItem item); + InventoryEntry *getSelectedEntry() { return get(_selectedItem); } + + InventoryItem getFirstExaminableItem() const; + void setLocationAndProcess(InventoryItem item, ObjectLocation location); + + // UI Control + void show(); + void blinkEgg(bool enabled); + void showHourGlass() const; + void setPortrait(InventoryItem item) const; + void drawEgg() const; + void drawBlinkingEgg(); + + // Handle inventory UI events. + bool handleMouseEvent(const Common::Event &ev); + + // State + bool isMagnifierInUse() { return _flagUseMagnifier; } + bool isFlag1() { return _flag1; } + bool isFlag2() { return _flag2; } + bool isEggHighlighted() { return _flagEggHightlighted; } + + // Serializable + void saveLoadWithSerializer(Common::Serializer &ser); + + /** + * Convert this object into a string representation. + * + * @return A string representation of this object. + */ + Common::String toString(); + +private: + static const uint32 _defaultBlinkingInterval = 250; ///< Default blinking interval in ms + + LastExpressEngine *_engine; + + InventoryEntry _entries[32]; + InventoryItem _selectedItem; + InventoryItem _highlightedItem; + bool _opened; + bool _visible; + + bool _showingHourGlass; + bool _blinkingEgg; + uint32 _blinkingTime; + uint32 _blinkingInterval; + uint32 _blinkingBrightness; + + // Flags + bool _flagUseMagnifier; + bool _flag1; + bool _flag2; + bool _flagEggHightlighted; + + Scene *_itemScene; + + // Important rects + Common::Rect _inventoryRect; + Common::Rect _menuRect; + Common::Rect _selectedRect; + + void init(); + + void open(); + void close(); + void examine(InventoryItem item); + Common::Rect getItemRect(int16 index) const; + + bool isItemSceneParameter(InventoryItem item) const; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_INVENTORY_H diff --git a/engines/lastexpress/game/logic.cpp b/engines/lastexpress/game/logic.cpp new file mode 100644 index 0000000000..4aedd04a0c --- /dev/null +++ b/engines/lastexpress/game/logic.cpp @@ -0,0 +1,577 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/logic.h" + +// Data +#include "lastexpress/data/animation.h" +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/snd.h" + +// Entities +#include "lastexpress/entities/chapters.h" + +// Game +#include "lastexpress/game/action.h" +#include "lastexpress/game/beetle.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/fight.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/menu.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savegame.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +#define EVENT_TICKS_BEETWEEN_SAVEGAMES 450 +#define GAME_TICKS_BEETWEEN_SAVEGAMES 2700 + +Logic::Logic(LastExpressEngine *engine) : _engine(engine) { + _action = new Action(engine); + _beetle = new Beetle(engine); + _entities = new Entities(engine); + _fight = new Fight(engine); + _saveload = new SaveLoad(engine); + _state = new State(engine); + + // Flags + _flagActionPerformed = false; + _ignoreFrameInterval = false; + _ticksSinceLastSavegame = EVENT_TICKS_BEETWEEN_SAVEGAMES; +} + +Logic::~Logic() { + delete _action; + delete _beetle; + delete _fight; + delete _entities; + delete _saveload; + delete _state; + + // Zero-out passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Event Handling +////////////////////////////////////////////////////////////////////////// +#define REDRAW_CURSOR() { \ + if (getInventory()->isMagnifierInUse()) \ + _engine->getCursor()->setStyle(kCursorMagnifier); \ + if (getInventory()->isFlag1() \ + || getInventory()->isFlag2() \ + || getInventory()->isEggHighlighted()) \ + _engine->getCursor()->setStyle(kCursorNormal); \ + return; \ +} + +void Logic::eventMouse(const Common::Event &ev) { + bool hotspotHandled = false; + + // Reset mouse flags + getFlags()->mouseLeftClick = false; + getFlags()->mouseRightClick = false; + + // Process event flags + if (ev.type == Common::EVENT_LBUTTONUP) { + + if (getFlags()->frameInterval) + _ignoreFrameInterval = false; + + getFlags()->frameInterval = false; + } + + if (getFlags()->flag_0) { + if (ev.type == Common::EVENT_LBUTTONUP || ev.type == Common::EVENT_RBUTTONUP) { + getFlags()->flag_0 = false; + getFlags()->shouldRedraw = true; + updateCursor(true); + getFlags()->frameInterval = true; + } + return; + } + + if (_ignoreFrameInterval && getScenes()->checkCurrentPosition(true) && _engine->getCursor()->getStyle() == kCursorForward) { + getFlags()->shouldRedraw = false; + getFlags()->flag_0 = true; + return; + } + + // Update coordinates + getGameState()->setCoordinates(ev.mouse); + + // Handle inventory + getInventory()->handleMouseEvent(ev); + + // Stop processing is inside the menu + if (getMenu()->isShown()) + return; + + // Handle whistle case + if (getInventory()->getSelectedItem() == kItemWhistle + && !getProgress().isEggOpen + && !getEntities()->isPlayerPosition(kCarGreenSleeping, 59) + && !getEntities()->isPlayerPosition(kCarGreenSleeping, 76) + && !getInventory()->isFlag1() + && !getInventory()->isFlag2() + && !getInventory()->isEggHighlighted() + && !getInventory()->isMagnifierInUse()) { + + // Update cursor + _engine->getCursor()->setStyle(getInventory()->get(kItemWhistle)->cursor); + + // Check if clicked + if (ev.type == Common::EVENT_LBUTTONUP && !getSound()->isBuffered("LIB045")) { + + getSound()->playSoundEvent(kEntityPlayer, 45); + + if (getEntities()->isPlayerPosition(kCarGreenSleeping, 26) || getEntities()->isPlayerPosition(kCarGreenSleeping, 25) || getEntities()->isPlayerPosition(kCarGreenSleeping, 23)) { + getSavePoints()->push(kEntityPlayer, kEntityMertens, kAction226078300); + } else if (getEntities()->isPlayerPosition(kCarRedSleeping, 26) || getEntities()->isPlayerPosition(kCarRedSleeping, 25) || getEntities()->isPlayerPosition(kCarRedSleeping, 23)) { + getSavePoints()->push(kEntityPlayer, kEntityCoudert, kAction226078300); + } + + if (!getState()->sceneUseBackup) + getInventory()->unselectItem(); + } + + REDRAW_CURSOR() + } + + // Handle match case + if (getInventory()->getSelectedItem() == kItemMatch + && (getEntities()->isPlayerInCar(kCarGreenSleeping) || getEntities()->isPlayerInCar(kCarRedSleeping)) + && getProgress().jacket == kJacketGreen + && !getInventory()->isFlag1() + && !getInventory()->isFlag2() + && !getInventory()->isEggHighlighted() + && !getInventory()->isMagnifierInUse() + && (getInventory()->get(kItem2)->location == kObjectLocationNone || getEntityData(kEntityPlayer)->car != kCarRedSleeping || getEntityData(kEntityPlayer)->entityPosition != kPosition_2300)) { + + // Update cursor + _engine->getCursor()->setStyle(getInventory()->get(kItemMatch)->cursor); + + if (ev.type == Common::EVENT_LBUTTONUP) { + + getAction()->playAnimation(isNight() ? kEventCathSmokeNight : kEventCathSmokeDay); + + if (!getState()->sceneUseBackup) + getInventory()->unselectItem(); + + getScenes()->processScene(); + } + + REDRAW_CURSOR() + } + + // Handle entity item case + EntityIndex entityIndex = getEntities()->canInteractWith(ev.mouse); + if (entityIndex + && !getInventory()->isFlag1() + && !getInventory()->isFlag2() + && !getInventory()->isEggHighlighted() + && !getInventory()->isMagnifierInUse()) { + + InventoryItem item = getEntityData(entityIndex)->inventoryItem; + if (getInventory()->hasItem((InventoryItem)(item & kItemToggleHigh))) { + hotspotHandled = true; + + _engine->getCursor()->setStyle(getInventory()->get((InventoryItem)(item & kItemToggleHigh))->cursor); + + if (ev.type == Common::EVENT_LBUTTONUP) + getSavePoints()->push(kEntityPlayer, entityIndex, kAction1, (InventoryItem)(item & kItemToggleHigh)); + } else if ((InventoryItem)(item & kItemInvalid)) { + hotspotHandled = true; + + _engine->getCursor()->setStyle(kCursorTalk2); + + if (ev.type == Common::EVENT_LBUTTONUP) + getSavePoints()->push(kEntityPlayer, entityIndex, kAction1, kCursorNormal); + } + } + + ////////////////////////////////////////////////////////////////////////// + // Handle standard actions + if (hotspotHandled || getInventory()->isFlag1() || getInventory()->isFlag2() || getInventory()->isEggHighlighted()) + return; + + // Magnifier in use + if (getInventory()->isMagnifierInUse()) { + _engine->getCursor()->setStyle(kCursorMagnifier); + + if (getInventory()->isFlag1() + || getInventory()->isFlag2() + || getInventory()->isEggHighlighted()) + _engine->getCursor()->setStyle(kCursorNormal); + + return; + } + + // Check hotspots + int location = 0; + SceneHotspot *hotspot = NULL; + Scene *scene = getScenes()->get(getState()->scene); + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (!(*it)->isInside(ev.mouse)) + continue; + + if ((*it)->location < location) + continue; + + if (!getAction()->getCursor(**it)) + continue; + + Scene *hotspotScene = getScenes()->get((*it)->scene); + + if (!getEntities()->getPosition(hotspotScene->car, hotspotScene->position) + || (*it)->cursor == kCursorTurnRight + || (*it)->cursor == kCursorTurnLeft) { + location = (*it)->location; + hotspot = *it; + } + } + + // No hotspot found: show the normal cursor + if (!hotspot) { + _engine->getCursor()->setStyle(kCursorNormal); + return; + } + + // Found an hotspot: update the cursor and perform the action if the user clicked the mouse + _engine->getCursor()->setStyle(getAction()->getCursor(*hotspot)); + + if (ev.type != Common::EVENT_LBUTTONUP || _flagActionPerformed) + return; + + _flagActionPerformed = true; + + SceneIndex processedScene = getAction()->processHotspot(*hotspot); + SceneIndex testScene = (processedScene == kSceneInvalid) ? hotspot->scene : processedScene; + + if (testScene) { + getFlags()->shouldRedraw = false; + + getScenes()->setScene(testScene); + + if (getFlags()->shouldDrawEggOrHourGlass) + getInventory()->drawEgg(); + + getFlags()->shouldRedraw = true; + updateCursor(true); + } + + // Switch to next chapter if necessary + if (hotspot->action == SceneHotspot::kActionSwitchChapter && hotspot->param1 == getState()->progress.chapter) + switchChapter(); +} + +void Logic::eventTick(const Common::Event &) { + uint ticks = 1; + + ////////////////////////////////////////////////////////////////////////// + // Adjust ticks if an action has been performed + if (_flagActionPerformed) + ticks = 10; + + _flagActionPerformed = false; + + ////////////////////////////////////////////////////////////////////////// + // Draw the blinking egg if needed + if (getGlobalTimer() && !getFlags()->shouldDrawEggOrHourGlass) + getInventory()->drawBlinkingEgg(); + + ////////////////////////////////////////////////////////////////////////// + // Adjust time and save game if needed + if (getFlags()->isGameRunning) { + getState()->timeTicks += ticks; + getState()->time += ticks * getState()->timeDelta; + + if (getState()->timeDelta) { + + // Auto-save + if (!_ticksSinceLastSavegame) { + _ticksSinceLastSavegame = EVENT_TICKS_BEETWEEN_SAVEGAMES; + getSaveLoad()->saveGame(kSavegameTypeAuto, kEntityChapters, kEventNone); + } + + // Save after game ticks interval + if ((getState()->timeTicks - getSaveLoad()->getLastSavegameTicks()) > GAME_TICKS_BEETWEEN_SAVEGAMES) + getSaveLoad()->saveGame(kSavegameTypeTickInterval, kEntityChapters, kEventNone); + } + } + + ////////////////////////////////////////////////////////////////////////// + // Load scene and process hotspot + if (getFlags()->flag_0 && !getFlags()->mouseLeftClick && !getFlags()->mouseRightClick) { + Scene *scene = getScenes()->get(getState()->scene); + + if (getScenes()->checkCurrentPosition(true) + && !getEntities()->getPosition(scene->car, scene->position)) { + + // Process hotspot + SceneHotspot *hotspot = scene->getHotspot(); + SceneIndex processedScene = getAction()->processHotspot(*hotspot); + SceneIndex testScene = (processedScene == kSceneInvalid) ? hotspot->scene : processedScene; + + if (testScene) { + getScenes()->setScene(testScene); + } else { + getFlags()->flag_0 = false; + getFlags()->shouldRedraw = true; + updateCursor(true); + } + + if (getFlags()->isGameRunning) + getSavePoints()->callAndProcess(); + + } else { + getFlags()->flag_0 = false; + getFlags()->shouldRedraw = true; + updateCursor(true); + } + + return; + } + + // Stop processing if the game is paused + if (!getFlags()->isGameRunning) + return; + + ////////////////////////////////////////////////////////////////////////// + // Update beetle, savepoints, entities and draw frames + if (_beetle->isLoaded()) + _beetle->update(); + + getSavePoints()->callAndProcess(); + getEntities()->updateCallbacks(); + getScenes()->drawFrames(true); + + ////////////////////////////////////////////////////////////////////////// + // Update cursor if we can interact with an entity + EntityIndex entity = getEntities()->canInteractWith(getCoords()); + if (!entity) { + if (_engine->getCursor()->getStyle() >= kCursorTalk2) + updateCursor(false); + + return; + } + + // Show item cursor on entity + if (getInventory()->hasItem((InventoryItem)(getEntityData(entity)->inventoryItem & kItemToggleHigh)) && (int)getEntityData(entity)->inventoryItem != (int)kCursorTalk2) { + _engine->getCursor()->setStyle(getInventory()->get((InventoryItem)(getEntityData(entity)->inventoryItem & kItemToggleHigh))->cursor); + return; + } + + getLogic()->updateCursor(false); + _engine->getCursor()->setStyle(kCursorTalk2); +} + +////////////////////////////////////////////////////////////////////////// +// Game over, Chapters & credits +////////////////////////////////////////////////////////////////////////// + +// Handle game over +void Logic::gameOver(SavegameType type, uint32 value, SceneIndex sceneIndex, bool showScene) const { + + getSound()->processEntries(); + getEntities()->reset(); + getFlags()->isGameRunning = false; + getSavePoints()->reset(); + getFlags()->flag_entities_0 = true; + + if (showScene) { + + getSound()->processEntry(SoundManager::kSoundType11); + + if (sceneIndex && !getFlags()->mouseRightClick) { + getScenes()->loadScene(sceneIndex); + + while (getSound()->isBuffered(kEntityTables4)) { + if (getFlags()->mouseRightClick) + break; + + getSound()->updateQueue(); + } + } + } + + // Show Menu + getMenu()->show(false, type, value); +} + +void Logic::switchChapter() const { + getSound()->clearStatus(); + + switch(getState()->progress.chapter) { + default: + break; + + case kChapter1: + getInventory()->addItem(kItemParchemin); + getInventory()->addItem(kItemMatchBox); + + RESET_ENTITY_STATE(kEntityChapters, Chapters, setup_chapter2); + break; + + case kChapter2: + getInventory()->addItem(kItemScarf); + + RESET_ENTITY_STATE(kEntityChapters, Chapters, setup_chapter3); + break; + + case kChapter3: + getInventory()->get(kItemFirebird)->location = kObjectLocation4; + getInventory()->get(kItemFirebird)->isPresent = false; + getInventory()->get(kItem11)->location = kObjectLocation1; + getInventory()->addItem(kItemWhistle); + getInventory()->addItem(kItemKey); + + RESET_ENTITY_STATE(kEntityChapters, Chapters, setup_chapter4); + break; + + case kChapter4: + RESET_ENTITY_STATE(kEntityChapters, Chapters, setup_chapter5); + break; + + case kChapter5: + playFinalSequence(); + break; + } +} + +void Logic::playFinalSequence() const { + getSound()->processEntries(); + + _action->playAnimation(kEventFinalSequence); + showCredits(); + + getEntities()->reset(); + getSavePoints()->reset(); + getFlags()->flag_entities_0 = true; + + getMenu()->show(false, kSavegameTypeIndex, 0); +} + +void Logic::showCredits() const { + error("Logic::showCredits: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// Misc +////////////////////////////////////////////////////////////////////////// +void Logic::updateCursor(bool) const { /* the cursor is always updated, even when we don't want to redraw it */ + CursorStyle style = kCursorNormal; + bool interact = false; + + if (getInventory()->getSelectedItem() != kItemWhistle + || getProgress().isEggOpen + || getEntities()->isPlayerPosition(kCarGreenSleeping, 59) + || getEntities()->isPlayerPosition(kCarGreenSleeping, 76) + || getInventory()->isFlag1() + || getInventory()->isFlag2() + || getInventory()->isEggHighlighted() + || getInventory()->isMagnifierInUse()) { + + if (getInventory()->getSelectedItem() != kItemMatch + || (!getEntities()->isPlayerInCar(kCarGreenSleeping) && !getEntities()->isPlayerInCar(kCarRedSleeping)) + || getProgress().jacket != kJacketGreen + || getInventory()->isFlag1() + || getInventory()->isFlag2() + || getInventory()->isEggHighlighted() + || getInventory()->isMagnifierInUse() + || (getInventory()->get(kItem2)->location + && getEntityData(kEntityPlayer)->car == kCarRedSleeping + && getEntityData(kEntityPlayer)->entityPosition == kPosition_2300)) { + + EntityIndex entity = getEntities()->canInteractWith(getCoords()); + if (entity + && !getInventory()->isFlag1() + && !getInventory()->isFlag2() + && !getInventory()->isEggHighlighted() + && !getInventory()->isMagnifierInUse()) { + if (getInventory()->hasItem((InventoryItem)(getEntityData(entity)->inventoryItem & kItemToggleHigh))) { + interact = true; + style = getInventory()->get((InventoryItem)(getEntityData(entity)->inventoryItem & kItemToggleHigh))->cursor; + } else if ((int)getEntityData(entity)->inventoryItem == kItemInvalid) { + interact = true; + style = kCursorTalk2; + } + } + + if (!interact + && !getInventory()->isFlag1() + && !getInventory()->isFlag2() + && !getInventory()->isEggHighlighted() + && !getInventory()->isMagnifierInUse()) { + int location = 0; + SceneHotspot *hotspot = NULL; + Scene *scene = getScenes()->get(getState()->scene); + + // Check all hotspots + for (Common::Array<SceneHotspot *>::iterator i = scene->getHotspots()->begin(); i != scene->getHotspots()->end(); ++i) { + if ((*i)->isInside(getCoords()) && (*i)->location >= location) { + if (getAction()->getCursor(**i)) { + Scene *hotspotScene = getScenes()->get((*i)->scene); + + if (!getEntities()->getPosition(hotspotScene->car, hotspotScene->position) + || (*i)->cursor == kCursorTurnRight + || (*i)->cursor == kCursorTurnLeft) { + hotspot = *i; + location = (*i)->location; + } + } + } + } + + style = (hotspot) ? getAction()->getCursor(*hotspot) : kCursorNormal; + } + } else { + style = getInventory()->get(kItemMatch)->cursor; + } + + } else { + style = getInventory()->get(kItemWhistle)->cursor; + } + + if (getInventory()->isMagnifierInUse()) + style = kCursorMagnifier; + + if (getInventory()->isFlag1() || getInventory()->isFlag2() || getInventory()->isEggHighlighted()) + style = kCursorNormal; + + _engine->getCursor()->setStyle(style); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/logic.h b/engines/lastexpress/game/logic.h new file mode 100644 index 0000000000..7e01655b68 --- /dev/null +++ b/engines/lastexpress/game/logic.h @@ -0,0 +1,91 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_LOGIC_H +#define LASTEXPRESS_LOGIC_H + +#include "lastexpress/shared.h" + +#include "lastexpress/game/entities.h" + +#include "lastexpress/eventhandler.h" + +#include "common/events.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Action; +class Beetle; +class Debugger; +class Entities; +class Fight; +class SaveLoad; +class State; + +class Logic : public EventHandler { +public: + Logic(LastExpressEngine *engine); + ~Logic(); + + void eventMouse(const Common::Event &ev); + void eventTick(const Common::Event &ev); + + void gameOver(SavegameType type, uint32 value, SceneIndex sceneIndex, bool showScene) const; + void playFinalSequence() const; + void updateCursor(bool redraw = true) const; + + Action *getGameAction() { return _action; } + Beetle *getGameBeetle() { return _beetle; } + Entities *getGameEntities() { return _entities; } + Fight *getGameFight() { return _fight; } + SaveLoad *getGameSaveLoad() { return _saveload; } + State *getGameState() { return _state; } + +private: + LastExpressEngine *_engine; + + Action *_action; ///< Actions + Beetle *_beetle; ///< Beetle catching + Entities *_entities; ///< Entities + Fight *_fight; ///< Fight handling + SaveLoad *_saveload; ///< Save & loading + State *_state; ///< Game state + + void switchChapter() const; + void showCredits() const; + + // Flags & Members + bool _flagActionPerformed; + bool _ignoreFrameInterval; + int _ticksSinceLastSavegame; + + friend class Debugger; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_LOGIC_H diff --git a/engines/lastexpress/game/menu.cpp b/engines/lastexpress/game/menu.cpp new file mode 100644 index 0000000000..19678a3255 --- /dev/null +++ b/engines/lastexpress/game/menu.cpp @@ -0,0 +1,1564 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/menu.h" + +// Data +#include "lastexpress/data/animation.h" +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/snd.h" +#include "lastexpress/data/scene.h" + +#include "lastexpress/game/fight.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/savegame.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +#define getNextGameId() (GameId)((_gameId + 1) % 6) + +namespace LastExpress { + +// Bottom-left buttons (quit.seq) +enum StartMenuButtons { + kButtonVolumeDownPushed, + kButtonVolumeDown, + kButtonVolume, + kButtonVolumeUp, + kButtonVolumeUpPushed, + kButtonBrightnessDownPushed, // 5 + kButtonBrightnessDown, + kButtonBrightness, + kButtonBrightnessUp, + kButtonBrightnessUpPushed, + kButtonQuit, // 10 + kButtonQuitPushed +}; + +// Egg buttons (buttns.seq) +enum StartMenuEggButtons { + kButtonShield, + kButtonRewind, + kButtonRewindPushed, + kButtonForward, + kButtonForwardPushed, + kButtonCredits, // 5 + kButtonCreditsPushed, + kButtonContinue +}; + +// Tooltips sequence (helpnewr.seq) +enum StartMenuTooltips { + kTooltipInsertCd1, + kTooltipInsertCd2, + kTooltipInsertCd3, + kTooltipContinueGame, + kTooltipReplayGame, + kTooltipContinueRewoundGame, // 5 + kTooltipViewGameEnding, + kTooltipStartAnotherGame, + kTooltipVolumeUp, + kTooltipVolumeDown, + kTooltipBrightnessUp, // 10 + kTooltipBrightnessDown, + kTooltipQuit, + kTooltipRewindParis, + kTooltipForwardStrasbourg, + kTooltipRewindStrasbourg, // 15 + kTooltipRewindMunich, + kTooltipForwardMunich, + kTooltipForwardVienna, + kTooltipRewindVienna, + kTooltipRewindBudapest, // 20 + kTooltipForwardBudapest, + kTooltipForwardBelgrade, + kTooltipRewindBelgrade, + kTooltipForwardConstantinople, + kTooltipSwitchBlueGame, // 25 + kTooltipSwitchRedGame, + kTooltipSwitchGoldGame, + kTooltipSwitchGreenGame, + kTooltipSwitchTealGame, + kTooltipSwitchPurpleGame, // 30 + kTooltipPlayNewGame, + kTooltipCredits, + kTooltipFastForward, + kTooltipRewind +}; + +////////////////////////////////////////////////////////////////////////// +// DATA +////////////////////////////////////////////////////////////////////////// + +// Information about the cities on the train line +static const struct { + uint8 frame; + TimeValue time; +} _trainCities[31] = { + {0, kTimeCityParis}, + {9, kTimeCityEpernay}, + {11, kTimeCityChalons}, + {16, kTimeCityBarLeDuc}, + {21, kTimeCityNancy}, + {25, kTimeCityLuneville}, + {35, kTimeCityAvricourt}, + {37, kTimeCityDeutschAvricourt}, + {40, kTimeCityStrasbourg}, + {53, kTimeCityBadenOos}, + {56, kTimeCityKarlsruhe}, + {60, kTimeCityStuttgart}, + {63, kTimeCityGeislingen}, + {66, kTimeCityUlm}, + {68, kTimeCityAugsburg}, + {73, kTimeCityMunich}, + {84, kTimeCitySalzbourg}, + {89, kTimeCityAttnangPuchheim}, + {97, kTimeCityWels}, + {100, kTimeCityLinz}, + {104, kTimeCityAmstetten}, + {111, kTimeCityVienna}, + {120, kTimeCityPoszony}, + {124, kTimeCityGalanta}, + {132, kTimeCityBudapest}, + {148, kTimeCityBelgrade}, + /* Line 1 ends at 150 - line 2 begins at 0 */ + {157, kTimeCityNish}, + {165, kTimeCityTzaribrod}, + {174, kTimeCitySofia}, + {198, kTimeCityAdrianople}, + {210, kTimeCityConstantinople}}; + +static const struct { + TimeValue time; + uint index; + StartMenuTooltips rewind; + StartMenuTooltips forward; +} _cityButtonsInfo[7] = { + {kTimeCityParis, 64, kTooltipRewindParis, kTooltipRewindParis}, + {kTimeCityStrasbourg, 128, kTooltipRewindStrasbourg, kTooltipForwardStrasbourg}, + {kTimeCityMunich, 129, kTooltipRewindMunich, kTooltipForwardMunich}, + {kTimeCityVienna, 130, kTooltipRewindVienna, kTooltipForwardVienna}, + {kTimeCityBudapest, 131, kTooltipRewindBudapest, kTooltipForwardBudapest}, + {kTimeCityBelgrade, 132, kTooltipRewindBelgrade, kTooltipForwardBelgrade}, + {kTimeCityConstantinople, 192, kTooltipForwardConstantinople, kTooltipForwardConstantinople} +}; + +////////////////////////////////////////////////////////////////////////// +// Clock +////////////////////////////////////////////////////////////////////////// +class Clock { +public: + Clock(LastExpressEngine *engine); + ~Clock(); + + void draw(uint32 time); + void clear(); + +private: + LastExpressEngine *_engine; + + // Frames + SequenceFrame *_frameMinutes; + SequenceFrame *_frameHour; + SequenceFrame *_frameSun; + SequenceFrame *_frameDate; +}; + +Clock::Clock(LastExpressEngine *engine) : _engine(engine), _frameMinutes(NULL), _frameHour(NULL), _frameSun(NULL), _frameDate(NULL) { + _frameMinutes = new SequenceFrame(loadSequence("eggmin.seq"), 0, true); + _frameHour = new SequenceFrame(loadSequence("egghour.seq"), 0, true); + _frameSun = new SequenceFrame(loadSequence("sun.seq"), 0, true); + _frameDate = new SequenceFrame(loadSequence("datenew.seq"), 0, true); +} + +Clock::~Clock() { + delete _frameMinutes; + delete _frameHour; + delete _frameSun; + delete _frameDate; + + // Zero passed pointers + _engine = NULL; +} + +void Clock::clear() { + getScenes()->removeAndRedraw(&_frameMinutes, false); + getScenes()->removeAndRedraw(&_frameHour, false); + getScenes()->removeAndRedraw(&_frameSun, false); + getScenes()->removeAndRedraw(&_frameDate, false); +} + +void Clock::draw(uint32 time) { + assert(time >= kTimeCityParis && time <= kTimeCityConstantinople); + + // Check that sequences have been loaded + if (!_frameMinutes || !_frameHour || !_frameSun || !_frameDate) + error("Clock::process: clock sequences have not been loaded correctly!"); + + // Clear existing frames + clear(); + + // Game starts at: 1037700 = 7:13 p.m. on July 24, 1914 + // Game ends at: 4941000 = 7:30 p.m. on July 26, 1914 + // Game lasts for: 3903300 = 2 days + 17 mins = 2897 mins + + // 15 = 1 second + // 15 * 60 = 900 = 1 minute + // 900 * 60 = 54000 = 1 hour + // 54000 * 24 = 1296000 = 1 day + + // Calculate each sequence index from the current time + uint8 hour = (uint8)((time % 1296000) / 54000); + uint8 minute = (uint8)((time % 54000) / 900); + uint32 index_date = 18 * time / 1296000; + if (hour == 23) + index_date += 18 * minute / 60; + + // Set sequences frames + _frameMinutes->setFrame(minute); + _frameHour->setFrame((5 * hour + minute / 12) % 60); + _frameSun->setFrame((5 * hour + minute / 12) % 120); + _frameDate->setFrame((uint16)index_date); + + // Adjust z-order and queue + _frameMinutes->getInfo()->location = 1; + _frameHour->getInfo()->location = 1; + _frameSun->getInfo()->location = 1; + _frameDate->getInfo()->location = 1; + + getScenes()->addToQueue(_frameMinutes); + getScenes()->addToQueue(_frameHour); + getScenes()->addToQueue(_frameSun); + getScenes()->addToQueue(_frameDate); +} + +////////////////////////////////////////////////////////////////////////// +// TrainLine +////////////////////////////////////////////////////////////////////////// +class TrainLine { +public: + TrainLine(LastExpressEngine *engine); + ~TrainLine(); + + void draw(uint32 time); + void clear(); + +private: + LastExpressEngine *_engine; + + // Frames + SequenceFrame *_frameLine1; + SequenceFrame *_frameLine2; +}; + +TrainLine::TrainLine(LastExpressEngine *engine) : _engine(engine), _frameLine1(NULL), _frameLine2(NULL) { + _frameLine1 = new SequenceFrame(loadSequence("line1.seq"), 0, true); + _frameLine2 = new SequenceFrame(loadSequence("line2.seq"), 0, true); +} + +TrainLine::~TrainLine() { + delete _frameLine1; + delete _frameLine2; + + // Zero passed pointers + _engine = NULL; +} + +void TrainLine::clear() { + getScenes()->removeAndRedraw(&_frameLine1, false); + getScenes()->removeAndRedraw(&_frameLine2, false); +} + +// Draw the train line at the time +// line1: 150 frames (=> Belgrade) +// line2: 61 frames (=> Constantinople) +void TrainLine::draw(uint32 time) { + assert(time >= kTimeCityParis && time <= kTimeCityConstantinople); + + // Check that sequences have been loaded + if (!_frameLine1 || !_frameLine2) + error("TrainLine::process: Line sequences have not been loaded correctly!"); + + // Clear existing frames + clear(); + + // Get the index of the last city the train has visited + uint index = 0; + for (uint i = 0; i < ARRAYSIZE(_trainCities); i++) + if ((uint32)_trainCities[i].time <= time) + index = i; + + uint16 frame; + if (time > (uint32)_trainCities[index].time) { + // Interpolate linearly to use a frame between the cities + uint8 diffFrames = _trainCities[index + 1].frame - _trainCities[index].frame; + uint diffTimeCities = (uint)(_trainCities[index + 1].time - _trainCities[index].time); + uint traveledTime = (time - (uint)_trainCities[index].time); + frame = (uint16)(_trainCities[index].frame + (traveledTime * diffFrames) / diffTimeCities); + } else { + // Exactly on the city + frame = _trainCities[index].frame; + } + + // Set frame, z-order and queue + if (frame < 150) { + _frameLine1->setFrame(frame); + + _frameLine1->getInfo()->location = 1; + getScenes()->addToQueue(_frameLine1); + } else { + // We passed Belgrade + _frameLine1->setFrame(149); + _frameLine2->setFrame(frame - 150); + + _frameLine1->getInfo()->location = 1; + _frameLine2->getInfo()->location = 1; + + getScenes()->addToQueue(_frameLine1); + getScenes()->addToQueue(_frameLine2); + } +} + + +////////////////////////////////////////////////////////////////////////// +// Menu +////////////////////////////////////////////////////////////////////////// +Menu::Menu(LastExpressEngine *engine) : _engine(engine), + _seqTooltips(NULL), _seqEggButtons(NULL), _seqButtons(NULL), _seqAcorn(NULL), _seqCity1(NULL), _seqCity2(NULL), _seqCity3(NULL), _seqCredits(NULL), + _gameId(kGameBlue), _hasShownStartScreen(false), _hasShownIntro(false), + _isShowingCredits(false), _isGameStarted(false), _isShowingMenu(false), + _creditsSequenceIndex(0), _checkHotspotsTicks(15), _mouseFlags(Common::EVENT_INVALID), _lastHotspot(NULL), + _currentIndex(0), _currentTime(0), _lowerTime(0), _index(0), _index2(0), _time(0), _delta(0), _handleTimeDelta(false) { + + _clock = new Clock(_engine); + _trainLine = new TrainLine(_engine); +} + +Menu::~Menu() { + delete _clock; + delete _trainLine; + + SAFE_DELETE(_seqTooltips); + SAFE_DELETE(_seqEggButtons); + SAFE_DELETE(_seqButtons); + SAFE_DELETE(_seqAcorn); + SAFE_DELETE(_seqCity1); + SAFE_DELETE(_seqCity2); + SAFE_DELETE(_seqCity3); + SAFE_DELETE(_seqCredits); + + _lastHotspot = NULL; + + // Zero passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Setup +void Menu::setup() { + + // Clear drawing queue + getScenes()->removeAndRedraw(&_frames[kOverlayAcorn], false); + SAFE_DELETE(_seqAcorn); + + // Load Menu scene + // + 1 = normal menu with open egg / clock + // + 2 = shield menu, when no savegame exists (no game has been started) + _isGameStarted = _lowerTime >= kTimeStartGame; + getScenes()->loadScene((SceneIndex)(_isGameStarted ? _gameId * 5 + 1 : _gameId * 5 + 2)); + getFlags()->shouldRedraw = true; + getLogic()->updateCursor(); + + ////////////////////////////////////////////////////////////////////////// + // Load Acorn sequence + _seqAcorn = loadSequence(getAcornSequenceName(_isGameStarted ? getNextGameId() : kGameBlue)); + + ////////////////////////////////////////////////////////////////////////// + // Check if we loaded sequences before + if (_seqTooltips && _seqTooltips->count() > 0) + return; + + // Load all static data + _seqTooltips = loadSequence("helpnewr.seq"); + _seqEggButtons = loadSequence("buttns.seq"); + _seqButtons = loadSequence("quit.seq"); + _seqCity1 = loadSequence("jlinetl.seq"); + _seqCity2 = loadSequence("jlinecen.seq"); + _seqCity3 = loadSequence("jlinebr.seq"); + _seqCredits = loadSequence("credits.seq"); + + _frames[kOverlayTooltip] = new SequenceFrame(_seqTooltips); + _frames[kOverlayEggButtons] = new SequenceFrame(_seqEggButtons); + _frames[kOverlayButtons] = new SequenceFrame(_seqButtons); + _frames[kOverlayAcorn] = new SequenceFrame(_seqAcorn); + _frames[kOverlayCity1] = new SequenceFrame(_seqCity1); + _frames[kOverlayCity2] = new SequenceFrame(_seqCity2); + _frames[kOverlayCity3] = new SequenceFrame(_seqCity3); + _frames[kOverlayCredits] = new SequenceFrame(_seqCredits); +} + +////////////////////////////////////////////////////////////////////////// +// Handle events +void Menu::eventMouse(const Common::Event &ev) { + if (!getFlags()->shouldRedraw) + return; + + bool redraw = true; + getFlags()->shouldRedraw = false; + + // Update coordinates + setCoords(ev.mouse); + //_mouseFlags = (Common::EventType)(ev.type & Common::EVENT_LBUTTONUP); + + if (_isShowingCredits) { + if (ev.type == Common::EVENT_RBUTTONUP) { + showFrame(kOverlayCredits, -1, true); + _isShowingCredits = false; + } + + if (ev.type == Common::EVENT_LBUTTONUP) { + // Last frame of the credits + if (_seqCredits && _creditsSequenceIndex == _seqCredits->count() - 1) { + showFrame(kOverlayCredits, -1, true); + _isShowingCredits = false; + } else { + ++_creditsSequenceIndex; + showFrame(kOverlayCredits, _creditsSequenceIndex, true); + } + } + } else { + // Check for hotspots + SceneHotspot *hotspot = NULL; + getScenes()->get(getState()->scene)->checkHotSpot(ev.mouse, &hotspot); + + if (_lastHotspot != hotspot || ev.type == Common::EVENT_LBUTTONUP) { + _lastHotspot = hotspot; + + if (ev.type == Common::EVENT_MOUSEMOVE) { /* todo check event type */ + if (!_handleTimeDelta && hasTimeDelta()) + setTime(); + } + + if (hotspot) { + redraw = handleEvent((StartMenuAction)hotspot->action, ev.type); + getFlags()->mouseRightClick = false; + getFlags()->mouseLeftClick = false; + } else { + hideOverlays(); + } + } + } + + if (redraw) { + getFlags()->shouldRedraw = true; + askForRedraw(); + } +} + +void Menu::eventTick(const Common::Event&) { + if (hasTimeDelta()) + adjustTime(); + else if (_handleTimeDelta) + _handleTimeDelta = false; + + // Check hotspots + if (!--_checkHotspotsTicks) { + checkHotspots(); + _checkHotspotsTicks = 15; + } +} + +////////////////////////////////////////////////////////////////////////// +// Show the intro and load the main menu scene +void Menu::show(bool doSavegame, SavegameType type, uint32 value) { + + if (_isShowingMenu) + return; + + _isShowingMenu = true; + getEntities()->reset(); + + // If no blue savegame exists, this might be the first time we start the game, so we show the full intro + if (!getFlags()->mouseRightClick) { + if (!SaveLoad::isSavegameValid(kGameBlue) && _engine->getResourceManager()->loadArchive(kArchiveCd1)) { + + if (!_hasShownIntro) { + // Show Broderbrund logo + Animation animation; + if (animation.load(getArchive("1930.nis"))) + animation.play(); + + getFlags()->mouseRightClick = false; + + // Play intro music + getSound()->playSoundWithSubtitles("MUS001.SND", SoundManager::kFlagMusic, kEntityPlayer); + + // Show The Smoking Car logo + if (animation.load(getArchive("1931.nis"))) + animation.play(); + + _hasShownIntro = true; + } + } else { + // Only show the quick intro + if (!_hasShownStartScreen) { + getSound()->playSoundWithSubtitles("MUS018.SND", SoundManager::kFlagMusic, kEntityPlayer); + getScenes()->loadScene(kSceneStartScreen); + + // Original game waits 60 frames and loops Sound::unknownFunction1 unless the right button is pressed + uint32 nextFrameCount = getFrameCount() + 60; + while (getFrameCount() < nextFrameCount) { + _engine->pollEvents(); + + if (getFlags()->mouseRightClick) + break; + + getSound()->updateQueue(); + } + } + } + } + + _hasShownStartScreen = true; + + // Init Menu + init(doSavegame, type, value); + + // Setup sound + getSound()->unknownFunction4(); + getSound()->resetQueue(SoundManager::kSoundType11, SoundManager::kSoundType13); + if (getSound()->isBuffered("TIMER")) + getSound()->removeFromQueue("TIMER"); + + // Init flags & misc + _isShowingCredits = false; + _handleTimeDelta = hasTimeDelta(); + getInventory()->unselectItem(); + + // Set Cursor type + _engine->getCursor()->setStyle(kCursorNormal); + _engine->getCursor()->show(true); + + setup(); + checkHotspots(); + + // Set event handlers + SET_EVENT_HANDLERS(Menu, this); +} + +bool Menu::handleEvent(StartMenuAction action, Common::EventType type) { + bool clicked = (type == Common::EVENT_LBUTTONUP); + + switch(action) { + default: + hideOverlays(); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuCredits: + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + if (clicked) { + showFrame(kOverlayEggButtons, kButtonCreditsPushed, true); + showFrame(kOverlayTooltip, -1, true); + + getSound()->playSound(kEntityPlayer, "LIB046"); + + hideOverlays(); + + _isShowingCredits = true; + _creditsSequenceIndex = 0; + + showFrame(kOverlayCredits, 0, true); + } else { + // TODO check flags ? + + showFrame(kOverlayEggButtons, kButtonCredits, true); + showFrame(kOverlayTooltip, kTooltipCredits, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuQuitGame: + showFrame(kOverlayTooltip, kTooltipQuit, true); + + if (clicked) { + showFrame(kOverlayButtons, kButtonQuitPushed, true); + + getSound()->clearStatus(); + getSound()->updateQueue(); + getSound()->playSound(kEntityPlayer, "LIB046"); + + // FIXME uncomment when sound queue is properly implemented + /*while (getSound()->isBuffered("LIB046")) + getSound()->updateQueue();*/ + + getFlags()->shouldRedraw = false; + + Engine::quitGame(); + + return false; + } else { + showFrame(kOverlayButtons, kButtonQuit, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuCase4: + if (clicked) + _index = 0; + // fall down to kMenuContinue + + ////////////////////////////////////////////////////////////////////////// + case kMenuContinue: { + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + // Determine the proper CD archive + ArchiveIndex cd = kArchiveCd1; + if (getProgress().chapter > kChapter1) + cd = (getProgress().chapter > kChapter3) ? kArchiveCd3 : kArchiveCd2; + + // Show tooltips & buttons to start a game, continue a game or load the proper cd + if (ResourceManager::isArchivePresent(cd)) { + if (_isGameStarted) { + showFrame(kOverlayEggButtons, kButtonContinue, true); + + if (_index2 == _index) { + showFrame(kOverlayTooltip, isGameFinished() ? kTooltipViewGameEnding : kTooltipContinueGame, true); + } else { + showFrame(kOverlayTooltip, kTooltipContinueRewoundGame, true); + } + + } else { + showFrame(kOverlayEggButtons, kButtonShield, true); + showFrame(kOverlayTooltip, kTooltipPlayNewGame, true); + } + } else { + showFrame(kOverlayEggButtons, -1, true); + showFrame(kOverlayTooltip, cd - 1, true); + } + + if (!clicked) + break; + + // Try loading the archive file + if (!_engine->getResourceManager()->loadArchive(cd)) + break; + + // Load the train data file and setup game + getScenes()->loadSceneDataFile(cd); + showFrame(kOverlayTooltip, -1, true); + getSound()->playSound(kEntityPlayer, "LIB046"); + + // Setup new game + getSavePoints()->reset(); + setLogicEventHandlers(); + + getSound()->processEntry(SoundManager::kSoundType11); + + if (!getFlags()->mouseRightClick) { + getScenes()->loadScene((SceneIndex)(5 * _gameId + 3)); + + if (!getFlags()->mouseRightClick) { + getScenes()->loadScene((SceneIndex)(5 * _gameId + 4)); + + if (!getFlags()->mouseRightClick) { + getScenes()->loadScene((SceneIndex)(5 * _gameId + 5)); + + if (!getFlags()->mouseRightClick) { + getSound()->processEntry(SoundManager::kSoundType11); + + // Show intro + Animation animation; + if (animation.load(getArchive("1601.nis"))) + animation.play(); + + getEvent(kEventIntro) = 1; + } + } + } + } + + if (!getEvent(kEventIntro)) { + getEvent(kEventIntro) = 1; + + getSound()->processEntry(SoundManager::kSoundType11); + } + + // Setup game + getFlags()->isGameRunning = true; + startGame(); + + if (!_isShowingMenu) + getInventory()->show(); + + return false; + } + + ////////////////////////////////////////////////////////////////////////// + case kMenuSwitchSaveGame: + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + if (clicked) { + showFrame(kOverlayAcorn, 1, true); + showFrame(kOverlayTooltip, -1, true); + getSound()->playSound(kEntityPlayer, "LIB047"); + + // Setup new menu screen + switchGame(); + setup(); + + // Set fight state to 0 + getFight()->resetState(); + + return true; + } + + // TODO Check for flag + + showFrame(kOverlayAcorn, 0, true); + + if (_isGameStarted) { + showFrame(kOverlayTooltip, kTooltipSwitchBlueGame, true); + break; + } + + if (_gameId == kGameGold) { + showFrame(kOverlayTooltip, kTooltipSwitchBlueGame, true); + break; + } + + if (!SaveLoad::isSavegameValid(getNextGameId())) { + showFrame(kOverlayTooltip, kTooltipStartAnotherGame, true); + break; + } + + // Stupid tooltips ids are not in order, so we can't just increment them... + switch(_gameId) { + default: + break; + + case kGameBlue: + showFrame(kOverlayTooltip, kTooltipSwitchRedGame, true); + break; + + case kGameRed: + showFrame(kOverlayTooltip, kTooltipSwitchGreenGame, true); + break; + + case kGameGreen: + showFrame(kOverlayTooltip, kTooltipSwitchPurpleGame, true); + break; + + case kGamePurple: + showFrame(kOverlayTooltip, kTooltipSwitchTealGame, true); + break; + + case kGameTeal: + showFrame(kOverlayTooltip, kTooltipSwitchGoldGame, true); + break; + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuRewindGame: + if (!_index || _currentTime < _time) { + hideOverlays(); + break; + } + + if (clicked) { + if (hasTimeDelta()) + _handleTimeDelta = false; + + showFrame(kOverlayEggButtons, kButtonRewindPushed, true); + showFrame(kOverlayTooltip, -1, true); + + getSound()->playSound(kEntityPlayer, "LIB046"); + + rewindTime(); + + _handleTimeDelta = false; + } else { + showFrame(kOverlayEggButtons, kButtonRewind, true); + showFrame(kOverlayTooltip, kTooltipRewind, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuForwardGame: + if (_index2 <= _index || _currentTime > _time) { + hideOverlays(); + break; + } + + if (clicked) { + if (hasTimeDelta()) + _handleTimeDelta = false; + + showFrame(kOverlayEggButtons, kButtonForwardPushed, true); + showFrame(kOverlayTooltip, -1, true); + + getSound()->playSound(kEntityPlayer, "LIB046"); + + forwardTime(); + + _handleTimeDelta = false; + } else { + showFrame(kOverlayEggButtons, kButtonForward, true); + showFrame(kOverlayTooltip, kTooltipFastForward, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuParis: + moveToCity(kParis, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuStrasBourg: + moveToCity(kStrasbourg, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuMunich: + moveToCity(kMunich, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuVienna: + moveToCity(kVienna, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuBudapest: + moveToCity(kBudapest, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuBelgrade: + moveToCity(kBelgrade, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuConstantinople: + moveToCity(kConstantinople, clicked); + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuDecreaseVolume: + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + // Cannot decrease volume further + if (getVolume() == 0) { + showFrame(kOverlayButtons, kButtonVolume, true); + showFrame(kOverlayTooltip, -1, true); + break; + } + + showFrame(kOverlayTooltip, kTooltipVolumeDown, true); + + // Show highlight on button & adjust volume if needed + if (clicked) { + showFrame(kOverlayButtons, kButtonVolumeDownPushed, true); + getSound()->playSound(kEntityPlayer, "LIB046"); + setVolume(getVolume() - 1); + + getSaveLoad()->saveVolumeBrightness(); + + uint32 nextFrameCount = getFrameCount() + 15; + while (nextFrameCount > getFrameCount()) { + _engine->pollEvents(); + + getSound()->updateQueue(); + } + } else { + showFrame(kOverlayButtons, kButtonVolumeDown, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuIncreaseVolume: + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + // Cannot increase volume further + if (getVolume() >= 7) { + showFrame(kOverlayButtons, kButtonVolume, true); + showFrame(kOverlayTooltip, -1, true); + break; + } + + showFrame(kOverlayTooltip, kTooltipVolumeUp, true); + + // Show highlight on button & adjust volume if needed + if (clicked) { + showFrame(kOverlayButtons, kButtonVolumeUpPushed, true); + getSound()->playSound(kEntityPlayer, "LIB046"); + setVolume(getVolume() + 1); + + getSaveLoad()->saveVolumeBrightness(); + + uint32 nextFrameCount = getFrameCount() + 15; + while (nextFrameCount > getFrameCount()) { + _engine->pollEvents(); + + getSound()->updateQueue(); + } + } else { + showFrame(kOverlayButtons, kButtonVolumeUp, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuDecreaseBrightness: + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + // Cannot increase brightness further + if (getBrightness() == 0) { + showFrame(kOverlayButtons, kButtonBrightness, true); + showFrame(kOverlayTooltip, -1, true); + break; + } + + showFrame(kOverlayTooltip, kTooltipBrightnessDown, true); + + // Show highlight on button & adjust brightness if needed + if (clicked) { + showFrame(kOverlayButtons, kButtonBrightnessDownPushed, true); + getSound()->playSound(kEntityPlayer, "LIB046"); + setBrightness(getBrightness() - 1); + + getSaveLoad()->saveVolumeBrightness(); + + // Reshow the background and frames (they will pick up the new brightness through the GraphicsManager) + _engine->getGraphicsManager()->draw(getScenes()->get((SceneIndex)(_isGameStarted ? _gameId * 5 + 1 : _gameId * 5 + 2)), GraphicsManager::kBackgroundC, true); + showFrame(kOverlayTooltip, kTooltipBrightnessDown, false); + showFrame(kOverlayButtons, kButtonBrightnessDownPushed, false); + } else { + showFrame(kOverlayButtons, kButtonBrightnessDown, true); + } + break; + + ////////////////////////////////////////////////////////////////////////// + case kMenuIncreaseBrightness: + if (hasTimeDelta()) { + hideOverlays(); + break; + } + + // Cannot increase brightness further + if (getBrightness() >= 6) { + showFrame(kOverlayButtons, kButtonBrightness, true); + showFrame(kOverlayTooltip, -1, true); + break; + } + + showFrame(kOverlayTooltip, kTooltipBrightnessUp, true); + + // Show highlight on button & adjust brightness if needed + if (clicked) { + showFrame(kOverlayButtons, kButtonBrightnessUpPushed, true); + getSound()->playSound(kEntityPlayer, "LIB046"); + setBrightness(getBrightness() + 1); + + getSaveLoad()->saveVolumeBrightness(); + + // Reshow the background and frames (they will pick up the new brightness through the GraphicsManager) + _engine->getGraphicsManager()->draw(getScenes()->get((SceneIndex)(_isGameStarted ? _gameId * 5 + 1 : _gameId * 5 + 2)), GraphicsManager::kBackgroundC, true); + showFrame(kOverlayTooltip, kTooltipBrightnessUp, false); + showFrame(kOverlayButtons, kButtonBrightnessUpPushed, false); + } else { + showFrame(kOverlayButtons, kButtonBrightnessUp, true); + } + break; + } + + return true; +} + +void Menu::setLogicEventHandlers() { + SET_EVENT_HANDLERS(Logic, getLogic()); + clear(); + _isShowingMenu = false; +} + +////////////////////////////////////////////////////////////////////////// +// Game-related +////////////////////////////////////////////////////////////////////////// +void Menu::init(bool doSavegame, SavegameType type, uint32 value) { + + bool useSameIndex = true; + + if (getGlobalTimer()) { + value = 0; + + // Check if the CD file is present + ArchiveIndex index = kArchiveCd1; + switch (getProgress().chapter) { + default: + case kChapter1: + break; + + case kChapter2: + case kChapter3: + index = kArchiveCd2; + break; + + case kChapter4: + case kChapter5: + index = kArchiveCd3; + break; + } + + if (ResourceManager::isArchivePresent(index)) { + setGlobalTimer(0); + useSameIndex = false; + + // TODO remove existing savegame and reset index & savegame name + warning("Menu::initGame: not implemented!"); + } + + doSavegame = false; + } else { + // TODO rename saves? + } + + // Create a new savegame if needed + if (!SaveLoad::isSavegamePresent(_gameId)) + SaveLoad::writeMainHeader(_gameId); + + if (doSavegame) + getSaveLoad()->saveGame(kSavegameTypeEvent2, kEntityPlayer, kEventNone); + + if (!getGlobalTimer()) { + // TODO: remove existing savegame temp file + } + + // Init savegame and get the header data + getSaveLoad()->initSavegame(_gameId, true); + SaveLoad::SavegameMainHeader header; + if (!SaveLoad::loadMainHeader(_gameId, &header)) + error("Menu::init: Corrupted savegame - Recovery path not implemented!"); + + // Init Menu values + _index2 = header.index; + _lowerTime = getSaveLoad()->getEntry(_index2)->time; + + if (useSameIndex) + _index = _index2; + + //if (!getGlobalTimer()) + // _index3 = 0; + + if (!getProgress().chapter) + getProgress().chapter = kChapter1; + + getState()->time = getSaveLoad()->getEntry(_index)->time; + getProgress().chapter = getSaveLoad()->getEntry(_index)->chapter; + + if (_lowerTime >= kTimeStartGame) { + _currentTime = getState()->time; + _time = getState()->time; + _clock->draw(_time); + _trainLine->draw(_time); + + initTime(type, value); + } +} + +void Menu::startGame() { + // TODO: we need to reset the current scene + getState()->scene = kSceneDefault; + + getEntities()->setup(true, kEntityPlayer); + warning("Menu::startGame: not implemented!"); +} + +// Switch to the next savegame +void Menu::switchGame() { + + // Switch back to blue game is the current game is not started + _gameId = SaveLoad::isSavegameValid(_gameId) ? getNextGameId() : kGameBlue; + + // Initialize savegame if needed + if (!SaveLoad::isSavegamePresent(_gameId)) + SaveLoad::writeMainHeader(_gameId); + + getState()->time = 0; + + // Clear menu elements + _clock->clear(); + _trainLine->clear(); + + // Clear loaded savegame data + getSaveLoad()->clearEntries(); + + init(false, kSavegameTypeIndex, 0); +} + +bool Menu::isGameFinished() const { + SaveLoad::SavegameEntryHeader *data = getSaveLoad()->getEntry(_index); + + if (_index2 != _index) + return false; + + if (data->type != SaveLoad::kHeaderType2) + return false; + + return (data->event == kEventAnnaKilled + || data->event == kEventKronosHostageAnnaNoFirebird + || data->event == kEventKahinaPunchBaggageCarEntrance + || data->event == kEventKahinaPunchBlue + || data->event == kEventKahinaPunchYellow + || data->event == kEventKahinaPunchSalon + || data->event == kEventKahinaPunchKitchen + || data->event == kEventKahinaPunchBaggageCar + || data->event == kEventKahinaPunchCar + || data->event == kEventKahinaPunchSuite4 + || data->event == kEventKahinaPunchRestaurant + || data->event == kEventKahinaPunch + || data->event == kEventKronosGiveFirebird + || data->event == kEventAugustFindCorpse + || data->event == kEventMertensBloodJacket + || data->event == kEventMertensCorpseFloor + || data->event == kEventMertensCorpseBed + || data->event == kEventCoudertBloodJacket + || data->event == kEventGendarmesArrestation + || data->event == kEventAbbotDrinkGiveDetonator + || data->event == kEventMilosCorpseFloor + || data->event == kEventLocomotiveAnnaStopsTrain + || data->event == kEventTrainStopped + || data->event == kEventCathVesnaRestaurantKilled + || data->event == kEventCathVesnaTrainTopKilled + || data->event == kEventLocomotiveConductorsDiscovered + || data->event == kEventViennaAugustUnloadGuns + || data->event == kEventViennaKronosFirebird + || data->event == kEventVergesAnnaDead + || data->event == kEventTrainExplosionBridge + || data->event == kEventKronosBringNothing); +} + +////////////////////////////////////////////////////////////////////////// +// Overlays & elements +////////////////////////////////////////////////////////////////////////// +void Menu::checkHotspots() { + if (!_isShowingMenu) + return; + + if (!getFlags()->shouldRedraw) + return; + + if (_isShowingCredits) + return; + + SceneHotspot *hotspot = NULL; + getScenes()->get(getState()->scene)->checkHotSpot(getCoords(), &hotspot); + + if (hotspot) + handleEvent((StartMenuAction)hotspot->action, _mouseFlags); + else + hideOverlays(); +} + +void Menu::hideOverlays() { + _lastHotspot = NULL; + + // Hide all menu overlays + for (MenuFrames::iterator it = _frames.begin(); it != _frames.end(); it++) + showFrame(it->_key, -1, false); + + getScenes()->drawFrames(true); +} + +void Menu::showFrame(StartMenuOverlay overlayType, int index, bool redraw) { + if (index == -1) { + getScenes()->removeFromQueue(_frames[overlayType]); + } else { + // Check that the overlay is valid + if (!_frames[overlayType]) + return; + + // Remove the frame and add a new one with the proper index + getScenes()->removeFromQueue(_frames[overlayType]); + _frames[overlayType]->setFrame((uint16)index); + getScenes()->addToQueue(_frames[overlayType]); + } + + if (redraw) + getScenes()->drawFrames(true); +} + +// Remove all frames from the queue +void Menu::clear() { + for (MenuFrames::iterator it = _frames.begin(); it != _frames.end(); it++) + getScenes()->removeAndRedraw(&it->_value, false); + + clearBg(GraphicsManager::kBackgroundOverlay); +} + +// Get the sequence name to use for the acorn highlight, depending of the currently loaded savegame +Common::String Menu::getAcornSequenceName(GameId id) const { + Common::String name = ""; + switch (id) { + default: + case kGameBlue: + name = "aconblu3.seq"; + break; + + case kGameRed: + name = "aconred.seq"; + break; + + case kGameGreen: + name = "acongren.seq"; + break; + + case kGamePurple: + name = "aconpurp.seq"; + break; + + case kGameTeal: + name = "aconteal.seq"; + break; + + case kGameGold: + name = "acongold.seq"; + break; + } + + return name; +} + +////////////////////////////////////////////////////////////////////////// +// Time +////////////////////////////////////////////////////////////////////////// +void Menu::initTime(SavegameType type, uint32 value) { + if (!value) + return; + + // The savegame entry index + uint32 entryIndex = 0; + + switch (type) { + default: + break; + + case kSavegameTypeIndex: + entryIndex = (_index <= value) ? 1 : _index - value; + break; + + case kSavegameTypeTime: + if (value < kTimeStartGame) + break; + + entryIndex = _index; + if (!entryIndex) + break; + + // Iterate through existing entries + do { + if (getSaveLoad()->getEntry(entryIndex)->time <= value) + break; + + entryIndex--; + } while (entryIndex); + break; + + case kSavegameTypeEvent: + entryIndex = _index; + if (!entryIndex) + break; + + do { + if (getSaveLoad()->getEntry(entryIndex)->event == (EventIndex)value) + break; + + entryIndex--; + } while (entryIndex); + break; + + case kSavegameTypeEvent2: + // TODO rewrite in a more legible way + if (_index > 1) { + uint32 index = _index; + do { + if (getSaveLoad()->getEntry(index)->event == (EventIndex)value) + break; + + index--; + } while (index > 1); + + entryIndex = index - 1; + } else { + entryIndex = _index - 1; + } + break; + } + + if (entryIndex) { + _currentIndex = entryIndex; + updateTime(getSaveLoad()->getEntry(entryIndex)->time); + } +} + +void Menu::updateTime(uint32 time) { + if (_currentTime == _time) + _delta = 0; + + _currentTime = time; + + if (_time != time) { + if (getSound()->isBuffered(kEntityChapters)) + getSound()->removeFromQueue(kEntityChapters); + + getSound()->playSoundWithSubtitles((_currentTime >= _time) ? "LIB042" : "LIB041", SoundManager::kFlagMenuClock, kEntityChapters); + adjustIndex(_currentTime, _time, false); + } +} + +void Menu::adjustIndex(uint32 time1, uint32 time2, bool searchEntry) { + uint32 index = 0; + int32 timeDelta = -1; + + if (time1 != time2) { + + index = _index; + + if (time2 >= time1) { + if (searchEntry) { + uint32 currentIndex = _index; + + if ((int32)_index >= 0) { + do { + // Calculate new delta + int32 newDelta = time1 - getSaveLoad()->getEntry(currentIndex)->time; + + if (newDelta >= 0 && timeDelta >= newDelta) { + timeDelta = newDelta; + index = currentIndex; + } + + --currentIndex; + } while ((int32)currentIndex >= 0); + } + } else { + index = _index - 1; + } + } else { + if (searchEntry) { + uint32 currentIndex = _index; + + if (_index2 >= _index) { + do { + // Calculate new delta + int32 newDelta = getSaveLoad()->getEntry(currentIndex)->time - time1; + + if (newDelta >= 0 && timeDelta > newDelta) { + timeDelta = newDelta; + index = currentIndex; + } + + ++currentIndex; + } while (currentIndex >= _index2); + } + } else { + index = _index + 1; + } + } + + _index = index; + checkHotspots(); + } + + if (_index == _currentIndex) { + if (getProgress().chapter != getSaveLoad()->getEntry(index)->chapter) + getProgress().chapter = getSaveLoad()->getEntry(_index)->chapter; + } +} + +void Menu::goToTime(uint32 time) { + + uint32 entryIndex = 0; + uint32 deltaTime = (uint32)ABS((int32)(getSaveLoad()->getEntry(0)->time - time)); + uint32 index = 0; + + do { + uint32 deltaTime2 = (uint32)ABS((int32)(getSaveLoad()->getEntry(index)->time - time)); + if (deltaTime2 < deltaTime) { + deltaTime = deltaTime2; + entryIndex = index; + } + + ++index; + } while (_index2 >= index); + + _currentIndex = entryIndex; + updateTime(getSaveLoad()->getEntry(entryIndex)->time); +} + +void Menu::setTime() { + _currentIndex = _index; + _currentTime = getSaveLoad()->getEntry(_currentIndex)->time; + + if (_time == _currentTime) + adjustTime(); +} + +void Menu::forwardTime() { + if (_index2 <= _index) + return; + + _currentIndex = _index2; + updateTime(getSaveLoad()->getEntry(_currentIndex)->time); +} + +void Menu::rewindTime() { + if (!_index) + return; + + _currentIndex = 0; + updateTime(getSaveLoad()->getEntry(_currentIndex)->time); +} + +void Menu::adjustTime() { + uint32 originalTime = _time; + + // Adjust time delta + uint32 timeDelta = (_delta >= 90) ? 9 : (9 * _delta + 89) / 90; + + if (_currentTime < _time) { + _time -= 900 * timeDelta; + + if (_time >= _currentTime) + _time = _currentTime; + } else { + _time += 900 * timeDelta; + + if (_time < _currentTime) + _time = _currentTime; + } + + if (_currentTime == _time && getSound()->isBuffered(kEntityChapters)) + getSound()->removeFromQueue(kEntityChapters); + + _clock->draw(_time); + _trainLine->draw(_time); + getScenes()->drawFrames(true); + + adjustIndex(_time, originalTime, true); + + ++_delta; +} + +void Menu::moveToCity(CityButton city, bool clicked) { + uint32 time = (uint32)_cityButtonsInfo[city].time; + + // TODO Check if we have access (there seems to be more checks on some internal times) - probably : current_time (menu only) / game time / some other? + if (_lowerTime < time || _time == time || _currentTime == time) { + hideOverlays(); + return; + } + + // Show city overlay + showFrame((StartMenuOverlay)((_cityButtonsInfo[city].index >> 6) + 3), _cityButtonsInfo[city].index & 63, true); + + if (clicked) { + showFrame(kOverlayTooltip, -1, true); + getSound()->playSound(kEntityPlayer, "LIB046"); + goToTime(time); + + _handleTimeDelta = true; + + return; + } + + // Special case of first and last cities + if (city == kParis || city == kConstantinople) { + showFrame(kOverlayTooltip, (city == kParis) ? kTooltipRewindParis : kTooltipForwardConstantinople, true); + return; + } + + showFrame(kOverlayTooltip, (_time <= time) ? _cityButtonsInfo[city].forward : _cityButtonsInfo[city].rewind, true); +} + +////////////////////////////////////////////////////////////////////////// +// Sound / Brightness +////////////////////////////////////////////////////////////////////////// + +// Get current volume (converted internal ScummVM value) +uint32 Menu::getVolume() const { + return getState()->volume; +} + +// Set the volume (converts to ScummVM values) +void Menu::setVolume(uint32 volume) const { + getState()->volume = volume; + + // Clamp volume + uint32 value = volume * Audio::Mixer::kMaxMixerVolume / 7; + + if (value > Audio::Mixer::kMaxMixerVolume) + value = Audio::Mixer::kMaxMixerVolume; + + _engine->_mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, (int32)value); +} + +uint32 Menu::getBrightness() const { + return getState()->brightness; +} + +void Menu::setBrightness(uint32 brightness) const { + getState()->brightness = brightness; + + // TODO reload cursor & font with adjusted brightness +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/menu.h b/engines/lastexpress/game/menu.h new file mode 100644 index 0000000000..54d1eb65ec --- /dev/null +++ b/engines/lastexpress/game/menu.h @@ -0,0 +1,211 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_MENU_H +#define LASTEXPRESS_MENU_H + +#include "lastexpress/data/sequence.h" + +#include "lastexpress/eventhandler.h" + +#include "lastexpress/shared.h" + +#include "common/hashmap.h" + +namespace LastExpress { + +class LastExpressEngine; +class Scene; +class SceneHotspot; + +class Clock; +class TrainLine; + +class Menu : public EventHandler { +public: + Menu(LastExpressEngine *engine); + ~Menu(); + + void show(bool doSavegame, SavegameType type, uint32 value); + + // Event handling + void eventMouse(const Common::Event &ev); + void eventTick(const Common::Event &ev); + + bool isShown() const { return _isShowingMenu; } + + GameId getGameId() const { return _gameId; } + +private: + // Start menu events + enum StartMenuAction { + kMenuContinue = 1, + kMenuCredits = 2, + kMenuQuitGame = 3, + kMenuCase4 = 4, + kMenuSwitchSaveGame = 6, + kMenuRewindGame = 7, + kMenuForwardGame = 8, + kMenuParis = 10, + kMenuStrasBourg = 11, + kMenuMunich = 12, + kMenuVienna = 13, + kMenuBudapest = 14, + kMenuBelgrade = 15, + kMenuConstantinople = 16, + kMenuDecreaseVolume = 17, + kMenuIncreaseVolume = 18, + kMenuDecreaseBrightness = 19, + kMenuIncreaseBrightness = 20 + }; + + // City buttons + enum CityButton { + kParis = 0, + kStrasbourg = 1, + kMunich = 2, + kVienna = 3, + kBudapest = 4, + kBelgrade = 5, + kConstantinople = 6 + }; + + // Start menu overlay elements + enum StartMenuOverlay { + kOverlayTooltip, // 0 + kOverlayEggButtons, + kOverlayButtons, + kOverlayAcorn, + kOverlayCity1, + kOverlayCity2, // 5 + kOverlayCity3, + kOverlayCredits + }; + + LastExpressEngine *_engine; + + // Sequences + Sequence* _seqTooltips; + Sequence* _seqEggButtons; + Sequence* _seqButtons; + Sequence* _seqAcorn; + Sequence* _seqCity1; + Sequence* _seqCity2; + Sequence* _seqCity3; + Sequence* _seqCredits; + + GameId _gameId; + + // Indicator to know if we need to show the start animation when showMenu is called + bool _hasShownStartScreen; + bool _hasShownIntro; + + bool _isShowingCredits; + bool _isGameStarted; + bool _isShowingMenu; + + + uint16 _creditsSequenceIndex; + + ////////////////////////////////////////////////////////////////////////// + // Event handling + uint32 _checkHotspotsTicks; + Common::EventType _mouseFlags; + SceneHotspot *_lastHotspot; + + void init(bool doSavegame, SavegameType type, uint32 value); + void setup(); + bool handleEvent(StartMenuAction action, Common::EventType type); + void checkHotspots(); + void setLogicEventHandlers(); + + ////////////////////////////////////////////////////////////////////////// + // Game-related + void startGame(); + void switchGame(); + bool isGameFinished() const; + + ////////////////////////////////////////////////////////////////////////// + // Overlays & elements + Clock *_clock; + TrainLine *_trainLine; + + struct MenuOverlays_EqualTo { + bool operator()(const StartMenuOverlay& x, const StartMenuOverlay& y) const { return x == y; } + }; + + struct MenuOverlays_Hash { + uint operator()(const StartMenuOverlay& x) const { return x; } + }; + + typedef Common::HashMap<StartMenuOverlay, SequenceFrame *, MenuOverlays_Hash, MenuOverlays_EqualTo> MenuFrames; + + MenuFrames _frames; + + void hideOverlays(); + void showFrame(StartMenuOverlay overlay, int index, bool redraw); + + void clear(); + + // TODO: remove? + void moveToCity(CityButton city, bool clicked); + + ////////////////////////////////////////////////////////////////////////// + // Misc + Common::String getAcornSequenceName(GameId id) const; + + ////////////////////////////////////////////////////////////////////////// + // Time + uint32 _currentIndex; // current savegame entry + uint32 _currentTime; // current game time + uint32 _lowerTime; // lower time value + + uint32 _index; + uint32 _index2; + uint32 _time; + uint32 _delta; + bool _handleTimeDelta; + + void initTime(SavegameType type, uint32 time); + void updateTime(uint32 time); + void adjustTime(); + void adjustIndex(uint32 time1, uint32 time2, bool searchEntry); + void goToTime(uint32 time); + void setTime(); + void forwardTime(); + void rewindTime(); + bool hasTimeDelta() { return (_currentTime - _time) >= 1; } + + ////////////////////////////////////////////////////////////////////////// + // Sound/Brightness related + uint32 getVolume() const; + void setVolume(uint32 volume) const; + uint32 getBrightness() const; + void setBrightness(uint32 brightness) const; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_MENU_H diff --git a/engines/lastexpress/game/object.cpp b/engines/lastexpress/game/object.cpp new file mode 100644 index 0000000000..0b336b941f --- /dev/null +++ b/engines/lastexpress/game/object.cpp @@ -0,0 +1,108 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/object.h" + +#include "lastexpress/game/logic.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +Common::String Objects::Object::toString() { + return Common::String::printf("{ %s - %d - %d - %d - %d }", ENTITY_NAME(entity), location, cursor, cursor2, location2); +} + +Objects::Objects(LastExpressEngine *engine) : _engine(engine) {} + +const Objects::Object Objects::get(ObjectIndex index) const { + if (index >= kObjectMax) + error("Objects::get - internal error: invalid object index (%d)", index); + + return _objects[index]; +} + +void Objects::update(ObjectIndex index, EntityIndex entity, ObjectLocation location, CursorStyle cursor, CursorStyle cursor2) { + if (index >= kObjectMax) + return; + + Object *object = &_objects[index]; + + // Store original location + ObjectLocation original_location = object->location; + + // Update entity + object->entity = entity; + object->location = location; + + if (cursor != kCursorKeepValue || cursor2 != kCursorKeepValue) { + if (cursor != kCursorKeepValue) + object->cursor = cursor; + if (cursor2 != kCursorKeepValue) + object->cursor2 = cursor2; + + getLogic()->updateCursor(); + } + + getFlags()->flag_3 = true; + + // Compartments + if (original_location != location && (original_location == kObjectLocation2 || location == kObjectLocation2)) + if ((index >= kObjectCompartment1 && index <= kObjectCompartment8) + || (index >= kObjectCompartmentA && index <= kObjectCompartmentF)) { + getScenes()->updateDoorsAndClock(); + } +} + +void Objects::updateLocation2(ObjectIndex index, ObjectLocation location2) { + if (index >= kObjectMax) + return; + + _objects[index].location2 = location2; +} + +////////////////////////////////////////////////////////////////////////// +// Serializable +////////////////////////////////////////////////////////////////////////// +void Objects::saveLoadWithSerializer(Common::Serializer &) { + error("Objects::saveLoadWithSerializer: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// toString +////////////////////////////////////////////////////////////////////////// +Common::String Objects::toString() { + Common::String ret = ""; + + for (int i = 0; i < kObjectMax; i++) + ret += Common::String::printf("%d : %s\n", i, _objects[i].toString().c_str()); + + return ret; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/object.h b/engines/lastexpress/game/object.h new file mode 100644 index 0000000000..3417e9bfcf --- /dev/null +++ b/engines/lastexpress/game/object.h @@ -0,0 +1,83 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_OBJECT_H +#define LASTEXPRESS_OBJECT_H + +#include "lastexpress/shared.h" + +#include "common/serializer.h" +#include "common/system.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Objects : Common::Serializable { +public: + + struct Object { // All fields should be saved as bytes + EntityIndex entity; + ObjectLocation location; + CursorStyle cursor; + CursorStyle cursor2; + ObjectLocation location2; + + Object() { + entity = kEntityPlayer; + location = kObjectLocationNone; + cursor = kCursorHandKnock; + cursor2 = kCursorHandKnock; + location2 = kObjectLocationNone; + } + + Common::String toString(); + }; + + Objects(LastExpressEngine *engine); + + const Object get(ObjectIndex index) const; + void update(ObjectIndex index, EntityIndex entity, ObjectLocation location, CursorStyle cursor, CursorStyle cursor2); + void updateLocation2(ObjectIndex index, ObjectLocation location2); + + // Serializable + void saveLoadWithSerializer(Common::Serializer &ser); + + /** + * Convert this object into a string representation. + * + * @return A string representation of this object. + */ + Common::String toString(); + +private: + LastExpressEngine* _engine; + + Object _objects[kObjectMax]; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_OBJECT_H diff --git a/engines/lastexpress/game/savegame.cpp b/engines/lastexpress/game/savegame.cpp new file mode 100644 index 0000000000..1bd3d8239b --- /dev/null +++ b/engines/lastexpress/game/savegame.cpp @@ -0,0 +1,310 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/logic.h" +#include "lastexpress/game/savegame.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/debug.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/helpers.h" + +#include "common/file.h" +#include "common/system.h" + +namespace LastExpress { + +// Savegame signatures +#define SAVEGAME_SIGNATURE 0x12001200 +#define SAVEGAME_HEADER 0xE660E660 + +// Names of savegames +static const struct { + const char *saveFile; +} gameInfo[6] = { + {"blue.egg"}, + {"red.egg"}, + {"green.egg"}, + {"purple.egg"}, + {"teal.egg"}, + {"gold.egg"} +}; + +////////////////////////////////////////////////////////////////////////// +// Constructors +////////////////////////////////////////////////////////////////////////// + +SaveLoad::SaveLoad(LastExpressEngine *engine) : _engine(engine) { + _gameTicksLastSavegame = 0; +} + +SaveLoad::~SaveLoad() { + //Zero passed pointers + _engine = NULL; + + clearEntries(); +} + +////////////////////////////////////////////////////////////////////////// +// Save & Load +////////////////////////////////////////////////////////////////////////// + +// Load game +bool SaveLoad::loadGame(GameId id) { + + if (!SaveLoad::isSavegamePresent(id)) + return false; + + //Common::InSaveFile *save = SaveLoad::openForLoading(id); + // Validate header + + + + + error("SaveLoad::loadgame: not implemented!"); + + return false; +} + +// Save game +void SaveLoad::saveGame(SavegameType type, EntityIndex entity, uint32 value) { + + // Save ticks + _gameTicksLastSavegame = getState()->timeTicks; + + warning("SaveLoad::savegame: not implemented!"); +} + +void SaveLoad::saveVolumeBrightness() { + warning("SaveLoad::saveVolumeBrightness: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// Static Members +////////////////////////////////////////////////////////////////////////// + +// Check if a specific savegame exists +bool SaveLoad::isSavegamePresent(GameId id) { + if (g_system->getSavefileManager()->listSavefiles(getSavegameName(id)).size() == 0) + return false; + + return true; +} + +// Check if the game has been started in the specific savegame +bool SaveLoad::isSavegameValid(GameId id) { + if (!isSavegamePresent(id)) { + debugC(2, kLastExpressDebugSavegame, "SaveLoad::isSavegameValid - Savegame does not exist: %s", getSavegameName(id).c_str()); + return false; + } + + SavegameMainHeader header; + if (!loadMainHeader(id, &header)) + return false; + + return validateMainHeader(header); +} + + +////////////////////////////////////////////////////////////////////////// +// Headers +////////////////////////////////////////////////////////////////////////// +bool SaveLoad::loadMainHeader(GameId id, SavegameMainHeader* header) { + // Read first 32 bytes of savegame + Common::InSaveFile *save = openForLoading(id); + if (!save) { + debugC(2, kLastExpressDebugSavegame, "SaveLoad::loadMainHeader - Cannot open savegame for reading: %s", getSavegameName(id).c_str()); + return false; + } + + // Check there is enough data + if (save->size() < 32) { + debugC(2, kLastExpressDebugSavegame, "SaveLoad::loadMainHeader - Savegame seems to be corrupted (not enough data: %i bytes): %s", save->size(), getSavegameName(id).c_str()); + delete save; + return false; + } + + header->signature = save->readUint32LE(); + header->index = save->readUint32LE(); + header->time = save->readUint32LE(); + header->field_C = save->readUint32LE(); + header->field_10 = save->readUint32LE(); + header->brightness = save->readSint32LE(); + header->volume = save->readSint32LE(); + header->field_1C = save->readUint32LE(); + + delete save; + + // Valide the header + if (!validateMainHeader(*header)) { + debugC(2, kLastExpressDebugSavegame, "SaveLoad::loadMainHeader - Cannot validate main header for savegame %s.", getSavegameName(id).c_str()); + return false; + } + + return true; +} + +void SaveLoad::loadEntryHeader(Common::InSaveFile *save, SavegameEntryHeader *header) { + header->signature = save->readUint32LE(); + header->type = (HeaderType)save->readUint32LE(); + header->time = save->readUint32LE(); + header->field_C = save->readUint32LE(); + header->chapter = (ChapterIndex)save->readUint32LE(); + header->event = (EventIndex)save->readUint32LE(); + header->field_18 = save->readUint32LE(); + header->field_1C = save->readUint32LE(); +} + +bool SaveLoad::validateMainHeader(const SavegameMainHeader &header) { + if (header.signature != SAVEGAME_SIGNATURE) + return false; + + /* Check not needed as it can never be < 0 + if (header.chapter < 0) + return false;*/ + + if (header.time < 32) + return false; + + if (header.field_C < 32) + return false; + + if (header.field_10 != 1 && header.field_10) + return false; + + if (header.brightness < 0 || header.brightness > 6) + return false; + + if (header.volume < 0 || header.volume > 7) + return false; + + if (header.field_1C != 9) + return false; + + return true; +} + +bool SaveLoad::validateEntryHeader(const SavegameEntryHeader &header) { + if (header.signature != SAVEGAME_HEADER) + return false; + + if (header.type < kHeaderType1 || header.type > kHeaderType5) + return false; + + if (header.time < kTimeStartGame || header.time > kTimeCityConstantinople) + return false; + + if (header.field_C <= 0 || header.field_C >= 15) + return false; + + /* No check for < 0, as it cannot happen normaly */ + if (header.chapter == 0) + return false; + + return true; +} + +SaveLoad::SavegameEntryHeader *SaveLoad::getEntry(uint32 index) { + if (index >= _gameHeaders.size()) + error("SaveLoad::getEntry: invalid index (was:%d, max:%d)", index, _gameHeaders.size() - 1); + + return _gameHeaders[index]; +} + +void SaveLoad::clearEntries() { + for (uint i = 0; i < _gameHeaders.size(); i++) + SAFE_DELETE(_gameHeaders[i]); + + _gameHeaders.clear(); +} + +////////////////////////////////////////////////////////////////////////// +// Init +////////////////////////////////////////////////////////////////////////// +void SaveLoad::writeMainHeader(GameId id) { + Common::OutSaveFile *save = openForSaving(id); + if (!save) { + debugC(2, kLastExpressDebugSavegame, "SaveLoad::initSavegame - Cannot open savegame for writing: %s", getSavegameName(id).c_str()); + return; + } + + // Write default values to savegame + save->writeUint32LE(SAVEGAME_SIGNATURE); + save->writeUint32LE(0); + save->writeUint32LE(32); + save->writeUint32LE(32); + save->writeUint32LE(0); + save->writeUint32LE(3); + save->writeUint32LE(7); + save->writeUint32LE(9); + + delete save; +} + +void SaveLoad::initSavegame(GameId id, bool resetHeaders) { + //Common::OutSaveFile *save = openForSaving(id); + //if (!save) { + // debugC(2, kLastExpressDebugSavegame, "SaveLoad::initSavegame - Cannot open savegame for writing: %s", getSavegameName(id).c_str()); + // return; + //} + + if (resetHeaders) { + clearEntries(); + + SavegameEntryHeader *header = new SavegameEntryHeader(); + header->time = kTimeCityParis; + header->chapter = kChapter1; + + _gameHeaders.push_back(header); + } + + // Open the savegame and read all game headers + + warning("SaveLoad::initSavegame: not implemented!"); + + //delete save; +} + +////////////////////////////////////////////////////////////////////////// +// Private methods +////////////////////////////////////////////////////////////////////////// + +// Get the file name from the savegame ID +Common::String SaveLoad::getSavegameName(GameId id) { + if (id >= 6) + error("SaveLoad::getSavegameName - attempting to use an invalid game id. Valid values: 0 - 5, was %d", id); + + return gameInfo[id].saveFile; +} + +Common::InSaveFile *SaveLoad::openForLoading(GameId id) { + return g_system->getSavefileManager()->openForLoading(getSavegameName(id)); +} + +Common::OutSaveFile *SaveLoad::openForSaving(GameId id) { + return g_system->getSavefileManager()->openForSaving(getSavegameName(id)); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/savegame.h b/engines/lastexpress/game/savegame.h new file mode 100644 index 0000000000..739e6a1798 --- /dev/null +++ b/engines/lastexpress/game/savegame.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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SAVELOAD_H +#define LASTEXPRESS_SAVELOAD_H + +/* + Savegame format + --------------- + + header: 32 bytes + uint32 {4} - signature: 0x12001200 + uint32 {4} - chapter - needs to be [0; 5] + uint32 {4} - time - needs to be >= 32 [1061100; timeMax] + uint32 {4} - ?? needs to be >= 32 + uint32 {4} - ?? needs to be = 1 + uint32 {4} - Brightness (needs to be [0-6]) + uint32 {4} - Volume (needs to be [0-7]) + uint32 {4} - ?? needs to be = 9 + + Game data Format + ----------------- + + uint32 {4} - entity + uint32 {4} - current time + uint32 {4} - time delta (how much a tick is in "real" time) + uint32 {4} - time ticks + uint32 {4} - scene Index max: 2500 + byte {1} - use backup scene + uint32 {4} - backup Scene Index 1 max: 2500 + uint32 {4} - backup Scene Index 2 max: 2500 + uint32 {4} - selected inventory item max: 32 + uint32 {4*100*10} - positions (by car) + uint32 {4*16} - compartments + uint32 {4*16} - compartments ?? + uint32 {4*128} - game progress + byte {512} - game events + byte {7*32} - inventory + byte {5*128} - objects + byte {1262*40} - entities (characters and train entities) + + uint32 {4} - sound queue state + uint32 {4} - ?? + uint32 {4} - number of sound entries + byte {count*68} - sound entries + + byte {16*128} - save point data + uint32 {4} - number of save points (max: 128) + byte {count*16} - save points + + ... more unknown stuff + +*/ + +#include "lastexpress/shared.h" + +#include "common/savefile.h" + +namespace LastExpress { + +class LastExpressEngine; + +class SaveLoad { +public: + enum HeaderType { + kHeaderTypeNone = 0, + kHeaderType1 = 1, + kHeaderType2 = 2, + kHeaderType3 = 3, + kHeaderType4 = 4, + kHeaderType5 = 5 + }; + + struct SavegameMainHeader { + uint32 signature; + uint32 index; + uint32 time; + uint32 field_C; + uint32 field_10; + int32 brightness; + int32 volume; + uint32 field_1C; + }; + + struct SavegameEntryHeader { + uint32 signature; + HeaderType type; + uint32 time; + int field_C; + ChapterIndex chapter; + EventIndex event; + int field_18; + int field_1C; + + SavegameEntryHeader() { + signature = 0; + type = kHeaderTypeNone; + time = 0; + field_C = 0; + chapter = kChapterAll; + event = kEventNone; + field_18 = 0; + field_1C = 0; + } + }; + + SaveLoad(LastExpressEngine *engine); + ~SaveLoad(); + + // Save & Load + bool loadGame(GameId id); + void saveGame(SavegameType type, EntityIndex entity, uint32 value); + + void saveVolumeBrightness(); + + // Init + void initSavegame(GameId id, bool resetHeaders); + static void writeMainHeader(GameId id); + + // Getting information + static bool isSavegamePresent(GameId id); + static bool isSavegameValid(GameId id); + + // Opening save files + static Common::InSaveFile *openForLoading(GameId id); + static Common::OutSaveFile *openForSaving(GameId id); + + // Headers + static bool loadMainHeader(GameId id, SavegameMainHeader* header); + SavegameEntryHeader *getEntry(uint32 index); + void clearEntries(); + + uint32 getLastSavegameTicks() const { return _gameTicksLastSavegame; } + +private: + LastExpressEngine *_engine; + + uint32 _gameTicksLastSavegame; + Common::Array<SavegameEntryHeader *> _gameHeaders; + + static Common::String getSavegameName(GameId id); + + static void loadEntryHeader(Common::InSaveFile *save, SavegameEntryHeader* header); + + static bool validateMainHeader(const SavegameMainHeader &header); + static bool validateEntryHeader(const SavegameEntryHeader &header); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SAVELOAD_H diff --git a/engines/lastexpress/game/savepoint.cpp b/engines/lastexpress/game/savepoint.cpp new file mode 100644 index 0000000000..e7bae494ed --- /dev/null +++ b/engines/lastexpress/game/savepoint.cpp @@ -0,0 +1,296 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/savepoint.h" + +#include "lastexpress/game/entities.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" + + +namespace LastExpress { + +SavePoints::SavePoints(LastExpressEngine *engine) : _engine(engine) { + for (int i = 0; i < 40; i++) + _callbacks[i] = NULL; +} + +SavePoints::~SavePoints() { + // Zero passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Savepoints +////////////////////////////////////////////////////////////////////////// +void SavePoints::push(EntityIndex entity2, EntityIndex entity1, ActionIndex action, uint32 param) { + if (_savepoints.size() >= _savePointsMaxSize) + return; + + SavePoint point; + point.entity1 = entity1; + point.action = action; + point.entity2 = entity2; + point.param.intValue = param; + + _savepoints.push_back(point); +} + +void SavePoints::push(EntityIndex entity2, EntityIndex entity1, ActionIndex action, const char* param) { + if (_savepoints.size() >= _savePointsMaxSize) + return; + + SavePoint point; + point.entity1 = entity1; + point.action = action; + point.entity2 = entity2; + strcpy((char *)&point.param.charValue, param); + + _savepoints.push_back(point); +} + +SavePoint SavePoints::pop() { + SavePoint point = _savepoints.front(); + _savepoints.pop_front(); + return point; +} + + +void SavePoints::pushAll(EntityIndex entity, ActionIndex action, uint32 param) { + for (uint32 index = 1; index < 40; index++) { + if ((EntityIndex)index != entity) + push(entity, (EntityIndex)index, action, param); + } +} + +// Process all savepoints +void SavePoints::process() { + while (_savepoints.size() > 0 && getFlags()->isGameRunning) { + SavePoint savepoint = pop(); + + // If this is a data savepoint, update the entity + // otherwise, execute the callback + if (!updateEntityFromData(savepoint)) { + + // Call requested callback + Entity::Callback *callback = getCallback(savepoint.entity1); + if (callback && callback->isValid()) { + debugC(8, kLastExpressDebugLogic, "Savepoint: entity1=%s, action=%s, entity2=%s", ENTITY_NAME(savepoint.entity1), ACTION_NAME(savepoint.action), ENTITY_NAME(savepoint.entity2)); + (*callback)(savepoint); + } + } + } +} + +void SavePoints::reset() { + _savepoints.clear(); +} + +////////////////////////////////////////////////////////////////////////// +// Data +////////////////////////////////////////////////////////////////////////// +void SavePoints::addData(EntityIndex entity, ActionIndex action, uint32 param) { + if (_data.size() >= _savePointsMaxSize) + return; + + SavePointData data; + data.entity1 = entity; + data.action = action; + data.param = param; + + _data.push_back(data); +} + +////////////////////////////////////////////////////////////////////////// +// Callbacks +////////////////////////////////////////////////////////////////////////// +void SavePoints::setCallback(EntityIndex index, Entity::Callback* callback) { + if (index >= 40) + error("SavePoints::setCallback - attempting to use an invalid entity index. Valid values 0-39, was %d", index); + + if (!callback || !callback->isValid()) + error("SavePoints::setCallback - attempting to set an invalid callback for entity %s", ENTITY_NAME(index)); + + _callbacks[index] = callback; +} + +Entity::Callback *SavePoints::getCallback(EntityIndex index) const { + if (index >= 40) + error("SavePoints::getCallback - attempting to use an invalid entity index. Valid values 0-39, was %d", index); + + return _callbacks[index]; +} + +void SavePoints::call(EntityIndex entity2, EntityIndex entity1, ActionIndex action, uint32 param) const { + SavePoint point; + point.entity1 = entity1; + point.action = action; + point.entity2 = entity2; + point.param.intValue = param; + + Entity::Callback *callback = getCallback(entity1); + if (callback != NULL && callback->isValid()) { + debugC(8, kLastExpressDebugLogic, "Savepoint: entity1=%s, action=%s, entity2=%s, param=%d", ENTITY_NAME(entity1), ACTION_NAME(action), ENTITY_NAME(entity2), param); + (*callback)(point); + } +} + +void SavePoints::call(EntityIndex entity2, EntityIndex entity1, ActionIndex action, const char *param) const { + SavePoint point; + point.entity1 = entity1; + point.action = action; + point.entity2 = entity2; + strcpy((char *)&point.param.charValue, param); + + Entity::Callback *callback = getCallback(entity1); + if (callback != NULL && callback->isValid()) { + debugC(8, kLastExpressDebugLogic, "Savepoint: entity1=%s, action=%s, entity2=%s, param=%s", ENTITY_NAME(entity1), ACTION_NAME(action), ENTITY_NAME(entity2), param); + (*callback)(point); + } +} + +void SavePoints::callAndProcess() { + SavePoint savepoint; // empty parameters + + // We ignore the kEntityPlayer callback in the list + EntityIndex index = kEntityAnna; + + // Call all callbacks with empty parameters + bool isRunning = getFlags()->isGameRunning; + while (isRunning) { + + Entity::Callback *callback = getCallback(index); + if (callback != NULL && callback->isValid()) { + (*callback)(savepoint); + isRunning = getFlags()->isGameRunning; + } + + index = (EntityIndex)(index + 1); + + // Process all savepoints when done + if (index >= 40) { + if (isRunning) + process(); + + return; + } + } +} + +////////////////////////////////////////////////////////////////////////// +// Misc +////////////////////////////////////////////////////////////////////////// +bool SavePoints::updateEntityFromData(const SavePoint &savepoint) { + for (int i = 0; i < (int)_data.size(); i++) { + + // Not a data savepoint! + if (!_data[i].entity1) + return false; + + // Found our data! + if (_data[i].entity1 == savepoint.entity1 && _data[i].action == savepoint.action) { + debugC(8, kLastExpressDebugLogic, "Update entity from data: entity1=%s, action=%s, param=%d", ENTITY_NAME(_data[i].entity1), ACTION_NAME(_data[i].action), _data[i].param); + + // the SavePoint param value is the index of the entity call parameter to update + getEntities()->get(_data[i].entity1)->getParamData()->updateParameters(_data[i].param); + + return true; + } + } + + return false; +} + +////////////////////////////////////////////////////////////////////////// +// Serializable +////////////////////////////////////////////////////////////////////////// +void SavePoints::saveLoadWithSerializer(Common::Serializer &s) { + + // Serialize savepoint data + uint32 dataSize = (s.isLoading() ? _savePointsMaxSize : _data.size()); + for (uint i = 0; i < dataSize; i++) { + if (s.isLoading()) { + SavePointData data; + _data.push_back(data); + } + + s.syncAsUint32LE(_data[i].entity1); + s.syncAsUint32LE(_data[i].action); + s.syncAsUint32LE(_data[i].entity2); + s.syncAsUint32LE(_data[i].param); + } + + // Skip uninitialized data if any + s.skip((_savePointsMaxSize - dataSize) * 16); + + // Number of savepoints + uint32 count = _savepoints.size(); + s.syncAsUint32LE(count); + + // Savepoints + if (s.isLoading()) { + for (uint i= 0; i < count; i++) { + SavePoint point; + s.syncAsUint32LE(point.entity1); + s.syncAsUint32LE(point.action); + s.syncAsUint32LE(point.entity2); + s.syncAsUint32LE(point.param.intValue); + + _savepoints.push_back(point); + + if (_savepoints.size() >= _savePointsMaxSize) + break; + } + } else { + for (Common::List<SavePoint>::iterator it = _savepoints.begin(); it != _savepoints.end(); ++it) { + s.syncAsUint32LE((*it).entity1); + s.syncAsUint32LE((*it).action); + s.syncAsUint32LE((*it).entity2); + s.syncAsUint32LE((*it).param.intValue); + } + } +} + +////////////////////////////////////////////////////////////////////////// +// toString +////////////////////////////////////////////////////////////////////////// +Common::String SavePoints::toString() { + Common::String ret = ""; + + ret += "Savepoint Data\n"; + for (uint i = 0; i < _data.size(); i++) + ret += _data[i].toString() + "\n"; + + ret += "\nSavepoints\n"; + for (Common::List<SavePoint>::iterator it = _savepoints.begin(); it != _savepoints.end(); ++it) + ret += (*it).toString() + "\n"; + + return ret; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/savepoint.h b/engines/lastexpress/game/savepoint.h new file mode 100644 index 0000000000..da1c9b8ed1 --- /dev/null +++ b/engines/lastexpress/game/savepoint.h @@ -0,0 +1,149 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SAVEPOINT_H +#define LASTEXPRESS_SAVEPOINT_H + +#include "lastexpress/entities/entity.h" + +#include "lastexpress/helpers.h" + +#include "common/array.h" +#include "common/list.h" +#include "common/serializer.h" + +/* + Savepoint format + ---------------- + + Save point: max: 127 - FIFO list (ie. goes back and overwrites first save point when full) + uint32 {4} - Entity 1 + uint32 {4} - Action + uint32 {4} - Entity 2 + uint32 {4} - Parameter + + Save point Data + uint32 {4} - Entity 1 + uint32 {4} - Action + uint32 {4} - Entity 2 + uint32 {4} - function pointer to ?? + +*/ + +namespace LastExpress { + +class LastExpressEngine; + +struct SavePoint { + EntityIndex entity1; + ActionIndex action; + EntityIndex entity2; + union { + uint32 intValue; + char charValue[5]; + } param; + + SavePoint() { + entity1 = kEntityPlayer; + action = kActionNone; + entity2 = kEntityPlayer; + param.intValue = 0; + } + + Common::String toString() { + return Common::String::printf("{ %s - %d - %s - %s }", ENTITY_NAME(entity1), action, ENTITY_NAME(entity2), param.charValue); + } +}; + +class SavePoints : Common::Serializable { +private: + typedef Common::Functor1<const SavePoint&, void> Callback; + +public: + + struct SavePointData { + EntityIndex entity1; + ActionIndex action; + EntityIndex entity2; + uint32 param; + + SavePointData() { + entity1 = kEntityPlayer; + action = kActionNone; + entity2 = kEntityPlayer; + param = 0; + } + + Common::String toString() { + return Common::String::printf(" { %s - %d - %s - %d }", ENTITY_NAME(entity1), action, ENTITY_NAME(entity2), param); + } + }; + + SavePoints(LastExpressEngine *engine); + ~SavePoints(); + + // Savepoints + void push(EntityIndex entity2, EntityIndex entity1, ActionIndex action, uint32 param = 0); + void push(EntityIndex entity2, EntityIndex entity1, ActionIndex action, const char* param); + void pushAll(EntityIndex entity, ActionIndex action, uint32 param = 0); + void process(); + void reset(); + + // Data + void addData(EntityIndex entity, ActionIndex action, uint32 param); + + // Callbacks + void setCallback(EntityIndex index, Entity::Callback* callback); + Callback *getCallback(EntityIndex entity) const; + void call(EntityIndex entity2, EntityIndex entity1, ActionIndex action, uint32 param = 0) const; + void call(EntityIndex entity2, EntityIndex entity1, ActionIndex action, const char *param) const; + void callAndProcess(); + + // Serializable + void saveLoadWithSerializer(Common::Serializer &s); + + /** + * Convert this object into a string representation. + * + * @return A string representation of this object. + */ + Common::String toString(); + +private: + static const uint32 _savePointsMaxSize = 128; + + LastExpressEngine *_engine; + + Common::List<SavePoint> _savepoints; ///< could be a queue, but we need to be able to iterate on the items + Common::Array<SavePointData> _data; + Callback* _callbacks[40]; + + SavePoint pop(); + bool updateEntityFromData(const SavePoint &point); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SAVEPOINT_H diff --git a/engines/lastexpress/game/scenes.cpp b/engines/lastexpress/game/scenes.cpp new file mode 100644 index 0000000000..2187d331b5 --- /dev/null +++ b/engines/lastexpress/game/scenes.cpp @@ -0,0 +1,1195 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/scenes.h" + +#include "lastexpress/data/scene.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/beetle.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/sound.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +SceneManager::SceneManager(LastExpressEngine *engine) : _engine(engine), + _flagNoEntity(false), _flagDrawEntities(false), _flagDrawSequences(false), _flagCoordinates(false), + _coords(0, 0, 480, 640), _clockHours(NULL), _clockMinutes(NULL) { + _sceneLoader = new SceneLoader(); +} + +SceneManager::~SceneManager() { + delete _sceneLoader; + + // Clear frames + for (Common::List<SequenceFrame *>::iterator door = _doors.begin(); door != _doors.end(); ++door) + SAFE_DELETE(*door); + + _doors.clear(); + + SAFE_DELETE(_clockHours); + SAFE_DELETE(_clockMinutes); + + // Zero-out passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Scene cache +////////////////////////////////////////////////////////////////////////// +void SceneManager::loadSceneDataFile(ArchiveIndex archive) { + // Demo only has CD2TRAIN.DAT file + if (_engine->isDemo()) + archive = kArchiveCd2; + + switch(archive) { + case kArchiveCd1: + case kArchiveCd2: + case kArchiveCd3: + if (!_sceneLoader->load(getArchive(Common::String::printf("CD%iTRAIN.DAT", archive)))) + error("SceneManager::loadSceneDataFile: cannot load data file CD%iTRAIN.DAT", archive); + break; + + default: + case kArchiveAll: + error("SceneManager::loadSceneDataFile: Invalid archive index (must be [1-3], was %d", archive); + break; + } +} + +////////////////////////////////////////////////////////////////////////// +// Scene loading +////////////////////////////////////////////////////////////////////////// +void SceneManager::loadScene(SceneIndex index) { + getFlags()->flag_0 = false; + getFlags()->flag_4 = true; + + if (getState()->sceneUseBackup) { + Scene *scene = getScenes()->get(index); + + if (scene->param3 != 255) { + getState()->sceneUseBackup = false; + getState()->sceneBackup2 = kSceneNone; + } + } + + // Save shouldRedraw state and redraw if necessary + bool shouldRedraw = getFlags()->shouldRedraw; + if (shouldRedraw) { + shouldRedraw = false; + // TODO check whether we need to do that here + askForRedraw(); + //redrawScreen(); + } + + // Set the scene + setScene(index); + + // TODO Events method call (might be a low level graphic that we don't need) + + if (getFlags()->isGameRunning && getFlags()->shouldDrawEggOrHourGlass) + getInventory()->drawEgg(); + + getFlags()->shouldRedraw = shouldRedraw; + + getLogic()->updateCursor(); +} + +void SceneManager::loadSceneFromObject(ObjectIndex object, bool alternate) { + switch (object) { + default: + break; + + case kObjectCompartment1: + case kObjectCompartment2: + case kObjectCompartment3: + case kObjectCompartment4: + case kObjectCompartment5: + case kObjectCompartment6: + case kObjectCompartment7: + if (alternate) + loadSceneFromPosition(kCarGreenSleeping, (Position)(17 - (object - 1) * 2)); + else + loadSceneFromPosition(kCarGreenSleeping, (Position)(38 - (object - 1) * 2)); + break; + + case kObjectCompartmentA: + case kObjectCompartmentB: + case kObjectCompartmentC: + case kObjectCompartmentD: + case kObjectCompartmentE: + case kObjectCompartmentF: + case kObjectCompartmentG: + if (alternate) + loadSceneFromPosition(kCarGreenSleeping, (Position)(17 - (object - 32) * 2)); + else + loadSceneFromPosition(kCarRedSleeping, (Position)(38 - (object - 32) * 2)); + break; + + case kObjectCompartment8: + case kObjectCompartmentH: + loadSceneFromPosition(object == kObjectCompartment8 ? kCarGreenSleeping : kCarRedSleeping, alternate ? 3 : 25); + break; + } +} + +void SceneManager::loadSceneFromItem(InventoryItem item) { + if (item >= kPortraitOriginal) + return; + + // Get the scene index from the item + SceneIndex index = getInventory()->get(item)->scene; + if (!index) + return; + + if (!getState()->sceneUseBackup) { + getState()->sceneUseBackup = true; + getState()->sceneBackup = getState()->scene; + } + + loadScene(index); +} + +void SceneManager::loadSceneFromPosition(CarIndex car, Position position, int param3) { + loadScene(getSceneIndexFromPosition(car, position, param3)); +} + +void SceneManager::loadSceneFromItemPosition(InventoryItem item) { + if (item >= kPortraitOriginal) + return; + + // Check item location + Inventory::InventoryEntry *entry = getInventory()->get(item); + if (!entry->location) + return; + + // Reset location + entry->location = kObjectLocationNone; + + if (item != kItem3 && item != kItem5 && item != kItem7) + return; + + // Set field value + CarIndex car = kCarRestaurant; + if (item == kItem5) car = kCarRedSleeping; + if (item == kItem7) car = kCarGreenSleeping; + + if (!getEntities()->isInsideTrainCar(kEntityPlayer, car)) + return; + + if (getFlags()->flag_0) + return; + + // Get current scene position + Scene *scene = getScenes()->get(getState()->scene); + Position position = scene->position; + + if (getState()->sceneUseBackup) { + Scene *sceneBackup = getScenes()->get(getState()->sceneBackup); + position = sceneBackup->position; + } + + // Checks are different for each item + if ((item == kItem3 && position == 56) + || (item == kItem5 && (position >= 23 && position <= 32)) + || (item == kItem7 && (position == 1 || (position >= 22 && position <= 33)))) { + if (getState()->sceneUseBackup) + getState()->sceneBackup = getSceneIndexFromPosition(car, position); + else + loadSceneFromPosition(car, position); + } +} + +////////////////////////////////////////////////////////////////////////// +// Scene drawing & processing +////////////////////////////////////////////////////////////////////////// +void SceneManager::setScene(SceneIndex index) { + _flagNoEntity = false; + + if (_flagDrawEntities) { + // TODO Setup screen size (0, 80)x(480x480) (is it necessary for our animations?) + drawScene(index); + _flagNoEntity = true; + } else { + _flagDrawEntities = true; + drawScene(index); + _flagDrawEntities = false; + } +} + +void SceneManager::drawScene(SceneIndex index) { + + ////////////////////////////////////////////////////////////////////////// + // Preprocess + preProcessScene(&index); + + ////////////////////////////////////////////////////////////////////////// + // Draw background + debugC(9, kLastExpressDebugScenes, "== Drawing scene: %d ==", index); + + // Update scene + _engine->getGraphicsManager()->draw(get(index), GraphicsManager::kBackgroundC, true); + getState()->scene = index; + + ////////////////////////////////////////////////////////////////////////// + // Update entities + Scene *scene = (getState()->sceneUseBackup ? get(getState()->sceneBackup) : get(index)); + + getEntityData(kEntityPlayer)->entityPosition = scene->entityPosition; + getEntityData(kEntityPlayer)->car = scene->car; + + getFlags()->flag_3 = true; + + if (getFlags()->isGameRunning) { + getSavePoints()->pushAll(kEntityPlayer, kActionDrawScene); + getSavePoints()->process(); + + if (_flagNoEntity) + return; + + getEntities()->updateFields(); + getEntities()->updateSequences(); + getEntities()->updateCallbacks(); + } + + ////////////////////////////////////////////////////////////////////////// + // Show the scene + askForRedraw(); + redrawScreen(); + + //////////////////////////////////////////////////////////// + // Post process scene + postProcessScene(); +} + +void SceneManager::processScene() { + if (!getState()->sceneUseBackup) { + loadScene(getState()->scene); + return; + } + + getState()->sceneUseBackup = false; + + // Select item if needed + InventoryItem item = getInventory()->getFirstExaminableItem(); + if (item && getInventory()->getSelectedItem() == item) + getInventory()->selectItem(item); + + Scene *backup = getScenes()->get(getState()->sceneBackup); + + if (getEntities()->getPosition(backup->car, backup->position)) + loadScene(processIndex(getState()->sceneBackup)); + else + loadScene(getState()->sceneBackup); +} + +LastExpress::SceneIndex SceneManager::processIndex(SceneIndex index) { + Scene *scene = get(index); + CarIndex car = scene->car; + + switch (car) { + default: + break; + + case kCarRedSleeping: + if (checkPosition(index, kCheckPositionLookingAtDoors)) { + Position position = (Position)(scene->position + (checkPosition(kSceneNone, kCheckPositionLookingUp) ? -1 : 1)); + + if (position == 4) + position = 3; + + if (position == 24) + position = 25; + + if (getEntities()->getPosition(car, position)) + return index; + else + return getSceneIndexFromPosition(car, position); + } else { + switch (scene->position) { + default: + break; + + case 41: + case 51: + if (!getEntities()->getPosition(car, 39)) + return getSceneIndexFromPosition(car, 39); + // Fallback to next case + + case 42: + case 52: + if (!getEntities()->getPosition(car, 14)) + return getSceneIndexFromPosition(car, 14); + // Fallback to next case + + case 43: + case 53: + if (!getEntities()->getPosition(car, 35)) + return getSceneIndexFromPosition(car, 35); + // Fallback to next case + + case 44: + case 54: + if (!getEntities()->getPosition(car, 10)) + return getSceneIndexFromPosition(car, 10); + // Fallback to next case + + case 45: + case 55: + if (!getEntities()->getPosition(car, 32)) + return getSceneIndexFromPosition(car, 32); + // Fallback to next case + + case 46: + case 56: + if (!getEntities()->getPosition(car, 7)) + return getSceneIndexFromPosition(car, 7); + // Fallback to next case + + case 47: + case 57: + if (!getEntities()->getPosition(car, 27)) + return getSceneIndexFromPosition(car, 27); + // Fallback to next case + + case 48: + case 58: + if (!getEntities()->getPosition(car, 2)) + return getSceneIndexFromPosition(car, 2); + break; + } + } + break; + + case kCarRestaurant: + switch (scene->position) { + default: + break; + + case 52: + case 53: + case 54: + if (!getEntities()->getPosition(car, 51)) + return getSceneIndexFromPosition(car, 51); + // Fallback to next case + + case 50: + case 56: + case 57: + case 58: + if (!getEntities()->getPosition(car, 55)) + return getSceneIndexFromPosition(car, 55); + // Fallback to next case + + case 59: + if (!getEntities()->getPosition(car, 60)) + return getSceneIndexFromPosition(car, 60); + // Fallback to next case + + case 60: + if (!getEntities()->getPosition(car, 59)) + return getSceneIndexFromPosition(car, 59); + // Fallback to next case + + case 62: + case 63: + case 64: + if (!getEntities()->getPosition(car, 61)) + return getSceneIndexFromPosition(car, 61); + // Fallback to next case + + case 66: + case 67: + case 68: + if (!getEntities()->getPosition(car, 65)) + return getSceneIndexFromPosition(car, 65); + // Fallback to next case + + case 69: + case 71: + if (!getEntities()->getPosition(car, 70)) + return getSceneIndexFromPosition(car, 70); + break; + } + break; + } + + return index; +} + +////////////////////////////////////////////////////////////////////////// +// Checks +////////////////////////////////////////////////////////////////////////// +bool SceneManager::checkPosition(SceneIndex index, CheckPositionType type) const { + Scene *scene = getScenes()->get((index ? index : getState()->scene)); + + CarIndex car = (CarIndex)scene->car; + Position position = scene->position; + + bool isInSleepingCar = (car == kCarGreenSleeping || car == kCarRedSleeping); + + switch (type) { + default: + error("SceneManager::checkPosition: Invalid position type: %d", type); + + case kCheckPositionLookingUp: + return isInSleepingCar && (position >= 1 && position <= 19); + + case kCheckPositionLookingDown: + return isInSleepingCar && (position >= 21 && position <= 40); + + case kCheckPositionLookingAtDoors: + return isInSleepingCar && ((position >= 2 && position <= 17) || (position >= 23 && position <= 39)); + + case kCheckPositionLookingAtClock: + return car == kCarRestaurant && position == 81; + } +} + +bool SceneManager::checkCurrentPosition(bool doCheckOtherCars) const { + Scene *scene = getScenes()->get(getState()->scene); + + Position position = scene->position; + CarIndex car = (CarIndex)scene->car; + + if (!doCheckOtherCars) + return (car == kCarGreenSleeping || car == kCarRedSleeping) + && ((position >= 41 && position <= 48) || (position >= 51 && position <= 58)); + + if (position == 99) + return true; + + switch (car){ + default: + break; + + case kCarGreenSleeping: + case kCarRedSleeping: + case kCarLocomotive: + if ((position >= 1 && position <= 18) || (position >= 22 && position <= 40)) + return true; + break; + + case kCarRestaurant: + if (position >= 73 && position <= 80) + return true; + + if (position == 10 || position == 11) + return true; + + break; + + case kCarBaggage: + switch (position) { + default: + break; + + case 10: + case 11: + case 80: + case 81: + case 82: + case 83: + case 84: + case 90: + case 91: + return true; + } + break; + + case kCarCoalTender: + if (position == 2 || position == 10 || position == 11) + return true; + break; + } + + return false; +} + +////////////////////////////////////////////////////////////////////////// +// Train +////////////////////////////////////////////////////////////////////////// +void SceneManager::updateDoorsAndClock() { + // Clear all sequences from the list + for (Common::List<SequenceFrame *>::iterator door = _doors.begin(); door != _doors.end(); ++door) { + removeFromQueue(*door); + setCoordinates(*door); + SAFE_DELETE(*door); + } + + // Cleanup doors sequences + _doors.clear(); + + if (_clockHours) { + removeFromQueue(_clockHours); + setCoordinates(_clockHours); + SAFE_DELETE(_clockHours); + } + + if (_clockMinutes) { + removeFromQueue(_clockMinutes); + setCoordinates(_clockMinutes); + SAFE_DELETE(_clockMinutes); + } + + // Queue doors sequences for display + if (checkPosition(kSceneNone, kCheckPositionLookingAtDoors)) { + + ObjectIndex firstIndex = kObjectNone; + + // Init objectIndex (or exit if not in one of the two compartment cars + if (getEntityData(kEntityPlayer)->car == kCarGreenSleeping) + firstIndex = kObjectCompartment1; + else if (getEntityData(kEntityPlayer)->car == kCarRedSleeping) + firstIndex = kObjectCompartmentA; + else + return; + + // Iterate over each door + for (ObjectIndex index = firstIndex; index < (ObjectIndex)(firstIndex + 8); index = (ObjectIndex)(index + 1)) { + + // Doors is not open, nothing to do + if (getObjects()->get(index).location != kObjectLocation2) + continue; + + // Load door sequence + Scene *scene = getScenes()->get(getState()->scene); + Common::String name = Common::String::printf("633X%c-%02d.seq", (index - firstIndex) + 65, scene->position); + Sequence *sequence = loadSequence1(name, 255); + + // If the sequence doesn't exists, skip + if (!sequence || !sequence->isLoaded()) + continue; + + // Adjust frame data and store in frame list + SequenceFrame *frame = new SequenceFrame(sequence, 0, true); + frame->getInfo()->location = (checkPosition(kSceneNone, kCheckPositionLookingUp) ? (firstIndex - index) - 1 : (index - firstIndex) - 8); + + _doors.push_back(frame); + + // Add frame to list + addToQueue(frame); + } + } + + // Queue clock sequences for display + if (checkPosition(kSceneNone, kCheckPositionLookingAtClock)) { + // Only used in scene 349 to show the hands on the clock + + Sequence *sequenceHours = loadSequence1("SCLKH-81.seq", 255); + Sequence *sequenceMinutes = loadSequence1("SCLKM-81.seq", 255); + + // Compute hours and minutes indexes + uint16 hoursIndex = getState()->time % 1296000 % 54000 / 900; + + uint hours = (getState()->time % 1296000) / 54000; + if (hours >= 12) + hours -= 12; + + uint16 minutesIndex = (uint16)(5 * hours + hoursIndex / 12); + + // Adjust z-order and store sequences to list + _clockHours = new SequenceFrame(sequenceHours, hoursIndex, true); + _clockHours->getInfo()->location = 65534; + + _clockMinutes = new SequenceFrame(sequenceMinutes, minutesIndex, true); + _clockMinutes->getInfo()->location = 65535; + + addToQueue(_clockHours); + addToQueue(_clockMinutes); + } +} + +void SceneManager::resetDoorsAndClock() { + for (Common::List<SequenceFrame *>::iterator door = _doors.begin(); door != _doors.end(); ++door) + SAFE_DELETE(*door); + + _doors.clear(); + + SAFE_DELETE(_clockHours); + SAFE_DELETE(_clockMinutes); + + // Remove the beetle sequences too if needed + getBeetle()->unload(); +} + +////////////////////////////////////////////////////////////////////////// +// Sequence list +////////////////////////////////////////////////////////////////////////// +void SceneManager::drawFrames(bool refreshScreen) { + if (!_flagDrawSequences) + return; + + // TODO handle flag coordinates + + clearBg(GraphicsManager::kBackgroundOverlay); + + for (Common::List<SequenceFrame *>::iterator i = _queue.begin(); i != _queue.end(); ++i) + _engine->getGraphicsManager()->draw(*i, GraphicsManager::kBackgroundOverlay); + + if (refreshScreen) { + askForRedraw(); + //redrawScreen(); + + _flagDrawSequences = false; + } +} + +void SceneManager::addToQueue(SequenceFrame * const frame) { + if (!frame) + return; + + // First check that the frame is not already in the queue + for (Common::List<SequenceFrame *>::iterator i = _queue.begin(); i != _queue.end(); ++i) { + if (frame->equal(*i)) + return; + } + + debugC(8, kLastExpressDebugGraphics, "Adding frame: %s / %d", frame->getName().c_str(), frame->getFrame()); + + // Set flag + _flagDrawSequences = true; + + // Queue empty: just insert the frame + if (_queue.empty()) { + _queue.push_back(frame); + return; + } + + // Frame is closer: insert in first place + if (frame->getInfo()->location > _queue.front()->getInfo()->location) { + _queue.push_front(frame); + return; + } + + // Insert the frame in the queue based on location + for (Common::List<SequenceFrame *>::iterator i = _queue.begin(); i != _queue.end(); ++i) { + if (frame->getInfo()->location > (*i)->getInfo()->location) { + _queue.insert(i, frame); + return; + } + } + + // We are the last frame in location order, insert at the back of the queue + _queue.push_back(frame); +} + +void SceneManager::removeFromQueue(SequenceFrame *frame) { + if (!frame) + return; + + debugC(8, kLastExpressDebugGraphics, "Removing frame: %s / %d", frame->getName().c_str(), frame->getFrame()); + + // Check that the frame is in the queue and remove it + for (Common::List<SequenceFrame *>::iterator i = _queue.begin(); i != _queue.end(); ++i) { + if (frame->equal(*i)) { + _queue.erase(i); + _flagDrawSequences = true; + break; + } + } +} + +void SceneManager::removeAndRedraw(SequenceFrame **frame, bool doRedraw) { + if (!frame) + return; + + removeFromQueue(*frame); + + if (doRedraw) + drawFrames(true); + + SAFE_DELETE(*frame); +} + +void SceneManager::resetQueue() { + _flagDrawSequences = true; + + // The original engine only deletes decompressed data, not the "sequences" since they are just pointers to a memory pool + _queue.clear(); +} + +void SceneManager::setCoordinates(SequenceFrame *frame) { + + if (!frame || frame->getInfo()->subType == 3) + return; + + _flagCoordinates = true; + + if (_coords.right > (int)frame->getInfo()->xPos1) + _coords.right = (int16)frame->getInfo()->xPos1; + + if (_coords.bottom > (int)frame->getInfo()->yPos1) + _coords.bottom = (int16)frame->getInfo()->yPos1; + + if (_coords.left < (int)frame->getInfo()->xPos2) + _coords.left = (int16)frame->getInfo()->xPos2; + + if (_coords.top < (int)frame->getInfo()->yPos2) + _coords.top = (int16)frame->getInfo()->yPos2; +} + +void SceneManager::resetCoordinates() { + _coords.top = 0; + _coords.left = 0; + _coords.bottom = 480; + _coords.right = 640; + + _flagCoordinates = false; +} + +////////////////////////////////////////////////////////////////////////// +// Helpers +////////////////////////////////////////////////////////////////////////// +SceneIndex SceneManager::getSceneIndexFromPosition(CarIndex car, Position position, int param3) { + // Probably can't happen (can we be called during cd-swap?) + if (_sceneLoader->count() <= 1) + return getState()->scene; + + SceneIndex index = kSceneMenu; + + Scene *firstScene = getScenes()->get(index); + + while (firstScene->car != car + || firstScene->position != position + || ((param3 != -1 || firstScene->param3) && firstScene->param3 != param3 && firstScene->type != Scene::kTypeItem3)) { + + // Increment index and look at the next scene + index = (SceneIndex)(index + 1); + + if (index >= _sceneLoader->count()) + return getState()->scene; + + // Load the next scene + firstScene = getScenes()->get(index); + } + + // Process index if necessary + Scene *scene = getScenes()->get(index); + if (getEntities()->getPosition(scene->car, scene->position)) + return processIndex(index); + + return index; +} + +////////////////////////////////////////////////////////////////////////// +// Scene processing +////////////////////////////////////////////////////////////////////////// + +// Process hotspots +// - if it returns kSceneInvalid, the hotspot scene has not been modified +// - if it returns kSceneNone, it has been modified +// +// Note: we use the original hotspot scene to pre-process again +#define PROCESS_HOTSPOT_SCENE(hotspot, index) { \ + SceneIndex processedScene = getAction()->processHotspot(*hotspot); \ + SceneIndex testScene = (processedScene == kSceneInvalid) ? (hotspot)->scene : processedScene; \ + if (testScene) { \ + *index = (hotspot)->scene; \ + preProcessScene(index); \ + } \ +} + +void SceneManager::preProcessScene(SceneIndex *index) { + + // Check index validity + if (*index == 0 || *index > 2500) + *index = kSceneMenu; + + Scene *scene = getScenes()->get(*index); + + switch (scene->type) { + case Scene::kTypeObject: { + ObjectIndex object = (ObjectIndex)scene->param1; + + if (object >= kObjectMax) + break; + + if (getObjects()->get(object).location == kObjectLocationNone) + break; + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (getObjects()->get(object).location != (*it)->location) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + break; + } + break; + } + + case Scene::kTypeItem: { + InventoryItem item = (InventoryItem)scene->param1; + + if (item >= kPortraitOriginal) + break; + + if (getInventory()->get(item)->location == kObjectLocationNone) + break; + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (getInventory()->get(item)->location != (*it)->location) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + break; + } + break; + } + + case Scene::kTypeItem2: { + InventoryItem item1 = (InventoryItem)scene->param1; + InventoryItem item2 = (InventoryItem)scene->param2; + + if (item1 >= kPortraitOriginal || item2 >= kPortraitOriginal) + break; + + int location = kObjectLocationNone; + + if (getInventory()->get(item1)->location != kObjectLocationNone) + location = kObjectLocation1; + + if (getInventory()->get(item2)->location != kObjectLocationNone) + location |= kObjectLocation2; + + if (!location) + break; + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (location != (*it)->location) + continue; + + if (getInventory()->get(item1)->location != (*it)->param1) + continue; + + if (getInventory()->get(item2)->location != (*it)->param2) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + break; + } + break; + } + + case Scene::kTypeObjectItem: { + ObjectIndex object = (ObjectIndex)scene->param1; + InventoryItem item = (InventoryItem)scene->param2; + + if (object >= kObjectMax) + break; + + if (item >= kPortraitOriginal) + break; + + int location = kObjectLocationNone; + + if (getObjects()->get(object).location == kObjectLocation2) + location = kObjectLocation1; + + if (getInventory()->get(item)->location != kObjectLocationNone) + location |= kObjectLocation2; + + if (!location) + break; + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (location != (*it)->location) + continue; + + if (getObjects()->get(object).location != (*it)->param1) + continue; + + if (getInventory()->get(item)->location != (*it)->param2) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + break; + } + break; + } + + case Scene::kTypeItem3: { + InventoryItem item1 = (InventoryItem)scene->param1; + InventoryItem item2 = (InventoryItem)scene->param2; + InventoryItem item3 = (InventoryItem)scene->param3; + + if (item1 >= kPortraitOriginal || item2 >= kPortraitOriginal || item3 >= kPortraitOriginal) + break; + + int location = kObjectLocationNone; + + if (getInventory()->get(item1)->location != kObjectLocationNone) + location = kObjectLocation1; + + if (getInventory()->get(item2)->location != kObjectLocationNone) + location |= kObjectLocation2; + + if (getInventory()->get(item3)->location != kObjectLocationNone) + location |= kObjectLocation4; + + if (!location) + break; + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (location != (*it)->location) + continue; + + if (getInventory()->get(item1)->location != (*it)->param1) + continue; + + if (getInventory()->get(item2)->location != (*it)->param2) + continue; + + if (getInventory()->get(item3)->location != (*it)->param3) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + break; + } + break; + } + + case Scene::kTypeObjectLocation2: { + ObjectIndex object = (ObjectIndex)scene->param1; + + if (object >= kObjectMax) + break; + + bool found = false; + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (getObjects()->get(object).location2 != (*it)->location) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + found = true; + break; + } + + // If we haven't found a proper hotspot, use the first hotspot from the current scene + if (!found) { + Scene *sceneHotspot = getScenes()->get(*index); + SceneHotspot *hotspot = sceneHotspot->getHotspot(); + + PROCESS_HOTSPOT_SCENE(hotspot, index); + } + break; + } + + case Scene::kTypeCompartments: + case Scene::kTypeCompartmentsItem: + if (scene->param1 >= 16) + break; + + if (getEntities()->getCompartments(scene->param1) || getEntities()->getCompartments1(scene->param1)) { + + Scene *currentScene = getScenes()->get(getState()->scene); + + if ((checkPosition(getState()->scene, kCheckPositionLookingUp) && checkPosition(*index, kCheckPositionLookingUp) && currentScene->entityPosition < scene->entityPosition) + || (checkPosition(getState()->scene, kCheckPositionLookingDown) && checkPosition(*index, kCheckPositionLookingDown) && currentScene->entityPosition > scene->entityPosition)) { + + if (State::getPowerOfTwo((uint32)getEntities()->getCompartments(scene->param1)) != 30 + && State::getPowerOfTwo((uint32)getEntities()->getCompartments1(scene->param1)) != 30 ) + getSound()->playSound(kEntityPlayer, "CAT1126A"); + + *index = scene->getHotspot()->scene; + } else { + *index = scene->getHotspot(1)->scene; + } + + preProcessScene(index); + } else { + // Stop processing here for kTypeCompartments + if (scene->type == Scene::kTypeCompartments) + break; + + InventoryItem item = (InventoryItem)scene->param2; + if (item >= kPortraitOriginal) + break; + + if (getInventory()->get(item)->location == kObjectLocationNone) + break; + + for (Common::Array<SceneHotspot *>::iterator it = scene->getHotspots()->begin(); it != scene->getHotspots()->end(); ++it) { + if (getInventory()->get(item)->location != (*it)->location) + continue; + + PROCESS_HOTSPOT_SCENE(*it, index); + break; + } + } + break; + + default: + break; + } + + // Sound processing + Scene *newScene = getScenes()->get(*index); + if (getSound()->isBuffered(kEntityTables4)) { + if (newScene->type != Scene::kTypeReadText || newScene->param1) + getSound()->processEntry(kEntityTables4); + } + + // Cleanup beetle sequences + if (getBeetle()->isLoaded()) { + if (newScene->type != Scene::kTypeLoadBeetleSequences) + getBeetle()->unload(); + } +} + +void SceneManager::postProcessScene() { + + Scene *scene = getScenes()->get(getState()->scene); + + switch (scene->type) { + case Scene::kTypeList: { + + // Adjust time + getState()->time += (scene->param1 + 10) * getState()->timeDelta; + getState()->timeTicks += (scene->param1 + 10); + + // Wait for a number of frames unless right mouse is clicked + uint32 nextFrameCount = getFrameCount() + 4 * scene->param1; + if (!getFlags()->mouseRightClick) { + while (nextFrameCount > getFrameCount()) { + _engine->pollEvents(); + + if (getFlags()->mouseRightClick) + break; + + getSound()->updateQueue(); + getSound()->updateSubtitles(); + } + } + + // Process hotspots and load scenes in the list + SceneHotspot *hotspot = scene->getHotspot(); + SceneIndex processedScene = getAction()->processHotspot(*hotspot); + SceneIndex testScene = (processedScene == kSceneInvalid) ? hotspot->scene : processedScene; + + if (getFlags()->mouseRightClick) { + + while (getScenes()->get(testScene)->type == Scene::kTypeList) { + hotspot = getScenes()->get(testScene)->getHotspot(); + processedScene = getAction()->processHotspot(*hotspot); + testScene = (processedScene == kSceneInvalid) ? hotspot->scene : processedScene; + } + } + + // If several entities are there, choose one to sound "Excuse me" + EntityPosition entityPosition = getEntityData(kEntityPlayer)->entityPosition; + if (getEntityData(kEntityPlayer)->car == kCar9 && (entityPosition == kPosition_4 || entityPosition == kPosition_3)) { + EntityIndex entities[39]; + + // Init entities + entities[0] = kEntityPlayer; + + uint progress = 0; + + for (uint i = 1; i < 40 /* number of entities */; i++) { + CarIndex car = getEntityData((EntityIndex)i)->car; + EntityPosition position = getEntityData((EntityIndex)i)->entityPosition; + + if (entityPosition == kPosition_4) { + if ((car == kCarRedSleeping && position > kPosition_9270) || (car == kCarRestaurant && position < kPosition_1540)) + entities[progress++] = (EntityIndex)i; + } else { + if ((car == kCarGreenSleeping && position > kPosition_9270) || (car == kCarRedSleeping && position < kPosition_850)) + entities[progress++] = (EntityIndex)i; + } + } + + if (progress) + getSound()->excuseMe((progress == 1) ? entities[0] : entities[rnd(progress)], kEntityPlayer, SoundManager::kFlagDefault); + } + + if (hotspot->scene) + setScene(hotspot->scene); + break; + } + + case Scene::kTypeSavePointChapter: + if (getProgress().field_18 == 2) + getSavePoints()->push(kEntityPlayer, kEntityChapters, kActionEndChapter); + break; + + case Scene::kTypeLoadBeetleSequences: + if ((getProgress().chapter == kChapter2 || getProgress().chapter == kChapter3) + && getInventory()->get(kItemBeetle)->location == kObjectLocation3) { + if (!getBeetle()->isLoaded()) + getBeetle()->load(); + } + break; + + case Scene::kTypeGameOver: + if (getState()->time >= kTimeCityGalanta || getProgress().field_18 == 4) + break; + + getSound()->processEntry(SoundManager::kSoundType7); + getSound()->playSound(kEntityTrain, "LIB050", SoundManager::kFlagDefault); + + switch (getProgress().chapter) { + default: + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneGameOverPolice2, true); + break; + + case kChapter1: + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneGameOverAlarm, true); + break; + + case kChapter4: + getLogic()->gameOver(kSavegameTypeIndex, 0, kSceneGameOverAlarm2, true); + break; + } + break; + + case Scene::kTypeReadText: + getSound()->readText(scene->param1); + break; + + case Scene::kType133: + if (getFlags()->flag_0) { + getFlags()->flag_0 = false; + getFlags()->shouldRedraw = true; + getLogic()->updateCursor(); + } + break; + + default: + break; + } +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/scenes.h b/engines/lastexpress/game/scenes.h new file mode 100644 index 0000000000..b70526839f --- /dev/null +++ b/engines/lastexpress/game/scenes.h @@ -0,0 +1,120 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SCENEMANAGER_H +#define LASTEXPRESS_SCENEMANAGER_H + +#include "lastexpress/data/scene.h" + +#include "common/hashmap.h" +#include "common/list.h" + +namespace LastExpress { + +class LastExpressEngine; +class SceneLoader; +class Sequence; +class SequenceFrame; + +class SceneManager { +public: + enum CheckPositionType { + kCheckPositionLookingUp, + kCheckPositionLookingDown, + kCheckPositionLookingAtDoors, + kCheckPositionLookingAtClock + }; + + SceneManager(LastExpressEngine *engine); + ~SceneManager(); + + // Scene cache + void loadSceneDataFile(ArchiveIndex archive); + Scene *get(SceneIndex sceneIndex) { return _sceneLoader->get(sceneIndex); } + + // Scene loading + void setScene(SceneIndex sceneIndex); + void loadScene(SceneIndex sceneIndex); + void loadSceneFromObject(ObjectIndex object, bool alternate = false); + void loadSceneFromItem(InventoryItem item); + void loadSceneFromItemPosition(InventoryItem item); + void loadSceneFromPosition(CarIndex car, Position position, int param3 = -1); + + // Scene drawing & processing + void drawScene(SceneIndex sceneIndex); + void processScene(); + SceneIndex processIndex(SceneIndex sceneIndex); + + // Checks + bool checkPosition(SceneIndex sceneIndex, CheckPositionType type) const; + bool checkCurrentPosition(bool doCheckOtherCars) const; + + // Train + void updateDoorsAndClock(); + void resetDoorsAndClock(); + + // Sequence queue + void drawFrames(bool refreshScreen); + void addToQueue(SequenceFrame * const frame); + void removeFromQueue(SequenceFrame *frame); + void removeAndRedraw(SequenceFrame **frame, bool doRedraw); + void resetQueue(); + void setCoordinates(SequenceFrame *frame); + + // Helpers + SceneIndex getSceneIndexFromPosition(CarIndex car, Position position, int param3 = -1); + + void setFlagDrawSequences() { _flagDrawSequences = true; } + +private: + LastExpressEngine *_engine; + SceneLoader *_sceneLoader; ///< Scene loader + + // Flags + bool _flagNoEntity; + bool _flagDrawEntities; + bool _flagDrawSequences; + bool _flagCoordinates; + + Common::Rect _coords; + + // Train sequences + Common::List<SequenceFrame *> _doors; + SequenceFrame *_clockHours; + SequenceFrame *_clockMinutes; + + // Sequence queue + Common::List<SequenceFrame *> _queue; + + // Scene processing + void preProcessScene(SceneIndex *index); + void postProcessScene(); + + void resetCoordinates(); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SCENEMANAGER_H diff --git a/engines/lastexpress/game/sound.cpp b/engines/lastexpress/game/sound.cpp new file mode 100644 index 0000000000..f8b45bb385 --- /dev/null +++ b/engines/lastexpress/game/sound.cpp @@ -0,0 +1,1676 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/sound.h" + +#include "lastexpress/data/snd.h" +#include "lastexpress/data/subtitle.h" + +#include "lastexpress/game/action.h" +#include "lastexpress/game/entities.h" +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/logic.h" +#include "lastexpress/game/savepoint.h" +#include "lastexpress/game/state.h" + +#include "lastexpress/helpers.h" +#include "lastexpress/lastexpress.h" +#include "lastexpress/resource.h" + +namespace LastExpress { + +// Letters & messages +const char *messages[24] = { + "", + "TXT1001", // 1 + "TXT1001A", // 2 + "TXT1011", // 3 + "TXT1012", // 4 + "TXT1013", // 5 + "TXT1014", // 6 + "TXT1020", // 7 + "TXT1030", // 8 + "END1009B", // 50 + "END1046", // 51 + "END1047", // 52 + "END1112", // 53 + "END1112A", // 54 + "END1503", // 55 + "END1505A", // 56 + "END1505B", // 57 + "END1610", // 58 + "END1612A", // 59 + "END1612C", // 61 + "END1612D", // 62 + "ENDALRM1", // 63 + "ENDALRM2", // 64 + "ENDALRM3" // 65 +}; + +const char *cities[17] = { + "EPERNAY", + "CHALONS", + "BARLEDUC", + "NANCY", + "LUNEVILL", + "AVRICOUR", + "DEUTSCHA", + "STRASBOU", + "BADENOOS", + "SALZBURG", + "ATTNANG", + "WELS", + "LINZ", + "VIENNA", + "POZSONY", + "GALANTA", + "POLICE" +}; + +const char *locomotiveSounds[5] = { + "ZFX1005", + "ZFX1006", + "ZFX1007", + "ZFX1007A", + "ZFX1007B" +}; + +static const SoundManager::FlagType soundFlags[32] = { + SoundManager::kFlagDefault, SoundManager::kFlag15, SoundManager::kFlag14, SoundManager::kFlag13, SoundManager::kFlag12, + SoundManager::kFlag11, SoundManager::kFlag11, SoundManager::kFlag10, SoundManager::kFlag10, SoundManager::kFlag9, SoundManager::kFlag9, SoundManager::kFlag8, SoundManager::kFlag8, + SoundManager::kFlag7, SoundManager::kFlag7, SoundManager::kFlag7, SoundManager::kFlag6, SoundManager::kFlag6, SoundManager::kFlag6, + SoundManager::kFlag5, SoundManager::kFlag5, SoundManager::kFlag5, SoundManager::kFlag5, SoundManager::kFlag4, SoundManager::kFlag4, SoundManager::kFlag4, SoundManager::kFlag4, + SoundManager::kFlag3, SoundManager::kFlag3, SoundManager::kFlag3, SoundManager::kFlag3, SoundManager::kFlag3 +}; + +SoundManager::SoundManager(LastExpressEngine *engine) : _engine(engine), _state(0), _currentType(kSoundType16), _flag(0) { + _soundStream = new StreamedSound(); + + // Initialize unknown data + _data0 = 0; + _data1 = 0; + _data2 = 0; + + memset(&_buffer, 0, sizeof(_buffer)); + memset(&_lastWarning, 0, sizeof(_lastWarning)); +} + +SoundManager::~SoundManager() { + delete _soundStream; + + // Zero passed pointers + _engine = NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Timer +////////////////////////////////////////////////////////////////////////// +void SoundManager::handleTimer() { + + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) { + SoundEntry *entry = (*i); + if (entry->stream == NULL) { + i = _cache.reverse_erase(i); + continue; + } else if (!entry->isStreamed) { + entry->isStreamed = true; + _soundStream->load(entry->stream); + } + } + + // TODO: stream any sound in the queue after filtering +} + +////////////////////////////////////////////////////////////////////////// +// Sound queue management +////////////////////////////////////////////////////////////////////////// +void SoundManager::updateQueue() { + //warning("Sound::unknownFunction1: not implemented!"); +} + +void SoundManager::resetQueue(SoundType type1, SoundType type2) { + if (!type2) + type2 = type1; + + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) { + if ((*i)->type != type1 && (*i)->type != type2) + resetEntry(*i); + } +} + +void SoundManager::removeFromQueue(EntityIndex entity) { + SoundEntry *entry = getEntry(entity); + + if (entry) + resetEntry(entry); +} + +void SoundManager::removeFromQueue(Common::String filename) { + SoundEntry *entry = getEntry(filename); + + if (entry) + resetEntry(entry); +} + +void SoundManager::clearQueue() { + _flag |= 4; + + // Wait a while for a flag to be set + for (int i = 0; i < 3000000; i++) + if (_flag & 8) + break; + + _flag |= 8; + + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) { + SoundEntry *entry = (*i); + + removeEntry(entry); + + // Delete entry + SAFE_DELETE(entry); + i = _cache.reverse_erase(i); + } + + updateSubtitles(); +} + +bool SoundManager::isBuffered(EntityIndex entity) { + return (getEntry(entity) != NULL); +} + +bool SoundManager::isBuffered(Common::String filename, bool testForEntity) { + SoundEntry *entry = getEntry(filename); + + if (testForEntity) + return entry != NULL && !entry->entity; + + return (entry != NULL); +} + +////////////////////////////////////////////////////////////////////////// +// Entry +////////////////////////////////////////////////////////////////////////// +void SoundManager::setupEntry(SoundEntry *entry, Common::String name, FlagType flag, int a4) { + if (!entry) + error("SoundManager::setupEntry: Invalid entry!"); + + entry->field_4C = a4; + setEntryType(entry, flag); + setEntryStatus(entry, flag); + + // Add entry to cache + _cache.push_back(entry); + + setupCache(entry); + loadSoundData(entry, name); +} + +void SoundManager::setEntryType(SoundEntry *entry, FlagType flag) { + switch (flag & kFlagType7) { + default: + case kFlagNone: + entry->type = _currentType; + _currentType = (SoundType)(_currentType + 1); + break; + + case kFlagType1_2: { + SoundEntry *previous2 = getEntry(kSoundType2); + if (previous2) + updateEntry(previous2, 0); + + SoundEntry *previous = getEntry(kSoundType1); + if (previous) { + previous->type = kSoundType2; + updateEntry(previous, 0); + } + + entry->type = kSoundType1; + } + break; + + case kFlagType3: { + SoundEntry *previous = getEntry(kSoundType3); + if (previous) { + previous->type = kSoundType4; + updateEntry(previous, 0); + } + + entry->type = kSoundType11; + } + break; + + case kFlagType7: { + SoundEntry *previous = getEntry(kSoundType7); + if (previous) + previous->type = kSoundType8; + + entry->type = kSoundType7; + } + break; + + case kFlagType9: { + SoundEntry *previous = getEntry(kSoundType9); + if (previous) + previous->type = kSoundType10; + + entry->type = kSoundType9; + } + break; + + case kFlagType11: { + SoundEntry *previous = getEntry(kSoundType11); + if (previous) + previous->type = kSoundType14; + + entry->type = kSoundType11; + } + break; + + case kFlagType13: { + SoundEntry *previous = getEntry(kSoundType13); + if (previous) + previous->type = kSoundType14; + + entry->type = kSoundType13; + } + break; + } +} + +void SoundManager::setEntryStatus(SoundEntry *entry, FlagType flag) const { + SoundStatus status = (SoundStatus)flag; + if (!((status & 0xFF) & kSoundStatusClear1)) + status = (SoundStatus)(status | kSoundStatusClear2); + + if (((status & 0xFF00) >> 8) & kSoundStatusClear0) + entry->status.status = (uint32)status; + else + entry->status.status = (status | kSoundStatusClear4); +} + +bool SoundManager::setupCache(SoundEntry *entry) { + warning("Sound::updateCache: not implemented!"); + return true; +} + +void SoundManager::clearStatus() { + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) + (*i)->status.status |= kSoundStatusClear3; +} + +void SoundManager::loadSoundData(SoundEntry *entry, Common::String name) { + entry->name2 = name; + + // Load sound data + entry->stream = getArchive(name); + + if (!entry->stream) + entry->stream = getArchive("DEFAULT.SND"); + + if (entry->stream) { + warning("Sound::loadSoundData: not implemented!"); + } else { + entry->status.status = kSoundStatusRemoved; + } +} + +void SoundManager::resetEntry(SoundEntry * entry) const { + entry->status.status |= kSoundStatusRemoved; + entry->entity = kEntityPlayer; + + if (entry->stream) { + if (!entry->isStreamed) + SAFE_DELETE(entry->stream); + + entry->stream = NULL; + } +} + + +void SoundManager::removeEntry(SoundEntry *entry) { + entry->status.status |= kSoundStatusRemoved; + + // Loop until ready + while (!(entry->status.status1 & 4) && !(_flag & 8) && (_flag & 1)); + + // The original game remove the entry from the cache here, + // but since we are called from within an iterator loop + // we will remove the entry there + // removeFromCache(entry); + + if (entry->subtitle) { + drawSubtitles(entry->subtitle); + SAFE_DELETE(entry->subtitle); + } + + if (entry->entity) { + if (entry->entity == kEntitySteam) + playLoopingSound(); + else if (entry->entity != kEntityTrain) + getSavePoints()->push(kEntityPlayer, entry->entity, kActionEndSound); + } +} + +void SoundManager::updateEntry(SoundEntry *entry, uint value) const { + if (!(entry->status.status3 & 64)) { + + int value2 = value; + + entry->status.status |= kSoundStatus_100000; + + if (value) { + if (_flag & 32) { + entry->field_40 = value; + value2 = value * 2 + 1; + } + + entry->field_3C = value2; + } else { + entry->field_3C = 0; + entry->status.status |= kSoundStatus_40000000; + } + } +} + +void SoundManager::updateEntryState(SoundEntry *entry) const { + if (_flag & 32) { + if (entry->type != kSoundType9 && entry->type != kSoundType7 && entry->type != kSoundType5) { + uint32 status = entry->status.status & kSoundStatusClear1; + + entry->status.status &= kSoundStatusClearAll; + + entry->field_40 = status; + entry->status.status |= status * 2 + 1; + } + } + + entry->status.status |= kSoundStatus_20; +} + +void SoundManager::processEntry(EntityIndex entity) { + SoundEntry *entry = getEntry(entity); + + if (entry) { + updateEntry(entry, 0); + entry->entity = kEntityPlayer; + } +} + +void SoundManager::processEntry(SoundType type) { + SoundEntry *entry = getEntry(type); + + if (entry) + updateEntry(entry, 0); +} + +void SoundManager::setupEntry(SoundType type, EntityIndex index) { + SoundEntry *entry = getEntry(type); + + if (entry) + entry->entity = index; +} + +void SoundManager::processEntry(Common::String filename) { + SoundEntry *entry = getEntry(filename); + + if (entry) { + updateEntry(entry, 0); + entry->entity = kEntityPlayer; + } +} + +void SoundManager::processEntries() { + _state = 0; + + processEntry(kSoundType1); + processEntry(kSoundType2); +} + +////////////////////////////////////////////////////////////////////////// +// Misc +////////////////////////////////////////////////////////////////////////// + +void SoundManager::unknownFunction4() { + warning("Sound::unknownFunction4: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// Entry search +////////////////////////////////////////////////////////////////////////// +SoundManager::SoundEntry *SoundManager::getEntry(EntityIndex index) { + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) { + if ((*i)->entity == index) + return *i; + } + + return NULL; +} + +SoundManager::SoundEntry *SoundManager::getEntry(Common::String name) { + if (!name.contains('.')) + name += ".SND"; + + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) { + if ((*i)->name2 == name) + return *i; + } + + return NULL; +} + +SoundManager::SoundEntry *SoundManager::getEntry(SoundType type) { + for (Common::List<SoundEntry *>::iterator i = _cache.begin(); i != _cache.end(); ++i) { + if ((*i)->type == type) + return *i; + } + + return NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Savegame +////////////////////////////////////////////////////////////////////////// +void SoundManager::saveLoadWithSerializer(Common::Serializer &ser) { + error("Sound::saveLoadWithSerializer: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// Game-related functions +////////////////////////////////////////////////////////////////////////// +void SoundManager::playSound(EntityIndex entity, Common::String filename, FlagType flag, byte a4) { + if (isBuffered(entity) && entity) + removeFromQueue(entity); + + FlagType currentFlag = (flag == -1) ? getSoundFlag(entity) : (FlagType)(flag | 0x80000); + + // Add .SND at the end of the filename if needed + if (!filename.contains('.')) + filename += ".SND"; + + if (!playSoundWithSubtitles(filename, currentFlag, entity, a4)) + if (entity) + getSavePoints()->push(kEntityPlayer, entity, kActionEndSound); +} + +SoundManager::SoundType SoundManager::playSoundWithSubtitles(Common::String filename, FlagType flag, EntityIndex entity, byte a4) { + SoundEntry* entry = new SoundEntry(); + setupEntry(entry, filename, flag, 30); + entry->entity = entity; + + if (a4) { + entry->field_48 = _data2 + 2 * a4; + entry->status.status |= kSoundStatus_8000; + } else { + // Get subtitles name + while (filename.size() > 4) + filename.deleteLastChar(); + + showSubtitles(entry, filename); + updateEntryState(entry); + } + + return entry->type; +} + +void SoundManager::playSoundEvent(EntityIndex entity, byte action, byte a3) { + char filename[12]; + int values[5]; + + if (getEntityData(entity)->car != getEntityData(kEntityPlayer)->car) + return; + + if (getEntities()->isInSalon(entity) != getEntities()->isInSalon(kEntityPlayer)) + return; + + int _action = (int)action; + FlagType flag = getSoundFlag(entity); + + switch (action) { + case 36: { + int _param3 = (flag <= 9) ? flag + 7 : 16; + + if (_param3 > 7) { + _data0 = (uint)_param3; + _data1 = _data2 + 2 * a3; + } + break; + } + + case 37: + _data0 = 7; + _data1 = _data2 + 2 * a3; + break; + + case 150: + case 156: + case 162: + case 168: + case 188: + case 198: + _action += 1 + (int)rnd(5); + break; + + case 174: + case 184: + case 194: + _action += 1 + (int)rnd(3); + break; + + case 180: + _action += 1 + (int)rnd(4); + break; + + case 246: + values[0] = 0; + values[1] = 104; + values[2] = 105; + values[3] = 106; + values[4] = 116; + _action = values[rnd(5)]; + break; + + case 247: + values[0] = 11; + values[1] = 123; + values[2] = 124; + _action = values[rnd(3)]; + break; + + case 248: + values[0] = 0; + values[1] = 103; + values[2] = 108; + values[3] = 109; + _action = values[rnd(4)]; + break; + + case 249: + values[0] = 0; + values[1] = 56; + values[2] = 112; + values[3] = 113; + _action = values[rnd(4)]; + break; + + case 250: + values[0] = 0; + values[1] = 107; + values[2] = 115; + values[3] = 117; + _action = values[rnd(4)]; + break; + + case 251: + values[0] = 0; + values[1] = 11; + values[2] = 56; + values[3] = 113; + _action = values[rnd(4)]; + break; + + case 252: + values[0] = 0; + values[1] = 6; + values[2] = 109; + values[3] = 121; + _action = values[rnd(4)]; + break; + + case 254: + values[0] = 0; + values[1] = 104; + values[2] = 120; + values[3] = 121; + _action = values[rnd(4)]; + break; + + case 255: + values[0] = 0; + values[1] = 106; + values[2] = 115; + _action = values[rnd(3)]; + break; + + default: + break; + } + + if (_action) { + sprintf((char *)&filename, "LIB%03d.SND", _action); + + if (flag) + playSoundWithSubtitles((char*)&filename, flag, kEntityPlayer, a3); + } +} + +void SoundManager::playSteam(CityIndex index) { + if (index >= ARRAYSIZE(cities)) + error("SoundManager::playSteam: invalid city index (was %d, max %d)", index, ARRAYSIZE(cities)); + + _state |= kSoundState2; + + if (!getEntry(kSoundType1)) + playSoundWithSubtitles("STEAM.SND", kFlagSteam, kEntitySteam); + + // Get the new sound entry and show subtitles + SoundEntry *entry = getEntry(kSoundType1); + if (entry) + showSubtitles(entry, cities[index]); +} + +void SoundManager::playFightSound(byte action, byte a4) { + int _action = (int)action; + char filename[12]; + int values[5]; + + switch (action) { + default: + break; + + case 174: + case 184: + case 194: + values[0] = action + 1; + values[1] = action + 2; + values[2] = action + 3; + _action = values[rnd(3)]; + break; + + case 180: + values[0] = action + 1; + values[1] = action + 2; + values[2] = action + 3; + values[3] = action + 4; + _action = values[rnd(4)]; + break; + + case 150: + case 156: + case 162: + case 168: + case 188: + case 198: + values[0] = action + 1; + values[1] = action + 2; + values[2] = action + 3; + values[3] = action + 4; + values[4] = action + 5; + _action = values[rnd(5)]; + break; + } + + if (_action) { + sprintf((char *)&filename, "LIB%03d.SND", _action); + playSound(kEntityTrain, (char*)&filename, kFlagDefault, a4); + } +} + +void SoundManager::playDialog(EntityIndex entity, EntityIndex entityDialog, FlagType flag, byte a4) { + if (isBuffered(getDialogName(entityDialog))) + removeFromQueue(getDialogName(entityDialog)); + + playSound(entity, getDialogName(entityDialog), flag, a4); +} + +void SoundManager::playLocomotiveSound() { + playSound(kEntityPlayer, locomotiveSounds[rnd(5)], (FlagType)(rnd(15) + 2)); +} + +const char *SoundManager::getDialogName(EntityIndex entity) const { + switch (entity) { + case kEntityAnna: + if (getEvent(kEventAnnaDialogGoToJerusalem)) + return "XANN12"; + + if (getEvent(kEventLocomotiveRestartTrain)) + return "XANN11"; + + if (getEvent(kEventAnnaBaggageTies) || getEvent(kEventAnnaBaggageTies2) || getEvent(kEventAnnaBaggageTies3) || getEvent(kEventAnnaBaggageTies4)) + return "XANN10"; + + if (getEvent(kEventAnnaTired) || getEvent(kEventAnnaTiredKiss)) + return "XANN9"; + + if (getEvent(kEventAnnaBaggageArgument)) + return "XANN8"; + + if (getEvent(kEventKronosVisit)) + return "XANN7"; + + if (getEvent(kEventAbbotIntroduction)) + return "XANN6A"; + + if (getEvent(kEventVassiliSeizure)) + return "XANN6"; + + if (getEvent(kEventAugustPresentAnna) || getEvent(kEventAugustPresentAnnaFirstIntroduction)) + return "XANN5"; + + if (getProgress().field_60) + return "XANN4"; + + if (getEvent(kEventAnnaGiveScarf) || getEvent(kEventAnnaGiveScarfDiner) || getEvent(kEventAnnaGiveScarfSalon) + || getEvent(kEventAnnaGiveScarfMonogram) || getEvent(kEventAnnaGiveScarfDinerMonogram) || getEvent(kEventAnnaGiveScarfSalonMonogram)) + return "XANN3"; + + if (getEvent(kEventDinerMindJoin)) + return "XANN2"; + + if (getEvent(kEventGotALight) || getEvent(kEventGotALightD)) + return "XANN1"; + + break; + + case kEntityAugust: + if (getEvent(kEventAugustTalkCigar)) + return "XAUG6"; + + if (getEvent(kEventAugustBringBriefcase)) + return "XAUG5"; + + // Getting closer to Vienna... + if (getState()->time > kTime2200500 && !getEvent(kEventAugustMerchandise)) + return "XAUG4A"; + + if (getEvent(kEventAugustMerchandise)) + return "XAUG4"; + + if (getEvent(kEventDinerAugust) || getEvent(kEventDinerAugustAlexeiBackground) || getEvent(kEventMeetAugustTylerCompartment) + || getEvent(kEventMeetAugustTylerCompartmentBed) || getEvent(kEventMeetAugustHisCompartment) || getEvent(kEventMeetAugustHisCompartmentBed)) + return "XAUG3"; + + if (getEvent(kEventAugustPresentAnnaFirstIntroduction)) + return "XAUG2"; + + if (getProgress().eventMertensAugustWaiting) + return "XAUG1"; + + break; + + case kEntityTatiana: + if (getEvent(kEventTatianaTylerCompartment)) + return "XTAT6"; + + if (getEvent(kEventTatianaCompartmentStealEgg)) + return "XTAT5"; + + if (getEvent(kEventTatianaGivePoem)) + return "XTAT3"; + + if (getProgress().field_64) + return "XTAT1"; + + break; + + case kEntityVassili: + if (getEvent(kEventCathFreePassengers)) + return "XVAS4"; + + if (getEvent(kEventVassiliCompartmentStealEgg)) + return "XVAS3"; + + if (getEvent(kEventAbbotIntroduction)) + return "XVAS2"; + + if (getEvent(kEventVassiliSeizure)) + return "XVAS1A"; + + if (getProgress().field_64) + return "XVAS1"; + + break; + + case kEntityAlexei: + if (getProgress().field_88) + return "XALX6"; + + if (getProgress().field_8C) + return "XALX5"; + + if (getProgress().field_90) + return "XALX4A"; + + if (getProgress().field_68) + return "XALX4"; + + if (getEvent(kEventAlexeiSalonPoem)) + return "XALX3"; + + if (getEvent(kEventAlexeiSalonVassili)) + return "XALX2"; + + if (getEvent(kEventAlexeiDiner) || getEvent(kEventAlexeiDinerOriginalJacket)) + return "XALX1"; + + break; + + case kEntityAbbot: + if (getEvent(kEventAbbotDrinkDefuse)) + return "XABB4"; + + if (getEvent(kEventAbbotInvitationDrink) || getEvent(kEventDefuseBomb)) + return "XABB3"; + + if (getEvent(kEventAbbotWrongCompartment) || getEvent(kEventAbbotWrongCompartmentBed)) + return "XABB2"; + + if (getEvent(kEventAbbotIntroduction)) + return "XABB1"; + + break; + + case kEntityMilos: + if (getEvent(kEventLocomotiveMilos) || getEvent(kEventLocomotiveMilosNight)) + return "XMIL5"; + + if (getEvent(kEventMilosCompartmentVisitTyler) && (getProgress().chapter == kChapter3 || getProgress().chapter == kChapter4)) + return "XMIL4"; + + if (getEvent(kEventMilosCorridorThanks) || getProgress().chapter == kChapter5) + return "XMIL3"; + + if (getEvent(kEventMilosCompartmentVisitAugust)) + return "XMIL2"; + + if (getEvent(kEventMilosTylerCompartmentDefeat)) + return "XMIL1"; + + break; + + case kEntityVesna: + if (getProgress().field_94) + return "XVES2"; + + if (getProgress().field_98) + return "XVES1"; + + break; + + case kEntityKronos: + if (getEvent(kEventKronosReturnBriefcase)) + return "XKRO6"; + + if (getEvent(kEventKronosBringEggCeiling) || getEvent(kEventKronosBringEgg)) + return "XKRO5"; + + if (getEvent(kEventKronosConversation) || getEvent(kEventKronosConversationFirebird)) { + ObjectLocation location = getInventory()->get(kItemFirebird)->location; + if (location != kObjectLocation6 && location != kObjectLocation5 && location != kObjectLocation2 && location != kObjectLocation1) + return "XKRO4A"; + } + + if (getEvent(kEventKronosConversationFirebird)) + return "XKRO4"; + + if (getEvent(kEventKronosConversation)) { + if (!getEvent(kEventMilosCompartmentVisitAugust)) + return "XKRO3"; + else + return "XKRO2"; + } + + if (getProgress().eventMertensKronosInvitation) + return "XKRO1"; + + break; + + case kEntityFrancois: + if (getProgress().field_9C) + return "XFRA3"; + + if (getProgress().field_A0 + || getEvent(kEventFrancoisWhistle) || getEvent(kEventFrancoisWhistleD) + || getEvent(kEventFrancoisWhistleNight) || getEvent(kEventFrancoisWhistleNightD)) + return "XFRA2"; + + if (getState()->time > kTimeParisEpernay) // Between Paris and Epernay + return "XFRA1"; + + break; + + case kEntityMmeBoutarel: + if (getProgress().field_A4) + return "XMME4"; + + if (getProgress().field_A8) + return "XMME3"; + + if (getProgress().field_A0) + return "XMME2"; + + if (getProgress().field_AC) + return "XMME1"; + + break; + + case kEntityBoutarel: + if (getProgress().eventMetBoutarel) + return "XMRB1"; + + break; + + case kEntityRebecca: + if (getProgress().field_B4) + return "XREB1A"; + + if (getProgress().field_B8) + return "XREB1"; + + break; + + case kEntitySophie: + if (getProgress().field_B0) + return "XSOP2"; + + if (getProgress().field_BC) + return "XSOP1B"; + + if (getProgress().field_B4) + return "XSOP1A"; + + if (getProgress().field_B8) + return "XSOP1"; + + break; + + case kEntityMahmud: + if (getProgress().field_C4) + return "XMAH1"; + + break; + + case kEntityYasmin: + if (getProgress().eventMetYasmin) + return "XHAR2"; + + break; + + case kEntityHadija: + if (getProgress().eventMetHadija) + return "XHAR1"; + + break; + + case kEntityAlouan: + if (getProgress().field_DC) + return "XHAR3"; + + break; + + case kEntityGendarmes: + if (getProgress().field_E0) + return "XHAR4"; + + break; + + case kEntityChapters: + if (getEvent(kEventCathDream) || getEvent(kEventCathWakingUp)) + return "XTYL3"; + + return "XTYL1"; + + default: + break; + } + + return NULL; +} + +////////////////////////////////////////////////////////////////////////// +// Letters & Messages +////////////////////////////////////////////////////////////////////////// +void SoundManager::readText(int id){ + if (!isBuffered(kEntityTables4)) + return; + + if (id < 0 || (id > 8 && id < 50) || id > 64) + error("Sound::readText - attempting to use invalid id. Valid values [1;8] - [50;64], was %d", id); + + // Get proper message file (names are stored in sequence in the array but id is [1;8] - [50;64]) + const char* text = messages[id <= 8 ? id : id - 41]; + + // Check if file is in cache for id [1;8] + if (id <= 8) + if (isBuffered(text)) + removeFromQueue(text); + + playSound(kEntityTables4, text, kFlagDefault); +} + +////////////////////////////////////////////////////////////////////////// +// Sound bites +////////////////////////////////////////////////////////////////////////// +void SoundManager::playWarningCompartment(EntityIndex entity, ObjectIndex compartment) { + +#define PLAY_WARNING(index, sound1, sound2, sound3, sound4, sound5, sound6) { \ + if (_lastWarning[index] + 450 >= getState()->timeTicks) { \ + if (rnd(2)) \ + playSound(kEntityMertens, sound1, kFlagDefault); \ + else \ + playSound(kEntityMertens, rnd(2) ? sound2 : sound3, kFlagDefault); \ + } else { \ + if (rnd(2)) \ + playSound(kEntityMertens, sound4, kFlagDefault); \ + else \ + playSound(kEntityMertens, rnd(2) ? sound5 : sound6, kFlagDefault); \ + } \ + _lastWarning[index] = getState()->timeTicks; \ +} + + if (entity != kEntityMertens && entity != kEntityCoudert) + return; + + ////////////////////////////////////////////////////////////////////////// + // Mertens + if (entity == kEntityMertens) { + + switch (compartment) { + default: + break; + + case kObjectCompartment2: + PLAY_WARNING(0, "Con1502A", "Con1500B", "Con1500C", "Con1502", "Con1500", "Con1500A"); + break; + + case kObjectCompartment3: + PLAY_WARNING(1, "Con1501A", "Con1500B", "Con1500C", "Con1501", "Con1500", "Con1500A"); + break; + + case kObjectCompartment4: + PLAY_WARNING(2, "Con1503", "Con1500B", "Con1500C", "Con1503", "Con1500", "Con1500A"); + break; + + case kObjectCompartment5: + case kObjectCompartment6: + case kObjectCompartment7: + case kObjectCompartment8: + ++_lastWarning[3]; + + switch (_lastWarning[3]) { + default: + break; + + case 1: + getSound()->playSound(kEntityMertens, "Con1503C", kFlagDefault); + break; + + case 2: + getSound()->playSound(kEntityMertens, rnd(2) ? "Con1503E" : "Con1503A", kFlagDefault); + break; + + case 3: + getSound()->playSound(kEntityMertens, rnd(2) ? "Con1503B" : "Con1503D", kFlagDefault); + _lastWarning[3] = 0; + break; + } + } + + return; + } + + ////////////////////////////////////////////////////////////////////////// + // Coudert + switch (compartment) { + default: + break; + + case kObjectCompartmentA: + if (_lastWarning[4] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1508" : "Jac1508A", kFlagDefault); + break; + + case kObjectCompartmentB: + if (_lastWarning[5] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + if (getProgress().field_40 || (getState()->time > kTimeCityLinz && getState()->time < kTime2133000)) + getSound()->playSound(kEntityCoudert, "Jac1507A", kFlagDefault); + else + getSound()->playSound(kEntityCoudert, "Jac1507", kFlagDefault); + break; + + case kObjectCompartmentC: + if (_lastWarning[6] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + if (getProgress().chapter < kChapter3) + getSound()->playSound(kEntityCoudert, "Jac1506", kFlagDefault); + else + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1506A" : "Jac1506B", kFlagDefault); + break; + + case kObjectCompartmentD: + if (_lastWarning[7] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + getSound()->playSound(kEntityCoudert, "Jac1505", kFlagDefault); + break; + + case kObjectCompartmentE: + if (_lastWarning[8] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + if (getProgress().field_40 || (getState()->time > kTime2115000 && getState()->time < kTime2133000)) { + getSound()->playSound(kEntityCoudert, "Jac1504B", kFlagDefault); + break; + } + + if (getEntities()->isInsideCompartment(kEntityRebecca, kCarRedSleeping, kPosition_4840)) + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + else + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1504" : "Jac1504A", kFlagDefault); + break; + + case kObjectCompartmentF: + if (_lastWarning[9] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + if (getProgress().field_40 || (getState()->time > kTime2083500 && getState()->time < kTime2133000)) { + getSound()->playSound(kEntityCoudert, "Jac1503B", kFlagDefault); + break; + } + + if (rnd(2) || getEntities()->isInsideCompartment(kEntityAnna, kCarRedSleeping, kPosition_4070)) + getSound()->playSound(kEntityCoudert, "Jac1503", kFlagDefault); + else + getSound()->playSound(kEntityCoudert, "Jac1503A", kFlagDefault); + break; + + case kObjectCompartmentG: + if (_lastWarning[10] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + if (rnd(2) || getEntities()->isInsideCompartment(kEntityMilos, kCarRedSleeping, kPosition_3050)) + getSound()->playSound(kEntityCoudert, "Jac1502", kFlagDefault); + else + getSound()->playSound(kEntityCoudert, "Jac1502A", kFlagDefault); + break; + + case kObjectCompartmentH: + if (_lastWarning[11] + 450 >= getState()->timeTicks) { + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + break; + } + + if (getEntities()->isInsideCompartment(kEntityIvo, kCarRedSleeping, kPosition_2740)) + getSound()->playSound(kEntityCoudert, rnd(2) ? "Jac1500" : "Jac1500A", kFlagDefault); + else + getSound()->playSound(kEntityCoudert, "Jac1501", kFlagDefault); + break; + } + + // Update ticks (Compartments A - H are indexes 4 - 11) + _lastWarning[compartment - 28] = getState()->timeTicks; +} + +void SoundManager::excuseMe(EntityIndex entity, EntityIndex entity2, FlagType flag) { + if (isBuffered(entity) && entity != kEntityPlayer && entity != kEntityChapters && entity != kEntityTrain) + return; + + if (entity2 == kEntityFrancois || entity2 == kEntityMax) + return; + + if (entity == kEntityFrancois && getEntityData(kEntityFrancois)->field_4A3 != 30) + return; + + if (flag == kFlagNone) + flag = getSoundFlag(entity); + + switch (entity) { + default: + break; + + case kEntityAnna: + playSound(kEntityPlayer, "ANN1107A", flag); + break; + + case kEntityAugust: + switch(rnd(4)) { + default: + break; + + case 0: + playSound(kEntityPlayer, "AUG1100A", flag); + break; + + case 1: + playSound(kEntityPlayer, "AUG1100B", flag); + break; + + case 2: + playSound(kEntityPlayer, "AUG1100C", flag); + break; + + case 3: + playSound(kEntityPlayer, "AUG1100D", flag); + break; + } + break; + + case kEntityMertens: + if (Entities::isFemale(entity2)) { + playSound(kEntityPlayer, (rnd(2) ? "CON1111" : "CON1111A"), flag); + } else { + if (entity2 || getProgress().jacket != kJacketGreen || !rnd(2)) { + switch(rnd(3)) { + default: + break; + + case 0: + playSound(kEntityPlayer, "CON1110A", flag); + break; + + case 1: + playSound(kEntityPlayer, "CON1110C", flag); + break; + + case 2: + playSound(kEntityPlayer, "CON1110", flag); + break; + } + } else { + if (isNight()) { + playSound(kEntityPlayer, (getProgress().field_18 == 2 ? "CON1110F" : "CON1110E")); + } else { + playSound(kEntityPlayer, "CON1110D"); + } + } + } + break; + + case kEntityCoudert: + if (Entities::isFemale(entity2)) { + playSound(kEntityPlayer, "JAC1111D", flag); + } else { + if (entity2 || getProgress().jacket != kJacketGreen || !rnd(2)) { + switch(rnd(4)) { + default: + break; + + case 0: + playSound(kEntityPlayer, "JAC1111", flag); + break; + + case 1: + playSound(kEntityPlayer, "JAC1111A", flag); + break; + + case 2: + playSound(kEntityPlayer, "JAC1111B", flag); + break; + + case 3: + playSound(kEntityPlayer, "JAC1111C", flag); + break; + } + } else { + playSound(kEntityPlayer, "JAC1113B", flag); + } + } + break; + + case kEntityPascale: + playSound(kEntityPlayer, (rnd(2) ? "HDE1002" : "HED1002A"), flag); + break; + + case kEntityServers0: + case kEntityServers1: + switch(rnd(3)) { + default: + break; + + case 0: + playSound(kEntityPlayer, (entity == kEntityServers0) ? "WAT1002" : "WAT1003", flag); + break; + + case 1: + playSound(kEntityPlayer, (entity == kEntityServers0) ? "WAT1002A" : "WAT1003A", flag); + break; + + case 2: + playSound(kEntityPlayer, (entity == kEntityServers0) ? "WAT1002B" : "WAT1003B", flag); + break; + } + break; + + case kEntityVerges: + if (Entities::isFemale(entity2)) { + playSound(kEntityPlayer, (rnd(2) ? "TRA1113A" : "TRA1113B")); + } else { + playSound(kEntityPlayer, "TRA1112", flag); + } + break; + + case kEntityTatiana: + playSound(kEntityPlayer, (rnd(2) ? "TAT1102A" : "TAT1102B"), flag); + break; + + case kEntityAlexei: + playSound(kEntityPlayer, (rnd(2) ? "ALX1099C" : "ALX1099D"), flag); + break; + + case kEntityAbbot: + if (Entities::isFemale(entity2)) { + playSound(kEntityPlayer, "ABB3002C", flag); + } else { + switch(rnd(3)) { + default: + break; + + case 0: + playSound(kEntityPlayer, "ABB3002", flag); + break; + + case 1: + playSound(kEntityPlayer, "ABB3002A", flag); + break; + + case 2: + playSound(kEntityPlayer, "ABB3002B", flag); + break; + } + } + break; + + case kEntityVesna: + switch(rnd(3)) { + default: + break; + + case 0: + playSound(kEntityPlayer, "VES1109A", flag); + break; + + case 1: + playSound(kEntityPlayer, "VES1109B", flag); + break; + + case 2: + playSound(kEntityPlayer, "VES1109C", flag); + break; + } + break; + + case kEntityKahina: + playSound(kEntityPlayer, (rnd(2) ? "KAH1001" : "KAH1001A"), flag); + break; + + case kEntityFrancois: + case kEntityMmeBoutarel: + switch(rnd(4)) { + default: + break; + + case 0: + playSound(kEntityPlayer, (entity == kEntityFrancois) ? "FRA1001" : "MME1103A", flag); + break; + + case 1: + playSound(kEntityPlayer, (entity == kEntityFrancois) ? "FRA1001A" : "MME1103B", flag); + break; + + case 2: + playSound(kEntityPlayer, (entity == kEntityFrancois) ? "FRA1001B" : "MME1103C", flag); + break; + + case 3: + playSound(kEntityPlayer, (entity == kEntityFrancois) ? "FRA1001C" : "MME1103D", flag); + break; + } + break; + + case kEntityBoutarel: + playSound(kEntityPlayer, "MRB1104", flag); + if (flag > 2) + getProgress().eventMetBoutarel = true; + break; + + case kEntityRebecca: + playSound(kEntityPlayer, (rnd(2) ? "REB1106" : "REB110A"), flag); + break; + + case kEntitySophie: { + switch(rnd(3)) { + default: + break; + + case 0: + playSound(kEntityPlayer, "SOP1105", flag); + break; + + case 1: + playSound(kEntityPlayer, Entities::isFemale(entity2) ? "SOP1105C" : "SOP1105A", flag); + break; + + case 2: + playSound(kEntityPlayer, Entities::isFemale(entity2) ? "SOP1105D" : "SOP1105B", flag); + break; + } + break; + } + + case kEntityMahmud: + playSound(kEntityPlayer, "MAH1101", flag); + break; + + case kEntityYasmin: + playSound(kEntityPlayer, "HAR1002", flag); + if (flag > 2) + getProgress().eventMetYasmin = true; + break; + + case kEntityHadija: + playSound(kEntityPlayer, (rnd(2) ? "HAR1001" : "HAR1001A"), flag); + if (flag > 2) + getProgress().eventMetHadija = true; + break; + + case kEntityAlouan: + playSound(kEntityPlayer, "HAR1004", flag); + break; + } +} + +void SoundManager::excuseMeCath() { + switch(rnd(3)) { + default: + playSound(kEntityPlayer, "CAT1126B"); + break; + + case 1: + playSound(kEntityPlayer, "CAT1126C"); + break; + + case 2: + playSound(kEntityPlayer, "CAT1126D"); + break; + } +} + +const char *SoundManager::justCheckingCath() const { + switch(rnd(4)) { + default: + break; + + case 0: + return "CAT5001"; + + case 1: + return "CAT5001A"; + + case 2: + return "CAT5001B"; + + case 3: + return "CAT5001C"; + } + + return "CAT5001"; +} + +const char *SoundManager::wrongDoorCath() const { + switch(rnd(5)) { + default: + break; + + case 0: + return "CAT1125"; + + case 1: + return "CAT1125A"; + + case 2: + return "CAT1125B"; + + case 3: + return "CAT1125C"; + + case 4: + return "CAT1125D"; + } + + return "CAT1125"; +} + +const char *SoundManager::justAMinuteCath() const { + switch(rnd(3)) { + default: + break; + + case 0: + return "CAT1520"; + + case 1: + return "CAT1521"; + + case 2: + return "CAT1125"; // ?? is this a bug in the original? + } + + return "CAT1520"; +} + +////////////////////////////////////////////////////////////////////////// +// Sound flags +////////////////////////////////////////////////////////////////////////// +SoundManager::FlagType SoundManager::getSoundFlag(EntityIndex entity) const { + if (entity == kEntityPlayer) + return kFlagDefault; + + if (getEntityData(entity)->car != getEntityData(kEntityPlayer)->car) + return kFlagNone; + + // Compute sound value + FlagType ret = kFlag2; + + // Get default value if valid + int index = ABS(getEntityData(entity)->entityPosition - getEntityData(kEntityPlayer)->entityPosition) / 230; + if (index < 32) + ret = soundFlags[index]; + + if (getEntityData(entity)->location == kLocationOutsideTrain) { + if (getEntityData(entity)->car != kCarKronos + && !getEntities()->isOutsideAlexeiWindow() + && !getEntities()->isOutsideAnnaWindow()) + return kFlagNone; + + return (FlagType)(ret / 6); + } + + switch (getEntityData(entity)->car) { + default: + break; + + case kCarKronos: + if (getEntities()->isInKronosSalon(entity) != getEntities()->isInKronosSalon(kEntityPlayer)) + ret = (FlagType)(ret * 2); + break; + + case kCarGreenSleeping: + case kCarRedSleeping: + if (getEntities()->isInGreenCarEntrance(kEntityPlayer) && !getEntities()->isInKronosSalon(entity)) + ret = (FlagType)(ret * 2); + + if (getEntityData(kEntityPlayer)->location + && (getEntityData(entity)->entityPosition != kPosition_1 || !getEntities()->isDistanceBetweenEntities(kEntityPlayer, entity, 400))) + ret = (FlagType)(ret * 2); + break; + + case kCarRestaurant: + if (getEntities()->isInSalon(entity) == getEntities()->isInSalon(kEntityPlayer) + && (getEntities()->isInRestaurant(entity) != getEntities()->isInRestaurant(kEntityPlayer))) + ret = (FlagType)(ret * 2); + else + ret = (FlagType)(ret * 4); + break; + } + + return ret; +} + +////////////////////////////////////////////////////////////////////////// +// Subtitles +////////////////////////////////////////////////////////////////////////// +void SoundManager::updateSubtitles() { + //warning("SoundManager::updateSubtitles: not implemented!"); +} + +void SoundManager::showSubtitles(SoundEntry *entry, Common::String filename) { + warning("SoundManager::showSubtitles: not implemented!"); +} + +void SoundManager::drawSubtitles(SubtitleManager *subtitle) { + warning("SoundManager::drawSubtitles: not implemented!"); +} + +////////////////////////////////////////////////////////////////////////// +// Misc +////////////////////////////////////////////////////////////////////////// +void SoundManager::playLoopingSound() { + warning("SoundManager::playLoopingSound: not implemented!"); +} + +void SoundManager::stopAllSound() const { + _soundStream->stop(); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/sound.h b/engines/lastexpress/game/sound.h new file mode 100644 index 0000000000..3cb17db80c --- /dev/null +++ b/engines/lastexpress/game/sound.h @@ -0,0 +1,333 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SOUND_H +#define LASTEXPRESS_SOUND_H + +/* + + Sound entry: 68 bytes (this is what appears in the savegames) + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - entity + uint32 {4} - ?? + uint32 {4} - ?? + char {16} - name 1 + char {16} - name 2 + + Sound queue entry: 120 bytes + uint16 {2} - status + byte {1} - ?? + byte {1} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - file data pointer + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - archive structure pointer + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - ?? + uint32 {4} - entity + uint32 {4} - ?? + uint32 {4} - ?? + char {16} - name 1 + char {16} - name 2 + uint32 {4} - pointer to next entry in the queue + uint32 {4} - subtitle data pointer + +*/ + +#include "lastexpress/shared.h" + +#include "common/list.h" +#include "common/system.h" +#include "common/serializer.h" + +namespace LastExpress { + +class LastExpressEngine; +class StreamedSound; +class SubtitleManager; + +class SoundManager : Common::Serializable { +public: + enum SoundType { + kSoundTypeNone = 0, + kSoundType1, + kSoundType2, + kSoundType3, + kSoundType4, + kSoundType5, + kSoundType6, + kSoundType7, + kSoundType8, + kSoundType9, + kSoundType10, + kSoundType11, + kSoundType12, + kSoundType13, + kSoundType14, + kSoundType15, + kSoundType16 + }; + + enum FlagType { + kFlagInvalid = -1, + kFlagNone = 0x0, + kFlag2 = 0x2, + kFlag3 = 0x3, + kFlag4 = 0x4, + kFlag5 = 0x5, + kFlag6 = 0x6, + kFlag7 = 0x7, + kFlag8 = 0x8, + kFlag9 = 0x9, + kFlag10 = 0xA, + kFlag11 = 0xB, + kFlag12 = 0xC, + kFlag13 = 0xD, + kFlag14 = 0xE, + kFlag15 = 0xF, + kFlagDefault = 0x10, + + kFlagType1_2 = 0x1000000, + kFlagSteam = 0x1001007, + kFlagType13 = 0x3000000, + kFlagMenuClock = 0x3080010, + kFlagType7 = 0x4000000, + kFlagType11 = 0x5000000, + kFlagMusic = 0x5000010, + kFlagType3 = 0x6000000, + kFlagLoop = 0x6001008, + kFlagType9 = 0x7000000 + }; + + SoundManager(LastExpressEngine *engine); + ~SoundManager(); + + // Timer + void handleTimer(); + + // State + void resetState() { _state |= kSoundType1; } + + // Sound queue + void updateQueue(); + void resetQueue(SoundType type1, SoundType type2 = kSoundTypeNone); + void clearQueue(); + + // Subtitles + void updateSubtitles(); + + // Entry + bool isBuffered(Common::String filename, bool testForEntity = false); + bool isBuffered(EntityIndex entity); + void setupEntry(SoundType type, EntityIndex index); + void processEntry(EntityIndex entity); + void processEntry(SoundType type); + void processEntry(Common::String filename); + void processEntries(); + void removeFromQueue(Common::String filename); + void removeFromQueue(EntityIndex entity); + + // Misc + void unknownFunction4(); + void clearStatus(); + + // Sound playing + void playSound(EntityIndex entity, Common::String filename, FlagType flag = kFlagInvalid, byte a4 = 0); + SoundType playSoundWithSubtitles(Common::String filename, FlagType flag, EntityIndex entity, byte a4 = 0); + void playSoundEvent(EntityIndex entity, byte action, byte a3 = 0); + void playDialog(EntityIndex entity, EntityIndex entityDialog, FlagType flag, byte a4); + void playSteam(CityIndex index); + void playFightSound(byte action, byte a4); + void playLocomotiveSound(); + void playWarningCompartment(EntityIndex entity, ObjectIndex compartment); + + // Dialog & Letters + void readText(int id); + const char *getDialogName(EntityIndex entity) const; + + // Sound bites + void excuseMe(EntityIndex entity, EntityIndex entity2 = kEntityPlayer, FlagType flag = kFlagNone); + void excuseMeCath(); + const char *justCheckingCath() const; + const char *wrongDoorCath() const; + const char *justAMinuteCath() const; + + // FLags + SoundManager::FlagType getSoundFlag(EntityIndex index) const; + + // Debug + void stopAllSound() const; + + // Serializable + void saveLoadWithSerializer(Common::Serializer &ser); + +private: + typedef int32* SoundBuffer; + + enum SoundStatus { + kSoundStatus_20 = 0x20, + kSoundStatusRemoved = 0x200, + + kSoundStatus_8000 = 0x8000, + kSoundStatus_100000 = 0x100000, + kSoundStatus_40000000 = 0x40000000, + + kSoundStatusClear0 = 0x10, + kSoundStatusClear1 = 0x1F, + kSoundStatusClear2 = 0x80, + kSoundStatusClear3 = 0x200, + kSoundStatusClear4 = 0x800, + kSoundStatusClearAll = 0xFFFFFFE0 + }; + + enum SoundState { + kSoundState0 = 0, + kSoundState1 = 1, + kSoundState2 = 2 + }; + + union SoundStatusUnion { + uint32 status; + byte status1; + byte status2; + byte status3; + byte status4; + + SoundStatusUnion() { + status = 0; + } + }; + + struct SoundEntry { + SoundStatusUnion status; + SoundType type; // int + //int field_8; + //int field_C; + //int field_10; + //int fileData; + //int field_18; + //int field_1C; + //int field_20; + //int field_24; + //int field_28; + Common::SeekableReadStream *stream; // int + //int field_30; + //int field_34; + //int field_38; + int field_3C; + int field_40; + EntityIndex entity; + int field_48; + int field_4C; + Common::String name1; //char[16]; + Common::String name2; //char[16]; + //int next; // offset to the next structure in the list (not used) + SubtitleManager *subtitle; + + bool isStreamed; // TEMPORARY + + SoundEntry() { + type = kSoundTypeNone; + stream = NULL; + field_3C = 0; + field_40 = 0; + entity = kEntityPlayer; + field_48 = 0; + field_4C = 0; + + subtitle = NULL; + + isStreamed = false; + }; + }; + + // Engine + LastExpressEngine* _engine; + + // State flag + int _state; + SoundType _currentType; + + // Sound stream + StreamedSound *_soundStream; + + // Unknown data + uint32 _data0; + uint32 _data1; + uint32 _data2; + uint32 _flag; + + // Filters + + int32 _buffer[2940]; ///< Static sound buffer + + // Compartment warnings by Mertens or Coudert + uint32 _lastWarning[12]; + + // Looping sound + void playLoopingSound(); + + // Sound cache + Common::List<SoundEntry *> _cache; + + SoundEntry *getEntry(EntityIndex index); + SoundEntry *getEntry(Common::String name); + SoundEntry *getEntry(SoundType type); + + void setupEntry(SoundEntry *entry, Common::String name, FlagType flag, int a4); + void setEntryType(SoundEntry *entry, FlagType flag); + void setEntryStatus(SoundEntry *entry, FlagType flag) const; + bool setupCache(SoundEntry *entry); + void loadSoundData(SoundEntry *entry, Common::String name); + + void updateEntry(SoundEntry *entry, uint value) const; + void updateEntryState(SoundEntry *entry) const ; + void resetEntry(SoundEntry *entry) const; + void removeEntry(SoundEntry *entry); + + // Subtitles + void showSubtitles(SoundEntry *entry, Common::String filename); + void drawSubtitles(SubtitleManager *subtitle); + + // Sound filter + void applyFilter(SoundEntry *entry, SoundBuffer buffer); +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SOUND_H diff --git a/engines/lastexpress/game/state.cpp b/engines/lastexpress/game/state.cpp new file mode 100644 index 0000000000..8675f6e312 --- /dev/null +++ b/engines/lastexpress/game/state.cpp @@ -0,0 +1,74 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/game/state.h" + +#include "lastexpress/game/inventory.h" +#include "lastexpress/game/object.h" +#include "lastexpress/game/savepoint.h" + +#include "lastexpress/lastexpress.h" + +namespace LastExpress { + +State::State(LastExpressEngine *engine) : _engine(engine), _timer(0) { + _inventory = new Inventory(engine); + _objects = new Objects(engine); + _savepoints = new SavePoints(engine); + _state = new GameState(); + _flags = new Flags(); +} + +State::~State() { + delete _inventory; + delete _objects; + delete _savepoints; + delete _state; + delete _flags; + + // Zero passed pointers + _engine = NULL; +} + +bool State::isNightTime() const { + return (_state->progress.chapter == kChapter1 + || _state->progress.chapter == kChapter4 + || (_state->progress.chapter == kChapter5 && !_state->progress.isNightTime)); +} + +uint32 State::getPowerOfTwo(uint32 x) { + if (!x || (x & 1)) + return 0; + + uint32 num = 0; + do { + x >>= 1; + num++; + } while ((x & 1) == 0); + + return num; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/game/state.h b/engines/lastexpress/game/state.h new file mode 100644 index 0000000000..a8ddfc7051 --- /dev/null +++ b/engines/lastexpress/game/state.h @@ -0,0 +1,593 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_STATE_H +#define LASTEXPRESS_STATE_H + +#include "lastexpress/shared.h" + +#include "common/system.h" + +namespace LastExpress { + +class LastExpressEngine; + +class Inventory; +class Objects; +class SavePoints; + +class State { +public: + struct GameProgress { + uint32 field_0; + JacketType jacket; + bool eventCorpseMovedFromFloor; + uint32 field_C; + bool eventCorpseFound; + uint32 field_14; ///< EntityIndex (used in Gendarmes) + uint32 field_18; + uint32 portrait; + bool eventCorpseThrown; + uint32 field_24; + uint32 field_28; + ChapterIndex chapter; + uint32 field_30; + bool eventMetAugust; + bool isNightTime; + uint32 field_3C; + uint32 field_40; + uint32 field_44; + uint32 field_48; + uint32 field_4C; + bool isTrainRunning; + uint32 field_54; + uint32 field_58; + uint32 field_5C; + uint32 field_60; + uint32 field_64; + uint32 field_68; + bool eventMertensAugustWaiting; + bool eventMertensKronosInvitation; + bool isEggOpen; + uint32 field_78; // time? + uint32 field_7C; + uint32 field_80; + uint32 field_84; + uint32 field_88; + uint32 field_8C; + uint32 field_90; + uint32 field_94; + uint32 field_98; + uint32 field_9C; + uint32 field_A0; + uint32 field_A4; + uint32 field_A8; + uint32 field_AC; + uint32 field_B0; + uint32 field_B4; + uint32 field_B8; + uint32 field_BC; + uint32 field_C0; + uint32 field_C4; + uint32 field_C8; + uint32 field_CC; + bool eventMetBoutarel; + bool eventMetHadija; + bool eventMetYasmin; + uint32 field_DC; + uint32 field_E0; + uint32 field_E4; + uint32 field_E8; + uint32 field_EC; + uint32 field_F0; + uint32 field_F4; + uint32 field_F8; + uint32 field_FC; + uint32 field_100; + uint32 field_104; + uint32 field_108; + uint32 field_10C; + uint32 field_110; + uint32 field_114; + uint32 field_118; + uint32 field_11C; + uint32 field_120; + uint32 field_124; + uint32 field_128; + uint32 field_12C; + uint32 field_130; + uint32 field_134; + uint32 field_138; + uint32 field_13C; + uint32 field_140; + uint32 field_144; + uint32 field_148; + uint32 field_14C; + uint32 field_150; + uint32 field_154; + uint32 field_158; + uint32 field_15C; + uint32 field_160; + uint32 field_164; + uint32 field_168; + uint32 field_16C; + uint32 field_170; + uint32 field_174; + uint32 field_178; + uint32 field_17C; + uint32 field_180; + uint32 field_184; + uint32 field_188; + uint32 field_18C; + uint32 field_190; + uint32 field_194; + uint32 field_198; + uint32 field_19C; + uint32 field_1A0; + uint32 field_1A4; + uint32 field_1A8; + uint32 field_1AC; + uint32 field_1B0; + uint32 field_1B4; + uint32 field_1B8; + uint32 field_1BC; + uint32 field_1C0; + uint32 field_1C4; + uint32 field_1C8; + uint32 field_1CC; + uint32 field_1D0; + uint32 field_1D4; + uint32 field_1D8; + uint32 field_1DC; + uint32 field_1E0; + uint32 field_1E4; + uint32 field_1E8; + uint32 field_1EC; + uint32 field_1F0; + uint32 field_1F4; + uint32 field_1F8; + uint32 field_1FC; + + GameProgress() { + field_0 = 0; + jacket = kJacketOriginal; + eventCorpseMovedFromFloor = false; + field_C = 0; + eventCorpseFound = false; + field_14 = 0; // 5 + field_18 = 0; + portrait = _defaultPortrait; + eventCorpseThrown = false; + field_24 = 0; + field_28 = 0; // 10 + chapter = kChapter1; + field_30 = 0; + eventMetAugust = false; + isNightTime = false; + field_3C = 0; // 15 + field_40 = 0; + field_44 = 0; + field_48 = 0; + field_4C = 0; + isTrainRunning = false; // 20 + field_54 = 0; + field_58 = 0; + field_5C = 0; + field_60 = 0; + field_64 = 0; // 25 + field_68 = 0; + eventMertensAugustWaiting = false; + eventMertensKronosInvitation = false; + isEggOpen = false; + field_78 = 0; // 30 + field_7C = 0; + field_80 = 0; + field_84 = 0; + field_88 = 0; + field_8C = 0; // 35 + field_90 = 0; + field_94 = 0; + field_98 = 0; + field_9C = 0; + field_A0 = 0; // 40 + field_A4 = 0; + field_A8 = 0; + field_AC = 0; + field_B0 = 0; + field_B4 = 0; // 45 + field_B8 = 0; + field_BC = 0; + field_C0 = 0; + field_C4 = 0; + field_C8 = 0; // 50 + field_CC = 0; + eventMetBoutarel = false; + eventMetHadija = false; + eventMetYasmin = false; + field_DC = 0; // 55 + field_E0 = 0; + field_E4 = 0; + field_E8 = 0; + field_EC = 0; + field_F0 = 0; // 60 + field_F4 = 0; + field_F8 = 0; + field_FC = 0; + field_100 = 0; + field_104 = 0; // 65 + field_108 = 0; + field_10C = 0; + field_110 = 0; + field_114 = 0; + field_118 = 0; // 70 + field_11C = 0; + field_120 = 0; + field_124 = 0; + field_128 = 0; + field_12C = 0; // 75 + field_130 = 0; + field_134 = 0; + field_138 = 0; + field_13C = 0; + field_140 = 0; // 80 + field_144 = 0; + field_148 = 0; + field_14C = 0; + field_150 = 0; + field_154 = 0; // 85 + field_158 = 0; + field_15C = 0; + field_160 = 0; + field_164 = 0; + field_168 = 0; // 90 + field_16C = 0; + field_170 = 0; + field_174 = 0; + field_178 = 0; + field_17C = 0; // 95 + field_180 = 0; + field_184 = 0; + field_188 = 0; + field_18C = 0; + field_190 = 0; // 100 + field_194 = 0; + field_198 = 0; + field_19C = 0; + field_1A0 = 0; + field_1A4 = 0; // 105 + field_1A8 = 0; + field_1AC = 0; + field_1B0 = 0; + field_1B4 = 0; + field_1B8 = 0; // 110 + field_1BC = 0; + field_1C0 = 0; + field_1C4 = 0; + field_1C8 = 0; + field_1CC = 0; // 115 + field_1D0 = 0; + field_1D4 = 0; + field_1D8 = 0; + field_1DC = 0; + field_1E0 = 0; // 120 + field_1E4 = 0; + field_1E8 = 0; + field_1EC = 0; + field_1F0 = 0; + field_1F4 = 0; // 125 + field_1F8 = 0; + field_1FC = 0; + } + + /** + * Query if if a progress value is equal to the specified value. + * + * Note: This is necessary because we store different types in the progress structure + * and need to test a value based on an index in Action::getCursor() + * + * @param index Zero-based index of the progress structure entry + * @param val The value. + * + * @return true if equal, false if not. + */ + bool isEqual(uint index, uint val) { + #define EXPOSE_VALUE(idx, name) case idx: return ((uint)name == val); + + switch (index) { + default: + error("GameProgress::isEqual: invalid index value (was: %d, max:127)", index); + break; + + EXPOSE_VALUE(0, field_0); + EXPOSE_VALUE(1, jacket); + EXPOSE_VALUE(2, eventCorpseMovedFromFloor); + EXPOSE_VALUE(3, field_C); + EXPOSE_VALUE(4, eventCorpseFound); + EXPOSE_VALUE(5, field_14); + EXPOSE_VALUE(6, field_18); + EXPOSE_VALUE(7, portrait); + EXPOSE_VALUE(8, eventCorpseThrown); + EXPOSE_VALUE(9, field_24); + EXPOSE_VALUE(10, field_28); + EXPOSE_VALUE(11, chapter); + EXPOSE_VALUE(12, field_30); + EXPOSE_VALUE(13, eventMetAugust); + EXPOSE_VALUE(14, isNightTime); + EXPOSE_VALUE(15, field_3C); + EXPOSE_VALUE(16, field_40); + EXPOSE_VALUE(17, field_44); + EXPOSE_VALUE(18, field_48); + EXPOSE_VALUE(19, field_4C); + EXPOSE_VALUE(20, isTrainRunning); + EXPOSE_VALUE(21, field_54); + EXPOSE_VALUE(22, field_58); + EXPOSE_VALUE(23, field_5C); + EXPOSE_VALUE(24, field_60); + EXPOSE_VALUE(25, field_64); + EXPOSE_VALUE(26, field_68); + EXPOSE_VALUE(27, eventMertensAugustWaiting); + EXPOSE_VALUE(28, eventMertensKronosInvitation); + EXPOSE_VALUE(29, isEggOpen); + EXPOSE_VALUE(30, field_78); + EXPOSE_VALUE(31, field_7C); + EXPOSE_VALUE(32, field_80); + EXPOSE_VALUE(33, field_84); + EXPOSE_VALUE(34, field_88); + EXPOSE_VALUE(35, field_8C); + EXPOSE_VALUE(36, field_90); + EXPOSE_VALUE(37, field_94); + EXPOSE_VALUE(38, field_98); + EXPOSE_VALUE(39, field_9C); + EXPOSE_VALUE(40, field_A0); + EXPOSE_VALUE(41, field_A4); + EXPOSE_VALUE(42, field_A8); + EXPOSE_VALUE(43, field_AC); + EXPOSE_VALUE(44, field_B0); + EXPOSE_VALUE(45, field_B4); + EXPOSE_VALUE(46, field_B8); + EXPOSE_VALUE(47, field_BC); + EXPOSE_VALUE(48, field_C0); + EXPOSE_VALUE(49, field_C4); + EXPOSE_VALUE(50, field_C8); + EXPOSE_VALUE(51, field_CC); + EXPOSE_VALUE(52, eventMetBoutarel); + EXPOSE_VALUE(53, eventMetHadija); + EXPOSE_VALUE(54, eventMetYasmin); + EXPOSE_VALUE(55, field_DC); + EXPOSE_VALUE(56, field_E0); + EXPOSE_VALUE(57, field_E4); + EXPOSE_VALUE(58, field_E8); + EXPOSE_VALUE(59, field_EC); + EXPOSE_VALUE(60, field_F0); + EXPOSE_VALUE(61, field_F4); + EXPOSE_VALUE(62, field_F8); + EXPOSE_VALUE(63, field_FC); + EXPOSE_VALUE(64, field_100); + EXPOSE_VALUE(65, field_104); + EXPOSE_VALUE(66, field_108); + EXPOSE_VALUE(67, field_10C); + EXPOSE_VALUE(68, field_110); + EXPOSE_VALUE(69, field_114); + EXPOSE_VALUE(70, field_118); + EXPOSE_VALUE(71, field_11C); + EXPOSE_VALUE(72, field_120); + EXPOSE_VALUE(73, field_124); + EXPOSE_VALUE(74, field_128); + EXPOSE_VALUE(75, field_12C); + EXPOSE_VALUE(76, field_130); + EXPOSE_VALUE(77, field_134); + EXPOSE_VALUE(78, field_138); + EXPOSE_VALUE(79, field_13C); + EXPOSE_VALUE(80, field_140); + EXPOSE_VALUE(81, field_144); + EXPOSE_VALUE(82, field_148); + EXPOSE_VALUE(83, field_14C); + EXPOSE_VALUE(84, field_150); + EXPOSE_VALUE(85, field_154); + EXPOSE_VALUE(86, field_158); + EXPOSE_VALUE(87, field_15C); + EXPOSE_VALUE(88, field_160); + EXPOSE_VALUE(89, field_164); + EXPOSE_VALUE(90, field_168); + EXPOSE_VALUE(91, field_16C); + EXPOSE_VALUE(92, field_170); + EXPOSE_VALUE(93, field_174); + EXPOSE_VALUE(94, field_178); + EXPOSE_VALUE(95, field_17C); + EXPOSE_VALUE(96, field_180); + EXPOSE_VALUE(97, field_184); + EXPOSE_VALUE(98, field_188); + EXPOSE_VALUE(99, field_18C); + EXPOSE_VALUE(100, field_190); + EXPOSE_VALUE(101, field_194); + EXPOSE_VALUE(102, field_198); + EXPOSE_VALUE(103, field_19C); + EXPOSE_VALUE(104, field_1A0); + EXPOSE_VALUE(105, field_1A4); + EXPOSE_VALUE(106, field_1A8); + EXPOSE_VALUE(107, field_1AC); + EXPOSE_VALUE(108, field_1B0); + EXPOSE_VALUE(109, field_1B4); + EXPOSE_VALUE(110, field_1B8); + EXPOSE_VALUE(111, field_1BC); + EXPOSE_VALUE(112, field_1C0); + EXPOSE_VALUE(113, field_1C4); + EXPOSE_VALUE(114, field_1C8); + EXPOSE_VALUE(115, field_1CC); + EXPOSE_VALUE(116, field_1D0); + EXPOSE_VALUE(117, field_1D4); + EXPOSE_VALUE(118, field_1D8); + EXPOSE_VALUE(119, field_1DC); + EXPOSE_VALUE(120, field_1E0); + EXPOSE_VALUE(121, field_1E4); + EXPOSE_VALUE(122, field_1E8); + EXPOSE_VALUE(123, field_1EC); + EXPOSE_VALUE(124, field_1F0); + EXPOSE_VALUE(125, field_1F4); + EXPOSE_VALUE(126, field_1F8); + EXPOSE_VALUE(127, field_1FC); + } + } + }; + + struct GameState { + // Header + uint32 brightness; + uint32 volume; + + // Game data + uint32 field_0; + uint32 time; + uint32 timeDelta; + uint32 timeTicks; + bool sceneUseBackup; // byte + SceneIndex scene; // uint32 + SceneIndex sceneBackup; // uint32 + SceneIndex sceneBackup2; // uin32 + + GameProgress progress; + byte events[512]; + + GameState() { + brightness = _defaultBrigthness; + volume = _defaultVolume; + + //Game data + time = _defaultTime; + timeDelta = _defaultTimeDelta; + timeTicks = 0; + sceneUseBackup = false; + scene = kSceneDefault; + sceneBackup = kSceneNone; + sceneBackup2 = kSceneNone; + + // Clear game events + memset(events, 0, 512*sizeof(byte)); + } + + /** + * Convert this object into a string representation. + * + * @return A string representation of this object. + */ + Common::String toString() { + Common::String ret = ""; + + ret += Common::String::printf("Time: %d - Time delta: %d - Ticks: %d\n", time, timeDelta, timeTicks); + ret += Common::String::printf("Brightness: %d - Volume: %d - UseBackup: %d\n", brightness, volume, sceneUseBackup); + ret += Common::String::printf("Scene: %d - Scene backup: %d - Scene backup 2: %d\n", scene, sceneBackup, sceneBackup2); + + return ret; + } + }; + + struct Flags { + bool flag_0; + bool flag_3; + bool flag_4; + bool flag_5; + + bool frameInterval; + + bool isGameRunning; + + // Mouse flags + bool mouseLeftClick; + bool mouseRightClick; + + bool flag_entities_0; + bool flag_entities_1; + + bool shouldRedraw; + bool shouldDrawEggOrHourGlass; + + Flags() { + flag_0 = false; + flag_3 = false; + flag_4 = false; + flag_5 = false; + + frameInterval = false; + + isGameRunning = false; + + mouseRightClick = false; + mouseLeftClick = false; + + flag_entities_0 = false; + flag_entities_1 = false; + + shouldRedraw = false; + shouldDrawEggOrHourGlass = false; + } + }; + + State(LastExpressEngine *engine); + ~State(); + + // Accessors + Inventory *getGameInventory() { return _inventory; } + Objects *getGameObjects() { return _objects; } + SavePoints *getGameSavePoints() { return _savepoints; } + GameState *getGameState() { return _state; } + Flags *getGameFlags() { return _flags; } + + // Time checks + bool isNightTime() const; + + // Timer + int getTimer() { return _timer; } + void setTimer(int val) { _timer = val; } + + // Coordinates + void setCoordinates(Common::Point coords) { _coords = coords; } + const Common::Point getCoordinates() { return _coords; } + + // Helpers + static uint32 getPowerOfTwo(uint32 x); + +private: + static const uint32 _defaultBrigthness = 0x3; + static const uint32 _defaultVolume = 0x7; + static const uint32 _defaultTime = 1037700; + static const uint32 _defaultTimeDelta = 3; + static const uint32 _defaultPortrait = 32; + + LastExpressEngine *_engine; + + // Timer + int _timer; + + Flags *_flags; ///< Flags + Inventory *_inventory; ///< Inventory + Objects *_objects; ///< Objects + SavePoints *_savepoints; ///< SavePoints + GameState *_state; ///< State + Common::Point _coords; ///< Current coordinates +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_STATE_H diff --git a/engines/lastexpress/graphics.cpp b/engines/lastexpress/graphics.cpp new file mode 100644 index 0000000000..e5a69d16ea --- /dev/null +++ b/engines/lastexpress/graphics.cpp @@ -0,0 +1,166 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/graphics.h" + +#include "common/system.h" + +namespace LastExpress { + +#define COLOR_KEY 0xFFFF + +GraphicsManager::GraphicsManager() : _changed(false) { + _screen.create(640, 480, 2); + + // Create the game surfaces + _backgroundA.create(640, 480, 2); + _backgroundC.create(640, 480, 2); + _overlay.create(640, 480, 2); + _inventory.create(640, 480, 2); + + clear(kBackgroundAll); +} + +GraphicsManager::~GraphicsManager() { + // Free the game surfaces + _screen.free(); + _backgroundA.free(); + _backgroundC.free(); + _overlay.free(); + _inventory.free(); +} + +void GraphicsManager::update() { + // Update the screen if needed and reset the status + if (_changed) { + mergePlanes(); + updateScreen(); + _changed = false; + } +} + +void GraphicsManager::change() { + _changed = true; +} + +void GraphicsManager::clear(BackgroundType type) { + clear(type, Common::Rect(640, 480)); +} + +void GraphicsManager::clear(BackgroundType type, const Common::Rect &rect) { + switch (type) { + default: + error("GraphicsManager::clear() - Unknown background type: %d", type); + break; + + case kBackgroundA: + case kBackgroundC: + case kBackgroundOverlay: + case kBackgroundInventory: + getSurface(type)->fillRect(rect, COLOR_KEY); + break; + + case kBackgroundAll: + _backgroundA.fillRect(rect, COLOR_KEY); + _backgroundC.fillRect(rect, COLOR_KEY); + _overlay.fillRect(rect, COLOR_KEY); + _inventory.fillRect(rect, COLOR_KEY); + break; + } +} + +bool GraphicsManager::draw(Drawable *drawable, BackgroundType type, bool transition) { + // TODO handle transition properly + if (transition) + clear(type); + + // TODO store rect for later use + Common::Rect rect = drawable->draw(getSurface(type)); + + return (!rect.isEmpty()); +} + +Graphics::Surface *GraphicsManager::getSurface(BackgroundType type) { + switch (type) { + default: + error("GraphicsManager::getSurface() - Unknown surface type: %d", type); + break; + + case kBackgroundA: + return &_backgroundA; + + case kBackgroundC: + return &_backgroundC; + + case kBackgroundOverlay: + return &_overlay; + + case kBackgroundInventory: + return &_inventory; + + case kBackgroundAll: + error("GraphicsManager::getSurface() - cannot return a surface for kBackgroundAll!"); + break; + } +} + +// TODO optimize to only merge dirty rects +void GraphicsManager::mergePlanes() { + // Clear screen surface + _screen.fillRect(Common::Rect(640, 480), 0); + + uint16 *screen = (uint16 *)_screen.pixels; + uint16 *inventory = (uint16 *)_inventory.pixels; + uint16 *overlay = (uint16 *)_overlay.pixels; + uint16 *backgroundC = (uint16 *)_backgroundC.pixels; + uint16 *backgroundA = (uint16 *)_backgroundA.pixels; + + for (int i = 0; i < 640 * 480; i++) { + + if (*inventory != COLOR_KEY) + *screen = *inventory; + else if (*overlay != COLOR_KEY) + *screen = *overlay; + else if (*backgroundA != COLOR_KEY) + *screen = *backgroundA; + else if (*backgroundC != COLOR_KEY) + *screen = *backgroundC; + else + *screen = 0; + + inventory++; + screen++; + overlay++; + backgroundA++; + backgroundC++; + } +} + +void GraphicsManager::updateScreen() { + g_system->fillScreen(0); + g_system->copyRectToScreen((byte *)_screen.getBasePtr(0, 0), 640 * 2, 0, 0, 640, 480); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/graphics.h b/engines/lastexpress/graphics.h new file mode 100644 index 0000000000..5231d00f6f --- /dev/null +++ b/engines/lastexpress/graphics.h @@ -0,0 +1,76 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_GRAPHICS_H +#define LASTEXPRESS_GRAPHICS_H + +#include "lastexpress/drawable.h" + +namespace LastExpress { + +class GraphicsManager { +public: + enum BackgroundType { + kBackgroundA, + kBackgroundC, + kBackgroundOverlay, + kBackgroundInventory, + kBackgroundAll + }; + + GraphicsManager(); + ~GraphicsManager(); + + // Update the screen + void update(); + + // Signal a change to the screen, will cause the planes to be remerged + void change(); + + // Clear some screen parts + void clear(BackgroundType type); + void clear(BackgroundType type, const Common::Rect &rect); + + // FIXME this is there for animation until we change it to use the graphic surface here instead of its private ones. + Graphics::Surface _screen; // Actual screen surface + + bool draw(Drawable *drawable, BackgroundType type, bool transition = false); + +private: + Graphics::Surface _backgroundA; // Background A + Graphics::Surface _backgroundC; // Background C + Graphics::Surface _overlay; // Overlay + Graphics::Surface _inventory; // Overlay + + void mergePlanes(); + void updateScreen(); + Graphics::Surface *getSurface(BackgroundType type); + + bool _changed; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_GRAPHICS_H diff --git a/engines/lastexpress/helpers.h b/engines/lastexpress/helpers.h new file mode 100644 index 0000000000..eb54a1a3ce --- /dev/null +++ b/engines/lastexpress/helpers.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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_HELPERS_H +#define LASTEXPRESS_HELPERS_H + +////////////////////////////////////////////////////////////////////////// +// Misc helpers +////////////////////////////////////////////////////////////////////////// + +// Misc +#define getArchive(name) _engine->getResourceManager()->getFileStream(name) +#define rnd(value) _engine->getRandom().getRandomNumber(value - 1) + +// Engine subclasses +#define getLogic() _engine->getGameLogic() +#define getMenu() _engine->getGameMenu() + +// Logic +#define getAction() getLogic()->getGameAction() +#define getBeetle() getLogic()->getGameBeetle() +#define getFight() getLogic()->getGameFight() +#define getEntities() getLogic()->getGameEntities() +#define getSaveLoad() getLogic()->getGameSaveLoad() +#define isNight() getLogic()->getGameState()->isNightTime() + +// State +#define getState() getLogic()->getGameState()->getGameState() +#define getEvent(id) getState()->events[id] +#define getFlags() getLogic()->getGameState()->getGameFlags() +#define getInventory() getLogic()->getGameState()->getGameInventory() +#define getObjects() getLogic()->getGameState()->getGameObjects() +#define getProgress() getState()->progress +#define getSavePoints() getLogic()->getGameState()->getGameSavePoints() +#define getGlobalTimer() getLogic()->getGameState()->getTimer() +#define setGlobalTimer(timer) getLogic()->getGameState()->setTimer(timer) +#define setCoords(coords) getLogic()->getGameState()->setCoordinates(coords) +#define getCoords() getLogic()->getGameState()->getCoordinates() +#define setFrameCount(count) _engine->setFrameCounter(count) +#define getFrameCount() _engine->getFrameCounter() + +// Scenes +#define getScenes() _engine->getSceneManager() + +// Sound +#define getSound() _engine->getSoundManager() + +// Others +#define getEntityData(entity) getEntities()->getData(entity) + +////////////////////////////////////////////////////////////////////////// +// Graphics +////////////////////////////////////////////////////////////////////////// + +// Sequences +#define loadSequence(name) Sequence::load(name, getArchive(name)) +#define loadSequence1(name, field30) Sequence::load(name, getArchive(name), field30) + +#define clearBg(type) _engine->getGraphicsManager()->clear(type) +#define showScene(index, type) _engine->getGraphicsManager()->draw(getScenes()->get(index), type); + +#define askForRedraw() _engine->getGraphicsManager()->change(); +#define redrawScreen() _engine->getGraphicsManager()->update(); _engine->_system->updateScreen(); + +// Used to delete entity sequences +#define SAFE_DELETE(_p) { if(_p) { delete (_p); (_p) = NULL; } } + +////////////////////////////////////////////////////////////////////////// +// Output +////////////////////////////////////////////////////////////////////////// +extern const char *g_actionNames[]; +extern const char *g_directionNames[]; +extern const char *g_entityNames[]; + +#define ACTION_NAME(action) (action > 18 ? Common::String::printf("%d", action).c_str() : g_actionNames[action]) +#define DIRECTION_NAME(direction) (direction >= 6 ? "INVALID" : g_directionNames[direction]) +#define ENTITY_NAME(index) (index >= 40 ? "INVALID" : g_entityNames[index]) + + +#endif // LASTEXPRESS_HELPERS_H diff --git a/engines/lastexpress/lastexpress.cpp b/engines/lastexpress/lastexpress.cpp new file mode 100644 index 0000000000..2ccdc14fbd --- /dev/null +++ b/engines/lastexpress/lastexpress.cpp @@ -0,0 +1,310 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/lastexpress.h" + +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/font.h" + +#include "lastexpress/game/logic.h" +#include "lastexpress/game/menu.h" +#include "lastexpress/game/scenes.h" +#include "lastexpress/game/state.h" +#include "lastexpress/game/sound.h" + +#include "lastexpress/graphics.h" +#include "lastexpress/helpers.h" +#include "lastexpress/resource.h" + +#include "common/config-manager.h" +#include "common/debug-channels.h" +#include "common/EventRecorder.h" + +#include "engines/util.h" + +const char *g_actionNames[] = {"None", "Action1", "Action2", "ExitCompartment", "Action4", "ExcuseMeCath", "ExcuseMe", "INVALID", "Knock", "OpenDoor", "Action10", "Action11", "Default", "INVALID", "INVALID", "INVALID", "Action16", "DrawScene", "Callback"}; +const char *g_directionNames[] = { "None", "Up", "Down", "Left", "Right", "Switch"}; +const char *g_entityNames[] = { "Player", "Anna", "August", "Mertens", "Coudert", "Pascale", "Servers0", "Servers1", "Cooks", "Verges", "Tatiana", "Vassili", "Alexei", "Abbot", "Milos", "Vesna", "Ivo", "Salko", "Kronos", "Kahina", "Francois", "MmeBoutarel", "Boutarel", "Rebecca", "Sophie", "Mahmud", "Yasmin", "Hadija", "Alouan", "Gendarmes", "Max", "Chapters", "Train", "Tables0", "Tables1", "Tables2", "Tables3", "Tables4", "Tables5", "Entity39"}; + + +namespace LastExpress { + +LastExpressEngine::LastExpressEngine(OSystem *syst, const ADGameDescription *gd) : + Engine(syst), _gameDescription(gd), _debugger(NULL), _cursor(NULL), + _font(NULL), _logic(NULL), _menu(NULL), _frameCounter(0), _lastFrameCount(0), + _graphicsMan(NULL), _resMan(NULL), _sceneMan(NULL), _soundMan(NULL), + eventMouse(NULL), eventTick(NULL), eventMouseBackup(NULL), eventTickBackup(NULL) { + + // Adding the default directories + const Common::FSNode gameDataDir(ConfMan.get("path")); + SearchMan.addSubDirectoryMatching(gameDataDir, "data"); + + // Initialize the custom debug levels + DebugMan.addDebugChannel(kLastExpressDebugAll, "All", "Debug everything"); + DebugMan.addDebugChannel(kLastExpressDebugGraphics, "Graphics", "Debug graphics & animation/sequence playback"); + DebugMan.addDebugChannel(kLastExpressDebugResource, "Resource", "Debug resource management"); + DebugMan.addDebugChannel(kLastExpressDebugCursor, "Cursor", "Debug cursor handling"); + DebugMan.addDebugChannel(kLastExpressDebugSound, "Sound", "Debug sound playback"); + DebugMan.addDebugChannel(kLastExpressDebugSubtitle, "Subtitle", "Debug subtitles"); + DebugMan.addDebugChannel(kLastExpressDebugSavegame, "Savegame", "Debug savegames"); + DebugMan.addDebugChannel(kLastExpressDebugLogic, "Logic", "Debug logic"); + DebugMan.addDebugChannel(kLastExpressDebugScenes, "Scenes", "Debug scenes & hotspots"); + DebugMan.addDebugChannel(kLastExpressDebugUnknown, "Unknown", "Debug unknown data"); + + g_eventRec.registerRandomSource(_random, "lastexpress"); +} + +LastExpressEngine::~LastExpressEngine() { + _timer->removeTimerProc(&soundTimer); + + // Delete the remaining objects + delete _cursor; + delete _font; + delete _logic; + delete _menu; + delete _graphicsMan; + delete _resMan; + delete _sceneMan; + delete _soundMan; + delete _debugger; + + // Cleanup event handlers + SAFE_DELETE(eventMouse); + SAFE_DELETE(eventTick); + SAFE_DELETE(eventMouseBackup); + SAFE_DELETE(eventTickBackup); + + // Zero passed pointers + _gameDescription = NULL; +} + +// TODO: which error should we return when some game files are missing/corrupted? +Common::Error LastExpressEngine::run() { + // Initialize the graphics + const Graphics::PixelFormat dataPixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0); + initGraphics(640, 480, true, &dataPixelFormat); + + // We do not support color conversion + if (_system->getScreenFormat() != dataPixelFormat) + return Common::kUnsupportedColorMode; + + // Create debugger. It requires GFX to be initialized + _debugger = new Debugger(this); + + // Start the resource and graphics managers + _resMan = new ResourceManager(isDemo()); + if (!_resMan->loadArchive(kArchiveCd1)) + return Common::kNoGameDataFoundError; + + _graphicsMan = new GraphicsManager(); + + // Load the cursor data + _cursor = _resMan->loadCursor(); + if (!_cursor) + return Common::kNoGameDataFoundError; + + // Load the font data + _font = _resMan->loadFont(); + if (!_font) + return Common::kNoGameDataFoundError; + + // Start scene manager + _sceneMan = new SceneManager(this); + _sceneMan->loadSceneDataFile(kArchiveCd1); + + // Game logic + _logic = new Logic(this); + + // Start sound manager and setup timer + _soundMan = new SoundManager(this); + _timer->installTimerProc(&soundTimer, 17, this); + + // Menu + _menu = new Menu(this); + _menu->show(false, kSavegameTypeIndex, 0); + + while (!shouldQuit()) { + _soundMan->updateQueue(); + _soundMan->updateSubtitles(); + + if (handleEvents()) + continue; + } + + return Common::kNoError; +} + +void LastExpressEngine::pollEvents() { + Common::Event ev; + _eventMan->pollEvent(ev); + + switch (ev.type) { + + case Common::EVENT_LBUTTONUP: + getGameLogic()->getGameState()->getGameFlags()->mouseLeftClick = true; + break; + + case Common::EVENT_RBUTTONUP: + getGameLogic()->getGameState()->getGameFlags()->mouseRightClick = true; + break; + + default: + break; + } +} + +bool LastExpressEngine::handleEvents() { + // Make sure all the subsystems have been initialized + if (!_debugger || !_graphicsMan) + error("LastExpressEngine::handleEvents: called before the required subsystems have been initialized!"); + + // Execute stored commands + if (_debugger->hasCommand()) { + _debugger->callCommand(); + + // re-attach the debugger + _debugger->attach(); + } + + // Show the debugger if required + _debugger->onFrame(); + + // Handle input + Common::Event ev; + while (_eventMan->pollEvent(ev)) { + switch (ev.type) { + + case Common::EVENT_KEYDOWN: + // CTRL-D: Attach the debugger + if ((ev.kbd.flags & Common::KBD_CTRL) && ev.kbd.keycode == Common::KEYCODE_d) + _debugger->attach(); + + //// DEBUG: Quit game on escape + //if (ev.kbd.keycode == Common::KEYCODE_ESCAPE) + // quitGame(); + + break; + + case Common::EVENT_MAINMENU: + // Closing the GMM + + case Common::EVENT_LBUTTONUP: + getGameLogic()->getGameState()->getGameFlags()->mouseLeftClick = true; + + // Adjust frameInterval flag + if (_frameCounter < _lastFrameCount + 30) + getGameLogic()->getGameState()->getGameFlags()->frameInterval = true; + _lastFrameCount = _frameCounter; + + if (eventMouse && eventMouse->isValid()) + (*eventMouse)(ev); + break; + + case Common::EVENT_RBUTTONUP: + getGameLogic()->getGameState()->getGameFlags()->mouseRightClick = true; + if (eventMouse && eventMouse->isValid()) + (*eventMouse)(ev); + break; + + case Common::EVENT_MOUSEMOVE: + if (eventMouse && eventMouse->isValid()) + (*eventMouse)(ev); + break; + + case Common::EVENT_QUIT: + quitGame(); + break; + + default: + break; + } + } + + // Game tick event + if (eventTick && eventTick->isValid()) + (*eventTick)(ev); + + // Update the screen + _graphicsMan->update(); + _system->updateScreen(); + _system->delayMillis(50); + + // The event loop may have triggered the quit status. In this case, + // stop the execution. + if (shouldQuit()) { + return true; + } + + return false; +} + +/////////////////////////////////////////////////////////////////////////////////// +/// Timer +/////////////////////////////////////////////////////////////////////////////////// +void LastExpressEngine::soundTimer(void *refCon) { + ((LastExpressEngine *)refCon)->handleSoundTimer(); +} + +void LastExpressEngine::handleSoundTimer() { + if (_frameCounter & 1) + if (_soundMan) + _soundMan->handleTimer(); + + _frameCounter++; +} + +/////////////////////////////////////////////////////////////////////////////////// +/// Event Handling +/////////////////////////////////////////////////////////////////////////////////// +void LastExpressEngine::backupEventHandlers() { + eventMouseBackup = eventMouse; + eventTickBackup = eventTick; +} + +void LastExpressEngine::restoreEventHandlers() { + if (eventMouseBackup == NULL || eventTickBackup == NULL) + error("LastExpressEngine::restoreEventHandlers: restore called before backing up the event handlers!"); + + eventMouse = eventMouseBackup; + eventTick = eventTickBackup; +} + +void LastExpressEngine::setEventHandlers(EventHandler::EventFunction *mouse, EventHandler::EventFunction *tick) { + eventMouse = mouse; + eventTick = tick; +} + +/////////////////////////////////////////////////////////////////////////////////// +/// Misc Engine +/////////////////////////////////////////////////////////////////////////////////// +bool LastExpressEngine::hasFeature(EngineFeature f) const { + return (f == kSupportsRTL); +} + +void LastExpressEngine::errorString(const char *buf_input, char *buf_output, int buf_output_size) { + snprintf(buf_output, (uint)buf_output_size, "%s", buf_input); +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/lastexpress.h b/engines/lastexpress/lastexpress.h new file mode 100644 index 0000000000..8acd36f77e --- /dev/null +++ b/engines/lastexpress/lastexpress.h @@ -0,0 +1,155 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_H +#define LASTEXPRESS_H + +#include "lastexpress/debug.h" +#include "lastexpress/eventhandler.h" + +#include "common/random.h" +#include "common/timer.h" + +#include "engines/advancedDetector.h" +#include "engines/engine.h" + +#include "graphics/pixelformat.h" + +/** + * This is the namespace of the LastExpress engine. + * + * Status of this engine: + * The basic framework for the engine is done and you can navigate through the + * train and interact with objects. Some logic for characters and entities has + * been implemented, but characters do not appear in-game yet and background + * sounds and animations are missing. + * - Resources: classes for the resource formats used by the game are mostly + * complete (subtitles integration/cursor transparency are missing) + * - Display: basic graphic manager functionality is implemented (transitions + * and dirty rects handling are missing) + * - Menu/Navigation: main menu is almost complete. Navigation and hotspot + * handling is also mostly implemented (with remaining bugs) + * - Logic: the framework is in place (with helper classes mostly complete), + * and some logic has been implemented (it is mostly hardcoded in the original) + * The shared entity code for drawing/handling of entities remains to be done. + * + * Maintainers: + * clone2727, jvprat, littleboy + * + * Supported games: + * - The Last Express + */ +namespace LastExpress { + +class Cursor; +class Font; +class GraphicsManager; +class Logic; +class Menu; +class ResourceManager; +class SceneManager; +class SoundManager; + +class LastExpressEngine : public Engine { +protected: + // Engine APIs + Common::Error run(); + virtual void errorString(const char *buf_input, char *buf_output, int buf_output_size); + virtual bool hasFeature(EngineFeature f) const; + virtual Debugger *getDebugger() { return _debugger; } + +public: + LastExpressEngine(OSystem *syst, const ADGameDescription *gd); + ~LastExpressEngine(); + + // Misc + Common::RandomSource getRandom() const {return _random; } + + // Game + Cursor *getCursor() const { return _cursor; } + Font *getFont() const { return _font; } + Logic *getGameLogic() const { return _logic; } + Menu *getGameMenu() const { return _menu; } + + // Managers + GraphicsManager *getGraphicsManager() const { return _graphicsMan; } + ResourceManager *getResourceManager() const { return _resMan; } + SceneManager *getSceneManager() const { return _sceneMan; } + SoundManager *getSoundManager() const { return _soundMan; } + + // Event handling + bool handleEvents(); + void pollEvents(); + + void backupEventHandlers(); + void restoreEventHandlers(); + void setEventHandlers(EventHandler::EventFunction *eventMouse, EventHandler::EventFunction *eventTick); + + bool isDemo() const { return (bool)(_gameDescription->flags & ADGF_DEMO); } + + // Frame Counter + uint32 getFrameCounter() { return _frameCounter; } + void setFrameCounter(uint32 count) { _frameCounter = count; } + +protected: + // Sound Timer + static void soundTimer(void *ptr); + void handleSoundTimer(); + +private: + const ADGameDescription *_gameDescription; + Graphics::PixelFormat _pixelFormat; + + // Misc + Debugger *_debugger; + Common::RandomSource _random; + + // Game + Cursor *_cursor; + Font *_font; + Logic *_logic; + Menu *_menu; + + // Frame counter + uint32 _frameCounter; + uint32 _lastFrameCount; + + // Managers + GraphicsManager *_graphicsMan; + ResourceManager *_resMan; + SceneManager *_sceneMan; + SoundManager *_soundMan; + + // Event handlers + EventHandler::EventFunction *eventMouse; + EventHandler::EventFunction *eventTick; + + EventHandler::EventFunction *eventMouseBackup; + EventHandler::EventFunction *eventTickBackup; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_H diff --git a/engines/lastexpress/module.mk b/engines/lastexpress/module.mk new file mode 100644 index 0000000000..12fbb4f85b --- /dev/null +++ b/engines/lastexpress/module.mk @@ -0,0 +1,73 @@ +MODULE := engines/lastexpress + +MODULE_OBJS := \ + data/animation.o \ + data/archive.o \ + data/background.o \ + data/cursor.o \ + data/font.o \ + data/scene.o \ + data/sequence.o \ + data/snd.o \ + data/subtitle.o \ + entities/entity.o \ + entities/abbot.o \ + entities/alexei.o \ + entities/alouan.o \ + entities/anna.o \ + entities/august.o \ + entities/boutarel.o \ + entities/chapters.o \ + entities/cooks.o \ + entities/coudert.o \ + entities/entity39.o \ + entities/francois.o \ + entities/gendarmes.o \ + entities/hadija.o \ + entities/ivo.o \ + entities/kahina.o \ + entities/kronos.o \ + entities/mahmud.o \ + entities/max.o \ + entities/mertens.o \ + entities/milos.o \ + entities/mmeboutarel.o \ + entities/pascale.o \ + entities/rebecca.o \ + entities/salko.o \ + entities/servers0.o \ + entities/servers1.o \ + entities/sophie.o \ + entities/tables.o \ + entities/tatiana.o \ + entities/train.o \ + entities/vassili.o \ + entities/verges.o \ + entities/vesna.o \ + entities/yasmin.o \ + game/action.o \ + game/beetle.o \ + game/entities.o \ + game/fight.o \ + game/inventory.o \ + game/logic.o \ + game/menu.o \ + game/object.o \ + game/savegame.o \ + game/savepoint.o \ + game/scenes.o \ + game/sound.o \ + game/state.o \ + debug.o \ + detection.o \ + graphics.o \ + lastexpress.o \ + resource.o + +# This module can be built as a plugin +ifeq ($(ENABLE_LASTEXPRESS), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk
\ No newline at end of file diff --git a/engines/lastexpress/resource.cpp b/engines/lastexpress/resource.cpp new file mode 100644 index 0000000000..5628d31f94 --- /dev/null +++ b/engines/lastexpress/resource.cpp @@ -0,0 +1,248 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "lastexpress/resource.h" + +#include "lastexpress/data/background.h" +#include "lastexpress/data/cursor.h" +#include "lastexpress/data/font.h" + +#include "lastexpress/debug.h" + +#include "common/debug.h" +#include "common/file.h" + +namespace LastExpress { + +const char *archiveDemoPath = "demo.hpf"; +const char *archiveHDPath = "hd.hpf"; +const char *archiveCD1Path = "cd1.hpf"; +const char *archiveCD2Path = "cd2.hpf"; +const char *archiveCD3Path = "cd3.hpf"; + +ResourceManager::ResourceManager(bool isDemo) : _isDemo(isDemo) { +} + +ResourceManager::~ResourceManager() { + reset(); +} + +bool ResourceManager::isArchivePresent(ArchiveIndex type) { + switch (type) { + default: + case kArchiveAll: + error("ResourceManager::isArchivePresent: Only checks for single CDs are valid!"); + + case kArchiveCd1: + return Common::File::exists(archiveCD1Path); + + case kArchiveCd2: + return Common::File::exists(archiveCD2Path); + + case kArchiveCd3: + return Common::File::exists(archiveCD3Path); + } +} + +// Load a specific archive collection +// - type is ignored in the demo version +// - use ArchiveAll to load all three cds +// - HD.hpf is always loaded along with the selected archive(s) +// - will remove all other archives +bool ResourceManager::loadArchive(ArchiveIndex type) { + // Unload all archives + reset(); + + // Demo version + if (_isDemo) + return loadArchive(archiveDemoPath); + + // Load HD + if (!loadArchive(archiveHDPath)) + return false; + + switch(type) { + case kArchiveCd1: + return loadArchive(archiveCD1Path); + + case kArchiveCd2: + return loadArchive(archiveCD2Path); + + case kArchiveCd3: + return loadArchive(archiveCD3Path); + + case kArchiveAll: + default: + if (loadArchive(archiveCD1Path)) + if (loadArchive(archiveCD2Path)) + return loadArchive(archiveCD3Path); + break; + } + + return false; +} + +void ResourceManager::reset() { + // Free the loaded archives + for (Common::Array<HPFArchive *>::iterator it = _archives.begin(); it != _archives.end(); ++it) { + delete (*it); + } + _archives.clear(); +} + +bool ResourceManager::loadArchive(const Common::String &name) { + HPFArchive *archive = new HPFArchive(name); + + if (archive->count() == 0) { + debugC(2, kLastExpressDebugResource, "Error opening archive: %s", name.c_str()); + + delete archive; + + return false; + } + + _archives.push_back(archive); + + return true; +} + +// Get a stream to file in the archive +// - same as createReadStreamForMember except it checks if the file exists and will assert / output a debug message if not +Common::SeekableReadStream *ResourceManager::getFileStream(const Common::String &name) { + + // Check if the file exits in the archive + if (!hasFile(name)) { +//#ifdef _DEBUG +// error("ResourceManager::getFileStream: cannot open file: %s", name.c_str()); +//#endif + debugC(2, kLastExpressDebugResource, "Error opening file: %s", name.c_str()); + return NULL; + } + + debugC(2, kLastExpressDebugResource, "Opening file: %s", name.c_str()); + + return createReadStreamForMember(name); +} + +////////////////////////////////////////////////////////////////////////// +// Archive functions +////////////////////////////////////////////////////////////////////////// +bool ResourceManager::hasFile(const Common::String &name) { + for (Common::Array<HPFArchive *>::iterator it = _archives.begin(); it != _archives.end(); ++it) { + if ((*it)->hasFile(name)) + return true; + } + + return false; +} + +int ResourceManager::listMembers(Common::ArchiveMemberList &list) { + int count = 0; + + for (Common::Array<HPFArchive *>::iterator it = _archives.begin(); it != _archives.end(); ++it) { + + Common::ArchiveMemberList members; + count += (*it)->listMembers(members); + + list.insert(list.end(), members.begin(), members.end()); + } + + return count; +} + +Common::ArchiveMemberPtr ResourceManager::getMember(const Common::String &name) { + if (!hasFile(name)) + return Common::ArchiveMemberPtr(); + + return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this)); +} + +Common::SeekableReadStream *ResourceManager::createReadStreamForMember(const Common::String &name) const { + for (Common::Array<HPFArchive * const>::iterator it = _archives.begin(); it != _archives.end(); ++it) { + + Common::SeekableReadStream *stream = (*it)->createReadStreamForMember(name); + + if (stream) + return stream; + } + + return NULL; +} + + +// Resource loading + +Background *ResourceManager::loadBackground(const Common::String &name) const { + // Open the resource + Common::SeekableReadStream *stream = createReadStreamForMember(name + ".bg"); + if (!stream) + return NULL; + + // Create the new background & load the data + Background *bg = new Background(); + if (!bg->load(stream)) { + delete bg; + // stream should be freed by the Background instance + return NULL; + } + + return bg; +} + +Cursor *ResourceManager::loadCursor() const { + // Open the resource + Common::SeekableReadStream *stream = createReadStreamForMember("cursors.tbm"); + if (!stream) + return NULL; + + // Create the new background + Cursor *c = new Cursor(); + if (!c->load(stream)) { + delete c; + // stream should be freed by the Cursor instance + return NULL; + } + + return c; +} + +Font *ResourceManager::loadFont() const { + // Open the resource + Common::SeekableReadStream *stream = createReadStreamForMember("font.dat"); + if (!stream) + return NULL; + + // Create the new background + Font *f = new Font(); + if (!f->load(stream)) { + delete f; + // stream should be freed by the Font instance + return NULL; + } + + return f; +} + +} // End of namespace LastExpress diff --git a/engines/lastexpress/resource.h b/engines/lastexpress/resource.h new file mode 100644 index 0000000000..89de8d9288 --- /dev/null +++ b/engines/lastexpress/resource.h @@ -0,0 +1,70 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_RESOURCE_H +#define LASTEXPRESS_RESOURCE_H + +#include "lastexpress/data/archive.h" +#include "lastexpress/shared.h" + +namespace LastExpress { + +class Background; +class Cursor; +class Font; + +class ResourceManager : public Common::Archive { +public: + ResourceManager(bool demo); + ~ResourceManager(); + + // Loading + bool loadArchive(ArchiveIndex type); + static bool isArchivePresent(ArchiveIndex type); + Common::SeekableReadStream *getFileStream(const Common::String &name); + + // Archive functions + bool hasFile(const Common::String &name); + int listMembers(Common::ArchiveMemberList &list); + Common::ArchiveMemberPtr getMember(const Common::String &name); + Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const; + + // Resource loading + Background *loadBackground(const Common::String &name) const; + Cursor *loadCursor() const; + Font *loadFont() const; + +private: + bool _isDemo; + + bool loadArchive(const Common::String &name); + void reset(); + + Common::Array<HPFArchive *> _archives; +}; + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_RESOURCE_H diff --git a/engines/lastexpress/shared.h b/engines/lastexpress/shared.h new file mode 100644 index 0000000000..3cfcbb334f --- /dev/null +++ b/engines/lastexpress/shared.h @@ -0,0 +1,1670 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef LASTEXPRESS_SHARED_H +#define LASTEXPRESS_SHARED_H + +#include "common/func.h" + +namespace LastExpress { + +////////////////////////////////////////////////////////////////////////// +// Time values +////////////////////////////////////////////////////////////////////////// +enum TimeValue { + kTimeNone = 0, + kTime5933 = 5933, + + kTimeCityParis = 1037700, + kTime1039500 = 1039500, + kTimeStartGame = 1061100, + + // Chapter 1 + kTimeChapter1 = 1062000, + kTime1071000 = 1071000, + kTimeParisEpernay = 1075500, + kTime1080000 = 1080000, + kTime1084500 = 1084500, + kTime1089000 = 1089000, + kTime1093500 = 1093500, + kTime1096200 = 1096200, + kTime1098000 = 1098000, + kTime1102500 = 1102500, + kTime1107000 = 1107000, + kTime1111500 = 1111500, + kTime1120500 = 1120500, + kTime1125000 = 1125000, + kTime1134000 = 1134000, + kTime1138500 = 1138500, + kTime1143000 = 1143000, + kTimeEnterEpernay = 1147500, + kTimeCityEpernay = 1148400, + kTimeExitEpernay = 1150200, + kTime1156500 = 1156500, + kTime1161000 = 1161000, + kTime1162800 = 1162800, + kTime1165500 = 1165500, + kTime1167300 = 1167300, + kTimeEnterChalons = 1170000, + kTimeCityChalons = 1170900, + kTimeExitChalons = 1173600, + kTime1174500 = 1174500, + kTime1179000 = 1179000, + kTime1183500 = 1183500, + kTime1184400 = 1184400, + kTime1188000 = 1188000, + kTime1189800 = 1189800, + kTime1192500 = 1192500, + kTime1197000 = 1197000, + kTime1201500 = 1201500, + kTime1206000 = 1206000, + kTime1215000 = 1215000, + kTime1224000 = 1224000, + kTime1225800 = 1225800, + kTimeCityBarLeDuc = 1228500, + kTimeExitBarLeDuc = 1231200, + kTime1242000 = 1242000, + kTime1260000 = 1260000, + kTimeCityNancy = 1303200, + kTimeExitNancy = 1307700, + kTime1323000 = 1323000, + kTimeCityLuneville = 1335600, + kTimeExitLuneville = 1338300, + kTimeCityAvricourt = 1359900, + kTimeExitAvricourt = 1363500, + kTimeCityDeutschAvricourt = 1367100, + kTimeExitDeutschAvricourt = 1370700, + kTime1386000 = 1386000, + kTimeBedTime = 1404000, + kTimeEnterStrasbourg = 1424700, + kTime1449000 = 1449000, + kTime1458000 = 1458000, + kTime1485000 = 1485000, + kTime1489500 = 1489500, + kTimeCityStrasbourg = 1490400, + kTime1492200 = 1492200, + kTimeExitStrasbourg = 1493100, + kTimeChapter1End = 1494000, + kTime1503000 = 1503000, + kTime1512000 = 1512000, + kTimeCityBadenOos = 1539000, + kTimeExitBadenOos = 1541700, + kTimeCityKarlsruhe = 1563300, + kTimeCityStuttgart = 1656000, + kTimeChapter1End2 = 1647000, + kTimeChapter1End3 = 1674000, + kTimeCityGeislingen = 1713600, + kTime1714500 = 1714500, + kTimeCityUlm = 1739700, + + // Chapter 2 + kTimeChapter2 = 1750500, + kTime1759500 = 1759500, + kTime1755000 = 1755000, + kTime1764000 = 1764000, + kTime1773000 = 1773000, + kTime1777500 = 1777500, + kTime1782000 = 1782000, + kTime1786500 = 1786500, + kTime1791000 = 1791000, + kTime1800000 = 1800000, + kTime1806300 = 1806300, + kTime1809000 = 1809000, + kTimeCityAugsburg = 1809900, + kTime1818000 = 1818000, + kTime1818900 = 1818900, + kTime1822500 = 1822500, + kTime1836000 = 1836000, + kTime1845000 = 1845000, + kTimeCityMunich = 1852200, + + // Chapter 3 + kTimeChapter3 = 1944000, + kTime1953000 = 1953000, + kTime1966500 = 1966500, + kTime1971000 = 1971000, + kTimeEnterSalzbourg = 1982700, + kTimeCitySalzbourg = 1984500, + kTime1989000 = 1989000, + kTimeExitSalzbourg = 1989900, + kTime1993500 = 1993500, + kTime1998000 = 1998000, + kTime2002500 = 2002500, + kTime2011500 = 2011500, + kTime2016000 = 2016000, + kTime2020500 = 2020500, + kTime2025000 = 2025000, + kTime2038500 = 2038500, + kTime2040300 = 2040300, + kTime2043000 = 2043000, + kTimeEnterAttnangPuchheim = 2047500, + kTimeCityAttnangPuchheim = 2049300, + kTime2052000 = 2052000, + kTimeExitAttnangPuchheim = 2052900, + kTime2061000 = 2061000, + kTime2062800 = 2062800, + kTime2070000 = 2070000, + kTimeEnterWels = 2073600, + kTimeCityWels = 2075400, + kTime2079000 = 2079000, + kTimeExitWels = 2079900, + kTime2083500 = 2083500, + kTime2088000 = 2088000, + kTime2092500 = 2092500, + kTime2097000 = 2097000, + kTimeEnterLinz = 2099700, + kTimeCityLinz = 2101500, + kTime2106000 = 2106000, + kTime2110500 = 2110500, + kTime2115000 = 2115000, + kTime2117700 = 2117700, + kTime2119500 = 2119500, + kTime2124000 = 2124000, + kTime2133000 = 2133000, + kTime2142000 = 2142000, + kTime2146500 = 2146500, + kTime2151000 = 2151000, + kTimeCityAmstetten = 2154600, + kTime2155500 = 2155500, + kTime2160000 = 2160000, + kTime2169000 = 2169000, + kTime2173500 = 2173500, + kTime2187000 = 2187000, + kTime2182500 = 2182500, + kTime2196000 = 2196000, + kTime2200500 = 2200500, + kTime2218500 = 2218500, + kTime2241000 = 2241000, + kTime2250000 = 2250000, + kTime2254500 = 2254500, + kTime2259000 = 2259000, + kTime2263500 = 2263500, + kTime2266200 = 2266200, + kTimeCityVienna = 2268000, + + // Chapter 4 + kTime2349000 = 2349000, + kTimeChapter4 = 2353500, + kTime2354400 = 2354400, + kTime2356200 = 2356200, + kTime2358000 = 2358000, + kTime2360700 = 2360700, + kTime2362500 = 2362500, + kTime2361600 = 2361600, + kTime2367000 = 2367000, + kTime2370600 = 2370600, + kTime2378700 = 2378700, + kTimeEnterPoszony = 2381400, + kTimeCityPoszony = 2383200, + kTime2385000 = 2385000, + kTimeExitPoszony = 2386800, + kTime2389500 = 2389500, + kTime2403000 = 2403000, + kTime2407500 = 2407500, + kTime2410200 = 2410200, + kTime2412000 = 2412000, + kTime2414700 = 2414700, + kTime2415600 = 2415600, + kTimeEnterGalanta = 2416500, + kTimeCityGalanta = 2418300, + kTime2421000 = 2421000, + kTimeExitGalanta = 2421900, + kTime2422800 = 2422800, + kTime2428200 = 2428200, + kTime2425500 = 2425500, + kTime2430000 = 2430000, + kTime2439000 = 2439000, + kTime2443500 = 2443500, + kTime2448000 = 2448000, + kTime2452500 = 2452500, + kTime2455200 = 2455200, + kTime2457000 = 2457000, + kTime2466000 = 2466000, + kTime2470500 = 2470500, + kTime2475000 = 2475000, + kTime2479500 = 2479500, + kTime2484000 = 2484000, + kTime2488500 = 2488500, + kTime2506500 = 2506500, + kTime2511000 = 2511000, + kTime2511900 = 2511900, + kTime2517300 = 2517300, + kTime2519100 = 2519100, + kTime2520000 = 2520000, + kTime2533500 = 2533500, + kTime2535300 = 2535300, + kTime2538000 = 2538000, + kTimeCityBudapest = 2551500, + + // Chapter 5 + kTimeChapter5 = 2844000, + kTimeTrainStopped = 2898000, + kTime2907000 = 2907000, + kTime2916000 = 2916000, + kTimeCityBelgrade = 2952000, + kTimeTrainStopped2 = 2943000, + kTimeCityNish = 3205800, + kTimeCityTzaribrod = 3492000, + kTime3645000 = 3645000, + kTimeCitySofia = 3690000, + kTimeCityAdrianople = 4320900, + kTime4923000 = 4923000, + kTime4929300 = 4929300, + kTimeCityConstantinople = 4941000, + + + kTime10881000 = 10881000, + kTimeEnd = 15803100, + kTime16451100 = 16451100, + + kTimeInvalid = 2147483647, + kTimeInvalid2 = 0xFFFFFEDA +}; + +////////////////////////////////////////////////////////////////////////// +// Archive & Chapter ID +////////////////////////////////////////////////////////////////////////// +enum ArchiveIndex { + kArchiveAll = 0, + kArchiveCd1 = 1, + kArchiveCd2 = 2, + kArchiveCd3 = 3 +}; + +enum ChapterIndex { + kChapterAll = 0, + kChapter1 = 1, + kChapter2 = 2, + kChapter3 = 3, + kChapter4 = 4, + kChapter5 = 5 +}; + +////////////////////////////////////////////////////////////////////////// +// Index of scenes +////////////////////////////////////////////////////////////////////////// +enum SceneIndex { + kSceneNone = 0, + kSceneMenu = 1, + + // Inventory + kSceneMatchbox = 31, + kSceneTelegram = 32, + kScenePassengerList = 33, + kSceneScarf = 34, + kSceneParchemin = 35, + kSceneArticle = 36, + kScenePaper = 37, + kSceneFirebird = 38, + kSceneBriefcase = 39, + + // Normal scenes + kSceneDefault = 40, + kScene41 = 41, + kSceneCompartmentCorpse = 42, // Tyler compartment with corpse on floor + + // Fight + kSceneFightMilos = 43, + kSceneFightMilosBedOpened = 44, + kSceneFightAnna = 45, + kSceneFightIvo = 46, + kSceneFightSalko = 47, + kSceneFightVesna = 48, + + kSceneEuropeMap = 49, + + // Game over + kSceneGameOverStopPolice = 50, + kSceneGameOverTrainStopped = 51, + kSceneGameOverTrainStopped2 = 52, + kSceneGameOverTrainExplosion = 53, + kSceneGameOverTrainExplosion2 = 54, + kSceneGameOverBloodJacket = 55, + kSceneGameOverPolice = 56, + kSceneGameOverPolice1 = 57, + kSceneGameOverAnnaDied = 58, + kSceneGameOverVienna = 59, + kSceneGameOverVienna1 = 60, + kSceneGameOverVienna2 = 61, + kSceneGameOverAlarm = 62, + kSceneGameOverPolice2 = 63, + kSceneGameOverAlarm2 = 64, + + // Start screen + kSceneStartScreen = 65, + + kSceneBeetle = 128, + + kSceneFightDefault = 820, + + kSceneInvalid = 0xffffffff +}; + +////////////////////////////////////////////////////////////////////////// +// Jacket +////////////////////////////////////////////////////////////////////////// +enum JacketType { + kJacketOriginal = 0, + kJacketBlood = 1, + kJacketGreen = 2 +}; + +////////////////////////////////////////////////////////////////////////// +// City +////////////////////////////////////////////////////////////////////////// +enum CityIndex { + kCityEpernay = 0, + kCityChalons, + kCityBarleduc, + kCityNancy, + kCityLuneville, + kCityAvricourt, // 5 + kCityDeutschAvricourt, + kCityStrasbourg, + kCityBadenOos, + kCitySalzbourg, + kCityAttnangPuchheim, // 10 + kCityWels, + kCityLinz, + kCityVienna, + kCityPoszony, + kCityGalanta, // 15 + kCityPolice +}; + +////////////////////////////////////////////////////////////////////////// +// Savegame ID +////////////////////////////////////////////////////////////////////////// +enum GameId { + kGameBlue, + kGameRed, + kGameGreen, + kGamePurple, + kGameTeal, + kGameGold +}; + +enum SavegameType { + kSavegameTypeIndex = 0, + kSavegameTypeTime = 1, + kSavegameTypeEvent = 2, + kSavegameTypeEvent2 = 3, + kSavegameTypeAuto = 4, + kSavegameTypeTickInterval = 5 +}; + +////////////////////////////////////////////////////////////////////////// +// Cursor style +////////////////////////////////////////////////////////////////////////// +enum CursorStyle { + kCursorNormal, + kCursorForward, + kCursorBackward, + kCursorTurnRight, + kCursorTurnLeft, + kCursorUp, + kCursorDown, + kCursorLeft, + kCursorRight, + kCursorHand, + kCursorHandKnock, // 10 + kCursorMagnifier, + kCursorHandPointer, + kCursorSleep, + kCursorTalk, + kCursorTalk2, // Need better name + + // Items + kCursorMatchBox, + kCursorTelegram, + kCursorPassengerList, + kCursorArticle, + kCursorScarf, // 20 + kCursorPaper, + kCursorParchemin, + kCursorMatch, + kCursorWhistle, + kCursorKey, + kCursorBomb, + kCursorFirebird, + kCursorBriefcase, + kCursorCorpse, + + // Combat + kCursorPunchLeft, // 30 + kCursorPunchRight, + + // Portraits + kCursorPortrait, // 32 + kCursorPortraitSelected, + kCursorPortraitGreen, + kCursorPortraitGreenSelected, + kCursorPortraitYellow, + kCursorPortraitYellowSelected, + kCursorHourGlass, + kCursorEggBlue, + kCursorEggRed, // 40 + kCursorEggGreen, + kCursorEggPurple, + kCursorEggTeal, + kCursorEggGold, + kCursorEggClock, + kCursorNormal2, + kCursorBlank, + kCursorMAX, + + // Special + kCursorProcess = 128, + kCursorKeepValue = 255 +}; + +////////////////////////////////////////////////////////////////////////// +// Position - should be between 0 & 100 +////////////////////////////////////////////////////////////////////////// +typedef unsigned char Position; + +////////////////////////////////////////////////////////////////////////// +// EntityPosition +////////////////////////////////////////////////////////////////////////// +enum EntityPosition { + kPositionNone = 0, + kPosition_1 = 1, + kPosition_3 = 3, + kPosition_4 = 4, + kPosition_500 = 500, + kPosition_540 = 540, + kPosition_750 = 750, + kPosition_849 = 849, + kPosition_850 = 850, + kPosition_851 = 851, + kPosition_1200 = 1200, + kPosition_1430 = 1430, + kPosition_1500 = 1500, + kPosition_1540 = 1540, + kPosition_1750 = 1750, + kPosition_2000 = 2000, + kPosition_2087 = 2087, + kPosition_2086 = 2086, + kPosition_2088 = 2088, + kPosition_2110 = 2110, + kPosition_2300 = 2300, + kPosition_2330 = 2330, + kPosition_2410 = 2410, + kPosition_2436 = 2436, + kPosition_2490 = 2490, + kPosition_2500 = 2500, + kPosition_2587 = 2587, + kPosition_2588 = 2588, + kPosition_2690 = 2690, + kPosition_2740 = 2740, + kPosition_2830 = 2830, + kPosition_2980 = 2980, + kPosition_3050 = 3050, + kPosition_3110 = 3110, + kPosition_3390 = 3390, + kPosition_3450 = 3450, + kPosition_3500 = 3500, + kPosition_3650 = 3650, + kPosition_3760 = 3760, + kPosition_3820 = 3820, + kPosition_3890 = 3890, + kPosition_3969 = 3969, + kPosition_3970 = 3970, + kPosition_4070 = 4070, + kPosition_4100 = 4100, + kPosition_4370 = 4370, + kPosition_4455 = 4455, + kPosition_4460 = 4460, + kPosition_4500 = 4500, + kPosition_4590 = 4590, + kPosition_4680 = 4680, + kPosition_4689 = 4689, + kPosition_4690 = 4690, + kPosition_4691 = 4691, + kPosition_4770 = 4470, + kPosition_4840 = 4840, + kPosition_5000 = 5000, + kPosition_5090 = 5090, + kPosition_5140 = 5140, + kPosition_5419 = 5419, + kPosition_5420 = 5420, + kPosition_5440 = 5440, + kPosition_5500 = 5500, + kPosition_5540 = 5540, + kPosition_5610 = 5610, + kPosition_5790 = 5790, + kPosition_5799 = 5799, + kPosition_5800 = 5800, + kPosition_5810 = 5810, + kPosition_5890 = 5890, + kPosition_5900 = 5900, + kPosition_5970 = 5970, + kPosition_6000 = 6000, + kPosition_6130 = 6130, + kPosition_6160 = 6160, + kPosition_6220 = 6220, + kPosition_6410 = 6410, + kPosition_6460 = 6460, + kPosition_6469 = 6469, + kPosition_6470 = 6470, + kPosition_6471 = 6471, + kPosition_6800 = 6800, + kPosition_6850 = 6850, + kPosition_7000 = 7000, + kPosition_7160 = 7160, + kPosition_7250 = 7250, + kPosition_7320 = 7320, + kPosition_7500 = 7500, + kPosition_7510 = 7510, + kPosition_7850 = 7850, + kPosition_7870 = 7870, + kPosition_7900 = 7900, + kPosition_7950 = 7950, + kPosition_8000 = 8000, + kPosition_8012 = 8012, + kPosition_8013 = 8013, + kPosition_8160 = 8160, + kPosition_8200 = 8200, + kPosition_8500 = 8500, + kPosition_8512 = 8512, + kPosition_8513 = 8513, + kPosition_8514 = 8514, + kPosition_8800 = 8800, + kPosition_9020 = 9020, + kPosition_9269 = 9269, + kPosition_9250 = 9250, + kPosition_9270 = 9270, + kPosition_9271 = 9271, + kPosition_9460 = 9460, + kPosition_9500 = 9500, + kPosition_9510 = 9510, + kPosition_30000 = 30000 +}; + +////////////////////////////////////////////////////////////////////////// +// Location +////////////////////////////////////////////////////////////////////////// +enum Location { + kLocationOutsideCompartment = 0, + kLocationInsideCompartment = 1, + kLocationOutsideTrain = 2 +}; + +////////////////////////////////////////////////////////////////////////// +// Car +////////////////////////////////////////////////////////////////////////// +enum CarIndex { + kCarNone = 0, + kCarBaggageRear = 1, + kCarKronos = 2, + kCarGreenSleeping = 3, + kCarRedSleeping = 4, + kCarRestaurant = 5, + kCarBaggage = 6, + kCarCoalTender = 7, + kCarLocomotive = 8, + kCar9 = 9 +}; + +////////////////////////////////////////////////////////////////////////// +// Clothes +////////////////////////////////////////////////////////////////////////// +enum ClothesIndex { + kClothesDefault = 0, + kClothes1 = 1, + kClothes2 = 2, + kClothes3 = 3, + + kClothesInvalid +}; + +////////////////////////////////////////////////////////////////////////// +// Location of objects +////////////////////////////////////////////////////////////////////////// +enum ObjectLocation { + kObjectLocationNone = 0, + kObjectLocation1 = 1, // Floor? + kObjectLocation2 = 2, // Bed ? + kObjectLocation3 = 3, + kObjectLocation4 = 4, // Window ? + kObjectLocation5 = 5, + kObjectLocation6 = 6, + kObjectLocation7 = 7, + kObjectLocation10 = 10 +}; + +////////////////////////////////////////////////////////////////////////// +// Entity direction +////////////////////////////////////////////////////////////////////////// +enum EntityDirection { + kDirectionNone = 0, + kDirectionUp = 1, + kDirectionDown = 2, + kDirectionLeft = 3, + kDirectionRight = 4, + kDirectionSwitch = 5 +}; + +////////////////////////////////////////////////////////////////////////// +// Combat +////////////////////////////////////////////////////////////////////////// +enum FightType { + kFightMilos = 2001, + kFightAnna = 2002, + kFightIvo = 2003, + kFightSalko = 2004, + kFightVesna = 2005 +}; + +////////////////////////////////////////////////////////////////////////// +// Index of items in inventory data +////////////////////////////////////////////////////////////////////////// +enum InventoryItem { + kItemNone, + kItemMatchBox, + kItem2, + kItem3, + kItemTelegram, + kItem5, // 5 + kItemPassengerList, + kItem7, + kItemScarf, + kItem9, + kItemParchemin, // 10 + kItem11, + kItemMatch, + kItemWhistle, + kItemBeetle, + kItemKey, // 15 + kItemBomb, + kItem17, + kItemFirebird, + kItemBriefcase, + kItemCorpse, // 20 + kItemGreenJacket, + kItem22, + kItemPaper, + kItemArticle, + kItem25, // 25 + kItem26, + kItem27, + kItem28, + kItem29, + kItem30, // 30 + kItem31, + + // Portrait (not an index) + kPortraitOriginal = 32, + kPortraitGreen = 34, + kPortraitYellow = 36, + + kItemInvalid = 128, + kItem146 = 146, + + // Toggles + kItemToggleHigh = 0x7F, + kItemToggleLow = 0xF7 +}; + +////////////////////////////////////////////////////////////////////////// +// Object ID +////////////////////////////////////////////////////////////////////////// +enum ObjectIndex { + kObjectNone, + kObjectCompartment1, + kObjectCompartment2, + kObjectCompartment3, + kObjectCompartment4, + kObjectCompartment5, // 5 + kObjectCompartment6, + kObjectCompartment7, + kObjectCompartment8, + kObjectOutsideTylerCompartment, + kObject10, // 10 + kObject11, + kObject12, + kObject13, + kObject14, + kObject15, // 15 + kObject16, + kObjectHandleBathroom, + kObjectHandleInsideBathroom, + kObjectKitchen, + kObject20, // 20 + kObject21, + kObject22, + kObjectTrainTimeTable, + kObjectRedSleepingCar, + kObject25, // 25 + kObjectHandleOutsideLeft, + kObjectHandleOutsideRight, + kObject28, + kObject29, + kObject30, // 30 + kObject31, + kObjectCompartmentA, + kObjectCompartmentB, + kObjectCompartmentC, + kObjectCompartmentD, // 35 + kObjectCompartmentE, + kObjectCompartmentF, + kObjectCompartmentG, + kObjectCompartmentH, + kObject40, // 40 + kObject41, + kObject42, + kObject43, + kObjectOutsideBetweenCompartments, + kObjectOutsideAnnaCompartment, // 45 + kObject46, + kObject47, + kObject48, // might be the egg + kObject49, + kObject50, // 50 + kObject51, + kObject52, + kObject53, + kObject54, + kObjectRestaurantCar, // 55 + kObject56, + kObject57, + kObject58, + kObject59, + kObject60, // 60 + kObject61, + kObject62, + kObject63, + kObject64, + kObject65, // 65 + kObject66, + kObject67, + kObject68, + kObject69, + kObject70, // 70 + kObject71, + kObject72, + kObjectCeiling, + kObject74, + kObjectCompartmentKronos, // 75 + kObject76, + kObject77, + kObject78, + kObject79, + kObject80, // 80 + kObject81, + kObject82, + kObject83, + kObject84, + kObject85, // 85 + kObject86, + kObject87, + kObject88, + kObject89, + kObject90, // 90 + kObject91, + kObject92, + kObject93, + kObject94, + kObject95, // 95 + kObject96, + kObject97, + kObject98, + kObject99, + kObject100, // 100 + kObject101, + kObject102, + kObject103, + kObject104, + kObject105, // 105 + kObject106, + kObject107, + kObject108, + kObjectCageMax, + kObject110, // 110 + kObject111, + kObject112, + kObject113, + kObject114, + kObject115, // 115 + kObject116, + kObject117, + kObject118, + kObject119, + kObject120, // 120 + kObject121, + kObject122, + kObject123, + kObject124, + kObject125, // 125 + kObject126, + kObject127, + kObjectMax +}; + +////////////////////////////////////////////////////////////////////////// +// Entity ID +////////////////////////////////////////////////////////////////////////// +enum EntityIndex { + kEntityPlayer, + kEntityAnna, + kEntityAugust, + kEntityMertens, + kEntityCoudert, + kEntityPascale, // 5 + kEntityServers0, + kEntityServers1, + kEntityCooks, + kEntityVerges, + kEntityTatiana, // 10 + kEntityVassili, + kEntityAlexei, + kEntityAbbot, + kEntityMilos, + kEntityVesna, // 15 + kEntityIvo, + kEntitySalko, + kEntityKronos, + kEntityKahina, + kEntityFrancois, // 20 + kEntityMmeBoutarel, + kEntityBoutarel, + kEntityRebecca, + kEntitySophie, + kEntityMahmud, // 25 + kEntityYasmin, + kEntityHadija, + kEntityAlouan, + kEntityGendarmes, + kEntityMax, // 30 + kEntityChapters, + kEntityTrain, + kEntityTables0, + kEntityTables1, + kEntityTables2, // 35 + kEntityTables3, + kEntityTables4, + kEntityTables5, + kEntity39, + + kEntitySteam = 255 +}; + +////////////////////////////////////////////////////////////////////////// +// Events +// - a single D at the end means that Cath is on the right of the "scene" (D = Down the train, U = Up the train) +// - DD: during the day, coming down the train +// - DU: during the day, coming up the train +// - ND: during the night, coming down the train +// - NU: during the night, coming up the train +////////////////////////////////////////////////////////////////////////// +enum EventIndex { + kEventNone = 0, + kEventGotALight = 1, + kEventGotALightD = 2, + kEventDinerMindJoin = 3, + kEventDinerAugustOriginalJacket = 4, + kEventDinerAugust = 5, + kEventDinerAugustAlexeiBackground = 6, + kEventMeetAugustTylerCompartment = 7, + kEventMeetAugustTylerCompartmentBed = 8, + kEventMeetAugustHisCompartment = 9, + kEventMeetAugustHisCompartmentBed = 10, + kEventAugustFindCorpse = 11, + kEventAugustPresentAnna = 12, + kEventAugustPresentAnnaFirstIntroduction = 13, + kEventAnnaIntroductionRejected = 14, + kEventAnnaConversationGoodNight = 15, + kEventAnnaVisitToCompartmentGun = 16, + kEventInvalid_17 = 17, + kEventAnnaGoodNight = 18, + kEventAnnaGoodNightInverse = 19, + kEventAugustGoodMorning = 20, + kEventAugustMerchandise = 21, + kEventAugustTalkGold = 22, + kEventAugustTalkGoldDay = 23, + kEventAugustTalkCompartmentDoor = 24, + kEventAugustTalkCompartmentDoorBlueRedingote = 25, + kEventAugustLunch = 26, + kEventKronosVisit = 27, + kEventAnnaSearchingCompartment = 28, + kEventAugustBringEgg = 29, + kEventAugustBringBriefcase = 30, + kEventAugustTalkCigar = 31, + kEventAnnaBaggageArgument = 32, + kEventAnnaBagagePart2 = 33, + kEventAnnaConversation_34 = 34, + kEventAugustDrink = 35, + kEventAnnaTired = 36, + kEventAnnaTiredKiss = 37, + kEventAnnaBaggageTies = 38, + kEventAnnaBaggageTies2 = 39, + kEventAnnaBaggageTies3 = 40, + kEventAnnaBaggageTies4 = 41, + kEventAugustUnhookCarsBetrayal = 42, + kEventAugustUnhookCars = 43, + kEventLocomotiveAnnaStopsTrain = 44, + kEventInvalid_45 = 45, + kEventTrainStopped = 46, + kEventAnnaKissTrainHijacked = 47, + kEventTrainHijacked = 48, + kEventAnnaKilled = 49, + kEventKronosGoingToInvitation = 50, + kEventKronosConversation = 51, + kEventKahinaAskSpeakFirebird = 52, + kEventKahinaAskSpeak = 53, + kEventKronosConversationFirebird = 54, + kEventKahinaGunYellow = 55, + kEventKahinaGunBlue = 56, + kEventKahinaGun = 57, + kEventKronosBringEggCeiling = 58, + kEventKronosBringEgg = 59, + kEventKronosBringNothing = 60, + kEventKronosReturnBriefcase = 61, + kEventKronosHostageAnna = 62, + kEventKronosGiveFirebird = 63, + kEventKahinaPunchBaggageCarEntrance = 64, + kEventKahinaPunchBlue = 65, + kEventKahinaPunchYellow = 66, + kEventKahinaPunchSalon = 67, + kEventKahinaPunchKitchen = 68, + kEventKahinaPunchBaggageCar = 69, + kEventKahinaPunchCar = 70, + kEventKahinaPunchSuite4 = 71, + kEventKahinaPunchRestaurant = 72, + kEventKronosHostageAnnaNoFirebird = 73, + kEventKahinaPunch = 74, + kEventKahinaWrongDoor = 75, + kEventAlexeiDiner = 76, + kEventAlexeiDinerOriginalJacket = 77, + kEventAlexeiSalonVassili = 78, + kEventAlexeiSalonCath = 79, + kEventAlexeiSalonPoem = 80, + kEventTatianaAskMatchSpeakRussian = 81, + kEventTatianaAskMatch = 82, + kEventTatianaGivePoem = 83, + kEventVassiliSeizure = 84, + kEventTatianaBreakfastAlexei = 85, + kEventTatianaBreakfast = 86, + kEventTatianaBreakfastGivePoem = 87, + kEventTatianaAlexei = 88, + kEventTatianaCompartmentStealEgg = 89, + kEventTatianaCompartment = 90, + kEventVassiliCompartmentStealEgg = 91, + kEventTatianaTylerCompartment = 92, + kEventTylerCastleDream= 93, + kEventVassiliDeadAlexei = 94, + kEventCathFreePassengers = 95, + kEventTatianaVassiliTalk = 96, + kEventTatianaVassiliTalkNight = 97, + kEventMilosTylerCompartmentVisit = 98, + kEventMilosTylerCompartmentBedVisit = 99, + kEventMilosTylerCompartment = 100, + kEventMilosTylerCompartmentBed = 101, + kEventMilosTylerCompartmentDefeat = 102, + kEventMilosCorpseFloor = 103, + kEventMilosCompartmentVisitAugust = 104, + kEventMilosCorridorThanks = 105, + kEventMilosCorridorThanksD = 106, + kEventMilosCompartmentVisitTyler = 107, + kEventLocomotiveMilos = 108, + kEventLocomotiveMilosNight = 109, + kEventAbbotIntroduction = 110, + kEventAbbotWrongCompartment = 111, + kEventAbbotWrongCompartmentBed = 112, + kEventAbbotInvitationDrink = 113, + kEventAbbotDrinkGiveDetonator = 114, + kEventTrainExplosionBridge = 115, + kEventDefuseBomb = 116, + kEventAbbotDrinkDefuse = 117, + kEventMertensLastCar = 118, + kEventMertensLastCarOriginalJacket = 119, + kEventMertensKronosInvitation = 120, + kEventMertensKronosInvitationCompartment = 121, + kEventMertensKronosInvitationClosedWindows = 122, + kEventMertensBloodJacket = 123, + kEventCoudertBloodJacket = 124, + kEventMertensCorpseFloor = 125, + kEventMertensCorpseBed = 126, + kEventMertensDontMakeBed = 127, + kEventInvalid_128 = 128, + kEventGendarmesArrestation = 129, + kEventVergesSuitcase = 130, + kEventVergesSuitcaseStart = 131, + kEventVergesSuitcaseOtherEntry = 132, + kEventVergesSuitcaseOtherEntryStart = 133, + kEventVergesSuitcaseNight = 134, + kEventVergesSuitcaseNightStart = 135, + kEventVergesSuitcaseNightOtherEntry = 136, + kEventVergesSuitcaseNightOtherEntryStart = 137, + kEventMertensAskTylerCompartment = 138, + kEventMertensAskTylerCompartmentD = 139, + kEventMertensPushCall = 140, + kEventMertensPushCallNight = 141, + kEventMertensAugustWaiting = 142, + kEventMertensAugustWaitingCompartment = 143, + kEventIntroBroderbrund = 144, + kEventCoudertAskTylerCompartment = 145, + kEventMertensKronosConcertInvitation = 146, + kEventCoudertGoingOutOfVassiliCompartment = 147, + kEventLocomotiveConductorsDiscovered = 148, + kEventLocomotiveConductorsLook = 149, + kEventMahmudWrongDoor = 150, + kEventMahmudWrongDoorOriginalJacket = 151, + kEventMahmudWrongDoorDay = 152, + kEventVergesEscortToDiningCar = 153, + kEventVergesBaggageCarOffLimits = 154, + kEventVergesCanIHelpYou = 155, + kEventCoudertBaggageCar = 156, + kEventCathTurningDay = 157, + kEventCathTurningNight = 158, + kEventIntro = 159, + kEventCathDream = 160, + kEventCorpseDropBridge = 161, + kEventTrainPassing = 162, + kEventVergesAnnaDead = 163, + kEventViennaAugustUnloadGuns = 164, + kEventViennaKronosFirebird = 165, + kEventViennaContinueGame = 166, + kEventCathVesnaRestaurantKilled = 167, + kEventCathMaxCage = 168, + kEventCathMaxFree = 169, + kEventCathMaxLickHand = 170, + kEventCathIvoFight = 171, + kEventCathSalkoTrainTopFight = 172, + kEventCathVesnaTrainTopFight = 173, + kEventCathVesnaTrainTopKilled = 174, + kEventCathVesnaTrainTopWin = 175, + kEventCathSalkoTrainTopWin = 176, + kEventFrancoisWhistle = 177, + kEventFrancoisWhistleD = 178, + kEventFrancoisWhistleNight = 179, + kEventFrancoisWhistleNightD = 180, + kEventFrancoisShowBeetle = 181, + kEventFrancoisShowBeetleD = 182, + kEventFrancoisTradeWhistle = 183, + kEventFrancoisTradeWhistleD = 184, + kEventFrancoisShowEgg = 185, + kEventFrancoisShowEggD = 186, + kEventFrancoisShowEggNight = 187, + kEventFrancoisShowEggNightD = 188, + kEventKronosBringFirebird = 189, + kEventKronosOpenFirebird = 190, + kEventFinalSequence = 191, + kEventLocomotiveRestartTrain = 192, + kEventLocomotiveOldBridge = 193, + kEventLocomotiveAbbotGetSomeRest = 194, + kEventLocomotiveAbbotShoveling = 195, + kEventLocomotiveMilosShovelingDay = 196, + kEventLocomotiveMilosShovelingNight = 197, + kEventAnnaGiveScarf = 198, + kEventAnnaGiveScarfDiner = 199, + kEventAnnaGiveScarfSalon = 200, + kEventAnnaGiveScarfMonogram = 201, + kEventAnnaGiveScarfDinerMonogram = 202, + kEventAnnaGiveScarfSalonMonogram = 203, + kEventAnnaGiveScarfAsk = 204, + kEventAnnaGiveScarfDinerAsk = 205, + kEventAnnaGiveScarfSalonAsk = 206, + kEventAugustArrivalInMunich = 207, + kEventAnnaDialogGoToJerusalem = 208, + kEventConcertStart = 209, + kEventConcertEnd = 210, + kEventCathFallingAsleep = 211, + kEventCathWakingUp = 212, + kEventConcertCough = 213, + kEventConcertSit = 214, + kEventConcertLeaveWithBriefcase = 215, + kEventCorpseDropFloorOriginal = 216, + kEventCorpseDropFloorGreen = 217, + kEventCorpsePickFloorOriginal = 218, + kEventCorpsePickFloorGreen = 219, + kEventCorpsePickFloorOpenedBedOriginal = 220, + kEventCorpsePickBedOriginal = 221, + kEventCorpsePickBedGreen = 222, + kEventCorpseDropBedOriginal = 223, + kEventCorpseDropBedGreen = 224, + kEventCorpseDropWindowOriginal = 225, + kEventCorpseDropWindowGreen = 226, + kEventCathFindCorpse = 227, + kEventCathLookOutsideWindowDay = 228, + kEventCathLookOutsideWindowNight = 229, + kEventCathGoOutsideTylerCompartmentDay = 230, + kEventCathGoOutsideTylerCompartmentNight = 231, + kEventCathGoOutsideDay = 232, + kEventCathGoOutsideNight = 233, + kEventCathSlipTylerCompartmentDay = 234, + kEventCathSlipTylerCompartmentNight = 235, + kEventCathSlipDay = 236, + kEventCathSlipNight = 237, + kEventCathGetInsideTylerCompartmentDay = 238, + kEventCathGetInsideTylerCompartmentNight = 239, + kEventCathGetInsideDay = 240, + kEventCathGetInsideNight = 241, + kEventCathGettingInsideAnnaCompartment = 242, + kEventCathClimbUpTrainGreenJacket = 243, + kEventCathClimbUpTrainNoJacketNight = 244, + kEventCathClimbUpTrainNoJacketDay = 245, + kEventCathClimbDownTrainGreenJacket = 246, + kEventCathClimbDownTrainNoJacketNight = 247, + kEventCathClimbDownTrainNoJacketDay= 248, + kEventCathTopTrainGreenJacket = 249, + kEventCathTopTrainNoJacketNight = 250, + kEventCathTopTrainNoJacketDay = 251, + kEventCathBreakCeiling = 252, + kEventCathJumpDownCeiling = 253, + kEventCathJumpUpCeilingBriefcase = 254, + kEventCathJumpUpCeiling = 255, + kEventPickGreenJacket = 256, + kEventPickScarfGreen = 257, + kEventPickScarfOriginal = 258, + kEventCloseMatchbox = 259, + kEventCathStruggleWithBonds = 260, + kEventCathBurnRope = 261, + kEventCathRemoveBonds = 262, + kEventCathStruggleWithBonds2 = 263, + kEventCathDefusingBomb = 264, + kEventCathSmokeNight = 265, + kEventCathSmokeDay = 266, + kEventCathOpenEgg = 267, + kEventCathOpenEggNoBackground = 268, + kEventCathCloseEgg = 269, + kEventCathCloseEggNoBackground = 270, + kEventCathUseWhistleOpenEgg = 271, + kEventCathUseWhistleOpenEggNoBackground = 272 +}; + +////////////////////////////////////////////////////////////////////////// +// Action ID (used by entity logic) +////////////////////////////////////////////////////////////////////////// +enum ActionIndex { + kActionNone = 0, + kAction1 = 1, + kActionEndSound = 2, + kActionExitCompartment = 3, + kAction4 = 4, + kActionExcuseMeCath = 5, + kActionExcuseMe = 6, + kActionKnock = 8, + kActionOpenDoor = 9, + kAction10 = 10, + kAction11 = 11, + kActionDefault = 12, + kAction16 = 16, + kActionDrawScene = 17, + kActionCallback = 18, + + ///////////////////////////// + // Abbot + ///////////////////////////// + kAction100969180 = 100969180, // Anna + kAction101169422 = 101169422, + kAction104060776 = 104060776, + kAction135600432 = 135600432, + kAction136196244 = 136196244, + kAction157159392 = 157159392, + kAction157489665 = 157489665, + kAction158480160 = 158480160, + kAction192054567 = 192054567, + kAction203073664 = 203073664, + kAction222609266 = 222609266, + + ///////////////////////////// + // Alexei + ///////////////////////////// + kAction100906246 = 100906246, + kAction123536024 = 123536024, + kAction124697504 = 124697504, + kAction135664192 = 135664192, + kAction135854208 = 135854208, + kAction188784532 = 188784532, + kAction221617184 = 221617184, + + ///////////////////////////// + // Alouan + ///////////////////////////// + kAction189489753 = 189489753, + kAction190219584 = 190219584, // Francois + + ///////////////////////////// + // Anna + ///////////////////////////// + kAction136702400 = 136702400, + kAction139254416 = 139254416, + kAction156049968 = 156049968, + kAction157370960 = 157370960, + kAction157894320 = 157894320, + kAction159332865 = 159332865, // August + kAction189299008 = 189299008, + kAction191668032 = 191668032, // some action during or before concert? + kAction201437056 = 201437056, + kAction235856512 = 235856512, + kAction236060709 = 236060709, + kAction238936000 = 238936000, + kAction259136835 = 259136835, + kAction291662081 = 291662081, + + + ///////////////////////////// + // August + ///////////////////////////// + kAction123793792 = 123793792, + kAction134611040 = 134611040, + kAction168046720 = 168046720, + kAction168627977 = 168627977, + kAction169032608 = 169032608, + kAction189426612 = 189426612, + kAction203859488 = 203859488, + kAction219522616 = 219522616, // Servers0 + kAction225182640 = 225182640, + kAction235257824 = 235257824, + + ///////////////////////////// + // Boutarel + ///////////////////////////// + kAction125039808 = 125039808, + kAction134466544 = 134466544, + kAction135854206 = 135854206, + kAction159003408 = 159003408, + kAction203520448 = 203520448, + kAction237889408 = 237889408, + + ///////////////////////////// + // Chapters + ///////////////////////////// + kAction135800432 = 135800432, + kActionChapter3 = 139122728, + kActionChapter5 = 139254416, + kAction156435676 = 156435676, + kAction169629818 = 169629818, + kAction171843264 = 171843264, + kAction190346110 = 190346110, + + ///////////////////////////// + // Cooks + ///////////////////////////// + kAction101632192 = 101632192, + kAction224849280 = 224849280, + kAction236976550 = 236976550, + + ///////////////////////////// + // Coudert + ///////////////////////////// + kAction123733488 = 123733488, + kAction154005632 = 154005632, + kAction155991520 = 155991520, + kAction157026693 = 157026693, + kAction168253822 = 168253822, + kAction168254872 = 168254872, + kAction169557824 = 169557824, + kAction171394341 = 171394341, // Mertens + kAction185671840 = 185671840, + kAction185737168 = 185737168, + kAction188570113 = 188570113, + kAction189026624 = 189026624, + kAction189750912 = 189750912, + kAction192063264 = 192063264, // Anna + kAction201431954 = 201431954, // Mertens / Verges + kAction201439712 = 201439712, + kAction205033696 = 205033696, + kAction205346192 = 205346192, // Francois + kAction219971920 = 219971920, // Anna + kAction223068211 = 223068211, // MmeBoutarel + kAction225932896 = 225932896, + kAction226031488 = 226031488, // Verges + kAction238358920 = 238358920, // Anna + kAction253868128 = 253868128, // Anna + kAction285528346 = 285528346, // Rebecca + kAction292048641 = 292048641, + kAction305159806 = 305159806, + kAction326348944 = 326348944, + kAction339669520 = 339669520, // Verges + + ///////////////////////////// + // Francois + ///////////////////////////// + kAction100901266 = 100901266, + kAction100957716 = 100957716, + kAction101107728 = 101107728, + kAction189872836 = 189872836, + kAction190390860 = 190390860, + + ///////////////////////////// + // Gendarmes + ///////////////////////////// + kAction168710784 = 168710784, + kAction169499649 = 169499649, + + ///////////////////////////// + // Kahina + ///////////////////////////// + kAction137503360 = 137503360, + + ///////////////////////////// + // Kronos + ///////////////////////////// + kAction137685712 = 137685712, + kAction138085344 = 138085344, + kAction171849314 = 171849314, + kAction235599361 = 235599361, + + ///////////////////////////// + // Mahmud + ///////////////////////////// + kAction102227384 = 102227384, // Mertens + kAction156567128 = 156567128, + kAction170483072 = 170483072, + kAction225563840 = 225563840, + + ///////////////////////////// + // Max + ///////////////////////////// + kAction71277948 = 71277948, + kAction158007856 = 158007856, + kAction101687594 = 101687594, + kAction122358304 = 122358304, // also Servers1/Boutarel? + kActionMaxFreeFromCage = 135204609, + kAction156622016 = 156622016, + + ///////////////////////////// + // Mertens + ///////////////////////////// + kAction155604840 = 155604840, // MmeBoutarel + kAction169633856 = 169633856, + kAction188635520 = 188635520, + kAction190082817 = 190082817, + kAction192849856 = 192849856, + kAction204379649 = 204379649, + kAction224122407 = 224122407, + kAction238732837 = 238732837, + kAction238790488 = 238790488, // Tatiana + kAction269436673 = 269436673, + kAction269624833 = 269624833, + kAction302614416 = 302614416, + kAction303343617 = 303343617, + + ///////////////////////////// + // Milos + ///////////////////////////// + kAction88652208 = 88652208, // Coudert + kAction122865568 = 122865568, + kAction123852928 = 123852928, + kAction123199584 = 123199584, // Coudert + kAction157691176 = 157691176, + kAction208228224 = 208228224, + kAction221683008 = 221683008, + kAction259125998 = 259125998, + + ///////////////////////////// + // Mme Boutarel + ///////////////////////////// + kAction102484312 = 102484312, + kAction102752636 = 102752636, + kAction134289824 = 134289824, + kAction168986720 = 168986720, + kAction202221040 = 202221040, + kAction242526416 = 242526416, + + ///////////////////////////// + // Pascale + ///////////////////////////// + kAction101824388 = 101824388, + kAction136059947 = 136059947, + kAction169750080 = 169750080, + kAction190605184 = 190605184, + kAction191604416 = 191604416, + kAction207769280 = 207769280, + kAction223262556 = 223262556, + kAction239072064 = 239072064, + kAction257489762 = 257489762, + kAction269479296 = 269479296, + kAction352703104 = 352703104, + kAction352768896 = 352768896, + + ///////////////////////////// + // Rebecca + ///////////////////////////// + kAction125496184 = 125496184, + kAction155465152 = 155465152, + kAction155980128 = 155980128, + kAction169358379 = 169358379, + kAction224253538 = 224253538, + kAction254915200 = 254915200, + + ///////////////////////////// + // Salko + ///////////////////////////// + kAction55996766 = 55996766, + kAction101169464 = 101169464, + kAction102675536 = 102675536, // Ivo + kAction136184016 = 136184016, + + ///////////////////////////// + // Servers 0 + ///////////////////////////// + kAction170016384 = 170016384, + kAction188893625 = 188893625, + kAction201964801 = 201964801, // August + kAction204704037 = 204704037, + kAction207330561 = 207330561, + kAction218128129 = 218128129, + kAction218586752 = 218586752, + kAction218983616 = 218983616, + kAction223712416 = 223712416, + kAction237485916 = 237485916, + kAction252568704 = 252568704, + kAction268773672 = 268773672, // Anna / August + kAction270068760 = 270068760, + kAction270410280 = 270410280, + kAction286403504 = 286403504, + kAction286534136 = 286534136, + kAction292758554 = 292758554, + kAction304061224 = 304061224, + kAction337548856 = 337548856, + + ///////////////////////////// + // Servers 1 + ///////////////////////////// + kAction101106391 = 101106391, + kAction122288808 = 122288808, // Boutarel + kAction123712592 = 123712592, // Ivo + kAction125826561 = 125826561, // August + kAction134486752 = 134486752, // August + kAction168717392 = 168717392, // Boutarel + kAction189688608 = 189688608, + kAction219377792 = 219377792, + kAction223002560 = 223002560, + kAction236237423 = 236237423, + kAction256200848 = 256200848, + kAction258136010 = 258136010, + kAction269485588 = 269485588, + kAction291721418 = 291721418, + kAction302203328 = 302203328, + kAction302996448 = 302996448, + kAction326144276 = 326144276, + + ///////////////////////////// + // Sophie + ///////////////////////////// + kActionProceedChapter5 = 70549068, + kAction123668192 = 123668192, + kAction125242096 = 125242096, + kAction136654208 = 136654208, + kAction259921280 = 259921280, + kAction292775040 = 292775040, + + ///////////////////////////// + // Tables + ///////////////////////////// + kActionDrawTablesWithChairs = 103798704, + kAction136455232 = 136455232, + + ///////////////////////////// + // Tatiana + ///////////////////////////// + kAction69239528 = 69239528, + kAction123857088 = 123857088, + kAction124973510 = 124973510, + kAction156444784 = 156444784, + kAction169360385 = 169360385, + kAction191198209 = 191198209, + kAction223183000 = 223183000, // August + kAction236053296 = 236053296, // Alexei + kAction236241630 = 236241630, // Anna + kAction236517970 = 236517970, // Anna + kAction268620864 = 268620864, // August + kAction290869168 = 290869168, + + ///////////////////////////// + // Train + ///////////////////////////// + kAction191070912 = 191070912, + kActionTrainStopRunning = 191350523, + kActionCatchBeetle = 202613084, + kAction203339360 = 203339360, + kActionTrainStartRunning = 203419131, + kAction203863200 = 203863200, + kAction222746496 = 222746496, + kActionBreakCeiling = 225056224, + kAction290410610 = 290410610, + kActionJumpDownCeiling = 338494260, + + ///////////////////////////// + // Verges + ///////////////////////////// + kAction125233040 = 125233040, // Abbot + kAction125499160 = 125499160, + kAction155853632 = 155853632, + kAction158617345 = 158617345, + kAction167854368 = 167854368, + kAction168187490 = 168187490, + kAction168255788 = 168255788, + kActionDeliverMessageToTyler = 191337656, + kAction202558662 = 202558662, + + ///////////////////////////// + // Vassili + ///////////////////////////// + kAction122732000 = 122732000, + kAction168459827 = 168459827, + kAction191477936 = 191477936, + + ///////////////////////////// + // Vesna + ///////////////////////////// + kAction124190740 = 124190740, + kAction134427424 = 134427424, + kAction135024800 = 135024800, + kAction137165825 = 137165825, + kAction155913424 = 155913424, + kAction190412928 = 190412928, + kAction203663744 = 203663744, + kAction204832737 = 204832737, + + ///////////////////////////// + // Misc + ///////////////////////////// + kAction158610240 = 158610240, + kAction167992577 = 167992577, + kAction168646401 = 168646401, + kAction169300225 = 169300225, + kAction169773228 = 169773228, + kActionEndChapter = 190346110, + kAction191001984 = 191001984, + kAction192637492 = 192637492, + kAction201959744 = 201959744, + kAction202621266 = 202621266, + kAction202884544 = 202884544, + kAction203078272 = 203078272, + kAction205034665 = 205034665, + kAction205294778 = 205294778, + kActionUseWhistle = 270751616, + kAction272177921 = 272177921, + kAction224309120 = 224309120, + kAction225358684 = 225358684, + kAction225367984 = 225367984, + kAction226078300 = 226078300, // Whistle + + kActionEnd +}; + +////////////////////////////////////////////////////////////////////////// +// Functors classes used by the engine +////////////////////////////////////////////////////////////////////////// + +// FIXME is this achievable with the existing Functor1Mem function +template<class Arg, class Res, class T> +class Functor1MemConst : public Common::Functor1<Arg, Res> { +public: + typedef Res (T::*FuncType)(Arg) const; + + Functor1MemConst(T *t, const FuncType &func) : _t(t), _func(func) {} + + bool isValid() const { return _func != 0 && _t != 0; } + Res operator()(Arg v1) const { + return (_t->*_func)(v1); + } +private: + mutable T *_t; + const FuncType _func; +}; + +// FIXME move this to existing func.h file +template<class Arg1, class Arg2, class Arg3, class Arg4, class Result> +struct QuaternaryFunction { + typedef Arg1 FirstArgumentType; + typedef Arg2 SecondArgumentType; + typedef Arg3 ThirdArgumentType; + typedef Arg4 FourthArgumentType; + typedef Result ResultType; +}; + +template<class Arg1, class Arg2, class Arg3, class Arg4, class Res> +struct Functor4 : public QuaternaryFunction<Arg1, Arg2, Arg3, Arg4, Res> { + virtual ~Functor4() {} + + virtual bool isValid() const = 0; + virtual Res operator()(Arg1, Arg2, Arg3, Arg4) const = 0; +}; + +template<class Arg1, class Arg2, class Arg3, class Arg4, class Res, class T> +class Functor4Mem : public Functor4<Arg1, Arg2, Arg3, Arg4, Res> { +public: + typedef Res (T::*FuncType)(Arg1, Arg2, Arg3, Arg4); + + Functor4Mem(T *t, const FuncType &func) : _t(t), _func(func) {} + + bool isValid() const { return _func != 0 && _t != 0; } + Res operator()(Arg1 v1, Arg2 v2, Arg3 v3, Arg4 v4) const { + return (_t->*_func)(v1, v2, v3, v4); + } +private: + mutable T *_t; + const FuncType _func; +}; + + +} // End of namespace LastExpress + +#endif // LASTEXPRESS_SHARED_H |