[Scummvm-git-logs] scummvm master -> 125fd9d80cfa46e2ae834281304654856ce42aec

sev- sev at scummvm.org
Fri Oct 23 22:59:08 UTC 2020


This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
f3f0d0228a IMAGE: Add decoder for CEL 3DO format
ce6eb34121 VIDEO: uplift 3do movie decoder from sherlock engine
3c97ebd4f9 VIDEO: support multi-track audios in 3DO videos
125fd9d80c PLUMBERS: support for 3DO version


Commit: f3f0d0228a3dfe6b4a255079f0753522cbebd2f3
    https://github.com/scummvm/scummvm/commit/f3f0d0228a3dfe6b4a255079f0753522cbebd2f3
Author: Vladimir Serbinenko (phcoder at google.com)
Date: 2020-10-24T00:59:01+02:00

Commit Message:
IMAGE: Add decoder for CEL 3DO format

This format is used by 3DO version of Plumbers Don't Wear Ties
but it's a generic format on 3DO similar to BMP on windows.

Changed paths:
  A image/cel_3do.cpp
  A image/cel_3do.h
    image/module.mk


diff --git a/image/cel_3do.cpp b/image/cel_3do.cpp
new file mode 100644
index 0000000000..5d9c96e6f7
--- /dev/null
+++ b/image/cel_3do.cpp
@@ -0,0 +1,165 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "image/cel_3do.h"
+
+#include "common/file.h"
+#include "common/stream.h"
+#include "common/substream.h"
+#include "common/textconsole.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+#include "image/bmp.h"
+
+namespace Image {
+
+enum CCBFlags {
+	kCCBPacked = 1 << 9,
+	kCCBNoPre0 = 1 << 22
+};
+
+Cel3DODecoder::Cel3DODecoder() {
+	_surface = 0;
+	_palette = 0;
+	_paletteColorCount = 0;
+}
+
+Cel3DODecoder::~Cel3DODecoder() {
+	destroy();
+}
+
+void Cel3DODecoder::destroy() {
+	_surface = 0;
+
+	delete[] _palette;
+	_palette = 0;
+
+	_paletteColorCount = 0;
+}
+
+bool Cel3DODecoder::loadStream(Common::SeekableReadStream &stream) {
+	destroy();
+
+	// This is not a full implementaion of CEL support,
+	// just what is currently needed for following games:
+	// * Plumbers don't wear ties
+	// TODO: support paletted
+
+	if (stream.readUint32BE() != MKTAG('C', 'C', 'B', ' '))
+		return false;
+
+	if (stream.readUint32BE() != 0x50) // block size
+		return false;
+
+	if (stream.readUint32BE() != 0) // CCB version
+		return false;
+
+	uint32 flags = stream.readUint32BE();
+
+	stream.skip(0x30);
+	uint32 pre0 = stream.readUint32BE();
+	/* pre1 = */ stream.readUint32BE();
+	uint32 width = stream.readUint32BE();
+	uint32 height = stream.readUint32BE();
+
+	while (!stream.eos()) {
+		if (stream.readUint32BE() == MKTAG('P', 'D', 'A', 'T'))
+			break;
+		stream.skip(stream.readUint32BE() - 8);
+	}
+
+	if (stream.eos())
+		return false;
+
+	if (width == 0 || height == 0)
+		return false;
+
+	/* pdat_size = */ stream.readUint32BE();
+
+	Graphics::PixelFormat format(2, 5, 5, 5, 1, 10, 5, 0, 15);
+	Graphics::Surface *surface = new Graphics::Surface();
+	surface->create(width, height, format);
+
+	uint16 *dst = (uint16 *)surface->getBasePtr(0, 0);
+
+	if(!(flags & kCCBNoPre0)) {
+		pre0 = stream.readUint32BE();
+		if(!(flags & kCCBPacked)) {
+			/* pre1 = */ stream.readUint32BE();
+		}
+	}
+
+	// Only RGB555 is supported
+	if ((pre0 & 0x17) != 0x16)
+		return false;
+
+	if(!(flags & kCCBPacked)) {
+		// RAW
+		// TODO: this can be optimized, especially on BE systems, but do we care?
+		for (uint xy = 0; xy < width * height; xy++)
+			*dst++ = stream.readUint16BE();
+	} else {
+		// RLE
+		for (uint y = 0; y < height; y++) {
+			int linecomprem = (stream.readUint16BE() + 2) * 4 - 2;
+			int linerem = width;
+			bool stopLine = false;
+			while (linerem > 0 && linecomprem > 0 && !stopLine) {
+				byte lead = stream.readByte();
+				linecomprem--;
+				switch (lead >> 6) {
+				case 0: // end of the line
+					stopLine = true;
+					break;
+				case 1: // copy
+					for (uint i = 0; i <= (lead & 0x3f) && linerem > 0 && linecomprem > 0;
+					     i++, linerem--, linecomprem -= 2)
+						*dst++ = stream.readUint16BE();
+					break;
+				case 2: // black
+					for (uint i = 0; i <= (lead & 0x3f) && linerem > 0; i++, linerem--)
+						*dst++ = 0;
+					break;
+				case 3: { // RLE multiply
+					uint16 rleval = stream.readUint16BE();
+					linecomprem -= 2;
+					for (uint i = 0; i <= (lead & 0x3f) && linerem > 0; i++, linerem--)
+						*dst++ = rleval;
+					break;
+				}
+				}
+			}
+			if (linecomprem > 0)
+				stream.skip(linecomprem);
+			if (linerem > 0) {
+				memset(dst, 0, 2 * linerem);
+				dst += linerem;
+			}
+		}
+	}
+
+	_surface = surface;
+
+	return true;
+}
+
+} // End of namespace Image
diff --git a/image/cel_3do.h b/image/cel_3do.h
new file mode 100644
index 0000000000..41c999ae9b
--- /dev/null
+++ b/image/cel_3do.h
@@ -0,0 +1,63 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef IMAGE_CEL_3DO_H
+#define IMAGE_CEL_3DO_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+#include "image/image_decoder.h"
+
+namespace Common {
+class SeekableReadStream;
+class WriteStream;
+}
+
+namespace Graphics {
+struct Surface;
+}
+
+namespace Image {
+
+class Codec;
+
+class Cel3DODecoder : public ImageDecoder {
+public:
+	Cel3DODecoder();
+	virtual ~Cel3DODecoder();
+
+	// ImageDecoder API
+	void destroy();
+	virtual bool loadStream(Common::SeekableReadStream &stream);
+	virtual const Graphics::Surface *getSurface() const { return _surface; }
+	const byte *getPalette() const { return _palette; }
+	uint16 getPaletteColorCount() const { return _paletteColorCount; }
+
+private:
+	const Graphics::Surface *_surface;
+	byte *_palette;
+	uint16 _paletteColorCount;
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/image/module.mk b/image/module.mk
index 21566dd602..2100f3093c 100644
--- a/image/module.mk
+++ b/image/module.mk
@@ -2,6 +2,7 @@ MODULE := image
 
 MODULE_OBJS := \
 	bmp.o \
+	cel_3do.o \
 	iff.o \
 	jpeg.o \
 	pcx.o \


Commit: ce6eb341215b5624eac7df9f148b64fd8941288c
    https://github.com/scummvm/scummvm/commit/ce6eb341215b5624eac7df9f148b64fd8941288c
Author: Vladimir Serbinenko (phcoder at google.com)
Date: 2020-10-24T00:59:01+02:00

Commit Message:
VIDEO: uplift 3do movie decoder from sherlock engine

The format is generic to 3DO and is also used by plumbers.
I think it's also accelerated on 3DO so probably is used by
a lot of 3DO titles

Changed paths:
  A video/3do_decoder.cpp
  A video/3do_decoder.h
  R engines/sherlock/scalpel/3do/movie_decoder.cpp
  R engines/sherlock/scalpel/3do/movie_decoder.h
    engines/sherlock/module.mk
    engines/sherlock/scalpel/scalpel.cpp
    engines/sherlock/scalpel/scalpel_talk.cpp
    video/module.mk


diff --git a/engines/sherlock/module.mk b/engines/sherlock/module.mk
index 265053a080..e2b2e65245 100644
--- a/engines/sherlock/module.mk
+++ b/engines/sherlock/module.mk
@@ -2,7 +2,6 @@ MODULE := engines/sherlock
 
 MODULE_OBJS = \
 	scalpel/scalpel.o \
-	scalpel/3do/movie_decoder.o \
 	scalpel/3do/scalpel_3do_screen.o \
 	scalpel/drivers/adlib.o \
 	scalpel/drivers/mt32.o \
diff --git a/engines/sherlock/scalpel/scalpel.cpp b/engines/sherlock/scalpel/scalpel.cpp
index 351d55a436..3469372d37 100644
--- a/engines/sherlock/scalpel/scalpel.cpp
+++ b/engines/sherlock/scalpel/scalpel.cpp
@@ -34,7 +34,7 @@
 #include "sherlock/sherlock.h"
 #include "sherlock/music.h"
 #include "sherlock/animation.h"
-#include "sherlock/scalpel/3do/movie_decoder.h"
+#include "video/3do_decoder.h"
 
 namespace Sherlock {
 
@@ -1272,7 +1272,7 @@ void ScalpelEngine::showScummVMRestoreDialog() {
 
 bool ScalpelEngine::play3doMovie(const Common::String &filename, const Common::Point &pos, bool isPortrait) {
 	Scalpel3DOScreen &screen = *(Scalpel3DOScreen *)_screen;
-	Scalpel3DOMovieDecoder *videoDecoder = new Scalpel3DOMovieDecoder();
+	Video::ThreeDOMovieDecoder *videoDecoder = new Video::ThreeDOMovieDecoder();
 	Graphics::ManagedSurface tempSurface;
 
 	Common::Point framePos(pos.x, pos.y);
diff --git a/engines/sherlock/scalpel/scalpel_talk.cpp b/engines/sherlock/scalpel/scalpel_talk.cpp
index f1460fd01d..55cf820726 100644
--- a/engines/sherlock/scalpel/scalpel_talk.cpp
+++ b/engines/sherlock/scalpel/scalpel_talk.cpp
@@ -30,7 +30,6 @@
 #include "sherlock/scalpel/scalpel_user_interface.h"
 #include "sherlock/scalpel/scalpel.h"
 #include "sherlock/screen.h"
-#include "sherlock/scalpel/3do/movie_decoder.h"
 
 namespace Sherlock {
 
diff --git a/engines/sherlock/scalpel/3do/movie_decoder.cpp b/video/3do_decoder.cpp
similarity index 84%
rename from engines/sherlock/scalpel/3do/movie_decoder.cpp
rename to video/3do_decoder.cpp
index d14c46e56b..ab6df337fd 100644
--- a/engines/sherlock/scalpel/3do/movie_decoder.cpp
+++ b/video/3do_decoder.cpp
@@ -27,7 +27,7 @@
 #include "audio/audiostream.h"
 #include "audio/decoders/3do.h"
 
-#include "sherlock/scalpel/3do/movie_decoder.h"
+#include "video/3do_decoder.h"
 #include "image/codecs/cinepak.h"
 
 // for Test-Code
@@ -40,19 +40,19 @@
 #include "graphics/pixelformat.h"
 #include "graphics/surface.h"
 
-namespace Sherlock {
+namespace Video {
 
-Scalpel3DOMovieDecoder::Scalpel3DOMovieDecoder()
+ThreeDOMovieDecoder::ThreeDOMovieDecoder()
 	: _stream(0), _videoTrack(0), _audioTrack(0) {
 	_streamVideoOffset = 0;
 	_streamAudioOffset = 0;
 }
 
-Scalpel3DOMovieDecoder::~Scalpel3DOMovieDecoder() {
+ThreeDOMovieDecoder::~ThreeDOMovieDecoder() {
 	close();
 }
 
-bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
+bool ThreeDOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 	uint32 videoSubType    = 0;
 	uint32 videoCodecTag   = 0;
 	uint32 videoHeight     = 0;
@@ -92,7 +92,7 @@ bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 			case MKTAG('F', 'H', 'D', 'R'):
 				// FILM header found
 				if (_videoTrack) {
-					warning("Sherlock 3DO movie: Multiple FILM headers found");
+					warning("3DO movie: Multiple FILM headers found");
 					close();
 					return false;
 				}
@@ -111,7 +111,7 @@ bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 				break;
 
 			default:
-				warning("Sherlock 3DO movie: Unknown subtype inside FILM packet");
+				warning("3DO movie: Unknown subtype inside FILM packet");
 				close();
 				return false;
 			}
@@ -128,7 +128,7 @@ bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 				// Bail if we already have a track
 				if (_audioTrack) {
-					warning("Sherlock 3DO movie: Multiple SNDS headers found");
+					warning("3DO movie: Multiple SNDS headers found");
 					close();
 					return false;
 				}
@@ -154,7 +154,7 @@ bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 				// Audio data
 				break;
 			default:
-				warning("Sherlock 3DO movie: Unknown subtype inside FILM packet");
+				warning("3DO movie: Unknown subtype inside FILM packet");
 				close();
 				return false;
 			}
@@ -173,7 +173,7 @@ bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 			break;
 
 		default:
-			warning("Unknown chunk-tag '%s' inside Sherlock 3DO movie", tag2str(chunkTag));
+			warning("Unknown chunk-tag '%s' inside 3DO movie", tag2str(chunkTag));
 			close();
 			return false;
 		}
@@ -197,7 +197,7 @@ bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 	return true;
 }
 
-void Scalpel3DOMovieDecoder::close() {
+void ThreeDOMovieDecoder::close() {
 	Video::VideoDecoder::close();
 
 	delete _stream; _stream = 0;
@@ -206,7 +206,7 @@ void Scalpel3DOMovieDecoder::close() {
 
 // We try to at least decode 1 frame
 // and also try to get at least 0.5 seconds of audio queued up
-void Scalpel3DOMovieDecoder::readNextPacket() {
+void ThreeDOMovieDecoder::readNextPacket() {
 	uint32 currentMovieTime = getTime();
 	uint32 wantedAudioQueued  = currentMovieTime + 500; // always try to be 0.500 seconds in front of movie time
 
@@ -300,7 +300,7 @@ void Scalpel3DOMovieDecoder::readNextPacket() {
 				break;
 
 			default:
-				error("Sherlock 3DO movie: Unknown subtype inside FILM packet");
+				error("3DO movie: Unknown subtype inside FILM packet");
 				break;
 			}
 			break;
@@ -332,7 +332,7 @@ void Scalpel3DOMovieDecoder::readNextPacket() {
 				break;
 
 			default:
-				error("Sherlock 3DO movie: Unknown subtype inside SNDS packet");
+				error("3DO movie: Unknown subtype inside SNDS packet");
 				break;
 			}
 			break;
@@ -349,7 +349,7 @@ void Scalpel3DOMovieDecoder::readNextPacket() {
 			break;
 
 		default:
-			error("Unknown chunk-tag '%s' inside Sherlock 3DO movie", tag2str(chunkTag));
+			error("Unknown chunk-tag '%s' inside 3DO movie", tag2str(chunkTag));
 		}
 
 		// Always seek to end of chunk
@@ -362,7 +362,7 @@ void Scalpel3DOMovieDecoder::readNextPacket() {
 	}
 }
 
-Scalpel3DOMovieDecoder::StreamVideoTrack::StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount) {
+ThreeDOMovieDecoder::StreamVideoTrack::StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount) {
 	_width = width;
 	_height = height;
 	_frameCount = frameCount;
@@ -373,27 +373,27 @@ Scalpel3DOMovieDecoder::StreamVideoTrack::StreamVideoTrack(uint32 width, uint32
 	if (codecTag == MKTAG('c', 'v', 'i', 'd'))
 		_codec = new Image::CinepakDecoder();
 	else
-		error("Unsupported Sherlock 3DO movie video codec tag '%s'", tag2str(codecTag));
+		error("Unsupported 3DO movie video codec tag '%s'", tag2str(codecTag));
 }
 
-Scalpel3DOMovieDecoder::StreamVideoTrack::~StreamVideoTrack() {
+ThreeDOMovieDecoder::StreamVideoTrack::~StreamVideoTrack() {
 	delete _codec;
 }
 
-bool Scalpel3DOMovieDecoder::StreamVideoTrack::endOfTrack() const {
+bool ThreeDOMovieDecoder::StreamVideoTrack::endOfTrack() const {
 	return getCurFrame() >= getFrameCount() - 1;
 }
 
-Graphics::PixelFormat Scalpel3DOMovieDecoder::StreamVideoTrack::getPixelFormat() const {
+Graphics::PixelFormat ThreeDOMovieDecoder::StreamVideoTrack::getPixelFormat() const {
 	return _codec->getPixelFormat();
 }
 
-void Scalpel3DOMovieDecoder::StreamVideoTrack::decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp) {
+void ThreeDOMovieDecoder::StreamVideoTrack::decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp) {
 	_surface = _codec->decodeFrame(*stream);
 	_curFrame++;
 }
 
-Scalpel3DOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels, Audio::Mixer::SoundType soundType) :
+ThreeDOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels, Audio::Mixer::SoundType soundType) :
 		AudioTrack(soundType) {
 	switch (codecTag) {
 	case MKTAG('A','D','P','4'):
@@ -402,7 +402,7 @@ Scalpel3DOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint
 		break;
 
 	default:
-		error("Unsupported Sherlock 3DO movie audio codec tag '%s'", tag2str(codecTag));
+		error("Unsupported 3DO movie audio codec tag '%s'", tag2str(codecTag));
 	}
 
 	_totalAudioQueued = 0; // currently 0 milliseconds queued
@@ -417,7 +417,7 @@ Scalpel3DOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint
 		_stereo = true;
 		break;
 	default:
-		error("Unsupported Sherlock 3DO movie audio channels %d", channels);
+		error("Unsupported 3DO movie audio channels %d", channels);
 	}
 
 	_audioStream = Audio::makeQueuingAudioStream(sampleRate, _stereo);
@@ -427,13 +427,13 @@ Scalpel3DOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint
 	memset(&_SDX2_PersistentSpace, 0, sizeof(_SDX2_PersistentSpace));
 }
 
-Scalpel3DOMovieDecoder::StreamAudioTrack::~StreamAudioTrack() {
+ThreeDOMovieDecoder::StreamAudioTrack::~StreamAudioTrack() {
 	delete _audioStream;
 //	free(_ADP4_PersistentSpace);
 //	free(_SDX2_PersistentSpace);
 }
 
-void Scalpel3DOMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, uint32 size) {
+void ThreeDOMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, uint32 size) {
 	Common::SeekableReadStream *compressedAudioStream = 0;
 	Audio::RewindableAudioStream *audioStream = 0;
 	uint32 audioLengthMSecs = 0;
@@ -460,8 +460,8 @@ void Scalpel3DOMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadSt
 	}
 }
 
-Audio::AudioStream *Scalpel3DOMovieDecoder::StreamAudioTrack::getAudioStream() const {
+Audio::AudioStream *ThreeDOMovieDecoder::StreamAudioTrack::getAudioStream() const {
 	return _audioStream;
 }
 
-} // End of namespace Sherlock
+} // End of namespace Video
diff --git a/engines/sherlock/scalpel/3do/movie_decoder.h b/video/3do_decoder.h
similarity index 93%
rename from engines/sherlock/scalpel/3do/movie_decoder.h
rename to video/3do_decoder.h
index b54c424315..e03bd8a1ed 100644
--- a/engines/sherlock/scalpel/3do/movie_decoder.h
+++ b/video/3do_decoder.h
@@ -39,12 +39,19 @@ namespace Image {
 class Codec;
 }
 
-namespace Sherlock {
+namespace Video {
 
-class Scalpel3DOMovieDecoder : public Video::VideoDecoder {
+/**
+ * Decoder for 3DO videos.
+ *
+ * Video decoder used in engines:
+ *  - sherlock
+ *  - plumbers
+ */
+class ThreeDOMovieDecoder : public Video::VideoDecoder {
 public:
-	Scalpel3DOMovieDecoder();
-	~Scalpel3DOMovieDecoder() override;
+	ThreeDOMovieDecoder();
+	~ThreeDOMovieDecoder() override;
 
 	bool loadStream(Common::SeekableReadStream *stream) override;
 	void close() override;
diff --git a/video/module.mk b/video/module.mk
index c1fb79256e..875b141672 100644
--- a/video/module.mk
+++ b/video/module.mk
@@ -1,6 +1,7 @@
 MODULE := video
 
 MODULE_OBJS := \
+	3do_decoder.o \
 	avi_decoder.o \
 	coktel_decoder.o \
 	dxa_decoder.o \


Commit: 3c97ebd4f955882bd554870fbfaf7c9ae2a97090
    https://github.com/scummvm/scummvm/commit/3c97ebd4f955882bd554870fbfaf7c9ae2a97090
Author: Vladimir Serbinenko (phcoder at google.com)
Date: 2020-10-24T00:59:01+02:00

Commit Message:
VIDEO: support multi-track audios in 3DO videos

It's used in plumbers with first track being speech and second track
being music

Changed paths:
    video/3do_decoder.cpp
    video/3do_decoder.h


diff --git a/video/3do_decoder.cpp b/video/3do_decoder.cpp
index ab6df337fd..b1297c576e 100644
--- a/video/3do_decoder.cpp
+++ b/video/3do_decoder.cpp
@@ -43,7 +43,7 @@
 namespace Video {
 
 ThreeDOMovieDecoder::ThreeDOMovieDecoder()
-	: _stream(0), _videoTrack(0), _audioTrack(0) {
+	: _stream(0), _videoTrack(0) {
 	_streamVideoOffset = 0;
 	_streamAudioOffset = 0;
 }
@@ -52,6 +52,10 @@ ThreeDOMovieDecoder::~ThreeDOMovieDecoder() {
 	close();
 }
 
+VideoDecoder::AudioTrack* ThreeDOMovieDecoder::getAudioTrack(int index) {
+	return _audioTracks[index];
+}
+
 bool ThreeDOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 	uint32 videoSubType    = 0;
 	uint32 videoCodecTag   = 0;
@@ -62,6 +66,7 @@ bool ThreeDOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 	uint32 audioCodecTag   = 0;
 	uint32 audioChannels   = 0;
 	uint32 audioSampleRate = 0;
+	uint32 audioTrackId  = 0;
 
 	close();
 
@@ -119,20 +124,14 @@ bool ThreeDOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 		}
 
 		case MKTAG('S','N','D','S'): {
-			_stream->skip(8);
+			_stream->readUint32BE(); // Unknown
+			audioTrackId = _stream->readUint32BE();
 			audioSubType = _stream->readUint32BE();
 
 			switch (audioSubType) {
-			case MKTAG('S', 'H', 'D', 'R'):
+			case MKTAG('S', 'H', 'D', 'R'): {
 				// Audio header
 
-				// Bail if we already have a track
-				if (_audioTrack) {
-					warning("3DO movie: Multiple SNDS headers found");
-					close();
-					return false;
-				}
-
 				// OK, this is the start of a audio stream
 				_stream->readUint32BE(); // Version, always 0x00000000
 				_stream->readUint32BE(); // Unknown 0x00000008 ?!
@@ -146,9 +145,12 @@ bool ThreeDOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 				_stream->readUint32BE(); // Unknown 0x00000004 compression ratio?
 				_stream->readUint32BE(); // Unknown 0x00000A2C
 
-				_audioTrack = new StreamAudioTrack(audioCodecTag, audioSampleRate, audioChannels, getSoundType());
-				addTrack(_audioTrack);
+				StreamAudioTrack *track = new StreamAudioTrack(audioCodecTag, audioSampleRate, audioChannels,
+									       getSoundType(), audioTrackId);
+				addTrack(track);
+				_audioTracks.push_back(track);
 				break;
+			}
 
 			case MKTAG('S', 'S', 'M', 'P'):
 				// Audio data
@@ -178,7 +180,7 @@ bool ThreeDOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 			return false;
 		}
 
-		if ((_videoTrack) && (_audioTrack))
+		if ((_videoTrack) && (!_audioTracks.empty()))
 			break;
 
 		// Seek to next chunk
@@ -186,7 +188,7 @@ bool ThreeDOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
 	}
 
 	// Bail if we didn't find video + audio
-	if ((!_videoTrack) || (!_audioTrack)) {
+	if ((!_videoTrack) || (_audioTracks.empty())) {
 		close();
 		return false;
 	}
@@ -221,6 +223,7 @@ void ThreeDOMovieDecoder::readNextPacket() {
 	uint32 videoFrameSize = 0;
 	uint32 audioSubType   = 0;
 	uint32 audioBytes     = 0;
+	uint32 audioTrackId   = 0;
 	bool videoGotFrame = false;
 	bool videoDone     = false;
 	bool audioDone     = false;
@@ -232,7 +235,7 @@ void ThreeDOMovieDecoder::readNextPacket() {
 		_stream->seek(_streamAudioOffset);
 	}
 
-	if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) {
+	if (wantedAudioQueued <= _audioTracks[0]->getTotalAudioQueued()) {
 		// already got enough audio queued up
 		audioDone = true;
 	}
@@ -306,7 +309,8 @@ void ThreeDOMovieDecoder::readNextPacket() {
 			break;
 
 		case MKTAG('S','N','D','S'):
-			_stream->skip(8);
+			_stream->readUint32BE();
+			audioTrackId = _stream->readUint32BE();
 			audioSubType = _stream->readUint32BE();
 
 			switch (audioSubType) {
@@ -319,11 +323,16 @@ void ThreeDOMovieDecoder::readNextPacket() {
 				if (_streamAudioOffset <= chunkOffset) {
 					// We are at an offset that is still relevant to audio decoding
 					if (!audioDone) {
+						uint queued = 0;
 						audioBytes = _stream->readUint32BE();
-						_audioTrack->queueAudio(_stream, audioBytes);
+						for (uint i = 0; i < _audioTracks.size(); i++)
+							if (_audioTracks[i]->matchesId(audioTrackId)) {
+								_audioTracks[i]->queueAudio(_stream, audioBytes);
+								queued = _audioTracks[i]->getTotalAudioQueued();
+							}
 
 						_streamAudioOffset = nextChunkOffset;
-						if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) {
+						if (wantedAudioQueued <= queued) {
 							// Got enough audio
 							audioDone = true;
 						}
@@ -393,7 +402,7 @@ void ThreeDOMovieDecoder::StreamVideoTrack::decodeFrame(Common::SeekableReadStre
 	_curFrame++;
 }
 
-ThreeDOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels, Audio::Mixer::SoundType soundType) :
+ThreeDOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels, Audio::Mixer::SoundType soundType, uint32 trackId) :
 		AudioTrack(soundType) {
 	switch (codecTag) {
 	case MKTAG('A','D','P','4'):
@@ -409,6 +418,7 @@ ThreeDOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint32
 
 	_codecTag    = codecTag;
 	_sampleRate  = sampleRate;
+	_trackId     = trackId;
 	switch (channels) {
 	case 1:
 		_stereo = false;
@@ -433,6 +443,10 @@ ThreeDOMovieDecoder::StreamAudioTrack::~StreamAudioTrack() {
 //	free(_SDX2_PersistentSpace);
 }
 
+bool ThreeDOMovieDecoder::StreamAudioTrack::matchesId(uint tid) {
+	return _trackId == tid;
+}
+
 void ThreeDOMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, uint32 size) {
 	Common::SeekableReadStream *compressedAudioStream = 0;
 	Audio::RewindableAudioStream *audioStream = 0;
diff --git a/video/3do_decoder.h b/video/3do_decoder.h
index e03bd8a1ed..908bcdcc36 100644
--- a/video/3do_decoder.h
+++ b/video/3do_decoder.h
@@ -58,6 +58,8 @@ public:
 
 protected:
 	void readNextPacket() override;
+	bool supportsAudioTrackSwitching() const override { return true; }
+	AudioTrack *getAudioTrack(int index) override;
 
 private:
 	int32 _streamVideoOffset; /* current stream offset for video decoding */
@@ -95,11 +97,13 @@ private:
 
 	class StreamAudioTrack : public AudioTrack {
 	public:
-		StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels, Audio::Mixer::SoundType soundType);
+		StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels, Audio::Mixer::SoundType soundType, uint32 trackId);
 		~StreamAudioTrack() override;
 
 		void queueAudio(Common::SeekableReadStream *stream, uint32 size);
 
+		bool matchesId(uint trackId);
+
 	protected:
 		Audio::AudioStream *getAudioStream() const override;
 
@@ -116,6 +120,7 @@ private:
 		uint32 _codecTag;
 		uint16 _sampleRate;
 		bool   _stereo;
+		uint32 _trackId;
 
 		Audio::audio_3DO_ADP4_PersistentSpace _ADP4_PersistentSpace;
 		Audio::audio_3DO_SDX2_PersistentSpace _SDX2_PersistentSpace;
@@ -123,7 +128,7 @@ private:
 
 	Common::SeekableReadStream *_stream;
 	StreamVideoTrack *_videoTrack;
-	StreamAudioTrack *_audioTrack;
+	Common::Array<StreamAudioTrack *> _audioTracks;
 };
 
 } // End of namespace Sherlock


Commit: 125fd9d80cfa46e2ae834281304654856ce42aec
    https://github.com/scummvm/scummvm/commit/125fd9d80cfa46e2ae834281304654856ce42aec
Author: Vladimir Serbinenko (phcoder at google.com)
Date: 2020-10-24T00:59:01+02:00

Commit Message:
PLUMBERS: support for 3DO version

It was fun to reverse engineer but the game is garbage, so I didn't test
it a lot, please report bugs.

Currently missing parts:

* effect when changing between scenes
* 3DO boot logo
* return to previous scene
* hide cursor

Changed paths:
    engines/plumbers/detection.cpp
    engines/plumbers/plumbers.cpp
    engines/plumbers/plumbers.h


diff --git a/engines/plumbers/detection.cpp b/engines/plumbers/detection.cpp
index 101a3cba41..635b343279 100644
--- a/engines/plumbers/detection.cpp
+++ b/engines/plumbers/detection.cpp
@@ -44,7 +44,6 @@ static const ADGameDescription gameDescriptions[] = {
 		GUIO1(GUIO_NOMIDI)
 	},
 
-	/*
 	// Plumbers 3DO version
 	{
 		"plumbers",
@@ -55,7 +54,6 @@ static const ADGameDescription gameDescriptions[] = {
 		ADGF_UNSTABLE,
 		GUIO1(GUIO_NOMIDI)
 	},
-	*/
 
 	AD_TABLE_END_MARKER
 };
diff --git a/engines/plumbers/plumbers.cpp b/engines/plumbers/plumbers.cpp
index 149d9b29b9..1b1c7ce974 100644
--- a/engines/plumbers/plumbers.cpp
+++ b/engines/plumbers/plumbers.cpp
@@ -23,6 +23,7 @@
 #include "plumbers/plumbers.h"
 #include "plumbers/console.h"
 
+#include "audio/decoders/aiff.h"
 #include "audio/decoders/wave.h"
 #include "audio/audiostream.h"
 
@@ -43,9 +44,32 @@
 #include "graphics/surface.h"
 
 #include "image/bmp.h"
+#include "image/cel_3do.h"
+
+#include "video/3do_decoder.h"
 
 namespace Plumbers {
 
+static const Common::KeyCode cheatKbd[] = {
+	Common::KEYCODE_UP,
+	Common::KEYCODE_DOWN,
+	Common::KEYCODE_RIGHT,
+	Common::KEYCODE_LEFT,
+	Common::KEYCODE_DOWN,
+	Common::KEYCODE_RIGHT,
+	Common::KEYCODE_RETURN
+};
+
+static const Common::JoystickButton cheatJoy[] = {
+	Common::JOYSTICK_BUTTON_DPAD_UP,
+	Common::JOYSTICK_BUTTON_DPAD_DOWN,
+	Common::JOYSTICK_BUTTON_DPAD_RIGHT,
+	Common::JOYSTICK_BUTTON_DPAD_LEFT,
+	Common::JOYSTICK_BUTTON_DPAD_DOWN,
+	Common::JOYSTICK_BUTTON_DPAD_RIGHT,
+	Common::JOYSTICK_BUTTON_X
+};
+
 PlumbersGame::PlumbersGame(OSystem *syst, const ADGameDescription *gameDesc) :
 		Engine(syst), _gameDescription(gameDesc), _console(0), _image(0) {
 	_timerInstalled = false;
@@ -59,6 +83,8 @@ PlumbersGame::PlumbersGame(OSystem *syst, const ADGameDescription *gameDesc) :
 	_curChoice = 0;
 	_totScene = -1;
 	_totScore = 0;
+	_cheatEnabled = false;
+	_cheatFSM = 0;
 
 	DebugMan.addDebugChannel(kDebugGeneral, "general", "General debug level");
 }
@@ -93,9 +119,83 @@ static const byte cursorPalette[] = {
 	0xff, 0xff, 0xff   // White
 };
 
+// TODO(3do):
+// * effect when changing between scenes
+// * 3do boot logo
+// * return to previous scene
+// * hide cursor
+
+void PlumbersGame::updateHiLite() {
+	_actions.push(Redraw);
+	if (_hiLite < 0)
+		return;
+	if (_scenes[_curSceneIdx]._style == Scene::STYLE_DECISION_MIKE) {
+		playSound(Common::String::format("%s/%s%dS.Aiff",
+						 _scenes[_curSceneIdx]._sceneName.c_str(),
+						 _scenes[_curSceneIdx]._decisionBitmap.c_str(), _hiLite + 1));
+	} else if (_scenes[_curSceneIdx]._style == Scene::STYLE_DECISION_TUN) {
+		playSound(Common::String::format("%s%c.aiff", _scenes[_curSceneIdx]._sceneName.c_str(), _hiLite + 'a'));
+	}
+}
+
+void PlumbersGame::joyUp() {
+	int decNum = _scenes[_curSceneIdx]._decisionChoices;
+	if (!_leftButtonDownFl || !_hiLiteEnabled)
+		return;
+	_kbdHiLite = _kbdHiLite < 0 ? 0 : (_kbdHiLite + decNum - 1) % decNum;
+	_hiLite = _kbdHiLite;
+	updateHiLite();
+}
+
+void PlumbersGame::joyDown() {
+	if (!_leftButtonDownFl || !_hiLiteEnabled)
+		return;
+	int decNum = _scenes[_curSceneIdx]._decisionChoices;
+	_kbdHiLite = _kbdHiLite < 0 ? 0 : (_kbdHiLite + 1) % decNum;
+	_hiLite = _kbdHiLite;
+	updateHiLite();
+}
+
+void PlumbersGame::joyA() {
+	if (_kbdHiLite < 0 || !_leftButtonDownFl || !_hiLiteEnabled)
+		return;
+	debugC(5, kDebugGeneral, "Accepting enter press with choice = %d", _kbdHiLite);
+	_curChoice = _kbdHiLite;
+	_totScore += _scenes[_curSceneIdx]._choices[_kbdHiLite]._points;
+	_actions.push(ChangeScene);
+	_leftButtonDownFl = false;
+}
+
+void PlumbersGame::skipVideo() {
+	if (_scenes[_curSceneIdx]._sceneName == "janp1weaver"
+	    || _scenes[_curSceneIdx]._sceneName == "janp2weaver") {
+		// Skip janp2weaver and janp3weaver
+		_curSceneIdx = getSceneNumb("titleweaver");
+		_actions.push(ShowScene);
+	} else {
+		_actions.push(ChangeScene);
+	}
+	_videoDecoder->close();
+	delete _videoDecoder;
+	_videoDecoder = nullptr;
+}
+
+
 Common::Error PlumbersGame::run() {
-	initGraphics(640, 480);
-	_image = new Image::BitmapDecoder();
+	if (getPlatform() == Common::kPlatform3DO) {
+		_image = new Image::Cel3DODecoder();
+		_ctrlHelpImage = new Image::Cel3DODecoder();
+		_screenW = 320;
+		_screenH = 240;
+		Graphics::PixelFormat pf(2, 5, 5, 5, 1, 10,  5,  0, 15);
+		initGraphics(_screenW, _screenH, &pf);
+	} else {
+		_image = new Image::BitmapDecoder();
+		_screenW = 640;
+		_screenH = 480;
+		initGraphics(_screenW, _screenH);
+	}
+
 	_console = new Console();
 	setDebugger(_console);
 
@@ -103,14 +203,26 @@ Common::Error PlumbersGame::run() {
 	CursorMan.replaceCursorPalette(cursorPalette, 0, 3);
 	CursorMan.showMouse(true);
 
-	readTables("game.bin");
+	if (getPlatform() == Common::kPlatform3DO)
+		readTables3DO("launchme");
+	else
+		readTablesPC("game.bin");
 
 	_showScoreFl = false;
 	_leftButtonDownFl = false;
 	_endGameFl = false;
+	// PC uses a palette, so we don't do highlighting.
+	// Original does the same
+	_hiLiteEnabled = getPlatform() == Common::kPlatform3DO;
 	_totScore = 0;
 	_curSceneIdx = _prvSceneIdx = 0;
 	_curChoice = 0;
+	_kbdHiLite = -1;
+	_mouseHiLite = -1;
+	_hiLite = -1;
+	_mouseHiLite = -1;
+	_kbdHiLite = -1;
+	_leftShoulderPressed = false;
 	_actions.clear();
 	_actions.push(ShowScene);
 
@@ -127,13 +239,10 @@ Common::Error PlumbersGame::run() {
 
 			case Common::EVENT_LBUTTONDOWN:
 				if (_leftButtonDownFl) {
-					Common::Point mousePos = g_system->getEventManager()->getMousePos();
-					for (_curChoice = 0; _curChoice < _scenes[_curSceneIdx]._decisionChoices; _curChoice++) {
-						if (_scenes[_curSceneIdx]._choices[_curChoice]._region.contains(mousePos))
-							break;
-					}
-					if (_curChoice < kMaxChoice) {
-						debugC(5, kDebugGeneral, "Accepting mouse click at %d : %d , choice = %d", mousePos.x, mousePos.y, _curChoice);
+					_curChoice = getMouseHiLite();
+
+					if (_curChoice >= 0 && _curChoice < _scenes[_curSceneIdx]._decisionChoices) {
+						debugC(5, kDebugGeneral, "Accepting mouse click with choice = %d", _curChoice);
 						_totScore += _scenes[_curSceneIdx]._choices[_curChoice]._points;
 						_actions.push(ChangeScene);
 						_leftButtonDownFl = false;
@@ -143,11 +252,94 @@ Common::Error PlumbersGame::run() {
 					onTimer(this);
 				}
 				break;
+			case Common::EVENT_JOYBUTTON_DOWN:
+				if (_videoDecoder) {
+					if (_cheatFSM < ARRAYSIZE(cheatJoy) && event.joystick.button == cheatJoy[_cheatFSM]) {
+						_cheatFSM++;
+						if (_cheatFSM == ARRAYSIZE(cheatJoy)) {
+							debugC(1, kDebugGeneral, "Cheat enabled");
+							_cheatEnabled = true;
+						}
+					} else if (event.joystick.button == cheatJoy[0])
+						_cheatFSM = 1;
+					else
+						_cheatFSM = 0;
+				}
+				if (_videoDecoder && (event.joystick.button == Common::JOYSTICK_BUTTON_A ||
+						      event.joystick.button == Common::JOYSTICK_BUTTON_B ||
+						      event.joystick.button == Common::JOYSTICK_BUTTON_X)) {
+					skipVideo();
+				} else if (event.joystick.button == Common::JOYSTICK_BUTTON_DPAD_UP ||
+					   event.joystick.button == Common::JOYSTICK_BUTTON_DPAD_LEFT) {
+					joyUp();
+				} else if (event.joystick.button == Common::JOYSTICK_BUTTON_DPAD_DOWN ||
+					   event.joystick.button == Common::JOYSTICK_BUTTON_DPAD_RIGHT) {
+					joyDown();
+				} else if (event.joystick.button == Common::JOYSTICK_BUTTON_A) {
+					joyA();
+				} else if (event.joystick.button == Common::JOYSTICK_BUTTON_LEFT_SHOULDER) {
+					_leftShoulderPressed = true;
+					if (_leftButtonDownFl && _ctrlHelpImage)
+						_actions.push(Redraw);
+				}
+				break;
+			case Common::EVENT_JOYBUTTON_UP:
+				if (event.joystick.button == Common::JOYSTICK_BUTTON_LEFT_SHOULDER) {
+					_leftShoulderPressed = false;
+					if (_leftButtonDownFl && _ctrlHelpImage)
+						_actions.push(Redraw);
+				}
+				break;
+			case Common::EVENT_KEYDOWN:
+				if (_videoDecoder) {
+					if (_cheatFSM < ARRAYSIZE(cheatKbd) && event.kbd.keycode == cheatKbd[_cheatFSM]) {
+						_cheatFSM++;
+						if (_cheatFSM == ARRAYSIZE(cheatKbd)) {
+							debugC(1, kDebugGeneral, "Cheat enabled");
+							_cheatEnabled = true;
+						}
+					} else if (event.kbd.keycode == cheatKbd[0])
+						_cheatFSM = 1;
+					else
+						_cheatFSM = 0;
+				}
+				if (event.kbd.keycode == Common::KEYCODE_SPACE && _videoDecoder) {
+					skipVideo();
+				} else if ((event.kbd.keycode == Common::KEYCODE_UP ||
+					    event.kbd.keycode == Common::KEYCODE_LEFT)) {
+					joyUp();
+				} else if ((event.kbd.keycode == Common::KEYCODE_DOWN ||
+					    event.kbd.keycode == Common::KEYCODE_RIGHT)) {
+					joyDown();
+				} else if (event.kbd.keycode == Common::KEYCODE_RETURN) {
+					joyA();
+				} else if (event.kbd.keycode == Common::KEYCODE_q) {
+					_leftShoulderPressed = true;
+					if (_leftButtonDownFl && _ctrlHelpImage)
+						_actions.push(Redraw);
+				}
+				break;
+			case Common::EVENT_KEYUP:
+				if (event.kbd.keycode == Common::KEYCODE_q) {
+					_leftShoulderPressed = false;
+					if (_leftButtonDownFl && _ctrlHelpImage)
+						_actions.push(Redraw);
+				}
+				break;
 			default:
 				break;
 			}
 		}
 
+		if (_leftButtonDownFl) {
+			int nh = getMouseHiLite();
+			if (nh != _mouseHiLite) {
+				_mouseHiLite = nh;
+				_hiLite = _mouseHiLite;
+				updateHiLite();
+			}
+		}
+
 		while (!_actions.empty()) {
 			switch (_actions.pop()) {
 			case Redraw:
@@ -163,15 +355,26 @@ Common::Error PlumbersGame::run() {
 				changeScene();
 				break;
 			case PlaySound:
-				playSound();
+				playSound(_scenes[_curSceneIdx]._sceneName + "/" + _scenes[_curSceneIdx]._waveFilename);
 				break;
 			default:
 				break;
 			}
 		}
 
+		if (_videoDecoder) {
+			if (_videoDecoder->endOfVideo()) {
+				_actions.push(ChangeScene);
+				_videoDecoder->close();
+				delete _videoDecoder;
+				_videoDecoder = nullptr;
+			} else if (_videoDecoder->needsUpdate()) {
+				drawScreen();
+			}
+		}
+
 		g_system->updateScreen();
-		g_system->delayMillis(10);
+		g_system->delayMillis(_videoDecoder ? (1000 / 60) : 10);
 	}
 
 	g_system->getTimerManager()->removeTimerProc(onTimer);
@@ -180,22 +383,75 @@ Common::Error PlumbersGame::run() {
 	return Common::kNoError;
 }
 
-void PlumbersGame::loadImage(const Common::String &dirname, const Common::String &filename) {
-	Common::String name = dirname + "/" + filename;
+void PlumbersGame::loadImage(const Common::String &name) {
 	debugC(1, kDebugGeneral, "%s : %s", __FUNCTION__, name.c_str());
 	Common::File file;
 	if (!file.open(name))
 		error("unable to load image %s", name.c_str());
 
 	_image->loadStream(file);
+	delete _compositeSurface;
+	_compositeSurface = nullptr;
+}
+
+// TODO: discover correct offsets
+Common::Point getMikeStart(uint num, uint total) {
+	if (total == 2)
+		return Common::Point(140 * num + 10, 120 * num + 10);
+	return Common::Point(60 * num + 20, 70 * num + 20);
+}
+
+// TODO: discover correct offsets
+Common::Point getMikeSize(uint total) {
+	if (total == 2)
+		return Common::Point(80, 100);
+	return Common::Point(80, 60);
+}
+
+void PlumbersGame::loadMikeDecision(const Common::String &dirname, const Common::String &baseFilename, uint num) {
+	Common::String baseName = dirname + "/" + baseFilename;
+	debugC(1, kDebugGeneral, "%s : %s", __FUNCTION__, baseName.c_str());
+	Graphics::Surface *surf = new Graphics::Surface();
+	surf->create(_screenW, _screenH, Graphics::PixelFormat(2, 5, 5, 5, 1, 10,  5,  0, 15));
+
+	delete _compositeSurface;
+	_compositeSurface = nullptr;
+
+	for (uint i = 0; i < num; i++) {
+		Common::Point p = getMikeStart(i, num);
+		Common::Point sz = getMikeSize(num);
+		Common::File fileP;
+		Common::String nameP = Common::String::format("%s%dP.CEL", baseName.c_str(), i + 1);
+		if (!fileP.open(nameP))
+			error("unable to load image %s", nameP.c_str());
+
+		_image->loadStream(fileP);
+		surf->copyRectToSurface(*_image->getSurface(), p.x, p.y,
+					Common::Rect(0, 0, sz.x, sz.y));
+
+		Common::File fileW;
+		Common::String nameW = Common::String::format("%s%dW.CEL", baseName.c_str(), i + 1);
+		if (!fileW.open(nameW))
+			error("unable to load image %s", nameW.c_str());
+		
+		_image->loadStream(fileW);
+		surf->copyRectToSurface(*_image->getSurface(), p.x + sz.x, p.y,
+					Common::Rect(0, 0, sz.x, sz.y));
+	}
+
+	_compositeSurface = surf;
+
+	Common::File fileCtrl;
+	if (fileCtrl.open(dirname + "/CONTROLHELP.CEL"))
+		_ctrlHelpImage->loadStream(fileCtrl);
 }
 
 void PlumbersGame::drawScreen() {
-	debugC(1, kDebugGeneral, "%s : %s", __FUNCTION__, _image ? "YES" : "NO");
-	if (_image) {
+	debugC(_videoDecoder ? 10 : 1, kDebugGeneral, "%s : %s", __FUNCTION__, _image ? "YES" : "NO");
+	if (_videoDecoder ? _videoDecoder->needsUpdate() : _image || _compositeSurface) {
 		if (_setDurationFl) {
 			g_system->getTimerManager()->removeTimerProc(onTimer);
-			g_system->getTimerManager()->installTimerProc(onTimer, _bitmaps[_curBitmapIdx]._duration * 100 * 1000, this, "timer");
+			g_system->getTimerManager()->installTimerProc(onTimer, _bitmaps[_curBitmapIdx]._duration * 1000, this, "timer");
 			_timerInstalled = true;
 			_actions.push(UpdateScene);
 		}
@@ -203,40 +459,85 @@ void PlumbersGame::drawScreen() {
 		Graphics::Surface *screen = g_system->lockScreen();
 		screen->fillRect(Common::Rect(0, 0, g_system->getWidth(), g_system->getHeight()), 0);
 
-		const Graphics::Surface *surface = _image->getSurface();
+		const Graphics::Surface *surface;
+		bool ctrlHelp = false;
+		if (_leftShoulderPressed && _leftButtonDownFl && _ctrlHelpImage) {
+			surface = _ctrlHelpImage->getSurface();
+			ctrlHelp = true;
+		} else if (_videoDecoder)
+			surface = _videoDecoder->decodeNextFrame();
+		else if (_compositeSurface)
+			surface = _compositeSurface;
+		else
+			surface = _image->getSurface();
+
+		Graphics::Surface modSurf;
+		bool modded = false;
+
+		if (_hiLiteEnabled && _hiLite >= 0 && _leftButtonDownFl && !ctrlHelp) {
+			Graphics::PixelFormat pf(2, 5, 5, 5, 1, 10,  5,  0, 15);
+			modSurf.create(surface->w, surface->h, pf);
+			modSurf.copyRectToSurface(*surface, 0, 0, Common::Rect(0, 0, surface->w, surface->h));
+			const Common::Rect rec = _scenes[_curSceneIdx]._choices[_hiLite]._region;
+			
+			for (int y = rec.top; y <= rec.bottom; y++) {
+				uint16 *p = (uint16 *) modSurf.getPixels() + modSurf.w * y + rec.left;
+				for (int x = rec.left; x < rec.right; x++, p++) {
+					uint r, g, b;
+					r = (*p >> 10) & 0x1f;
+					g = (*p >> 5) & 0x1f;
+					b = (*p >> 0) & 0x1f;
+					// TODO: figure out the correct multipliers
+					r = MIN<int>(3 * r / 2, 0x1f);
+					g = MIN<int>(3 * g / 2, 0x1f);
+					b = MIN<int>(3 * b / 2, 0x1f);
+					*p = (*p & 0x8000) | (r << 10) | (g << 5) | (b);
+				}
+			}
+			modded = true;
+		}
 
-		int w = CLIP<int>(surface->w, 0, 640);
-		int h = CLIP<int>(surface->h, 0, 480);
+		int w = CLIP<int>(surface->w, 0, _screenW);
+		int h = CLIP<int>(surface->h, 0, _screenH);
 
-		int x = (640 - w) / 2;
-		int y = (480 - h) / 2;
+		int x = (_screenW - w) / 2;
+		int y = (_screenH - h) / 2;
 
-		screen->copyRectToSurface(*surface, x, y, Common::Rect(0, 0, w, h));
+		screen->copyRectToSurface(modded ? modSurf : *surface, x, y, Common::Rect(0, 0, w, h));
 
 		if (_showScoreFl) {
 			Common::String score = Common::String::format("Your Score is: %ld", _totScore);
-			const Graphics::Font &font(*FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont));
-			Common::Rect rect(10, 440, 200, 440 + font.getFontHeight());
-			screen->fillRect(rect, 0);
-			font.drawString(screen, score, rect.left, rect.top, 190, 255, Graphics::kTextAlignCenter);
+			const Graphics::Font &font(*FontMan.getFontByUsage(
+						       _screenW >= 640 ? Graphics::FontManager::kBigGUIFont : Graphics::FontManager::kGUIFont));
+			int scoreTop = _screenH - _screenH / 12;
+			int scoreMaxWidth = _screenW >= 640 ? 200 : 150;
+			uint scoreColor = screen->format.bytesPerPixel == 1 ? 0xff : screen->format.RGBToColor(0xff, 0xff, 0xff);
+			Common::Rect rect(10, scoreTop, scoreMaxWidth, scoreTop + font.getFontHeight());
+			if (getPlatform() != Common::kPlatform3DO)
+				screen->fillRect(rect, 0);
+			font.drawString(screen, score, rect.left, rect.top, scoreMaxWidth - 10,
+					scoreColor, Graphics::kTextAlignCenter);
 			_showScoreFl = false;
 		}
 
 		g_system->unlockScreen();
-		g_system->getPaletteManager()->setPalette(_image->getPalette(), 0, 256);
+		if (_image->getPalette() != nullptr)
+			g_system->getPaletteManager()->setPalette(_image->getPalette(), 0, 256);
 		g_system->updateScreen();
 	}
 }
 
-void PlumbersGame::playSound() {
-	Common::String name = _scenes[_curSceneIdx]._sceneName + "/" + _scenes[_curSceneIdx]._waveFilename;
+void PlumbersGame::playSound(const Common::String &name) {
 	debugC(3, kDebugGeneral, "%s : %s", __FUNCTION__, name.c_str());
 	Common::File *file = new Common::File();
 	if (!file->open(name))
 		error("unable to load sound %s", name.c_str());
 
-	Audio::RewindableAudioStream *audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES);
-	Audio::AudioStream *stream = audioStream;
+	Audio::AudioStream *stream;
+	if (name.hasSuffix(".aiff") || name.hasSuffix(".Aiff"))
+		stream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);
+	else
+		stream = Audio::makeWAVStream(file, DisposeAfterUse::YES);
 	stopSound();
 	_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, stream, -1, Audio::Mixer::kMaxChannelVolume);
 }
@@ -249,53 +550,97 @@ void PlumbersGame::stopSound() {
 
 void PlumbersGame::showScene() {
 	debugC(1, kDebugGeneral, "%s : %d", __FUNCTION__, _curSceneIdx);
-	_curBitmapIdx = _scenes[_curSceneIdx]._startBitmap;
-	loadImage(_scenes[_curSceneIdx]._sceneName, _bitmaps[_curBitmapIdx]._filename);
+	_curBitmapIdx = _scenes[_curSceneIdx]._startBitmap - 1;
+	updateScene();
+	if (_scenes[_curSceneIdx]._waveFilename != "")
+		_actions.push(PlaySound);
 	_actions.push(Redraw);
-	_setDurationFl = true;
-	_actions.push(PlaySound);
+}
+
+int PlumbersGame::getMouseHiLite() {
+	Common::Point mousePos = g_system->getEventManager()->getMousePos();
+	for (int i = 0; i < _scenes[_curSceneIdx]._decisionChoices && i < kMaxChoice; i++) {
+		if (_scenes[_curSceneIdx]._choices[i]._region.contains(mousePos))
+			return i;
+	}
+
+	return -1;
 }
 
 void PlumbersGame::updateScene() {
 	debugC(2, kDebugGeneral, "%s : %d", __FUNCTION__, _curBitmapIdx);
 	_curBitmapIdx++;
 	if (_curBitmapIdx >= _scenes[_curSceneIdx]._startBitmap + _scenes[_curSceneIdx]._bitmapNum) {
-		if (_scenes[_curSceneIdx]._decisionChoices == 1) {
+		if (_scenes[_curSceneIdx]._style == Scene::STYLE_VIDEO) {
+			_videoDecoder = new Video::ThreeDOMovieDecoder();
+			_curChoice = 0;
+			if (!_videoDecoder->loadFile(_scenes[_curSceneIdx]._sceneName)) {
+				_actions.push(ChangeScene);
+				return;
+			}
+			_videoDecoder->start();
+		} else if (_scenes[_curSceneIdx]._decisionChoices == 1) {
 			_curChoice = 0;
 			_actions.push(ChangeScene);
 		} else {
 			_showScoreFl = true;
 			_leftButtonDownFl = true;
 			_setDurationFl = false;
-			loadImage(_scenes[_curSceneIdx]._sceneName, _scenes[_curSceneIdx]._decisionBitmap);
+			if (_scenes[_curSceneIdx]._style == Scene::STYLE_DECISION_MIKE) {
+				loadMikeDecision(_scenes[_curSceneIdx]._sceneName, _scenes[_curSceneIdx]._decisionBitmap,
+					_scenes[_curSceneIdx]._decisionChoices);
+				_hiLite = 0;
+				_kbdHiLite = 0;
+				updateHiLite();
+			} else if (_scenes[_curSceneIdx]._style == Scene::STYLE_DECISION_TUN) {
+				loadImage(_scenes[_curSceneIdx]._sceneName + ".cel");
+				_hiLite = 0;
+				_kbdHiLite = 0;
+				updateHiLite();
+				Common::File fileCtrl;
+				if (fileCtrl.open("tuntest/dec/controlhelp.cel"))
+					_ctrlHelpImage->loadStream(fileCtrl);
+			} else {
+				loadImage(_scenes[_curSceneIdx]._sceneName + "/" + _scenes[_curSceneIdx]._decisionBitmap);
+				_hiLite = -1;
+				_kbdHiLite = -1;
+			}
+
+			_mouseHiLite = getMouseHiLite();
 		}
 	} else {
-		loadImage(_scenes[_curSceneIdx]._sceneName, _bitmaps[_curBitmapIdx]._filename);
+		loadImage(_scenes[_curSceneIdx]._sceneName  + "/" + _bitmaps[_curBitmapIdx]._filename);
 		_setDurationFl = true;
 	}
 }
 
 void PlumbersGame::changeScene() {
 	debugC(1, kDebugGeneral, "%s : %d", __FUNCTION__, _curChoice);
-	if (_scenes[_curSceneIdx]._choices[_curChoice]._sceneIdx == -1) {
+	if (_scenes[_curSceneIdx]._choices[_curChoice]._sceneName == "SC-1") {
 		_curSceneIdx = _prvSceneIdx;
 		_curBitmapIdx = 9999;
 		_actions.push(UpdateScene);
 		_actions.push(Redraw);
-	} else if (_scenes[_curSceneIdx]._choices[_curChoice]._sceneIdx == 32767) {
+	} else if (_scenes[_curSceneIdx]._choices[_curChoice]._sceneName == "restart") {
+		_curSceneIdx = 0;
+		_totScore = 0;
+		_actions.push(UpdateScene);
+		_actions.push(Redraw);
+	} else if (_scenes[_curSceneIdx]._choices[_curChoice]._sceneName == "SC32767"
+		|| _scenes[_curSceneIdx]._choices[_curChoice]._sceneName == "end") {
 		_endGameFl = true;
 	} else {
 		if (_scenes[_curSceneIdx]._decisionChoices > 1)
 			_prvSceneIdx = _curSceneIdx;
 		if (_scenes[_curSceneIdx]._choices[_curChoice]._skipScene) {
-			_curSceneIdx = getSceneNumb(_scenes[_curSceneIdx]._choices[_curChoice]._sceneIdx);
+			_curSceneIdx = getSceneNumb(_scenes[_curSceneIdx]._choices[_curChoice]._sceneName);
 			_curBitmapIdx = 9999;
 			_actions.push(UpdateScene);
 			_actions.push(Redraw);
 			g_system->getTimerManager()->removeTimerProc(onTimer);
 			_timerInstalled = false;
 		} else {
-			_curSceneIdx = getSceneNumb(_scenes[_curSceneIdx]._choices[_curChoice]._sceneIdx);
+			_curSceneIdx = getSceneNumb(_scenes[_curSceneIdx]._choices[_curChoice]._sceneName);
 			_actions.push(ShowScene);
 		}
 	}
@@ -318,7 +663,7 @@ void PlumbersGame::initTables() {
 	memset(_bitmaps, 0, sizeof(_bitmaps));
 }
 
-void PlumbersGame::readTables(const Common::String &fileName) {
+void PlumbersGame::readTablesPC(const Common::String &fileName) {
 	Common::File file;
 	if (!file.open(fileName))
 		error("sReadTables(): Error reading BIN file");
@@ -341,10 +686,11 @@ void PlumbersGame::readTables(const Common::String &fileName) {
 		_scenes[i]._waveFilename = Common::String(buf);
 		file.read(buf, kMaxName);
 		_scenes[i]._decisionBitmap = Common::String(buf);
+		_scenes[i]._style = Scene::STYLE_PC;
 
 		for (int j = 0; j < kMaxChoice; j++) {
 			_scenes[i]._choices[j]._points = file.readSint32LE();
-			_scenes[i]._choices[j]._sceneIdx = file.readSint16LE();
+			_scenes[i]._choices[j]._sceneName = Common::String::format("SC%02d", file.readSint16LE());
 			_scenes[i]._choices[j]._skipScene = file.readSint16LE();
 			int left = file.readSint16LE();
 			int top = file.readSint16LE();
@@ -355,18 +701,317 @@ void PlumbersGame::readTables(const Common::String &fileName) {
 	}
 
 	for (int i = 0; i < kMaxBitmaps; i++) {
-		_bitmaps[i]._duration = file.readSint16LE();
+		_bitmaps[i]._duration = file.readSint16LE() * 100;
 		file.read(buf, kMaxName);
 		_bitmaps[i]._filename = Common::String(buf);
 	}
 }
 
-int PlumbersGame::getSceneNumb(int sNo) {
-	debugC(1, kDebugGeneral, "%s : %d", __FUNCTION__, sNo);
-	Common::String testString = Common::String::format("SC%02d", sNo);
+static void makeMikeDecision(Scene &scene, uint num) {
+	scene._bitmapNum = 0;
+	scene._startBitmap = 0;
+	scene._decisionChoices = num;
+	scene._waveFilename = "";
+	scene._style = Scene::STYLE_DECISION_MIKE;
+
+	Common::Point sz = getMikeSize(num);
+
+	for (uint i = 0; i < num; i++) {
+		Common::Point ms = getMikeStart(i, num);
+		scene._choices[i]._region = Common::Rect(ms.x, ms.y, ms.x + 2 * sz.x, ms.y + sz.y);
+	}
+}
+
+static void makeVideo(Scene &scene, const Common::String &videoName, const Common::String &nextScene) {
+	scene._bitmapNum = 0;
+	scene._startBitmap = 0;
+	scene._decisionChoices = 1;
+	scene._waveFilename = "";
+	scene._style = Scene::STYLE_VIDEO;
+	scene._sceneName = videoName;
+	scene._decisionBitmap = "";
+	scene._choices[0]._sceneName = nextScene;
+}
+
+static const struct {
+	const char *from;
+	struct {
+		const char *scene;
+		int points;
+	} to[kMaxChoice];
+} tungraph[] = {
+	{
+		"dec13",
+		{
+			{"sc15", -10000},
+			{"sc17", -10000},
+			{"sc20", -50000}
+		}
+	},
+	{
+		"dec16",
+		{
+			{"dec13", -20000},
+			{"restart", 0}
+		}
+	},
+	{
+
+		"dec18",
+		{
+			{"dec13", -30000},
+			{"restart", 0}
+		}
+	},
+	{
+		"dec20",
+		{
+			{"sc21", -90000},
+			{"sc22", -90000}
+		}
+	},
+	{
+		"dec22",
+		{
+			{"dec20", -90000},
+			{"restart", 0}
+		}
+	},
+	{
+		"dec23",
+		{
+			{"sc24", 50000},
+			{"sc28", -50000}
+		}
+	},
+	{
+		"dec25",
+		{
+			{"sc26", -75000},
+			{"sc27", -90000}
+		}
+	},
+	{
+		"dec26",
+		{
+			{"dec25", -75000},
+			{"restart", 0}
+		}
+	},
+	{
+		"dec28",
+		{
+			{"dec23", -75000},
+			{"restart", 0}
+		}
+	},
+	{
+		"dec29",
+		{
+			{"sc30", -20000},
+			{"sc31", 90000}
+		}
+	},
+	{
+		"dec30",
+		{
+			{"sc32", 0},
+			{"restart", 0},
+			{"end", 0}
+		}
+	},
+	{
+		"dec31",
+		{
+			{"dec29", -20000},
+			{"end", 0}
+		}
+
+	}
+};
+
+void PlumbersGame::readTables3DO(const Common::String &fileName) {
+	Common::File file;
+	if (!file.open(fileName))
+		error("sReadTables(): Error reading launchme file");
+
+	initTables();
+
+	file.seek(0x1ec08);
+
+	Common::HashMap<Common::String, int> imgCounter, firstImg;
+
+	uint bitmapCtr = 0;
+
+	for (; bitmapCtr < 287; bitmapCtr++) {
+		char buf[16];
+		file.read(buf, 16);
+		_bitmaps[bitmapCtr]._filename = Common::String(buf);
+		_bitmaps[bitmapCtr]._duration = (file.readSint32BE() * 1000) / 60;
+		Common::String scene = Common::String(buf).substr(0, 4);
+		scene.toLowercase();
+		imgCounter[scene]++;
+		if (!firstImg.contains(scene))
+			firstImg[scene] = bitmapCtr;
+	}
+
+	file.seek(0x205d0);
+
+	for (; bitmapCtr < 704; bitmapCtr++) {
+		char buf[16];
+		file.read(buf, 16);
+		_bitmaps[bitmapCtr]._filename = Common::String(buf);
+		_bitmaps[bitmapCtr]._duration = (file.readSint32BE() * 1000) / 60;
+		Common::String scene = Common::String(buf).substr(0, 4);
+		scene.toLowercase();
+		imgCounter[scene]++;
+		if (!firstImg.contains(scene))
+			firstImg[scene] = bitmapCtr;
+	}
+	
+	uint scPtr = 0;
+	makeVideo(_scenes[scPtr++], "kirinweaver", "janp1weaver");
+	makeVideo(_scenes[scPtr++], "janp1weaver", "janp2weaver");
+	makeVideo(_scenes[scPtr++], "janp2weaver", "janp3weaver");
+	makeVideo(_scenes[scPtr++], "janp3weaver", "titleweaver");
+	makeVideo(_scenes[scPtr++], "titleweaver", "miketest/sc00");
+
+	makeMikeDecision(_scenes[scPtr], 2);
+	_scenes[scPtr]._sceneName = "miketest/sc00";
+	_scenes[scPtr]._decisionBitmap = "DEC00";
+	_scenes[scPtr]._choices[0]._sceneName = "miketest/sc01";
+	_scenes[scPtr++]._choices[1]._sceneName = "miketest/sc07a";
+	
+	for (uint scNo = 1; scNo <= 13; scNo++, scPtr++) {
+		Common::String imgScene = scNo == 5 ?
+			"sc44" : Common::String::format("sc%02d", scNo);
+		_scenes[scPtr]._bitmapNum = imgCounter[imgScene];
+		_scenes[scPtr]._startBitmap = firstImg[imgScene];
+		_scenes[scPtr]._sceneName = scNo == 5 ? "miketest/sc04a" : Common::String::format("miketest/sc%02d", scNo);
+		_scenes[scPtr]._waveFilename = Common::String::format("DIA%02d.aiff", scNo == 5 ? 4 : scNo);
+		_scenes[scPtr]._style = Scene::STYLE_PC;
+		_scenes[scPtr]._decisionChoices = 1;
+		switch(scNo) {
+		case 4:
+		case 5:
+			_scenes[scPtr]._choices[0]._sceneName = "miketest/sc06";
+			break;
+		case 11:
+			_scenes[scPtr]._choices[0]._sceneName = "miketest/sc13";
+			break;
+		case 13:
+			_scenes[scPtr]._choices[0]._sceneName = "tuntest/dec/dec13";
+			break;
+		case 7:
+		case 8:
+		case 12:
+			_scenes[scPtr]._choices[0]._sceneName = Common::String::format("miketest/sc%02da", scNo);
+			break;
+		default:
+			_scenes[scPtr]._choices[0]._sceneName = Common::String::format("miketest/sc%02d", scNo + 1);
+			break;
+		}
+	}
+
+	makeMikeDecision(_scenes[scPtr], 3);
+	_scenes[scPtr]._sceneName = "miketest/sc07a";
+	_scenes[scPtr]._decisionBitmap = "DEC07";
+	_scenes[scPtr]._choices[0]._sceneName = "miketest/sc08";
+	_scenes[scPtr]._choices[0]._points = -10000;
+	_scenes[scPtr]._choices[1]._sceneName = "miketest/sc11";
+	_scenes[scPtr]._choices[1]._points = 10000;
+	_scenes[scPtr]._choices[2]._sceneName = "miketest/sc12";
+	_scenes[scPtr]._choices[2]._points = -20000;
+	scPtr++;
+
+	makeMikeDecision(_scenes[scPtr], 2);
+	_scenes[scPtr]._sceneName = "miketest/sc08a";
+	_scenes[scPtr]._decisionBitmap = "DEC08";
+	_scenes[scPtr]._choices[0]._sceneName = "miketest/sc09";
+	_scenes[scPtr]._choices[0]._points = 0;
+	_scenes[scPtr]._choices[1]._sceneName = "miketest/sc09";
+	_scenes[scPtr]._choices[1]._points = 10000;
+	scPtr++;
+
+	makeMikeDecision(_scenes[scPtr], 2);
+	_scenes[scPtr]._sceneName = "miketest/sc12a";
+	_scenes[scPtr]._decisionBitmap = "DEC12";
+	_scenes[scPtr]._choices[0]._sceneName = "miketest/sc07a";
+	_scenes[scPtr]._choices[0]._points = 0;
+	_scenes[scPtr]._choices[1]._sceneName = "restart";
+	_scenes[scPtr]._choices[1]._points = 0;
+	scPtr++;
+	
+	for (uint scNo = 15; scNo <= 32; scNo++) {
+		// there is no sc19
+		if (scNo == 19)
+			continue;
+		Common::String imgScene = Common::String::format("sc%02d", scNo);
+		_scenes[scPtr]._bitmapNum = imgCounter[imgScene];
+		_scenes[scPtr]._startBitmap = firstImg[imgScene];
+		_scenes[scPtr]._sceneName = Common::String::format("tuntest/sc%02d", scNo);
+		_scenes[scPtr]._waveFilename = Common::String::format("sc%02d.aiff", scNo);
+		_scenes[scPtr]._style = Scene::STYLE_PC;
+		_scenes[scPtr]._decisionChoices = 1;
+		if (scNo == 32)
+			_scenes[scPtr]._choices[0]._sceneName = "end";
+		else if (scNo == 16 || scNo == 18 || scNo == 20 || scNo == 22 || scNo == 23 || scNo == 25
+		    || scNo == 26 || scNo == 28 || scNo == 29 || scNo == 30 || scNo == 31)
+			_scenes[scPtr]._choices[0]._sceneName = Common::String::format("tuntest/dec/dec%02d", scNo);
+		else
+			_scenes[scPtr]._choices[0]._sceneName = Common::String::format("tuntest/sc%02d", scNo + 1);
+		scPtr++;
+	}
+
+	file.seek(0x20290);
+
+	for (int i = 0; i < 26; i++) {
+		char buf[16];
+		file.read(buf, 16);
+		uint32 x = file.readUint32BE();
+		uint32 y = file.readUint32BE();
+		uint32 w = file.readUint32BE();
+		uint32 h = file.readUint32BE();
+		Common::String shortName = Common::String(buf).substr(0, 5);
+		Common::String sceneName = "tuntest/dec/" + shortName;
+		if (i == 0 || _scenes[scPtr - 1]._sceneName != sceneName) {
+			_scenes[scPtr]._bitmapNum = 0;
+			_scenes[scPtr]._startBitmap = 0;
+			_scenes[scPtr]._decisionChoices = 0;
+			_scenes[scPtr]._waveFilename = "";
+			_scenes[scPtr]._style = Scene::STYLE_DECISION_TUN;
+			_scenes[scPtr]._sceneName = sceneName;
+			_scenes[scPtr]._decisionBitmap = shortName;
+			scPtr++;
+		}
+
+		Scene &scene = _scenes[scPtr - 1];
+		assert(scene._decisionChoices < kMaxChoice);
+		scene._choices[scene._decisionChoices]._region = Common::Rect(x, y, x + w, y + h);
+		for (uint j = 0 ; j < ARRAYSIZE(tungraph); j++) {
+			if (shortName == tungraph[j].from) {
+				Common::String target = tungraph[j].to[scene._decisionChoices].scene;
+				if (target[0] == 's')
+					scene._choices[scene._decisionChoices]._sceneName = "tuntest/" + target;
+				else
+					scene._choices[scene._decisionChoices]._sceneName = "tuntest/dec/" + target;
+				scene._choices[scene._decisionChoices]._points = tungraph[j].to[scene._decisionChoices].points;
+				break;
+			}
+		}
+		scene._decisionChoices++;
+	}
+
+	_totScene = scPtr;
+}
+
+int PlumbersGame::getSceneNumb(const Common::String &sName) {
+	debugC(1, kDebugGeneral, "%s : %s", __FUNCTION__, sName.c_str());
+	if (sName == "miketest/sc04" && _cheatEnabled)
+	        return getSceneNumb("miketest/sc04a");
 
 	for (int sCurScene = 0; sCurScene < _totScene; sCurScene++) {
-		if (testString == _scenes[sCurScene]._sceneName)
+		if (sName == _scenes[sCurScene]._sceneName)
 			return sCurScene;
 	}
 	return 0;
diff --git a/engines/plumbers/plumbers.h b/engines/plumbers/plumbers.h
index 0a38e938f7..8c15822911 100644
--- a/engines/plumbers/plumbers.h
+++ b/engines/plumbers/plumbers.h
@@ -32,12 +32,18 @@
 
 #include "audio/mixer.h"
 
+#include "video/3do_decoder.h"
+
 struct ADGameDescription;
 
 namespace Image {
 class ImageDecoder;
 }
 
+namespace Graphics {
+class Surface;
+}
+
 namespace Plumbers {
 
 class Console;
@@ -46,6 +52,31 @@ enum PlumbersDebugChannels {
 	kDebugGeneral = 1 << 0
 };
 
+static const int kMaxChoice = 3;
+
+struct Choice {
+	long _points;
+	int  _skipScene;
+	Common::Rect _region;
+	Common::String _sceneName;
+};
+	
+struct Scene {
+	int	 _bitmapNum;
+	int	 _startBitmap;
+	int	 _decisionChoices;
+	Common::String _sceneName;
+	Common::String _waveFilename;
+	Common::String _decisionBitmap;
+	enum {
+		STYLE_PC = 0,
+		STYLE_DECISION_MIKE = 1,
+		STYLE_DECISION_TUN = 2,
+		STYLE_VIDEO = 3
+	} _style;
+	Choice _choices[kMaxChoice];
+};
+
 class PlumbersGame : public Engine {
 public:
 	PlumbersGame(OSystem *syst, const ADGameDescription *gameDesc);
@@ -61,7 +92,6 @@ public:
 private:
 	static const int kMaxName = 13 + 1;
 	static const int kMaxBitmaps = 2000;
-	static const int kMaxChoice = 3;
 	static const int kMaxScene = 100;
 
 	struct {
@@ -69,22 +99,10 @@ private:
 		Common::String _filename;
 	} _bitmaps[kMaxBitmaps];
 
-	struct {
-		int	 _bitmapNum;
-		int	 _startBitmap;
-		int	 _decisionChoices;
-		Common::String _sceneName;
-		Common::String _waveFilename;
-		Common::String _decisionBitmap;
-		struct {
-			long _points;
-			int  _sceneIdx;
-			int  _skipScene;
-			Common::Rect _region;
-		} _choices[kMaxChoice];
-	} _scenes[kMaxScene];
+	Scene _scenes[kMaxScene];
 
 	Image::ImageDecoder *_image;
+	Image::ImageDecoder *_ctrlHelpImage;
 	Console *_console;
 
 	bool _showScoreFl;
@@ -97,6 +115,14 @@ private:
 	int	 _curChoice;
 	int	 _totScene;
 	long _totScore;
+	int _screenW, _screenH;
+	int _kbdHiLite;
+	int _mouseHiLite;
+	int _hiLite;
+	bool _hiLiteEnabled;
+	bool _cheatEnabled;
+	int _cheatFSM;
+	bool _leftShoulderPressed;
 
 	enum Action {
 		Redraw,
@@ -107,13 +133,17 @@ private:
 	};
 
 	Common::Queue<Action> _actions;
+	Graphics::Surface *_compositeSurface;
 
-	void loadImage(const Common::String &dirname, const Common::String &filename);
+	void loadImage(const Common::String &name);
+	void loadMikeDecision(const Common::String &dirname, const Common::String &baseFilename, uint num);
 	void drawScreen();
+	void updateHiLite();
 
 	Audio::SoundHandle _soundHandle;
+	Video::ThreeDOMovieDecoder *_videoDecoder;
 
-	void playSound();
+	void playSound(const Common::String &name);
 	void stopSound();
 
 	void showScene();
@@ -124,8 +154,15 @@ private:
 	static void onTimer(void *arg);
 
 	void initTables();
-	void readTables(const Common::String &fileName);
-	int getSceneNumb(int sNo);
+	void readTablesPC(const Common::String &fileName);
+    	void readTables3DO(const Common::String &fileName);
+	int getSceneNumb(const Common::String &sName);
+	int getMouseHiLite();
+
+	void joyUp();
+	void joyDown();
+	void joyA();
+	void skipVideo();
 };
 } // End of namespace Plumbers
 




More information about the Scummvm-git-logs mailing list