diff options
Diffstat (limited to 'engines')
79 files changed, 6438 insertions, 1698 deletions
diff --git a/engines/engine.cpp b/engines/engine.cpp index d3b9b113cf..8fff99f3fc 100644 --- a/engines/engine.cpp +++ b/engines/engine.cpp @@ -269,8 +269,8 @@ void splashScreen() {  	// Load logo  	Graphics::Surface *logo = bitmap.getSurface()->convertTo(g_system->getOverlayFormat(), bitmap.getPalette()); -	int lx = (g_system->getOverlayWidth() - logo->w) / 2; -	int ly = (g_system->getOverlayHeight() - logo->h) / 2; +	int lx = MAX((g_system->getOverlayWidth() - logo->w) / 2, 0); +	int ly = MAX((g_system->getOverlayHeight() - logo->h) / 2, 0);  	// Print version information  	const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont); @@ -283,7 +283,10 @@ void splashScreen() {  	screen.free();  	// Draw logo -	g_system->copyRectToOverlay(logo->getPixels(), logo->pitch, lx, ly, logo->w, logo->h); +	int lw = MIN<uint16>(logo->w, g_system->getOverlayWidth() - lx); +	int lh = MIN<uint16>(logo->h, g_system->getOverlayHeight() - ly); + +	g_system->copyRectToOverlay(logo->getPixels(), logo->pitch, lx, ly, lw, lh);  	logo->free();  	delete logo; diff --git a/engines/mohawk/cursors.cpp b/engines/mohawk/cursors.cpp index 4b66829e6a..72eebca917 100644 --- a/engines/mohawk/cursors.cpp +++ b/engines/mohawk/cursors.cpp @@ -34,8 +34,8 @@  #include "graphics/wincursor.h"  #ifdef ENABLE_MYST -#include "mohawk/bitmap.h"  #include "mohawk/myst.h" +#include "mohawk/myst_graphics.h"  #endif  namespace Mohawk { @@ -86,11 +86,9 @@ void DefaultCursorManager::setCursor(uint16 id) {  #ifdef ENABLE_MYST  MystCursorManager::MystCursorManager(MohawkEngine_Myst *vm) : _vm(vm) { -	_bmpDecoder = new MystBitmap();  }  MystCursorManager::~MystCursorManager() { -	delete _bmpDecoder;  }  void MystCursorManager::showCursor() { @@ -111,17 +109,18 @@ void MystCursorManager::setCursor(uint16 id) {  		return;  	} -	// Both Myst and Myst ME use the "MystBitmap" format for cursor images. -	MohawkSurface *mhkSurface = _bmpDecoder->decodeImage(_vm->getResource(ID_WDIB, id)); -	Graphics::Surface *surface = mhkSurface->getSurface();  	Common::SeekableReadStream *clrcStream = _vm->getResource(ID_CLRC, id);  	uint16 hotspotX = clrcStream->readUint16LE();  	uint16 hotspotY = clrcStream->readUint16LE();  	delete clrcStream; +	// Both Myst and Myst ME use the "MystBitmap" format for cursor images. +	MohawkSurface *mhkSurface = _vm->_gfx->findImage(id); +	Graphics::Surface *surface = mhkSurface->getSurface(); +  	// Myst ME stores some cursors as 24bpp images instead of 8bpp  	if (surface->format.bytesPerPixel == 1) { -		CursorMan.replaceCursor(surface->getPixels(), surface->w, surface->h, hotspotX, hotspotY, 0); +		CursorMan.replaceCursor(surface->getPixels(), surface->w, surface->h, hotspotX, hotspotY, 255);  		// We're using the screen palette for the original game, but we need  		// to use this for any 8bpp cursor in ME. @@ -133,7 +132,6 @@ void MystCursorManager::setCursor(uint16 id) {  	}  	_vm->_needsUpdate = true; -	delete mhkSurface;  }  void MystCursorManager::setDefaultCursor() { diff --git a/engines/mohawk/cursors.h b/engines/mohawk/cursors.h index c41b5c273e..742ae30107 100644 --- a/engines/mohawk/cursors.h +++ b/engines/mohawk/cursors.h @@ -102,7 +102,6 @@ enum {  };  class MohawkEngine_Myst; -class MystBitmap;  // The cursor manager for Myst  // Uses WDIB + CLRC resources @@ -119,7 +118,6 @@ public:  private:  	MohawkEngine_Myst *_vm; -	MystBitmap *_bmpDecoder;  };  #endif // ENABLE_MYST diff --git a/engines/mohawk/graphics.h b/engines/mohawk/graphics.h index 5f9b523e9a..f9fdeea15f 100644 --- a/engines/mohawk/graphics.h +++ b/engines/mohawk/graphics.h @@ -74,6 +74,10 @@ public:  	// Free all surfaces in the cache  	void clearCache(); +	// findImage will search the cache to find the image. +	// If not found, it will call decodeImage to get a new one. +	MohawkSurface *findImage(uint16 id); +  	void preloadImage(uint16 image);  	virtual void setPalette(uint16 id);  	void copyAnimImageToScreen(uint16 image, int left = 0, int top = 0); @@ -85,10 +89,6 @@ public:  protected:  	void copyAnimImageSectionToScreen(MohawkSurface *image, Common::Rect src, Common::Rect dest); -	// findImage will search the cache to find the image. -	// If not found, it will call decodeImage to get a new one. -	MohawkSurface *findImage(uint16 id); -  	// decodeImage will always return a new image.  	virtual MohawkSurface *decodeImage(uint16 id) = 0;  	virtual Common::Array<MohawkSurface *> decodeImages(uint16 id); diff --git a/engines/mohawk/myst_graphics.cpp b/engines/mohawk/myst_graphics.cpp index 5db9697a78..427fba4d22 100644 --- a/engines/mohawk/myst_graphics.cpp +++ b/engines/mohawk/myst_graphics.cpp @@ -47,8 +47,7 @@ MystGraphics::MystGraphics(MohawkEngine_Myst* vm) : GraphicsManager(), _vm(vm) {  	} else {  		// Paletted  		initGraphics(_viewport.width(), _viewport.height(), true); -		setBasePalette(); -		setPaletteToScreen(); +		clearScreenPalette();  	}  	_pixelFormat = _vm->_system->getScreenFormat(); @@ -86,7 +85,7 @@ MohawkSurface *MystGraphics::decodeImage(uint16 id) {  	bool isPict = false; -	if (_vm->getFeatures() & GF_ME) { +	if ((_vm->getFeatures() & GF_ME) && dataStream->size() > 512 + 10 + 4) {  		// Here we detect whether it's really a PICT or a WDIB. Since a MystBitmap  		// would be compressed, there's no way to detect for the BM without a hack.  		// So, we search for the PICT version opcode for detection. @@ -109,8 +108,11 @@ MohawkSurface *MystGraphics::decodeImage(uint16 id) {  	} else {  		mhkSurface = _bmpDecoder->decodeImage(dataStream); -		if (_vm->getFeatures() & GF_ME) +		if (_vm->getFeatures() & GF_ME) {  			mhkSurface->convertToTrueColor(); +		} else { +			remapSurfaceToSystemPalette(mhkSurface); +		}  	}  	assert(mhkSurface); @@ -204,7 +206,7 @@ void MystGraphics::copyImageSectionToBackBuffer(uint16 image, Common::Rect src,  	if (!(_vm->getFeatures() & GF_ME)) {  		// Make sure the palette is set  		assert(mhkSurface->getPalette()); -		memcpy(_palette + 10 * 3, mhkSurface->getPalette() + 10 * 3, (256 - 10 * 2) * 3); +		memcpy(_palette, mhkSurface->getPalette(), 256 * 3);  		setPaletteToScreen();  	}  } @@ -703,10 +705,10 @@ void MystGraphics::clearScreenPalette() {  	_vm->_system->getPaletteManager()->setPalette(palette, 0, 256);  } -void MystGraphics::setBasePalette() { +void MystGraphics::remapSurfaceToSystemPalette(MohawkSurface *mhkSurface) {  	// Entries [0, 9] of the palette  	static const byte lowPalette[] = { -		0xFF, 0xFF, 0xFF, +		0x00, 0x00, 0x00,  		0x80, 0x00, 0x00,  		0x00, 0x80, 0x00,  		0x80, 0x80, 0x00, @@ -729,15 +731,68 @@ void MystGraphics::setBasePalette() {  		0x00, 0x00, 0xFF,  		0xFF, 0x00, 0xFF,  		0x00, 0xFF, 0xFF, -		0x00, 0x00, 0x00 +		0xFF, 0xFF, 0xFF  	}; -	// Note that 0 and 255 are different from normal Windows. -	// Myst seems to hack that to white, resp. black (probably for Mac compat). +	byte *originalPalette = mhkSurface->getPalette(); + +	// The target palette is made of the Windows reserved palette, and colors 10 to 245 +	// of the bitmap palette. Entries 0 to 9 and 246 to 255 of the bitmap palette are +	// discarded. +	byte targetPalette[256 * 3]; +	memcpy(targetPalette, lowPalette, sizeof(lowPalette)); +	memcpy(targetPalette + sizeof(lowPalette), originalPalette + sizeof(lowPalette), sizeof(_palette) - sizeof(lowPalette) - sizeof(highPalette)); +	memcpy(targetPalette + sizeof(_palette) - sizeof(highPalette), highPalette, sizeof(highPalette)); + +	// Remap the discarded entries from the bitmap palette using the target palette. +	byte lowColorMap[ARRAYSIZE(lowPalette) / 3]; +	byte highColorMap[ARRAYSIZE(highPalette) / 3]; + +	for (uint i = 0; i < ARRAYSIZE(lowColorMap); i++) { +		uint colorIndex = 3 * i; +		byte red = originalPalette[colorIndex + 0]; +		byte green = originalPalette[colorIndex + 1]; +		byte blue = originalPalette[colorIndex + 2]; + +		lowColorMap[i] = getColorIndex(targetPalette, red, green, blue); +	} + +	for (uint i = 0; i < ARRAYSIZE(highColorMap); i++) { +		uint colorIndex = 3 * (i + 246); +		byte red = originalPalette[colorIndex + 0]; +		byte green = originalPalette[colorIndex + 1]; +		byte blue = originalPalette[colorIndex + 2]; + +		highColorMap[i] = getColorIndex(targetPalette, red, green, blue); +	} + +	// Replace the original palette with the target palette +	memcpy(originalPalette, targetPalette, sizeof(targetPalette)); + +	// Remap the pixel data to the target palette +	Graphics::Surface *surface = mhkSurface->getSurface(); +	byte *pixels = (byte *) surface->getPixels(); + +	for (int i = 0; i < surface->w * surface->h; i++) { +		if (pixels[i] < ARRAYSIZE(lowColorMap)) { +			pixels[i] = lowColorMap[pixels[i]]; +		} else if (pixels[i] >= 246) { +			pixels[i] = highColorMap[pixels[i] - 246]; +		} +	} +} + +byte MystGraphics::getColorIndex(const byte *palette, byte red, byte green, byte blue) { +	for (uint i = 0; i < 256; i++) { +		if (palette[(3 * i) + 0] == red && palette[(3 * i) + 1] == green && palette[(3 * i) + 2] == blue) { +			return i; +		} +	} -	memcpy(_palette, lowPalette, sizeof(lowPalette)); -	memset(_palette + sizeof(lowPalette), 0, sizeof(_palette) - sizeof(lowPalette) - sizeof(highPalette)); -	memcpy(_palette + sizeof(_palette) - sizeof(highPalette), highPalette, sizeof(highPalette)); +	// GDI actually chooses the nearest color if no exact match is found, +	// but this should not happen in Myst +	debug(1, "Color (%d, %d, %d) not in target palette", red, green, blue); +	return 0;  }  void MystGraphics::setPaletteToScreen() { diff --git a/engines/mohawk/myst_graphics.h b/engines/mohawk/myst_graphics.h index 93e388cb83..cd09a53a3a 100644 --- a/engines/mohawk/myst_graphics.h +++ b/engines/mohawk/myst_graphics.h @@ -56,7 +56,6 @@ public:  	void fadeFromBlack();  	void clearScreenPalette(); -	void setBasePalette();  	void setPaletteToScreen();  	const byte *getPalette() const { return _palette; } @@ -86,6 +85,9 @@ private:  	void transitionSlideToBottom(Common::Rect rect, uint16 steps, uint16 delay);  	void transitionPartialToRight(Common::Rect rect, uint32 width, uint32 steps);  	void transitionPartialToLeft(Common::Rect rect, uint32 width, uint32 steps); + +	void remapSurfaceToSystemPalette(MohawkSurface *mhkSurface); +	byte getColorIndex(const byte *palette, byte red, byte green, byte blue);  };  } // End of namespace Mohawk diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index 51fb52bb21..27ac4fac49 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -41,9 +41,7 @@  #include "sci/graphics/cache.h"  #include "sci/graphics/cursor.h"  #include "sci/graphics/screen.h" -#include "sci/graphics/paint.h"  #include "sci/graphics/paint16.h" -#include "sci/graphics/paint32.h"  #include "sci/graphics/palette.h"  #include "sci/graphics/ports.h"  #include "sci/graphics/view.h" @@ -54,6 +52,7 @@  #include "sci/video/seq_decoder.h"  #ifdef ENABLE_SCI32  #include "sci/graphics/frameout.h" +#include "sci/graphics/paint32.h"  #include "video/coktel_decoder.h"  #include "sci/video/robot_decoder.h"  #endif @@ -1648,7 +1647,7 @@ bool Console::cmdDrawPic(int argc, const char **argv) {  #endif  	uint16 resourceId = atoi(argv[1]); -	_engine->_gfxPaint->kernelDrawPicture(resourceId, 100, false, false, false, 0); +	_engine->_gfxPaint16->kernelDrawPicture(resourceId, 100, false, false, false, 0);  	_engine->_gfxScreen->copyToScreen();  	_engine->sleep(2000); diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index c01613268a..0b69aa9221 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -3591,7 +3591,7 @@ static const struct ADGameDescription SciGameDescriptions[] = {  		{"resource.map", 0, "aba367f2102e81782d961b14fbe3d630", 10246},  		{"resource.000", 0, "263dce4aa34c49d3ad29bec889007b1c", 11571394},  		AD_LISTEND}, -		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI)	}, +		Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_UNSTABLE, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI)	},  	// RAMA - English DOS/Windows Demo  	// Executable scanning reports "2.100.002", VERSION file reports "000.000.008" diff --git a/engines/sci/engine/gc.cpp b/engines/sci/engine/gc.cpp index 70c8c52bf0..b229490570 100644 --- a/engines/sci/engine/gc.cpp +++ b/engines/sci/engine/gc.cpp @@ -24,6 +24,10 @@  #include "common/array.h"  #include "sci/graphics/ports.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/controls32.h" +#endif +  namespace Sci {  //#define GC_DEBUG_CODE @@ -150,6 +154,12 @@ AddrSet *findAllActiveReferences(EngineState *s) {  		}  	} +#ifdef ENABLE_SCI32 +	// Init: ScrollWindows +	if (g_sci->_gfxControls32) +		wm.pushArray(g_sci->_gfxControls32->listObjectReferences()); +#endif +  	debugC(kDebugLevelGC, "[GC] -- Finished explicitly loaded scripts, done with root set");  	processWorkList(s->_segMan, wm, heap); diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index 62566a74b2..1202982986 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -412,7 +412,7 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv);  reg_t kTextColors(EngineState *s, int argc, reg_t *argv);  reg_t kTextFonts(EngineState *s, int argc, reg_t *argv);  reg_t kShow(EngineState *s, int argc, reg_t *argv); -reg_t kRemapColors16(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColors(EngineState *s, int argc, reg_t *argv);  reg_t kDummy(EngineState *s, int argc, reg_t *argv);  reg_t kEmpty(EngineState *s, int argc, reg_t *argv);  reg_t kStub(EngineState *s, int argc, reg_t *argv); @@ -420,6 +420,26 @@ reg_t kStubNull(EngineState *s, int argc, reg_t *argv);  #ifdef ENABLE_SCI32  // SCI2 Kernel Functions +reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioPlay(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioStop(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioPause(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioResume(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioPosition(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioRate(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioVolume(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioGetCapability(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioBitDepth(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioDistort(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioMixing(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioChannels(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioPreload(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv); +reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv); +  reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);  reg_t kArray(EngineState *s, int argc, reg_t *argv);  reg_t kListAt(EngineState *s, int argc, reg_t *argv); @@ -447,19 +467,28 @@ reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv);  reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv);  reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowPageUp(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowPageDown(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowUpArrow(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowDownArrow(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowHome(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowEnd(EngineState *s, int argc, reg_t *argv);  reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowGo(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowModify(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowHide(EngineState *s, int argc, reg_t *argv);  reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv);  reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv);  reg_t kMulDiv(EngineState *s, int argc, reg_t *argv); -reg_t kRemapColors(EngineState *s, int argc, reg_t *argv); -reg_t kRemapOff(EngineState *s, int argc, reg_t *argv); -reg_t kRemapByRange(EngineState *s, int argc, reg_t *argv); -reg_t kRemapByPercent(EngineState *s, int argc, reg_t *argv); -reg_t kRemapToGray(EngineState *s, int argc, reg_t *argv); -reg_t kRemapToPercentGray(EngineState *s, int argc, reg_t *argv); -reg_t kRemapSetNoMatchRange(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColorsOff(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColorsByRange(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColorsByPercent(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColorsToGray(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColorsToPercentGray(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColorsBlockRange(EngineState *s, int argc, reg_t *argv);  reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv);  reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv); @@ -537,6 +566,7 @@ reg_t kMoveToFront(EngineState *s, int argc, reg_t *argv);  reg_t kMoveToEnd(EngineState *s, int argc, reg_t *argv);  reg_t kGetWindowsOption(EngineState *s, int argc, reg_t *argv);  reg_t kWinHelp(EngineState *s, int argc, reg_t *argv); +reg_t kMessageBox(EngineState *s, int argc, reg_t *argv);  reg_t kGetConfig(EngineState *s, int argc, reg_t *argv);  reg_t kGetSierraProfileInt(EngineState *s, int argc, reg_t *argv);  reg_t kCelInfo(EngineState *s, int argc, reg_t *argv); @@ -558,7 +588,6 @@ reg_t kPlayDuck(EngineState *s, int argc, reg_t *argv);  reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv);  reg_t kDoSoundPlay(EngineState *s, int argc, reg_t *argv); -reg_t kDoSoundRestore(EngineState *s, int argc, reg_t *argv);  reg_t kDoSoundDispose(EngineState *s, int argc, reg_t *argv);  reg_t kDoSoundMute(EngineState *s, int argc, reg_t *argv);  reg_t kDoSoundStop(EngineState *s, int argc, reg_t *argv); @@ -573,7 +602,6 @@ reg_t kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv);  reg_t kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv);  reg_t kDoSoundGlobalReverb(EngineState *s, int argc, reg_t *argv);  reg_t kDoSoundSetHold(EngineState *s, int argc, reg_t *argv); -reg_t kDoSoundDummy(EngineState *s, int argc, reg_t *argv);  reg_t kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv);  reg_t kDoSoundSuspend(EngineState *s, int argc, reg_t *argv);  reg_t kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv); diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index 0ede307e6b..dacaafe757 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -102,7 +102,7 @@ struct SciKernelMapSubEntry {  static const SciKernelMapSubEntry kDoSound_subops[] = {  	{ SIG_SOUNDSCI0,       0, MAP_CALL(DoSoundInit),               "o",                    NULL },  	{ SIG_SOUNDSCI0,       1, MAP_CALL(DoSoundPlay),               "o",                    NULL }, -	{ SIG_SOUNDSCI0,       2, MAP_CALL(DoSoundRestore),            "(o)",                  NULL }, +	{ SIG_SOUNDSCI0,       2, MAP_EMPTY(DoSoundRestore),           "(o)",                  NULL },  	{ SIG_SOUNDSCI0,       3, MAP_CALL(DoSoundDispose),            "o",                    NULL },  	{ SIG_SOUNDSCI0,       4, MAP_CALL(DoSoundMute),               "(i)",                  NULL },  	{ SIG_SOUNDSCI0,       5, MAP_CALL(DoSoundStop),               "o",                    NULL }, @@ -115,7 +115,7 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {  	{ SIG_SOUNDSCI0,      12, MAP_CALL(DoSoundStopAll),            "",                     NULL },  	{ SIG_SOUNDSCI1EARLY,  0, MAP_CALL(DoSoundMasterVolume),       NULL,                   NULL },  	{ SIG_SOUNDSCI1EARLY,  1, MAP_CALL(DoSoundMute),               NULL,                   NULL }, -	{ SIG_SOUNDSCI1EARLY,  2, MAP_CALL(DoSoundRestore),            NULL,                   NULL }, +	{ SIG_SOUNDSCI1EARLY,  2, MAP_EMPTY(DoSoundRestore),           NULL,                   NULL },  	{ SIG_SOUNDSCI1EARLY,  3, MAP_CALL(DoSoundGetPolyphony),       NULL,                   NULL },  	{ SIG_SOUNDSCI1EARLY,  4, MAP_CALL(DoSoundUpdate),             NULL,                   NULL },  	{ SIG_SOUNDSCI1EARLY,  5, MAP_CALL(DoSoundInit),               NULL,                   NULL }, @@ -128,11 +128,11 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {  	{ SIG_SOUNDSCI1EARLY, 12, MAP_CALL(DoSoundSendMidi),           "oiii",                 NULL },  	{ SIG_SOUNDSCI1EARLY, 13, MAP_CALL(DoSoundGlobalReverb),       "(i)",                  NULL },  	{ SIG_SOUNDSCI1EARLY, 14, MAP_CALL(DoSoundSetHold),            "oi",                   NULL }, -	{ SIG_SOUNDSCI1EARLY, 15, MAP_CALL(DoSoundDummy),              "",                     NULL }, +	{ SIG_SOUNDSCI1EARLY, 15, MAP_EMPTY(DoSoundDummy),             "",                     NULL },  	//  ^^ Longbow demo  	{ SIG_SOUNDSCI1LATE,   0, MAP_CALL(DoSoundMasterVolume),       NULL,                   NULL },  	{ SIG_SOUNDSCI1LATE,   1, MAP_CALL(DoSoundMute),               NULL,                   NULL }, -	{ SIG_SOUNDSCI1LATE,   2, MAP_CALL(DoSoundRestore),            "",                     NULL }, +	{ SIG_SOUNDSCI1LATE,   2, MAP_EMPTY(DoSoundRestore),           "",                     NULL },  	{ SIG_SOUNDSCI1LATE,   3, MAP_CALL(DoSoundGetPolyphony),       NULL,                   NULL },  	{ SIG_SOUNDSCI1LATE,   4, MAP_CALL(DoSoundGetAudioCapability), "",                     NULL },  	{ SIG_SOUNDSCI1LATE,   5, MAP_CALL(DoSoundSuspend),            "i",                    NULL }, @@ -143,7 +143,7 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {  	{ SIG_SOUNDSCI1LATE,  10, MAP_CALL(DoSoundPause),              NULL,                   NULL },  	{ SIG_SOUNDSCI1LATE,  11, MAP_CALL(DoSoundFade),               "oiiii(i)",             kDoSoundFade_workarounds },  	{ SIG_SOUNDSCI1LATE,  12, MAP_CALL(DoSoundSetHold),            NULL,                   NULL }, -	{ SIG_SOUNDSCI1LATE,  13, MAP_CALL(DoSoundDummy),              NULL,                   NULL }, +	{ SIG_SOUNDSCI1LATE,  13, MAP_EMPTY(DoSoundDummy),             NULL,                   NULL },  	{ SIG_SOUNDSCI1LATE,  14, MAP_CALL(DoSoundSetVolume),          "oi",                   NULL },  	{ SIG_SOUNDSCI1LATE,  15, MAP_CALL(DoSoundSetPriority),        "oi",                   NULL },  	{ SIG_SOUNDSCI1LATE,  16, MAP_CALL(DoSoundSetLoop),            "oi",                   NULL }, @@ -152,36 +152,97 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {  	{ SIG_SOUNDSCI1LATE,  19, MAP_CALL(DoSoundGlobalReverb),       NULL,                   NULL },  	{ SIG_SOUNDSCI1LATE,  20, MAP_CALL(DoSoundUpdate),             NULL,                   NULL },  #ifdef ENABLE_SCI32 -	{ SIG_SOUNDSCI21,      0, MAP_CALL(DoSoundMasterVolume),       NULL,                   NULL }, -	{ SIG_SOUNDSCI21,      1, MAP_CALL(DoSoundMute),               NULL,                   NULL }, -	{ SIG_SOUNDSCI21,      2, MAP_CALL(DoSoundRestore),            NULL,                   NULL }, -	{ SIG_SOUNDSCI21,      3, MAP_CALL(DoSoundGetPolyphony),       NULL,                   NULL }, -	{ SIG_SOUNDSCI21,      4, MAP_CALL(DoSoundGetAudioCapability), NULL,                   NULL }, -	{ SIG_SOUNDSCI21,      5, MAP_CALL(DoSoundSuspend),            NULL,                   NULL }, -	{ SIG_SOUNDSCI21,      6, MAP_CALL(DoSoundInit),               NULL,                   NULL }, -	{ SIG_SOUNDSCI21,      7, MAP_CALL(DoSoundDispose),            NULL,                   NULL }, -	{ SIG_SOUNDSCI21,      8, MAP_CALL(DoSoundPlay),               "o(i)",                 NULL }, +	{ SIG_SOUNDSCI21,      0, MAP_CALL(DoSoundMasterVolume),       "(i)",                  NULL }, +	{ SIG_SOUNDSCI21,      1, MAP_CALL(DoSoundMute),               "(i)",                  NULL }, +	{ SIG_SOUNDSCI21,      2, MAP_EMPTY(DoSoundRestore),           NULL,                   NULL }, +	{ SIG_SOUNDSCI21,      3, MAP_CALL(DoSoundGetPolyphony),       "",                     NULL }, +	{ SIG_SOUNDSCI21,      4, MAP_CALL(DoSoundGetAudioCapability), "",                     NULL }, +	{ SIG_SOUNDSCI21,      5, MAP_CALL(DoSoundSuspend),            "i",                    NULL }, +	{ SIG_SOUNDSCI21,      6, MAP_CALL(DoSoundInit),               "o",                    NULL }, +	{ SIG_SOUNDSCI21,      7, MAP_CALL(DoSoundDispose),            "o",                    NULL }, +	{ SIG_SOUNDSCI21,      8, MAP_CALL(DoSoundPlay),               "o",                    kDoSoundPlay_workarounds },  	// ^^ TODO: if this is really the only change between SCI1LATE AND SCI21, we could rename the  	//     SIG_SOUNDSCI1LATE #define to SIG_SINCE_SOUNDSCI1LATE and make it being SCI1LATE+. Although  	//     I guess there are many more changes somewhere  	// TODO: Quest for Glory 4 (SCI2.1) uses the old scheme, we need to detect it accordingly  	//        signature for SCI21 should be "o" -	{ SIG_SOUNDSCI21,      9, MAP_CALL(DoSoundStop),               NULL,                   NULL }, -	{ SIG_SOUNDSCI21,     10, MAP_CALL(DoSoundPause),              NULL,                   NULL }, -	{ SIG_SOUNDSCI21,     11, MAP_CALL(DoSoundFade),               NULL,                   kDoSoundFade_workarounds }, -	{ SIG_SOUNDSCI21,     12, MAP_CALL(DoSoundSetHold),            NULL,                   NULL }, -	{ SIG_SOUNDSCI21,     13, MAP_CALL(DoSoundDummy),              NULL,                   NULL }, -	{ SIG_SOUNDSCI21,     14, MAP_CALL(DoSoundSetVolume),          NULL,                   NULL }, -	{ SIG_SOUNDSCI21,     15, MAP_CALL(DoSoundSetPriority),        NULL,                   NULL }, -	{ SIG_SOUNDSCI21,     16, MAP_CALL(DoSoundSetLoop),            NULL,                   NULL }, -	{ SIG_SOUNDSCI21,     17, MAP_CALL(DoSoundUpdateCues),         NULL,                   NULL }, -	{ SIG_SOUNDSCI21,     18, MAP_CALL(DoSoundSendMidi),           NULL,                   NULL }, -	{ SIG_SOUNDSCI21,     19, MAP_CALL(DoSoundGlobalReverb),       NULL,                   NULL }, -	{ SIG_SOUNDSCI21,     20, MAP_CALL(DoSoundUpdate),             NULL,                   NULL }, +	{ SIG_SOUNDSCI21,      9, MAP_CALL(DoSoundStop),               "o",                    NULL }, +	{ SIG_SOUNDSCI21,     10, MAP_CALL(DoSoundPause),              "[o0]i",                NULL }, +	{ SIG_SOUNDSCI21,     11, MAP_CALL(DoSoundFade),               "oiiii",                kDoSoundFade_workarounds }, +	{ SIG_SOUNDSCI21,     12, MAP_CALL(DoSoundSetHold),            "oi",                   NULL }, +	{ SIG_SOUNDSCI21,     13, MAP_EMPTY(DoSoundDummy),             NULL,                   NULL }, +	{ SIG_SOUNDSCI21,     14, MAP_CALL(DoSoundSetVolume),          "oi",                   NULL }, +	{ SIG_SOUNDSCI21,     15, MAP_CALL(DoSoundSetPriority),        "oi",                   NULL }, +	{ SIG_SOUNDSCI21,     16, MAP_CALL(DoSoundSetLoop),            "oi",                   NULL }, +	{ SIG_SOUNDSCI21,     17, MAP_CALL(DoSoundUpdateCues),         "o",                    NULL }, +	{ SIG_SOUNDSCI21,     18, MAP_CALL(DoSoundSendMidi),           "oiiii",                NULL }, +	{ SIG_SOUNDSCI21,     19, MAP_CALL(DoSoundGlobalReverb),       "(i)",                  NULL }, +	{ SIG_SOUNDSCI21,     20, MAP_CALL(DoSoundUpdate),             "o",                    NULL },  #endif  	SCI_SUBOPENTRY_TERMINATOR  }; +#ifdef ENABLE_SCI32 +// NOTE: In SSCI, some 'unused' kDoAudio subops are actually +// called indirectly by kDoSound: +// +// kDoSoundGetAudioCapability -> kDoAudioGetCapability +// kDoSoundPlay       -> kDoAudioPlay, kDoAudioStop +// kDoSoundPause      -> kDoAudioPause, kDoAudioResume +// kDoSoundFade       -> kDoAudioFade +// kDoSoundSetVolume  -> kDoAudioVolume +// kDoSoundSetLoop    -> kDoAudioSetLoop +// kDoSoundUpdateCues -> kDoAudioPosition +// +// In ScummVM, logic inside these kernel functions has been +// moved to methods of Audio32, and direct calls to Audio32 +// are made from kDoSound instead. +// +// Some kDoAudio methods are esoteric and appear to be used +// only by one or two games: +// +// kDoAudioMixing: Phantasmagoria (other games call this +// function, but only to disable the feature) +// kDoAudioHasSignal: SQ6 TalkRandCycle +// kDoAudioPan: Rama RegionSFX::pan method +// +// Finally, there is a split in SCI2.1mid audio code. +// QFG4CD & SQ6 do not have opcodes 18 and 19, but they +// exist in GK2, KQ7 2.00b, Phantasmagoria 1, PQ:SWAT, and +// Torin. (It is unknown if they exist in MUMG Deluxe or +// Shivers 1; they are not used in either of these games.) + +//    version,         subId, function-mapping,                    signature,              workarounds +static const SciKernelMapSubEntry kDoAudio_subops[] = { +	{ SIG_SCI32,           0, MAP_CALL(DoAudioInit),               "",                     NULL }, +	// SCI2 includes a Sync script that would call +	// kDoAudioWaitForPlay, but SSCI has no opcode 1 until +	// SCI2.1early +	{ SIG_SINCE_SCI21,     1, MAP_CALL(DoAudioWaitForPlay),        "(i)(i)(i)(i)(i)(i)(i)", NULL }, +	{ SIG_SCI32,           2, MAP_CALL(DoAudioPlay),               "(i)(i)(i)(i)(i)(i)(i)", NULL }, +	{ SIG_SCI32,           3, MAP_CALL(DoAudioStop),               "(i)(i)(i)(i)(i)",      NULL }, +	{ SIG_SCI32,           4, MAP_CALL(DoAudioPause),              "(i)(i)(i)(i)(i)",      NULL }, +	{ SIG_SCI32,           5, MAP_CALL(DoAudioResume),             "(i)(i)(i)(i)(i)",      NULL }, +	{ SIG_SCI32,           6, MAP_CALL(DoAudioPosition),           "(i)(i)(i)(i)(i)",      NULL }, +	{ SIG_SCI32,           7, MAP_CALL(DoAudioRate),               "(i)",                  NULL }, +	{ SIG_SCI32,           8, MAP_CALL(DoAudioVolume),             "(i)(i)(i)(i)(i)(i)",   NULL }, +	{ SIG_SCI32,           9, MAP_CALL(DoAudioGetCapability),      "",                     NULL }, +	{ SIG_SCI32,          10, MAP_CALL(DoAudioBitDepth),           "(i)",                  NULL }, +	{ SIG_SCI32,          11, MAP_DUMMY(DoAudioDistort),           "(i)",                  NULL }, +	{ SIG_SCI32,          12, MAP_CALL(DoAudioMixing),             "(i)",                  NULL }, +	{ SIG_SCI32,          13, MAP_CALL(DoAudioChannels),           "(i)",                  NULL }, +	{ SIG_SCI32,          14, MAP_CALL(DoAudioPreload),            "(i)",                  NULL }, +	{ SIG_SINCE_SCI21MID, 15, MAP_CALL(DoAudioFade),               "(iiii)(i)(i)",         NULL }, +	{ SIG_SINCE_SCI21MID, 16, MAP_DUMMY(DoAudioFade36),            "iiiii(iii)(i)",        NULL }, +	{ SIG_SINCE_SCI21MID, 17, MAP_CALL(DoAudioHasSignal),          "",                     NULL }, +	{ SIG_SINCE_SCI21MID, 18, MAP_EMPTY(DoAudioCritical),          "",                     NULL }, +	{ SIG_SINCE_SCI21MID, 19, MAP_CALL(DoAudioSetLoop),            "iii(o)",               NULL }, +	{ SIG_SCI3,           20, MAP_DUMMY(DoAudioPan),               "",                     NULL }, +	{ SIG_SCI3,           21, MAP_DUMMY(DoAudioPanOff),            "",                     NULL }, +	SCI_SUBOPENTRY_TERMINATOR +}; +#endif +  //    version,         subId, function-mapping,                    signature,              workarounds  static const SciKernelMapSubEntry kGraph_subops[] = {  	{ SIG_SCI32,           1, MAP_CALL(StubNull),                  "",                     NULL }, // called by gk1 sci32 right at the start @@ -353,12 +414,12 @@ static const SciKernelMapSubEntry kList_subops[] = {  //    version,         subId, function-mapping,                    signature,              workarounds  static const SciKernelMapSubEntry kRemapColors_subops[] = { -	{ SIG_SCI32,           0, MAP_CALL(RemapOff),                 "(i)",                  NULL }, -	{ SIG_SCI32,           1, MAP_CALL(RemapByRange),             "iiii(i)",              NULL }, -	{ SIG_SCI32,           2, MAP_CALL(RemapByPercent),           "ii(i)",                NULL }, -	{ SIG_SCI32,           3, MAP_CALL(RemapToGray),              "ii(i)",                NULL }, -	{ SIG_SCI32,           4, MAP_CALL(RemapToPercentGray),       "iii(i)",               NULL }, -	{ SIG_SCI32,           5, MAP_CALL(RemapSetNoMatchRange),     "ii",                   NULL }, +	{ SIG_SCI32,           0, MAP_CALL(RemapColorsOff),            "(i)",                  NULL }, +	{ SIG_SCI32,           1, MAP_CALL(RemapColorsByRange),        "iiii(i)",              NULL }, +	{ SIG_SCI32,           2, MAP_CALL(RemapColorsByPercent),      "ii(i)",                NULL }, +	{ SIG_SCI32,           3, MAP_CALL(RemapColorsToGray),         "ii(i)",                NULL }, +	{ SIG_SCI32,           4, MAP_CALL(RemapColorsToPercentGray),  "iii(i)",               NULL }, +	{ SIG_SCI32,           5, MAP_CALL(RemapColorsBlockRange),     "ii",                   NULL },  	SCI_SUBOPENTRY_TERMINATOR  }; @@ -407,25 +468,30 @@ static const SciKernelMapSubEntry kString_subops[] = {  //    version,         subId, function-mapping,                    signature,              workarounds  static const SciKernelMapSubEntry kScrollWindow_subops[] = {  	{ SIG_SCI32,           0, MAP_CALL(ScrollWindowCreate),        "oi",                   NULL }, -	{ SIG_SCI32,           1, MAP_CALL(ScrollWindowAdd),           "o.ii.(.)",             NULL }, -	{ SIG_SCI32,           2, MAP_DUMMY(ScrollWindowClear),        "o",                    NULL }, -	{ SIG_SCI32,           3, MAP_DUMMY(ScrollWindowPageUp),       "o",                    NULL }, -	{ SIG_SCI32,           4, MAP_DUMMY(ScrollWindowPageDown),     "o",                    NULL }, -	{ SIG_SCI32,           5, MAP_DUMMY(ScrollWindowUpArrow),      "o",                    NULL }, -	{ SIG_SCI32,           6, MAP_DUMMY(ScrollWindowDownArrow),    "o",                    NULL }, -	{ SIG_SCI32,           7, MAP_DUMMY(ScrollWindowHome),         "o",                    NULL }, -	{ SIG_SCI32,           8, MAP_DUMMY(ScrollWindowEnd),          "o",                    NULL }, -	{ SIG_SCI32,           9, MAP_DUMMY(ScrollWindowResize),       "o.",                   NULL }, -	{ SIG_SCI32,          10, MAP_CALL(ScrollWindowWhere),         "oi",                   NULL }, -	{ SIG_SCI32,          11, MAP_DUMMY(ScrollWindowGo),           "o..",                  NULL }, -	{ SIG_SCI32,          12, MAP_DUMMY(ScrollWindowInsert),       "o.....",               NULL }, -	{ SIG_SCI32,          13, MAP_DUMMY(ScrollWindowDelete),       "o.",                   NULL }, -	{ SIG_SCI32,          14, MAP_DUMMY(ScrollWindowModify),       "o.....(.)",            NULL }, -	{ SIG_SCI32,          15, MAP_DUMMY(ScrollWindowHide),         "o",                    NULL }, -	{ SIG_SCI32,          16, MAP_CALL(ScrollWindowShow),          "o",                    NULL }, -	{ SIG_SCI32,          17, MAP_CALL(ScrollWindowDestroy),       "o",                    NULL }, -	{ SIG_SCI32,          18, MAP_DUMMY(ScrollWindowText),         "o",                    NULL }, -	{ SIG_SCI32,          19, MAP_DUMMY(ScrollWindowReconstruct),  "o.",                   NULL }, +	{ SIG_SCI32,           1, MAP_CALL(ScrollWindowAdd),           "iriii(i)",             kScrollWindowAdd_workarounds }, +	{ SIG_SCI32,           2, MAP_DUMMY(ScrollWindowClear),        "i",                    NULL }, +	{ SIG_SCI32,           3, MAP_CALL(ScrollWindowPageUp),        "i",                    NULL }, +	{ SIG_SCI32,           4, MAP_CALL(ScrollWindowPageDown),      "i",                    NULL }, +	{ SIG_SCI32,           5, MAP_CALL(ScrollWindowUpArrow),       "i",                    NULL }, +	{ SIG_SCI32,           6, MAP_CALL(ScrollWindowDownArrow),     "i",                    NULL }, +	{ SIG_SCI32,           7, MAP_CALL(ScrollWindowHome),          "i",                    NULL }, +	{ SIG_SCI32,           8, MAP_CALL(ScrollWindowEnd),           "i",                    NULL }, +	{ SIG_SCI32,           9, MAP_DUMMY(ScrollWindowResize),       "i.",                   NULL }, +	{ SIG_SCI32,          10, MAP_CALL(ScrollWindowWhere),         "ii",                   NULL }, +	{ SIG_SCI32,          11, MAP_CALL(ScrollWindowGo),            "i..",                  NULL }, +	{ SIG_SCI32,          12, MAP_DUMMY(ScrollWindowInsert),       "i.....",               NULL }, +	{ SIG_SCI32,          13, MAP_DUMMY(ScrollWindowDelete),       "i.",                   NULL }, +	{ SIG_SCI32,          14, MAP_CALL(ScrollWindowModify),        "iiriii(i)",            NULL }, +	{ SIG_SCI32,          15, MAP_CALL(ScrollWindowHide),          "i",                    NULL }, +	{ SIG_SCI32,          16, MAP_CALL(ScrollWindowShow),          "i",                    NULL }, +	{ SIG_SCI32,          17, MAP_CALL(ScrollWindowDestroy),       "i",                    NULL }, +	// LSL6hires uses kScrollWindowText and kScrollWindowReconstruct to try to save +	// and restore the content of the game's subtitle window, but this feature did not +	// use the normal save/load functionality of the engine and was actually broken +	// (all text formatting was missing on restore). Since there is no real reason to +	// save the subtitle scrollback anyway, we just ignore calls to these two functions. +	{ SIG_SCI32,          18, MAP_EMPTY(ScrollWindowText),         "i",                    NULL }, +	{ SIG_SCI32,          19, MAP_EMPTY(ScrollWindowReconstruct),  "i.",                   NULL },  	SCI_SUBOPENTRY_TERMINATOR  }; @@ -484,7 +550,10 @@ static SciKernelMapEntry s_kernelMap[] = {  	{ MAP_CALL(DisposeList),       SIG_EVERYWHERE,           "l",                     NULL,            NULL },  	{ MAP_CALL(DisposeScript),     SIG_EVERYWHERE,           "i(i*)",                 NULL,            kDisposeScript_workarounds },  	{ MAP_CALL(DisposeWindow),     SIG_EVERYWHERE,           "i(i)",                  NULL,            NULL }, -	{ MAP_CALL(DoAudio),           SIG_EVERYWHERE,           "i(.*)",                 NULL,            NULL }, // subop +	{ MAP_CALL(DoAudio),           SCI_VERSION_NONE, SCI_VERSION_2, SIGFOR_ALL, "i(.*)", NULL,         NULL }, // subop +#ifdef ENABLE_SCI32 +	{ "DoAudio", kDoAudio32,       SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)",          kDoAudio_subops, NULL }, +#endif  	{ MAP_CALL(DoAvoider),         SIG_EVERYWHERE,           "o(i)",                  NULL,            NULL },  	{ MAP_CALL(DoBresen),          SIG_EVERYWHERE,           "o",                     NULL,            NULL },  	{ MAP_CALL(DoSound),           SIG_EVERYWHERE,           "i(.*)",                 kDoSound_subops, NULL }, @@ -565,9 +634,9 @@ static SciKernelMapEntry s_kernelMap[] = {  	{ MAP_CALL(PriCoord),          SIG_EVERYWHERE,           "i",                     NULL,            NULL },  	{ MAP_CALL(Random),            SIG_EVERYWHERE,           "i(i)(i)",               NULL,            NULL },  	{ MAP_CALL(ReadNumber),        SIG_EVERYWHERE,           "r",                     NULL,            kReadNumber_workarounds }, -	{ "RemapColors", kRemapColors16,       SIG_SCI11, SIGFOR_ALL,    "i(i)(i)(i)(i)", NULL,            NULL }, +	{ MAP_CALL(RemapColors),       SIG_SCI11, SIGFOR_ALL,    "i(i)(i)(i)(i)",         NULL,            NULL },  #ifdef ENABLE_SCI32 -	{ MAP_CALL(RemapColors),       SIG_SCI32, SIGFOR_ALL,    "i(i)(i)(i)(i)(i)",      kRemapColors_subops, NULL }, +	{ "RemapColors", kRemapColors32, SIG_SCI32, SIGFOR_ALL,  "i(i)(i)(i)(i)(i)",      kRemapColors_subops, NULL },  #endif  	{ MAP_CALL(ResCheck),          SIG_EVERYWHERE,           "ii(iiii)",              NULL,            NULL },  	{ MAP_CALL(RespondsTo),        SIG_EVERYWHERE,           ".i",                    NULL,            NULL }, @@ -659,6 +728,8 @@ static SciKernelMapEntry s_kernelMap[] = {  	{ MAP_CALL(ListEachElementDo), SIG_EVERYWHERE,           "li(.*)",                NULL,            NULL },  	{ MAP_CALL(ListFirstTrue),     SIG_EVERYWHERE,           "li(.*)",                NULL,            NULL },  	{ MAP_CALL(ListIndexOf),       SIG_EVERYWHERE,           "l[o0]",                 NULL,            NULL }, +	// kMessageBox is used only by KQ7 1.51 +	{ MAP_CALL(MessageBox),        SIG_SCI32, SIGFOR_ALL,    "rri",                   NULL,            NULL },  	{ "OnMe", kIsOnMe,             SIG_EVERYWHERE,           "iioi",                  NULL,            NULL },  	// Purge is used by the memory manager in SSCI to ensure that X number of bytes (the so called "unmovable  	// memory") are available when the current room changes. This is similar to the SCI0-SCI1.1 FlushResources @@ -736,9 +807,12 @@ static SciKernelMapEntry s_kernelMap[] = {  	{ MAP_CALL(SetFontRes),        SIG_SCI21EARLY, SIGFOR_ALL, "ii",                  NULL,            NULL },  	{ MAP_CALL(Font),              SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)",           kFont_subops,    NULL },  	{ MAP_CALL(Bitmap),            SIG_EVERYWHERE,           "(.*)",                  kBitmap_subops,  NULL }, -	{ MAP_CALL(AddLine),           SIG_EVERYWHERE,           "oiiiiiiiii",            NULL,            NULL }, -	{ MAP_CALL(UpdateLine),        SIG_EVERYWHERE,           "[r0]oiiiiiiiii",        NULL,            NULL }, -	{ MAP_CALL(DeleteLine),        SIG_EVERYWHERE,           "[r0]o",                 NULL,            NULL }, +	{ MAP_CALL(AddLine),           SIG_EVERYWHERE,           "oiiii(iiiii)",          NULL,            NULL }, +	// The first argument is a ScreenItem instance ID that is created by the +	// engine, not the VM; as a result, in ScummVM, this argument looks like +	// an integer and not an object, although it is an object reference. +	{ MAP_CALL(UpdateLine),        SIG_EVERYWHERE,           "ioiiii(iiiii)",         NULL,            NULL }, +	{ MAP_CALL(DeleteLine),        SIG_EVERYWHERE,           "io",                    NULL,            NULL },  	// SCI2.1 Empty Functions diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 335763a35f..d604bb85d0 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -806,25 +806,47 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {  				return NULL_REG;  		} else if (virtualId < SAVEGAMEID_OFFICIALRANGE_START) {  			// virtualId is low, we assume that scripts expect us to create new slot -			if (g_sci->getGameId() == GID_JONES) { +			switch (g_sci->getGameId()) { +			case GID_JONES:  				// Jones has one save slot only  				savegameId = 0; -			} else if (virtualId == s->_lastSaveVirtualId) { -				// if last virtual id is the same as this one, we assume that caller wants to overwrite last save -				savegameId = s->_lastSaveNewId; -			} else { -				uint savegameNr; -				// savegameId is in lower range, scripts expect us to create a new slot -				for (savegameId = SAVEGAMESLOT_FIRST; savegameId <= SAVEGAMESLOT_LAST; savegameId++) { -					for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) { -						if (savegameId == saves[savegameNr].id) +				break; +			case GID_FANMADE: { +				// Fanmade game, try to identify the game +				const char *gameName = g_sci->getGameObjectName(); + +				if (strcmp(gameName, "CascadeQuest") == 0) { +					// Cascade Quest calls us directly to auto-save and uses slot 99, +					//  put that save into slot 0 (ScummVM auto-save slot) see bug #7007 +					if (virtualId == (SAVEGAMEID_OFFICIALRANGE_START - 1)) { +						savegameId = 0; +					} +				} +				break; +			} +			default: +				break; +			} + +			if (savegameId < 0) { +				// savegameId not set yet +				if (virtualId == s->_lastSaveVirtualId) { +					// if last virtual id is the same as this one, we assume that caller wants to overwrite last save +					savegameId = s->_lastSaveNewId; +				} else { +					uint savegameNr; +					// savegameId is in lower range, scripts expect us to create a new slot +					for (savegameId = SAVEGAMESLOT_FIRST; savegameId <= SAVEGAMESLOT_LAST; savegameId++) { +						for (savegameNr = 0; savegameNr < saves.size(); savegameNr++) { +							if (savegameId == saves[savegameNr].id) +								break; +						} +						if (savegameNr == saves.size()) // Slot not found, seems to be good to go  							break;  					} -					if (savegameNr == saves.size()) // Slot not found, seems to be good to go -						break; +					if (savegameId > SAVEGAMESLOT_LAST) +						error("kSavegame: no more savegame slots available");  				} -				if (savegameId > SAVEGAMESLOT_LAST) -					error("kSavegame: no more savegame slots available");  			}  		} else {  			error("kSaveGame: invalid savegameId used"); diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index 73236b98ed..cae5a09789 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -1258,7 +1258,7 @@ reg_t kShow(EngineState *s, int argc, reg_t *argv) {  }  // Early variant of the SCI32 kRemapColors kernel function, used in the demo of QFG4 -reg_t kRemapColors16(EngineState *s, int argc, reg_t *argv) { +reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) {  	uint16 operation = argv[0].toUint16();  	switch (operation) { diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp index b140062ca0..019a06930c 100644 --- a/engines/sci/engine/kgraphics32.cpp +++ b/engines/sci/engine/kgraphics32.cpp @@ -52,7 +52,9 @@  #include "sci/graphics/controls32.h"  #include "sci/graphics/font.h"	// TODO: remove once kBitmap is moved in a separate class  #include "sci/graphics/frameout.h" +#include "sci/graphics/paint32.h"  #include "sci/graphics/palette32.h" +#include "sci/graphics/remap32.h"  #include "sci/graphics/text32.h"  #endif @@ -253,6 +255,10 @@ reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) {  	return s->r_acc;  } +reg_t kMessageBox(EngineState *s, int argc, reg_t *argv) { +	return g_sci->_gfxControls32->kernelMessageBox(s->_segMan->getString(argv[0]), s->_segMan->getString(argv[1]), argv[2].toUint16()); +} +  /**   * Causes an immediate plane transition with an optional transition   * effect @@ -359,140 +365,138 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {  }  reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv) { -	debug("kScrollWindowCreate"); -	kStub(s, argc, argv); -	return argv[0]; +	const reg_t object = argv[0]; +	const uint16 maxNumEntries = argv[1].toUint16(); + +	SegManager *segMan = s->_segMan; +	const int16 borderColor = readSelectorValue(segMan, object, SELECTOR(borderColor)); +	const TextAlign alignment = (TextAlign)readSelectorValue(segMan, object, SELECTOR(mode)); +	const GuiResourceId fontId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(font)); +	const int16 backColor = readSelectorValue(segMan, object, SELECTOR(back)); +	const int16 foreColor = readSelectorValue(segMan, object, SELECTOR(fore)); +	const reg_t plane = readSelector(segMan, object, SELECTOR(plane)); + +	Common::Rect rect; +	rect.left = readSelectorValue(segMan, object, SELECTOR(nsLeft)); +	rect.top = readSelectorValue(segMan, object, SELECTOR(nsTop)); +	rect.right = readSelectorValue(segMan, object, SELECTOR(nsRight)) + 1; +	rect.bottom = readSelectorValue(segMan, object, SELECTOR(nsBottom)) + 1; +	const Common::Point position(rect.left, rect.top); + +	return g_sci->_gfxControls32->makeScrollWindow(rect, position, plane, foreColor, backColor, fontId, alignment, borderColor, maxNumEntries);  }  reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv) { -	debug("kScrollWindowAdd"); -	return kStubNull(s, argc, argv); +	ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + +	const Common::String text = s->_segMan->getString(argv[1]); +	const GuiResourceId fontId = argv[2].toSint16(); +	const int16 color = argv[3].toSint16(); +	const TextAlign alignment = (TextAlign)argv[4].toSint16(); +	const bool scrollTo = argc > 5 ? (bool)argv[5].toUint16() : true; + +	return scrollWindow->add(text, fontId, color, alignment, scrollTo);  }  reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv) { -	debug("kScrollWindowWhere"); -	return kStubNull(s, argc, argv); +	ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + +	const uint16 where = (argv[1].toUint16() * scrollWindow->where()).toInt(); + +	return make_reg(0, where); +} + +reg_t kScrollWindowGo(EngineState *s, int argc, reg_t *argv) { +	ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + +	const Ratio scrollTop(argv[1].toSint16(), argv[2].toSint16()); +	scrollWindow->go(scrollTop); + +	return s->r_acc; +} + +reg_t kScrollWindowModify(EngineState *s, int argc, reg_t *argv) { +	ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + +	const reg_t entryId = argv[1]; +	const Common::String newText = s->_segMan->getString(argv[2]); +	const GuiResourceId fontId = argv[3].toSint16(); +	const int16 color = argv[4].toSint16(); +	const TextAlign alignment = (TextAlign)argv[5].toSint16(); +	const bool scrollTo = argc > 6 ? (bool)argv[6].toUint16() : true; + +	return scrollWindow->modify(entryId, newText, fontId, color, alignment, scrollTo); +} + +reg_t kScrollWindowHide(EngineState *s, int argc, reg_t *argv) { +	ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + +	scrollWindow->hide(); + +	return s->r_acc;  }  reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv) { -	debug("kScrollWindowShow"); -	return kStubNull(s, argc, argv); +	ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + +	scrollWindow->show(); + +	return s->r_acc;  } -reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv) { -	debug("kScrollWindowDestroy"); -	return kStubNull(s, argc, argv); +reg_t kScrollWindowPageUp(EngineState *s, int argc, reg_t *argv) { +	ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + +	scrollWindow->pageUp(); + +	return s->r_acc;  } -#if 0 -reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { -	// Used by SQ6 and LSL6 hires for the text area in the bottom of the -	// screen. The relevant scripts also exist in Phantasmagoria 1, but they're -	// unused. This is always called by scripts 64906 (ScrollerWindow) and -	// 64907 (ScrollableWindow). - -	reg_t kWindow = argv[1]; -	uint16 op = argv[0].toUint16(); -	switch (op) { -	case 0:	// Init -		// TODO: Init reads the nsLeft, nsTop, nsRight, nsBottom, -		//       borderColor, fore, back, mode, font, plane selectors -		//       from the window in argv[1]. -		g_sci->_gfxFrameout->initScrollText(argv[2].toUint16());	// maxItems -		g_sci->_gfxFrameout->clearScrollTexts(); -		return argv[1];	// kWindow -	case 1: // Show message, called by ScrollableWindow::addString -	case 14: // Modify message, called by ScrollableWindow::modifyString -		// TODO: The parameters in Modify are shifted by one: the first -		//       argument is the handle of the text to modify. The others -		//       are as Add. -		{ -		Common::String text = s->_segMan->getString(argv[2]); -		uint16 x = 0; -		uint16 y = 0; -		// TODO: argv[3] is font -		// TODO: argv[4] is color -		// TODO: argv[5] is alignment (0 = left, 1 = center, 2 = right) -		//       font,color,alignment may also be -1. (Maybe same as previous?) -		// TODO: argv[6] is an optional bool, defaulting to true if not present. -		//       If true, the old contents are scrolled out of view. -		// TODO: Return a handle of the inserted text. (Used for modify/insert) -		//       This handle looks like it should also be usable by kString. -		g_sci->_gfxFrameout->addScrollTextEntry(text, kWindow, x, y, (op == 14)); -		} -		break; -	case 2: // Clear, called by ScrollableWindow::erase -		g_sci->_gfxFrameout->clearScrollTexts(); -		break; -	case 3: // Page up, called by ScrollableWindow::scrollTo -		// TODO -		kStub(s, argc, argv); -		break; -	case 4: // Page down, called by ScrollableWindow::scrollTo -		// TODO -		kStub(s, argc, argv); -		break; -	case 5: // Up arrow, called by ScrollableWindow::scrollTo -		g_sci->_gfxFrameout->prevScrollText(); -		break; -	case 6: // Down arrow, called by ScrollableWindow::scrollTo -		g_sci->_gfxFrameout->nextScrollText(); -		break; -	case 7: // Home, called by ScrollableWindow::scrollTo -		g_sci->_gfxFrameout->firstScrollText(); -		break; -	case 8: // End, called by ScrollableWindow::scrollTo -		g_sci->_gfxFrameout->lastScrollText(); -		break; -	case 9: // Resize, called by ScrollableWindow::resize and ScrollerWindow::resize -		// TODO: This reads the nsLeft, nsTop, nsRight, nsBottom -		//       selectors from the SCI object passed in argv[2]. -		kStub(s, argc, argv); -		break; -	case 10: // Where, called by ScrollableWindow::where -		// TODO: -		// Gives the current relative scroll location as a fraction -		// with argv[2] as the denominator. (Return value is the numerator.) -		// Silenced the warnings because of the high amount of console spam -		//kStub(s, argc, argv); -		break; -	case 11: // Go, called by ScrollableWindow::scrollTo -		// TODO: -		// Two arguments provide a fraction: argv[2] is num., argv[3] is denom. -		// Scrolls to the relative location given by the fraction. -		kStub(s, argc, argv); -		break; -	case 12: // Insert, called by ScrollableWindow::insertString -		// 5 extra parameters here: -		// handle of insert location (new string takes that position). -		// text, font, color, alignment -		// TODO -		kStub(s, argc, argv); -		break; -	// case 13 (Delete) is handled below -	// case 14 (Modify) is handled above -	case 15: // Hide, called by ScrollableWindow::hide -		g_sci->_gfxFrameout->toggleScrollText(false); -		break; -	case 16: // Show, called by ScrollableWindow::show -		g_sci->_gfxFrameout->toggleScrollText(true); -		break; -	case 17: // Destroy, called by ScrollableWindow::dispose -		g_sci->_gfxFrameout->clearScrollTexts(); -		break; -	case 13: // Delete, unused -	case 18: // Text, unused -	case 19: // Reconstruct, unused -		error("kScrollWindow: Unused subop %d invoked", op); -		break; -	default: -		error("kScrollWindow: unknown subop %d", op); -		break; -	} +reg_t kScrollWindowPageDown(EngineState *s, int argc, reg_t *argv) { +	ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + +	scrollWindow->pageDown(); + +	return s->r_acc; +} + +reg_t kScrollWindowUpArrow(EngineState *s, int argc, reg_t *argv) { +	ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + +	scrollWindow->upArrow(); + +	return s->r_acc; +} + +reg_t kScrollWindowDownArrow(EngineState *s, int argc, reg_t *argv) { +	ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + +	scrollWindow->downArrow(); + +	return s->r_acc; +} + +reg_t kScrollWindowHome(EngineState *s, int argc, reg_t *argv) { +	ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + +	scrollWindow->home(); + +	return s->r_acc; +} + +reg_t kScrollWindowEnd(EngineState *s, int argc, reg_t *argv) { +	ScrollWindow *scrollWindow = g_sci->_gfxControls32->getScrollWindow(argv[0]); + +	scrollWindow->end(); + +	return s->r_acc; +} + +reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv) { +	g_sci->_gfxControls32->destroyScrollWindow(argv[0]);  	return s->r_acc;  } -#endif  reg_t kFont(EngineState *s, int argc, reg_t *argv) {  	if (!s) @@ -546,46 +550,31 @@ reg_t kBitmapDrawLine(EngineState *s, int argc, reg_t *argv) {  }  reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv) { -	// viewId, loopNo, celNo, displace x, displace y, unused, view x, view y +	BitmapResource bitmap(argv[0]); +	CelObjView view(argv[1].toUint16(), argv[2].toSint16(), argv[3].toSint16()); -	// called e.g. from TiledBitmap::resize() in Torin's Passage, script 64869 -	// The tiled view seems to always have 2 loops. -	// These loops need to have 1 cel in loop 0 and 8 cels in loop 1. +	const int16 x = argc > 4 ? argv[4].toSint16() : 0; +	const int16 y = argc > 5 ? argv[5].toSint16() : 0; +	const int16 alignX = argc > 7 ? argv[7].toSint16() : -1; +	const int16 alignY = argc > 8 ? argv[8].toSint16() : -1; -	return kStubNull(s, argc + 1, argv - 1); +	Common::Point position( +		x == -1 ? bitmap.getDisplace().x : x, +		y == -1 ? bitmap.getDisplace().y : y +	); -#if 0 -	// tiled surface -	// 6 params, called e.g. from TiledBitmap::resize() in Torin's Passage, -	// script 64869 -	reg_t hunkId = argv[1];	// obtained from kBitmap(0) -	// The tiled view seems to always have 2 loops. -	// These loops need to have 1 cel in loop 0 and 8 cels in loop 1. -	uint16 viewNum = argv[2].toUint16();	// vTiles selector -	uint16 loop = argv[3].toUint16(); -	uint16 cel = argv[4].toUint16(); -	uint16 x = argv[5].toUint16(); -	uint16 y = argv[6].toUint16(); - -	byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); -	// Get totalWidth, totalHeight -	uint16 totalWidth = READ_LE_UINT16(memoryPtr); -	uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); -	byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; - -	GfxView *view = g_sci->_gfxCache->getView(viewNum); -	uint16 tileWidth = view->getWidth(loop, cel); -	uint16 tileHeight = view->getHeight(loop, cel); -	const byte *tileBitmap = view->getBitmap(loop, cel); -	uint16 width = MIN<uint16>(totalWidth - x, tileWidth); -	uint16 height = MIN<uint16>(totalHeight - y, tileHeight); - -	for (uint16 curY = 0; curY < height; curY++) { -		for (uint16 curX = 0; curX < width; curX++) { -			bitmap[(curY + y) * totalWidth + (curX + x)] = tileBitmap[curY * tileWidth + curX]; -		} -	} -#endif +	position.x -= alignX == -1 ? view._displace.x : alignX; +	position.y -= alignY == -1 ? view._displace.y : alignY; + +	Common::Rect drawRect( +		position.x, +		position.y, +		position.x + view._width, +		position.y + view._height +	); +	drawRect.clip(Common::Rect(bitmap.getWidth(), bitmap.getHeight())); +	view.draw(bitmap.getBuffer(), drawRect, position, view._mirrorX); +	return s->r_acc;  }  reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv) { @@ -616,9 +605,8 @@ reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv) {  	textRect.clip(Common::Rect(bitmap.getWidth(), bitmap.getHeight()));  	reg_t textBitmapObject = g_sci->_gfxText32->createFontBitmap(textRect.width(), textRect.height(), Common::Rect(textRect.width(), textRect.height()), text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, false); -	Buffer bitmapBuffer(bitmap.getWidth(), bitmap.getHeight(), bitmap.getPixels());  	CelObjMem textCel(textBitmapObject); -	textCel.draw(bitmapBuffer, textRect, Common::Point(textRect.left, textRect.top), false); +	textCel.draw(bitmap.getBuffer(), textRect, Common::Point(textRect.left, textRect.top), false);  	s->_segMan->freeHunkEntry(textBitmapObject);  	return s->r_acc; @@ -635,8 +623,7 @@ reg_t kBitmapDrawColor(EngineState *s, int argc, reg_t *argv) {  		argv[4].toSint16() + 1  	); -	Buffer buffer(bitmap.getWidth(), bitmap.getHeight(), bitmap.getPixels()); -	buffer.fillRect(fillRect, argv[5].toSint16()); +	bitmap.getBuffer().fillRect(fillRect, argv[5].toSint16());  	return s->r_acc;  } @@ -700,46 +687,77 @@ reg_t kEditText(EngineState *s, int argc, reg_t *argv) {  }  reg_t kAddLine(EngineState *s, int argc, reg_t *argv) { -	return kStubNull(s, argc, argv); // return 0:0 for now, so that follow up calls won't create signature mismatches -#if 0 -	reg_t plane = argv[0]; -	Common::Point startPoint(argv[1].toUint16(), argv[2].toUint16()); -	Common::Point endPoint(argv[3].toUint16(), argv[4].toUint16()); -	byte priority = (byte)argv[5].toUint16(); -	byte color = (byte)argv[6].toUint16(); -	byte style = (byte)argv[7].toUint16();	// 0: solid, 1: dashed, 2: pattern -	byte pattern = (byte)argv[8].toUint16(); -	byte thickness = (byte)argv[9].toUint16(); -//	return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, 0); -	return s->r_acc; -#endif +	const reg_t plane = argv[0]; +	const Common::Point startPoint(argv[1].toSint16(), argv[2].toSint16()); +	const Common::Point endPoint(argv[3].toSint16(), argv[4].toSint16()); + +	int16 priority; +	uint8 color; +	LineStyle style; +	uint16 pattern; +	uint8 thickness; + +	if (argc == 10) { +		priority = argv[5].toSint16(); +		color = (uint8)argv[6].toUint16(); +		style = (LineStyle)argv[7].toSint16(); +		pattern = argv[8].toUint16(); +		thickness = (uint8)argv[9].toUint16(); +	} else { +		priority = 1000; +		color = 255; +		style = kLineStyleSolid; +		pattern = 0; +		thickness = 1; +	} + +	return g_sci->_gfxPaint32->kernelAddLine(plane, startPoint, endPoint, priority, color, style, pattern, thickness);  }  reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) { -	return kStub(s, argc, argv); +	const reg_t screenItemObject = argv[0]; +	const reg_t planeObject = argv[1]; +	const Common::Point startPoint(argv[2].toSint16(), argv[3].toSint16()); +	const Common::Point endPoint(argv[4].toSint16(), argv[5].toSint16()); + +	int16 priority; +	uint8 color; +	LineStyle style; +	uint16 pattern; +	uint8 thickness; + +	Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject); +	if (plane == nullptr) { +		error("kUpdateLine: Plane %04x:%04x not found", PRINT_REG(planeObject)); +	} + +	ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject); +	if (screenItem == nullptr) { +		error("kUpdateLine: Screen item %04x:%04x not found", PRINT_REG(screenItemObject)); +	} + +	if (argc == 11) { +		priority = argv[6].toSint16(); +		color = (uint8)argv[7].toUint16(); +		style = (LineStyle)argv[8].toSint16(); +		pattern = argv[9].toUint16(); +		thickness = (uint8)argv[10].toUint16(); +	} else { +		priority = screenItem->_priority; +		color = screenItem->_celInfo.color; +		style = kLineStyleSolid; +		pattern = 0; +		thickness = 1; +	} + +	g_sci->_gfxPaint32->kernelUpdateLine(screenItem, plane, startPoint, endPoint, priority, color, style, pattern, thickness); -#if 0 -	reg_t hunkId = argv[0]; -	reg_t plane = argv[1]; -	Common::Point startPoint(argv[2].toUint16(), argv[3].toUint16()); -	Common::Point endPoint(argv[4].toUint16(), argv[5].toUint16()); -	// argv[6] is unknown (a number, usually 200) -	byte color = (byte)argv[7].toUint16(); -	byte priority = (byte)argv[8].toUint16(); -	byte control = (byte)argv[9].toUint16(); -	// argv[10] is unknown (usually a small number, 1 or 2). Thickness, perhaps? -//	g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control);  	return s->r_acc; -#endif  } +  reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv) { -	return kStub(s, argc, argv); -#if 0 -	reg_t hunkId = argv[0]; -	reg_t plane = argv[1]; -//	g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId); +	g_sci->_gfxPaint32->kernelDeleteLine(argv[0], argv[1]);  	return s->r_acc; -#endif  }  reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) { @@ -908,57 +926,69 @@ reg_t kPalCycle(EngineState *s, int argc, reg_t *argv) {  	return s->r_acc;  } -reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) { +reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv) {  	if (!s)  		return make_reg(0, getSciVersion());  	error("not supposed to call this");  } -reg_t kRemapOff(EngineState *s, int argc, reg_t *argv) { -	byte color = (argc >= 1) ? argv[0].toUint16() : 0; -	g_sci->_gfxRemap32->remapOff(color); +reg_t kRemapColorsOff(EngineState *s, int argc, reg_t *argv) { +	if (argc == 0) { +		g_sci->_gfxRemap32->remapAllOff(); +	} else { +		const uint8 color = argv[0].toUint16(); +		g_sci->_gfxRemap32->remapOff(color); +	}  	return s->r_acc;  } -reg_t kRemapByRange(EngineState *s, int argc, reg_t *argv) { -	byte color = argv[0].toUint16(); -	byte from = argv[1].toUint16(); -	byte to = argv[2].toUint16(); -	byte base = argv[3].toUint16(); -	// The last parameter, depth, is unused -	g_sci->_gfxRemap32->setRemappingRange(color, from, to, base); +reg_t kRemapColorsByRange(EngineState *s, int argc, reg_t *argv) { +	const uint8 color = argv[0].toUint16(); +	const int16 from = argv[1].toSint16(); +	const int16 to = argv[2].toSint16(); +	const int16 base = argv[3].toSint16(); +	// NOTE: There is an optional last parameter after `base` +	// which was only used by the priority map debugger, which +	// does not exist in release versions of SSCI +	g_sci->_gfxRemap32->remapByRange(color, from, to, base);  	return s->r_acc;  } -reg_t kRemapByPercent(EngineState *s, int argc, reg_t *argv) { -	byte color = argv[0].toUint16(); -	byte percent = argv[1].toUint16(); -	// The last parameter, depth, is unused -	g_sci->_gfxRemap32->setRemappingPercent(color, percent); +reg_t kRemapColorsByPercent(EngineState *s, int argc, reg_t *argv) { +	const uint8 color = argv[0].toUint16(); +	const int16 percent = argv[1].toSint16(); +	// NOTE: There is an optional last parameter after `percent` +	// which was only used by the priority map debugger, which +	// does not exist in release versions of SSCI +	g_sci->_gfxRemap32->remapByPercent(color, percent);  	return s->r_acc;  } -reg_t kRemapToGray(EngineState *s, int argc, reg_t *argv) { -	byte color = argv[0].toUint16(); -	byte gray = argv[1].toUint16(); -	// The last parameter, depth, is unused -	g_sci->_gfxRemap32->setRemappingToGray(color, gray); +reg_t kRemapColorsToGray(EngineState *s, int argc, reg_t *argv) { +	const uint8 color = argv[0].toUint16(); +	const int16 gray = argv[1].toSint16(); +	// NOTE: There is an optional last parameter after `gray` +	// which was only used by the priority map debugger, which +	// does not exist in release versions of SSCI +	g_sci->_gfxRemap32->remapToGray(color, gray);  	return s->r_acc;  } -reg_t kRemapToPercentGray(EngineState *s, int argc, reg_t *argv) { -	byte color = argv[0].toUint16(); -	byte gray = argv[1].toUint16(); -	byte percent = argv[2].toUint16(); -	// The last parameter, depth, is unused -	g_sci->_gfxRemap32->setRemappingToPercentGray(color, gray, percent); +reg_t kRemapColorsToPercentGray(EngineState *s, int argc, reg_t *argv) { +	const uint8 color = argv[0].toUint16(); +	const int16 gray = argv[1].toSint16(); +	const int16 percent = argv[2].toSint16(); +	// NOTE: There is an optional last parameter after `percent` +	// which was only used by the priority map debugger, which +	// does not exist in release versions of SSCI +	g_sci->_gfxRemap32->remapToPercentGray(color, gray, percent);  	return s->r_acc;  } -reg_t kRemapSetNoMatchRange(EngineState *s, int argc, reg_t *argv) { -	byte from = argv[0].toUint16(); -	byte count = argv[1].toUint16(); -	g_sci->_gfxRemap32->setNoMatchRange(from, count); +reg_t kRemapColorsBlockRange(EngineState *s, int argc, reg_t *argv) { +	const uint8 from = argv[0].toUint16(); +	const uint8 count = argv[1].toUint16(); +	g_sci->_gfxRemap32->blockRange(from, count);  	return s->r_acc;  } diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp index 7ac744f584..06f16299aa 100644 --- a/engines/sci/engine/kpathing.cpp +++ b/engines/sci/engine/kpathing.cpp @@ -326,7 +326,7 @@ static void draw_line(EngineState *s, Common::Point p1, Common::Point p2, int ty  	p2.y = CLIP<int16>(p2.y, 0, height - 1);  	assert(type >= 0 && type <= 3); -	g_sci->_gfxPaint->kernelGraphDrawLine(p1, p2, poly_colors[type], 255, 255); +	g_sci->_gfxPaint16->kernelGraphDrawLine(p1, p2, poly_colors[type], 255, 255);  }  static void draw_point(EngineState *s, Common::Point p, int start, int width, int height) { diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp index 398a623286..ed53b8d52f 100644 --- a/engines/sci/engine/ksound.cpp +++ b/engines/sci/engine/ksound.cpp @@ -26,7 +26,11 @@  #include "sci/engine/kernel.h"  #include "sci/engine/vm.h"		// for Object  #include "sci/sound/audio.h" +#ifdef ENABLE_SCI32 +#include "sci/sound/audio32.h" +#endif  #include "sci/sound/soundcmd.h" +#include "sci/sound/sync.h"  #include "audio/mixer.h"  #include "common/system.h" @@ -46,7 +50,6 @@ reg_t kDoSound(EngineState *s, int argc, reg_t *argv) {  CREATE_DOSOUND_FORWARD(DoSoundInit)  CREATE_DOSOUND_FORWARD(DoSoundPlay) -CREATE_DOSOUND_FORWARD(DoSoundRestore)  CREATE_DOSOUND_FORWARD(DoSoundDispose)  CREATE_DOSOUND_FORWARD(DoSoundMute)  CREATE_DOSOUND_FORWARD(DoSoundStop) @@ -61,13 +64,41 @@ CREATE_DOSOUND_FORWARD(DoSoundUpdateCues)  CREATE_DOSOUND_FORWARD(DoSoundSendMidi)  CREATE_DOSOUND_FORWARD(DoSoundGlobalReverb)  CREATE_DOSOUND_FORWARD(DoSoundSetHold) -CREATE_DOSOUND_FORWARD(DoSoundDummy)  CREATE_DOSOUND_FORWARD(DoSoundGetAudioCapability)  CREATE_DOSOUND_FORWARD(DoSoundSuspend)  CREATE_DOSOUND_FORWARD(DoSoundSetVolume)  CREATE_DOSOUND_FORWARD(DoSoundSetPriority)  CREATE_DOSOUND_FORWARD(DoSoundSetLoop) +#ifdef ENABLE_SCI32 +reg_t kDoSoundPhantasmagoriaMac(EngineState *s, int argc, reg_t *argv) { +	// Phantasmagoria Mac (and seemingly no other game (!)) uses this +	// cutdown version of kDoSound. + +	switch (argv[0].toUint16()) { +	case 0: +		return g_sci->_soundCmd->kDoSoundMasterVolume(argc - 1, argv + 1, s->r_acc); +	case 2: +		return g_sci->_soundCmd->kDoSoundInit(argc - 1, argv + 1, s->r_acc); +	case 3: +		return g_sci->_soundCmd->kDoSoundDispose(argc - 1, argv + 1, s->r_acc); +	case 4: +		return g_sci->_soundCmd->kDoSoundPlay(argc - 1, argv + 1, s->r_acc); +	case 5: +		return g_sci->_soundCmd->kDoSoundStop(argc - 1, argv + 1, s->r_acc); +	case 8: +		return g_sci->_soundCmd->kDoSoundSetVolume(argc - 1, argv + 1, s->r_acc); +	case 9: +		return g_sci->_soundCmd->kDoSoundSetLoop(argc - 1, argv + 1, s->r_acc); +	case 10: +		return g_sci->_soundCmd->kDoSoundUpdateCues(argc - 1, argv + 1, s->r_acc); +	} + +	error("Unknown kDoSound Phantasmagoria Mac subop %d", argv[0].toUint16()); +	return s->r_acc; +} +#endif +  reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) {  	switch (argv[0].toUint16()) {  	case kSciAudioPlay: { @@ -113,7 +144,8 @@ reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) {  }  /** - * Used for speech playback and digital soundtracks in CD games + * Used for speech playback and digital soundtracks in CD games. + * This is the SCI16 version; SCI32 is handled separately.   */  reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {  	// JonesCD uses different functions based on the cdaudio.map file @@ -184,14 +216,6 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {  		int16 volume = argv[1].toUint16();  		volume = CLIP<int16>(volume, 0, AUDIO_VOLUME_MAX);  		debugC(kDebugLevelSound, "kDoAudio: set volume to %d", volume); -#ifdef ENABLE_SCI32 -		if (getSciVersion() >= SCI_VERSION_2_1_EARLY) { -			int16 volumePrev = mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2; -			volumePrev = CLIP<int16>(volumePrev, 0, AUDIO_VOLUME_MAX); -			mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2); -			return make_reg(0, volumePrev); -		} else -#endif  		mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2);  		break;  	} @@ -232,12 +256,6 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {  		if (getSciVersion() <= SCI_VERSION_1_1) {  			debugC(kDebugLevelSound, "kDoAudio: CD audio subop");  			return kDoCdAudio(s, argc - 1, argv + 1); -#ifdef ENABLE_SCI32 -		} else { -			// TODO: This isn't CD Audio in SCI32 anymore -			warning("kDoAudio: Unhandled case 10, %d extra arguments passed", argc - 1); -			break; -#endif  		}  		// 3 new subops in Pharkas CD (including CD demo). kDoAudio in Pharkas sits at seg026:038C @@ -286,14 +304,12 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {  }  reg_t kDoSync(EngineState *s, int argc, reg_t *argv) { -	SegManager *segMan = s->_segMan;  	switch (argv[0].toUint16()) {  	case kSciAudioSyncStart: {  		ResourceId id; -		g_sci->_audio->stopSoundSync(); +		g_sci->_sync->stop(); -		// Load sound sync resource and lock it  		if (argc == 3) {  			id = ResourceId(kResourceTypeSync, argv[2].toUint16());  		} else if (argc == 7) { @@ -304,14 +320,14 @@ reg_t kDoSync(EngineState *s, int argc, reg_t *argv) {  			return s->r_acc;  		} -		g_sci->_audio->setSoundSync(id, argv[1], segMan); +		g_sci->_sync->start(id, argv[1]);  		break;  	}  	case kSciAudioSyncNext: -		g_sci->_audio->doSoundSync(argv[1], segMan); +		g_sci->_sync->next(argv[1]);  		break;  	case kSciAudioSyncStop: -		g_sci->_audio->stopSoundSync(); +		g_sci->_sync->stop();  		break;  	default:  		error("DoSync: Unhandled subfunction %d", argv[0].toUint16()); @@ -321,6 +337,155 @@ reg_t kDoSync(EngineState *s, int argc, reg_t *argv) {  }  #ifdef ENABLE_SCI32 +reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv) { +	if (!s) +		return make_reg(0, getSciVersion()); +	error("not supposed to call this"); +} + +reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv) { +	return make_reg(0, 0); +} + +reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv) { +	return g_sci->_audio32->kernelPlay(false, argc, argv); +} + +reg_t kDoAudioPlay(EngineState *s, int argc, reg_t *argv) { +	return g_sci->_audio32->kernelPlay(true, argc, argv); +} + +reg_t kDoAudioStop(EngineState *s, int argc, reg_t *argv) { +	const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG); +	return make_reg(0, g_sci->_audio32->stop(channelIndex)); +} + +reg_t kDoAudioPause(EngineState *s, int argc, reg_t *argv) { +	const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG); +	return make_reg(0, g_sci->_audio32->pause(channelIndex)); +} + +reg_t kDoAudioResume(EngineState *s, int argc, reg_t *argv) { +	const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG); +	return make_reg(0, g_sci->_audio32->resume(channelIndex)); +} + +reg_t kDoAudioPosition(EngineState *s, int argc, reg_t *argv) { +	const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG); +	return make_reg(0, g_sci->_audio32->getPosition(channelIndex)); +} + +reg_t kDoAudioRate(EngineState *s, int argc, reg_t *argv) { +	// NOTE: In the original engine this would set the hardware +	// DSP sampling rate; ScummVM mixer does not need this, so +	// we only store the value to satisfy engine compatibility. + +	if (argc > 0) { +		const uint16 sampleRate = argv[0].toUint16(); +		if (sampleRate != 0) { +			g_sci->_audio32->setSampleRate(sampleRate); +		} +	} + +	return make_reg(0, g_sci->_audio32->getSampleRate()); +} + +reg_t kDoAudioVolume(EngineState *s, int argc, reg_t *argv) { +	const int16 volume = argc > 0 ? argv[0].toSint16() : -1; +	const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 1, argc > 2 ? argv[2] : NULL_REG); + +	if (volume != -1) { +		g_sci->_audio32->setVolume(channelIndex, volume); +	} + +	return make_reg(0, g_sci->_audio32->getVolume(channelIndex)); +} + +reg_t kDoAudioGetCapability(EngineState *s, int argc, reg_t *argv) { +	return make_reg(0, 1); +} + +reg_t kDoAudioBitDepth(EngineState *s, int argc, reg_t *argv) { +	// NOTE: In the original engine this would set the hardware +	// DSP bit depth; ScummVM mixer does not need this, so +	// we only store the value to satisfy engine compatibility. + +	if (argc > 0) { +		const uint16 bitDepth = argv[0].toUint16(); +		if (bitDepth != 0) { +			g_sci->_audio32->setBitDepth(bitDepth); +		} +	} + +	return make_reg(0, g_sci->_audio32->getBitDepth()); +} + +reg_t kDoAudioMixing(EngineState *s, int argc, reg_t *argv) { +	if (argc > 0) { +		g_sci->_audio32->setAttenuatedMixing(argv[0].toUint16()); +	} + +	return make_reg(0, g_sci->_audio32->getAttenuatedMixing()); +} + +reg_t kDoAudioChannels(EngineState *s, int argc, reg_t *argv) { +	// NOTE: In the original engine this would set the hardware +	// DSP stereo output; ScummVM mixer does not need this, so +	// we only store the value to satisfy engine compatibility. + +	if (argc > 0) { +		const int16 numChannels = argv[0].toSint16(); +		if (numChannels != 0) { +			g_sci->_audio32->setNumOutputChannels(numChannels); +		} +	} + +	return make_reg(0, g_sci->_audio32->getNumOutputChannels()); +} + +reg_t kDoAudioPreload(EngineState *s, int argc, reg_t *argv) { +	// NOTE: In the original engine this would cause audio +	// data for new channels to be preloaded to memory when +	// the channel was initialized; we do not need this, so +	// we only store the value to satisfy engine compatibility. + +	if (argc > 0) { +		g_sci->_audio32->setPreload(argv[0].toUint16()); +	} + +	return make_reg(0, g_sci->_audio32->getPreload()); +} + +reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv) { +	if (argc < 4) { +		return make_reg(0, 0); +	} + +	// NOTE: Sierra did a nightmarish hack here, temporarily replacing +	// the argc of the kernel arguments with 2 and then restoring it +	// after findChannelByArgs was called. +	const int16 channelIndex = g_sci->_audio32->findChannelByArgs(2, argv, 0, argc > 5 ? argv[5] : NULL_REG); + +	const int16 volume = argv[1].toSint16(); +	const int16 speed = argv[2].toSint16(); +	const int16 steps = argv[3].toSint16(); +	const bool stopAfterFade = argc > 4 ? (bool)argv[4].toUint16() : false; + +	return make_reg(0, g_sci->_audio32->fadeChannel(channelIndex, volume, speed, steps, stopAfterFade)); +} + +reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv) { +	return make_reg(0, g_sci->_audio32->hasSignal()); +} + +reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv) { +	const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc == 3 ? argv[2] : NULL_REG); + +	const bool loop = argv[0].toSint16() != 0 && argv[0].toSint16() != 1; + +	g_sci->_audio32->setLoop(channelIndex, loop); +	return s->r_acc; +}  reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv) {  	// This is used by script 90 of MUMG Deluxe from the main menu to toggle @@ -335,33 +500,6 @@ reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv) {  	return s->r_acc;  } -reg_t kDoSoundPhantasmagoriaMac(EngineState *s, int argc, reg_t *argv) { -	// Phantasmagoria Mac (and seemingly no other game (!)) uses this -	// cutdown version of kDoSound. - -	switch (argv[0].toUint16()) { -	case 0: -		return g_sci->_soundCmd->kDoSoundMasterVolume(argc - 1, argv + 1, s->r_acc); -	case 2: -		return g_sci->_soundCmd->kDoSoundInit(argc - 1, argv + 1, s->r_acc); -	case 3: -		return g_sci->_soundCmd->kDoSoundDispose(argc - 1, argv + 1, s->r_acc); -	case 4: -		return g_sci->_soundCmd->kDoSoundPlay(argc - 1, argv + 1, s->r_acc); -	case 5: -		return g_sci->_soundCmd->kDoSoundStop(argc - 1, argv + 1, s->r_acc); -	case 8: -		return g_sci->_soundCmd->kDoSoundSetVolume(argc - 1, argv + 1, s->r_acc); -	case 9: -		return g_sci->_soundCmd->kDoSoundSetLoop(argc - 1, argv + 1, s->r_acc); -	case 10: -		return g_sci->_soundCmd->kDoSoundUpdateCues(argc - 1, argv + 1, s->r_acc); -	} - -	error("Unknown kDoSound Phantasmagoria Mac subop %d", argv[0].toUint16()); -	return s->r_acc; -} -  #endif  } // End of namespace Sci diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index 302f046458..0972aec4a4 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -48,8 +48,9 @@  #include "sci/sound/music.h"  #ifdef ENABLE_SCI32 -#include "sci/graphics/palette32.h"  #include "sci/graphics/frameout.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/remap32.h"  #endif  namespace Sci { @@ -807,6 +808,33 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) {  		}  	}  } + +void GfxRemap32::saveLoadWithSerializer(Common::Serializer &s) { +	if (s.getVersion() < 35) { +		return; +	} + +	s.syncAsByte(_numActiveRemaps); +	s.syncAsByte(_blockedRangeStart); +	s.syncAsSint16LE(_blockedRangeCount); + +	for (uint i = 0; i < _remaps.size(); ++i) { +		SingleRemap &singleRemap = _remaps[i]; +		s.syncAsByte(singleRemap._type); +		if (s.isLoading() && singleRemap._type != kRemapNone) { +			singleRemap.reset(); +		} +		s.syncAsByte(singleRemap._from); +		s.syncAsByte(singleRemap._to); +		s.syncAsByte(singleRemap._delta); +		s.syncAsByte(singleRemap._percent); +		s.syncAsByte(singleRemap._gray); +	} + +	if (s.isLoading()) { +		_needsUpdate = true; +	} +}  #endif  void GfxPorts::saveLoadWithSerializer(Common::Serializer &s) { diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h index 459e992e24..43909accf2 100644 --- a/engines/sci/engine/savegame.h +++ b/engines/sci/engine/savegame.h @@ -37,6 +37,7 @@ struct EngineState;   *   * Version - new/changed feature   * ============================= + *      35 - SCI32 remap   *      34 - SCI32 palettes, and store play time in ticks   *      33 - new overridePriority flag in MusicEntry   *      32 - new playBed flag in MusicEntry @@ -59,7 +60,7 @@ struct EngineState;   */  enum { -	CURRENT_SAVEGAME_VERSION = 34, +	CURRENT_SAVEGAME_VERSION = 35,  	MINIMUM_SAVEGAME_VERSION = 14  }; diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index 116ffdd5a2..5d7fa49ce5 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -379,12 +379,40 @@ static const SciScriptPatcherEntry ecoquest2Signatures[] = {  };  // =========================================================================== +// Fan-made games +// Attention: Try to make script patches as specific as possible + +// CascadeQuest::autosave in script 994 is called various times to auto-save the game. +// The script use a fixed slot "999" for this purpose. This doesn't work in ScummVM, because we do not let +//  scripts save directly into specific slots, but instead use virtual slots / detect scripts wanting to +//  create a new slot. +// +// For this game we patch the code to use slot 99 instead. kSaveGame also checks for Cascade Quest, +//  will then check, if slot 99 is asked for and will then use the actual slot 0, which is the official +//  ScummVM auto-save slot. +// +// Responsible method: CascadeQuest::autosave +// Fixes bug: #7007 +static const uint16 fanmadeSignatureCascadeQuestFixAutoSaving[] = { +	SIG_MAGICDWORD, +	0x38, SIG_UINT16(0x03e7),        // pushi 3E7 (999d) -> save game slot 999 +	0x74, SIG_UINT16(0x06f8),        // lofss "AutoSave" +	0x89, 0x1e,                      // lsg global[1E] +	0x43, 0x2d, 0x08,                // callk SaveGame +	SIG_END +}; + +static const uint16 fanmadePatchCascadeQuestFixAutoSaving[] = { +	0x38, PATCH_UINT16((SAVEGAMEID_OFFICIALRANGE_START - 1)), // fix slot +	PATCH_END +}; +  // EventHandler::handleEvent in Demo Quest has a bug, and it jumps to the  // wrong address when an incorrect word is typed, therefore leading to an  // infinite loop. This script bug was not apparent in SSCI, probably because  // event handling was slightly different there, so it was never discovered.  // Fixes bug: #5120 -static const uint16 fanmadeSignatureInfiniteLoop[] = { +static const uint16 fanmadeSignatureDemoQuestInfiniteLoop[] = {  	0x38, SIG_UINT16(0x004c),        // pushi 004c  	0x39, 0x00,                      // pushi 00  	0x87, 0x01,                      // lap 01 @@ -395,15 +423,16 @@ static const uint16 fanmadeSignatureInfiniteLoop[] = {  	SIG_END  }; -static const uint16 fanmadePatchInfiniteLoop[] = { +static const uint16 fanmadePatchDemoQuestInfiniteLoop[] = {  	PATCH_ADDTOOFFSET(+10), -	0x30, SIG_UINT16(0x0032),        // bnt 0032  [06a8] --> pushi 004c +	0x30, PATCH_UINT16(0x0032),      // bnt 0032  [06a8] --> pushi 004c  	PATCH_END  }; -//          script, description,                                      signature                     patch +//          script, description,                                      signature                                  patch  static const SciScriptPatcherEntry fanmadeSignatures[] = { -	{  true,   999, "infinite loop on typo",                       1, fanmadeSignatureInfiniteLoop, fanmadePatchInfiniteLoop }, +	{  true,   994, "Cascade Quest: fix auto-saving",              1, fanmadeSignatureCascadeQuestFixAutoSaving, fanmadePatchCascadeQuestFixAutoSaving }, +	{  true,   999, "Demo Quest: infinite loop on typo",           1, fanmadeSignatureDemoQuestInfiniteLoop,     fanmadePatchDemoQuestInfiniteLoop },  	SCI_SIGNATUREENTRY_TERMINATOR  }; @@ -1654,9 +1683,160 @@ static const uint16 laurabow1PatchEasterEggViewFix[] = {  	PATCH_END  }; +// When oiling the armor or opening the visor of the armor, the scripts +//  first check if Laura/ego is near the armor and if she is not, they will move her +//  to the armor. After that further code is executed. +// +// The current location is checked by a ego::inRect() call. +// +// The given rect for the inRect call inside openVisor::changeState was made larger for Atari ST/Amiga versions. +//  We change the PC version to use the same rect. +// +// Additionally the coordinate, that Laura is moved to, is 152, 107 and may not be reachable depending on where +//  Laura/ego was, when "use oil on helmet of armor" / "open visor of armor" got entered. +//  Bad coordinates are for example 82, 110, which then cause collisions and effectively an endless loop. +//  Game will effectively "freeze" and the user is only able to restore a previous game. +//  This also happened, when using the original interpreter. +//  We change the destination coordinate to 152, 110, which seems to be reachable all the time. +// +// The following patch fixes the rect for the PC version of the game. +// +// Applies to at least: English PC Floppy +// Responsible method: openVisor::changeState (script 37) +// Fixes bug: #7119 +static const uint16 laurabow1SignatureArmorOpenVisorFix[] = { +	0x39, 0x04,                         // pushi 04 +	SIG_MAGICDWORD, +	0x39, 0x6a,                         // pushi 6a (106d) +	0x38, SIG_UINT16(0x96),             // pushi 0096 (150d) +	0x39, 0x6c,                         // pushi 6c (108d) +	0x38, SIG_UINT16(0x98),             // pushi 0098 (152d) +	SIG_END +}; + +static const uint16 laurabow1PatchArmorOpenVisorFix[] = { +	PATCH_ADDTOOFFSET(+2), +	0x39, 0x68,                         // pushi 68 (104d)   (-2) +	0x38, SIG_UINT16(0x94),             // pushi 0094 (148d) (-2) +	0x39, 0x6f,                         // pushi 6f (111d)   (+3) +	0x38, SIG_UINT16(0x9a),             // pushi 009a (154d) (+2) +	PATCH_END +}; + +// This here fixes the destination coordinate (exact details are above). +// +// Applies to at least: English PC Floppy, English Atari ST Floppy, English Amiga Floppy +// Responsible method: openVisor::changeState, oiling::changeState (script 37) +// Fixes bug: #7119 +static const uint16 laurabow1SignatureArmorMoveToFix[] = { +	SIG_MAGICDWORD, +	0x36,                               // push +	0x39, 0x6b,                         // pushi 6B (107d) +	0x38, SIG_UINT16(0x0098),           // pushi 98 (152d) +	0x7c,                               // pushSelf +	0x81, 0x00,                         // lag global[0] +	SIG_END +}; + +static const uint16 laurabow1PatchArmorMoveToFix[] = { +	PATCH_ADDTOOFFSET(+1), +	0x39, 0x6e,                         // pushi 6E (110d) - adjust x, so that no collision can occur anymore +	PATCH_END +}; + +// In some cases like for example when the player oils the arm of the armor, command input stays +// disabled, even when the player exits fast enough, so that Laura doesn't die. +// +// This is caused by the scripts only enabling control (directional movement), but do not enable command input as well. +// +// This bug also happens, when using the original interpreter. +// And it was fixed for the Atari ST + Amiga versions of the game. +// +// Applies to at least: English PC Floppy +// Responsible method: 2nd subroutine in script 37, called by oiling::changeState(7) +// Fixes bug: #7154 +static const uint16 laurabow1SignatureArmorOilingArmFix[] = { +	0x38, SIG_UINT16(0x0089),           // pushi 89h +	0x76,                               // push0 +	SIG_MAGICDWORD, +	0x72, SIG_UINT16(0x1a5c),           // lofsa "Can" - offsets are not skipped to make sure only the PC version gets patched +	0x4a, 0x04,                         // send 04 +	0x38, SIG_UINT16(0x0089),           // pushi 89h +	0x76,                               // push0 +	0x72, SIG_UINT16(0x19a1),           // lofsa "Visor" +	0x4a, 0x04,                         // send 04 +	0x38, SIG_UINT16(0x0089),           // pushi 89h +	0x76,                               // push0 +	0x72, SIG_UINT16(0x194a),           // lofsa "note" +	0x4a, 0x04,                         // send 04 +	0x38, SIG_UINT16(0x0089),           // pushi 89h +	0x76,                               // push0 +	0x72, SIG_UINT16(0x18f3),           // lofsa "valve" +	0x4a, 0x04,                         // send 04 +	0x8b, 0x34,                         // lsl local[34h] +	0x35, 0x02,                         // ldi 02 +	0x1c,                               // ne? +	0x30, SIG_UINT16(0x0014),           // bnt [to ret] +	0x8b, 0x34,                         // lsl local[34h] +	0x35, 0x05,                         // ldi 05 +	0x1c,                               // ne? +	0x30, SIG_UINT16(0x000c),           // bnt [to ret] +	0x8b, 0x34,                         // lsl local[34h] +	0x35, 0x06,                         // ldi 06 +	0x1c,                               // ne? +	0x30, SIG_UINT16(0x0004),           // bnt [to ret] +	// followed by code to call script 0 export to re-enable controls and call setMotion +	SIG_END +}; + +static const uint16 laurabow1PatchArmorOilingArmFix[] = { +	PATCH_ADDTOOFFSET(+3),              // skip over pushi 89h +	0x3c,                               // dup +	0x3c,                               // dup +	0x3c,                               // dup +	// saves a total of 6 bytes +	0x76,                               // push0 +	0x72, SIG_UINT16(0x1a59),           // lofsa "Can" +	0x4a, 0x04,                         // send 04 +	0x76,                               // push0 +	0x72, SIG_UINT16(0x19a1),           // lofsa "Visor" +	0x4a, 0x04,                         // send 04 +	0x76,                               // push0 +	0x72, SIG_UINT16(0x194d),           // lofsa "note" +	0x4a, 0x04,                         // send 04 +	0x76,                               // push0 +	0x72, SIG_UINT16(0x18f9),           // lofsa "valve" 18f3 +	0x4a, 0x04,                         // send 04 +	// new code to enable input as well, needs 9 spare bytes +	0x38, SIG_UINT16(0x00e2),           // canInput +	0x78,                               // push1 +	0x78,                               // push1 +	0x51, 0x2b,                         // class User +	0x4a, 0x06,                         // send 06 -> call User::canInput(1) +	// original code, but changed a bit to save some more bytes +	0x8b, 0x34,                         // lsl local[34h] +	0x35, 0x02,                         // ldi 02 +	0x04,                               // sub +	0x31, 0x12,                         // bnt [to ret] +	0x36,                               // push +	0x35, 0x03,                         // ldi 03 +	0x04,                               // sub +	0x31, 0x0c,                         // bnt [to ret] +	0x78,                               // push1 +	0x1a,                               // eq? +	0x2f, 0x08,                         // bt [to ret] +	// saves 7 bytes, we only need 3, so waste 4 bytes +	0x35, 0x00,                         // ldi 0 +	0x35, 0x00,                         // ldi 0 +	PATCH_END +}; +  //          script, description,                                      signature                           patch  static const SciScriptPatcherEntry laurabow1Signatures[] = { -	{  true,     4, "easter egg view fix",                         1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix }, +	{  true,     4, "easter egg view fix",                         1, laurabow1SignatureEasterEggViewFix,  laurabow1PatchEasterEggViewFix }, +	{  true,    37, "armor open visor fix",                        1, laurabow1SignatureArmorOpenVisorFix, laurabow1PatchArmorOpenVisorFix }, +	{  true,    37, "armor move to fix",                           2, laurabow1SignatureArmorMoveToFix,    laurabow1PatchArmorMoveToFix }, +	{  true,    37, "allowing input, after oiling arm",            1, laurabow1SignatureArmorOilingArmFix, laurabow1PatchArmorOilingArmFix },  	SCI_SIGNATUREENTRY_TERMINATOR  }; @@ -2761,7 +2941,7 @@ static const uint16 qfg3PatchImportDialog[] = {  // Teller::doChild. We jump to this call of hero::solvePuzzle to get that same  // behaviour.  // Applies to at least: English, German, Italian, French, Spanish Floppy -// Responsible method: unknown +// Responsible method: uhuraTell::doChild  // Fixes bug: #5172  static const uint16 qfg3SignatureWooDialog[] = {  	SIG_MAGICDWORD, @@ -3514,24 +3694,35 @@ static const uint16 sq1vgaSignatureSpiderDroidTiming[] = {  	0x30, SIG_UINT16(0x0005),           // bnt [further method code]  	0x35, 0x00,                         // ldi 00  	0x32, SIG_UINT16(0x0052),           // jmp [super-call] -	0x89, 0xa6,                         // lsg global[a6] +	0x89, 0xa6,                         // lsg global[a6] <-- flag gets set to 1 when ego went up the skeleton tail, when going down it's set to 2  	0x35, 0x01,                         // ldi 01  	0x1a,                               // eq? -	0x30, SIG_UINT16(0x0012),           // bnt [2nd code], in case global A6 <> 1 +	0x30, SIG_UINT16(0x0012),           // bnt [PChase set code], in case global A6 <> 1  	0x81, 0xb5,                         // lag global[b5] -	0x30, SIG_UINT16(0x000d),           // bnt [2nd code], in case global B5 == 0 +	0x30, SIG_UINT16(0x000d),           // bnt [PChase set code], in case global B5 == 0  	0x38, SIG_UINT16(0x008c),           // pushi 008c  	0x78,                               // push1  	0x72, SIG_UINT16(0x1cb6),           // lofsa 1CB6 (moveToPath)  	0x36,                               // push  	0x54, 0x06,                         // self 06  	0x32, SIG_UINT16(0x0038),           // jmp [super-call] +	// PChase set call  	0x81, 0xb5,                         // lag global[B5]  	0x18,                               // not  	0x30, SIG_UINT16(0x0032),           // bnt [super-call], in case global B5 <> 0 +	// followed by: +	// is spider in current room +	// is global A6h == 2? -> set PChase  	SIG_END  }; // 58 bytes) +// Global A6h <> 1 (did NOT went up the skeleton) +//  Global B5h = 0 -> set PChase +//  Global B5h <> 0 -> do not do anything +// Global A6h = 1 (did went up the skeleton) +//  Global B5h = 0 -> set PChase +//  Global B5h <> 0 -> set moveToPath +  static const uint16 sq1vgaPatchSpiderDroidTiming[] = {  	0x63, 0x4e,                         // pToa script  	0x2f, 0x68,                         // bt [super-call] @@ -3556,8 +3747,8 @@ static const uint16 sq1vgaPatchSpiderDroidTiming[] = {  	0x65, 0x4c,                         // aTop cycleSpeed  	0x65, 0x5e,                         // aTop moveSpeed  	// new code end -	0x89, 0xb5,                         // lsg global[B5] -	0x31, 0x13,                         // bnt [2nd code chunk] +	0x81, 0xb5,                         // lag global[B5] +	0x31, 0x13,                         // bnt [PChase code chunk]  	0x89, 0xa6,                         // lsg global[A6]  	0x35, 0x01,                         // ldi 01  	0x1a,                               // eq? diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index 0cb8ff48d7..073bb93983 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -514,6 +514,14 @@ const SciWorkaroundEntry kDisposeScript_workarounds[] = {  };  //    gameID,           room,script,lvl,          object-name, method-name, local-call-signature, index,                workaround +const SciWorkaroundEntry kDoSoundPlay_workarounds[] = { +	{ GID_LSL6HIRES,    -1,  64989,   0,          NULL,          "play",                    NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument +	{ GID_QFG4,         -1,  64989,   0,          NULL,          "play",                    NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument +	{ GID_PQ4,          -1,  64989,   0,          NULL,          "play",                    NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument +	SCI_WORKAROUNDENTRY_TERMINATOR +}; + +//    gameID,           room,script,lvl,          object-name, method-name, local-call-signature, index,                workaround  const SciWorkaroundEntry kDoSoundFade_workarounds[] = {  	{ GID_KQ5,           213,   989,  0,       "globalSound3", "fade",                      NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // english floppy: when bandits leave the secret temple, parameter 4 is an object - bug #5078  	{ GID_KQ6,           105,   989,  0,        "globalSound", "fade",                      NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // floppy: during intro, parameter 4 is an object @@ -745,6 +753,12 @@ const SciWorkaroundEntry kUnLoad_workarounds[] = {  	SCI_WORKAROUNDENTRY_TERMINATOR  }; +//    gameID,           room,script,lvl,          object-name, method-name,  local-call-signature, index,                workaround +const SciWorkaroundEntry kScrollWindowAdd_workarounds[] = { +	{ GID_PHANTASMAGORIA, 45, 64907,  0,   "ScrollableWindow", "addString",                  NULL,     0, { WORKAROUND_STILLCALL, 0 } }, // ScrollWindow interface passes the last two parameters twice +}; + +  SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin) {  	// HACK for SCI3: Temporarily ignore this  	if (getSciVersion() == SCI_VERSION_3) { diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h index 8f519a8c9c..248d37fc6c 100644 --- a/engines/sci/engine/workarounds.h +++ b/engines/sci/engine/workarounds.h @@ -74,6 +74,7 @@ extern const SciWorkaroundEntry kDeviceInfo_workarounds[];  extern const SciWorkaroundEntry kDisplay_workarounds[];  extern const SciWorkaroundEntry kDirLoop_workarounds[];  extern const SciWorkaroundEntry kDisposeScript_workarounds[]; +extern const SciWorkaroundEntry kDoSoundPlay_workarounds[];  extern const SciWorkaroundEntry kDoSoundFade_workarounds[];  extern const SciWorkaroundEntry kFindKey_workarounds[];  extern const SciWorkaroundEntry kDeleteKey_workarounds[]; @@ -98,6 +99,7 @@ extern const SciWorkaroundEntry kStrAt_workarounds[];  extern const SciWorkaroundEntry kStrCpy_workarounds[];  extern const SciWorkaroundEntry kStrLen_workarounds[];  extern const SciWorkaroundEntry kUnLoad_workarounds[]; +extern const SciWorkaroundEntry kScrollWindowAdd_workarounds[];  extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin); diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp index 77d333a717..befa5cda18 100644 --- a/engines/sci/graphics/celobj32.cpp +++ b/engines/sci/graphics/celobj32.cpp @@ -26,13 +26,12 @@  #include "sci/graphics/celobj32.h"  #include "sci/graphics/frameout.h"  #include "sci/graphics/palette32.h" -#include "sci/graphics/picture.h" -#include "sci/graphics/remap.h" +#include "sci/graphics/remap32.h"  #include "sci/graphics/text32.h" -#include "sci/graphics/view.h"  namespace Sci {  #pragma mark CelScaler +  CelScaler *CelObj::_scaler = nullptr;  void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) { @@ -323,6 +322,10 @@ public:  #pragma mark -  #pragma mark CelObj - Remappers +/** + * Pixel mapper for a CelObj with transparent pixels and no + * remapping data. + */  struct MAPPER_NoMD {  	inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {  		if (pixel != skipColor) { @@ -330,25 +333,49 @@ struct MAPPER_NoMD {  		}  	}  }; + +/** + * Pixel mapper for a CelObj with no transparent pixels and + * no remapping data. + */  struct MAPPER_NoMDNoSkip {  	inline void draw(byte *target, const byte pixel, const uint8) const {  		*target = pixel;  	}  }; +/** + * Pixel mapper for a CelObj with transparent pixels, + * remapping data, and remapping enabled. + */  struct MAPPER_Map {  	inline void draw(byte *target, const byte pixel, const uint8 skipColor) const {  		if (pixel != skipColor) { +			// NOTE: For some reason, SSCI never checks if the source +			// pixel is *above* the range of remaps.  			if (pixel < g_sci->_gfxRemap32->getStartColor()) {  				*target = pixel; -			} else { -				if (g_sci->_gfxRemap32->remapEnabled(pixel)) -					*target = g_sci->_gfxRemap32->remapColor(pixel, *target); +			} else if (g_sci->_gfxRemap32->remapEnabled(pixel)) { +				*target = g_sci->_gfxRemap32->remapColor(pixel, *target);  			}  		}  	}  }; +/** + * Pixel mapper for a CelObj with transparent pixels, + * remapping data, and remapping disabled. + */ +struct MAPPER_NoMap { +	inline void draw(byte *target, const byte pixel, const uint8 skipColor) const { +		// NOTE: For some reason, SSCI never checks if the source +		// pixel is *above* the range of remaps. +		if (pixel != skipColor && pixel < g_sci->_gfxRemap32->getStartColor()) { +			*target = pixel; +		} +	} +}; +  void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const {  	const Common::Point &scaledPosition = screenItem._scaledPosition;  	const Ratio &scaleX = screenItem._ratioX; @@ -523,6 +550,7 @@ void CelObj::submitPalette() const {  #pragma mark -  #pragma mark CelObj - Caching +  int CelObj::_nextCacheId = 1;  CelCache *CelObj::_cache = nullptr; @@ -624,33 +652,35 @@ void dummyFill(Buffer &target, const Common::Rect &targetRect) {  }  void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { -	debug("drawHzFlip"); -	dummyFill(target, targetRect); +	render<MAPPER_NoMap, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition);  }  void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { -	debug("drawNoFlip"); -	dummyFill(target, targetRect); +	render<MAPPER_NoMap, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition);  }  void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { -	debug("drawUncompNoFlip"); -	dummyFill(target, targetRect); +	render<MAPPER_NoMap, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition);  }  void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { -	debug("drawUncompHzFlip"); -	dummyFill(target, targetRect); +	render<MAPPER_NoMap, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition);  }  void CelObj::scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { -	debug("scaleDraw"); -	dummyFill(target, targetRect); +	if (_drawMirrored) { +		render<MAPPER_NoMap, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); +	} else { +		render<MAPPER_NoMap, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); +	}  }  void CelObj::scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { -	debug("scaleDrawUncomp"); -	dummyFill(target, targetRect); +	if (_drawMirrored) { +		render<MAPPER_NoMap, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); +	} else { +		render<MAPPER_NoMap, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); +	}  }  void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { @@ -670,17 +700,19 @@ void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect,  }  void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { -	if (_drawMirrored) +	if (_drawMirrored) {  		render<MAPPER_Map, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); -	else +	} else {  		render<MAPPER_Map, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); +	}  }  void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { -	if (_drawMirrored) +	if (_drawMirrored) {  		render<MAPPER_Map, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); -	else +	} else {  		render<MAPPER_Map, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); +	}  }  void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { @@ -715,14 +747,16 @@ void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &sca  }  void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { -	if (_drawMirrored) +	if (_drawMirrored) {  		render<MAPPER_NoMD, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); -	else +	} else {  		render<MAPPER_NoMD, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); +	}  }  #pragma mark -  #pragma mark CelObjView +  CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) {  	_info.type = kCelTypeView;  	_info.resourceId = viewId; @@ -765,8 +799,8 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int  	if (_scaledWidth == 0 || _scaledHeight == 0) {  		byte sizeFlag = data[5];  		if (sizeFlag == 0) { -			_scaledWidth = 320; -			_scaledHeight = 200; +			_scaledWidth = kLowResX; +			_scaledHeight = kLowResY;  		} else if (sizeFlag == 1) {  			_scaledWidth = 640;  			_scaledHeight = 480; @@ -840,8 +874,12 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int  bool CelObjView::analyzeUncompressedForRemap() const {  	byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);  	for (int i = 0; i < _width * _height; ++i) { -		byte pixel = pixels[i]; -		if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) { +		const byte pixel = pixels[i]; +		if ( +			pixel >= g_sci->_gfxRemap32->getStartColor() && +			pixel <= g_sci->_gfxRemap32->getEndColor() && +			pixel != _transparentColor +		) {  			return true;  		}  	} @@ -853,8 +891,12 @@ bool CelObjView::analyzeForRemap() const {  	for (int y = 0; y < _height; y++) {  		const byte *curRow = reader.getRow(y);  		for (int x = 0; x < _width; x++) { -			byte pixel = curRow[x]; -			if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) { +			const byte pixel = curRow[x]; +			if ( +				pixel >= g_sci->_gfxRemap32->getStartColor() && +				pixel <= g_sci->_gfxRemap32->getEndColor() && +				pixel != _transparentColor +			) {  				return true;  			}  		} @@ -881,6 +923,7 @@ byte *CelObjView::getResPointer() const {  #pragma mark -  #pragma mark CelObjPic +  CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {  	_info.type = kCelTypePic;  	_info.resourceId = picId; @@ -942,8 +985,8 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {  		_scaledWidth = sizeFlag1;  		_scaledHeight = sizeFlag2;  	} else if (sizeFlag1 == 0) { -		_scaledWidth = 320; -		_scaledHeight = 200; +		_scaledWidth = kLowResX; +		_scaledHeight = kLowResY;  	} else if (sizeFlag1 == 1) {  		_scaledWidth = 640;  		_scaledHeight = 480; @@ -1002,6 +1045,7 @@ byte *CelObjPic::getResPointer() const {  #pragma mark -  #pragma mark CelObjMem +  CelObjMem::CelObjMem(const reg_t bitmapObject) {  	_info.type = kCelTypeMem;  	_info.bitmap = bitmapObject; @@ -1031,6 +1075,7 @@ byte *CelObjMem::getResPointer() const {  #pragma mark -  #pragma mark CelObjColor +  CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) {  	_info.type = kCelTypeColor;  	_info.color = color; diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h index 6e401b3df4..e405592b5f 100644 --- a/engines/sci/graphics/celobj32.h +++ b/engines/sci/graphics/celobj32.h @@ -31,6 +31,20 @@  namespace Sci {  typedef Common::Rational Ratio; +// SCI32 has four different coordinate systems: +// 1. low resolution, 2. game/script resolution, +// 3. text/bitmap resolution, 4. screen resolution +// +// In CelObj, these values are used when there is +// no baked in resolution of cels. +// +// In ScreenItem, it is used when deciding which +// path to take to calculate dimensions. +enum { +	kLowResX = 320, +	kLowResY = 200 +}; +  enum CelType {  	kCelTypeView  = 0,  	kCelTypePic   = 1, diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp index faf1d7d1a2..61dfbedfc5 100644 --- a/engines/sci/graphics/controls32.cpp +++ b/engines/sci/graphics/controls32.cpp @@ -21,7 +21,8 @@   */  #include "common/system.h" - +#include "common/translation.h" +#include "gui/message.h"  #include "sci/sci.h"  #include "sci/console.h"  #include "sci/event.h" @@ -41,7 +42,31 @@ GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *tex  	_gfxCache(cache),  	_gfxText32(text),  	_overwriteMode(false), -	_nextCursorFlashTick(0) {} +	_nextCursorFlashTick(0), +	// SSCI used a memory handle for a ScrollWindow object +	// as ID. We use a simple numeric handle instead. +	_nextScrollWindowId(10000) {} + +GfxControls32::~GfxControls32() { +	ScrollWindowMap::iterator it; +	for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it) +		delete it->_value; +} + +#pragma mark - +#pragma mark Garbage collection + +Common::Array<reg_t> GfxControls32::listObjectReferences() { +	Common::Array<reg_t> ret; +	ScrollWindowMap::const_iterator it; +	for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it) +		ret.push_back(it->_value->getBitmap()); + +	return ret; +} + +#pragma mark - +#pragma mark Text input control  reg_t GfxControls32::kernelEditText(const reg_t controlObject) {  	SegManager *segMan = _segMan; @@ -350,4 +375,470 @@ void GfxControls32::flashCursor(TextEditor &editor) {  		_nextCursorFlashTick = g_sci->getTickCount() + 30;  	}  } + +#pragma mark - +#pragma mark Scrollable window control + +ScrollWindow::ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) : +	_gfxText32(segMan, g_sci->_gfxCache), +	_maxNumEntries(maxNumEntries), +	_firstVisibleChar(0), +	_topVisibleLine(0), +	_lastVisibleChar(0), +	_bottomVisibleLine(0), +	_numLines(0), +	_numVisibleLines(0), +	_plane(plane), +	_foreColor(defaultForeColor), +	_backColor(defaultBackColor), +	_borderColor(defaultBorderColor), +	_fontId(defaultFontId), +	_alignment(defaultAlignment), +	_visible(false), +	_position(position), +	_screenItem(nullptr), +	_nextEntryId(1) { + +	_entries.reserve(maxNumEntries); + +	_gfxText32.setFont(_fontId); +	_pointSize = _gfxText32._font->getHeight(); + +	const uint16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; +	const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + +	Common::Rect bitmapRect(gameRect); +	mulinc(bitmapRect, Ratio(_gfxText32._scaledWidth, scriptWidth), Ratio(_gfxText32._scaledHeight, scriptHeight)); + +	_textRect.left = 2; +	_textRect.top = 2; +	_textRect.right = bitmapRect.width() - 2; +	_textRect.bottom = bitmapRect.height() - 2; + +	uint8 skipColor = 0; +	while (skipColor == _foreColor || skipColor == _backColor) { +		skipColor++; +	} + +	assert(bitmapRect.width() > 0 && bitmapRect.height() > 0); +	_bitmap = _gfxText32.createFontBitmap(bitmapRect.width(), bitmapRect.height(), _textRect, "", _foreColor, _backColor, skipColor, _fontId, _alignment, _borderColor, false, false); + +	debugC(1, kDebugLevelGraphics, "New ScrollWindow: textRect size: %d x %d, bitmap: %04x:%04x", _textRect.width(), _textRect.height(), PRINT_REG(_bitmap)); +} + +ScrollWindow::~ScrollWindow() { +	// _gfxText32._bitmap will get GCed once ScrollWindow is gone. +	// _screenItem will be deleted by GfxFrameout +} + +Ratio ScrollWindow::where() const { +	return Ratio(_topVisibleLine, MAX(_numLines, 1)); +} + +void ScrollWindow::show() { +	if (_visible) { +		return; +	} + +	if (_screenItem == nullptr) { +		CelInfo32 celInfo; +		celInfo.type = kCelTypeMem; +		celInfo.bitmap = _bitmap; + +		_screenItem = new ScreenItem(_plane, celInfo, _position, ScaleInfo()); +	} + +	Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane); +	plane->_screenItemList.add(_screenItem); + +	_visible = true; +} + +void ScrollWindow::hide() { +	if (!_visible) { +		return; +	} + +	g_sci->_gfxFrameout->deleteScreenItem(_screenItem, _plane); +	_screenItem = nullptr; +	g_sci->_gfxFrameout->frameOut(true); + +	_visible = false; +} + +reg_t ScrollWindow::add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) { +	if (_entries.size() == _maxNumEntries) { +		ScrollWindowEntry removedEntry = _entries.remove_at(0); +		_text.erase(0, removedEntry.text.size()); +		// `_firstVisibleChar` will be reset shortly if +		// `scrollTo` is true, so there is no reason to +		// update it +		if (!scrollTo) { +			_firstVisibleChar -= removedEntry.text.size(); +		} +	} + +	_entries.push_back(ScrollWindowEntry()); +	ScrollWindowEntry &entry = _entries.back(); + +	// NOTE: In SSCI the line ID was a memory handle for the +	// string of this line. We use a numeric ID instead. +	entry.id = make_reg(0, _nextEntryId++); + +	if (_nextEntryId > _maxNumEntries) { +		_nextEntryId = 1; +	} + +	// NOTE: In SSCI this was updated after _text was +	// updated, which meant there was an extra unnecessary +	// subtraction operation (subtracting `entry.text` size) +	if (scrollTo) { +		_firstVisibleChar = _text.size(); +	} + +	fillEntry(entry, text, fontId, foreColor, alignment); +	_text += entry.text; + +	computeLineIndices(); +	update(true); + +	return entry.id; +} + +void ScrollWindow::fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment) { +	entry.alignment = alignment; +	entry.foreColor = foreColor; +	entry.fontId = fontId; + +	Common::String formattedText; + +	// NB: There are inconsistencies here. +	// If there is a multi-line entry with non-default properties, and it +	// is only partially displayed, it may not be displayed right, since the +	// property directives are only added to the first line. +	// (Verified by trying this in SSCI SQ6 with a custom ScrollWindowAdd call.) +	// +	// The converse is also a potential issue (but unverified), where lines +	// with properties -1 can inherit properties from the previously rendered +	// line instead of the defaults. + +	// NOTE: SSCI added "|s<lineIndex>|" here, but |s| is +	// not a valid control code, so it just always ended up +	// getting skipped +	if (entry.fontId != -1) { +		formattedText += Common::String::format("|f%d|", entry.fontId); +	} +	if (entry.foreColor != -1) { +		formattedText += Common::String::format("|c%d|", entry.foreColor); +	} +	if (entry.alignment != -1) { +		formattedText += Common::String::format("|a%d|", entry.alignment); +	} +	formattedText += text; +	entry.text = formattedText; +} + +reg_t ScrollWindow::modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) { + +	EntriesList::iterator it = _entries.begin(); +	uint firstCharLocation = 0; +	for ( ; it != _entries.end(); ++it) { +		if (it->id == id) { +			break; +		} +		firstCharLocation += it->text.size(); +	} + +	if (it == _entries.end()) { +		return make_reg(0, 0); +	} + +	ScrollWindowEntry &entry = *it; + +	uint oldTextLength = entry.text.size(); + +	fillEntry(entry, text, fontId, foreColor, alignment); +	_text.replace(firstCharLocation, oldTextLength, entry.text); + +	if (scrollTo) { +		_firstVisibleChar = firstCharLocation; +	} + +	computeLineIndices(); +	update(true); + +	return entry.id; +} + +void ScrollWindow::upArrow() { +	if (_topVisibleLine == 0) { +		return; +	} + +	_topVisibleLine--; +	_bottomVisibleLine--; + +	if (_bottomVisibleLine - _topVisibleLine + 1 < _numVisibleLines) { +		_bottomVisibleLine = _numLines - 1; +	} + +	_firstVisibleChar = _startsOfLines[_topVisibleLine]; +	_lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1; + +	_visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1); + +	Common::String lineText(_text.c_str() + _startsOfLines[_topVisibleLine], _text.c_str() + _startsOfLines[_topVisibleLine + 1] - 1); + +	debugC(3, kDebugLevelGraphics, "ScrollWindow::upArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str()); + +	_gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollUp); + +	if (_visible) { +		assert(_screenItem); + +		_screenItem->update(); +		g_sci->_gfxFrameout->frameOut(true); +	} +} + +void ScrollWindow::downArrow() { +	if (_topVisibleLine + 1 >= _numLines) { +		return; +	} + +	_topVisibleLine++; +	_bottomVisibleLine++; + +	if (_bottomVisibleLine + 1 >= _numLines) { +		_bottomVisibleLine = _numLines - 1; +	} + +	_firstVisibleChar = _startsOfLines[_topVisibleLine]; +	_lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1; + +	_visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1); + +	Common::String lineText; +	if (_bottomVisibleLine - _topVisibleLine + 1 == _numVisibleLines) { +		lineText = Common::String(_text.c_str() + _startsOfLines[_bottomVisibleLine], _text.c_str() + _startsOfLines[_bottomVisibleLine + 1] - 1); +	} else { +		// scroll in empty string +	} + +	debugC(3, kDebugLevelGraphics, "ScrollWindow::downArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str()); + + +	_gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollDown); + +	if (_visible) { +		assert(_screenItem); + +		_screenItem->update(); +		g_sci->_gfxFrameout->frameOut(true); +	} +} + +void ScrollWindow::go(const Ratio location) { +	const int line = (location * _numLines).toInt(); +	if (line < 0 || line > _numLines) { +		error("Index is Out of Range in ScrollWindow"); +	} + +	_firstVisibleChar = _startsOfLines[line]; +	update(true); + +	// HACK: +	// It usually isn't possible to set _topVisibleLine >= _numLines, and so +	// update() doesn't. However, in this case we should set _topVisibleLine +	// past the end. This is clearly visible in Phantasmagoria when dragging +	// the slider in the About dialog to the very end. The slider ends up lower +	// than where it can be moved by scrolling down with the arrows. +	if (location.isOne()) { +		_topVisibleLine = _numLines; +	} +} + +void ScrollWindow::home() { +	if (_firstVisibleChar == 0) { +		return; +	} + +	_firstVisibleChar = 0; +	update(true); +} + +void ScrollWindow::end() { +	if (_bottomVisibleLine + 1 >= _numLines) { +		return; +	} + +	int line = _numLines - _numVisibleLines; +	if (line < 0) { +		line = 0; +	} +	_firstVisibleChar = _startsOfLines[line]; +	update(true); +} + +void ScrollWindow::pageUp() { +	if (_topVisibleLine == 0) { +		return; +	} + +	_topVisibleLine -= _numVisibleLines; +	if (_topVisibleLine < 0) { +		_topVisibleLine = 0; +	} + +	_firstVisibleChar = _startsOfLines[_topVisibleLine]; +	update(true); +} + +void ScrollWindow::pageDown() { +	if (_topVisibleLine + 1 >= _numLines) { +		return; +	} + +	_topVisibleLine += _numVisibleLines; +	if (_topVisibleLine + 1 >= _numLines) { +		_topVisibleLine = _numLines - 1; +	} + +	_firstVisibleChar = _startsOfLines[_topVisibleLine]; +	update(true); +} + +void ScrollWindow::computeLineIndices() { +	_gfxText32.setFont(_fontId); +	// NOTE: Unlike SSCI, foreColor and alignment are not +	// set since these properties do not affect the width of +	// lines + +	if (_gfxText32._font->getHeight() != _pointSize) { +		error("Illegal font size font = %d pointSize = %d, should be %d.", _fontId, _gfxText32._font->getHeight(), _pointSize); +	} + +	Common::Rect lineRect(0, 0, _textRect.width(), _pointSize + 3); + +	_startsOfLines.clear(); + +	// NOTE: The original engine had a 1000-line limit; we +	// do not enforce any limit +	for (uint charIndex = 0; charIndex < _text.size(); ) { +		_startsOfLines.push_back(charIndex); +		charIndex += _gfxText32.getTextCount(_text, charIndex, lineRect, false); +	} + +	_numLines = _startsOfLines.size(); + +	_startsOfLines.push_back(_text.size()); + +	_lastVisibleChar = _gfxText32.getTextCount(_text, 0, _fontId, _textRect, false) - 1; + +	_bottomVisibleLine = 0; +	while ( +		_bottomVisibleLine < _numLines - 1 && +		_startsOfLines[_bottomVisibleLine + 1] < _lastVisibleChar +	) { +		++_bottomVisibleLine; +	} + +	_numVisibleLines = _bottomVisibleLine + 1; +} + +void ScrollWindow::update(const bool doFrameOut) { +	_topVisibleLine = 0; +	while ( +		_topVisibleLine < _numLines - 1 && +		_firstVisibleChar >= _startsOfLines[_topVisibleLine + 1] +	) { +		++_topVisibleLine; +	} + +	_bottomVisibleLine = _topVisibleLine + _numVisibleLines - 1; +	if (_bottomVisibleLine >= _numLines) { +		_bottomVisibleLine = _numLines - 1; +	} + +	_firstVisibleChar = _startsOfLines[_topVisibleLine]; + +	if (_bottomVisibleLine >= 0) { +		_lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1; +	} else { +		_lastVisibleChar = -1; +	} + +	_visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1); + +	_gfxText32.erase(_textRect, false); +	_gfxText32.drawTextBox(_visibleText); + +	if (_visible) { +		assert(_screenItem); + +		_screenItem->update(); +		if (doFrameOut) { +			g_sci->_gfxFrameout->frameOut(true); +		} +	} +} + +reg_t GfxControls32::makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) { + +	ScrollWindow *scrollWindow = new ScrollWindow(_segMan, gameRect, position, planeObj, defaultForeColor, defaultBackColor, defaultFontId, defaultAlignment, defaultBorderColor, maxNumEntries); + +	const uint16 id = _nextScrollWindowId++; +	_scrollWindows[id] = scrollWindow; +	return make_reg(0, id); +} + +ScrollWindow *GfxControls32::getScrollWindow(const reg_t id) { +	ScrollWindowMap::iterator it; +	it = _scrollWindows.find(id.toUint16()); +	if (it == _scrollWindows.end()) +		error("Invalid ScrollWindow ID"); + +	return it->_value; +} + +void GfxControls32::destroyScrollWindow(const reg_t id) { +	ScrollWindow *scrollWindow = getScrollWindow(id); +	scrollWindow->hide(); +	_scrollWindows.erase(id.getOffset()); +	delete scrollWindow; +} + +#pragma mark - +#pragma mark Message box + +int16 GfxControls32::showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue) { +	GUI::MessageDialog dialog(message, okLabel, altLabel); +	return (dialog.runModal() == GUI::kMessageOK) ? okValue : altValue; +} + +reg_t GfxControls32::kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style) { +	if (g_engine) { +		g_engine->pauseEngine(true); +	} + +	int16 result; + +	switch (style & 0xF) { +	case kMessageBoxOK: +		result = showMessageBox(message, _("OK"), NULL, 1, 1); +	break; +	case kMessageBoxYesNo: +		result = showMessageBox(message, _("Yes"), _("No"), 6, 7); +	break; +	default: +		error("Unsupported MessageBox style 0x%x", style & 0xF); +	} + +	if (g_engine) { +		g_engine->pauseEngine(false); +	} + +	return make_reg(0, result); +} +  } // End of namespace Sci diff --git a/engines/sci/graphics/controls32.h b/engines/sci/graphics/controls32.h index 1bb7679ddd..460b0b5625 100644 --- a/engines/sci/graphics/controls32.h +++ b/engines/sci/graphics/controls32.h @@ -23,12 +23,19 @@  #ifndef SCI_GRAPHICS_CONTROLS32_H  #define SCI_GRAPHICS_CONTROLS32_H +#include "sci/graphics/text32.h" +  namespace Sci {  class GfxCache;  class GfxScreen;  class GfxText32; +enum MessageBoxStyle { +	kMessageBoxOK               = 0x0, +	kMessageBoxYesNo            = 0x4 +}; +  struct TextEditor {  	/**  	 * The bitmap where the editor is rendered. @@ -100,24 +107,403 @@ struct TextEditor {  };  /** + * A single block of text written to a ScrollWindow. + */ +struct ScrollWindowEntry { +	/** +	 * ID of the line. In SSCI this was actually a memory +	 * handle for the string of this line. We use a simple +	 * numeric ID instead. +	 */ +	reg_t id; + +	/** +	 * The alignment to use when rendering this line of +	 * text. If -1, the default alignment from the +	 * corresponding ScrollWindow will be used. +	 */ +	TextAlign alignment; + +	/** +	 * The color to use to render this line of text. If -1, +	 * the default foreground color from the corresponding +	 * ScrollWindow will be used. +	 */ +	int16 foreColor; + +	/** +	 * The font to use to render this line of text. If -1, +	 * the default font from the corresponding ScrollWindow +	 * will be used. +	 */ +	GuiResourceId fontId; + +	/** +	 * The text. +	 */ +	Common::String text; +}; + +class ScreenItem; + +/** + * A scrollable text window. + */ +class ScrollWindow { +public: +	ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries); +	~ScrollWindow(); + +	/** +	 * Adds a new text entry to the window. If `fontId`, +	 * `foreColor`, or `alignment` are `-1`, the +	 * ScrollWindow's default values will be used. +	 */ +	reg_t add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo); + +	/** +	 * Modifies an existing text entry with the given ID. If +	 * `fontId`, `foreColor`, or `alignment` are `-1`, the +	 * ScrollWindow's default values will be used. +	 */ +	reg_t modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo); + +	/** +	 * Shows the ScrollWindow if it is not already visible. +	 */ +	void show(); + +	/** +	 * Hides the ScrollWindow if it is currently visible. +	 */ +	void hide(); + +	/** +	 * Gets the number of lines that the content of a +	 * ScrollWindow is scrolled upward, as a ratio of the +	 * total number of lines of content. +	 */ +	Ratio where() const; + +	/** +	 * Scrolls the window to a specific location. +	 */ +	void go(const Ratio location); + +	/** +	 * Scrolls the window to the top. +	 */ +	void home(); + +	/** +	 * Scrolls the window to the bottom. +	 */ +	void end(); + +	/** +	 * Scrolls the window up one line. +	 */ +	void upArrow(); + +	/** +	 * Scrolls the window down one line. +	 */ +	void downArrow(); + +	/** +	 * Scrolls the window up by one page. +	 */ +	void pageUp(); + +	/** +	 * Scrolls the window down by one page. +	 */ +	void pageDown(); + +	/** +	 * Gets a reference to the in-memory bitmap that +	 * is used to render the text in the ScrollWindow. +	 */ +	const reg_t getBitmap() const { return _bitmap; } + +private: +	typedef Common::Array<ScrollWindowEntry> EntriesList; + +	/** +	 * A convenience function that fills a +	 * ScrollWindowEntry's properties. +	 */ +	void fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment); + +	/** +	 * Rescans the entire text of the ScrollWindow when an +	 * entry is added or modified, calculating the character +	 * offsets of all line endings, the total number of +	 * lines of text, the height of the viewport (in lines +	 * of text), the last character visible in the viewport +	 * (assuming the viewport is scrolled to the top), and +	 * the line index of the bottommost visible line +	 * (assuming the viewport is scrolled to the top). +	 */ +	void computeLineIndices(); + +	/** +	 * Calculates which text is visible within the +	 * ScrollWindow's viewport and renders the text to the +	 * internal bitmap. +	 * +	 * If `doFrameOut` is true, the screen will be refreshed +	 * immediately instead of waiting for the next call to +	 * `kFrameOut`. +	 */ +	void update(const bool doFrameOut); + +	/** +	 * The text renderer. +	 */ +	GfxText32 _gfxText32; + +	/** +	 * The individual text entries added to the +	 * ScrollWindow. +	 */ +	EntriesList _entries; + +	/** +	 * The maximum number of entries allowed. Once this +	 * limit is reached, the oldest entry will be removed +	 * when a new entry is added. +	 */ +	uint _maxNumEntries; + +	/** +	 * A mapping from a line index to the line's character +	 * offset in `_text`. +	 */ +	Common::Array<int> _startsOfLines; + +	/** +	 * All text added to the window. +	 */ +	Common::String _text; + +	/** +	 * Text that is within the viewport of the ScrollWindow. +	 */ +	Common::String _visibleText; + +	/** +	 * The offset of the first visible character in `_text`. +	 */ +	int _firstVisibleChar; + +	/** +	 * The index of the line that is at the top of the +	 * viewport. +	 */ +	int _topVisibleLine; + +	/** +	 * The index of the last visible character in `_text`, +	 * or -1 if there is no text. +	 */ +	int _lastVisibleChar; + +	/** +	 * The index of the line that is at the bottom of the +	 * viewport, or -1 if there is no text. +	 */ +	int _bottomVisibleLine; + +	/** +	 * The total number of lines in the backbuffer. This +	 * number may be higher than the total number of entries +	 * if an entry contains newlines. +	 */ +	int _numLines; + +	/** +	 * The number of lines that are currently visible in the +	 * text area of the window. +	 */ +	int _numVisibleLines; + +	/** +	 * The plane in which the ScrollWindow should be +	 * rendered. +	 */ +	reg_t _plane; + +	/** +	 * The default text color. +	 */ +	uint8 _foreColor; + +	/** +	 * The default background color of the text bitmap. +	 */ +	uint8 _backColor; + +	/** +	 * The default border color of the text bitmap. If -1, +	 * the viewport will have no border. +	 */ +	int16 _borderColor; + +	/** +	 * The default font used for rendering text into the +	 * ScrollWindow. +	 */ +	GuiResourceId _fontId; + +	/** +	 * The default text alignment used for rendering text +	 * into the ScrollWindow. +	 */ +	TextAlign _alignment; + +	/** +	 * The visibility of the ScrollWindow. +	 */ +	bool _visible; + +	/** +	 * The dimensions of the text box inside the font +	 * bitmap, in text-system coordinates. +	 */ +	Common::Rect _textRect; + +	/** +	 * The top-left corner of the ScrollWindow's screen +	 * item, in game script coordinates, relative to the +	 * parent plane. +	 */ +	Common::Point _position; + +	/** +	 * The height of the default font in screen pixels. All +	 * fonts rendered into the ScrollWindow must have this +	 * same height. +	 */ +	uint8 _pointSize; + +	/** +	 * The bitmap used to render text. +	 */ +	reg_t _bitmap; + +	/** +	 * A monotonically increasing ID used to identify +	 * text entries added to the ScrollWindow. +	 */ +	uint16 _nextEntryId; + +	/** +	 * The ScrollWindow's screen item. +	 */ +	ScreenItem *_screenItem; +}; + +/**   * Controls class, handles drawing of controls in SCI32 (SCI2, SCI2.1, SCI3) games   */  class GfxControls32 {  public:  	GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text); - -	reg_t kernelEditText(const reg_t controlObject); +	~GfxControls32();  private:  	SegManager *_segMan;  	GfxCache *_gfxCache;  	GfxText32 *_gfxText32; +#pragma mark - +#pragma mark Garbage collection +public: +	Common::Array<reg_t> listObjectReferences(); + +#pragma mark - +#pragma mark Text input control +public: +	reg_t kernelEditText(const reg_t controlObject); + +private: +	/** +	 * If true, typing will overwrite text that already +	 * exists at the text cursor's current position. +	 */  	bool _overwriteMode; + +	/** +	 * The tick at which the text cursor should be toggled +	 * by `flashCursor`. +	 */  	uint32 _nextCursorFlashTick; + +	/** +	 * Draws the text cursor for the given editor. +	 */  	void drawCursor(TextEditor &editor); + +	/** +	 * Erases the text cursor for the given editor. +	 */  	void eraseCursor(TextEditor &editor); + +	/** +	 * Toggles the text cursor for the given editor to be +	 * either drawn or erased. +	 */  	void flashCursor(TextEditor &editor); + +#pragma mark - +#pragma mark Scrollable window control +public: +	/** +	 * Creates a new scrollable window and returns the ID +	 * for the new window, which is used by game scripts to +	 * interact with scrollable windows. +	 */ +	reg_t makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries); + +	/** +	 * Gets a registered ScrollWindow instance by ID. +	 */ +	ScrollWindow *getScrollWindow(const reg_t id); + +	/** +	 * Destroys the scroll window with the given ID. +	 */ +	void destroyScrollWindow(const reg_t id); + +private: +	typedef Common::HashMap<uint16, ScrollWindow *> ScrollWindowMap; + +	/** +	 * Monotonically increasing ID used to identify +	 * ScrollWindow instances. +	 */ +	uint16 _nextScrollWindowId; + +	/** +	 * A lookup table for registered ScrollWindow instances. +	 */ +	ScrollWindowMap _scrollWindows; + +#pragma mark - +#pragma mark Message box +public: +	/** +	 * Displays an OS-level message dialog. +	 */ +	reg_t kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style); + +private: +	/** +	 * Convenience function for creating and showing a +	 * message box. +	 */ +	int16 showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue);  };  } // End of namespace Sci diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index 2d44e38390..fd37020896 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -42,15 +42,13 @@  #include "sci/graphics/coordadjuster.h"  #include "sci/graphics/compare.h"  #include "sci/graphics/font.h" -#include "sci/graphics/view.h"  #include "sci/graphics/screen.h"  #include "sci/graphics/paint32.h"  #include "sci/graphics/palette32.h" -#include "sci/graphics/picture.h" -#include "sci/graphics/remap.h" -#include "sci/graphics/text32.h"  #include "sci/graphics/plane32.h" +#include "sci/graphics/remap32.h"  #include "sci/graphics/screen_item32.h" +#include "sci/graphics/text32.h"  #include "sci/graphics/frameout.h"  #include "sci/video/robot_decoder.h" @@ -69,14 +67,12 @@ static int16 unknownCDefaults[2][16] = {  	/* SCI2.1mid+ */   { 0,  2,  2,  3,  3,  4,  4,  5,  5,  6,  6,  0,  0,   7,   7, 0 }  }; -GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32) : +GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette) :  	_isHiRes(false), -	_cache(cache),  	_palette(palette),  	_resMan(resMan),  	_screen(screen),  	_segMan(segMan), -	_paint32(paint32),  	_benchmarkingFinished(false),  	_throttleFrameOut(true),  	_showStyles(nullptr), @@ -257,17 +253,52 @@ void GfxFrameout::syncWithScripts(bool addElements) {  }  #pragma mark - +#pragma mark Benchmarking + +bool GfxFrameout::checkForFred(const reg_t object) { +	const int16 viewId = readSelectorValue(_segMan, object, SELECTOR(view)); +	const SciGameId gameId = g_sci->getGameId(); + +	if (gameId == GID_QFG4 && viewId == 9999) { +		return true; +	} + +	if (gameId != GID_QFG4 && viewId == -556) { +		return true; +	} + +	if (Common::String(_segMan->getObjectName(object)) == "fred") { +		return true; +	} + +	return false; +} + +#pragma mark -  #pragma mark Screen items +void GfxFrameout::deleteScreenItem(ScreenItem *screenItem, Plane *plane) { +	if (screenItem->_created == 0) { +		screenItem->_created = 0; +		screenItem->_updated = 0; +		screenItem->_deleted = getScreenCount(); +	} else { +		plane->_screenItemList.erase(screenItem); +		plane->_screenItemList.pack(); +	} +} + +void GfxFrameout::deleteScreenItem(ScreenItem *screenItem, const reg_t planeObject) { +	Plane *plane = _planes.findByObject(planeObject); +	deleteScreenItem(screenItem, plane); +} +  void GfxFrameout::kernelAddScreenItem(const reg_t object) {  	// The "fred" object is used to test graphics performance;  	// it is impacted by framerate throttling, so disable the  	// throttling when this item is on the screen for the  	// performance check to pass. -	if (!_benchmarkingFinished && ( -		(int16)readSelectorValue(_segMan, object, SELECTOR(view)) == -556 || -		Common::String(_segMan->getObjectName(object)) == "fred" -	)) { +	if (!_benchmarkingFinished && _throttleFrameOut && checkForFred(object)) {  		_throttleFrameOut = false;  	} @@ -314,10 +345,7 @@ void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {  	// it is impacted by framerate throttling, so disable the  	// throttling when this item is on the screen for the  	// performance check to pass. -	if (!_benchmarkingFinished && ( -		(int16)readSelectorValue(_segMan, object, SELECTOR(view)) == -556 || -		Common::String(_segMan->getObjectName(object)) == "fred" -	)) { +	if (!_benchmarkingFinished && checkForFred(object)) {  		_benchmarkingFinished = true;  		_throttleFrameOut = true;  	} @@ -335,14 +363,7 @@ void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {  		return;  	} -	if (screenItem->_created == 0) { -		screenItem->_created = 0; -		screenItem->_updated = 0; -		screenItem->_deleted = getScreenCount(); -	} else { -		plane->_screenItemList.erase(screenItem); -		plane->_screenItemList.pack(); -	} +	deleteScreenItem(screenItem, plane);  }  #pragma mark - @@ -471,7 +492,7 @@ void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pi  #pragma mark -  #pragma mark Rendering -void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) { +void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseRect) {  // TODO: Robot  //	if (_robot != nullptr) {  //		_robot.doRobot(); @@ -489,7 +510,7 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect)  		remapMarkRedraw();  	} -	calcLists(screenItemLists, eraseLists, rect); +	calcLists(screenItemLists, eraseLists, eraseRect);  	for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {  		list->sort(); @@ -532,130 +553,221 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect)  //	}  } -// Determine the parts of 'r' that aren't overlapped by 'other'. -// Returns -1 if r and other have no intersection. -// Returns number of returned parts (in outRects) otherwise. -// (In particular, this returns 0 if r is contained in other.) +/** + * Determines the parts of `r` that aren't overlapped by `other`. + * Returns -1 if `r` and `other` have no intersection. + * Returns number of returned parts (in `outRects`) otherwise. + * (In particular, this returns 0 if `r` is contained in `other`.) + */  int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]) {  	if (!r.intersects(other)) {  		return -1;  	} -	int count = 0; +	int splitCount = 0;  	if (r.top < other.top) { -		Common::Rect &t = outRects[count++]; +		Common::Rect &t = outRects[splitCount++];  		t = r;  		t.bottom = other.top;  		r.top = other.top;  	}  	if (r.bottom > other.bottom) { -		Common::Rect &t = outRects[count++]; +		Common::Rect &t = outRects[splitCount++];  		t = r;  		t.top = other.bottom;  		r.bottom = other.bottom;  	}  	if (r.left < other.left) { -		Common::Rect &t = outRects[count++]; +		Common::Rect &t = outRects[splitCount++];  		t = r;  		t.right = other.left;  		r.left = other.left;  	}  	if (r.right > other.right) { -		Common::Rect &t = outRects[count++]; +		Common::Rect &t = outRects[splitCount++];  		t = r;  		t.left = other.right;  	} -	return count; +	return splitCount;  } -void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect) { -	RectList rectlist; -	Common::Rect outRects[4]; +/** + * Determines the parts of `middleRect` that aren't overlapped + * by `showRect`, optimised for contiguous memory writes. + * Returns -1 if `middleRect` and `showRect` have no intersection. + * Returns number of returned parts (in `outRects`) otherwise. + * (In particular, this returns 0 if `middleRect` is contained + * in `other`.) + * + * `middleRect` is modified directly to extend into the upper + * and lower rects. + */ +int splitRectsForRender(Common::Rect &middleRect, const Common::Rect &showRect, Common::Rect(&outRects)[2]) { +	if (!middleRect.intersects(showRect)) { +		return -1; +	} + +	const int16 minLeft = MIN(middleRect.left, showRect.left); +	const int16 maxRight = MAX(middleRect.right, showRect.right); + +	int16 upperLeft, upperTop, upperRight, upperMaxTop; +	if (middleRect.top < showRect.top) { +		upperLeft = middleRect.left; +		upperTop = middleRect.top; +		upperRight = middleRect.right; +		upperMaxTop = showRect.top; +	} +	else { +		upperLeft = showRect.left; +		upperTop = showRect.top; +		upperRight = showRect.right; +		upperMaxTop = middleRect.top; +	} + +	int16 lowerLeft, lowerRight, lowerBottom, lowerMinBottom; +	if (middleRect.bottom > showRect.bottom) { +		lowerLeft = middleRect.left; +		lowerRight = middleRect.right; +		lowerBottom = middleRect.bottom; +		lowerMinBottom = showRect.bottom; +	} else { +		lowerLeft = showRect.left; +		lowerRight = showRect.right; +		lowerBottom = showRect.bottom; +		lowerMinBottom = middleRect.bottom; +	} + +	int splitCount = 0; +	middleRect.left = minLeft; +	middleRect.top = upperMaxTop; +	middleRect.right = maxRight; +	middleRect.bottom = lowerMinBottom; + +	if (upperTop != upperMaxTop) { +		Common::Rect &upperRect = outRects[0]; +		upperRect.left = upperLeft; +		upperRect.top = upperTop; +		upperRect.right = upperRight; +		upperRect.bottom = upperMaxTop; + +		// Merge upper rect into middle rect if possible +		if (upperRect.left == middleRect.left && upperRect.right == middleRect.right) { +			middleRect.top = upperRect.top; +		} else { +			++splitCount; +		} +	} + +	if (lowerBottom != lowerMinBottom) { +		Common::Rect &lowerRect = outRects[splitCount]; +		lowerRect.left = lowerLeft; +		lowerRect.top = lowerMinBottom; +		lowerRect.right = lowerRight; +		lowerRect.bottom = lowerBottom; + +		// Merge lower rect into middle rect if possible +		if (lowerRect.left == middleRect.left && lowerRect.right == middleRect.right) { +			middleRect.bottom = lowerRect.bottom; +		} else { +			++splitCount; +		} +	} + +	assert(splitCount <= 2); +	return splitCount; +} +// NOTE: The third rectangle parameter is only ever given a non-empty rect +// by VMD code, via `frameOut` +void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect) { +	RectList eraseList; +	Common::Rect outRects[4];  	int deletedPlaneCount = 0; -	bool addedToRectList = false; -	int planeCount = _planes.size(); +	bool addedToEraseList = false;  	bool foundTransparentPlane = false; -	if (!calcRect.isEmpty()) { -		addedToRectList = true; -		rectlist.add(calcRect); +	if (!eraseRect.isEmpty()) { +		addedToEraseList = true; +		eraseList.add(eraseRect);  	} +	PlaneList::size_type planeCount = _planes.size();  	for (int outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) { -		Plane *outerPlane = _planes[outerPlaneIndex]; +		const Plane *outerPlane = _planes[outerPlaneIndex]; +		const Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object); +		// NOTE: SSCI only ever checks for kPlaneTypeTransparent here, even +		// though kPlaneTypeTransparentPicture is also a transparent plane  		if (outerPlane->_type == kPlaneTypeTransparent) {  			foundTransparentPlane = true;  		} -		Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object); -  		if (outerPlane->_deleted) { -			if (visiblePlane != nullptr) { -				if (!visiblePlane->_screenRect.isEmpty()) { -					addedToRectList = true; -					rectlist.add(visiblePlane->_screenRect); -				} +			if (visiblePlane != nullptr && !visiblePlane->_screenRect.isEmpty()) { +				eraseList.add(visiblePlane->_screenRect); +				addedToEraseList = true;  			}  			++deletedPlaneCount; -		} else if (visiblePlane != nullptr) { -			if (outerPlane->_updated) { -				--outerPlane->_updated; - -				int splitcount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects); -				if (splitcount) { -					if (splitcount == -1) { -						if (!visiblePlane->_screenRect.isEmpty()) { -							rectlist.add(visiblePlane->_screenRect); -						} +		} else if (visiblePlane != nullptr && outerPlane->_moved) { +			// _moved will be decremented in the final loop through the planes, +			// at the end of this function + +			{ +				const int splitCount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects); +				if (splitCount) { +					if (splitCount == -1 && !visiblePlane->_screenRect.isEmpty()) { +						eraseList.add(visiblePlane->_screenRect);  					} else { -						for (int i = 0; i < splitcount; ++i) { -							rectlist.add(outRects[i]); +						for (int i = 0; i < splitCount; ++i) { +							eraseList.add(outRects[i]);  						}  					} - -					addedToRectList = true; +					addedToEraseList = true;  				} +			} -				if (!outerPlane->_redrawAllCount) { -					int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects); -					if (splitCount) { -						for (int i = 0; i < splitCount; ++i) { -							rectlist.add(outRects[i]); -						} -						addedToRectList = true; +			if (!outerPlane->_redrawAllCount) { +				const int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects); +				if (splitCount) { +					for (int i = 0; i < splitCount; ++i) { +						eraseList.add(outRects[i]);  					} +					addedToEraseList = true;  				}  			}  		} -		if (addedToRectList) { -			for (RectList::iterator rect = rectlist.begin(); rect != rectlist.end(); ++rect) { -				for (int innerPlaneIndex = _planes.size() - 1; innerPlaneIndex >= 0; --innerPlaneIndex) { -					Plane *innerPlane = _planes[innerPlaneIndex]; - -					if (!innerPlane->_deleted && innerPlane->_type != kPlaneTypeTransparent && innerPlane->_screenRect.intersects(**rect)) { -						if (innerPlane->_redrawAllCount == 0) { -							eraseLists[innerPlaneIndex].add(innerPlane->_screenRect.findIntersectingRect(**rect)); +		if (addedToEraseList) { +			for (int rectIndex = 0; rectIndex < eraseList.size(); ++rectIndex) { +				const Common::Rect &rect = *eraseList[rectIndex]; +				for (int innerPlaneIndex = planeCount - 1; innerPlaneIndex >= 0; --innerPlaneIndex) { +					const Plane &innerPlane = *_planes[innerPlaneIndex]; + +					if ( +						!innerPlane._deleted && +						innerPlane._type != kPlaneTypeTransparent && +						innerPlane._screenRect.intersects(rect) +					) { +						if (!innerPlane._redrawAllCount) { +							eraseLists[innerPlaneIndex].add(innerPlane._screenRect.findIntersectingRect(rect));  						} -						int splitCount = splitRects(**rect, innerPlane->_screenRect, outRects); +						const int splitCount = splitRects(rect, innerPlane._screenRect, outRects);  						for (int i = 0; i < splitCount; ++i) { -							rectlist.add(outRects[i]); +							eraseList.add(outRects[i]);  						} -						rectlist.erase(rect); +						eraseList.erase_at(rectIndex);  						break;  					}  				}  			} -			rectlist.pack(); +			eraseList.pack();  		}  	} @@ -667,9 +779,9 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL  			if (plane->_deleted) {  				--plane->_deleted;  				if (plane->_deleted <= 0) { -					PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object)); -					if (visiblePlaneIt != _visiblePlanes.end()) { -						_visiblePlanes.erase(visiblePlaneIt); +					const int visiblePlaneIndex = _visiblePlanes.findIndexByObject(plane->_object); +					if (visiblePlaneIndex != -1) { +						_visiblePlanes.remove_at(visiblePlaneIndex);  					}  					_planes.remove_at(planeIndex); @@ -684,107 +796,114 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL  		}  	} +	// Some planes may have been deleted, so re-retrieve count  	planeCount = _planes.size(); -	for (int outerIndex = 0; outerIndex < planeCount; ++outerIndex) { + +	for (PlaneList::size_type outerIndex = 0; outerIndex < planeCount; ++outerIndex) {  		// "outer" just refers to the outer loop -		Plane *outerPlane = _planes[outerIndex]; -		if (outerPlane->_priorityChanged) { -			--outerPlane->_priorityChanged; +		Plane &outerPlane = *_planes[outerIndex]; +		if (outerPlane._priorityChanged) { +			--outerPlane._priorityChanged; -			Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane->_object); +			const Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane._object);  			if (visibleOuterPlane == nullptr) { -				warning("calcLists could not find visible plane for %04x:%04x", PRINT_REG(outerPlane->_object)); +				warning("calcLists could not find visible plane for %04x:%04x", PRINT_REG(outerPlane._object));  				continue;  			} -			rectlist.add(outerPlane->_screenRect.findIntersectingRect(visibleOuterPlane->_screenRect)); +			eraseList.add(outerPlane._screenRect.findIntersectingRect(visibleOuterPlane->_screenRect)); -			for (int innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) { +			for (PlaneList::size_type innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) {  				// "inner" just refers to the inner loop -				Plane *innerPlane = _planes[innerIndex]; -				Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane->_object); - -				int rectCount = rectlist.size(); -				for (int rectIndex = 0; rectIndex < rectCount; ++rectIndex) { -					int splitCount = splitRects(*rectlist[rectIndex], _planes[innerIndex]->_screenRect, outRects); +				const Plane &innerPlane = *_planes[innerIndex]; +				const Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane._object); +				const RectList::size_type rectCount = eraseList.size(); +				for (RectList::size_type rectIndex = 0; rectIndex < rectCount; ++rectIndex) { +					const int splitCount = splitRects(*eraseList[rectIndex], innerPlane._screenRect, outRects);  					if (splitCount == 0) {  						if (visibleInnerPlane != nullptr) {  							// same priority, or relative priority between inner/outer changed -							if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) { -								if (outerPlane->_priority <= innerPlane->_priority) { -									eraseLists[innerIndex].add(*rectlist[rectIndex]); +							if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) { +								if (outerPlane._priority <= innerPlane._priority) { +									eraseLists[innerIndex].add(*eraseList[rectIndex]);  								} else { -									eraseLists[outerIndex].add(*rectlist[rectIndex]); +									eraseLists[outerIndex].add(*eraseList[rectIndex]);  								}  							}  						} -						rectlist.erase_at(rectIndex); +						eraseList.erase_at(rectIndex);  					} else if (splitCount != -1) {  						for (int i = 0; i < splitCount; ++i) { -							rectlist.add(outRects[i]); +							eraseList.add(outRects[i]);  						}  						if (visibleInnerPlane != nullptr) {  							// same priority, or relative priority between inner/outer changed -							if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) { -								*rectlist[rectIndex] = outerPlane->_screenRect.findIntersectingRect(innerPlane->_screenRect); -								if (outerPlane->_priority <= innerPlane->_priority) { -									eraseLists[innerIndex].add(*rectlist[rectIndex]); -								} -								else { -									eraseLists[outerIndex].add(*rectlist[rectIndex]); +							if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) { +								*eraseList[rectIndex] = outerPlane._screenRect.findIntersectingRect(innerPlane._screenRect); + +								if (outerPlane._priority <= innerPlane._priority) { +									eraseLists[innerIndex].add(*eraseList[rectIndex]); +								} else { +									eraseLists[outerIndex].add(*eraseList[rectIndex]);  								}  							}  						} -						rectlist.erase_at(rectIndex); +						eraseList.erase_at(rectIndex);  					}  				} -				rectlist.pack(); +				eraseList.pack();  			}  		}  	} -	for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { -		Plane *plane = _planes[planeIndex]; -		Plane *visiblePlane = nullptr; +	for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) { +		Plane &plane = *_planes[planeIndex]; +		Plane *visiblePlane = _visiblePlanes.findByObject(plane._object); -		PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object)); -		if (visiblePlaneIt != _visiblePlanes.end()) { -			visiblePlane = *visiblePlaneIt; -		} +		if (!plane._screenRect.isEmpty()) { +			if (plane._redrawAllCount) { +				plane.redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); +			} else { +				if (visiblePlane == nullptr) { +					error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane._object)); +				} -		if (plane->_redrawAllCount) { -			plane->redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); -		} else { -			if (visiblePlane == nullptr) { -				error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane->_object)); +				plane.calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);  			} +		} else { +			plane.decrementScreenItemArrayCounts(visiblePlane, false); +		} -			plane->calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); +		if (plane._moved) { +			// the work for handling moved/resized planes was already done +			// earlier in the function, we are just cleaning up now +			--plane._moved;  		} -		if (plane->_created) { -			_visiblePlanes.add(new Plane(*plane)); -			--plane->_created; -		} else if (plane->_moved) { -			assert(visiblePlaneIt != _visiblePlanes.end()); -			**visiblePlaneIt = *plane; -			--plane->_moved; +		if (plane._created) { +			_visiblePlanes.add(new Plane(plane)); +			--plane._created; +		} else if (plane._updated) { +			*visiblePlane = plane; +			--plane._updated;  		}  	} +	// NOTE: SSCI only looks for kPlaneTypeTransparent, not +	// kPlaneTypeTransparentPicture  	if (foundTransparentPlane) { -		for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { -			for (int i = planeIndex + 1; i < planeCount; ++i) { +		for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) { +			for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) {  				if (_planes[i]->_type == kPlaneTypeTransparent) {  					_planes[i]->filterUpEraseRects(drawLists[i], eraseLists[planeIndex]);  				}  			}  			if (_planes[planeIndex]->_type == kPlaneTypeTransparent) { -				for (int i = planeIndex - 1; i >= 0; --i) { +				for (PlaneList::size_type i = planeIndex - 1; i >= 0; --i) {  					_planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]);  				} @@ -793,7 +912,7 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL  				}  			} -			for (int i = planeIndex + 1; i < planeCount; ++i) { +			for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) {  				if (_planes[i]->_type == kPlaneTypeTransparent) {  					_planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]);  				} @@ -807,17 +926,19 @@ void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) {  		return;  	} -	for (RectList::const_iterator it = eraseList.begin(); it != eraseList.end(); ++it) { -		mergeToShowList(**it, _showList, _overdrawThreshold); -		_currentBuffer.fillRect(**it, plane._back); +	const RectList::size_type eraseListSize = eraseList.size(); +	for (RectList::size_type i = 0; i < eraseListSize; ++i) { +		mergeToShowList(*eraseList[i], _showList, _overdrawThreshold); +		_currentBuffer.fillRect(*eraseList[i], plane._back);  	}  }  void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) { -	for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) { -		DrawItem &drawItem = **it; +	const DrawList::size_type drawListSize = screenItemList.size(); +	for (DrawList::size_type i = 0; i < drawListSize; ++i) { +		const DrawItem &drawItem = *screenItemList[i];  		mergeToShowList(drawItem.rect, _showList, _overdrawThreshold); -		ScreenItem &screenItem = *drawItem.screenItem; +		const ScreenItem &screenItem = *drawItem.screenItem;  		// TODO: Remove  //		debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), PRINT_RECT(drawItem.rect));  		CelObj &celObj = *screenItem._celObj; @@ -826,36 +947,65 @@ void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) {  }  void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold) { -	Common::Rect merged(drawRect); - -	bool didDelete = true; -	RectList::size_type count = showList.size(); -	while (didDelete && count) { -		didDelete = false; - -		for (RectList::size_type i = 0; i < count; ++i) { -			Common::Rect existing = *showList[i]; -			Common::Rect candidate; -			candidate.left = MIN(merged.left, existing.left); -			candidate.top = MIN(merged.top, existing.top); -			candidate.right = MAX(merged.right, existing.right); -			candidate.bottom = MAX(merged.bottom, existing.bottom); - -			if (candidate.height() * candidate.width() - merged.width() * merged.height() - existing.width() * existing.height() <= overdrawThreshold) { -				merged = candidate; -				showList.erase_at(i); -				didDelete = true; +	RectList mergeList; +	Common::Rect merged; +	mergeList.add(drawRect); + +	for (RectList::size_type i = 0; i < mergeList.size(); ++i) { +		bool didMerge = false; +		const Common::Rect &r1 = *mergeList[i]; +		if (!r1.isEmpty()) { +			for (RectList::size_type j = 0; j < showList.size(); ++j) { +				const Common::Rect &r2 = *showList[j]; +				if (!r2.isEmpty()) { +					merged = r1; +					merged.extend(r2); + +					int difference = merged.width() * merged.height(); +					difference -= r1.width() * r1.height(); +					difference -= r2.width() * r2.height(); +					if (r1.intersects(r2)) { +						const Common::Rect overlap = r1.findIntersectingRect(r2); +						difference += overlap.width() * overlap.height(); +					} + +					if (difference <= overdrawThreshold) { +						mergeList.erase_at(i); +						showList.erase_at(j); +						mergeList.add(merged); +						didMerge = true; +						break; +					} else { +						Common::Rect outRects[2]; +						int splitCount = splitRectsForRender(*mergeList[i], *showList[j], outRects); +						if (splitCount != -1) { +							mergeList.add(*mergeList[i]); +							mergeList.erase_at(i); +							showList.erase_at(j); +							didMerge = true; +							while (splitCount--) { +								mergeList.add(outRects[splitCount]); +							} +							break; +						} +					} +				}  			} -		} -		count = showList.pack(); +			if (didMerge) { +				showList.pack(); +			} +		}  	} -	showList.add(merged); +	mergeList.pack(); +	for (RectList::size_type i = 0; i < mergeList.size(); ++i) { +		showList.add(*mergeList[i]); +	}  }  void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) { -	Palette sourcePalette(*_palette->getNextPalette()); +	Palette sourcePalette(_palette->getNextPalette());  	alterVmap(sourcePalette, sourcePalette, -1, styleRanges);  	int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16(); @@ -864,8 +1014,6 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry  	_showList.add(rect);  	showBits(); -	Common::Rect calcRect(0, 0); -  	// NOTE: The original engine allocated these as static arrays of 100  	// pointers to ScreenItemList / RectList  	ScreenItemListList screenItemLists; @@ -878,7 +1026,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry  		remapMarkRedraw();  	} -	calcLists(screenItemLists, eraseLists, calcRect); +	calcLists(screenItemLists, eraseLists);  	for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {  		list->sort();  	} @@ -897,7 +1045,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry  		drawScreenItemList(screenItemLists[i]);  	} -	Palette nextPalette(*_palette->getNextPalette()); +	Palette nextPalette(_palette->getNextPalette());  	if (prevRoom < 1000) {  		for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) { @@ -908,6 +1056,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry  		}  	} else {  		for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) { +			// TODO: Limiting range 72 to 103 is NOT present in every game  			if (styleRanges[i] == -1 || (styleRanges[i] == 0 && i > 71 && i < 104)) {  				sourcePalette.colors[i] = nextPalette.colors[i];  				sourcePalette.colors[i].used = true; @@ -938,7 +1087,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry  		remapMarkRedraw();  	} -	calcLists(screenItemLists, eraseLists, calcRect); +	calcLists(screenItemLists, eraseLists);  	for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {  		list->sort();  	} @@ -967,10 +1116,6 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry  	_frameNowVisible = true;  } -// TODO: What does the bit masking for the show rects do, -// and does it cause an off-by-one error in rect calculations -// since SOL_Rect is BR inclusive and Common::Rect is BR -// exclusive?  void GfxFrameout::showBits() {  	for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {  		Common::Rect rounded(**rect); diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h index 0f06ee1c85..e736872773 100644 --- a/engines/sci/graphics/frameout.h +++ b/engines/sci/graphics/frameout.h @@ -149,10 +149,7 @@ struct ShowStyleEntry {  typedef Common::Array<DrawList> ScreenItemListList;  typedef Common::Array<RectList> EraseListList; -class GfxCache;  class GfxCoordAdjuster32; -class GfxPaint32; -class GfxPalette;  class GfxScreen;  /** @@ -162,16 +159,14 @@ class GfxScreen;  class GfxFrameout {  private:  	bool _isHiRes; -	GfxCache *_cache;  	GfxCoordAdjuster32 *_coordAdjuster;  	GfxPalette32 *_palette;  	ResourceManager *_resMan;  	GfxScreen *_screen;  	SegManager *_segMan; -	GfxPaint32 *_paint32;  public: -	GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32); +	GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette);  	~GfxFrameout();  	void clear(); @@ -194,13 +189,28 @@ private:  	 */  	bool _throttleFrameOut; +	/** +	 * Determines whether or not a screen item is the "Fred" +	 * object. +	 */ +	bool checkForFred(const reg_t object); +  #pragma mark -  #pragma mark Screen items  private: -	void deleteScreenItem(ScreenItem *screenItem, const reg_t plane);  	void remapMarkRedraw();  public: +	/** +	 * Deletes a screen item from the given plane. +	 */ +	void deleteScreenItem(ScreenItem *screenItem, Plane *plane); + +	/** +	 * Deletes a screen item from the given plane. +	 */ +	void deleteScreenItem(ScreenItem *screenItem, const reg_t plane); +  	void kernelAddScreenItem(const reg_t object);  	void kernelUpdateScreenItem(const reg_t object);  	void kernelDeleteScreenItem(const reg_t object); @@ -366,8 +376,10 @@ private:  	 * over the entire screen for rendering the next frame.  	 * The draw and erase lists in `drawLists` and  	 * `eraseLists` each represent one plane on the screen. +	 * The optional `eraseRect` argument allows a specific +	 * area of the screen to be erased.  	 */ -	void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect); +	void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect = Common::Rect());  	/**  	 * Erases the areas in the given erase list from the @@ -420,9 +432,10 @@ public:  	/**  	 * Updates the internal screen buffer for the next  	 * frame. If `shouldShowBits` is true, also sends the -	 * buffer to hardware. +	 * buffer to hardware. If `eraseRect` is non-empty, +	 * it is added to the erase list for this frame.  	 */ -	void frameOut(const bool shouldShowBits, const Common::Rect &rect = Common::Rect()); +	void frameOut(const bool shouldShowBits, const Common::Rect &eraseRect = Common::Rect());  	/**  	 * Modifies the raw pixel data for the next frame with diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h index 19dddd74b8..3fcc83c5e2 100644 --- a/engines/sci/graphics/helpers.h +++ b/engines/sci/graphics/helpers.h @@ -191,6 +191,12 @@ struct Buffer : public Graphics::Surface {  	uint16 scriptWidth;  	uint16 scriptHeight; +	Buffer() : +		screenWidth(0), +		screenHeight(0), +		scriptWidth(320), +		scriptHeight(200) {} +  	Buffer(const uint16 width, const uint16 height, uint8 *const pix) :  		screenWidth(width),  		screenHeight(height), @@ -231,7 +237,7 @@ struct Color {  		return used == other.used && r == other.r && g == other.g && b == other.b;  	}  	inline bool operator!=(const Color &other) const { -		return !(*this == other); +		return !operator==(other);  	}  #endif  }; diff --git a/engines/sci/graphics/paint.h b/engines/sci/graphics/paint.h deleted file mode 100644 index b2277131d5..0000000000 --- a/engines/sci/graphics/paint.h +++ /dev/null @@ -1,39 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef SCI_GRAPHICS_PAINT_H -#define SCI_GRAPHICS_PAINT_H - -namespace Sci { - -class GfxPaint { -public: -	GfxPaint(); -	virtual ~GfxPaint(); - -	virtual void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo); -	virtual void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control); -}; - -} // End of namespace Sci - -#endif diff --git a/engines/sci/graphics/paint16.h b/engines/sci/graphics/paint16.h index 955cfdec8f..317388b2df 100644 --- a/engines/sci/graphics/paint16.h +++ b/engines/sci/graphics/paint16.h @@ -23,8 +23,6 @@  #ifndef SCI_GRAPHICS_PAINT16_H  #define SCI_GRAPHICS_PAINT16_H -#include "sci/graphics/paint.h" -  namespace Sci {  class GfxPorts; @@ -36,7 +34,7 @@ class GfxView;  /**   * Paint16 class, handles painting/drawing for SCI16 (SCI0-SCI1.1) games   */ -class GfxPaint16 : public GfxPaint { +class GfxPaint16 {  public:  	GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio);  	~GfxPaint16(); diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp index a210a469f1..bfd46484e9 100644 --- a/engines/sci/graphics/paint32.cpp +++ b/engines/sci/graphics/paint32.cpp @@ -20,49 +20,162 @@   *   */ -#include "sci/sci.h" -#include "sci/engine/state.h" -#include "sci/engine/selector.h" -#include "sci/graphics/coordadjuster.h" -#include "sci/graphics/cache.h" +#include "graphics/primitives.h" +#include "sci/engine/seg_manager.h"  #include "sci/graphics/paint32.h" -#include "sci/graphics/font.h" -#include "sci/graphics/picture.h" -#include "sci/graphics/view.h" -#include "sci/graphics/screen.h" -#include "sci/graphics/palette.h" +#include "sci/graphics/text32.h"  namespace Sci { -GfxPaint32::GfxPaint32(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette) -	: _resMan(resMan), _coordAdjuster(coordAdjuster), _screen(screen), _palette(palette) { +GfxPaint32::GfxPaint32(SegManager *segMan) : +	_segMan(segMan) {} + +reg_t GfxPaint32::kernelAddLine(const reg_t planeObject, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness) { +	Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject); +	if (plane == nullptr) { +		error("kAddLine: Plane %04x:%04x not found", PRINT_REG(planeObject)); +	} + +	Common::Rect gameRect; +	BitmapResource bitmap = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect); + +	CelInfo32 celInfo; +	celInfo.type = kCelTypeMem; +	celInfo.bitmap = bitmap.getObject(); +	// SSCI stores the line color on `celInfo`, even though +	// this is not a `kCelTypeColor`, as a hack so that +	// `kUpdateLine` can get the originally used color +	celInfo.color = color; + +	ScreenItem *screenItem = new ScreenItem(planeObject, celInfo, Common::Rect(startPoint.x, startPoint.y, startPoint.x + bitmap.getWidth(), startPoint.y + bitmap.getHeight())); +	screenItem->_priority = priority; +	screenItem->_fixedPriority = true; + +	plane->_screenItemList.add(screenItem); + +	return screenItem->_object; +} + +void GfxPaint32::kernelUpdateLine(ScreenItem *screenItem, Plane *plane, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness) { + +	Common::Rect gameRect; +	BitmapResource bitmap = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect); + +	_segMan->freeHunkEntry(screenItem->_celInfo.bitmap); +	screenItem->_celInfo.bitmap = bitmap.getObject(); +	screenItem->_celInfo.color = color; +	screenItem->_position = startPoint; +	screenItem->_priority = priority; +	screenItem->update();  } -GfxPaint32::~GfxPaint32() { +void GfxPaint32::kernelDeleteLine(const reg_t screenItemObject, const reg_t planeObject) { +	Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject); +	if (plane == nullptr) { +		return; +	} + +	ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject); +	if (screenItem == nullptr) { +		return; +	} + +	_segMan->freeHunkEntry(screenItem->_celInfo.bitmap); +	g_sci->_gfxFrameout->deleteScreenItem(screenItem, plane);  } -void GfxPaint32::fillRect(Common::Rect rect, byte color) { -	int16 y, x; -	Common::Rect clipRect = rect; +void GfxPaint32::plotter(int x, int y, int color, void *data) { +	LineProperties &properties = *static_cast<LineProperties *>(data); +	byte *pixels = properties.bitmap->getPixels(); + +	const uint32 index = properties.bitmap->getWidth() * y + x; + +	if (index < properties.bitmap->getDataSize()) { +		if (properties.solid) { +			pixels[index] = (uint8)color; +			return; +		} + +		if (properties.horizontal && x != properties.lastAddress) { +			properties.lastAddress = x; +			++properties.patternIndex; +		} else if (!properties.horizontal && y != properties.lastAddress) { +			properties.lastAddress = y; +			++properties.patternIndex; +		} -	clipRect.clip(_screen->getWidth(), _screen->getHeight()); +		if (properties.pattern[properties.patternIndex]) { +			pixels[index] = (uint8)color; +		} -	for (y = clipRect.top; y < clipRect.bottom; y++) { -		for (x = clipRect.left; x < clipRect.right; x++) { -			_screen->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, color, 0, 0); +		if (properties.patternIndex == ARRAYSIZE(properties.pattern)) { +			properties.patternIndex = 0;  		} +	} else { +		warning("GfxPaint32::plotter: Attempted to write out of bounds (%u >= %u)", index, properties.bitmap->getDataSize());  	}  } -void GfxPaint32::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) { -	GfxPicture *picture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, pictureId, false); +BitmapResource GfxPaint32::makeLineBitmap(const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, uint16 pattern, uint8 thickness, Common::Rect &outRect) { +	const uint8 skipColor = color != 250 ? 250 : 0; -	picture->draw(animationNr, mirroredFlag, addToFlag, EGApaletteNo); -	delete picture; -} +	// Thickness is expected to be 2n+1 +	thickness = ((MAX((uint8)1, thickness) - 1) | 1); +	const uint8 halfThickness = thickness >> 1; + +	outRect.left = (startPoint.x < endPoint.x ? startPoint.x : endPoint.x) - halfThickness; +	outRect.top = (startPoint.y < endPoint.y ? startPoint.y : endPoint.y) - halfThickness; +	outRect.right = (startPoint.x > endPoint.x ? startPoint.x : endPoint.x) + halfThickness + 1; +	outRect.bottom = (startPoint.y > endPoint.y ? startPoint.y : endPoint.y) + halfThickness + 1; + +	BitmapResource bitmap(_segMan, outRect.width(), outRect.height(), skipColor, 0, 0, g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth, g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight, 0, false); + +	byte *pixels = bitmap.getPixels(); +	memset(pixels, skipColor, bitmap.getWidth() * bitmap.getHeight()); + +	LineProperties properties; +	properties.bitmap = &bitmap; + +	switch (style) { +	case kLineStyleSolid: +		pattern = 0xFFFF; +		properties.solid = true; +		break; +	case kLineStyleDashed: +		pattern = 0xFF00; +		properties.solid = false; +		break; +	case kLineStylePattern: +		properties.solid = pattern == 0xFFFF; +		break; +	} + +	const Common::Rect drawRect( +		startPoint.x - outRect.left, +		startPoint.y - outRect.top, +		endPoint.x - outRect.left, +		endPoint.y - outRect.top +	); -void GfxPaint32::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) { -	_screen->drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, color, priority, control); +	if (!properties.solid) { +		for (int i = 0; i < ARRAYSIZE(properties.pattern); ++i) { +			properties.pattern[i] = (pattern & 0x8000); +			pattern <<= 1; +		} + +		properties.patternIndex = 0; +		properties.horizontal = ABS(drawRect.right - drawRect.left) > ABS(drawRect.bottom - drawRect.top); +		properties.lastAddress = properties.horizontal ? drawRect.left : drawRect.top; +	} + +	if (thickness <= 1) { +		Graphics::drawLine(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, color, plotter, &properties); +	} else { +		Graphics::drawThickLine2(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, thickness, color, plotter, &properties); +	} + +	return bitmap;  } +  } // End of namespace Sci diff --git a/engines/sci/graphics/paint32.h b/engines/sci/graphics/paint32.h index e7a3ec256d..6d5a957fcd 100644 --- a/engines/sci/graphics/paint32.h +++ b/engines/sci/graphics/paint32.h @@ -23,30 +23,48 @@  #ifndef SCI_GRAPHICS_PAINT32_H  #define SCI_GRAPHICS_PAINT32_H -#include "sci/graphics/paint.h" -  namespace Sci { +class BitmapResource; +class Plane; +class ScreenItem; +class SegManager; -class GfxPorts; +enum LineStyle { +	kLineStyleSolid, +	kLineStyleDashed, +	kLineStylePattern +};  /**   * Paint32 class, handles painting/drawing for SCI32 (SCI2+) games   */ -class GfxPaint32 : public GfxPaint { +class GfxPaint32 {  public: -	GfxPaint32(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette); -	~GfxPaint32(); +	GfxPaint32(SegManager *segMan); -	void fillRect(Common::Rect rect, byte color); +private: +	SegManager *_segMan; -	void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo); -	void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control); +#pragma mark - +#pragma mark Line drawing +public: +	reg_t kernelAddLine(const reg_t planeObject, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness); +	void kernelUpdateLine(ScreenItem *screenItem, Plane *plane, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness); +	void kernelDeleteLine(const reg_t screenItemObject, const reg_t planeObject);  private: -	ResourceManager *_resMan; -	GfxCoordAdjuster *_coordAdjuster; -	GfxScreen *_screen; -	GfxPalette *_palette; +	typedef struct { +		BitmapResource *bitmap; +		bool pattern[16]; +		uint8 patternIndex; +		bool solid; +		bool horizontal; +		int lastAddress; +	} LineProperties; + +	static void plotter(int x, int y, int color, void *data); + +	BitmapResource makeLineBitmap(const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness, Common::Rect &outRect);  };  } // End of namespace Sci diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp index 6844011675..0840e82a40 100644 --- a/engines/sci/graphics/palette32.cpp +++ b/engines/sci/graphics/palette32.cpp @@ -28,7 +28,7 @@  #include "sci/event.h"  #include "sci/resource.h"  #include "sci/graphics/palette32.h" -#include "sci/graphics/remap.h" +#include "sci/graphics/remap32.h"  #include "sci/graphics/screen.h"  namespace Sci { @@ -78,10 +78,6 @@ inline void mergePaletteInternal(Palette *const to, const Palette *const from) {  	}  } -const Palette *GfxPalette32::getNextPalette() const { -	return &_nextPalette; -} -  void GfxPalette32::submit(Palette &palette) {  	// TODO: The resource manager in SCI32 retains raw data of palettes from  	// the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and @@ -178,49 +174,6 @@ void GfxPalette32::set(Palette *newPalette, bool force, bool forceRealMerge) {  	submit(*newPalette);  } -// In SCI32 engine this method is SOLPalette::Match(Rgb24 *, int, int *, int *) -// and is used by Remap -// TODO: Anything that calls GfxPalette::matchColor(int, int, int) is going to -// match using an algorithm from SCI16 engine right now. This needs to be -// corrected in the future so either nothing calls -// GfxPalette::matchColor(int, int, int), or it is fixed to match the other -// SCI32 algorithms. -int16 GfxPalette32::matchColor(const byte r, const byte g, const byte b, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable) { -	int16 bestIndex = -1; -	int bestDifference = 0xFFFFF; -	int difference = defaultDifference; - -	// SQ6 DOS really does check only the first 236 entries -	for (int i = 0, channelDifference; i < 236; ++i) { -		if (matchTable[i] == 0) { -			continue; -		} - -		difference = _sysPalette.colors[i].r - r; -		difference *= difference; -		if (bestDifference <= difference) { -			continue; -		} -		channelDifference = _sysPalette.colors[i].g - g; -		difference += channelDifference * channelDifference; -		if (bestDifference <= difference) { -			continue; -		} -		channelDifference = _sysPalette.colors[i].b - b; -		difference += channelDifference * channelDifference; -		if (bestDifference <= difference) { -			continue; -		} -		bestDifference = difference; -		bestIndex = i; -	} - -	// NOTE: This value is only valid if the last index to -	// perform a difference calculation was the best index -	lastCalculatedDifference = difference; -	return bestIndex; -} -  bool GfxPalette32::updateForFrame() {  	applyAll();  	_versionUpdated = false; diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h index a5450776dc..7dda53e5c1 100644 --- a/engines/sci/graphics/palette32.h +++ b/engines/sci/graphics/palette32.h @@ -113,12 +113,12 @@ private:  public:  	virtual void saveLoadWithSerializer(Common::Serializer &s) override; -	const Palette *getNextPalette() const; +	inline const Palette &getNextPalette() const { return _nextPalette; }; +	inline const Palette &getCurrentPalette() const { return _sysPalette; };  	bool kernelSetFromResource(GuiResourceId resourceId, bool force) override;  	int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override;  	void set(Palette *newPalette, bool force, bool forceRealMerge = false) override; -	int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable);  	/**  	 * Submits a palette to display. Entries marked as “used” in the @@ -133,7 +133,7 @@ public:  	void applyAll();  #pragma mark - -#pragma mark color look-up +#pragma mark Color look-up  private:  	/**  	 * An optional lookup table used to remap RGB565 colors to a palette @@ -240,6 +240,11 @@ private:  	 * According to SCI engine code, when two cyclers overlap,  	 * a fatal error has occurred and the engine will display  	 * an error and then exit. +	 * +	 * The cycle map is also by the color remapping system to +	 * avoid attempting to remap to palette entries that are +	 * cycling (so won't be the expected color once the cycler +	 * runs again).  	 */  	bool _cycleMap[256];  	inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear); @@ -257,7 +262,7 @@ public:  	void cycleAllOff();  	void applyAllCycles();  	void applyCycles(); -	const bool *getCyclemap() { return _cycleMap; } +	inline const bool *getCycleMap() const { return _cycleMap; }  #pragma mark -  #pragma mark Fading diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp index 470986fb3c..175875c414 100644 --- a/engines/sci/graphics/plane32.cpp +++ b/engines/sci/graphics/plane32.cpp @@ -27,7 +27,7 @@  #include "sci/graphics/frameout.h"  #include "sci/graphics/lists32.h"  #include "sci/graphics/plane32.h" -#include "sci/graphics/remap.h" +#include "sci/graphics/remap32.h"  #include "sci/graphics/screen.h"  #include "sci/graphics/screen_item32.h" @@ -163,11 +163,15 @@ void Plane::printDebugInfo(Console *con) const {  void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX) {  	uint16 celCount = 1000; +	bool transparent = true;  	for (uint16 celNo = 0; celNo < celCount; ++celNo) {  		CelObjPic *celObj = new CelObjPic(pictureId, celNo);  		if (celCount == 1000) {  			celCount = celObj->_celCount;  		} +		if (!celObj->_transparent) { +			transparent = false; +		}  		ScreenItem *screenItem = new ScreenItem(_object, celObj->_info);  		screenItem->_pictureId = pictureId; @@ -184,6 +188,7 @@ void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *p  		delete screenItem->_celObj;  		screenItem->_celObj = celObj;  	} +	_type = transparent ? kPlaneTypeTransparentPicture : kPlaneTypePicture;  }  void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX) { @@ -196,7 +201,7 @@ void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position,  void Plane::changePic() {  	_pictureChanged = false; -	if (_type != kPlaneTypePicture) { +	if (_type != kPlaneTypePicture && _type != kPlaneTypeTransparentPicture) {  		return;  	} @@ -240,16 +245,20 @@ void Plane::deleteAllPics() {  #pragma mark Plane - Rendering  void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const { -	int index = planeList.findIndexByObject(_object); +	const int nextPlaneIndex = planeList.findIndexByObject(_object) + 1; +	const PlaneList::size_type planeCount = planeList.size();  	for (DrawList::size_type i = 0; i < drawList.size(); ++i) { -		for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) { -			if (planeList[j]->_type != kPlaneTypeTransparent) { -				Common::Rect ptr[4]; -				int count = splitRects(drawList[i]->rect, planeList[j]->_screenRect, ptr); -				if (count != -1) { -					for (int k = count - 1; k >= 0; --k) { -						drawList.add(drawList[i]->screenItem, ptr[k]); +		for (PlaneList::size_type j = nextPlaneIndex; j < planeCount; ++j) { +			if ( +				planeList[j]->_type != kPlaneTypeTransparent && +				planeList[j]->_type != kPlaneTypeTransparentPicture +			) { +				Common::Rect outRects[4]; +				int splitCount = splitRects(drawList[i]->rect, planeList[j]->_screenRect, outRects); +				if (splitCount != -1) { +					while (splitCount--) { +						drawList.add(drawList[i]->screenItem, outRects[splitCount]);  					}  					drawList.erase_at(i); @@ -262,17 +271,20 @@ void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList  }  void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const { -	int index = planeList.findIndexByObject(_object); +	const int nextPlaneIndex = planeList.findIndexByObject(_object) + 1; +	const PlaneList::size_type planeCount = planeList.size();  	for (RectList::size_type i = 0; i < eraseList.size(); ++i) { -		for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) { -			if (planeList[j]->_type != kPlaneTypeTransparent) { -				Common::Rect ptr[4]; - -				int count = splitRects(*eraseList[i], planeList[j]->_screenRect, ptr); -				if (count != -1) { -					for (int k = count - 1; k >= 0; --k) { -						eraseList.add(ptr[k]); +		for (PlaneList::size_type j = nextPlaneIndex; j < planeCount; ++j) { +			if ( +				planeList[j]->_type != kPlaneTypeTransparent && +				planeList[j]->_type != kPlaneTypeTransparentPicture +			) { +				Common::Rect outRects[4]; +				int splitCount = splitRects(*eraseList[i], planeList[j]->_screenRect, outRects); +				if (splitCount != -1) { +					while (splitCount--) { +						eraseList.add(outRects[splitCount]);  					}  					eraseList.erase_at(i); @@ -285,94 +297,109 @@ void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeLi  }  void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) { -	ScreenItemList::size_type planeItemCount = _screenItemList.size(); -	ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size(); +	const ScreenItemList::size_type screenItemCount = _screenItemList.size(); +	const ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size(); + +	for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) { +		// Items can be added to ScreenItemList and we don't want to process +		// those new items, but the list also can grow smaller, so we need +		// to check that we are still within the upper bound of the list and +		// quit if we aren't any more +		if (i >= _screenItemList.size()) { +			break; +		} + +		ScreenItem *item = _screenItemList[i]; +		if (item == nullptr) { +			continue; +		} -	for (ScreenItemList::size_type i = 0; i < planeItemCount; ++i) { -		ScreenItem *vitem = nullptr;  		// NOTE: The original engine used an array without bounds checking  		// so could just get the visible screen item directly; we need to  		// verify that the index is actually within the valid range for  		// the visible plane before accessing the item to avoid a range  		// error. +		const ScreenItem *visibleItem = nullptr;  		if (i < visiblePlaneItemCount) { -			vitem = visiblePlane._screenItemList[i]; +			visibleItem = visiblePlane._screenItemList[i];  		} -		ScreenItem *item = _screenItemList[i]; -		if (i < _screenItemList.size() && item != nullptr) { -			if (item->_deleted) { -				// add item's rect to erase list -				if ( -					i < visiblePlane._screenItemList.size() && -					vitem != nullptr && -					!vitem->_screenRect.isEmpty() -				) { -					if (g_sci->_gfxRemap32->getRemapCount()) { -						mergeToRectList(vitem->_screenRect, eraseList); -					} else { -						eraseList.add(vitem->_screenRect); -					} -				} -			} else if (item->_created) { -				// add item to draw list -				item->calcRects(*this); - -				if(!item->_screenRect.isEmpty()) { -					if (g_sci->_gfxRemap32->getRemapCount()) { -						drawList.add(item, item->_screenRect); -						mergeToRectList(item->_screenRect, eraseList); -					} else { -						drawList.add(item, item->_screenRect); -					} -				} -			} else if (item->_updated) { -				// add old rect to erase list, new item to draw list -				item->calcRects(*this); +		// Keep erase rects for this screen item from drawing outside +		// of its owner plane +		Common::Rect visibleItemScreenRect; +		if (visibleItem != nullptr) { +			visibleItemScreenRect = visibleItem->_screenRect; +			visibleItemScreenRect.clip(_screenRect); +		} + +		if (item->_deleted) { +			// Add item's rect to erase list +			if ( +				visibleItem != nullptr && +				!visibleItemScreenRect.isEmpty() +			) {  				if (g_sci->_gfxRemap32->getRemapCount()) { -					// if item and vitem don't overlap, ... -					if (item->_screenRect.isEmpty() || -						i >= visiblePlaneItemCount || -						vitem == nullptr || -						vitem->_screenRect.isEmpty() || -						!vitem->_screenRect.intersects(item->_screenRect) -					) { -						// add item to draw list, and old rect to erase list -						if (!item->_screenRect.isEmpty()) { -							drawList.add(item, item->_screenRect); -							mergeToRectList(item->_screenRect, eraseList); -						} -						if ( -							i < visiblePlaneItemCount && -							vitem != nullptr && -							!vitem->_screenRect.isEmpty() -						) { -							mergeToRectList(vitem->_screenRect, eraseList); -						} -					} else { -						// otherwise, add bounding box of old+new to erase list, -						// and item to draw list +					mergeToRectList(visibleItemScreenRect, eraseList); +				} else { +					eraseList.add(visibleItemScreenRect); +				} +			} +		} -						// TODO: This was changed from disasm, verify please! -						Common::Rect extendedScreenRect = vitem->_screenRect; -						extendedScreenRect.extend(item->_screenRect); +		if (!item->_created && !item->_updated) { +			continue; +		} -						drawList.add(item, item->_screenRect); -						mergeToRectList(extendedScreenRect, eraseList); -					} +		item->calcRects(*this); +		const Common::Rect itemScreenRect(item->_screenRect); + +		if (item->_created) { +			// Add item to draw list +			if(!itemScreenRect.isEmpty()) { +				if (g_sci->_gfxRemap32->getRemapCount()) { +					drawList.add(item, itemScreenRect); +					mergeToRectList(itemScreenRect, eraseList);  				} else { -					// if no active remaps, just add item to draw list and old rect -					// to erase list -					if (!item->_screenRect.isEmpty()) { -						drawList.add(item, item->_screenRect); +					drawList.add(item, itemScreenRect); +				} +			} +		} else { +			// Add old rect to erase list, new item to draw list + +			if (g_sci->_gfxRemap32->getRemapCount()) { +				// If item and visibleItem don't overlap... +				if (itemScreenRect.isEmpty() || +					visibleItem == nullptr || +					visibleItemScreenRect.isEmpty() || +					!visibleItemScreenRect.intersects(itemScreenRect) +				) { +					// ...add item to draw list, and old rect to erase list... +					if (!itemScreenRect.isEmpty()) { +						drawList.add(item, itemScreenRect); +						mergeToRectList(itemScreenRect, eraseList);  					} -					if ( -						i < visiblePlaneItemCount && -						vitem != nullptr && -						!vitem->_screenRect.isEmpty() -					) { -						eraseList.add(vitem->_screenRect); +					if (visibleItem != nullptr && !visibleItemScreenRect.isEmpty()) { +						mergeToRectList(visibleItemScreenRect, eraseList);  					} +				} else { +					// ...otherwise, add bounding box of old+new to erase list, +					// and item to draw list +					Common::Rect extendedScreenRect = visibleItemScreenRect; +					extendedScreenRect.extend(itemScreenRect); + +					drawList.add(item, itemScreenRect); +					mergeToRectList(extendedScreenRect, eraseList); +				} +			} else { +				// If no active remaps, just add item to draw list and old rect +				// to erase list + +				// TODO: SCI3 update rects for VMD? +				if (!itemScreenRect.isEmpty()) { +					drawList.add(item, itemScreenRect); +				} +				if (visibleItem != nullptr && !visibleItemScreenRect.isEmpty()) { +					eraseList.add(visibleItemScreenRect);  				}  			}  		} @@ -385,40 +412,44 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList  	// We store the current size of the drawlist, as we want to loop  	// over the currently inserted entries later.  	DrawList::size_type drawListSizePrimary = drawList.size(); +	const RectList::size_type eraseListCount = eraseList.size(); -	if (/* TODO: dword_C6288 */ false) {  // "high resolution pictures"???? +	// TODO: Figure out which games need which rendering method +	if (/* TODO: dword_C6288 */ false) {  // "high resolution pictures"  		_screenItemList.sort(); -		bool encounteredPic = false; -		bool v81 = false; +		bool pictureDrawn = false; +		bool screenItemDrawn = false; -		for (RectList::size_type i = 0; i < eraseList.size(); ++i) { -			const Common::Rect *rect = eraseList[i]; +		for (RectList::size_type i = 0; i < eraseListCount; ++i) { +			const Common::Rect &rect = *eraseList[i]; -			for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { +			for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {  				ScreenItem *item = _screenItemList[j]; -				if (j < _screenItemList.size() && item != nullptr) { -					if (rect->intersects(item->_screenRect)) { -						const Common::Rect intersection = rect->findIntersectingRect(item->_screenRect); -						if (!item->_deleted) { -							if (encounteredPic) { -								if (item->_celInfo.type == kCelTypePic) { -									if (v81 || item->_celInfo.celNo == 0) { -										drawList.add(item, intersection); -									} -								} else { -									if (!item->_updated && !item->_created) { -										drawList.add(item, intersection); -									} -									v81 = true; +				if (item == nullptr) { +					continue; +				} + +				if (rect.intersects(item->_screenRect)) { +					const Common::Rect intersection = rect.findIntersectingRect(item->_screenRect); +					if (!item->_deleted) { +						if (pictureDrawn) { +							if (item->_celInfo.type == kCelTypePic) { +								if (screenItemDrawn || item->_celInfo.celNo == 0) { +									mergeToDrawList(j, intersection, drawList);  								}  							} else {  								if (!item->_updated && !item->_created) { -									drawList.add(item, intersection); -								} -								if (item->_celInfo.type == kCelTypePic) { -									encounteredPic = true; +									mergeToDrawList(j, intersection, drawList);  								} +								screenItemDrawn = true; +							} +						} else { +							if (!item->_updated && !item->_created) { +								mergeToDrawList(j, intersection, drawList); +							} +							if (item->_celInfo.type == kCelTypePic) { +								pictureDrawn = true;  							}  						}  					} @@ -428,22 +459,23 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList  		_screenItemList.unsort();  	} else { -		// add all items overlapping the erase list to the draw list -		for (RectList::size_type i = 0; i < eraseList.size(); ++i) { -			for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { +		// Add all items overlapping the erase list to the draw list +		for (RectList::size_type i = 0; i < eraseListCount; ++i) { +			const Common::Rect &rect = *eraseList[i]; +			for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) {  				ScreenItem *item = _screenItemList[j];  				if (  					item != nullptr &&  					!item->_created && !item->_updated && !item->_deleted && -					eraseList[i]->intersects(item->_screenRect) +					rect.intersects(item->_screenRect)  				) { -					drawList.add(item, eraseList[i]->findIntersectingRect(item->_screenRect)); +					drawList.add(item, rect.findIntersectingRect(item->_screenRect));  				}  			}  		}  	} -	if (g_sci->_gfxRemap32->getRemapCount() == 0) { // no remaps active? +	if (g_sci->_gfxRemap32->getRemapCount() == 0) {  		// Add all items that overlap with items in the drawlist and have higher  		// priority. @@ -451,23 +483,28 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList  		// those that were added because of the erase list in the previous loop,  		// or those to be added in this loop.  		for (DrawList::size_type i = 0; i < drawListSizePrimary; ++i) { -			DrawItem *dli = drawList[i]; +			const DrawItem *drawListEntry = nullptr; +			if (i < drawList.size()) { +				drawListEntry = drawList[i]; +			} -			for (ScreenItemList::size_type j = 0; j < planeItemCount; ++j) { -				ScreenItem *sli = _screenItemList[j]; +			for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { +				ScreenItem *newItem = nullptr; +				if (j < _screenItemList.size()) { +					newItem = _screenItemList[j]; +				}  				if ( -					i < drawList.size() && dli != nullptr && -					j < _screenItemList.size() && sli != nullptr && -					!sli->_created && !sli->_updated && !sli->_deleted +					drawListEntry != nullptr && newItem != nullptr && +					!newItem->_created && !newItem->_updated && !newItem->_deleted  				) { -					ScreenItem *item = dli->screenItem; +					const ScreenItem *drawnItem = drawListEntry->screenItem;  					if ( -						(sli->_priority > item->_priority || (sli->_priority == item->_priority && sli->_object > item->_object)) && -						dli->rect.intersects(sli->_screenRect) +						(newItem->_priority > drawnItem->_priority || (newItem->_priority == drawnItem->_priority && newItem->_object > drawnItem->_object)) && +						drawListEntry->rect.intersects(newItem->_screenRect)  					) { -						drawList.add(sli, dli->rect.findIntersectingRect(sli->_screenRect)); +						mergeToDrawList(j, drawListEntry->rect.findIntersectingRect(newItem->_screenRect), drawList);  					}  				}  			} @@ -475,14 +512,11 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList  	}  	decrementScreenItemArrayCounts(&visiblePlane, false); -	_screenItemList.pack(); -	visiblePlane._screenItemList.pack();  }  void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate) { -	// The size of the screenItemList may change, so it is -	// critical to re-check the size on each iteration -	for (ScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { +	const ScreenItemList::size_type screenItemCount = _screenItemList.size(); +	for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) {  		ScreenItem *item = _screenItemList[i];  		if (item != nullptr) { @@ -495,7 +529,7 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force  					visiblePlane->_screenItemList.findByObject(item->_object) != nullptr  				)  			) { -				*visiblePlane->_screenItemList[i] = *_screenItemList[i]; +				*visiblePlane->_screenItemList[i] = *item;  			}  			if (item->_updated) { @@ -514,175 +548,180 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force  			if (item->_deleted) {  				item->_deleted--;  				if (!item->_deleted) { -					visiblePlane->_screenItemList.erase_at(i); +					if (visiblePlane != nullptr && visiblePlane->_screenItemList.findByObject(item->_object) != nullptr) { +						visiblePlane->_screenItemList.erase_at(i); +					}  					_screenItemList.erase_at(i);  				}  			}  		}  	} + +	_screenItemList.pack(); +	if (visiblePlane != nullptr) { +		visiblePlane->_screenItemList.pack(); +	}  } -void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const { -	if (_type == kPlaneTypeTransparent) { -		for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) { -			const Common::Rect *r = transparentEraseList[i]; -			for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { -				ScreenItem *item = _screenItemList[j]; -				if (item != nullptr) { -					if (r->intersects(item->_screenRect)) { -						mergeToDrawList(j, *r, drawList); -					} +void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const { +	const RectList::size_type higherEraseCount = higherEraseList.size(); + +	if (_type == kPlaneTypeTransparent || _type == kPlaneTypeTransparentPicture) { +		for (RectList::size_type i = 0; i < higherEraseCount; ++i) { +			const Common::Rect &r = *higherEraseList[i]; +			const ScreenItemList::size_type screenItemCount = _screenItemList.size(); +			for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { +				const ScreenItem *item = _screenItemList[j]; +				if (item != nullptr && r.intersects(item->_screenRect)) { +					mergeToDrawList(j, r, drawList);  				}  			}  		}  	} else { -		for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) { -			Common::Rect *r = transparentEraseList[i]; -			if (r->intersects(_screenRect)) { -				r->clip(_screenRect); -				mergeToRectList(*r, eraseList); - -				for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { -					ScreenItem *item = _screenItemList[j]; - -					if (item != nullptr) { -						if (r->intersects(item->_screenRect)) { -							mergeToDrawList(j, *r, drawList); -						} +		for (RectList::size_type i = 0; i < higherEraseCount; ++i) { +			Common::Rect r = *higherEraseList[i]; +			if (r.intersects(_screenRect)) { +				r.clip(_screenRect); +				mergeToRectList(r, eraseList); + +				const ScreenItemList::size_type screenItemCount = _screenItemList.size(); +				for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { +					const ScreenItem *item = _screenItemList[j]; +					if (item != nullptr && r.intersects(item->_screenRect)) { +						mergeToDrawList(j, r, drawList);  					}  				} -				Common::Rect ptr[4]; -				const Common::Rect *r2 = transparentEraseList[i]; -				int count = splitRects(*r2, *r, ptr); -				for (int k = count - 1; k >= 0; --k) { -					transparentEraseList.add(ptr[k]); +				Common::Rect outRects[4]; +				const Common::Rect &r2 = *higherEraseList[i]; +				int splitCount = splitRects(r2, r, outRects); +				while (splitCount--) { +					higherEraseList.add(outRects[splitCount]);  				} -				transparentEraseList.erase_at(i); +				higherEraseList.erase_at(i);  			}  		} -		transparentEraseList.pack(); +		higherEraseList.pack();  	}  } -void Plane::filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const { -	for (DrawList::size_type i = 0; i < drawList.size(); ++i) { -		const Common::Rect &r = drawList[i]->rect; - -		for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { -			ScreenItem *item = _screenItemList[j]; -			if (item != nullptr) { -				if (r.intersects(item->_screenRect)) { -					mergeToDrawList(j, r, transparentDrawList); -				} +void Plane::filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const { +	const DrawList::size_type lowerDrawCount = lowerDrawList.size(); +	for (DrawList::size_type i = 0; i < lowerDrawCount; ++i) { +		const Common::Rect &r = lowerDrawList[i]->rect; +		const ScreenItemList::size_type screenItemCount = _screenItemList.size(); +		for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { +			const ScreenItem *item = _screenItemList[j]; +			if (item != nullptr && r.intersects(item->_screenRect)) { +				mergeToDrawList(j, r, drawList);  			}  		}  	}  } -void Plane::filterUpEraseRects(DrawList &drawList, RectList &eraseList) const { -	for (RectList::size_type i = 0; i < eraseList.size(); ++i) { -		const Common::Rect &r = *eraseList[i]; -		for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { -			ScreenItem *item = _screenItemList[j]; - -			if (item != nullptr) { -				if (r.intersects(item->_screenRect)) { -					mergeToDrawList(j, r, drawList); -				} +void Plane::filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const { +	const RectList::size_type lowerEraseCount = lowerEraseList.size(); +	for (RectList::size_type i = 0; i < lowerEraseCount; ++i) { +		const Common::Rect &r = *lowerEraseList[i]; +		const ScreenItemList::size_type screenItemCount = _screenItemList.size(); +		for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { +			const ScreenItem *item = _screenItemList[j]; +			if (item != nullptr && r.intersects(item->_screenRect)) { +				mergeToDrawList(j, r, drawList);  			}  		}  	}  }  void Plane::mergeToDrawList(const ScreenItemList::size_type index, const Common::Rect &rect, DrawList &drawList) const { -	RectList rects; - -	ScreenItem *item = _screenItemList[index]; -	Common::Rect r = item->_screenRect; +	RectList mergeList; +	ScreenItem &item = *_screenItemList[index]; +	Common::Rect r = item._screenRect;  	r.clip(rect); -	rects.add(r); +	mergeList.add(r); -	for (RectList::size_type i = 0; i < rects.size(); ++i) { -		r = *rects[i]; +	for (RectList::size_type i = 0; i < mergeList.size(); ++i) { +		r = *mergeList[i]; -		for (DrawList::size_type j = 0; j < drawList.size(); ++j) { -			const DrawItem *drawitem = drawList[j]; -			if (item->_object == drawitem->screenItem->_object) { -				if (drawitem->rect.contains(r)) { -					rects.erase_at(i); +		const DrawList::size_type drawCount = drawList.size(); +		for (DrawList::size_type j = 0; j < drawCount; ++j) { +			const DrawItem &drawItem = *drawList[j]; +			if (item._object == drawItem.screenItem->_object) { +				if (drawItem.rect.contains(r)) { +					mergeList.erase_at(i);  					break;  				}  				Common::Rect outRects[4]; -				const int count = splitRects(r, drawitem->rect, outRects); -				if (count != -1) { -					for (int k = count - 1; k >= 0; --k) { -						rects.add(outRects[k]); +				int splitCount = splitRects(r, drawItem.rect, outRects); +				if (splitCount != -1) { +					while (splitCount--) { +						mergeList.add(outRects[splitCount]);  					} -					rects.erase_at(i); +					mergeList.erase_at(i);  					// proceed to the next rect -					r = *rects[++i]; +					r = *mergeList[++i];  				}  			}  		}  	} -	rects.pack(); +	mergeList.pack(); -	for (RectList::size_type i = 0; i < rects.size(); ++i) { -		drawList.add(item, *rects[i]); +	for (RectList::size_type i = 0; i < mergeList.size(); ++i) { +		drawList.add(&item, *mergeList[i]);  	}  } -void Plane::mergeToRectList(const Common::Rect &rect, RectList &rectList) const { -	RectList temp; -	temp.add(rect); +void Plane::mergeToRectList(const Common::Rect &rect, RectList &eraseList) const { +	RectList mergeList; +	Common::Rect r; +	mergeList.add(rect); -	for (RectList::size_type i = 0; i < temp.size(); ++i) { -		Common::Rect r = *temp[i]; +	for (RectList::size_type i = 0; i < mergeList.size(); ++i) { +		r = *mergeList[i]; -		for (RectList::size_type j = 0; j < rectList.size(); ++j) { -			const Common::Rect *innerRect = rectList[j]; -			if (innerRect->contains(r)) { -				temp.erase_at(i); +		const RectList::size_type eraseCount = eraseList.size(); +		for (RectList::size_type j = 0; j < eraseCount; ++j) { +			const Common::Rect &eraseRect = *eraseList[j]; +			if (eraseRect.contains(r)) { +				mergeList.erase_at(i);  				break;  			} -			Common::Rect out[4]; -			const int count = splitRects(r, *innerRect, out); -			if (count != -1) { -				for (int k = count - 1; k >= 0; --k) { -					temp.add(out[k]); +			Common::Rect outRects[4]; +			int splitCount = splitRects(r, eraseRect, outRects); +			if (splitCount != -1) { +				while (splitCount--) { +					mergeList.add(outRects[splitCount]);  				} -				temp.erase_at(i); +				mergeList.erase_at(i);  				// proceed to the next rect -				r = *temp[++i]; +				r = *mergeList[++i];  			}  		}  	} -	temp.pack(); +	mergeList.pack(); -	for (RectList::size_type i = 0; i < temp.size(); ++i) { -		rectList.add(*temp[i]); +	for (RectList::size_type i = 0; i < mergeList.size(); ++i) { +		eraseList.add(*mergeList[i]);  	}  }  void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) { -	for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) { -		if (*screenItemPtr != nullptr) { -			ScreenItem &screenItem = **screenItemPtr; -			if (!screenItem._deleted) { -				screenItem.calcRects(*this); -				if (!screenItem._screenRect.isEmpty()) { -					drawList.add(&screenItem, screenItem._screenRect); -				} +	const ScreenItemList::size_type screenItemCount = _screenItemList.size(); +	for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) { +		ScreenItem *screenItem = _screenItemList[i]; +		if (screenItem != nullptr && !screenItem->_deleted) { +			screenItem->calcRects(*this); +			if (!screenItem->_screenRect.isEmpty()) { +				mergeToDrawList(i, screenItem->_screenRect, drawList);  			}  		}  	} @@ -696,21 +735,27 @@ void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList  	breakDrawListByPlanes(drawList, planeList);  	--_redrawAllCount;  	decrementScreenItemArrayCounts(visiblePlane, true); -	_screenItemList.pack(); -	if (visiblePlane != nullptr) { -		visiblePlane->_screenItemList.pack(); -	}  }  void Plane::setType() { -	if (_pictureId == kPlanePicOpaque) { -		_type = kPlaneTypeOpaque; -	} else if (_pictureId == kPlanePicTransparent) { -		_type = kPlaneTypeTransparent; -	} else if (_pictureId == kPlanePicColored) { +	switch (_pictureId) { +	case kPlanePicColored:  		_type = kPlaneTypeColored; -	} else { -		_type = kPlaneTypePicture; +		break; +	case kPlanePicTransparent: +		_type = kPlaneTypeTransparent; +		break; +	case kPlanePicOpaque: +		_type = kPlaneTypeOpaque; +		break; +	case kPlanePicTransparentPicture: +		_type = kPlaneTypeTransparentPicture; +		break; +	default: +		if (_type != kPlaneTypeTransparentPicture) { +			_type = kPlaneTypePicture; +		} +		break;  	}  } @@ -731,10 +776,12 @@ void Plane::sync(const Plane *other, const Common::Rect &screenRect) {  			_planeRect.right > other->_planeRect.right ||  			_planeRect.bottom > other->_planeRect.bottom  		) { +			// the plane moved or got larger  			_redrawAllCount = g_sci->_gfxFrameout->getScreenCount(); -			_updated = g_sci->_gfxFrameout->getScreenCount(); +			_moved = g_sci->_gfxFrameout->getScreenCount();  		} else if (_planeRect != other->_planeRect) { -			_updated = g_sci->_gfxFrameout->getScreenCount(); +			// the plane got smaller +			_moved = g_sci->_gfxFrameout->getScreenCount();  		}  		if (_priority != other->_priority) { @@ -755,7 +802,7 @@ void Plane::sync(const Plane *other, const Common::Rect &screenRect) {  	_deleted = 0;  	if (_created == 0) { -		_moved = g_sci->_gfxFrameout->getScreenCount(); +		_updated = g_sci->_gfxFrameout->getScreenCount();  	}  	convertGameRectToPlaneRect(); @@ -801,18 +848,22 @@ void Plane::scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool  }  void Plane::remapMarkRedraw() { -	for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) { -		if (*screenItemPtr != nullptr) { -			ScreenItem &screenItem = **screenItemPtr; -			if (screenItem.getCelObj()._remap && !screenItem._deleted && !screenItem._created) { -				screenItem._updated = g_sci->_gfxFrameout->getScreenCount(); -			} +	ScreenItemList::size_type screenItemCount = _screenItemList.size(); +	for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) { +		ScreenItem *screenItem = _screenItemList[i]; +		if ( +			screenItem != nullptr && +			!screenItem->_deleted && !screenItem->_created && +			screenItem->getCelObj()._remap +		) { +			screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();  		}  	}  }  #pragma mark -  #pragma mark PlaneList +  void PlaneList::add(Plane *plane) {  	for (iterator it = begin(); it != end(); ++it) {  		if ((*it)->_priority > plane->_priority) { diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h index c93fb5b64e..acd535e75a 100644 --- a/engines/sci/graphics/plane32.h +++ b/engines/sci/graphics/plane32.h @@ -32,19 +32,21 @@  namespace Sci {  enum PlaneType { -	kPlaneTypeColored     = 0, -	kPlaneTypePicture     = 1, -	kPlaneTypeTransparent = 2, -	kPlaneTypeOpaque      = 3 +	kPlaneTypeColored            = 0, +	kPlaneTypePicture            = 1, +	kPlaneTypeTransparent        = 2, +	kPlaneTypeOpaque             = 3, +	kPlaneTypeTransparentPicture = 4  };  enum PlanePictureCodes { -	// NOTE: Any value at or below 65532 means the plane +	// NOTE: Any value at or below 65531 means the plane  	// is a kPlaneTypePicture. -	kPlanePic            = 65532, -	kPlanePicOpaque      = 65533, -	kPlanePicTransparent = 65534, -	kPlanePicColored     = 65535 +	kPlanePic                   = 65531, +	kPlanePicTransparentPicture = 65532, +	kPlanePicOpaque             = 65533, +	kPlanePicTransparent        = 65534, +	kPlanePicColored            = 65535  };  #pragma mark - @@ -62,7 +64,14 @@ public:  #pragma mark DrawList  struct DrawItem { +	/** +	 * The screen item to draw. +	 */  	ScreenItem *screenItem; + +	/** +	 * The target rectangle of the draw operation. +	 */  	Common::Rect rect;  	inline bool operator<(const DrawItem &other) const { @@ -189,16 +198,15 @@ public:  	 *   not match  	 * - `deleted` is set when the plane is deleted by a  	 *   kernel call -	 * - `moved` is set when the plane is synchronised from -	 *   another plane and is not already in the "created" -	 *   state +	 * - `moved` is set when the plane has been moved or +	 *   resized  	 */  	int _created, _updated, _deleted, _moved;  	/**  	 * The vanishing point for the plane. Used when -	 * calculating the correct scaling of the plane's screen -	 * items according to their position. +	 * automatically calculating the correct scaling of the +	 * plane's screen items according to their position.  	 */  	Common::Point _vanishingPoint; @@ -358,42 +366,33 @@ public:  private:  	/**  	 * Splits all rects in the given draw list at the edges -	 * of all non-transparent planes above the current -	 * plane. +	 * of all higher-priority, non-transparent, intersecting +	 * planes.  	 */  	void breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const;  	/** -	 * Splits all rects in the given erase list rects at the -	 * edges of all non-transparent planes above the current -	 * plane. +	 * Splits all rects in the given erase list at the +	 * edges of higher-priority, non-transparent, +	 * intersecting planes.  	 */  	void breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const;  	/** -	 * Synchronises changes to screen items from the current -	 * plane to the visible plane and deletes screen items -	 * from the current plane that have been marked as -	 * deleted. If `forceUpdate` is true, all screen items -	 * on the visible plane will be updated, even if they -	 * are not marked as having changed. -	 */ -	void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate); - -	/** -	 * Merges the screen item from this plane at the given -	 * index into the given draw list, clipped to the given -	 * rect. TODO: Finish documenting +	 * Adds the screen item at `index` into `drawList`, +	 * ensuring it is only drawn within the bounds of +	 * `rect`. If an existing draw list entry exists +	 * for this screen item, it will be modified. +	 * Otherwise, a new entry will be added.  	 */  	void mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const;  	/** -	 * Adds the given rect into the given rect list, -	 * merging it with other rects already inside the list, -	 * if possible, to avoid overdraw. TODO: Finish -	 * documenting +	 * Merges `rect` with an existing rect in `eraseList`, +	 * if possible. Otherwise, adds the rect as a new entry +	 * to `eraseList`.  	 */ -	void mergeToRectList(const Common::Rect &rect, RectList &rectList) const; +	void mergeToRectList(const Common::Rect &rect, RectList &eraseList) const;  public:  	/** @@ -406,19 +405,73 @@ public:  	void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);  	/** -	 * TODO: Documentation +	 * Synchronises changes to screen items from the current +	 * plane to the visible plane and deletes screen items +	 * from the current plane that have been marked as +	 * deleted. If `forceUpdate` is true, all screen items +	 * on the visible plane will be updated, even if they +	 * are not marked as having changed. +	 */ +	void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate); + +	/** +	 * This method is called from the highest priority plane +	 * to the lowest priority plane. +	 * +	 * Adds screen items from this plane to the draw list +	 * that must be redrawn because they intersect entries +	 * in the `higherEraseList`. +	 * +	 * If this plane is opaque, all intersecting erase rects +	 * in `lowerEraseList` are removed, as they would be +	 * completely overwritten by the contents of this plane. +	 * +	 * If this plane is transparent, erase rects from the +	 * `lowerEraseList` are added to the erase list for this +	 * plane, so that lower planes. +	 * +	 * @param drawList The draw list for this plane. +	 * @param eraseList The erase list for this plane. +	 * @param higherEraseList The erase list for a plane +	 * above this plane.  	 */ -	void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const; +	void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const;  	/** -	 * TODO: Documentation +	 * This method is called from the lowest priority plane +	 * to the highest priority plane. +	 * +	 * Adds screen items from this plane to the draw list +	 * that must be drawn because the lower plane is being +	 * redrawn and potentially transparent screen items +	 * from this plane would draw over the lower priority +	 * plane's screen items. +	 * +	 * This method applies only to transparent planes. +	 * +	 * @param drawList The draw list for this plane. +	 * @param eraseList The erase list for a plane below +	 * this plane.  	 */ -	void filterUpEraseRects(DrawList &drawList, RectList &eraseList) const; +	void filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const;  	/** -	 * TODO: Documentation +	 * This method is called from the lowest priority plane +	 * to the highest priority plane. +	 * +	 * Adds screen items from this plane to the draw list +	 * that must be drawn because the lower plane is being +	 * redrawn and potentially transparent screen items +	 * from this plane would draw over the lower priority +	 * plane's screen items. +	 * +	 * This method applies only to transparent planes. +	 * +	 * @param drawList The draw list for this plane. +	 * @param lowerDrawList The draw list for a plane below +	 * this plane.  	 */ -	void filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const; +	void filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const;  	/**  	 * Updates all of the plane's non-deleted screen items @@ -442,6 +495,8 @@ private:  	using PlaneListBase::push_back;  public: +	typedef int size_type; +  	// A method for finding the index of a plane inside a  	// PlaneList is used because entries in the main plane  	// list and visible plane list of GfxFrameout are diff --git a/engines/sci/graphics/remap.cpp b/engines/sci/graphics/remap.cpp index ff49e52f13..2abf03ea29 100644 --- a/engines/sci/graphics/remap.cpp +++ b/engines/sci/graphics/remap.cpp @@ -21,31 +21,23 @@   */  #include "sci/sci.h" -#include "sci/resource.h"  #include "sci/graphics/palette.h" -#include "sci/graphics/palette32.h"  #include "sci/graphics/remap.h"  #include "sci/graphics/screen.h"  namespace Sci { -#pragma mark - -#pragma mark SCI16 remapping (QFG4 demo) -  GfxRemap::GfxRemap(GfxPalette *palette)  	: _palette(palette) {  	_remapOn = false;  	resetRemapping();  } -GfxRemap::~GfxRemap() { -} -  byte GfxRemap::remapColor(byte remappedColor, byte screenColor) {  	assert(_remapOn); -	if (_remappingType[remappedColor] == kRemappingByRange) +	if (_remappingType[remappedColor] == kRemapByRange)  		return _remappingByRange[screenColor]; -	else if (_remappingType[remappedColor] == kRemappingByPercent) +	else if (_remappingType[remappedColor] == kRemapByPercent)  		return _remappingByPercent[screenColor];  	else  		error("remapColor(): Color %d isn't remapped", remappedColor); @@ -58,7 +50,7 @@ void GfxRemap::resetRemapping() {  	_remappingPercentToSet = 0;  	for (int i = 0; i < 256; i++) { -		_remappingType[i] = kRemappingNone; +		_remappingType[i] = kRemapNone;  		_remappingByPercent[i] = i;  		_remappingByRange[i] = i;  	} @@ -80,7 +72,7 @@ void GfxRemap::setRemappingPercent(byte color, byte percent) {  		_remappingByPercent[i] = _palette->kernelFindColor(r, g, b);  	} -	_remappingType[color] = kRemappingByPercent; +	_remappingType[color] = kRemapByPercent;  }  void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) { @@ -90,7 +82,7 @@ void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) {  		_remappingByRange[i] = i + base;  	} -	_remappingType[color] = kRemappingByRange; +	_remappingType[color] = kRemapByRange;  }  void GfxRemap::updateRemapping() { @@ -104,277 +96,4 @@ void GfxRemap::updateRemapping() {  		}  	}  } - -#pragma mark - -#pragma mark SCI32 remapping - -#ifdef ENABLE_SCI32 - -GfxRemap32::GfxRemap32(GfxPalette32 *palette) : _palette(palette) { -	for (int i = 0; i < REMAP_COLOR_COUNT; i++) -		_remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone); -	_noMapStart = _noMapCount = 0; -	_update = false; -	_remapCount = 0; - -	// The remap range was 245 - 254 in SCI2, but was changed to 235 - 244 in SCI21 middle. -	// All versions of KQ7 are using the older remap range semantics. -	_remapEndColor = (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) ? 244 : 254; -} - -void GfxRemap32::remapOff(byte color) { -	if (!color) { -		for (int i = 0; i < REMAP_COLOR_COUNT; i++) -			_remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone); - -		_remapCount = 0; -	} else { -		assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); -		const byte index = _remapEndColor - color; -		_remaps[index] = RemapParams(0, 0, 0, 0, 100, kRemappingNone); -		_remapCount--; -	} - -	_update = true; -} - -void GfxRemap32::setRemappingRange(byte color, byte from, byte to, byte base) { -	assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); -	_remaps[_remapEndColor - color] = RemapParams(from, to, base, 0, 100, kRemappingByRange); -	initColorArrays(_remapEndColor - color); -	_remapCount++; -	_update = true; -} - -void GfxRemap32::setRemappingPercent(byte color, byte percent) { -	assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); -	_remaps[_remapEndColor - color] = RemapParams(0, 0, 0, 0, percent, kRemappingByPercent); -	initColorArrays(_remapEndColor - color); -	_remapCount++; -	_update = true; -} - -void GfxRemap32::setRemappingToGray(byte color, byte gray) { -	assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); -	_remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, 100, kRemappingToGray); -	initColorArrays(_remapEndColor - color); -	_remapCount++; -	_update = true; -} - -void GfxRemap32::setRemappingToPercentGray(byte color, byte gray, byte percent) { -	assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); -	_remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, percent, kRemappingToPercentGray); -	initColorArrays(_remapEndColor - color); -	_remapCount++; -	_update = true; -} - -void GfxRemap32::setNoMatchRange(byte from, byte count) { -	_noMapStart = from; -	_noMapCount = count; -} - -bool GfxRemap32::remapEnabled(byte color) const { -	assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); -	const byte index = _remapEndColor - color; -	return (_remaps[index].type != kRemappingNone); -} - -byte GfxRemap32::remapColor(byte color, byte target) { -	assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); -	const byte index = _remapEndColor - color; -	if (_remaps[index].type != kRemappingNone) -		return _remaps[index].remap[target]; -	else -		return target; -} - -void GfxRemap32::initColorArrays(byte index) { -	Palette *curPalette = &_palette->_sysPalette; -	RemapParams *curRemap = &_remaps[index]; - -	memcpy(curRemap->curColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color)); -	memcpy(curRemap->targetColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color)); -} - -bool GfxRemap32::updateRemap(byte index, bool palChanged) { -	int result; -	RemapParams *curRemap = &_remaps[index]; -	const Palette *curPalette = &_palette->_sysPalette; -	const Palette *nextPalette = _palette->getNextPalette(); -	bool changed = false; - -	if (!_update && !palChanged) -		return false; - -	Common::fill(_targetChanged, _targetChanged + NON_REMAPPED_COLOR_COUNT, false); - -	switch (curRemap->type) { -	case kRemappingNone: -		return false; -	case kRemappingByRange: -		for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++)  { -			if (curRemap->from <= i && i <= curRemap->to) -				result = i + curRemap->base; -			else -				result = i; - -			if (curRemap->remap[i] != result) { -				changed = true; -				curRemap->remap[i] = result; -			} - -			curRemap->colorChanged[i] = true; -		} -		return changed; -	case kRemappingByPercent: -		for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { -			// NOTE: This method uses nextPalette instead of curPalette -			Color color = nextPalette->colors[i]; - -			if (curRemap->curColor[i] != color) { -				curRemap->colorChanged[i] = true; -				curRemap->curColor[i] = color; -			} - -			if (curRemap->percent != curRemap->oldPercent || curRemap->colorChanged[i])  { -				byte red = CLIP<byte>(color.r * curRemap->percent / 100, 0, 255); -				byte green = CLIP<byte>(color.g * curRemap->percent / 100, 0, 255); -				byte blue = CLIP<byte>(color.b * curRemap->percent / 100, 0, 255); -				byte used = curRemap->targetColor[i].used; - -				Color newColor = { used, red, green, blue }; -				if (curRemap->targetColor[i] != newColor)  { -					_targetChanged[i] = true; -					curRemap->targetColor[i] = newColor; -				} -			} -		} -		 -		changed = applyRemap(index); -		Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); -		curRemap->oldPercent = curRemap->percent; -		return changed; -	case kRemappingToGray: -		for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { -			Color color = curPalette->colors[i]; - -			if (curRemap->curColor[i] != color) { -				curRemap->colorChanged[i] = true; -				curRemap->curColor[i] = color; -			} - -			if (curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i])  { -				byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8; -				byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255); -				byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255); -				byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255); -				byte used = curRemap->targetColor[i].used; - -				Color newColor = { used, red, green, blue }; -				if (curRemap->targetColor[i] != newColor)  { -					_targetChanged[i] = true; -					curRemap->targetColor[i] = newColor; -				} -			} -		} - -		changed = applyRemap(index); -		Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); -		curRemap->oldGray = curRemap->gray; -		return changed; -	case kRemappingToPercentGray: -		for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { -			Color color = curPalette->colors[i]; - -			if (curRemap->curColor[i] != color) { -				curRemap->colorChanged[i] = true; -				curRemap->curColor[i] = color; -			} - -			if (curRemap->percent != curRemap->oldPercent || curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i])  { -				byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8; -				lumosity = lumosity * curRemap->percent / 100; -				byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255); -				byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255); -				byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255); -				byte used = curRemap->targetColor[i].used; - -				Color newColor = { used, red, green, blue }; -				if (curRemap->targetColor[i] != newColor)  { -					_targetChanged[i] = true; -					curRemap->targetColor[i] = newColor; -				} -			} -		} - -		changed = applyRemap(index); -		Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); -		curRemap->oldPercent = curRemap->percent; -		curRemap->oldGray = curRemap->gray; -		return changed; -	default: -		return false; -	} -} - -static int colorDistance(Color a, Color b) { -	int rDiff = (a.r - b.r) * (a.r - b.r); -	int gDiff = (a.g - b.g) * (a.g - b.g); -	int bDiff = (a.b - b.b) * (a.b - b.b); -	return rDiff + gDiff + bDiff; -} - -bool GfxRemap32::applyRemap(byte index) { -	RemapParams *curRemap = &_remaps[index]; -	const bool *cycleMap = _palette->getCyclemap(); -	bool unmappedColors[NON_REMAPPED_COLOR_COUNT]; -	bool changed = false; - -	Common::fill(unmappedColors, unmappedColors + NON_REMAPPED_COLOR_COUNT, false); -	if (_noMapCount) -		Common::fill(unmappedColors + _noMapStart, unmappedColors + _noMapStart + _noMapCount, true); - -	for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++)  { -		if (cycleMap[i]) -			unmappedColors[i] = true; -	} - -	for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++)  { -		Color targetColor = curRemap->targetColor[i]; -		bool colorChanged = curRemap->colorChanged[curRemap->remap[i]]; - -		if (!_targetChanged[i] && !colorChanged) -			continue; - -		if (_targetChanged[i] && colorChanged) -			if (curRemap->distance[i] < 100 && colorDistance(targetColor, curRemap->curColor[curRemap->remap[i]]) <= curRemap->distance[i]) -				continue; - -		int diff = 0; -		int16 result = _palette->matchColor(targetColor.r, targetColor.g, targetColor.b, curRemap->distance[i], diff, unmappedColors); -		if (result != -1 && curRemap->remap[i] != result)  { -			changed = true; -			curRemap->remap[i] = result; -			curRemap->distance[i] = diff; -		} -	} - -	return changed; -} - -bool GfxRemap32::remapAllTables(bool palChanged) { -	bool changed = false; - -	for (int i = 0; i < REMAP_COLOR_COUNT; i++) { -		changed |= updateRemap(i, palChanged); -	} - -	_update = false; -	return changed; -} - -#endif -  } // End of namespace Sci diff --git a/engines/sci/graphics/remap.h b/engines/sci/graphics/remap.h index d012568f7f..98177f6d19 100644 --- a/engines/sci/graphics/remap.h +++ b/engines/sci/graphics/remap.h @@ -24,42 +24,36 @@  #define SCI_GRAPHICS_REMAP_H  #include "common/array.h" -#include "sci/graphics/helpers.h" +#include "common/serializer.h"  namespace Sci {  class GfxScreen; -enum ColorRemappingType { -	kRemappingNone = 0, -	kRemappingByRange = 1, -	kRemappingByPercent = 2, -	kRemappingToGray = 3, -	kRemappingToPercentGray = 4 -}; - -#define REMAP_COLOR_COUNT 9 -#define NON_REMAPPED_COLOR_COUNT 236 -  /** - * Remap class, handles color remapping + * This class handles color remapping for the QFG4 demo.   */  class GfxRemap { +private: +	enum ColorRemappingType { +		kRemapNone = 0, +		kRemapByRange = 1, +		kRemapByPercent = 2 +	}; +  public:  	GfxRemap(GfxPalette *_palette); -	~GfxRemap();  	void resetRemapping();  	void setRemappingPercent(byte color, byte percent);  	void setRemappingRange(byte color, byte from, byte to, byte base);  	bool isRemapped(byte color) const { -		return _remapOn && (_remappingType[color] != kRemappingNone); +		return _remapOn && (_remappingType[color] != kRemapNone);  	}  	byte remapColor(byte remappedColor, byte screenColor);  	void updateRemapping();  private: -	GfxScreen *_screen;  	GfxPalette *_palette;  	bool _remapOn; @@ -68,87 +62,6 @@ private:  	byte _remappingByRange[256];  	uint16 _remappingPercentToSet;  }; - -#ifdef ENABLE_SCI32 - -struct RemapParams { -	byte from; -	byte to; -	byte base; -	byte gray; -	byte oldGray; -	byte percent; -	byte oldPercent; -	ColorRemappingType type; -	Color curColor[256]; -	Color targetColor[256]; -	byte distance[256]; -	byte remap[256]; -	bool colorChanged[256]; - -	RemapParams() { -		from = to = base = gray = oldGray = percent = oldPercent = 0; -		type = kRemappingNone; - -		// curColor and targetColor are initialized in GfxRemap32::initColorArrays -		memset(curColor, 0, 256 * sizeof(Color)); -		memset(targetColor, 0, 256 * sizeof(Color)); -		memset(distance, 0, 256); -		for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) -			remap[i] = i; -		Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true); -	} - -	RemapParams(byte from_, byte to_, byte base_, byte gray_, byte percent_, ColorRemappingType type_) { -		from = from_; -		to = to_; -		base = base_; -		gray = oldGray = gray_; -		percent = oldPercent = percent_; -		type = type_; - -		// curColor and targetColor are initialized in GfxRemap32::initColorArrays -		memset(curColor, 0, 256 * sizeof(Color)); -		memset(targetColor, 0, 256 * sizeof(Color)); -		memset(distance, 0, 256); -		for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) -			remap[i] = i; -		Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true); -	} -}; - -class GfxRemap32 { -public: -	GfxRemap32(GfxPalette32 *palette); -	~GfxRemap32() {} - -	void remapOff(byte color); -	void setRemappingRange(byte color, byte from, byte to, byte base); -	void setRemappingPercent(byte color, byte percent); -	void setRemappingToGray(byte color, byte gray); -	void setRemappingToPercentGray(byte color, byte gray, byte percent); -	void setNoMatchRange(byte from, byte count); -	bool remapEnabled(byte color) const; -	byte remapColor(byte color, byte target); -	bool remapAllTables(bool palChanged); -	int getRemapCount() const { return _remapCount; } -	int getStartColor() const { return _remapEndColor - REMAP_COLOR_COUNT + 1; } -	int getEndColor() const { return _remapEndColor; } -private: -	GfxPalette32 *_palette; -	RemapParams _remaps[REMAP_COLOR_COUNT]; -	bool _update; -	byte _noMapStart, _noMapCount; -	bool _targetChanged[NON_REMAPPED_COLOR_COUNT]; -	byte _remapEndColor; -	int _remapCount; - -	void initColorArrays(byte index); -	bool applyRemap(byte index); -	bool updateRemap(byte index, bool palChanged); -}; -#endif -  } // End of namespace Sci  #endif diff --git a/engines/sci/graphics/remap32.cpp b/engines/sci/graphics/remap32.cpp new file mode 100644 index 0000000000..d5a2362f14 --- /dev/null +++ b/engines/sci/graphics/remap32.cpp @@ -0,0 +1,468 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "sci/sci.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/remap32.h" + +namespace Sci { + +#pragma mark SingleRemap + +void SingleRemap::reset() { +	_lastPercent = 100; +	_lastGray = 0; + +	const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); +	const Palette ¤tPalette = g_sci->_gfxPalette32->getCurrentPalette(); +	for (uint i = 0; i < remapStartColor; ++i) { +		const Color &color = currentPalette.colors[i]; +		_remapColors[i] = i; +		_originalColors[i] = color; +		_originalColorsChanged[i] = true; +		_idealColors[i] = color; +		_idealColorsChanged[i] = false; +		_matchDistances[i] = 0; +	} +} + +bool SingleRemap::update() { +	switch (_type) { +	case kRemapNone: +		break; +	case kRemapByRange: +		return updateRange(); +	case kRemapByPercent: +		return updateBrightness(); +	case kRemapToGray: +		return updateSaturation(); +	case kRemapToPercentGray: +		return updateSaturationAndBrightness(); +	default: +		error("Illegal remap type %d", _type); +	} + +	return false; +} + +bool SingleRemap::updateRange() { +	const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); +	bool updated = false; + +	for (uint i = 0; i < remapStartColor; ++i) { +		uint8 targetColor; +		if (_from <= i && i <= _to) { +			targetColor = i + _delta; +		} else { +			targetColor = i; +		} + +		if (_remapColors[i] != targetColor) { +			updated = true; +			_remapColors[i] = targetColor; +		} + +		_originalColorsChanged[i] = true; +	} + +	return updated; +} + +bool SingleRemap::updateBrightness() { +	const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); +	const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette(); +	for (uint i = 1; i < remapStartColor; ++i) { +		Color color(nextPalette.colors[i]); + +		if (_originalColors[i] != color) { +			_originalColorsChanged[i] = true; +			_originalColors[i] = color; +		} + +		if (_percent != _lastPercent || _originalColorsChanged[i]) { +			// NOTE: SSCI checked if percent was over 100 and only +			// then clipped values, but we always unconditionally +			// ensure the result is in the correct range +			color.r = MIN(255, (uint16)color.r * _percent / 100); +			color.g = MIN(255, (uint16)color.g * _percent / 100); +			color.b = MIN(255, (uint16)color.b * _percent / 100); + +			if (_idealColors[i] != color) { +				_idealColorsChanged[i] = true; +				_idealColors[i] = color; +			} +		} +	} + +	const bool updated = apply(); +	Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); +	Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); +	_lastPercent = _percent; +	return updated; +} + +bool SingleRemap::updateSaturation() { +	const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); +	const Palette ¤tPalette = g_sci->_gfxPalette32->getCurrentPalette(); +	for (uint i = 1; i < remapStartColor; ++i) { +		Color color(currentPalette.colors[i]); +		if (_originalColors[i] != color) { +			_originalColorsChanged[i] = true; +			_originalColors[i] = color; +		} + +		if (_gray != _lastGray || _originalColorsChanged[i]) { +			const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100; + +			color.r = MIN(255, color.r - ((color.r - luminosity) * _gray / 100)); +			color.g = MIN(255, color.g - ((color.g - luminosity) * _gray / 100)); +			color.b = MIN(255, color.b - ((color.b - luminosity) * _gray / 100)); + +			if (_idealColors[i] != color) { +				_idealColorsChanged[i] = true; +				_idealColors[i] = color; +			} +		} +	} + +	const bool updated = apply(); +	Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); +	Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); +	_lastGray = _gray; +	return updated; +} + +bool SingleRemap::updateSaturationAndBrightness() { +	const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); +	const Palette ¤tPalette = g_sci->_gfxPalette32->getCurrentPalette(); +	for (uint i = 1; i < remapStartColor; i++) { +		Color color(currentPalette.colors[i]); +		if (_originalColors[i] != color) { +			_originalColorsChanged[i] = true; +			_originalColors[i] = color; +		} + +		if (_percent != _lastPercent || _gray != _lastGray || _originalColorsChanged[i]) { +			const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100; + +			color.r = MIN(255, color.r - ((color.r - luminosity) * _gray) / 100); +			color.g = MIN(255, color.g - ((color.g - luminosity) * _gray) / 100); +			color.b = MIN(255, color.b - ((color.b - luminosity) * _gray) / 100); + +			if (_idealColors[i] != color) { +				_idealColorsChanged[i] = true; +				_idealColors[i] = color; +			} +		} +	} + +	const bool updated = apply(); +	Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); +	Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); +	_lastPercent = _percent; +	_lastGray = _gray; +	return updated; +} + +bool SingleRemap::apply() { +	const GfxRemap32 *const gfxRemap32 = g_sci->_gfxRemap32; +	const uint8 remapStartColor = gfxRemap32->getStartColor(); + +	// Blocked colors are not allowed to be used as target +	// colors for the remap +	bool blockedColors[236]; +	Common::fill(blockedColors, blockedColors + remapStartColor, false); + +	const bool *const paletteCycleMap = g_sci->_gfxPalette32->getCycleMap(); + +	const int16 blockedRangeCount = gfxRemap32->getBlockedRangeCount(); +	if (blockedRangeCount) { +		const uint8 blockedRangeStart = gfxRemap32->getBlockedRangeStart(); +		Common::fill(blockedColors + blockedRangeStart, blockedColors + blockedRangeStart + blockedRangeCount, true); +	} + +	for (uint i = 0; i < remapStartColor; ++i) { +		if (paletteCycleMap[i]) { +			blockedColors[i] = true; +		} +	} + +	// NOTE: SSCI did a loop over colors here to create a +	// new array of updated, unblocked colors, but then +	// never used it + +	bool updated = false; +	for (uint i = 1; i < remapStartColor; ++i) { +		int distance; + +		if (!_idealColorsChanged[i] && !_originalColorsChanged[_remapColors[i]]) { +			continue; +		} + +		if ( +			_idealColorsChanged[i] && +			_originalColorsChanged[_remapColors[i]] && +			_matchDistances[i] < 100 && +			colorDistance(_idealColors[i], _originalColors[_remapColors[i]]) <= _matchDistances[i] +		) { +			continue; +		} + +		const int16 bestColor = matchColor(_idealColors[i], _matchDistances[i], distance, blockedColors); + +		if (bestColor != -1 && _remapColors[i] != bestColor) { +			updated = true; +			_remapColors[i] = bestColor; +			_matchDistances[i] = distance; +		} +	} + +	return updated; +} + +int SingleRemap::colorDistance(const Color &a, const Color &b) const { +	int channelDistance = a.r - b.r; +	int distance = channelDistance * channelDistance; +	channelDistance = a.g - b.g; +	distance += channelDistance * channelDistance; +	channelDistance = a.b - b.b; +	distance += channelDistance * channelDistance; +	return distance; +} + +int16 SingleRemap::matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const { +	int16 bestIndex = -1; +	int bestDistance = 0xFFFFF; +	int distance = minimumDistance; +	const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette(); + +	for (uint i = 0, channelDistance; i < g_sci->_gfxRemap32->getStartColor(); ++i) { +		if (blockedIndexes[i]) { +			continue; +		} + +		distance = nextPalette.colors[i].r - color.r; +		distance *= distance; +		if (bestDistance <= distance) { +			continue; +		} +		channelDistance = nextPalette.colors[i].g - color.g; +		distance += channelDistance * channelDistance; +		if (bestDistance <= distance) { +			continue; +		} +		channelDistance = nextPalette.colors[i].b - color.b; +		distance += channelDistance * channelDistance; +		if (bestDistance <= distance) { +			continue; +		} +		bestDistance = distance; +		bestIndex = i; +	} + +	// This value is only valid if the last index to +	// perform a distance calculation was the best index +	outDistance = distance; +	return bestIndex; +} + +#pragma mark - +#pragma mark GfxRemap32 + +GfxRemap32::GfxRemap32() : +	_needsUpdate(false), +	_blockedRangeStart(0), +	_blockedRangeCount(0), +	_remapStartColor(236), +	_numActiveRemaps(0) { +	// The `_remapStartColor` seems to always be 236 in SSCI, +	// but if it is ever changed then the various C-style +	// member arrays hard-coded to 236 need to be changed to +	// match the highest possible value of `_remapStartColor` +	assert(_remapStartColor == 236); + +	if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) { +		_remaps.resize(9); +	} else { +		_remaps.resize(19); +	} + +	_remapEndColor = _remapStartColor + _remaps.size() - 1; +} + +void GfxRemap32::remapOff(const uint8 color) { +	if (color == 0) { +		remapAllOff(); +		return; +	} + +	// NOTE: SSCI simply ignored invalid input values, but +	// we at least give a warning so games can be investigated +	// for script bugs +	if (color < _remapStartColor || color > _remapEndColor) { +		warning("GfxRemap32::remapOff: %d out of remap range", color); +		return; +	} + +	const uint8 index = _remapEndColor - color; +	SingleRemap &singleRemap = _remaps[index]; +	singleRemap._type = kRemapNone; +	--_numActiveRemaps; +	_needsUpdate = true; +} + +void GfxRemap32::remapAllOff() { +	for (uint i = 0, len = _remaps.size(); i < len; ++i) { +		_remaps[i]._type = kRemapNone; +	} + +	_numActiveRemaps = 0; +	_needsUpdate = true; +} + +void GfxRemap32::remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta) { +	// NOTE: SSCI simply ignored invalid input values, but +	// we at least give a warning so games can be investigated +	// for script bugs +	if (color < _remapStartColor || color > _remapEndColor) { +		warning("GfxRemap32::remapByRange: %d out of remap range", color); +		return; +	} + +	if (from < 0) { +		warning("GfxRemap32::remapByRange: attempt to remap negative color %d", from); +		return; +	} + +	if (to >= _remapStartColor) { +		warning("GfxRemap32::remapByRange: attempt to remap into the remap zone at %d", to); +		return; +	} + +	const uint8 index = _remapEndColor - color; +	SingleRemap &singleRemap = _remaps[index]; + +	if (singleRemap._type == kRemapNone) { +		++_numActiveRemaps; +		singleRemap.reset(); +	} + +	singleRemap._from = from; +	singleRemap._to = to; +	singleRemap._delta = delta; +	singleRemap._type = kRemapByRange; +	_needsUpdate = true; +} + +void GfxRemap32::remapByPercent(const uint8 color, const int16 percent) { +	// NOTE: SSCI simply ignored invalid input values, but +	// we at least give a warning so games can be investigated +	// for script bugs +	if (color < _remapStartColor || color > _remapEndColor) { +		warning("GfxRemap32::remapByPercent: %d out of remap range", color); +		return; +	} + +	const uint8 index = _remapEndColor - color; +	SingleRemap &singleRemap = _remaps[index]; + +	if (singleRemap._type == kRemapNone) { +		++_numActiveRemaps; +		singleRemap.reset(); +	} + +	singleRemap._percent = percent; +	singleRemap._type = kRemapByPercent; +	_needsUpdate = true; +} + +void GfxRemap32::remapToGray(const uint8 color, const int8 gray) { +	// NOTE: SSCI simply ignored invalid input values, but +	// we at least give a warning so games can be investigated +	// for script bugs +	if (color < _remapStartColor || color > _remapEndColor) { +		warning("GfxRemap32::remapToGray: %d out of remap range", color); +		return; +	} + +	if (gray < 0 || gray > 100) { +		error("RemapToGray percent out of range; gray = %d", gray); +	} + +	const uint8 index = _remapEndColor - color; +	SingleRemap &singleRemap = _remaps[index]; + +	if (singleRemap._type == kRemapNone) { +		++_numActiveRemaps; +		singleRemap.reset(); +	} + +	singleRemap._gray = gray; +	singleRemap._type = kRemapToGray; +	_needsUpdate = true; +} + +void GfxRemap32::remapToPercentGray(const uint8 color, const int16 gray, const int16 percent) { +	// NOTE: SSCI simply ignored invalid input values, but +	// we at least give a warning so games can be investigated +	// for script bugs +	if (color < _remapStartColor || color > _remapEndColor) { +		warning("GfxRemap32::remapToPercentGray: %d out of remap range", color); +		return; +	} + +	const uint8 index = _remapEndColor - color; +	SingleRemap &singleRemap = _remaps[index]; + +	if (singleRemap._type == kRemapNone) { +		++_numActiveRemaps; +		singleRemap.reset(); +	} + +	singleRemap._percent = percent; +	singleRemap._gray = gray; +	singleRemap._type = kRemapToPercentGray; +	_needsUpdate = true; +} + +void GfxRemap32::blockRange(const uint8 from, const int16 count) { +	_blockedRangeStart = from; +	_blockedRangeCount = count; +} + +bool GfxRemap32::remapAllTables(const bool paletteUpdated) { +	if (!_needsUpdate && !paletteUpdated) { +		return false; +	} + +	bool updated = false; + +	for (SingleRemapsList::iterator it = _remaps.begin(); it != _remaps.end(); ++it) { +		if (it->_type != kRemapNone) { +			updated |= it->update(); +		} +	} + +	_needsUpdate = false; +	return updated; +} +} // End of namespace Sci diff --git a/engines/sci/graphics/remap32.h b/engines/sci/graphics/remap32.h new file mode 100644 index 0000000000..5f629d733e --- /dev/null +++ b/engines/sci/graphics/remap32.h @@ -0,0 +1,400 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_GRAPHICS_REMAP32_H +#define SCI_GRAPHICS_REMAP32_H + +#include "common/algorithm.h" +#include "common/array.h" +#include "common/scummsys.h" +#include "sci/graphics/helpers.h" + +namespace Sci { +class GfxPalette32; + +enum RemapType { +	kRemapNone = 0, +	kRemapByRange = 1, +	kRemapByPercent = 2, +	kRemapToGray = 3, +	kRemapToPercentGray = 4 +}; + +#pragma mark - +#pragma mark SingleRemap + +/** + * SingleRemap objects each manage one remapping operation. + */ +class SingleRemap { +public: +	SingleRemap() : _type(kRemapNone) {} + +	/** +	 * The type of remap. +	 */ +	RemapType _type; + +	/** +	 * The first color that should be shifted by a range +	 * remap. +	 */ +	uint8 _from; + +	/** +	 * The last color that should be shifted a range remap. +	 */ +	uint8 _to; + +	/** +	 * The direction and amount that the colors should be +	 * shifted in a range remap. +	 */ +	int16 _delta; + +	/** +	 * The difference in brightness that should be +	 * applied by a brightness (percent) remap. +	 * +	 * This value may be be greater than 100, in +	 * which case the color will be oversaturated. +	 */ +	int16 _percent; + +	/** +	 * The amount of desaturation that should be +	 * applied by a saturation (gray) remap, where +	 * 0 is full saturation and 100 is full +	 * desaturation. +	 */ +	uint8 _gray; + +	/** +	 * The final array used by CelObj renderers to composite +	 * remapped pixels to the screen buffer. +	 * +	 * Here is how it works: +	 * +	 * The source bitmap being rendered will have pixels +	 * within the remap range (236-245 or 236-254), and the +	 * target buffer will have colors in the non-remapped +	 * range (0-235). +	 * +	 * To arrive at the correct color, first the source +	 * pixel is used to look up the correct SingleRemap for +	 * that pixel. Then, the final composited color is +	 * looked up in this array using the target's pixel +	 * color. In other words, +	 * `target = _remaps[remapEndColor - source].remapColors[target]`. +	 */ +	uint8 _remapColors[236]; + +	/** +	 * Resets this SingleRemap's color information to +	 * default values. +	 */ +	void reset(); + +	/** +	 * Recalculates and reapplies remap colors to the +	 * `_remapColors` array. +	 */ +	bool update(); + +private: +	/** +	 * The previous brightness value. Used to +	 * determine whether or not targetColors needs +	 * to be updated. +	 */ +	int16 _lastPercent; + +	/** +	 * The previous saturation value. Used to +	 * determine whether or not targetColors needs +	 * to be updated. +	 */ +	uint8 _lastGray; + +	/** +	 * The colors from the current GfxPalette32 palette +	 * before this SingleRemap is applied. +	 */ +	Color _originalColors[236]; + +	/** +	 * Map of colors that changed in `_originalColors` +	 * when this SingleRemap was updated. This map is +	 * transient and gets reset to `false` after the +	 * SingleRemap finishes updating. +	 */ +	bool _originalColorsChanged[236]; + +	/** +	 * The ideal target RGB color values for each generated +	 * remap color. +	 */ +	Color _idealColors[236]; + +	/** +	 * Map of colors that changed in `_idealColors` when +	 * this SingleRemap was updated. This map is transient +	 * and gets reset to `false` after the SingleRemap +	 * finishes applying. +	 */ +	bool _idealColorsChanged[236]; + +	/** +	 * When applying a SingleRemap, finding an appropriate +	 * color in the palette is the responsibility of a +	 * distance function. Once a match is found, the +	 * distance of that match is stored here so that the +	 * next time the SingleRemap is applied, it can check +	 * the distance from the previous application and avoid +	 * triggering an expensive redraw of the entire screen +	 * if the new palette value only changed slightly. +	 */ +	int _matchDistances[236]; + +	/** +	 * Computes the final target values for a range remap +	 * and applies them directly to the `_remaps` map. +	 * +	 * @note Was ByRange in SSCI. +	 */ +	bool updateRange(); + +	/** +	 * Computes the intermediate target values for a +	 * brightness remap and applies them indirectly via +	 * the `apply` method. +	 * +	 * @note Was ByPercent in SSCI. +	 */ +	bool updateBrightness(); + +	/** +	 * Computes the intermediate target values for a +	 * saturation remap and applies them indirectly via +	 * the `apply` method. +	 * +	 * @note Was ToGray in SSCI. +	 */ +	bool updateSaturation(); + +	/** +	 * Computes the intermediate target values for a +	 * saturation + brightness bitmap and applies them +	 * indirectly via the `apply` method. +	 * +	 * @note Was ToPercentGray in SSCI. +	 */ +	bool updateSaturationAndBrightness(); + +	/** +	 * Computes and applies the final values to the +	 * `_remaps` map. +	 * +	 * @note In SSCI, a boolean array of changed values +	 * was passed into this method, but this was done by +	 * creating arrays on the stack in the caller. Instead +	 * of doing this, we simply add another member property +	 * `_idealColorsChanged` and use that instead. +	 */ +	bool apply(); + +	/** +	 * Calculates the square distance of two colors. +	 * +	 * @note In SSCI this method is Rgb24::Dist, but it is +	 * only used by SingleRemap. +	 */ +	int colorDistance(const Color &a, const Color &b) const; + +	/** +	 * Finds the closest index in the next palette matching +	 * the given RGB color. Returns -1 if no match can be +	 * found that is closer than `minimumDistance`. +	 * +	 * @note In SSCI, this method is SOLPalette::Match, but +	 * this particular signature is only used by +	 * SingleRemap. +	 */ +	int16 matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const; +}; + +#pragma mark - +#pragma mark GfxRemap32 + +/** + * This class provides color remapping support for SCI32 + * games. + */ +class GfxRemap32 : public Common::Serializable { +public: +	GfxRemap32(); + +	void saveLoadWithSerializer(Common::Serializer &s); + +	inline uint8 getRemapCount() const { return _numActiveRemaps; } +	inline uint8 getStartColor() const { return _remapStartColor; } +	inline uint8 getEndColor() const { return _remapEndColor; } +	inline uint8 getBlockedRangeStart() const { return _blockedRangeStart; } +	inline int16 getBlockedRangeCount() const { return _blockedRangeCount; } + +	/** +	 * Turns off remapping of the given color. If `color` is +	 * 0, all remaps are turned off. +	 */ +	void remapOff(const uint8 color); + +	/** +	 * Turns off all color remaps. +	 */ +	void remapAllOff(); + +	/** +	 * Configures a SingleRemap for the remap color `color`. +	 * The SingleRemap will shift palette colors between +	 * `from` and `to` (inclusive) by `delta` palette +	 * entries when the remap is applied. +	 */ +	void remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta); + +	/** +	 * Configures a SingleRemap for the remap color `color` +	 * to modify the brightness of remapped colors by +	 * `percent`. +	 */ +	void remapByPercent(const uint8 color, const int16 percent); + +	/** +	 * Configures a SingleRemap for the remap color `color` +	 * to modify the saturation of remapped colors by +	 * `gray`. +	 */ +	void remapToGray(const uint8 color, const int8 gray); + +	/** +	 * Configures a SingleRemap for the remap color `color` +	 * to modify the brightness of remapped colors by +	 * `percent`, and saturation of remapped colors by +	 * `gray`. +	 */ +	void remapToPercentGray(const uint8 color, const int16 gray, const int16 percent); + +	/** +	 * Prevents GfxRemap32 from using the given range of +	 * palette entries as potential remap targets. +	 * +	 * @NOTE Was DontMapToRange in SSCI. +	 */ +	void blockRange(const uint8 from, const int16 count); + +	/** +	 * Determines whether or not the given color has an +	 * active remapper. If it does not, it is treated as a +	 * skip color and the pixel is not drawn. +	 * +	 * @note SSCI uses a boolean array to decide whether a +	 * a pixel is remapped, but it is possible to get the +	 * same information from `_remaps`, as this function +	 * does. +	 * Presumably, the separate array was created for +	 * performance reasons, since this is called a lot in +	 * the most critical section of the renderer. +	 */ +	inline bool remapEnabled(uint8 color) const { +		const uint8 index = _remapEndColor - color; +		assert(index < _remaps.size()); +		return (_remaps[index]._type != kRemapNone); +	} + +	/** +	 * Calculates the correct color for a target by looking +	 * up the target color in the SingleRemap that controls +	 * the given sourceColor. If there is no remap for the +	 * given color, it will be treated as a skip color. +	 */ +	inline uint8 remapColor(const uint8 sourceColor, const uint8 targetColor) const { +		const uint8 index = _remapEndColor - sourceColor; +		assert(index < _remaps.size()); +		const SingleRemap &singleRemap = _remaps[index]; +		assert(singleRemap._type != kRemapNone); +		return singleRemap._remapColors[targetColor]; +	} + +	/** +	 * Updates all active remaps in response to a palette +	 * change or a remap settings change. +	 * +	 * `paletteChanged` is true if the next palette in +	 * GfxPalette32 has been previously modified by other +	 * palette operations. +	 */ +	bool remapAllTables(const bool paletteUpdated); + +private: +	typedef Common::Array<SingleRemap> SingleRemapsList; + +	/** +	 * The first index of the remap area in the system +	 * palette. +	 */ +	const uint8 _remapStartColor; + +	/** +	 * The last index of the remap area in the system +	 * palette. +	 */ +	uint8 _remapEndColor; + +	/** +	 * The number of currently active remaps. +	 */ +	uint8 _numActiveRemaps; + +	/** +	 * The list of SingleRemaps. +	 */ +	SingleRemapsList _remaps; + +	/** +	 * If true, indicates that one or more SingleRemaps were +	 * reconfigured and all remaps need to be recalculated. +	 */ +	bool _needsUpdate; + +	/** +	 * The first color that is blocked from being used as a +	 * remap target color. +	 */ +	uint8 _blockedRangeStart; + +	/** +	 * The size of the range of blocked colors. If zero, +	 * all colors are potential targets for remapping. +	 */ +	int16 _blockedRangeCount; +}; +} // End of namespace Sci +#endif diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp index fba0fa0422..c1644a5ea3 100644 --- a/engines/sci/graphics/screen_item32.cpp +++ b/engines/sci/graphics/screen_item32.cpp @@ -273,7 +273,9 @@ void ScreenItem::calcRects(const Plane &plane) {  		// Cel may use a coordinate system that is not the same size as the  		// script coordinate system (usually this means high-resolution  		// pictures with low-resolution scripts) -		if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) { +		if (celObj._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) { +			// high resolution coordinates +  			if (_useInsetRect) {  				const Ratio scriptToCelX(celObj._scaledWidth, scriptWidth);  				const Ratio scriptToCelY(celObj._scaledHeight, scriptHeight); @@ -345,6 +347,8 @@ void ScreenItem::calcRects(const Plane &plane) {  			_ratioX = scaleX * celToScreenX;  			_ratioY = scaleY * celToScreenY;  		} else { +			// low resolution coordinates +  			int displaceX = celObj._displace.x;  			if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) {  				displaceX = celObj._width - celObj._displace.x - 1; @@ -515,6 +519,25 @@ void ScreenItem::update(const reg_t object) {  	_deleted = 0;  } +void ScreenItem::update() { +	Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane); +	if (plane == nullptr) { +		error("ScreenItem::update: Invalid plane %04x:%04x", PRINT_REG(_plane)); +	} + +	if (plane->_screenItemList.findByObject(_object) == nullptr) { +		error("ScreenItem::update: %04x:%04x not in plane %04x:%04x", PRINT_REG(_object), PRINT_REG(_plane)); +	} + +	if (!_created) { +		_updated = g_sci->_gfxFrameout->getScreenCount(); +	} +	_deleted = 0; + +	delete _celObj; +	_celObj = nullptr; +} +  // TODO: This code is quite similar to calcRects, so try to deduplicate  // if possible  Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { @@ -563,7 +586,9 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {  		displaceX = celObj._width - displaceX - 1;  	} -	if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) { +	if (celObj._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) { +		// high resolution coordinates +  		if (_useInsetRect) {  			Ratio scriptToCelX(celObj._scaledWidth, scriptWidth);  			Ratio scriptToCelY(celObj._scaledHeight, scriptHeight); @@ -597,6 +622,8 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const {  		mulinc(nsRect, celToScriptX, celToScriptY);  		nsRect.translate(_position.x - displaceX, _position.y - displaceY);  	} else { +		// low resolution coordinates +  		if (!scaleX.isOne() || !scaleY.isOne()) {  			mulinc(nsRect, scaleX, scaleY);  			// TODO: This was in the original code, baked into the @@ -630,23 +657,43 @@ ScreenItem *ScreenItemList::findByObject(const reg_t object) const {  	return *screenItemIt;  }  void ScreenItemList::sort() { -	// TODO: SCI engine used _unsorted as an array of indexes into the -	// list itself and then performed the same swap operations on the -	// _unsorted array as the _storage array during sorting, but the -	// only reason to do this would be if some of the pointers in the -	// list were replaced so the pointer values themselves couldn’t -	// simply be recorded and then restored later. It is not yet -	// verified whether this simplification of the sort/unsort is -	// safe. +	if (size() < 2) { +		return; +	} +  	for (size_type i = 0; i < size(); ++i) { -		_unsorted[i] = (*this)[i]; +		_unsorted[i] = i;  	} -	Common::sort(begin(), end(), sortHelper); +	for (size_type i = size() - 1; i > 0; --i) { +		bool swap = false; + +		for (size_type j = 0; j < i; ++j)  { +			value_type &a = operator[](j); +			value_type &b = operator[](j + 1); + +			if (a == nullptr || *a > *b) { +				SWAP(a, b); +				SWAP(_unsorted[j], _unsorted[j + 1]); +				swap = true; +			} +		} + +		if (!swap) { +			break; +		} +	}  }  void ScreenItemList::unsort() { +	if (size() < 2) { +		return; +	} +  	for (size_type i = 0; i < size(); ++i) { -		(*this)[i] = _unsorted[i]; +		while (_unsorted[i] != i) { +			SWAP(operator[](_unsorted[i]), operator[](i)); +			SWAP(_unsorted[_unsorted[i]], _unsorted[i]); +		}  	}  } diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h index 91f54b48e9..2e44e418ce 100644 --- a/engines/sci/graphics/screen_item32.h +++ b/engines/sci/graphics/screen_item32.h @@ -236,6 +236,24 @@ public:  		return false;  	} +	inline bool operator>(const ScreenItem &other) const { +		if (_priority > other._priority) { +			return true; +		} + +		if (_priority == other._priority) { +			if (_position.y + _z > other._position.y + other._z) { +				return true; +			} + +			if (_position.y + _z == other._position.y + other._z) { +				return _object > other._object; +			} +		} + +		return false; +	} +  	/**  	 * Calculates the dimensions and scaling parameters for  	 * the screen item, using the given plane as the parent @@ -261,6 +279,12 @@ public:  	void update(const reg_t object);  	/** +	 * Updates the properties of the screen item for one not belonging +	 * to a VM object. Originally GraphicsMgr::UpdateScreenItem. +	 */ +	void update(); + +	/**  	 * Gets the "now seen" rect for the screen item, which  	 * represents the current size and position of the  	 * screen item on the screen in script coordinates. @@ -273,12 +297,10 @@ public:  typedef StablePointerArray<ScreenItem, 250> ScreenItemListBase;  class ScreenItemList : public ScreenItemListBase { -	inline static bool sortHelper(const ScreenItem *a, const ScreenItem *b) { -		return *a < *b; -	} -public: -	ScreenItem *_unsorted[250]; +private: +	size_type _unsorted[250]; +public:  	ScreenItem *findByObject(const reg_t object) const;  	void sort();  	void unsort(); diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp index d1c223d5d5..277e6e93d0 100644 --- a/engines/sci/graphics/text32.cpp +++ b/engines/sci/graphics/text32.cpp @@ -39,18 +39,24 @@  namespace Sci {  int16 GfxText32::_defaultFontId = 0; +int16 GfxText32::_scaledWidth = 0; +int16 GfxText32::_scaledHeight = 0;  GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) :  	_segMan(segMan),  	_cache(fonts), -	_scaledWidth(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), -	_scaledHeight(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),  	// Not a typo, the original engine did not initialise height, only width  	_width(0),  	_text(""),  	_bitmap(NULL_REG) {  		_fontId = _defaultFontId;  		_font = _cache->getFont(_defaultFontId); + +		if (_scaledWidth == 0) { +			// initialize the statics +			_scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; +			_scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; +		}  	}  reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const bool dimmed, const bool doScaling) { @@ -115,7 +121,6 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &  	int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;  	int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; -	int borderSize = 1;  	mulinc(_textRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight));  	CelObjView view(celInfo.resourceId, celInfo.loopNo, celInfo.celNo); @@ -132,7 +137,6 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &  	BitmapResource bitmap(_segMan, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false);  	_bitmap = bitmap.getObject(); -	Buffer buffer(_width, _height, bitmap.getPixels());  	// NOTE: The engine filled the bitmap pixels with 11 here, which is silly  	// because then it just erased the bitmap using the skip color. So we don't @@ -142,7 +146,7 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &  	erase(bitmapRect, false);  	_backColor = backColor; -	view.draw(buffer, bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight)); +	view.draw(bitmap.getBuffer(), bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight));  	if (_backColor != skipColor && _foreColor != skipColor) {  		erase(_textRect, false); @@ -153,7 +157,7 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &  			error("TODO: Implement transparent text");  		} else {  			if (borderColor != -1) { -				drawFrame(bitmapRect, borderSize, _borderColor, false); +				drawFrame(bitmapRect, 1, _borderColor, false);  			}  			drawTextBox(); @@ -231,8 +235,11 @@ void GfxText32::drawTextBox() {  	int16 textRectWidth = _textRect.width();  	_drawPosition.y = _textRect.top;  	uint charIndex = 0; -	if (getLongest(&charIndex, textRectWidth) == 0) { -		error("DrawTextBox GetLongest=0"); + +	if (g_sci->getGameId() == GID_SQ6 || g_sci->getGameId() == GID_MOTHERGOOSEHIRES) { +		if (getLongest(&charIndex, textRectWidth) == 0) { +			error("DrawTextBox GetLongest=0"); +		}  	}  	charIndex = 0; @@ -311,6 +318,10 @@ void GfxText32::drawText(const uint index, uint length) {  				++text;  				--length;  			} +			if (length > 0) { +				++text; +				--length; +			}  		} else {  			drawChar(currentChar);  		} @@ -498,7 +509,7 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const {  					--length;  					fontId = fontId * 10 + currentChar - '0'; -				} while (length > 0 && currentChar >= '0' && currentChar <= '9'); +				} while (length > 0 && *text >= '0' && *text <= '9');  				if (length > 0) {  					font = _cache->getFont(fontId); @@ -506,7 +517,11 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const {  			}  			// Forward through any more unknown control character data -			while (length > 0 && currentChar != '|') { +			while (length > 0 && *text != '|') { +				++text; +				--length; +			} +			if (length > 0) {  				++text;  				--length;  			} @@ -514,8 +529,10 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const {  			width += font->getCharWidth(currentChar);  		} -		currentChar = *text++; -		--length; +		if (length > 0) { +			currentChar = *text++; +			--length; +		}  	}  	return width; @@ -573,11 +590,16 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth,  		}  	} else {  		result.right = getTextWidth(0, 10000); -		// NOTE: In the original engine code, the bottom was not decremented -		// by 1, which means that the rect was actually a pixel taller than -		// the height of the font. This was not the case in the other branch, -		// which decremented the bottom by 1 at the end of the loop. -		result.bottom = _font->getHeight() + 1; + +		if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { +			result.bottom = 0; +		} else { +			// NOTE: In the original engine code, the bottom was not decremented +			// by 1, which means that the rect was actually a pixel taller than +			// the height of the font. This was not the case in the other branch, +			// which decremented the bottom by 1 at the end of the loop. +			result.bottom = _font->getHeight() + 1; +		}  	}  	if (doScaling) { @@ -593,14 +615,8 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth,  void GfxText32::erase(const Common::Rect &rect, const bool doScaling) {  	Common::Rect targetRect = doScaling ? scaleRect(rect) : rect; -	byte *bitmap = _segMan->getHunkPointer(_bitmap); -	byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28); - -	// NOTE: There is an extra optimisation within the SCI code to -	// do a single memset if the scaledRect is the same size as -	// the bitmap, not implemented here. -	Buffer buffer(_width, _height, pixels); -	buffer.fillRect(targetRect, _backColor); +	BitmapResource bitmap(_bitmap); +	bitmap.getBuffer().fillRect(targetRect, _backColor);  }  int16 GfxText32::getStringWidth(const Common::String &text) { @@ -635,5 +651,69 @@ int16 GfxText32::getTextCount(const Common::String &text, const uint index, cons  	return getTextCount(text, index, textRect, doScaling);  } +void GfxText32::scrollLine(const Common::String &lineText, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir) { +	BitmapResource bmr(_bitmap); +	byte *pixels = bmr.getPixels(); + +	int h = _font->getHeight(); + +	if (dir == kScrollUp) { +		// Scroll existing text down +		for (int i = 0; i < (numLines - 1) * h; ++i) { +			int y = _textRect.top + numLines * h - i - 1; +			memcpy(pixels + y * _width + _textRect.left, +			       pixels + (y - h) * _width + _textRect.left, +			       _textRect.width()); +		} +	} else { +		// Scroll existing text up +		for (int i = 0; i < (numLines - 1) * h; ++i) { +			int y = _textRect.top + i; +			memcpy(pixels + y * _width + _textRect.left, +			       pixels + (y + h) * _width + _textRect.left, +			       _textRect.width()); +		} +	} + +	Common::Rect lineRect = _textRect; + +	if (dir == kScrollUp) { +		lineRect.bottom = lineRect.top + h; +	} else { +		// It is unclear to me what the purpose of this bottom++ is. +		// It does not seem to be the usual inc/exc issue. +		lineRect.top += (numLines - 1) * h; +		lineRect.bottom++; +	} + +	erase(lineRect, false); + +	_drawPosition.x = _textRect.left; +	_drawPosition.y = _textRect.top; +	if (dir == kScrollDown) { +		_drawPosition.y += (numLines - 1) * h; +	} + +	_foreColor = color; +	_alignment = align; +	//int fc = _foreColor; + +	setFont(fontId); + +	_text = lineText; +	int16 textWidth = getTextWidth(0, lineText.size()); + +	if (_alignment == kTextAlignCenter) { +		_drawPosition.x += (_textRect.width() - textWidth) / 2; +	} else if (_alignment == kTextAlignRight) { +		_drawPosition.x += _textRect.width() - textWidth; +	} + +	//_foreColor = fc; +	//setFont(fontId); + +	drawText(0, lineText.size()); +} +  } // End of namespace Sci diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h index 20adb3d7c7..a61760dd87 100644 --- a/engines/sci/graphics/text32.h +++ b/engines/sci/graphics/text32.h @@ -23,15 +23,23 @@  #ifndef SCI_GRAPHICS_TEXT32_H  #define SCI_GRAPHICS_TEXT32_H +#include "sci/engine/state.h"  #include "sci/graphics/celobj32.h"  #include "sci/graphics/frameout.h" +#include "sci/graphics/helpers.h"  namespace Sci {  enum TextAlign { -	kTextAlignLeft   = 0, -	kTextAlignCenter = 1, -	kTextAlignRight  = 2 +	kTextAlignDefault = -1, +	kTextAlignLeft    = 0, +	kTextAlignCenter  = 1, +	kTextAlignRight   = 2 +}; + +enum ScrollDirection { +	kScrollUp, +	kScrollDown  };  enum BitmapFlags { @@ -53,6 +61,7 @@ inline void set##property(uint##size value) {\  class BitmapResource {  	byte *_bitmap;  	reg_t _object; +	Buffer _buffer;  	/**  	 * Gets the size of the bitmap header for the current @@ -96,6 +105,8 @@ public:  			if (_bitmap == nullptr || getUncompressedDataOffset() != getBitmapHeaderSize()) {  				error("Invalid Text bitmap %04x:%04x", PRINT_REG(bitmap));  			} + +			_buffer = Buffer(getWidth(), getHeight(), getPixels());  	}  	/** @@ -103,7 +114,6 @@ public:  	 * segment manager.  	 */  	inline BitmapResource(SegManager *segMan, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool remap) { -  		_object = segMan->allocateHunkEntry("Bitmap()", getBitmapSize(width, height));  		_bitmap = segMan->getHunkPointer(_object); @@ -124,12 +134,18 @@ public:  		setControlOffset(0);  		setScaledWidth(scaledWidth);  		setScaledHeight(scaledHeight); + +		_buffer = Buffer(getWidth(), getHeight(), getPixels());  	} -	reg_t getObject() const { +	inline reg_t getObject() const {  		return _object;  	} +	inline Buffer &getBuffer() { +		return _buffer; +	} +  	BITMAP_PROPERTY(16, Width, 0);  	BITMAP_PROPERTY(16, Height, 2); @@ -173,7 +189,7 @@ public:  		return READ_SCI11ENDIAN_UINT32(_bitmap + 20);  	} -	void setHunkPaletteOffset(uint32 hunkPaletteOffset) { +	inline void setHunkPaletteOffset(uint32 hunkPaletteOffset) {  		if (hunkPaletteOffset) {  			hunkPaletteOffset += getBitmapHeaderSize();  		} @@ -351,15 +367,15 @@ public:  	/**  	 * The size of the x-dimension of the coordinate system -	 * used by the text renderer. +	 * used by the text renderer. Static since it was global in SSCI.  	 */ -	int16 _scaledWidth; +	static int16 _scaledWidth;  	/**  	 * The size of the y-dimension of the coordinate system -	 * used by the text renderer. +	 * used by the text renderer. Static since it was global in SSCI.  	 */ -	int16 _scaledHeight; +	static int16 _scaledHeight;  	/**  	 * The currently active font resource used to write text @@ -456,6 +472,13 @@ public:  	 * `textRect` using the given font.  	 */  	int16 getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling); + +	/** +	 * Scroll up/down one line. `numLines` is the number of the lines in the +	 * textarea, and `textLine` contains the text to draw as the newly +	 * visible line. Originally FontMgr::DrawOneLine and FontMgr::UpOneLine. +	 */ +	void scrollLine(const Common::String &textLine, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir);  };  } // End of namespace Sci diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h index 91590208c1..96b48c0477 100644 --- a/engines/sci/graphics/view.h +++ b/engines/sci/graphics/view.h @@ -55,6 +55,7 @@ struct LoopInfo {  class GfxScreen;  class GfxPalette; +class Resource;  /**   * View class, handles loading of view resources and drawing contained cels to screen diff --git a/engines/sci/module.mk b/engines/sci/module.mk index a02147e4d0..5d54e2a52c 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -51,7 +51,6 @@ MODULE_OBJS := \  	graphics/fontsjis.o \  	graphics/maciconbar.o \  	graphics/menu.o \ -	graphics/paint.o \  	graphics/paint16.o \  	graphics/palette.o \  	graphics/picture.o \ @@ -69,6 +68,7 @@ MODULE_OBJS := \  	sound/midiparser_sci.o \  	sound/music.o \  	sound/soundcmd.o \ +	sound/sync.o \  	sound/drivers/adlib.o \  	sound/drivers/amigamac.o \  	sound/drivers/cms.o \ @@ -88,8 +88,11 @@ MODULE_OBJS += \  	graphics/paint32.o \  	graphics/plane32.o \  	graphics/palette32.o \ +	graphics/remap32.o \  	graphics/screen_item32.o \  	graphics/text32.o \ +	sound/audio32.o \ +	sound/decoders/sol.o \  	video/robot_decoder.o  endif diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp index 9f977aaefd..3e50fc1082 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -26,6 +26,9 @@  #include "common/fs.h"  #include "common/macresman.h"  #include "common/textconsole.h" +#ifdef ENABLE_SCI32 +#include "common/memstream.h" +#endif  #include "sci/resource.h"  #include "sci/resource_intern.h" @@ -221,6 +224,12 @@ void Resource::writeToStream(Common::WriteStream *stream) const {  	stream->write(data, size);  } +#ifdef ENABLE_SCI32 +Common::SeekableReadStream *Resource::makeStream() const { +	return new Common::MemoryReadStream(data, size, DisposeAfterUse::NO); +} +#endif +  uint32 Resource::getAudioCompressionType() const {  	return _source->getAudioCompressionType();  } @@ -229,7 +238,6 @@ uint32 AudioVolumeResourceSource::getAudioCompressionType() const {  	return _audioCompressionType;  } -  ResourceSource::ResourceSource(ResSourceType type, const Common::String &name, int volNum, const Common::FSNode *resFile)   : _sourceType(type), _name(name), _volumeNumber(volNum), _resourceFile(resFile) {  	_scanned = false; @@ -1043,7 +1051,13 @@ Resource *ResourceManager::findResource(ResourceId id, bool lock) {  	if (retval->_status == kResStatusNoMalloc)  		loadResource(retval);  	else if (retval->_status == kResStatusEnqueued) +		// The resource is removed from its current position +		// in the LRU list because it has been requested +		// again. Below, it will either be locked, or it +		// will be added back to the LRU list at the 'most +		// recent' position.  		removeFromLRU(retval); +  	// Unless an error occurred, the resource is now either  	// locked or allocated, but never queued or freed. @@ -1445,13 +1459,18 @@ void ResourceManager::readResourcePatchesBase36() {  		files.clear();  		// audio36 resources start with a @, A, or B -		// sync36 resources start with a # +		// sync36 resources start with a #, S, or T  		if (i == kResourceTypeAudio36) {  			SearchMan.listMatchingMembers(files, "@???????.???");  			SearchMan.listMatchingMembers(files, "A???????.???");  			SearchMan.listMatchingMembers(files, "B???????.???"); -		} else +		} else {  			SearchMan.listMatchingMembers(files, "#???????.???"); +#ifdef ENABLE_SCI32 +			SearchMan.listMatchingMembers(files, "S???????.???"); +			SearchMan.listMatchingMembers(files, "T???????.???"); +#endif +		}  		for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {  			name = (*x)->getName(); diff --git a/engines/sci/resource.h b/engines/sci/resource.h index ef474d97c2..f70bf48bd4 100644 --- a/engines/sci/resource.h +++ b/engines/sci/resource.h @@ -84,7 +84,10 @@ enum ResourceType {  	kResourceTypePatch,  	kResourceTypeBitmap,  	kResourceTypePalette, -	kResourceTypeCdAudio, +	kResourceTypeCdAudio = 12, +#ifdef ENABLE_SCI32 +	kResourceTypeWave = 12, +#endif  	kResourceTypeAudio,  	kResourceTypeSync,  	kResourceTypeMessage, @@ -212,6 +215,10 @@ public:  		return (_type == other._type) && (_number == other._number) && (_tuple == other._tuple);  	} +	bool operator!=(const ResourceId &other) const { +		return !operator==(other); +	} +  	bool operator<(const ResourceId &other) const {  		return (_type < other._type) || ((_type == other._type) && (_number < other._number))  			    || ((_type == other._type) && (_number == other._number) && (_tuple < other._tuple)); @@ -259,6 +266,10 @@ public:  	 */  	void writeToStream(Common::WriteStream *stream) const; +#ifdef ENABLE_SCI32 +	Common::SeekableReadStream *makeStream() const; +#endif +  	const Common::String &getResourceLocation() const;  	// FIXME: This audio specific method is a hack. After all, why should a diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp index 5717a09121..5ab443a16d 100644 --- a/engines/sci/resource_audio.cpp +++ b/engines/sci/resource_audio.cpp @@ -25,7 +25,7 @@  #include "common/archive.h"  #include "common/file.h"  #include "common/textconsole.h" - +#include "common/memstream.h"  #include "sci/resource.h"  #include "sci/resource_intern.h"  #include "sci/util.h" @@ -869,6 +869,7 @@ void WaveResourceSource::loadResource(ResourceManager *resMan, Resource *res) {  	if (!fileStream)  		return; +	assert(fileStream->size() == -1 || res->_fileOffset < fileStream->size());  	fileStream->seek(res->_fileOffset, SEEK_SET);  	res->loadFromWaveFile(fileStream);  	if (_resourceFile) @@ -922,6 +923,7 @@ void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource *  			break;  		}  	} else { +		assert(fileStream->size() == -1 || res->_fileOffset < fileStream->size());  		// original file, directly seek to given offset and get SCI1/SCI1.1 audio resource  		fileStream->seek(res->_fileOffset, SEEK_SET);  	} diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index e14d12b918..41fa144b06 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -43,12 +43,12 @@  #include "sci/sound/audio.h"  #include "sci/sound/music.h" +#include "sci/sound/sync.h"  #include "sci/sound/soundcmd.h"  #include "sci/graphics/animate.h"  #include "sci/graphics/cache.h"  #include "sci/graphics/compare.h"  #include "sci/graphics/controls16.h" -#include "sci/graphics/controls32.h"  #include "sci/graphics/coordadjuster.h"  #include "sci/graphics/cursor.h"  #include "sci/graphics/maciconbar.h" @@ -64,9 +64,12 @@  #include "sci/graphics/transitions.h"  #ifdef ENABLE_SCI32 +#include "sci/graphics/controls32.h" +#include "sci/graphics/frameout.h"  #include "sci/graphics/palette32.h" +#include "sci/graphics/remap32.h"  #include "sci/graphics/text32.h" -#include "sci/graphics/frameout.h" +#include "sci/sound/audio32.h"  #include "sci/video/robot_decoder.h"  #endif @@ -86,6 +89,10 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam  	_gfxMacIconBar = 0;  	_audio = 0; +	_sync = nullptr; +#ifdef ENABLE_SCI32 +	_audio32 = nullptr; +#endif  	_features = 0;  	_resMan = 0;  	_gamestate = 0; @@ -161,16 +168,18 @@ SciEngine::~SciEngine() {  	// and will be destroyed when _gfxPalette16 is  	// destroyed  	delete _gfxControls32; +	delete _gfxPaint32;  	delete _gfxText32;  	delete _robotDecoder;  	delete _gfxFrameout;  	delete _gfxRemap32; +	delete _audio32;  #endif  	delete _gfxMenu;  	delete _gfxControls16;  	delete _gfxText16;  	delete _gfxAnimate; -	delete _gfxPaint; +	delete _gfxPaint16;  	delete _gfxTransitions;  	delete _gfxCompare;  	delete _gfxCoordAdjuster; @@ -182,6 +191,7 @@ SciEngine::~SciEngine() {  	delete _gfxScreen;  	delete _audio; +	delete _sync;  	delete _soundCmd;  	delete _kernel;  	delete _vocabulary; @@ -266,7 +276,14 @@ Common::Error SciEngine::run() {  	// Also, XMAS1990 apparently had a parser too. Refer to http://forums.scummvm.org/viewtopic.php?t=9135  	if (getGameId() == GID_CHRISTMAS1990)  		_vocabulary = new Vocabulary(_resMan, false); -	_audio = new AudioPlayer(_resMan); + +#ifdef ENABLE_SCI32 +	if (getSciVersion() >= SCI_VERSION_2_1_EARLY) { +		_audio32 = new Audio32(_resMan); +	} else +#endif +		_audio = new AudioPlayer(_resMan); +	_sync = new Sync(_resMan, segMan);  	_gamestate = new EngineState(segMan);  	_eventMan = new EventManager(_resMan->detectFontExtended()); @@ -660,7 +677,6 @@ void SciEngine::initGraphics() {  	_gfxCursor = 0;  	_gfxMacIconBar = 0;  	_gfxMenu = 0; -	_gfxPaint = 0;  	_gfxPaint16 = 0;  	_gfxPalette16 = 0;  	_gfxRemap16 = 0; @@ -684,7 +700,7 @@ void SciEngine::initGraphics() {  	if (getSciVersion() >= SCI_VERSION_2) {  		_gfxPalette32 = new GfxPalette32(_resMan, _gfxScreen);  		_gfxPalette16 = _gfxPalette32; -		_gfxRemap32 = new GfxRemap32(_gfxPalette32); +		_gfxRemap32 = new GfxRemap32();  	} else {  #endif  		_gfxPalette16 = new GfxPalette(_resMan, _gfxScreen); @@ -703,10 +719,9 @@ void SciEngine::initGraphics() {  		_gfxCoordAdjuster = new GfxCoordAdjuster32(_gamestate->_segMan);  		_gfxCursor->init(_gfxCoordAdjuster, _eventMan);  		_gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxCoordAdjuster); -		_gfxPaint32 = new GfxPaint32(_resMan, _gfxCoordAdjuster, _gfxScreen, _gfxPalette32); -		_gfxPaint = _gfxPaint32; +		_gfxPaint32 = new GfxPaint32(_gamestate->_segMan);  		_robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh); -		_gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette32, _gfxPaint32); +		_gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxScreen, _gfxPalette32);  		_gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache);  		_gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32);  		_gfxFrameout->run(); @@ -719,7 +734,6 @@ void SciEngine::initGraphics() {  		_gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxCoordAdjuster);  		_gfxTransitions = new GfxTransitions(_gfxScreen, _gfxPalette16);  		_gfxPaint16 = new GfxPaint16(_resMan, _gamestate->_segMan, _gfxCache, _gfxPorts, _gfxCoordAdjuster, _gfxScreen, _gfxPalette16, _gfxTransitions, _audio); -		_gfxPaint = _gfxPaint16;  		_gfxAnimate = new GfxAnimate(_gamestate, _scriptPatcher, _gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen, _gfxPalette16, _gfxCursor, _gfxTransitions);  		_gfxText16 = new GfxText16(_gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen);  		_gfxControls16 = new GfxControls16(_gamestate->_segMan, _gfxPorts, _gfxPaint16, _gfxText16, _gfxScreen); @@ -801,7 +815,10 @@ void SciEngine::runGame() {  void SciEngine::exitGame() {  	if (_gamestate->abortScriptProcessing != kAbortLoadGame) {  		_gamestate->_executionStack.clear(); -		_audio->stopAllAudio(); +		if (_audio) { +			_audio->stopAllAudio(); +		} +		_sync->stop();  		_soundCmd->clearPlayList();  	} @@ -897,6 +914,10 @@ Common::String SciEngine::unwrapFilename(const Common::String &name) const {  	return name;  } +const char *SciEngine::getGameObjectName() { +	return _gamestate->_segMan->getObjectName(_gameObjectAddress); +} +  int SciEngine::inQfGImportRoom() const {  	if (_gameId == GID_QFG2 && _gamestate->currentRoomNumber() == 805) {  		// QFG2 character import screen diff --git a/engines/sci/sci.h b/engines/sci/sci.h index c49a516d01..956187ce69 100644 --- a/engines/sci/sci.h +++ b/engines/sci/sci.h @@ -56,6 +56,7 @@ class SoundCommandParser;  class EventManager;  class SegManager;  class ScriptPatcher; +class Sync;  class GfxAnimate;  class GfxCache; @@ -66,7 +67,6 @@ class GfxCoordAdjuster;  class GfxCursor;  class GfxMacIconBar;  class GfxMenu; -class GfxPaint;  class GfxPaint16;  class GfxPaint32;  class GfxPalette; @@ -82,6 +82,7 @@ class GfxTransitions;  #ifdef ENABLE_SCI32  class RobotDecoder;  class GfxFrameout; +class Audio32;  #endif  // our engine debug levels @@ -302,6 +303,8 @@ public:  	/** Remove the 'TARGET-' prefix of the given filename, if present. */  	Common::String unwrapFilename(const Common::String &name) const; +	const char *getGameObjectName(); // Gets the name of the game object (should only be used for identifying fanmade games) +  	/**  	 * Checks if we are in a QfG import screen, where special handling  	 * of file-listings is performed. @@ -356,7 +359,6 @@ public:  	GfxPalette32 *_gfxPalette32; // Palette for 32-bit gfx  	GfxRemap *_gfxRemap16;	// Remapping for the QFG4 demo  	GfxRemap32 *_gfxRemap32; // Remapping for 32-bit gfx -	GfxPaint *_gfxPaint;  	GfxPaint16 *_gfxPaint16; // Painting in 16-bit gfx  	GfxPaint32 *_gfxPaint32; // Painting in 32-bit gfx  	GfxPorts *_gfxPorts; // Port managment for 16-bit gfx @@ -367,11 +369,13 @@ public:  	GfxMacIconBar *_gfxMacIconBar; // Mac Icon Bar manager  #ifdef ENABLE_SCI32 +	Audio32 *_audio32;  	RobotDecoder *_robotDecoder;  	GfxFrameout *_gfxFrameout; // kFrameout and the like for 32-bit gfx  #endif  	AudioPlayer *_audio; +	Sync *_sync;  	SoundCommandParser *_soundCmd;  	GameFeatures *_features; diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp index a74bfa245f..4fb9a58003 100644 --- a/engines/sci/sound/audio.cpp +++ b/engines/sci/sound/audio.cpp @@ -22,7 +22,6 @@  #include "sci/resource.h"  #include "sci/engine/kernel.h" -#include "sci/engine/selector.h"  #include "sci/engine/seg_manager.h"  #include "sci/sound/audio.h" @@ -45,7 +44,7 @@  namespace Sci {  AudioPlayer::AudioPlayer(ResourceManager *resMan) : _resMan(resMan), _audioRate(11025), -		_syncResource(NULL), _syncOffset(0), _audioCdStart(0), _initCD(false) { +		_audioCdStart(0), _initCD(false) {  	_mixer = g_system->getMixer();  	_wPlayFlag = false; @@ -56,7 +55,6 @@ AudioPlayer::~AudioPlayer() {  }  void AudioPlayer::stopAllAudio() { -	stopSoundSync();  	stopAudio();  	if (_audioCdStart > 0)  		audioCdStop(); @@ -255,13 +253,7 @@ static void deDPCM16(byte *soundBuf, Common::SeekableReadStream &audioStream, ui  static void deDPCM8Nibble(byte *soundBuf, int32 &s, byte b) {  	if (b & 8) { -#ifdef ENABLE_SCI32 -		// SCI2.1 reverses the order of the table values here -		if (getSciVersion() >= SCI_VERSION_2_1_EARLY) -			s -= tableDPCM8[b & 7]; -		else -#endif -			s -= tableDPCM8[7 - (b & 7)]; +		s -= tableDPCM8[7 - (b & 7)];  	} else  		s += tableDPCM8[b & 7];  	s = CLIP<int32>(s, 0, 255); @@ -474,43 +466,6 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32  	return NULL;  } -void AudioPlayer::setSoundSync(ResourceId id, reg_t syncObjAddr, SegManager *segMan) { -	_syncResource = _resMan->findResource(id, 1); -	_syncOffset = 0; - -	if (_syncResource) { -		writeSelectorValue(segMan, syncObjAddr, SELECTOR(syncCue), 0); -	} else { -		warning("setSoundSync: failed to find resource %s", id.toString().c_str()); -		// Notify the scripts to stop sound sync -		writeSelectorValue(segMan, syncObjAddr, SELECTOR(syncCue), SIGNAL_OFFSET); -	} -} - -void AudioPlayer::doSoundSync(reg_t syncObjAddr, SegManager *segMan) { -	if (_syncResource && (_syncOffset < _syncResource->size - 1)) { -		int16 syncCue = -1; -		int16 syncTime = (int16)READ_SCI11ENDIAN_UINT16(_syncResource->data + _syncOffset); - -		_syncOffset += 2; - -		if ((syncTime != -1) && (_syncOffset < _syncResource->size - 1)) { -			syncCue = (int16)READ_SCI11ENDIAN_UINT16(_syncResource->data + _syncOffset); -			_syncOffset += 2; -		} - -		writeSelectorValue(segMan, syncObjAddr, SELECTOR(syncTime), syncTime); -		writeSelectorValue(segMan, syncObjAddr, SELECTOR(syncCue), syncCue); -	} -} - -void AudioPlayer::stopSoundSync() { -	if (_syncResource) { -		_resMan->unlockResource(_syncResource); -		_syncResource = NULL; -	} -} -  int AudioPlayer::audioCdPlay(int track, int start, int duration) {  	if (!_initCD) {  		// Initialize CD mode if we haven't already diff --git a/engines/sci/sound/audio.h b/engines/sci/sound/audio.h index 4a8b26567d..3d25dcaeef 100644 --- a/engines/sci/sound/audio.h +++ b/engines/sci/sound/audio.h @@ -46,12 +46,6 @@ enum AudioCommands {  	kSciAudioCD = 10 /* Plays SCI1.1 CD audio */  }; -enum AudioSyncCommands { -	kSciAudioSyncStart = 0, -	kSciAudioSyncNext = 1, -	kSciAudioSyncStop = 2 -}; -  #define AUDIO_VOLUME_MAX 127  class Resource; @@ -77,10 +71,6 @@ public:  	void handleFanmadeSciAudio(reg_t sciAudioObject, SegManager *segMan); -	void setSoundSync(ResourceId id, reg_t syncObjAddr, SegManager *segMan); -	void doSoundSync(reg_t syncObjAddr, SegManager *segMan); -	void stopSoundSync(); -  	int audioCdPlay(int track, int start, int duration);  	void audioCdStop();  	void audioCdUpdate(); @@ -93,8 +83,6 @@ private:  	uint16 _audioRate;  	Audio::SoundHandle _audioHandle;  	Audio::Mixer *_mixer; -	Resource *_syncResource; /**< Used by kDoSync for speech syncing in CD talkie games */ -	uint _syncOffset;  	uint32 _audioCdStart;  	bool _wPlayFlag;  	bool _initCD; diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp new file mode 100644 index 0000000000..ced88a3028 --- /dev/null +++ b/engines/sci/sound/audio32.cpp @@ -0,0 +1,997 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "sci/sound/audio32.h" +#include "audio/audiostream.h"      // for SeekableAudioStream +#include "audio/decoders/raw.h"     // for makeRawStream, RawFlags::FLAG_16BITS +#include "audio/decoders/wave.h"    // for makeWAVStream +#include "audio/rate.h"             // for RateConverter, makeRateConverter +#include "audio/timestamp.h"        // for Timestamp +#include "common/config-manager.h"  // for ConfMan +#include "common/endian.h"          // for MKTAG +#include "common/memstream.h"       // for MemoryReadStream +#include "common/str.h"             // for String +#include "common/stream.h"          // for SeekableReadStream +#include "common/system.h"          // for OSystem, g_system +#include "common/textconsole.h"     // for warning +#include "common/types.h"           // for Flag::NO +#include "engine.h"                 // for Engine, g_engine +#include "sci/engine/vm_types.h"    // for reg_t, make_reg, NULL_REG +#include "sci/resource.h"           // for ResourceId, ResourceType::kResour... +#include "sci/sci.h"                // for SciEngine, g_sci, getSciVersion +#include "sci/sound/decoders/sol.h" // for makeSOLStream + +namespace Sci { + +bool detectSolAudio(Common::SeekableReadStream &stream) { +	const size_t initialPosition = stream.pos(); + +// TODO: Resource manager for audio resources reads past the +// header so even though this is the detection algorithm +// in SSCI, ScummVM can't use it +#if 0 +	byte header[6]; +	if (stream.read(header, sizeof(header)) != sizeof(header)) { +		stream.seek(initialPosition); +		return false; +	} + +	stream.seek(initialPosition); + +	if (header[0] != 0x8d || READ_BE_UINT32(header + 2) != MKTAG('S', 'O', 'L', 0)) { +		return false; +	} + +	return true; +#else +	byte header[4]; +	if (stream.read(header, sizeof(header)) != sizeof(header)) { +		stream.seek(initialPosition); +		return false; +	} + +	stream.seek(initialPosition); + +	if (READ_BE_UINT32(header) != MKTAG('S', 'O', 'L', 0)) { +		return false; +	} + +	return true; +#endif +} + +bool detectWaveAudio(Common::SeekableReadStream &stream) { +	const size_t initialPosition = stream.pos(); + +	byte blockHeader[8]; +	if (stream.read(blockHeader, sizeof(blockHeader)) != sizeof(blockHeader)) { +		stream.seek(initialPosition); +		return false; +	} + +	stream.seek(initialPosition); +	const uint32 headerType = READ_BE_UINT32(blockHeader); + +	if (headerType != MKTAG('R', 'I', 'F', 'F')) { +		return false; +	} + +	return true; +} + +#pragma mark - + +Audio32::Audio32(ResourceManager *resMan) : +	_resMan(resMan), +	_mixer(g_system->getMixer()), +	_handle(), +	_mutex(), + +	_numActiveChannels(0), +	_inAudioThread(false), + +	_globalSampleRate(44100), +	_maxAllowedSampleRate(44100), +	_globalBitDepth(16), +	_maxAllowedBitDepth(16), +	_globalNumOutputChannels(2), +	_maxAllowedOutputChannels(2), +	_preload(0), + +	_robotAudioPaused(false), + +	_pausedAtTick(0), +	_startedAtTick(0), + +	_attenuatedMixing(true), + +	 _monitoredChannelIndex(-1), +	 _monitoredBuffer(nullptr), +	 _monitoredBufferSize(0), +	 _numMonitoredSamples(0) { + +	if (getSciVersion() < SCI_VERSION_3) { +		_channels.resize(5); +	} else { +		_channels.resize(8); +	} + +	_useModifiedAttenuation = false; +	if (getSciVersion() == SCI_VERSION_2_1_MIDDLE) { +		switch (g_sci->getGameId()) { +		case GID_MOTHERGOOSEHIRES: +		case GID_PQ4: +		case GID_QFG4: +		case GID_SQ6: +			_useModifiedAttenuation = true; +		default: +			break; +		} +	} else if (getSciVersion() == SCI_VERSION_2_1_EARLY) { +		switch (g_sci->getGameId()) { +		// 1.51 uses the non-standard attenuation, but 2.00b +		// does not, which is strange. +		case GID_KQ7: +			_useModifiedAttenuation = true; +		default: +			break; +		} +	} + +	_mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +	_mixer->pauseHandle(_handle, true); +} + +Audio32::~Audio32() { +	stop(kAllChannels); +	_mixer->stopHandle(_handle); +	free(_monitoredBuffer); +} + +#pragma mark - +#pragma mark AudioStream implementation + +int Audio32::writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop) { +	int samplesToRead = numSamples; + +	// The parent rate converter will request N * 2 +	// samples from this `readBuffer` call, because +	// we tell it that we send stereo output, but +	// the source stream we're mixing in may be +	// mono, in which case we need to request half +	// as many samples from the mono stream and let +	// the converter double them for stereo output +	if (!sourceStream->isStereo()) { +		samplesToRead >>= 1; +	} + +	int samplesWritten = 0; + +	do { +		if (loop && sourceStream->endOfStream()) { +			sourceStream->rewind(); +		} + +		const int loopSamplesWritten = converter->flow(*sourceStream, targetBuffer, samplesToRead, leftVolume, rightVolume); + +		if (loopSamplesWritten == 0) { +			break; +		} + +		samplesToRead -= loopSamplesWritten; +		samplesWritten += loopSamplesWritten; +		targetBuffer += loopSamplesWritten << 1; +	} while (loop && samplesToRead > 0); + +	if (!sourceStream->isStereo()) { +		samplesWritten <<= 1; +	} + +	return samplesWritten; +} + +// In earlier versions of SCI32 engine, audio mixing is +// split into three different functions. +// +// The first function is called from the main game thread in +// AsyncEventCheck; later versions of SSCI also call it when +// getting the playback position. This function is +// responsible for cleaning up finished channels and +// filling active channel buffers with decompressed audio +// matching the hardware output audio format so they can +// just be copied into the main DAC buffer directly later. +// +// The second function is called by the audio hardware when +// the DAC buffer needs to be filled, and by `play` when +// there is only one active sample (so it can just blow away +// whatever was already in the DAC buffer). It merges all +// active channels into the DAC buffer and then updates the +// offset into the DAC buffer. +// +// Finally, a third function is called by the second +// function, and it actually puts data into the DAC buffer, +// performing volume, distortion, and balance adjustments. +// +// Since we only have one callback from the audio thread, +// and should be able to do all audio processing in +// real time, and we have streams, and we do not need to +// completely fill the audio buffer, the functionality of +// all these original functions is combined here and +// simplified. +int Audio32::readBuffer(Audio::st_sample_t *buffer, const int numSamples) { +	Common::StackLock lock(_mutex); + +	// ResourceManager is not thread-safe so we need to +	// avoid calling into it from the audio thread, but at +	// the same time we need to be able to clear out any +	// finished channels on a regular basis +	_inAudioThread = true; + +	// The system mixer should not try to get data when +	// Audio32 is paused +	assert(_pausedAtTick == 0 && _numActiveChannels > 0); + +	freeUnusedChannels(); + +	// The caller of `readBuffer` is a rate converter, +	// which reuses (without clearing) an intermediate +	// buffer, so we need to zero the intermediate buffer +	// to prevent mixing into audio data from the last +	// callback. +	memset(buffer, 0, numSamples * sizeof(Audio::st_sample_t)); + +	// This emulates the attenuated mixing mode of SSCI +	// engine, which reduces the volume of the target +	// buffer when each new channel is mixed in. +	// Instead of manipulating the content of the target +	// buffer when mixing (which would either require +	// modification of RateConverter or an expensive second +	// pass against the entire target buffer), we just +	// scale the volume for each channel in advance, with +	// the earliest (lowest) channel having the highest +	// amount of attenuation (lowest volume). +	uint8 attenuationAmount; +	uint8 attenuationStepAmount; +	if (_useModifiedAttenuation) { +		// channel | divisor +		//       0 | 0  (>> 0) +		//       1 | 4  (>> 2) +		//       2 | 8... +		attenuationAmount = _numActiveChannels * 2; +		attenuationStepAmount = 2; +	} else { +		// channel | divisor +		//       0 | 2  (>> 1) +		//       1 | 4  (>> 2) +		//       2 | 6... +		if (_monitoredChannelIndex == -1 && _numActiveChannels > 1) { +			attenuationAmount = _numActiveChannels + 1; +			attenuationStepAmount = 1; +		} else { +			attenuationAmount = 0; +			attenuationStepAmount = 0; +		} +	} + +	int maxSamplesWritten = 0; + +	for (int16 channelIndex = 0; channelIndex < _numActiveChannels; ++channelIndex) { +		attenuationAmount -= attenuationStepAmount; + +		const AudioChannel &channel = getChannel(channelIndex); + +		if (channel.pausedAtTick || (channel.robot && _robotAudioPaused)) { +			continue; +		} + +		// Channel finished fading and had the +		// stopChannelOnFade flag set, so no longer exists +		if (channel.fadeStepsRemaining && processFade(channelIndex)) { +			--channelIndex; +			continue; +		} + +		if (channel.robot) { +			// TODO: Robot audio into output buffer +			continue; +		} + +		if (channel.vmd) { +			// TODO: VMD audio into output buffer +			continue; +		} + +		Audio::st_volume_t leftVolume, rightVolume; + +		if (channel.pan == -1 || !isStereo()) { +			leftVolume = rightVolume = channel.volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume; +		} else { +			// TODO: This should match the SCI3 algorithm, +			// which seems to halve the volume of each +			// channel when centered; is this intended? +			leftVolume = channel.volume * (100 - channel.pan) / 100 * Audio::Mixer::kMaxChannelVolume / kMaxVolume; +			rightVolume = channel.volume * channel.pan / 100 * Audio::Mixer::kMaxChannelVolume / kMaxVolume; +		} + +		if (_monitoredChannelIndex == -1 && _attenuatedMixing) { +			leftVolume >>= attenuationAmount; +			rightVolume >>= attenuationAmount; +		} + +		if (channelIndex == _monitoredChannelIndex) { +			const size_t bufferSize = numSamples * sizeof(Audio::st_sample_t); +			if (_monitoredBufferSize < bufferSize) { +				_monitoredBuffer = (Audio::st_sample_t *)realloc(_monitoredBuffer, bufferSize); +				_monitoredBufferSize = bufferSize; +			} + +			memset(_monitoredBuffer, 0, _monitoredBufferSize); + +			_numMonitoredSamples = writeAudioInternal(channel.stream, channel.converter, _monitoredBuffer, numSamples, leftVolume, rightVolume, channel.loop); + +			Audio::st_sample_t *sourceBuffer = _monitoredBuffer; +			Audio::st_sample_t *targetBuffer = buffer; +			const Audio::st_sample_t *const end = _monitoredBuffer + _numMonitoredSamples; +			while (sourceBuffer != end) { +				Audio::clampedAdd(*targetBuffer++, *sourceBuffer++); +			} + +			if (_numMonitoredSamples > maxSamplesWritten) { +				maxSamplesWritten = _numMonitoredSamples; +			} +		} else if (!channel.stream->endOfStream() || channel.loop) { +			const int channelSamplesWritten = writeAudioInternal(channel.stream, channel.converter, buffer, numSamples, leftVolume, rightVolume, channel.loop); +			if (channelSamplesWritten > maxSamplesWritten) { +				maxSamplesWritten = channelSamplesWritten; +			} +		} +	} + +	_inAudioThread = false; + +	return maxSamplesWritten; +} + +#pragma mark - +#pragma mark Channel management + +int16 Audio32::findChannelByArgs(int argc, const reg_t *argv, const int startIndex, const reg_t soundNode) const { +	// NOTE: argc/argv are already reduced by one in our engine because +	// this call is always made from a subop, so no reduction for the +	// subop is made in this function. SSCI takes extra steps to skip +	// the subop argument. + +	argc -= startIndex; +	if (argc <= 0) { +		return kAllChannels; +	} + +	Common::StackLock lock(_mutex); + +	if (_numActiveChannels == 0) { +		return kNoExistingChannel; +	} + +	ResourceId searchId; + +	if (argc < 5) { +		searchId = ResourceId(kResourceTypeAudio, argv[startIndex].toUint16()); +	} else { +		searchId = ResourceId( +			kResourceTypeAudio36, +			argv[startIndex].toUint16(), +			argv[startIndex + 1].toUint16(), +			argv[startIndex + 2].toUint16(), +			argv[startIndex + 3].toUint16(), +			argv[startIndex + 4].toUint16() +		); +	} + +	return findChannelById(searchId, soundNode); +} + +int16 Audio32::findChannelById(const ResourceId resourceId, const reg_t soundNode) const { +	Common::StackLock lock(_mutex); + +	if (_numActiveChannels == 0) { +		return kNoExistingChannel; +	} + +	if (resourceId.getType() == kResourceTypeAudio) { +		for (int16 i = 0; i < _numActiveChannels; ++i) { +			const AudioChannel channel = _channels[i]; +			if ( +				channel.id == resourceId && +				(soundNode.isNull() || soundNode == channel.soundNode) +			) { +				return i; +			} +		} +	} else if (resourceId.getType() == kResourceTypeAudio36) { +		for (int16 i = 0; i < _numActiveChannels; ++i) { +			const AudioChannel &candidate = getChannel(i); +			if (!candidate.robot && candidate.id == resourceId) { +				return i; +			} +		} +	} else { +		error("Audio32::findChannelById: Unknown resource type %d", resourceId.getType()); +	} + +	return kNoExistingChannel; +} + +void Audio32::freeUnusedChannels() { +	Common::StackLock lock(_mutex); +	for (int channelIndex = 0; channelIndex < _numActiveChannels; ++channelIndex) { +		const AudioChannel &channel = getChannel(channelIndex); +		if (channel.stream->endOfStream()) { +			if (channel.loop) { +				channel.stream->rewind(); +			} else { +				stop(channelIndex--); +			} +		} +	} + +	if (!_inAudioThread) { +		unlockResources(); +	} +} + +void Audio32::freeChannel(const int16 channelIndex) { +	// The original engine did this: +	// 1. Unlock memory-cached resource, if one existed +	// 2. Close patched audio file descriptor, if one existed +	// 3. Free decompression memory buffer, if one existed +	// 4. Clear monitored memory buffer, if one existed +	Common::StackLock lock(_mutex); +	AudioChannel &channel = getChannel(channelIndex); + +	// We cannot unlock resources from the audio thread +	// because ResourceManager is not thread-safe; instead, +	// we just record that the resource needs unlocking and +	// unlock it whenever we are on the main thread again +	if (_inAudioThread) { +		_resourcesToUnlock.push_back(channel.resource); +	} else { +		_resMan->unlockResource(channel.resource); +	} + +	channel.resource = nullptr; +	delete channel.stream; +	channel.stream = nullptr; +	delete channel.resourceStream; +	channel.resourceStream = nullptr; +	delete channel.converter; +	channel.converter = nullptr; + +	if (_monitoredChannelIndex == channelIndex) { +		_monitoredChannelIndex = -1; +	} +} + +void Audio32::unlockResources() { +	Common::StackLock lock(_mutex); +	assert(!_inAudioThread); + +	for (UnlockList::const_iterator it = _resourcesToUnlock.begin(); it != _resourcesToUnlock.end(); ++it) { +		_resMan->unlockResource(*it); +	} +	_resourcesToUnlock.clear(); +} + +#pragma mark - +#pragma mark Script compatibility + +void Audio32::setSampleRate(uint16 rate) { +	if (rate > _maxAllowedSampleRate) { +		rate = _maxAllowedSampleRate; +	} + +	_globalSampleRate = rate; +} + +void Audio32::setBitDepth(uint8 depth) { +	if (depth > _maxAllowedBitDepth) { +		depth = _maxAllowedBitDepth; +	} + +	_globalBitDepth = depth; +} + +void Audio32::setNumOutputChannels(int16 numChannels) { +	if (numChannels > _maxAllowedOutputChannels) { +		numChannels = _maxAllowedOutputChannels; +	} + +	_globalNumOutputChannels = numChannels; +} + +#pragma mark - +#pragma mark Playback + +uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor) { +	Common::StackLock lock(_mutex); + +	freeUnusedChannels(); + +	if (channelIndex != kNoExistingChannel) { +		AudioChannel &channel = getChannel(channelIndex); + +		if (channel.pausedAtTick) { +			resume(channelIndex); +			return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000); +		} + +		warning("Tried to resume channel %s that was not paused", channel.id.toString().c_str()); +		return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000); +	} + +	if (_numActiveChannels == _channels.size()) { +		warning("Audio mixer is full when trying to play %s", resourceId.toString().c_str()); +		return 0; +	} + +	// NOTE: SCI engine itself normally searches in this order: +	// +	// For Audio36: +	// +	// 1. First, request a FD using Audio36 name and use it as the +	//    source FD for reading the audio resource data. +	// 2a. If the returned FD is -1, or equals the audio map, or +	//     equals the audio bundle, try to get the offset of the +	//     data from the audio map, using the Audio36 name. +	// +	//     If the returned offset is -1, this is not a valid resource; +	//     return 0. Otherwise, set the read offset for the FD to the +	//     returned offset. +	// 2b. Otherwise, use the FD as-is (it is a patch file), with zero +	//     offset, and record it separately so it can be closed later. +	// +	// For plain audio: +	// +	// 1. First, request an Audio resource from the resource cache. If +	//    one does not exist, make the same request for a Wave resource. +	// 2a. If an audio resource was discovered, record its memory ID +	//     and clear the streaming FD +	// 2b. Otherwise, request an Audio FD. If one does not exist, make +	//     the same request for a Wave FD. If neither exist, this is not +	//     a valid resource; return 0. Otherwise, use the returned FD as +	//     the streaming ID and set the memory ID to null. +	// +	// Once these steps are complete, the audio engine either has a file +	// descriptor + offset that it can use to read streamed audio, or it +	// has a memory ID that it can use to read cached audio. +	// +	// Here in ScummVM we just ask the resource manager to give us the +	// resource and we get a seekable stream. + +	// TODO: This should be fixed to use streaming, which means +	// fixing the resource manager to allow streaming, which means +	// probably rewriting a bunch of the resource manager. +	Resource *resource = _resMan->findResource(resourceId, true); +	if (resource == nullptr) { +		return 0; +	} + +	channelIndex = _numActiveChannels++; + +	AudioChannel &channel = getChannel(channelIndex); +	channel.id = resourceId; +	channel.resource = resource; +	channel.loop = loop; +	channel.robot = false; +	channel.vmd = false; +	channel.lastFadeTick = 0; +	channel.fadeStepsRemaining = 0; +	channel.soundNode = soundNode; +	channel.volume = volume < 0 || volume > kMaxVolume ? (int)kMaxVolume : volume; +	// TODO: SCI3 introduces stereo audio +	channel.pan = -1; + +	if (monitor) { +		_monitoredChannelIndex = channelIndex; +	} + +	Common::MemoryReadStream headerStream(resource->_header, resource->_headerSize, DisposeAfterUse::NO); +	Common::SeekableReadStream *dataStream = channel.resourceStream = resource->makeStream(); + +	if (detectSolAudio(headerStream)) { +		channel.stream = makeSOLStream(&headerStream, dataStream, DisposeAfterUse::NO); +	} else if (detectWaveAudio(*dataStream)) { +		channel.stream = Audio::makeWAVStream(dataStream, DisposeAfterUse::NO); +	} else { +		byte flags = Audio::FLAG_LITTLE_ENDIAN; +		if (_globalBitDepth == 16) { +			flags |= Audio::FLAG_16BITS; +		} else { +			flags |= Audio::FLAG_UNSIGNED; +		} + +		if (_globalNumOutputChannels == 2) { +			flags |= Audio::FLAG_STEREO; +		} + +		channel.stream = Audio::makeRawStream(dataStream, _globalSampleRate, flags, DisposeAfterUse::NO); +	} + +	channel.converter = Audio::makeRateConverter(channel.stream->getRate(), getRate(), channel.stream->isStereo(), false); + +	// NOTE: SCI engine sets up a decompression buffer here for the audio +	// stream, plus writes information about the sample to the channel to +	// convert to the correct hardware output format, and allocates the +	// monitoring buffer to match the bitrate/samplerate/channels of the +	// original stream. We do not need to do any of these things since we +	// use audio streams, and allocate and fill the monitoring buffer +	// when reading audio data from the stream. + +	channel.duration = /* round up */ 1 + (channel.stream->getLength().msecs() * 60 / 1000); + +	const uint32 now = g_sci->getTickCount(); +	channel.pausedAtTick = autoPlay ? 0 : now; +	channel.startedAtTick = now; + +	if (_numActiveChannels == 1) { +		_startedAtTick = now; +		_mixer->pauseHandle(_handle, false); +	} + +	return channel.duration; +} + +bool Audio32::resume(const int16 channelIndex) { +	if (channelIndex == kNoExistingChannel) { +		return false; +	} + +	Common::StackLock lock(_mutex); +	const uint32 now = g_sci->getTickCount(); + +	if (channelIndex == kAllChannels) { +		// Global pause in SSCI is an extra layer over +		// individual channel pauses, so only unpause channels +		// if there was not a global pause in place +		if (_pausedAtTick == 0) { +			return false; +		} + +		for (int i = 0; i < _numActiveChannels; ++i) { +			AudioChannel &channel = getChannel(i); +			if (!channel.pausedAtTick) { +				channel.startedAtTick += now - _pausedAtTick; +			} +		} + +		_startedAtTick += now - _pausedAtTick; +		_pausedAtTick = 0; +		_mixer->pauseHandle(_handle, false); +		return true; +	} else if (channelIndex == kRobotChannel) { +		for (int i = 0; i < _numActiveChannels; ++i) { +			AudioChannel &channel = getChannel(i); +			if (channel.robot) { +				channel.startedAtTick += now - channel.pausedAtTick; +				channel.pausedAtTick = 0; +				// TODO: Robot +				// StartRobot(); +				return true; +			} +		} +	} else { +		AudioChannel &channel = getChannel(channelIndex); +		if (channel.pausedAtTick) { +			channel.startedAtTick += now - channel.pausedAtTick; +			channel.pausedAtTick = 0; +			return true; +		} +	} + +	return false; +} + +bool Audio32::pause(const int16 channelIndex) { +	if (channelIndex == kNoExistingChannel) { +		return false; +	} + +	Common::StackLock lock(_mutex); +	const uint32 now = g_sci->getTickCount(); +	bool didPause = false; + +	if (channelIndex == kAllChannels) { +		if (_pausedAtTick == 0) { +			_pausedAtTick = now; +			_mixer->pauseHandle(_handle, true); +			didPause = true; +		} +	} else if (channelIndex == kRobotChannel) { +		_robotAudioPaused = true; +		for (int16 i = 0; i < _numActiveChannels; ++i) { +			AudioChannel &channel = getChannel(i); +			if (channel.robot) { +				channel.pausedAtTick = now; +			} +		} + +		// NOTE: The actual engine returns false here regardless of whether +		// or not channels were paused +	} else { +		AudioChannel &channel = getChannel(channelIndex); + +		if (channel.pausedAtTick == 0) { +			channel.pausedAtTick = now; +			didPause = true; +		} +	} + +	return didPause; +} + +int16 Audio32::stop(const int16 channelIndex) { +	Common::StackLock lock(_mutex); +	const int16 oldNumChannels = _numActiveChannels; + +	if (channelIndex == kNoExistingChannel || oldNumChannels == 0) { +		return 0; +	} + +	if (channelIndex == kAllChannels) { +		for (int i = 0; i < oldNumChannels; ++i) { +			freeChannel(i); +		} +		_numActiveChannels = 0; +	} else { +		freeChannel(channelIndex); +		--_numActiveChannels; +		for (int i = channelIndex; i < oldNumChannels - 1; ++i) { +			_channels[i] = _channels[i + 1]; +			if (i + 1 == _monitoredChannelIndex) { +				_monitoredChannelIndex = i; +			} +		} +	} + +	// NOTE: SSCI stops the DSP interrupt and frees the +	// global decompression buffer here if there are no +	// more active channels +	if (_numActiveChannels == 0) { +		_mixer->pauseHandle(_handle, true); +	} + +	return oldNumChannels; +} + +int16 Audio32::getPosition(const int16 channelIndex) const { +	Common::StackLock lock(_mutex); +	if (channelIndex == kNoExistingChannel || _numActiveChannels == 0) { +		return -1; +	} + +	// NOTE: SSCI treats this as an unsigned short except for +	// when the value is 65535, then it treats it as signed +	int position = -1; +	const uint32 now = g_sci->getTickCount(); + +	// NOTE: The original engine also queried the audio driver to see whether +	// it thought that there was audio playback occurring via driver opcode 9 +	if (channelIndex == kAllChannels) { +		if (_pausedAtTick) { +			position = _pausedAtTick - _startedAtTick; +		} else { +			position = now - _startedAtTick; +		} +	} else { +		const AudioChannel &channel = getChannel(channelIndex); + +		if (channel.pausedAtTick) { +			position = channel.pausedAtTick - channel.startedAtTick; +		} else if (_pausedAtTick) { +			position = _pausedAtTick - channel.startedAtTick; +		} else { +			position = now - channel.startedAtTick; +		} +	} + +	return MIN(position, 65534); +} + +void Audio32::setLoop(const int16 channelIndex, const bool loop) { +	Common::StackLock lock(_mutex); + +	if (channelIndex < 0 || channelIndex >= _numActiveChannels) { +		return; +	} + +	AudioChannel &channel = getChannel(channelIndex); +	channel.loop = loop; +} + +reg_t Audio32::kernelPlay(const bool autoPlay, const int argc, const reg_t *const argv) { +	if (argc == 0) { +		return make_reg(0, _numActiveChannels); +	} + +	const int16 channelIndex = findChannelByArgs(argc, argv, 0, NULL_REG); +	ResourceId resourceId; +	bool loop; +	int16 volume; +	bool monitor = false; +	reg_t soundNode = NULL_REG; + +	if (argc >= 5) { +		resourceId = ResourceId(kResourceTypeAudio36, argv[0].toUint16(), argv[1].toUint16(), argv[2].toUint16(), argv[3].toUint16(), argv[4].toUint16()); + +		if (argc < 6 || argv[5].toSint16() == 1) { +			loop = false; +		} else { +			// NOTE: Uses -1 for infinite loop. Presumably the +			// engine was supposed to allow counter loops at one +			// point, but ended up only using loop as a boolean. +			loop = (bool)argv[5].toSint16(); +		} + +		if (argc < 7 || argv[6].toSint16() < 0 || argv[6].toSint16() > Audio32::kMaxVolume) { +			volume = Audio32::kMaxVolume; + +			if (argc >= 7) { +				monitor = true; +			} +		} else { +			volume = argv[6].toSint16(); +		} +	} else { +		resourceId = ResourceId(kResourceTypeAudio, argv[0].toUint16()); + +		if (argc < 2 || argv[1].toSint16() == 1) { +			loop = false; +		} else { +			loop = (bool)argv[1].toSint16(); +		} + +		// TODO: SCI3 uses the 0x80 bit as a flag to +		// indicate "priority channel", but the volume is clamped +		// in this call to 0x7F so that flag never makes it into +		// the audio subsystem +		if (argc < 3 || argv[2].toSint16() < 0 || argv[2].toSint16() > Audio32::kMaxVolume) { +			volume = Audio32::kMaxVolume; + +			if (argc >= 3) { +				monitor = true; +			} +		} else { +			volume = argv[2].toSint16(); +		} + +		soundNode = argc == 4 ? argv[3] : NULL_REG; +	} + +	return make_reg(0, play(channelIndex, resourceId, autoPlay, loop, volume, soundNode, monitor)); +} + +#pragma mark - +#pragma mark Effects + +int16 Audio32::getVolume(const int16 channelIndex) const { +	Common::StackLock lock(_mutex); +	if (channelIndex < 0 || channelIndex >= _numActiveChannels) { +		return _mixer->getChannelVolume(_handle) * kMaxVolume / Audio::Mixer::kMaxChannelVolume; +	} + +	return getChannel(channelIndex).volume; +} + +void Audio32::setVolume(const int16 channelIndex, int16 volume) { +	Common::StackLock lock(_mutex); + +	volume = MIN((int16)kMaxVolume, volume); +	if (channelIndex == kAllChannels) { +		ConfMan.setInt("sfx_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume); +		ConfMan.setInt("speech_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume); +		_mixer->setChannelVolume(_handle, volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume); +		g_engine->syncSoundSettings(); +	} else if (channelIndex != kNoExistingChannel) { +		getChannel(channelIndex).volume = volume; +	} +} + +bool Audio32::fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade) { +	Common::StackLock lock(_mutex); + +	if (channelIndex < 0 || channelIndex >= _numActiveChannels) { +		return false; +	} + +	AudioChannel &channel = getChannel(channelIndex); + +	if (channel.id.getType() != kResourceTypeAudio || channel.volume == targetVolume) { +		return false; +	} + +	if (steps) { +		channel.fadeVolume = targetVolume; +		channel.fadeSpeed = speed; +		channel.fadeStepsRemaining = steps; +		channel.stopChannelOnFade = stopAfterFade; +		channel.lastFadeTick = g_sci->getTickCount(); +	} else { +		setVolume(channelIndex, targetVolume); +	} + +	return true; +} + +bool Audio32::processFade(const int16 channelIndex) { +	Common::StackLock lock(_mutex); +	AudioChannel &channel = getChannel(channelIndex); + +	uint32 now = g_sci->getTickCount(); + +	if (channel.lastFadeTick + channel.fadeSpeed <= now) { +		--channel.fadeStepsRemaining; + +		if (!channel.fadeStepsRemaining) { +			if (channel.stopChannelOnFade) { +				stop(channelIndex); +				return true; +			} else { +				setVolume(channelIndex, channel.fadeVolume); +			} +		} else { +			int volume = channel.volume - (channel.volume - channel.fadeVolume) / (channel.fadeStepsRemaining + 1); + +			if (volume == channel.fadeVolume) { +				channel.fadeStepsRemaining = 1; +			} + +			setVolume(channelIndex, volume); +			channel.lastFadeTick = now; +		} +	} + +	return false; +} + +#pragma mark - +#pragma mark Signal monitoring + +bool Audio32::hasSignal() const { +	Common::StackLock lock(_mutex); + +	if (_monitoredChannelIndex == -1) { +		return false; +	} + +	const Audio::st_sample_t *buffer = _monitoredBuffer; +	const Audio::st_sample_t *const end = _monitoredBuffer + _numMonitoredSamples; + +	while (buffer != end) { +		const Audio::st_sample_t sample = *buffer++; +		if (sample > 1280 || sample < -1280) { +			return true; +		} +	} + +	return false; +} + +} // End of namespace Sci diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h new file mode 100644 index 0000000000..416b81d865 --- /dev/null +++ b/engines/sci/sound/audio32.h @@ -0,0 +1,591 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_AUDIO32_H +#define SCI_AUDIO32_H +#include "audio/audiostream.h"     // for AudioStream, SeekableAudioStream (... +#include "audio/mixer.h"           // for Mixer, SoundHandle +#include "audio/rate.h"            // for Audio::st_volume_t, RateConverter +#include "common/array.h"          // for Array +#include "common/mutex.h"          // for StackLock, Mutex +#include "common/scummsys.h"       // for int16, uint8, uint32, uint16 +#include "engines/sci/resource.h"  // for ResourceId +#include "sci/engine/vm_types.h"   // for reg_t, NULL_REG + +namespace Sci { + +/** + * An audio channel used by the software SCI mixer. + */ +struct AudioChannel { +	/** +	 * The ID of the resource loaded into this channel. +	 */ +	ResourceId id; + +	/** +	 * The resource loaded into this channel. +	 */ +	Resource *resource; + +	/** +	 * Data stream containing the raw audio for the channel. +	 */ +	Common::SeekableReadStream *resourceStream; + +	/** +	 * The audio stream loaded into this channel. +	 * `SeekableAudioStream` is used here instead of +	 * `RewindableAudioStream` because +	 * `RewindableAudioStream` does not include the +	 * `getLength` function, which is needed to tell the +	 * game engine the duration of audio streams. +	 */ +	Audio::SeekableAudioStream *stream; + +	/** +	 * The converter used to transform and merge the input +	 * stream into the mixer's output buffer. +	 */ +	Audio::RateConverter *converter; + +	/** +	 * Duration of the channel, in ticks. +	 */ +	uint32 duration; + +	/** +	 * The tick when the channel was started. +	 */ +	uint32 startedAtTick; + +	/** +	 * The tick when the channel was paused. +	 */ +	uint32 pausedAtTick; + +	/** +	 * Whether or not the audio in this channel should loop +	 * infinitely. +	 */ +	bool loop; + +	/** +	 * The time the last fade iteration occurred. +	 */ +	uint32 lastFadeTick; + +	/** +	 * The target volume of the fade. +	 */ +	int fadeVolume; + +	/** +	 * The number of ticks that should elapse between +	 * each change of volume. +	 */ +	int fadeSpeed; + +	/** +	 * The number of iterations the fade should take to +	 * complete. If this value is 0, it indicates that the +	 * channel is not fading. +	 */ +	int fadeStepsRemaining; + +	/** +	 * Whether or not the channel should be stopped and +	 * freed when the fade is complete. +	 */ +	bool stopChannelOnFade; + +	/** +	 * Whether or not this channel contains a Robot +	 * audio block. +	 */ +	bool robot; + +	/** +	 * Whether or not this channel contains a VMD audio +	 * track. +	 */ +	bool vmd; + +	/** +	 * For digital sound effects, the related VM +	 * Sound::nodePtr object for the sound. +	 */ +	reg_t soundNode; + +	/** +	 * The playback volume, from 1 to 127 inclusive. +	 */ +	int volume; + +	/** +	 * The amount to pan to the right, from 0 to 100. +	 * 50 is centered, -1 is not panned. +	 */ +	int pan; +}; + +/** + * Special audio channel indexes used to select a channel + * for digital audio playback. + */ +enum AudioChannelIndex { +	kRobotChannel = -3, +	kNoExistingChannel = -2, +	kAllChannels = -1 +}; + +/** + * Audio32 acts as a permanent audio stream into the system + * mixer and provides digital audio services for the SCI32 + * engine, since the system mixer does not support all the + * features of SCI. + */ +class Audio32 : public Audio::AudioStream { +public: +	Audio32(ResourceManager *resMan); +	~Audio32(); + +private: +	ResourceManager *_resMan; +	Audio::Mixer *_mixer; +	Audio::SoundHandle _handle; +	Common::Mutex _mutex; + +	enum { +		/** +		 * The maximum channel volume. +		 */ +		kMaxVolume = 127 +	}; + +#pragma mark - +#pragma mark AudioStream implementation +public: +	int readBuffer(Audio::st_sample_t *buffer, const int numSamples); +	bool isStereo() const { return true; } +	int getRate() const { return _mixer->getOutputRate(); } +	bool endOfData() const { return _numActiveChannels == 0; } +	bool endOfStream() const { return false; } + +private: +	/** +	 * Mixes audio from the given source stream into the +	 * target buffer using the given rate converter. +	 */ +	int writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop); + +#pragma mark - +#pragma mark Channel management +public: +	/** +	 * Gets the number of currently active channels. +	 */ +	inline uint8 getNumActiveChannels() const { +		Common::StackLock lock(_mutex); +		return _numActiveChannels; +	} + +	/** +	 * Finds a channel that is already configured for the +	 * given audio sample. +	 * +	 * @param startIndex The location of the audio resource +	 * information in the arguments list. +	 */ +	int16 findChannelByArgs(int argc, const reg_t *argv, const int startIndex, const reg_t soundNode) const; + +	/** +	 * Finds a channel that is already configured for the +	 * given audio sample. +	 */ +	int16 findChannelById(const ResourceId resourceId, const reg_t soundNode = NULL_REG) const; + +private: +	typedef Common::Array<Resource *> UnlockList; + +	/** +	 * The audio channels. +	 */ +	Common::Array<AudioChannel> _channels; + +	/** +	 * The number of active audio channels in the mixer. +	 * Being active is not the same as playing; active +	 * channels may be paused. +	 */ +	uint8 _numActiveChannels; + +	/** +	 * Whether or not we are in the audio thread. +	 * +	 * This flag is used instead of passing a parameter to +	 * `freeUnusedChannels` because a parameter would +	 * require forwarding through the public method `stop`, +	 * and there is not currently any reason for this +	 * implementation detail to be exposed. +	 */ +	bool _inAudioThread; + +	/** +	 * The list of resources from freed channels that need +	 * to be unlocked from the main thread. +	 */ +	UnlockList _resourcesToUnlock; + +	/** +	 * Gets the audio channel at the given index. +	 */ +	inline AudioChannel &getChannel(const int16 channelIndex) { +		Common::StackLock lock(_mutex); +		assert(channelIndex >= 0 && channelIndex < _numActiveChannels); +		return _channels[channelIndex]; +	} + +	/** +	 * Gets the audio channel at the given index. +	 */ +	inline const AudioChannel &getChannel(const int16 channelIndex) const { +		Common::StackLock lock(_mutex); +		assert(channelIndex >= 0 && channelIndex < _numActiveChannels); +		return _channels[channelIndex]; +	} + +	/** +	 * Frees all non-looping channels that have reached the +	 * end of their stream. +	 */ +	void freeUnusedChannels(); + +	/** +	 * Frees resources allocated to the given channel. +	 */ +	void freeChannel(const int16 channelIndex); + +	/** +	 * Unlocks all resources that were freed by the audio +	 * thread. +	 */ +	void unlockResources(); + +#pragma mark - +#pragma mark Script compatibility +public: +	/** +	 * Gets the (fake) sample rate of the hardware DAC. +	 * For script compatibility only. +	 */ +	inline uint16 getSampleRate() const { +		return _globalSampleRate; +	} + +	/** +	 * Sets the (fake) sample rate of the hardware DAC. +	 * For script compatibility only. +	 */ +	void setSampleRate(uint16 rate); + +	/** +	 * Gets the (fake) bit depth of the hardware DAC. +	 * For script compatibility only. +	 */ +	inline uint8 getBitDepth() const { +		return _globalBitDepth; +	} + +	/** +	 * Sets the (fake) sample rate of the hardware DAC. +	 * For script compatibility only. +	 */ +	void setBitDepth(uint8 depth); + +	/** +	 * Gets the (fake) number of output (speaker) channels +	 * of the hardware DAC. For script compatibility only. +	 */ +	inline uint8 getNumOutputChannels() const { +		return _globalNumOutputChannels; +	} + +	/** +	 * Sets the (fake) number of output (speaker) channels +	 * of the hardware DAC. For script compatibility only. +	 */ +	void setNumOutputChannels(int16 numChannels); + +	/** +	 * Gets the (fake) number of preloaded channels. +	 * For script compatibility only. +	 */ +	inline uint8 getPreload() const { +		return _preload; +	} + +	/** +	 * Sets the (fake) number of preloaded channels. +	 * For script compatibility only. +	 */ +	inline void setPreload(uint8 preload) { +		_preload = preload; +	} + +private: +	/** +	 * The hardware DAC sample rate. Stored only for script +	 * compatibility. +	 */ +	uint16 _globalSampleRate; + +	/** +	 * The maximum allowed sample rate of the system mixer. +	 * Stored only for script compatibility. +	 */ +	uint16 _maxAllowedSampleRate; + +	/** +	 * The hardware DAC bit depth. Stored only for script +	 * compatibility. +	 */ +	uint8 _globalBitDepth; + +	/** +	 * The maximum allowed bit depth of the system mixer. +	 * Stored only for script compatibility. +	 */ +	uint8 _maxAllowedBitDepth; + +	/** +	 * The hardware DAC output (speaker) channel +	 * configuration. Stored only for script compatibility. +	 */ +	uint8 _globalNumOutputChannels; + +	/** +	 * The maximum allowed number of output (speaker) +	 * channels of the system mixer. Stored only for script +	 * compatibility. +	 */ +	uint8 _maxAllowedOutputChannels; + +	/** +	 * The number of audio channels that should have their +	 * data preloaded into memory instead of streaming from +	 * disk. +	 * 1 = all channels, 2 = 2nd active channel and above, +	 * etc. +	 * Stored only for script compatibility. +	 */ +	uint8 _preload; + +#pragma mark - +#pragma mark Robot +public: + +private: +	/** +	 * When true, channels marked as robot audio will not be +	 * played. +	 */ +	bool _robotAudioPaused; + +#pragma mark - +#pragma mark Playback +public: +	/** +	 * Starts or resumes playback of an audio channel. +	 */ +	uint16 play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor); + +	/** +	 * Resumes playback of a paused audio channel, or of +	 * the entire audio player. +	 */ +	bool resume(const int16 channelIndex); +	bool resume(const ResourceId resourceId, const reg_t soundNode = NULL_REG) { +		Common::StackLock lock(_mutex); +		return resume(findChannelById(resourceId, soundNode)); +	} + +	/** +	 * Pauses an audio channel, or the entire audio player. +	 */ +	bool pause(const int16 channelIndex); +	bool pause(const ResourceId resourceId, const reg_t soundNode = NULL_REG) { +		Common::StackLock lock(_mutex); +		return pause(findChannelById(resourceId, soundNode)); +	} + +	/** +	 * Stops and unloads an audio channel, or the entire +	 * audio player. +	 */ +	int16 stop(const int16 channelIndex); +	int16 stop(const ResourceId resourceId, const reg_t soundNode = NULL_REG) { +		Common::StackLock lock(_mutex); +		return stop(findChannelById(resourceId, soundNode)); +	} + +	/** +	 * Returns the playback position for the given channel +	 * number, in ticks. +	 */ +	int16 getPosition(const int16 channelIndex) const; +	int16 getPosition(const ResourceId resourceId, const reg_t soundNode = NULL_REG) { +		Common::StackLock lock(_mutex); +		return getPosition(findChannelById(resourceId, soundNode)); +	} + +	/** +	 * Sets whether or not the given channel should loop. +	 */ +	void setLoop(const int16 channelIndex, const bool loop); +	void setLoop(const ResourceId resourceId, const reg_t soundNode, const bool loop) { +		Common::StackLock lock(_mutex); +		setLoop(findChannelById(resourceId, soundNode), loop); +	} + +	reg_t kernelPlay(const bool autoPlay, const int argc, const reg_t *const argv); + +private: +	/** +	 * The tick when audio was globally paused. +	 */ +	uint32 _pausedAtTick; + +	/** +	 * The tick when audio was globally started. +	 */ +	uint32 _startedAtTick; + +#pragma mark - +#pragma mark Effects +public: +	/** +	 * Gets the volume for a given channel. Passing +	 * `kAllChannels` will get the global volume. +	 */ +	int16 getVolume(const int16 channelIndex) const; +	int16 getVolume(const ResourceId resourceId, const reg_t soundNode) const { +		Common::StackLock lock(_mutex); +		return getVolume(findChannelById(resourceId, soundNode)); +	} + +	/** +	 * Sets the volume of an audio channel. Passing +	 * `kAllChannels` will set the global volume. +	 */ +	void setVolume(const int16 channelIndex, int16 volume); +	void setVolume(const ResourceId resourceId, const reg_t soundNode, const int16 volume) { +		Common::StackLock lock(_mutex); +		setVolume(findChannelById(resourceId, soundNode), volume); +	} + +	/** +	 * Initiate an immediate fade of the given channel. +	 */ +	bool fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade); +	bool fadeChannel(const ResourceId resourceId, const reg_t soundNode, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade) { +		Common::StackLock lock(_mutex); +		return fadeChannel(findChannelById(resourceId, soundNode), targetVolume, speed, steps, stopAfterFade); +	} + +	/** +	 * Gets whether attenuated mixing mode is active. +	 */ +	inline bool getAttenuatedMixing() const { +		return _attenuatedMixing; +	} + +	/** +	 * Sets the attenuated mixing mode. +	 */ +	void setAttenuatedMixing(bool attenuated) { +		Common::StackLock lock(_mutex); +		_attenuatedMixing = attenuated; +	} + +private: +	/** +	 * If true, audio will be mixed by reducing the target +	 * buffer by half every time a new channel is mixed in. +	 * The final channel is not attenuated. +	 */ +	bool _attenuatedMixing; + +	/** +	 * When true, a modified attenuation algorithm is used +	 * (`A/4 + B`) instead of standard linear attenuation +	 * (`A/2 + B/2`). +	 */ +	bool _useModifiedAttenuation; + +	/** +	 * Processes an audio fade for the given channel. +	 * +	 * @returns true if the fade was completed and the +	 * channel was stopped. +	 */ +	bool processFade(const int16 channelIndex); + +#pragma mark - +#pragma mark Signal monitoring +public: +	/** +	 * Returns whether the currently monitored audio channel +	 * contains any signal within the next audio frame. +	 */ +	bool hasSignal() const; + +private: +	/** +	 * The index of the channel being monitored for signal, +	 * or -1 if no channel is monitored. When a channel is +	 * monitored, it also causes the engine to play only the +	 * monitored channel. +	 */ +	int16 _monitoredChannelIndex; + +	/** +	 * The data buffer holding decompressed audio data for +	 * the channel that will be monitored for an audio +	 * signal. +	 */ +	Audio::st_sample_t *_monitoredBuffer; + +	/** +	 * The size of the buffer, in bytes. +	 */ +	size_t _monitoredBufferSize; + +	/** +	 * The number of valid audio samples in the signal +	 * monitoring buffer. +	 */ +	int _numMonitoredSamples; +}; + +} // End of namespace Sci +#endif diff --git a/engines/sci/sound/decoders/sol.cpp b/engines/sci/sound/decoders/sol.cpp new file mode 100644 index 0000000000..e445403120 --- /dev/null +++ b/engines/sci/sound/decoders/sol.cpp @@ -0,0 +1,273 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 "audio/audiostream.h" +#include "audio/decoders/raw.h" +#include "common/substream.h" +#include "common/util.h" +#include "engines/sci/sci.h" +#include "engines/sci/sound/decoders/sol.h" + +namespace Sci { + +// Note that the 16-bit version is also used in coktelvideo.cpp +static const uint16 tableDPCM16[128] = { +	0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080, +	0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120, +	0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0, +	0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230, +	0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280, +	0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0, +	0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320, +	0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370, +	0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0, +	0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480, +	0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700, +	0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00, +	0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000 +}; + +static const byte tableDPCM8[8] = { 0, 1, 2, 3, 6, 10, 15, 21 }; + +/** + * Decompresses 16-bit DPCM compressed audio. Each byte read + * outputs one sample into the decompression buffer. + */ +static void deDPCM16(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, int16 &sample) { +	for (uint32 i = 0; i < numBytes; ++i) { +		const uint8 delta = audioStream.readByte(); +		if (delta & 0x80) { +			sample -= tableDPCM16[delta & 0x7f]; +		} else { +			sample += tableDPCM16[delta]; +		} +		sample = CLIP<int16>(sample, -32768, 32767); +		*out++ = TO_LE_16(sample); +	} +} + +/** + * Decompresses one half of an 8-bit DPCM compressed audio + * byte. + */ +static void deDPCM8Nibble(int16 *out, uint8 &sample, uint8 delta) { +	const uint8 lastSample = sample; +	if (delta & 8) { +		sample -= tableDPCM8[delta & 7]; +	} else { +		sample += tableDPCM8[delta & 7]; +	} +	sample = CLIP<byte>(sample, 0, 255); +	*out = ((lastSample + sample) << 7) ^ 0x8000; +} + +/** + * Decompresses 8-bit DPCM compressed audio. Each byte read + * outputs two samples into the decompression buffer. + */ +static void deDPCM8(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, uint8 &sample) { +	for (uint32 i = 0; i < numBytes; ++i) { +		const uint8 delta = audioStream.readByte(); +		deDPCM8Nibble(out++, sample, delta >> 4); +		deDPCM8Nibble(out++, sample, delta & 0xf); +	} +} + +# pragma mark - + +template<bool STEREO, bool S16BIT> +SOLStream<STEREO, S16BIT>::SOLStream(Common::SeekableReadStream *stream, const DisposeAfterUse::Flag disposeAfterUse, const int32 dataOffset, const uint16 sampleRate, const int32 rawDataSize) : +	_stream(stream, disposeAfterUse), +	_dataOffset(dataOffset), +	_sampleRate(sampleRate), +	// SSCI aligns the size of SOL data to 32 bits +	_rawDataSize(rawDataSize & ~3) { +		// TODO: This is not valid for stereo SOL files, which +		// have interleaved L/R compression so need to store the +		// carried values for each channel separately. See +		// 60900.aud from Lighthouse for an example stereo file +		if (S16BIT) { +			_dpcmCarry16 = 0; +		} else { +			_dpcmCarry8 = 0x80; +		} + +		const uint8 compressionRatio = 2; +		const uint8 numChannels = STEREO ? 2 : 1; +		const uint8 bytesPerSample = S16BIT ? 2 : 1; +		_length = Audio::Timestamp((_rawDataSize * compressionRatio * 1000) / (_sampleRate * numChannels * bytesPerSample), 60); +	} + +template <bool STEREO, bool S16BIT> +bool SOLStream<STEREO, S16BIT>::seek(const Audio::Timestamp &where) { +	if (where != 0) { +		// In order to seek in compressed SOL files, all +		// previous bytes must be known since it uses +		// differential compression. Therefore, only seeking +		// to the beginning is supported now (SSCI does not +		// offer seeking anyway) +		return false; +	} + +	if (S16BIT) { +		_dpcmCarry16 = 0; +	} else { +		_dpcmCarry8 = 0x80; +	} + +	return _stream->seek(_dataOffset, SEEK_SET); +} + +template <bool STEREO, bool S16BIT> +Audio::Timestamp SOLStream<STEREO, S16BIT>::getLength() const { +	return _length; +} + +template <bool STEREO, bool S16BIT> +int SOLStream<STEREO, S16BIT>::readBuffer(int16 *buffer, const int numSamples) { +	// Reading an odd number of 8-bit samples will result in a loss of samples +	// since one byte represents two samples and we do not store the second +	// nibble in this case; it should never happen in reality +	assert(S16BIT || (numSamples % 2) == 0); + +	const int samplesPerByte = S16BIT ? 1 : 2; + +	int32 bytesToRead = numSamples / samplesPerByte; +	if (_stream->pos() + bytesToRead > _rawDataSize) { +		bytesToRead = _rawDataSize - _stream->pos(); +	} + +	if (S16BIT) { +		deDPCM16(buffer, *_stream, bytesToRead, _dpcmCarry16); +	} else { +		deDPCM8(buffer, *_stream, bytesToRead, _dpcmCarry8); +	} + +	const int samplesRead = bytesToRead * samplesPerByte; +	return samplesRead; +} + +template <bool STEREO, bool S16BIT> +bool SOLStream<STEREO, S16BIT>::isStereo() const { +	return STEREO; +} + +template <bool STEREO, bool S16BIT> +int SOLStream<STEREO, S16BIT>::getRate() const { +	return _sampleRate; +} + +template <bool STEREO, bool S16BIT> +bool SOLStream<STEREO, S16BIT>::endOfData() const { +	return _stream->eos() || _stream->pos() >= _dataOffset + _rawDataSize; +} + +template <bool STEREO, bool S16BIT> +bool SOLStream<STEREO, S16BIT>::rewind() { +	return seek(0); +} + +Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { + +	// TODO: Might not be necessary? Makes seeking work, but +	// not sure if audio is ever actually seeked in SSCI. +	const int32 initialPosition = stream->pos(); + +	byte header[6]; +	if (stream->read(header, sizeof(header)) != sizeof(header)) { +		return nullptr; +	} + +	if (header[0] != 0x8d || READ_BE_UINT32(header + 2) != MKTAG('S', 'O', 'L', 0)) { +		return nullptr; +	} + +	const uint8 headerSize = header[1]; +	const uint16 sampleRate = stream->readUint16LE(); +	const byte flags = stream->readByte(); +	const uint32 dataSize = stream->readUint32LE(); + +	if (flags & kCompressed) { +		if (flags & kStereo && flags & k16Bit) { +			return new SOLStream<true, true>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize); +		} else if (flags & kStereo) { +			return new SOLStream<true, false>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize); +		} else if (flags & k16Bit) { +			return new SOLStream<false, true>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize); +		} else { +			return new SOLStream<false, false>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize); +		} +	} + +	byte rawFlags = Audio::FLAG_LITTLE_ENDIAN; +	if (flags & k16Bit) { +		rawFlags |= Audio::FLAG_16BITS; +	} else { +		rawFlags |= Audio::FLAG_UNSIGNED; +	} + +	if (flags & kStereo) { +		rawFlags |= Audio::FLAG_STEREO; +	} + +	return Audio::makeRawStream(new Common::SeekableSubReadStream(stream, initialPosition + headerSize, initialPosition + headerSize + dataSize, disposeAfterUse), sampleRate, rawFlags, disposeAfterUse); +} + +// TODO: This needs to be removed when resource manager is fixed +// to not split audio into two parts +Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *headerStream, Common::SeekableReadStream *dataStream, DisposeAfterUse::Flag disposeAfterUse) { + +	if (headerStream->readUint32BE() != MKTAG('S', 'O', 'L', 0)) { +		return nullptr; +	} + +	const uint16 sampleRate = headerStream->readUint16LE(); +	const byte flags = headerStream->readByte(); +	const int32 dataSize = headerStream->readSint32LE(); + +	if (flags & kCompressed) { +		if (flags & kStereo && flags & k16Bit) { +			return new SOLStream<true, true>(dataStream, disposeAfterUse, 0, sampleRate, dataSize); +		} else if (flags & kStereo) { +			return new SOLStream<true, false>(dataStream, disposeAfterUse, 0, sampleRate, dataSize); +		} else if (flags & k16Bit) { +			return new SOLStream<false, true>(dataStream, disposeAfterUse, 0, sampleRate, dataSize); +		} else { +			return new SOLStream<false, false>(dataStream, disposeAfterUse, 0, sampleRate, dataSize); +		} +	} + +	byte rawFlags = Audio::FLAG_LITTLE_ENDIAN; +	if (flags & k16Bit) { +		rawFlags |= Audio::FLAG_16BITS; +	} else { +		rawFlags |= Audio::FLAG_UNSIGNED; +	} + +	if (flags & kStereo) { +		rawFlags |= Audio::FLAG_STEREO; +	} + +	return Audio::makeRawStream(dataStream, sampleRate, rawFlags, disposeAfterUse); +} + +} diff --git a/engines/sci/sound/decoders/sol.h b/engines/sci/sound/decoders/sol.h new file mode 100644 index 0000000000..1046d0b213 --- /dev/null +++ b/engines/sci/sound/decoders/sol.h @@ -0,0 +1,89 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_SOUND_DECODERS_SOL_H +#define SCI_SOUND_DECODERS_SOL_H +#include "audio/audiostream.h" +#include "common/stream.h" + +namespace Sci { + +enum SOLFlags { +	kCompressed = 1, +	k16Bit      = 4, +	kStereo     = 16 +}; + +template <bool STEREO, bool S16BIT> +class SOLStream : public Audio::SeekableAudioStream { +private: +	/** +	 * Read stream containing possibly-compressed SOL audio. +	 */ +	Common::DisposablePtr<Common::SeekableReadStream> _stream; + +	/** +	 * Start offset of the audio data in the read stream. +	 */ +	int32 _dataOffset; + +	/** +	 * Sample rate of audio data. +	 */ +	uint16 _sampleRate; + +	/** +	 * The raw (possibly-compressed) size of audio data in +	 * the stream. +	 */ +	int32 _rawDataSize; + +	/** +	 * The last sample from the previous DPCM decode. +	 */ +	union { +		int16 _dpcmCarry16; +		uint8 _dpcmCarry8; +	}; + +	/** +	 * The calculated length of the stream. +	 */ +	Audio::Timestamp _length; + +	virtual bool seek(const Audio::Timestamp &where) override; +	virtual Audio::Timestamp getLength() const override; +	virtual int readBuffer(int16 *buffer, const int numSamples) override; +	virtual bool isStereo() const override; +	virtual int getRate() const override; +	virtual bool endOfData() const override; +	virtual bool rewind() override; + +public: +	SOLStream(Common::SeekableReadStream *stream, const DisposeAfterUse::Flag disposeAfterUse, const int32 dataOffset, const uint16 sampleRate, const int32 rawDataSize); +}; + +Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse); + +Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *headerStream, Common::SeekableReadStream *dataStream, DisposeAfterUse::Flag disposeAfterUse); +} +#endif diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp index 5a11ac386a..3f34ecc2f8 100644 --- a/engines/sci/sound/music.cpp +++ b/engines/sci/sound/music.cpp @@ -212,6 +212,13 @@ void SciMusic::clearPlayList() {  void SciMusic::pauseAll(bool pause) {  	const MusicList::iterator end = _playList.end();  	for (MusicList::iterator i = _playList.begin(); i != end; ++i) { +#ifdef ENABLE_SCI32 +		// The entire DAC will have been paused by the caller; +		// do not pause the individual samples too +		if (_soundVersion >= SCI_VERSION_2_1_EARLY && (*i)->isSample) { +			continue; +		} +#endif  		soundToggle(*i, pause);  	}  } @@ -472,7 +479,16 @@ void SciMusic::soundPlay(MusicEntry *pSnd) {  		}  	} -	if (pSnd->pStreamAud) { +	if (pSnd->isSample) { +#ifdef ENABLE_SCI32 +		if (_soundVersion >= SCI_VERSION_2_1_EARLY) { +			g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj); + +			g_sci->_audio32->play(kNoExistingChannel, ResourceId(kResourceTypeAudio, pSnd->resourceId), true, pSnd->loop != 0 && pSnd->loop != 1, pSnd->volume, pSnd->soundObj, false); + +			return; +		} else +#endif  		if (!_pMixer->isSoundHandleActive(pSnd->hCurrentAud)) {  			if ((_currentlyPlayingSample) && (_pMixer->isSoundHandleActive(_currentlyPlayingSample->hCurrentAud))) {  				// Another sample is already playing, we have to stop that one @@ -550,10 +566,18 @@ void SciMusic::soundStop(MusicEntry *pSnd) {  	pSnd->status = kSoundStopped;  	if (_soundVersion <= SCI_VERSION_0_LATE)  		pSnd->isQueued = false; -	if (pSnd->pStreamAud) { -		if (_currentlyPlayingSample == pSnd) -			_currentlyPlayingSample = NULL; -		_pMixer->stopHandle(pSnd->hCurrentAud); +	if (pSnd->isSample) { +#ifdef ENABLE_SCI32 +		if (_soundVersion >= SCI_VERSION_2_1_EARLY) { +			g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj); +		} else { +#endif +			if (_currentlyPlayingSample == pSnd) +				_currentlyPlayingSample = NULL; +			_pMixer->stopHandle(pSnd->hCurrentAud); +#ifdef ENABLE_SCI32 +		} +#endif  	}  	if (pSnd->pMidiParser) { @@ -572,9 +596,12 @@ void SciMusic::soundStop(MusicEntry *pSnd) {  void SciMusic::soundSetVolume(MusicEntry *pSnd, byte volume) {  	assert(volume <= MUSIC_VOLUME_MAX); -	if (pSnd->pStreamAud) { -		// we simply ignore volume changes for samples, because sierra sci also -		//  doesn't support volume for samples via kDoSound +	if (pSnd->isSample) { +#ifdef ENABLE_SCI32 +		if (_soundVersion >= SCI_VERSION_2_1_EARLY) { +			g_sci->_audio32->setVolume(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj, volume); +		} +#endif  	} else if (pSnd->pMidiParser) {  		Common::StackLock lock(_mutex);  		pSnd->pMidiParser->mainThreadBegin(); @@ -614,12 +641,20 @@ void SciMusic::soundKill(MusicEntry *pSnd) {  	_mutex.unlock(); -	if (pSnd->pStreamAud) { -		if (_currentlyPlayingSample == pSnd) { -			// Forget about this sound, in case it was currently playing -			_currentlyPlayingSample = NULL; +	if (pSnd->isSample) { +#ifdef ENABLE_SCI32 +		if (_soundVersion >= SCI_VERSION_2_1_EARLY) { +			g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj); +		} else { +#endif +			if (_currentlyPlayingSample == pSnd) { +				// Forget about this sound, in case it was currently playing +				_currentlyPlayingSample = NULL; +			} +			_pMixer->stopHandle(pSnd->hCurrentAud); +#ifdef ENABLE_SCI32  		} -		_pMixer->stopHandle(pSnd->hCurrentAud); +#endif  		delete pSnd->pStreamAud;  		pSnd->pStreamAud = NULL;  		delete pSnd->pLoopStream; @@ -685,6 +720,18 @@ void SciMusic::soundResume(MusicEntry *pSnd) {  }  void SciMusic::soundToggle(MusicEntry *pSnd, bool pause) { +#ifdef ENABLE_SCI32 +	if (_soundVersion >= SCI_VERSION_2_1_EARLY && pSnd->isSample) { +		if (pause) { +			g_sci->_audio32->pause(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj); +		} else { +			g_sci->_audio32->resume(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj); +		} + +		return; +	} +#endif +  	if (pause)  		soundPause(pSnd);  	else @@ -813,6 +860,7 @@ MusicEntry::MusicEntry() {  	pStreamAud = 0;  	pLoopStream = 0;  	pMidiParser = 0; +	isSample = false;  	for (int i = 0; i < 16; ++i) {  		_usedChannels[i] = 0xFF; diff --git a/engines/sci/sound/music.h b/engines/sci/sound/music.h index 047f63b3b7..3a6de81c49 100644 --- a/engines/sci/sound/music.h +++ b/engines/sci/sound/music.h @@ -31,6 +31,9 @@  #include "sci/sci.h"  #include "sci/resource.h"  #include "sci/sound/drivers/mididriver.h" +#ifdef ENABLE_SCI32 +#include "sci/sound/audio32.h" +#endif  namespace Audio {  class LoopingAudioStream; @@ -123,6 +126,7 @@ public:  	Audio::RewindableAudioStream *pStreamAud;  	Audio::LoopingAudioStream *pLoopStream;  	Audio::SoundHandle hCurrentAud; +	bool isSample;  public:  	MusicEntry(); diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp index efa4735bf4..c5d8dda56b 100644 --- a/engines/sci/sound/soundcmd.cpp +++ b/engines/sci/sound/soundcmd.cpp @@ -23,6 +23,7 @@  #include "common/config-manager.h"  #include "audio/audiostream.h"  #include "audio/mixer.h" +#include "sci/resource.h"  #include "sci/sound/audio.h"  #include "sci/sound/music.h"  #include "sci/sound/soundcmd.h" @@ -97,12 +98,21 @@ void SoundCommandParser::initSoundResource(MusicEntry *newSound) {  		// user wants the digital version.  		if (_useDigitalSFX || !newSound->soundRes) {  			int sampleLen; -			newSound->pStreamAud = _audio->getAudioStream(newSound->resourceId, 65535, &sampleLen); -			newSound->soundType = Audio::Mixer::kSFXSoundType; +#ifdef ENABLE_SCI32 +			if (_soundVersion >= SCI_VERSION_2_1_EARLY) { +				newSound->isSample = g_sci->getResMan()->testResource(ResourceId(kResourceTypeAudio, newSound->resourceId)); +			} else { +#endif +				newSound->pStreamAud = _audio->getAudioStream(newSound->resourceId, 65535, &sampleLen); +				newSound->soundType = Audio::Mixer::kSFXSoundType; +				newSound->isSample = newSound->pStreamAud != nullptr; +#ifdef ENABLE_SCI32 +			} +#endif  		}  	} -	if (!newSound->pStreamAud && newSound->soundRes) +	if (!newSound->isSample && newSound->soundRes)  		_music->soundInitSnd(newSound);  } @@ -134,7 +144,7 @@ void SoundCommandParser::processInitSound(reg_t obj) {  	_music->pushBackSlot(newSound); -	if (newSound->soundRes || newSound->pStreamAud) { +	if (newSound->soundRes || newSound->isSample) {  		// Notify the engine  		if (_soundVersion <= SCI_VERSION_0_LATE)  			writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundInitialized); @@ -215,17 +225,6 @@ void SoundCommandParser::processPlaySound(reg_t obj, bool playBed) {  	musicSlot->fadeStep = 0;  } -reg_t SoundCommandParser::kDoSoundRestore(int argc, reg_t *argv, reg_t acc) { -	// Called after loading, to restore the playlist -	// We don't really use or need this -	return acc; -} - -reg_t SoundCommandParser::kDoSoundDummy(int argc, reg_t *argv, reg_t acc) { -	warning("cmdDummy invoked");	// not supposed to occur -	return acc; -} -  reg_t SoundCommandParser::kDoSoundDispose(int argc, reg_t *argv, reg_t acc) {  	debugC(kDebugLevelSound, "kDoSound(dispose): %04x:%04x", PRINT_REG(argv[0]));  	processDisposeSound(argv[0]); @@ -314,10 +313,22 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {  	}  	reg_t obj = argv[0]; -	uint16 value = argc > 1 ? argv[1].toUint16() : 0; -	if (!obj.getSegment()) {		// pause the whole playlist -		_music->pauseAll(value); -	} else {	// pause a playlist slot +	const bool shouldPause = argc > 1 ? argv[1].toUint16() : false; +	if ( +		(_soundVersion < SCI_VERSION_2_1_EARLY && !obj.getSegment()) || +		(_soundVersion >= SCI_VERSION_2_1_EARLY && obj.isNull()) +	) { +		_music->pauseAll(shouldPause); +#ifdef ENABLE_SCI32 +		if (_soundVersion >= SCI_VERSION_2_1_EARLY) { +			if (shouldPause) { +				g_sci->_audio32->pause(kAllChannels); +			} else { +				g_sci->_audio32->resume(kAllChannels); +			} +		} +#endif +	} else {  		MusicEntry *musicSlot = _music->getSlot(obj);  		if (!musicSlot) {  			// This happens quite frequently @@ -325,7 +336,23 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {  			return acc;  		} -		_music->soundToggle(musicSlot, value); +#ifdef ENABLE_SCI32 +		// NOTE: The original engine also expected a global +		// "kernel call" flag to be true in order to perform +		// this action, but the architecture of the ScummVM +		// implementation is so different that it doesn't +		// matter here +		if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) { +			const int16 channelIndex = g_sci->_audio32->findChannelById(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj); + +			if (shouldPause) { +				g_sci->_audio32->pause(channelIndex); +			} else { +				g_sci->_audio32->resume(channelIndex); +			} +		} else +#endif +			_music->soundToggle(musicSlot, shouldPause);  	}  	return acc;  } @@ -355,7 +382,11 @@ reg_t SoundCommandParser::kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc)  		int vol = CLIP<int16>(argv[0].toSint16(), 0, MUSIC_MASTERVOLUME_MAX);  		vol = vol * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX;  		ConfMan.setInt("music_volume", vol); -		ConfMan.setInt("sfx_volume", vol); +		// In SCI32, digital audio volume is controlled separately by +		// kDoAudioVolume +		if (_soundVersion < SCI_VERSION_2_1_EARLY) { +			ConfMan.setInt("sfx_volume", vol); +		}  		g_engine->syncSoundSettings();  	}  	return acc; @@ -378,6 +409,13 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {  	int volume = musicSlot->volume; +#ifdef ENABLE_SCI32 +	if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) { +		g_sci->_audio32->fadeChannel(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj, argv[2].toSint16(), argv[3].toSint16(), argv[4].toSint16(), (bool)argv[5].toSint16()); +		return acc; +	} +#endif +  	// If sound is not playing currently, set signal directly  	if (musicSlot->status != kSoundPlaying) {  		debugC(kDebugLevelSound, "kDoSound(fade): %04x:%04x fading requested, but sound is currently not playing", PRINT_REG(obj)); @@ -466,7 +504,18 @@ void SoundCommandParser::processUpdateCues(reg_t obj) {  		return;  	} -	if (musicSlot->pStreamAud) { +	if (musicSlot->isSample) { +#ifdef ENABLE_SCI32 +		if (_soundVersion >= SCI_VERSION_2_1_EARLY) { +			const int position = g_sci->_audio32->getPosition(g_sci->_audio32->findChannelById(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj)); + +			if (position == -1) { +				processStopSound(musicSlot->soundObj, true); +			} + +			return; +		} +#endif  		// Update digital sound effect slots  		uint currentLoopCounter = 0; @@ -669,6 +718,12 @@ reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) {  	value = CLIP<int>(value, 0, MUSIC_VOLUME_MAX); +#ifdef ENABLE_SCI32 +	// SSCI unconditionally sets volume if it is digital audio +	if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) { +		_music->soundSetVolume(musicSlot, value); +	} else +#endif  	if (musicSlot->volume != value) {  		musicSlot->volume = value;  		_music->soundSetVolume(musicSlot, value); @@ -727,6 +782,15 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {  		}  		return acc;  	} + +#ifdef ENABLE_SCI32 +	if (_soundVersion >= SCI_VERSION_2_1_EARLY) { +		if (value != -1) { +			value = 1; +		} +	} +#endif +  	if (value == -1) {  		musicSlot->loop = 0xFFFF;  	} else { @@ -734,6 +798,13 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {  	}  	writeSelectorValue(_segMan, obj, SELECTOR(loop), musicSlot->loop); + +#ifdef ENABLE_SCI32 +	if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) { +		g_sci->_audio32->setLoop(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj, value == -1); +	} +#endif +  	return acc;  } diff --git a/engines/sci/sound/sync.cpp b/engines/sci/sound/sync.cpp new file mode 100644 index 0000000000..4e75dab725 --- /dev/null +++ b/engines/sci/sound/sync.cpp @@ -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. + * + */ + +#include "sci/engine/kernel.h" +#include "sci/util.h" +#include "sync.h" + +namespace Sci { + +Sync::Sync(ResourceManager *resMan, SegManager *segMan) : +	_resMan(resMan), +	_segMan(segMan), +	_resource(nullptr), +	_offset(0) {} + +Sync::~Sync() { +	stop(); +} + +void Sync::start(const ResourceId id, const reg_t syncObjAddr) { +	_resource = _resMan->findResource(id, true); +	_offset = 0; + +	if (_resource) { +		writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncCue), 0); +	} else { +		warning("Sync::start: failed to find resource %s", id.toString().c_str()); +		// Notify the scripts to stop sound sync +		writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncCue), SIGNAL_OFFSET); +	} +} + +void Sync::next(const reg_t syncObjAddr) { +	if (_resource && (_offset < _resource->size - 1)) { +		int16 syncCue = -1; +		int16 syncTime = (int16)READ_SCI11ENDIAN_UINT16(_resource->data + _offset); + +		_offset += 2; + +		if ((syncTime != -1) && (_offset < _resource->size - 1)) { +			syncCue = (int16)READ_SCI11ENDIAN_UINT16(_resource->data + _offset); +			_offset += 2; +		} + +		writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncTime), syncTime); +		writeSelectorValue(_segMan, syncObjAddr, SELECTOR(syncCue), syncCue); +	} +} + +void Sync::stop() { +	if (_resource) { +		_resMan->unlockResource(_resource); +		_resource = nullptr; +	} +} + +} // End of namespace Sci diff --git a/engines/sci/graphics/paint.cpp b/engines/sci/sound/sync.h index 482b81aff1..4b9e2d1b3c 100644 --- a/engines/sci/graphics/paint.cpp +++ b/engines/sci/sound/sync.h @@ -20,25 +20,42 @@   *   */ -#include "graphics/primitives.h" +#ifndef SCI_SOUND_SYNC_H +#define SCI_SOUND_SYNC_H -#include "sci/sci.h" -#include "sci/engine/state.h"  #include "sci/engine/selector.h" -#include "sci/graphics/paint.h" +#include "sci/engine/vm_types.h"  namespace Sci { -GfxPaint::GfxPaint() { -} +enum AudioSyncCommands { +	kSciAudioSyncStart = 0, +	kSciAudioSyncNext = 1, +	kSciAudioSyncStop = 2 +}; -GfxPaint::~GfxPaint() { -} +class Resource; +class ResourceManager; +class SegManager; -void GfxPaint::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) { -} +/** + * Sync class, kDoSync and relevant functions for SCI games. + * Provides AV synchronization for animations. + */ +class Sync { +	SegManager *_segMan; +	ResourceManager *_resMan; +	Resource *_resource; +	uint _offset; + +public: +	Sync(ResourceManager *resMan, SegManager *segMan); +	~Sync(); -void GfxPaint::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) { -} +	void start(const ResourceId id, const reg_t syncObjAddr); +	void next(const reg_t syncObjAddr); +	void stop(); +};  } // End of namespace Sci +#endif diff --git a/engines/scumm/he/intern_he.h b/engines/scumm/he/intern_he.h index 7f7babc604..c6abac3ecc 100644 --- a/engines/scumm/he/intern_he.h +++ b/engines/scumm/he/intern_he.h @@ -121,13 +121,24 @@ class ScummEngine_v70he : public ScummEngine_v60he {  	friend class ResExtractor;  protected: +	enum HESndFlags { +		HE_SND_LOOP = 1, +		HE_SND_APPEND = 2, +		HE_SND_SOFT_SOUND = 4, +		HE_SND_QUICK_START = 8, +		HE_SND_OFFSET = 16, +		HE_SND_VOL = 32, +		HE_SND_FREQUENCY = 64, +		HE_SND_PAN = 128 +	}; +  	ResExtractor *_resExtractor;  	byte *_heV7DiskOffsets;  	byte *_heV7RoomOffsets;  	uint32 *_heV7RoomIntOffsets; -	int32 _heSndSoundId, _heSndOffset, _heSndChannel, _heSndFlags, _heSndSoundFreq; +	int32 _heSndSoundId, _heSndOffset, _heSndChannel, _heSndFlags, _heSndSoundFreq, _heSndPan, _heSndVol;  	int _numStoredFlObjects;  	ObjectData *_storedFlObjects; @@ -168,7 +179,7 @@ protected:  	virtual void setDefaultCursor();  	/* HE version 70 script opcodes */ -	void o70_startSound(); +	void o70_soundOps();  	void o70_pickupObject();  	void o70_getActorRoom();  	void o70_resourceRoutines(); @@ -622,7 +633,7 @@ protected:  	void o100_redimArray();  	void o100_roomOps();  	void o100_setSystemMessage(); -	void o100_startSound(); +	void o100_soundOps();  	void o100_setSpriteInfo();  	void o100_startScript();  	void o100_systemOps(); diff --git a/engines/scumm/he/script_v100he.cpp b/engines/scumm/he/script_v100he.cpp index 4a4c340802..714f431188 100644 --- a/engines/scumm/he/script_v100he.cpp +++ b/engines/scumm/he/script_v100he.cpp @@ -184,7 +184,7 @@ void ScummEngine_v100he::setupOpcodes() {  	OPCODE(0x74, o6_delay);  	OPCODE(0x75, o6_delayMinutes);  	OPCODE(0x76, o6_delaySeconds); -	OPCODE(0x77, o100_startSound); +	OPCODE(0x77, o100_soundOps);  	/* 78 */  	OPCODE(0x78, o80_sourceDebug);  	OPCODE(0x79, o100_setSpriteInfo); @@ -1742,69 +1742,69 @@ void ScummEngine_v100he::o100_setSystemMessage() {  	}  } -void ScummEngine_v100he::o100_startSound() { +void ScummEngine_v100he::o100_soundOps() {  	byte filename[260];  	int var, value;  	byte subOp = fetchScriptByte();  	switch (subOp) { -	case 6: -		_heSndFlags |= 16; +	case 6:		// SO_AT +		_heSndFlags |= HE_SND_OFFSET;  		_heSndOffset = pop();  		break; -	case 47: +	case 47:	// SO_LOAD  		copyScriptString(filename, sizeof(filename));  		_heSndSoundId = pop();  		if (_heSndSoundId)  			debug(0, "Load sound %d from file %s\n", _heSndSoundId, filename);  		break; -	case 55: -		_heSndFlags |= 8; +	case 55:	// SO_NOW +		_heSndFlags |= HE_SND_QUICK_START;  		break; -	case 83: +	case 83:	// SO_VARIABLE  		value = pop();  		var = pop();  		_heSndSoundId = pop();  		((SoundHE *)_sound)->setSoundVar(_heSndSoundId, var, value);  		break; -	case 92: -		_sound->addSoundToQueue(_heSndSoundId, _heSndOffset, _heSndChannel, _heSndFlags); +	case 92:	// SO_END +		_sound->addSoundToQueue(_heSndSoundId, _heSndOffset, _heSndChannel, _heSndFlags, _heSndSoundFreq, _heSndPan, _heSndVol);  		break; -	case 128: -		_heSndFlags |= 2; +	case 128:	// SO_SOUND_ADD +		_heSndFlags |= HE_SND_APPEND;  		break; -	case 129: +	case 129:	// SO_SOUND_CHANNEL  		_heSndChannel = pop();  		break; -	case 130: -		_heSndFlags |= 64; -		pop(); +	case 130:	// SO_SOUND_FREQUENCY +		_heSndFlags |= HE_SND_FREQUENCY; +		_heSndSoundFreq = pop();  		break; -	case 131: -		_heSndFlags |= 1; +	case 131:	// SO_SOUND_LOOPING +		_heSndFlags |= HE_SND_LOOP;  		break; -	case 132: // Music -	case 134: // Sound +	case 132:	// SO_SOUND_MODIFY +	case 134:	// SO_SOUND_START  		_heSndSoundId = pop();  		_heSndOffset = 0;  		_heSndSoundFreq = 11025;  		_heSndChannel = VAR(VAR_SOUND_CHANNEL);  		_heSndFlags = 0;  		break; -	case 133: -		_heSndFlags |= 128; -		pop(); +	case 133:	// SO_SOUND_PAN +		_heSndFlags |= HE_SND_PAN; +		_heSndPan = pop();  		break; -	case 135: -		_heSndFlags |= 4; +	case 135:	// SO_SOUND_SOFT +		_heSndFlags |= HE_SND_SOFT_SOUND;  		break; -	case 136: -		_heSndFlags |= 32; -		pop(); +	case 136:	// SO_SOUND_VOLUME +		_heSndFlags |= HE_SND_VOL; +		_heSndVol = pop();  		break;  	default: -		error("o100_startSound invalid case %d", subOp); +		error("o100_soundOps invalid case %d", subOp);  	}  } diff --git a/engines/scumm/he/script_v70he.cpp b/engines/scumm/he/script_v70he.cpp index b91943c685..0bdeb3211e 100644 --- a/engines/scumm/he/script_v70he.cpp +++ b/engines/scumm/he/script_v70he.cpp @@ -39,7 +39,7 @@ namespace Scumm {  void ScummEngine_v70he::setupOpcodes() {  	ScummEngine_v60he::setupOpcodes(); -	OPCODE(0x74, o70_startSound); +	OPCODE(0x74, o70_soundOps);  	OPCODE(0x84, o70_pickupObject);  	OPCODE(0x8c, o70_getActorRoom);  	OPCODE(0x9b, o70_resourceRoutines); @@ -52,60 +52,60 @@ void ScummEngine_v70he::setupOpcodes() {  	OPCODE(0xfa, o70_setSystemMessage);  } -void ScummEngine_v70he::o70_startSound() { +void ScummEngine_v70he::o70_soundOps() {  	int var, value;  	byte subOp = fetchScriptByte();  	switch (subOp) { -	case 9: -		_heSndFlags |= 4; +	case 9:		// SO_SOUND_SOFT? +		_heSndFlags |= HE_SND_SOFT_SOUND;  		break; -	case 23: +	case 23:	// SO_VARIABLE  		value = pop();  		var = pop();  		_heSndSoundId = pop();  		((SoundHE *)_sound)->setSoundVar(_heSndSoundId, var, value);  		break; -	case 25: +	case 25:	// SO_SOUND_VOLUME  		value = pop();  		_heSndSoundId = pop(); -		_sound->addSoundToQueue(_heSndSoundId, 0, 0, 8); +		_sound->addSoundToQueue(_heSndSoundId, 0, 0, HE_SND_VOL, 0, 0, value);  		break; -	case 56: -		_heSndFlags |= 16; +	case 56:	// SO_NOW +		_heSndFlags |= HE_SND_QUICK_START;  		break; -	case 164: -		_heSndFlags |= 2; +	case 164:	// SO_SOUND_ADD +		_heSndFlags |= HE_SND_APPEND;  		break;  	case 222:  		// WORKAROUND: For errors in room script 240 (room 4) of maze  		break; -	case 224: +	case 224:	// SO_SOUND_FREQUENCY  		_heSndSoundFreq = pop();  		break; -	case 230: +	case 230:	// SO_SOUND_CHANNEL  		_heSndChannel = pop();  		break; -	case 231: +	case 231:	// SO_AT  		_heSndOffset = pop();  		break; -	case 232: +	case 232:	// SO_SOUND_START  		_heSndSoundId = pop();  		_heSndOffset = 0;  		_heSndSoundFreq = 11025;  		_heSndChannel = VAR(VAR_SOUND_CHANNEL);  		break; -	case 245: -		_heSndFlags |= 1; +	case 245:	// SO_SOUND_LOOPING +		_heSndFlags |= HE_SND_LOOP;  		break; -	case 255: -		_sound->addSoundToQueue(_heSndSoundId, _heSndOffset, _heSndChannel, _heSndFlags); +	case 255:	// SO_END +		_sound->addSoundToQueue(_heSndSoundId, _heSndOffset, _heSndChannel, _heSndFlags, _heSndSoundFreq);  		_heSndFlags = 0;  		break;  	default: -		error("o70_startSound invalid case %d", subOp); +		error("o70_soundOps invalid case %d", subOp);  	}  } diff --git a/engines/scumm/he/sound_he.cpp b/engines/scumm/he/sound_he.cpp index 8670116c68..9da3641064 100644 --- a/engines/scumm/he/sound_he.cpp +++ b/engines/scumm/he/sound_he.cpp @@ -59,31 +59,29 @@ SoundHE::~SoundHE() {  	delete[] _heSoundChannels;  } -void SoundHE::addSoundToQueue(int sound, int heOffset, int heChannel, int heFlags) { +void SoundHE::addSoundToQueue(int sound, int heOffset, int heChannel, int heFlags, int heFreq, int hePan, int heVol) {  	if (_vm->VAR_LAST_SOUND != 0xFF)  		_vm->VAR(_vm->VAR_LAST_SOUND) = sound; -	if ((_vm->_game.heversion <= 99 && (heFlags & 16)) || (_vm->_game.heversion >= 100 && (heFlags & 8))) { -		playHESound(sound, heOffset, heChannel, heFlags); -		return; +	if (heFlags & 8) { +		playHESound(sound, heOffset, heChannel, heFlags, heFreq, hePan, heVol);  	} else { - -		Sound::addSoundToQueue(sound, heOffset, heChannel, heFlags); +		Sound::addSoundToQueue(sound, heOffset, heChannel, heFlags, heFreq, hePan, heVol);  	}  } -void SoundHE::addSoundToQueue2(int sound, int heOffset, int heChannel, int heFlags) { +void SoundHE::addSoundToQueue2(int sound, int heOffset, int heChannel, int heFlags, int heFreq, int hePan, int heVol) {  	int i = _soundQue2Pos;  	while (i--) {  		if (_soundQue2[i].sound == sound && !(heFlags & 2))  			return;  	} -	Sound::addSoundToQueue2(sound, heOffset, heChannel, heFlags); +	Sound::addSoundToQueue2(sound, heOffset, heChannel, heFlags, heFreq, hePan, heVol);  }  void SoundHE::processSoundQueues() { -	int snd, heOffset, heChannel, heFlags; +	int snd, heOffset, heChannel, heFlags, heFreq, hePan, heVol;  	if (_vm->_game.heversion >= 72) {  		for (int i = 0; i <_soundQue2Pos; i++) { @@ -91,8 +89,11 @@ void SoundHE::processSoundQueues() {  			heOffset = _soundQue2[i].offset;  			heChannel = _soundQue2[i].channel;  			heFlags = _soundQue2[i].flags; +			heFreq = _soundQue2[_soundQue2Pos].freq; +			hePan = _soundQue2[_soundQue2Pos].pan; +			heVol = _soundQue2[_soundQue2Pos].vol;  			if (snd) -				playHESound(snd, heOffset, heChannel, heFlags); +				playHESound(snd, heOffset, heChannel, heFlags, heFreq, hePan, heVol);  		}  		_soundQue2Pos = 0;  	} else { @@ -102,8 +103,11 @@ void SoundHE::processSoundQueues() {  			heOffset = _soundQue2[_soundQue2Pos].offset;  			heChannel = _soundQue2[_soundQue2Pos].channel;  			heFlags = _soundQue2[_soundQue2Pos].flags; +			heFreq = _soundQue2[_soundQue2Pos].freq; +			hePan = _soundQue2[_soundQue2Pos].pan; +			heVol = _soundQue2[_soundQue2Pos].vol;  			if (snd) -				playHESound(snd, heOffset, heChannel, heFlags); +				playHESound(snd, heOffset, heChannel, heFlags, heFreq, hePan, heVol);  		}  	} @@ -527,7 +531,7 @@ byte *findSoundTag(uint32 tag, byte *ptr) {  	return NULL;  } -void SoundHE::playHESound(int soundID, int heOffset, int heChannel, int heFlags) { +void SoundHE::playHESound(int soundID, int heOffset, int heChannel, int heFlags, int heFreq, int hePan, int heVol) {  	Audio::RewindableAudioStream *stream = 0;  	byte *ptr, *spoolPtr;  	int size = -1; diff --git a/engines/scumm/he/sound_he.h b/engines/scumm/he/sound_he.h index e0324d0753..d5a2817a0f 100644 --- a/engines/scumm/he/sound_he.h +++ b/engines/scumm/he/sound_he.h @@ -61,8 +61,8 @@ public:  	SoundHE(ScummEngine *parent, Audio::Mixer *mixer);  	~SoundHE(); -	virtual void addSoundToQueue(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0); -	virtual void addSoundToQueue2(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0); +	virtual void addSoundToQueue(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0, int heFreq = 0, int hePan = 0, int heVol = 0); +	virtual void addSoundToQueue2(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0, int heFreq = 0, int hePan = 0, int heVol = 0);  	virtual int isSoundRunning(int sound) const;  	virtual void stopSound(int sound); @@ -75,7 +75,7 @@ public:  	int getSoundPos(int sound);  	int getSoundVar(int sound, int var);  	void setSoundVar(int sound, int var, int val); -	void playHESound(int soundID, int heOffset, int heChannel, int heFlags); +	void playHESound(int soundID, int heOffset, int heChannel, int heFlags, int heFreq, int hePan, int heVol);  	void processSoundCode();  	void processSoundOpcodes(int sound, byte *codePtr, int *soundVars);  	void setOverrideFreq(int freq); diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index d5727f2a7c..3b7dea194b 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -805,6 +805,8 @@ ScummEngine_v70he::ScummEngine_v70he(OSystem *syst, const DetectorResult &dr)  	_heSndChannel = 0;  	_heSndFlags = 0;  	_heSndSoundFreq = 0; +	_heSndPan = 0; +	_heSndVol = 0;  	_numStoredFlObjects = 0;  	_storedFlObjects = (ObjectData *)calloc(100, sizeof(ObjectData)); diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index 33b7c3108d..a62092f493 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -110,7 +110,7 @@ Sound::~Sound() {  	delete _talkChannelHandle;  } -void Sound::addSoundToQueue(int sound, int heOffset, int heChannel, int heFlags) { +void Sound::addSoundToQueue(int sound, int heOffset, int heChannel, int heFlags, int heFreq, int hePan, int heVol) {  	if (_vm->VAR_LAST_SOUND != 0xFF)  		_vm->VAR(_vm->VAR_LAST_SOUND) = sound;  	_lastSound = sound; @@ -119,15 +119,18 @@ void Sound::addSoundToQueue(int sound, int heOffset, int heChannel, int heFlags)  	if (sound <= _vm->_numSounds)  		_vm->ensureResourceLoaded(rtSound, sound); -	addSoundToQueue2(sound, heOffset, heChannel, heFlags); +	addSoundToQueue2(sound, heOffset, heChannel, heFlags, heFreq, hePan, heVol);  } -void Sound::addSoundToQueue2(int sound, int heOffset, int heChannel, int heFlags) { +void Sound::addSoundToQueue2(int sound, int heOffset, int heChannel, int heFlags, int heFreq, int hePan, int heVol) {  	assert(_soundQue2Pos < ARRAYSIZE(_soundQue2));  	_soundQue2[_soundQue2Pos].sound = sound;  	_soundQue2[_soundQue2Pos].offset = heOffset;  	_soundQue2[_soundQue2Pos].channel = heChannel;  	_soundQue2[_soundQue2Pos].flags = heFlags; +	_soundQue2[_soundQue2Pos].freq = heFreq; +	_soundQue2[_soundQue2Pos].pan = hePan; +	_soundQue2[_soundQue2Pos].vol = heVol;  	_soundQue2Pos++;  } @@ -806,6 +809,9 @@ void Sound::stopSound(int sound) {  			_soundQue2[i].offset = 0;  			_soundQue2[i].channel = 0;  			_soundQue2[i].flags = 0; +			_soundQue2[i].freq = 0; +			_soundQue2[i].pan = 0; +			_soundQue2[i].vol = 0;  		}  	}  } diff --git a/engines/scumm/sound.h b/engines/scumm/sound.h index 7fdb16371c..bc1e88f76b 100644 --- a/engines/scumm/sound.h +++ b/engines/scumm/sound.h @@ -67,6 +67,9 @@ protected:  		int32 offset;  		int16 channel;  		int16 flags; +		int16 freq; +		int16 pan; +		int16 vol;  	} _soundQue2[10];  	Common::String _sfxFilename; @@ -101,8 +104,8 @@ public:  public:  	Sound(ScummEngine *parent, Audio::Mixer *mixer);  	virtual ~Sound(); -	virtual void addSoundToQueue(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0); -	virtual void addSoundToQueue2(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0); +	virtual void addSoundToQueue(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0, int heFreq = 0, int hePan = 0, int heVol = 0); +	virtual void addSoundToQueue2(int sound, int heOffset = 0, int heChannel = 0, int heFlags = 0, int heFreq = 0, int hePan = 0, int heVol = 0);  	void processSound();  	void playSound(int soundID); diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp index 32d5abb865..dc7ca67cfe 100644 --- a/engines/tinsel/music.cpp +++ b/engines/tinsel/music.cpp @@ -401,7 +401,7 @@ MidiMusicPlayer::MidiMusicPlayer(TinselEngine *vm) {  	if (milesAudioEnabled) {  		// Discworld 1 (DOS) uses Miles Audio 3  		// use our own Miles Audio drivers -		//  +		//  		// It seems that there are multiple versions of Discworld 1  		//  		// Version 1: @@ -639,10 +639,14 @@ PCMMusicPlayer::PCMMusicPlayer() {  	_dimmed = false;  	_dimmedTinsel = false;  	_dimIteration = 0; +	_dimmedVolume = 0; +	_dimPosition = 0;  	_fadeOutVolume = 0;  	_fadeOutIteration = 0; +	_hScript = _hSegment = 0; +  	_end = true;  	_vm->_mixer->playStream(Audio::Mixer::kMusicSoundType, diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp index 1b733a08ba..44e81494f7 100644 --- a/engines/tinsel/tinsel.cpp +++ b/engines/tinsel/tinsel.cpp @@ -823,6 +823,9 @@ TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc)  		_console(0), _sound(0), _midiMusic(0), _pcmMusic(0), _bmv(0) {  	_vm = this; +	_gameId = 0; +	_driver = NULL; +  	_config = new Config(this);  	// Register debug flags diff --git a/engines/toltecs/segmap.cpp b/engines/toltecs/segmap.cpp index 399a044d5d..d50e110485 100644 --- a/engines/toltecs/segmap.cpp +++ b/engines/toltecs/segmap.cpp @@ -30,6 +30,7 @@ SegmentMap::SegmentMap(ToltecsEngine *vm) : _vm(vm) {  	_maskRectData = NULL;  	memset(_deadEndPathRects, 0, sizeof(_closedPathRects));  	_closedPathRectsCount = 0; +	_deadEndPathRectsCount = 0;  	_pathNodesCount = 0;  } diff --git a/engines/tony/font.cpp b/engines/tony/font.cpp index 850aff17be..fb40bf5c67 100644 --- a/engines/tony/font.cpp +++ b/engines/tony/font.cpp @@ -921,6 +921,12 @@ RMDialogChoice::RMDialogChoice() {  	_curAdded = 0;  	_bShow = false; + +	_curSelection = 0; +	_numChoices = 0; + +	_drawedStrings = NULL; +	_ptDrawStrings = NULL;  }  RMDialogChoice::~RMDialogChoice() { diff --git a/engines/tony/game.cpp b/engines/tony/game.cpp index 0a2c62330b..7b9cb4c6ca 100644 --- a/engines/tony/game.cpp +++ b/engines/tony/game.cpp @@ -319,6 +319,11 @@ RMOptionScreen::RMOptionScreen() {  	_fadeTime = 0;  	_nEditPos = 0;  	_nLastState = MENUGAME; + +	_bExit = false; +	_bLoadMenuOnly = false; +	_bNoLoadSave = false; +	_bAlterGfx = false;  }  RMOptionScreen::~RMOptionScreen() { @@ -1145,7 +1150,7 @@ void RMOptionScreen::doFrame(CORO_PARAM, RMInput *input) {  							// Turn on edit mode  							_bEditSaveName = true;  							_nEditPos = _ctx->i; -							strcpy(_editName, _curThumbName[_ctx->i].c_str()); +							Common::strlcpy(_editName, _curThumbName[_ctx->i].c_str(), sizeof(_editName));  							_ctx->bRefresh = true;  						} diff --git a/engines/tony/gfxcore.h b/engines/tony/gfxcore.h index c9081506d7..1a0738e5b7 100644 --- a/engines/tony/gfxcore.h +++ b/engines/tony/gfxcore.h @@ -442,6 +442,7 @@ private:  		OTList();  		OTList(RMGfxPrimitive *pr) {  			_prim = pr; +			_next = NULL;  		}  	}; diff --git a/engines/tony/mpal/mpalutils.cpp b/engines/tony/mpal/mpalutils.cpp index 84c8a68919..d52e7332cc 100644 --- a/engines/tony/mpal/mpalutils.cpp +++ b/engines/tony/mpal/mpalutils.cpp @@ -37,6 +37,8 @@ namespace MPAL {   * @param resId					MPAL resource to open   */  RMRes::RMRes(uint32 resID) { +	_buf = NULL; +  	_h = g_vm->_resUpdate.queryResource(resID);  	if (_h == NULL)  		_h = mpalQueryResource(resID); diff --git a/engines/tony/sound.cpp b/engines/tony/sound.cpp index aa86750ad5..fed51dacf4 100644 --- a/engines/tony/sound.cpp +++ b/engines/tony/sound.cpp @@ -161,6 +161,7 @@ FPSfx::FPSfx(bool soundOn) {  	_loopStream = 0;  	_rewindableStream = 0;  	_paused = false; +	_loop = 0;  	g_vm->_activeSfx.push_back(this);  } diff --git a/engines/tony/tony.cpp b/engines/tony/tony.cpp index d8fa45cb5d..c51f449aa1 100644 --- a/engines/tony/tony.cpp +++ b/engines/tony/tony.cpp @@ -83,6 +83,8 @@ TonyEngine::TonyEngine(OSystem *syst, const TonyGameDescription *gameDesc) : Eng  	_bTimeFreezed = false;  	_nTimeFreezed = 0;  	_vdbCodec = FPCODEC_UNKNOWN; + +	memset(_funcList, 0, sizeof(_funcList));  }  TonyEngine::~TonyEngine() {  | 
