[Scummvm-git-logs] scummvm master -> 78522c9122b2c823ffa765c02c655188bcc0dd9c

fracturehill noreply at scummvm.org
Thu Sep 14 19:54:42 UTC 2023


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

Summary:
538e124959 NANCY: Make nancy6 bootable
6b719b4b44 NANCY: Read CVTX chunks
6c4621b8ee NANCY: Document engine data structs
c0b89cccbe NANCY: Fix PasswordPuzzle reading
edf03037a2 NANCY: Properly clear deleted RenderObjects from screen
53a20b9002 NANCY: Finish implementing RaycastPuzzle
78522c9122 NANCY: Silence compiler warning


Commit: 538e12495975b255c8d015a101c682dec6ca5022
    https://github.com/scummvm/scummvm/commit/538e12495975b255c8d015a101c682dec6ca5022
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-14T22:54:17+03:00

Commit Message:
NANCY: Make nancy6 bootable

Made changes to BOOT chunk reading code so the game
no longer crashes on startup.

Changed paths:
    engines/nancy/enginedata.cpp
    engines/nancy/enginedata.h


diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index 031355fdcb6..354b54baab0 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -43,6 +43,9 @@ BSUM::BSUM(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 	s.skip(0x49, kGameTypeNancy1, kGameTypeNancy1);
 	s.skip(0x43, kGameTypeNancy2);
 
+	readFilename(s, conversationTextsFilename, kGameTypeNancy6);
+	readFilename(s, autotextFilename, kGameTypeNancy6);
+
 	s.syncAsUint16LE(firstScene.sceneID);
 	s.skip(0xC, kGameTypeVampire, kGameTypeVampire); // Palette name + unknown 2 bytes
 	s.syncAsUint16LE(firstScene.frameID);
@@ -359,7 +362,14 @@ SET::SET(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 	chunkStream->skip(20); // image info
 	chunkStream->skip(16); // bounds for all scrollbars
 
-	uint numButtons = g_nancy->getGameType() == kGameTypeVampire ? 5 : 4;
+	uint numButtons;
+	if (g_nancy->getGameType() == kGameTypeVampire)  {
+		numButtons = 5;
+	} else if (g_nancy->getGameType() <= kGameTypeNancy5) {
+		numButtons = 4;
+	} else {
+		numButtons = 3;
+	}
 
 	readRectArray(*chunkStream, _scrollbarBounds, 3);
 	readRectArray(*chunkStream, _buttonDests, numButtons);
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index e4e7270c916..679ea6ed14e 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -38,6 +38,9 @@ struct BSUM : public EngineData {
 
 	byte header[90];
 
+	Common::String conversationTextsFilename;
+	Common::String autotextFilename;
+
 	// Game start section
 	SceneChangeDescription firstScene;
 	uint16 startTimeHours;


Commit: 6b719b4b44599411c2aa5c5837ce3447e80eadef
    https://github.com/scummvm/scummvm/commit/6b719b4b44599411c2aa5c5837ce3447e80eadef
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-14T22:54:17+03:00

Commit Message:
NANCY: Read CVTX chunks

Added code for reading CVTX chunks, which contain
an array of key-value pairs of strings (introduced in nancy6).

Changed paths:
    engines/nancy/enginedata.cpp
    engines/nancy/enginedata.h
    engines/nancy/nancy.cpp


diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index 354b54baab0..fdf7d022936 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -92,7 +92,6 @@ VIEW::VIEW(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 }
 
 PCAL::PCAL(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
-	assert(chunkStream);
 	uint num = chunkStream->readUint16LE();
 	readFilenameArray(*chunkStream, calNames, num);
 }
@@ -185,11 +184,8 @@ INV::INV(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 }
 
 TBOX::TBOX(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
-	assert(chunkStream);
-
 	bool isVampire = g_nancy->getGameType() == Nancy::GameType::kGameTypeVampire;
 
-	chunkStream->seek(0);
 	readRect(*chunkStream, scrollbarSrcBounds);
 
 	chunkStream->seek(0x20);
@@ -318,11 +314,7 @@ HELP::HELP(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 }
 
 CRED::CRED(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
-	assert(chunkStream);
-
 	bool isVampire = g_nancy->getGameType() == kGameTypeVampire;
-	chunkStream->seek(0);
-
 	readFilename(*chunkStream, imageName);
 
 	textNames.resize(isVampire ? 7 : 1);
@@ -537,20 +529,12 @@ CLOK::CLOK(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 }
 
 SPEC::SPEC(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
-	assert(chunkStream);
-
-	chunkStream->seek(0);
-
 	fadeToBlackNumFrames = chunkStream->readByte();
 	fadeToBlackFrameTime = chunkStream->readUint16LE();
 	crossDissolveNumFrames = chunkStream->readUint16LE();
 }
 
 RCLB::RCLB(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
-	assert(chunkStream);
-
-	chunkStream->seek(0);
-
 	lightSwitchID = chunkStream->readUint16LE();
 	unk2 = chunkStream->readUint16LE();
 
@@ -628,10 +612,6 @@ RCLB::RCLB(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
 }
 
 RCPR::RCPR(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
-	assert(chunkStream);
-
-	chunkStream->seek(0);
-
 	readRectArray(*chunkStream, screenViewportSizes, 6);
 	viewportSizeUsed = chunkStream->readUint16LE();
 
@@ -696,4 +676,28 @@ ImageChunk::ImageChunk(Common::SeekableReadStream *chunkStream) : EngineData(chu
 	height = chunkStream->readUint16LE();
 }
 
+CVTX::CVTX(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
+	uint16 numEntries = chunkStream->readUint16LE();
+
+	char *buf = nullptr;
+	uint bufSize = 0;
+	Common::String keyName;
+
+	for (uint i = 0; i < numEntries; ++i) {
+		readFilename(*chunkStream, keyName);
+		uint16 stringSize = chunkStream->readUint16LE();
+		if (stringSize > bufSize) {
+			delete buf;
+			buf = new char[stringSize * 2];
+			bufSize = stringSize * 2;
+
+			chunkStream->read(buf, stringSize);
+			buf[stringSize] = '\0';
+			texts.setVal(keyName, buf);
+		}
+	}
+
+	delete buf;
+}
+
 } // End of namespace Nancy
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index 679ea6ed14e..b09522793e8 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -380,6 +380,12 @@ struct ImageChunk : public EngineData {
 	uint16 height;
 };
 
+struct CVTX : public EngineData {
+	CVTX(Common::SeekableReadStream *chunkStream);
+
+	Common::HashMap<Common::String, Common::String> texts;
+};
+
 } // End of namespace Nancy
 
 #endif // NANCY_ENGINEDATA_H
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index b27f60cf13b..ec007f95d1c 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -369,13 +369,13 @@ void NancyEngine::bootGameEngine() {
 	// Setup mixer
 	syncSoundSettings();
 
-	IFF *boot = new IFF("boot");
-	if (!boot->load())
+	IFF *iff = new IFF("boot");
+	if (!iff->load())
 		error("Failed to load boot script");
 
 	// Load BOOT chunks data
 	Common::SeekableReadStream *chunkStream = nullptr;
-	#define LOAD_BOOT_L(t, s) if (chunkStream = boot->getChunkStream(s), chunkStream) {	\
+	#define LOAD_BOOT_L(t, s) if (chunkStream = iff->getChunkStream(s), chunkStream) {	\
 								_engineData.setVal(s, new t(chunkStream));				\
 								delete chunkStream;										\
 							}
@@ -404,29 +404,57 @@ void NancyEngine::bootGameEngine() {
 	LOAD_BOOT_L(ImageChunk, "FR0")
 	LOAD_BOOT_L(ImageChunk, "LG0")
 
-	chunkStream = boot->getChunkStream("PLG0");
+	chunkStream = iff->getChunkStream("PLG0");
 	if (!chunkStream) {
-		chunkStream = boot->getChunkStream("PLGO"); // nancy4 and above use an O instead of a zero
+		chunkStream = iff->getChunkStream("PLGO"); // nancy4 and above use an O instead of a zero
 	}	
 
 	if (chunkStream) {
 		_engineData.setVal("PLG0", new ImageChunk(chunkStream));
 	}
 
-	_cursorManager->init(boot->getChunkStream("CURS"));
+	_cursorManager->init(iff->getChunkStream("CURS"));
 
 	_graphicsManager->init();
-	_graphicsManager->loadFonts(boot->getChunkStream("FONT"));
-
-	#undef LOAD_BOOT_L
-	#undef LOAD_BOOT
+	_graphicsManager->loadFonts(iff->getChunkStream("FONT"));
 
 	preloadCals();
 
 	_sound->initSoundChannels();
-	_sound->loadCommonSounds(boot);
+	_sound->loadCommonSounds(iff);
+
+	delete iff;
+
+	// Load convo texts and autotext
+	const BSUM *bsum = (const BSUM *)getEngineData("BSUM");
+	if (bsum && bsum->conversationTextsFilename.size() && bsum->autotextFilename.size())  {
+		iff = new IFF(bsum->conversationTextsFilename);
+		if (!iff->load()) {
+			error("Could not load CONVO IFF");
+		}
+
+		if (chunkStream = iff->getChunkStream("CVTX"), chunkStream) {
+			_engineData.setVal("CONVO", new CVTX(chunkStream));
+			delete chunkStream;
+		}
+
+		delete iff;
 
-	delete boot;
+		iff = new IFF(bsum->autotextFilename);
+		if (!iff->load()) {
+			error("Could not load AUTOTEXT IFF");
+		}
+
+		if (chunkStream = iff->getChunkStream("CVTX"), chunkStream) {
+			_engineData.setVal("AUTOTEXT", new CVTX(chunkStream));
+			delete chunkStream;
+		}
+
+		delete iff;
+	}
+
+	#undef LOAD_BOOT_L
+	#undef LOAD_BOOT
 }
 
 State::State *NancyEngine::getStateObject(NancyState::NancyState state) const {


Commit: 6c4621b8ee56a40d0d60cd5abece43e83ae3accc
    https://github.com/scummvm/scummvm/commit/6c4621b8ee56a40d0d60cd5abece43e83ae3accc
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-14T22:54:17+03:00

Commit Message:
NANCY: Document engine data structs

Added comments describing the structs inside enginedata.h

Changed paths:
    engines/nancy/enginedata.h


diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index b09522793e8..8eb7aceb21b 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -33,6 +33,7 @@ struct EngineData {
 	virtual ~EngineData() {}
 };
 
+// Boot summary. Contains data for the UI, game clock, starting a new game.
 struct BSUM : public EngineData {
 	BSUM(Common::SeekableReadStream *chunkStream);
 
@@ -69,6 +70,7 @@ struct BSUM : public EngineData {
 	uint16 fastMovementTimeDelta;
 };
 
+// Contains rects defining the in-game viewport
 struct VIEW : public EngineData {
 	VIEW(Common::SeekableReadStream *chunkStream);
 
@@ -76,12 +78,17 @@ struct VIEW : public EngineData {
 	Common::Rect bounds;
 };
 
+// Contains a list of .cal filenames, which are to be loaded at startup.
+// .cal files themselves are just collections of image files used in dialogue.
+// First introduced in nancy2.
 struct PCAL : public EngineData {
 	PCAL(Common::SeekableReadStream *chunkStream);
 
 	Common::Array<Common::String> calNames;
 };
 
+// Contains definitions for all in-game items, as well as data for the
+// inventory box at the bottom right of the game screen.
 struct INV : public EngineData {
 	struct ItemDescription {
 		Common::String name;
@@ -117,6 +124,7 @@ struct INV : public EngineData {
 	Common::Array<ItemDescription> itemDescriptions;
 };
 
+// Contains data about the textbox at the bottom left of the game screen
 struct TBOX : public EngineData {
 	TBOX(Common::SeekableReadStream *chunkStream);
 
@@ -138,6 +146,7 @@ struct TBOX : public EngineData {
 	uint16 highlightConversationFontID;
 };
 
+// Contains data about the map state. Only used in TVD and nancy1
 struct MAP : public EngineData {
 	struct Location {
 		Common::String description;
@@ -171,6 +180,7 @@ struct MAP : public EngineData {
 	Common::Point cursorPosition;
 };
 
+// Contains data for the help screen.
 struct HELP : public EngineData {
 	HELP(Common::SeekableReadStream *chunkStream);
 
@@ -180,6 +190,7 @@ struct HELP : public EngineData {
 	Common::Rect buttonHoverSrc;
 };
 
+// Contains data for the credits screen.
 struct CRED : public EngineData {
 	CRED(Common::SeekableReadStream *chunkStream);
 
@@ -191,6 +202,7 @@ struct CRED : public EngineData {
 	SoundDescription sound;
 };
 
+// Contains data for the main menu.
 struct MENU : public EngineData {
 	MENU(Common::SeekableReadStream *chunkStream);
 
@@ -201,6 +213,7 @@ struct MENU : public EngineData {
 	Common::Array<Common::Rect> _buttonDisabledSrcs;
 };
 
+// Contains data for the Setup screen (a.k.a settings menu)
 struct SET : public EngineData {
 	SET(Common::SeekableReadStream *chunkStream);
 
@@ -219,6 +232,7 @@ struct SET : public EngineData {
 	Common::Array<SoundDescription> _sounds;
 };
 
+// Contains data for the Save/Load screen
 struct LOAD : public EngineData {
 	LOAD(Common::SeekableReadStream *chunkStream);
 
@@ -258,6 +272,8 @@ struct LOAD : public EngineData {
 	// Common::Rect _gameSavedBounds
 };
 
+// Contains data for the prompt that appears when exiting the game
+// without saving first. Introduced in nancy3.
 struct SDLG : public EngineData {
 	SDLG(Common::SeekableReadStream *chunkStream);
 
@@ -276,18 +292,22 @@ struct SDLG : public EngineData {
 	Common::Rect _cancelDownSrc;
 };
 
+// Contains data for the hint system. Only used in nancy1.
 struct HINT : public EngineData {
 	HINT(Common::SeekableReadStream *chunkStream);
 
 	Common::Array<uint16> numHints;
 };
 
+// Contains data for the slider puzzle. First used in nancy1
 struct SPUZ : public EngineData {
 	SPUZ(Common::SeekableReadStream *chunkStream);
 
 	Common::Array<Common::Array<int16>> tileOrder;
 };
 
+// Contains data for the clock UI that appears at the bottom left of the screen (top left in TVD)
+// Not used in nancy1 but still present in the data.
 struct CLOK : public EngineData {
 	CLOK(Common::SeekableReadStream *chunkStream);
 
@@ -311,6 +331,8 @@ struct CLOK : public EngineData {
 	Common::Array<Common::Rect> nancy5CountdownSrcs;
 };
 
+// Contains data for special effects (fades between scenes/fades to black).
+// Introduced in nancy2.
 struct SPEC : public EngineData {
 	SPEC(Common::SeekableReadStream *chunkStream);
 
@@ -319,6 +341,8 @@ struct SPEC : public EngineData {
 	byte crossDissolveNumFrames;
 };
 
+// Contains data for the raycast puzzle in nancy3. Specifically, this is the
+// data for the different "themes" that appear in the 3D space.
 struct RCLB : public EngineData {
 	struct Theme {
 		Common::String themeName;
@@ -349,6 +373,8 @@ struct RCLB : public EngineData {
 	Common::Array<Theme> themes;
 };
 
+// Contains data about the raycast puzzle in nancy3. Specifically, this is the
+// data for the debug map and the names of the textures to be used when rendering.
 struct RCPR : public EngineData {
 	RCPR(Common::SeekableReadStream *chunkStream);
 
@@ -372,6 +398,7 @@ struct RCPR : public EngineData {
 	Common::Array<Common::String> floorNames;
 };
 
+// Contains the name and dimensions of an image.
 struct ImageChunk : public EngineData {
 	ImageChunk(Common::SeekableReadStream *chunkStream);
 
@@ -380,6 +407,10 @@ struct ImageChunk : public EngineData {
 	uint16 height;
 };
 
+// Contains text data. Every string is tagged with a key via which
+// it can be accessed. Used to store dialogue and journal (autotext) strings.
+// NOT found inside BOOT; these are stored in their own cifs, the names of which
+// can be found inside BSUM. Introduced in nancy6. 
 struct CVTX : public EngineData {
 	CVTX(Common::SeekableReadStream *chunkStream);
 


Commit: c0b89cccbe69cd54ccd2857c9c9d9fce33d756b8
    https://github.com/scummvm/scummvm/commit/c0b89cccbe69cd54ccd2857c9c9d9fce33d756b8
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-14T22:54:17+03:00

Commit Message:
NANCY: Fix PasswordPuzzle reading

Added two missing version checks without which older
versions of the puzzle would crash the game.

Changed paths:
    engines/nancy/action/puzzle/passwordpuzzle.cpp


diff --git a/engines/nancy/action/puzzle/passwordpuzzle.cpp b/engines/nancy/action/puzzle/passwordpuzzle.cpp
index 0cb65efb291..835c954fff3 100644
--- a/engines/nancy/action/puzzle/passwordpuzzle.cpp
+++ b/engines/nancy/action/puzzle/passwordpuzzle.cpp
@@ -55,7 +55,7 @@ void PasswordPuzzle::readData(Common::SeekableReadStream &stream) {
 	uint numPasswords = 1;
 	char buf[20];
 
-	s.syncAsUint16LE(numNames);
+	s.syncAsUint16LE(numNames, kGameTypeNancy4);
 	_names.resize(numNames);
 	for (uint i = 0; i < numNames; ++i) {
 		stream.read(buf, 20);
@@ -64,9 +64,9 @@ void PasswordPuzzle::readData(Common::SeekableReadStream &stream) {
 
 		_maxNameLength = MAX(_maxNameLength, _names[i].size());
 	}
-	s.skip((5 - numNames) * 20);
+	s.skip((5 - numNames) * 20, kGameTypeNancy4);
 
-	s.syncAsUint16LE(numPasswords);
+	s.syncAsUint16LE(numPasswords, kGameTypeNancy4);
 	_passwords.resize(numPasswords);
 	for (uint i = 0; i < numPasswords; ++i) {
 		stream.read(buf, 20);
@@ -75,7 +75,7 @@ void PasswordPuzzle::readData(Common::SeekableReadStream &stream) {
 
 		_maxPasswordLength = MAX(_maxPasswordLength, _passwords[i].size());
 	}
-	s.skip((5 - numPasswords) * 20);
+	s.skip((5 - numPasswords) * 20, kGameTypeNancy4);
 
 	_solveExitScene.readData(stream);
 	_solveSound.readNormal(stream);


Commit: edf03037a231037130725eb72dd23e35c9de6c0f
    https://github.com/scummvm/scummvm/commit/edf03037a231037130725eb72dd23e35c9de6c0f
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-14T22:54:17+03:00

Commit Message:
NANCY: Properly clear deleted RenderObjects from screen

Added code that makes sure that when a RenderObject is removed from GraphicsManager's internal list, its graphics
do not stay on screen.

Changed paths:
    engines/nancy/graphics.cpp
    engines/nancy/graphics.h


diff --git a/engines/nancy/graphics.cpp b/engines/nancy/graphics.cpp
index bd75e629e72..73d13d41a03 100644
--- a/engines/nancy/graphics.cpp
+++ b/engines/nancy/graphics.cpp
@@ -58,7 +58,6 @@ void GraphicsManager::draw(bool updateScreen) {
 	}
 
 	g_nancy->_cursorManager->applyCursor();
-	Common::List<Common::Rect> dirtyRects;
 
 	// Update graphics for all RenderObjects and determine
 	// the areas of the screen that need to be redrawn
@@ -71,14 +70,14 @@ void GraphicsManager::draw(bool updateScreen) {
 			if (current._isVisible) {
 				if (current.hasMoved() && !current.getPreviousScreenPosition().isEmpty()) {
 					// Object moved to a new location on screen, update the previous one
-					dirtyRects.push_back(current.getPreviousScreenPosition());
+					_dirtyRects.push_back(current.getPreviousScreenPosition());
 				}
 
 				// Redraw the current location
-				dirtyRects.push_back(current.getScreenPosition());
+				_dirtyRects.push_back(current.getScreenPosition());
 			} else if (!current.getPreviousScreenPosition().isEmpty()) {
 				// Object just turned invisible, redraw the last location
-				dirtyRects.push_back(current.getPreviousScreenPosition());
+				_dirtyRects.push_back(current.getPreviousScreenPosition());
 			}
 		}
 
@@ -87,10 +86,10 @@ void GraphicsManager::draw(bool updateScreen) {
 	}
 
 	// Filter out dirty rects that are completely inside others to reduce overdraw
-	for (auto outer = dirtyRects.begin(); outer != dirtyRects.end(); ++outer) {
-		for (auto inner = dirtyRects.begin(); inner != dirtyRects.end(); ++inner) {
+	for (auto outer = _dirtyRects.begin(); outer != _dirtyRects.end(); ++outer) {
+		for (auto inner = _dirtyRects.begin(); inner != _dirtyRects.end(); ++inner) {
 			if (inner != outer && (*outer).contains(*inner)) {
-				dirtyRects.erase(inner);
+				_dirtyRects.erase(inner);
 				break;
 			}
 		}
@@ -104,7 +103,7 @@ void GraphicsManager::draw(bool updateScreen) {
 			continue;
 		}
 
-		for (Common::Rect rect : dirtyRects) {
+		for (Common::Rect rect : _dirtyRects) {
 			if (rect.intersects(current.getScreenPosition())) {
 				blitToScreen(current, rect.findIntersectingRect(current.getScreenPosition()));
 			}
@@ -115,6 +114,9 @@ void GraphicsManager::draw(bool updateScreen) {
 	if (updateScreen) {
 		_screen.update();
 	}
+
+	// Remove all dirty rects for the next frame
+	_dirtyRects.clear();
 }
 
 void GraphicsManager::loadFonts(Common::SeekableReadStream *chunkStream) {
@@ -144,6 +146,8 @@ void GraphicsManager::addObject(RenderObject *object) {
 void GraphicsManager::removeObject(RenderObject *object) {
 	for (auto &r : _objects) {
 		if (r == object) {
+			// Make sure the object gets properly cleared
+			_dirtyRects.push_back(r->getPreviousScreenPosition());
 			_objects.erase(&r);
 			break;
 		}
diff --git a/engines/nancy/graphics.h b/engines/nancy/graphics.h
index ddd8915d9a7..26b33419751 100644
--- a/engines/nancy/graphics.h
+++ b/engines/nancy/graphics.h
@@ -87,6 +87,8 @@ private:
 	Graphics::Screen _screen;
 	Common::Array<Font> _fonts;
 
+	Common::List<Common::Rect> _dirtyRects;
+
 	bool _isSuppressed;
 };
 


Commit: 53a20b9002c5720242ac5f6140b8cdb416477bd9
    https://github.com/scummvm/scummvm/commit/53a20b9002c5720242ac5f6140b8cdb416477bd9
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-14T22:54:17+03:00

Commit Message:
NANCY: Finish implementing RaycastPuzzle

The raycasted maze puzzle in nancy3 is now completable
and nearly feature-complete. This includes proper lighting,
non-wonky movement, and sound FX.
Currently still missing  are the debug features like showing
the map, enlarging the 3D viewport, moving up/down,
and ghosting through walls. Since they're not supposed to
be used by players, they were deemed nonessential and
will be implemented at a later point.

Changed paths:
    engines/nancy/action/puzzle/raycastpuzzle.cpp
    engines/nancy/action/puzzle/raycastpuzzle.h


diff --git a/engines/nancy/action/puzzle/raycastpuzzle.cpp b/engines/nancy/action/puzzle/raycastpuzzle.cpp
index 9e879d1fe36..769cfb498cc 100644
--- a/engines/nancy/action/puzzle/raycastpuzzle.cpp
+++ b/engines/nancy/action/puzzle/raycastpuzzle.cpp
@@ -86,7 +86,6 @@ public:
 	void writeDoors(uint startX, uint startY, uint themeID);
 	void writeLightSwitch(uint startX, uint startY, uint quadrant);
 	void writeExitFloorTexture(uint themeID);
-	void validateMap();
 
 	Common::Array<uint32> _wallMap, _infoMap;
 	Common::Array<int16> _floorMap, _ceilingMap;
@@ -140,7 +139,6 @@ RaycastLevelBuilder::RaycastLevelBuilder(uint width, uint height, uint verticalH
 	fillWalls();
 	fillLocalWallAndInfo();
 	writeThemesAndExitFloor();
-	validateMap();
 }
 
 void RaycastLevelBuilder::fillCells() {
@@ -762,6 +760,7 @@ void RaycastLevelBuilder::writeDoors(uint startX, uint startY, uint themeID) {
 		}
 
 		// Subtract 2 from all lightmap values when a door is added
+		// This looks extremely ugly but the original devs must've added it for a reason
 		byte lowWall, midWall, highWall;
 		lowWall = lightmapValue & 0xF;
 		midWall = (lightmapValue >> 4) & 0xF;
@@ -786,7 +785,7 @@ void RaycastLevelBuilder::writeDoors(uint startX, uint startY, uint themeID) {
 	}
 }
 
-void RaycastLevelBuilder::writeLightSwitch(uint startX, uint startY, uint quadrant) {
+void RaycastLevelBuilder::writeLightSwitch(uint startX, uint startY, uint switchID) {
 	bool foundSwitchLocation = false;
 
 	for (uint checkedCells = 0; checkedCells < _fullNumCells && !foundSwitchLocation; ++checkedCells) {
@@ -798,7 +797,7 @@ void RaycastLevelBuilder::writeLightSwitch(uint startX, uint startY, uint quadra
 		}
 
 		if (foundSwitchLocation) {
-			_infoMap[y * _fullWidth + x] = (quadrant << 8) | 2;
+			_infoMap[y * _fullWidth + x] = (switchID << 8) | 2;
 
 			uint lightmapValue = _floorCeilingLightMap[y * _fullWidth + x];
 
@@ -844,20 +843,27 @@ void RaycastLevelBuilder::writeExitFloorTexture(uint themeID) {
 	}
 }
 
-void RaycastLevelBuilder::validateMap() {
-	for (uint y = 0; y < _fullHeight; ++y) {
-		for (uint x = 0; x < _fullWidth; ++x) {
-			if (_wallMap[y * _fullWidth + x] == 1) {
+void RaycastPuzzle::validateMap() {
+	for (uint y = 0; y < _mapFullHeight; ++y) {
+		for (uint x = 0; x < _mapFullWidth; ++x) {
+			if (_wallMap[y * _mapFullWidth + x] == 1) {
 				error("wallMap not complete at coordinates x = %d, y = %d", x, y);
 			}
 
-			if (_floorMap[y * _fullWidth + x] == -1) {
+			if (_floorMap[y * _mapFullWidth + x] == -1) {
 				error("floorMap not complete at coordinates x = %d, y = %d", x, y);
 			}
 
-			if (_ceilingMap[y * _fullWidth + x] == -1) {
+			if (_ceilingMap[y * _mapFullWidth + x] == -1) {
 				error("wallMap not complete at coordinates x = %d, y = %d", x, y);
 			}
+
+			// Find light switches
+			if ((_infoMap[y * _mapFullWidth + x] & 0xFF) == 2) {
+				_lightSwitchIDs.push_back((_infoMap[y * _mapFullWidth + x] >> 8) & 0xFF);
+				_lightSwitchPositions.push_back(Common::Point(x, y));
+				_lightSwitchStates.push_back(false);
+			}
 		}
 	}
 }
@@ -877,7 +883,7 @@ public:
 private:
 	bool loadInner() override;
 
-	enum State { kInitDrawSurface, kCopyData, kInitMap, kInitTables1, kInitTables2, kLoadTextures };
+	enum State { kInitDrawSurface, kInitPlayerLocationRotation, kCopyData, kInitMap, kInitTables1, kInitTables2, kLoadTextures };
 
 	State _loadState;
 
@@ -898,9 +904,31 @@ bool RaycastDeferredLoader::loadInner() {
 		_owner._drawSurface.create(viewport.width(), viewport.height(), g_nancy->_graphicsManager->getInputPixelFormat());
 		_owner.setTransparent(true);
 
-		_loadState = kCopyData;
+		_loadState = kInitPlayerLocationRotation;
 		break;
 	}
+	case kInitPlayerLocationRotation :
+		if (	_builder._wallMap[_builder._startY * _builder._fullWidth + _builder._startX + 1] == 0 &&
+					_builder._wallMap[_builder._startY * _builder._fullWidth + _builder._startX + 2] == 0) {
+			_owner._playerRotation = 0;
+		} else if (	_builder._wallMap[(_builder._startY - 1) * _builder._fullWidth + _builder._startX] == 0 &&
+					_builder._wallMap[(_builder._startY - 2) * _builder._fullWidth + _builder._startX] == 0) {
+			_owner._playerRotation = 1024;
+		} else if (	_builder._wallMap[_builder._startY * _builder._fullWidth + _builder._startX - 1] == 0 &&
+					_builder._wallMap[_builder._startY * _builder._fullWidth + _builder._startX - 2] == 0) {
+			_owner._playerRotation = 2048;
+		} else if (	_builder._wallMap[(_builder._startY + 1) * _builder._fullWidth + _builder._startX] == 0 &&
+					_builder._wallMap[(_builder._startY + 2) * _builder._fullWidth + _builder._startX] == 0) {
+			_owner._playerRotation = 3072;
+		} else {
+			_owner._playerRotation = 512;
+		}
+
+		_owner._playerX = _builder._startX * 128 + 64;
+		_owner._playerY = _builder._startY * 128 + 64;
+
+		_loadState = kCopyData;
+		break;
 	case kCopyData :
 		_owner._wallMap.swap(_builder._wallMap);
 		_owner._infoMap.swap(_builder._infoMap);
@@ -909,24 +937,17 @@ bool RaycastDeferredLoader::loadInner() {
 		_owner._heightMap.swap(_builder._heightMap);
 		_owner._wallLightMap.swap(_builder._wallLightMap);
 		_owner._floorCeilingLightMap.swap(_builder._floorCeilingLightMap);
+		_owner._wallLightMapBackup = _owner._wallLightMap;
+		_owner._floorCeilingLightMapBackup = _owner._floorCeilingLightMap;
 		_owner._mapFullWidth = _builder._fullWidth;
 		_owner._mapFullHeight = _builder._fullHeight;
 
 		_loadState = kInitMap;
 		break;
 	case kInitMap : {
-		// TODO map is a debug feature, make sure to hide it
-		// Also, fix the fact that it's rendered upside-down
-		const BSUM *bootSummary = (const BSUM *)g_nancy->getEngineData("BSUM");
-		assert(bootSummary);
-
-		_owner._map._drawSurface.create(_owner._mapFullWidth, _owner._mapFullHeight, g_nancy->_graphicsManager->getInputPixelFormat());
-		Common::Rect mapPos(bootSummary->textboxScreenPosition);
-		mapPos.setWidth(_owner._mapFullWidth * 2);
-		mapPos.setHeight(_owner._mapFullHeight * 2);
-		_owner._map.moveTo(mapPos);
-		_owner._map.init();
 		_owner.drawMap();
+		// TODO: Add console command for setting debug features (map, player height, screen size, ghost mode, etc.)
+		_owner._map.setVisible(false);
 
 		_loadState = kInitTables1;
 		break;
@@ -1032,10 +1053,7 @@ bool RaycastDeferredLoader::loadInner() {
 				}
 			}
 
-			// TODO these need to be set according to the start position in _infoMap
-			_owner._playerRotation = 2048;
-			_owner._playerX = _owner._playerY = 320;
-			
+			_owner.validateMap();			
 			_isDone = true;
 		}
 
@@ -1092,16 +1110,28 @@ void RaycastPuzzle::execute() {
 		init();
 		break;
 	case kRun:
-		// TODO check light switches
-		// TODO check exit
+		checkSwitch();
+		checkExit();
 
 		break;
 	case kActionTrigger:
+		if (g_nancy->_sound->isSoundPlaying(_solveSound)) {
+			return;
+		}
+
+		g_nancy->_sound->stopSound(_solveSound);
+		g_nancy->_sound->stopSound(_dummySound);
+		_solveScene.execute();
+		finishExecution();
 		break;
 	}
 }
 
 void RaycastPuzzle::handleInput(NancyInput &input) {
+	if (_state != kRun) {
+		return;
+	}
+	
 	uint32 time = g_nancy->getTotalPlayTime();
 	uint32 deltaTime = time - _lastMovementTime;
 	_lastMovementTime = time;
@@ -1152,24 +1182,24 @@ void RaycastPuzzle::handleInput(NancyInput &input) {
 
 	clampRotation(_playerRotation);
 
-	int32 newX = _playerX;
-	int32 newY = _playerY;
+	float newX = _playerX;
+	float newY = _playerY;
 	bool hasMoved = false;
 	bool hasMovedSlowdown = false;
 
 	// Move forward/backwards
-
-	// Invert the rotation to avoid some annoying precision errors
-	int32 invertedRotation = 4095 - (_playerRotation + 2048);
-	clampRotation(invertedRotation);
+	// Improvement: we do _not_ use the sin/cos tables since they produce wonky movement
+	float fRotation = (float)(4095 - _playerRotation) / 4096.0;
+	float dX = sin(fRotation * _pi * 2) * deltaPosition;
+	float dY = cos(fRotation * _pi * 2) * deltaPosition;
 
 	if (input.input & NancyInput::kMoveUp || (input.input & NancyInput::kLeftMouseButtonHeld && mouseIsInBounds)) {
 		if (input.input & NancyInput::kMoveFastModifier) {
-			newX = _playerX - (int32)(_sinTable[invertedRotation] * deltaPosition) * 2;
-			newY = _playerY - (int32)(_cosTable[invertedRotation] * deltaPosition) * 2;
+			newX = _playerX + dX * 2;
+			newY = _playerY + dY * 2;
 		} else {
-			newX = _playerX - (int32)(_sinTable[invertedRotation] * deltaPosition);
-			newY = _playerY - (int32)(_cosTable[invertedRotation] * deltaPosition);
+			newX = _playerX + dX;
+			newY = _playerY + dY;
 		}
 
 		hasMoved = true;
@@ -1177,17 +1207,19 @@ void RaycastPuzzle::handleInput(NancyInput &input) {
 
 	if (input.input & NancyInput::kMoveDown || (input.input & NancyInput::kRightMouseButtonHeld && mouseIsInBounds)) {
 		if (input.input & NancyInput::kMoveFastModifier) {
-			newX = _playerX + (int32)(_sinTable[invertedRotation] * deltaPosition) * 2;
-			newY = _playerY + (int32)(_cosTable[invertedRotation] * deltaPosition) * 2;
+			newX = _playerX - dX * 2;
+			newY = _playerY - dY * 2;
 		} else {
-			newX = _playerX + (int32)(_sinTable[invertedRotation] * deltaPosition);
-			newY = _playerY + (int32)(_cosTable[invertedRotation] * deltaPosition);
+			newX = _playerX - dX;
+			newY = _playerY - dY;
 		}
 
 		hasMoved = true;
 	}
 
 	// Perform slowdown
+	// Improvement: the original engine's slowdown is VERY buggy
+	// and almost never actually works
 	if (!hasMoved && _nextSlowdownMovementTime < time && _slowdownFramesLeft > 0) {
 		_slowdownDeltaX = (float)_slowdownDeltaX * 9.0 / 10.0;
 		_slowdownDeltaY = (float)_slowdownDeltaY * 9.0 / 10.0;
@@ -1201,8 +1233,13 @@ void RaycastPuzzle::handleInput(NancyInput &input) {
 
 	// Perform collision
 	if (hasMoved) {
-		uint yGrid = newX >> 7;
-		uint xGrid = newY >> 7;
+		uint yGrid = ((int32)newX) >> 7;
+		uint xGrid = ((int32)newY) >> 7;
+
+		int32 xCell = ((int32)newX) & 0x7F;
+		int32 yCell = ((int32)newY) & 0x7F;
+
+		int collisionSize = 48;
 
 		// Check neighboring cells
 		uint32 cellLeft = xGrid > 0 ? _wallMap[yGrid * _mapFullWidth + xGrid - 1] : 1;
@@ -1216,31 +1253,26 @@ void RaycastPuzzle::handleInput(NancyInput &input) {
 		cellRight = (cellRight & kDoor) ? 0 : cellRight;
 		cellBottom = (cellBottom & kDoor) ? 0 : cellBottom;
 
-		int collisionSize = 48;
-
-		int32 xCell = newX & 0x7F;
-		int32 yCell = newY & 0x7F;
-
 		if (cellLeft && yCell < collisionSize) {
-			newY = (newY & 0xFF80) + collisionSize;
+			newY = (((int32)newY) & 0xFF80) + collisionSize;
 		}
 
 		if (cellTop && xCell < collisionSize) {
-			newX = (newX & 0xFF80) + collisionSize;
+			newX = (((int32)newX) & 0xFF80) + collisionSize;
 		}
 
 		if (cellRight && yCell > (128 - collisionSize)) {
-			newY = (newY & 0xFF80) + (128 - collisionSize);
+			newY = (((int32)newY) & 0xFF80) + (128 - collisionSize);
 		}
 
 		if (cellBottom && xCell > (128 - collisionSize)) {
-			newX = (newX & 0xFF80) + (128 - collisionSize);
+			newX = (((int32)newX) & 0xFF80) + (128 - collisionSize);
 		}
 
-		yGrid = newX >> 7;
-		xGrid = newY >> 7;
-		yCell = newX & 0x7F;
-		xCell = newY & 0x7F;
+		yGrid = ((int32)newX) >> 7;
+		xGrid = ((int32)newY) >> 7;
+		yCell = ((int32)newX) & 0x7F;
+		xCell = ((int32)newY) & 0x7F;
 
 		cellLeft = xGrid > 0 ? _wallMap[yGrid * _mapFullWidth + xGrid - 1] : 1;
 		cellTop = yGrid > 0 ? _wallMap[(yGrid - 1) * _mapFullWidth + xGrid] : 1;
@@ -1262,24 +1294,39 @@ void RaycastPuzzle::handleInput(NancyInput &input) {
 		cellBottomLeft = (cellBottomLeft & kDoor) ? 0 : cellBottomLeft;
 		cellBottomRight = (cellBottomRight & kDoor) ? 0 : cellBottomRight;
 
-		collisionSize = 21;
-
 		// Make sure the player doesn't clip diagonally into a wall
-		// TODO this is still wonky
+		// Improvement: in the original engine the player just gets stuck when hitting a corner;
+		// instead, we move along smoothly 
 		if (cellTopLeft && !cellLeft && !cellTop && (yCell < collisionSize) && (xCell < collisionSize)) {
-			return;
+			if (yCell > xCell) {
+				newX = (((int32)newX) & 0xFF80) + collisionSize;
+			} else {
+				newY = (((int32)newY) & 0xFF80) + collisionSize;
+			}
 		}
 
 		if (cellTopRight && !cellRight && !cellTop && (yCell < collisionSize) && (xCell > (128 - collisionSize))) {
-			return;
+			if (yCell > (128 - xCell)) {
+				newX = (((int32)newX) & 0xFF80) + collisionSize;
+			} else {
+				newY = (((int32)newY) & 0xFF80) + (128 - collisionSize);
+			}
 		}
 
 		if (cellBottomLeft && !cellLeft && !cellBottom && (yCell > (128 - collisionSize)) && (xCell < collisionSize)) {
-			return;
+			if (128 - yCell > xCell) {
+				newX = (((int32)newX) & 0xFF80) + (128 - collisionSize);
+			} else {
+				newY = (((int32)newY) & 0xFF80) + collisionSize;
+			}
 		}
 
 		if (cellBottomRight && !cellRight && !cellBottom && (yCell > (128 - collisionSize)) && (xCell > (128 - collisionSize))) {
-			return;
+			if (128 - yCell > 128 - xCell) {
+				newX = (((int32)newX) & 0xFF80) + (128 - collisionSize);
+			} else {
+				newY = (((int32)newY) & 0xFF80) + (128 - collisionSize);
+			}
 		}
 
 		if (!hasMovedSlowdown) {
@@ -1291,22 +1338,31 @@ void RaycastPuzzle::handleInput(NancyInput &input) {
 
 		_playerX = newX;
 		_playerY = newY;
-
-		debug("x = %u, y = %u", _playerX, _playerY);
 	}
 }
 
 void RaycastPuzzle::updateGraphics() {
 	if (_state == kRun) {
 		drawMaze();
+		updateMap();
 	}
 }
 
 void RaycastPuzzle::drawMap() {
-	_map._drawSurface.clear();
+	// Improvement: the original map is drawn upside-down; ours isn't
+	const BSUM *bootSummary = (const BSUM *)g_nancy->getEngineData("BSUM");
+	assert(bootSummary);
+
+	_mapBaseSurface.create(_mapFullWidth, _mapFullHeight, g_nancy->_graphicsManager->getInputPixelFormat());
+	_map._drawSurface.create(_mapFullWidth, _mapFullHeight, g_nancy->_graphicsManager->getInputPixelFormat());
+	Common::Rect mapPos(bootSummary->textboxScreenPosition);
+	mapPos.setWidth(_mapFullWidth * 2);
+	mapPos.setHeight(_mapFullHeight * 2);
+	_map.moveTo(mapPos);
+	_map.init();
 
 	uint16 *pixelPtr;
-	Graphics::PixelFormat &format = _map._drawSurface.format;
+	Graphics::PixelFormat &format = _mapBaseSurface.format;
 
 	uint16 wallColor = format.RGBToColor(_puzzleData->wallColor[0], _puzzleData->wallColor[1], _puzzleData->wallColor[2]);
 	uint16 uColor6 = format.RGBToColor(_puzzleData->uColor6[0], _puzzleData->uColor6[1], _puzzleData->uColor6[2]);
@@ -1319,7 +1375,7 @@ void RaycastPuzzle::drawMap() {
 	uint16 exitColor = format.RGBToColor(_puzzleData->exitColor[0], _puzzleData->exitColor[1], _puzzleData->exitColor[2]);
 
 	for (uint y = 0; y < _mapFullHeight; ++y) {
-		pixelPtr = (uint16 *)_map._drawSurface.getBasePtr(0, y);
+		pixelPtr = (uint16 *)_mapBaseSurface.getBasePtr(0, _mapFullHeight - y - 1);
 		for (uint x = 0; x < _mapFullWidth; ++x) {
 			uint32 wallMapCell = _wallMap[y * _mapFullHeight + x];
 			uint32 infoMapCell = _infoMap[y * _mapFullHeight + x];
@@ -1362,19 +1418,25 @@ void RaycastPuzzle::drawMap() {
 			++pixelPtr;
 		}
 	}
-
-	_map.setVisible(true);
 }
 
-void RaycastPuzzle::loadTextures() {
-	// TODO this is slow and freezes the engine for a few seconds
-	
+void RaycastPuzzle::updateMap() {
+	if (_map.isVisible()) {
+		_map._drawSurface.blitFrom(_mapBaseSurface);
+		Graphics::PixelFormat &format = _mapBaseSurface.format;
+		uint16 playerColor = format.RGBToColor(_puzzleData->playerColor[0], _puzzleData->playerColor[1], _puzzleData->playerColor[2]);
+		_map._drawSurface.setPixel((((uint)_playerY) >> 7), _mapFullWidth - 1 - (((uint)_playerX) >> 7), playerColor);
+
+		_map.setVisible(true);
+	}
 }
 
 void RaycastPuzzle::createTextureLightSourcing(Common::Array<Graphics::ManagedSurface> *array, const Common::String &textureName) {
 	Graphics::PixelFormat format = g_nancy->_graphicsManager->getInputPixelFormat();
 	array->resize(8);
 
+	uint16 transColor = g_nancy->_graphicsManager->getTransColor();
+
 	g_nancy->_resource->loadImage(textureName, (*array)[0]);
 
 	uint width = (*array)[0].w;
@@ -1389,17 +1451,26 @@ void RaycastPuzzle::createTextureLightSourcing(Common::Array<Graphics::ManagedSu
 	for (uint y = 0; y < height; ++y) {
 		for (uint x = 0; x < width; ++x) {
 			uint offset = y * width + x;
-			byte r, g, b, rStep, gStep, bStep;
-			format.colorToRGB(((uint16 *)(*array)[0].getPixels())[offset], r, g, b);
-			rStep = (float)r / 8.0 * 65536.0;
-			gStep = (float)g / 8.0 * 65536.0;
-			bStep = (float)b / 8.0 * 65536.0;
-			for (uint i = 1; i < 8; ++i) {
-				r -= rStep;
-				g -= gStep;
-				b -= bStep;
-				((uint16 *)(*array)[i].getPixels())[offset] = format.RGBToColor(r, g, b);
+			uint16 color = ((uint16 *)(*array)[0].getPixels())[offset];
+			if (color == transColor) {
+				for (uint i = 1; i < 8; ++i) {
+					// Do not darken transparent color
+					((uint16 *)(*array)[i].getPixels())[offset] = color;
+				}
+			} else {
+				byte r, g, b, rStep, gStep, bStep;
+				format.colorToRGB(color, r, g, b);
+				rStep = (float)r / 8.0;
+				gStep = (float)g / 8.0;
+				bStep = (float)b / 8.0;
+				for (uint i = 1; i < 8; ++i) {
+					r -= rStep;
+					g -= gStep;
+					b -= bStep;
+					((uint16 *)(*array)[i].getPixels())[offset] = format.RGBToColor(r, g, b);
+				}
 			}
+			
 		}
 	}
 }
@@ -1416,7 +1487,7 @@ void RaycastPuzzle::drawMaze() {
 	_drawSurface.clear(_drawSurface.getTransparentColor());
 
 	// Draw walls
-	for (int x = viewBounds.left; x <= viewBounds.right; ++x) {
+	for (int x = viewBounds.left; x < viewBounds.right; ++x) {
 		int32 columnAngleForX = _wallCastColumnAngles[x];
 		int32 rotatedColumnAngleForX = columnAngleForX + _playerRotation;
 		clampRotation(rotatedColumnAngleForX);
@@ -1481,7 +1552,7 @@ void RaycastPuzzle::drawMaze() {
 			Common::Point yGrid(-1, -1);
 
 			if (xDist < _maxWorldDistance) {
-				xGrid.y = (int)xRayX >> 7;
+				xGrid.y = ((int)xRayX) >> 7;
 				xGrid.x = (int)(xRayY / 128.0);
 
 				if (xGrid.x < _mapFullWidth && xGrid.y < _mapFullHeight && xGrid.x >= 0 && xGrid.y >= 0) {
@@ -1491,7 +1562,7 @@ void RaycastPuzzle::drawMaze() {
 
 			if (yDist < _maxWorldDistance) {
 				yGrid.y = (int)(yRayX / 128.0);
-				yGrid.x = (int)yRayY >> 7;
+				yGrid.x = ((int)yRayY) >> 7;
 
 				if (yGrid.x < _mapFullWidth && yGrid.y < _mapFullHeight && yGrid.x >= 0 && yGrid.y >= 0) {
 					yGridTile = _wallMap[yGrid.y * _mapFullWidth + yGrid.x];
@@ -1685,7 +1756,7 @@ void RaycastPuzzle::drawMaze() {
 			if (drawnWallHeight > 1) {
 				uint16 *destPixel = (uint16 *)_drawSurface.getBasePtr(x, drawnWallBottom);
 				byte *zBufferDestPixel = &_zBuffer[drawnWallBottom * _drawSurface.w + x];
-				byte baseLightVal = MIN<byte>(_depthBuffer[x] / 128, 7);
+				byte baseLightVal = MIN<byte>(_depthBuffer[x] / 768, 7);
 				uint srcYSubtractVal = (uint)(((float)numSrcPixelsToDraw / (float)drawnWallHeight) * 65536.0);
 
 				srcY <<= 16;
@@ -1844,7 +1915,6 @@ void RaycastPuzzle::drawMaze() {
 	}
 
 	// Draw floors and ceilings
-	// TODO exit does not get rendered correctly
 	int32 leftAngle = _playerRotation + _leftmostAngle;
 	int32 rightAngle = _playerRotation + _rightmostAngle;
 
@@ -1890,7 +1960,7 @@ void RaycastPuzzle::drawMaze() {
 
 		for (int x = viewBounds.left; x < viewBounds.right; ++x) {
 			if (_zBuffer[floorY * _drawSurface.w + x] != curZBufferDepth) {
-				byte offset = (floorSrcFracY >> 23) * _mapFullWidth + (floorSrcFracX >> 23);
+				uint16 offset = (floorSrcFracY >> 23) * _mapFullWidth + (floorSrcFracX >> 23);
 				*floorDest = *(int16 *)_floorTextures[_floorMap[offset]][_floorCeilingLightMap[offset] & 0xF].getBasePtr((floorSrcFracX >> 16) & 0x7F, (floorSrcFracY >> 16) & 0x7F);
 			}
 
@@ -1899,7 +1969,7 @@ void RaycastPuzzle::drawMaze() {
 			floorSrcFracY += floorSrcIncrementY;
 
 			if (_zBuffer[ceilingY * _drawSurface.w + x] != curZBufferDepth) {
-				byte offset = (ceilingSrcFracY >> 23) * _mapFullWidth + (ceilingSrcFracX >> 23);
+				uint16 offset = (ceilingSrcFracY >> 23) * _mapFullWidth + (ceilingSrcFracX >> 23);
 				*ceilingDest = *(int16 *)_ceilingTextures[_ceilingMap[offset]][(_floorCeilingLightMap[offset] >> 4) & 0xF].getBasePtr((ceilingSrcFracX >> 16) & 0x7F, (ceilingSrcFracY >> 16) & 0x7F);
 			}
 
@@ -1926,7 +1996,73 @@ void RaycastPuzzle::clearZBuffer() {
 }
 
 void RaycastPuzzle::checkSwitch() {
+	// X/Y swapping intentional. The axes get mixed up somewhere between level generation
+	// and running and I'm not really sure where
+	Common::Point gridPos(((uint)_playerY) >> 7, ((uint)_playerX) >> 7);
+
+	for (int i = 0; i < (int)_lightSwitchPositions.size(); ++i) {
+		if (_lightSwitchPositions[i] == gridPos) {
+			if (_lightSwitchPlayerIsOn != i) {
+				// Player just stepped on light switch
+				_lightSwitchPlayerIsOn = i;
+
+				if (_lightSwitchStates[i] == false) {
+					// Switch was unpressed, press and turn light ON
+					for (uint y = 0; y < _mapFullHeight; ++y) {
+						for (uint x = 0; x < _mapFullWidth; ++x) {
+							if ((_wallLightMap[y * _mapFullWidth + x] >> 0xC) == _lightSwitchIDs[i]) {
+								_wallLightMap[y * _mapFullWidth + x] &= 0xF000;
+							}
+
+							if ((_floorCeilingLightMap[y * _mapFullWidth + x] >> 0xC) == _lightSwitchIDs[i]) {
+								_floorCeilingLightMap[y * _mapFullWidth + x] &= 0xF000;
+							}
+						}
+					}
+
+					_dummySound.name = _switchSoundName;
+					_dummySound.channelID = _switchSoundChannelID;
+					g_nancy->_sound->loadSound(_dummySound);
+					g_nancy->_sound->playSound(_dummySound);
 
+					_lightSwitchStates[i] = true;
+				} else {
+					// Switch was pressed, unpress and turn light OFF
+					for (uint y = 0; y < _mapFullHeight; ++y) {
+						for (uint x = 0; x < _mapFullWidth; ++x) {
+							if ((_wallLightMap[y * _mapFullWidth + x] >> 0xC) == _lightSwitchIDs[i]) {
+								_wallLightMap[y * _mapFullWidth + x] = _wallLightMapBackup[y * _mapFullWidth + x];
+							}
+
+							if ((_floorCeilingLightMap[y * _mapFullWidth + x] >> 0xC) == _lightSwitchIDs[i]) {
+								_floorCeilingLightMap[y * _mapFullWidth + x] = _floorCeilingLightMapBackup[y * _mapFullWidth + x];
+							}
+						}
+					}
+
+					_lightSwitchStates[i] = false;
+				}
+			}
+		} else {
+			if (_lightSwitchPlayerIsOn == i) {
+				// Player just stepped off light switch
+
+				_lightSwitchPlayerIsOn = -1;
+			}
+		}
+	}
+}
+
+void RaycastPuzzle::checkExit() {
+	// X/Y swapping intentional; see above
+	Common::Point gridPos(((uint)_playerY) >> 7, ((uint)_playerX) >> 7);
+
+	if (_infoMap[gridPos.y * _mapFullWidth + gridPos.x] == 1) {
+		g_nancy->_sound->loadSound(_solveSound);
+		g_nancy->_sound->playSound(_solveSound);
+
+		_state = kActionTrigger;
+	}
 }
 
 } // End of namespace Action
diff --git a/engines/nancy/action/puzzle/raycastpuzzle.h b/engines/nancy/action/puzzle/raycastpuzzle.h
index 6b6af1ccdba..9ab2bc67830 100644
--- a/engines/nancy/action/puzzle/raycastpuzzle.h
+++ b/engines/nancy/action/puzzle/raycastpuzzle.h
@@ -53,14 +53,17 @@ protected:
 	Common::String getRecordTypeName() const override { return "RaycastPuzzle"; }
 	bool isViewportRelative() const override { return true; }
 
-	void loadTextures();
+	void validateMap();
+
 	void createTextureLightSourcing(Common::Array<Graphics::ManagedSurface> *array, const Common::String &textureName);
 
 	void drawMap();
+	void updateMap();
 	void drawMaze();
 	void clearZBuffer();
 
 	void checkSwitch();
+	void checkExit();
 
 	uint16 _mapWidth = 0;
 	uint16 _mapHeight = 0;
@@ -78,11 +81,13 @@ protected:
 	Common::Array<uint32> _wallMap, _infoMap;
 	Common::Array<int16> _floorMap, _ceilingMap;
 	Common::Array<uint16> _wallLightMap, _floorCeilingLightMap, _heightMap;
+	Common::Array<uint16> _wallLightMapBackup, _floorCeilingLightMapBackup;
 
 	uint16 _mapFullWidth = 0;
 	uint16 _mapFullHeight = 0;
 
 	RenderObject _map;
+	Graphics::ManagedSurface _mapBaseSurface;
 
 	double _pi = 3.141592653589793;
 	uint _fov = 192;
@@ -101,8 +106,9 @@ protected:
 	int32 _leftmostAngle = -1;
 	int32 _rightmostAngle = -1;
 
-	int32 _playerX = -1;				// Player position with precision 1/128th of cell width/height
-	int32 _playerY = -1;
+										// Improvement: we store position as float for smoother movement
+	float _playerX = -1;				// Player position with precision 1/128th of cell width/height
+	float _playerY = -1;
 	int32 _playerRotation = 0; 			// Rotation of player (0 - 4096)
 	uint32 _playerAltitude = 88;		// Z position of "camera"; only modified in god mode
 
@@ -117,6 +123,11 @@ protected:
 	int32 _slowdownDeltaX = -1;
 	int32 _slowdownDeltaY = -1;
 
+	Common::Array<byte> _lightSwitchIDs;
+	Common::Array<Common::Point> _lightSwitchPositions;
+	Common::Array<bool> _lightSwitchStates;
+	int _lightSwitchPlayerIsOn = -1;
+
 	const RCPR *_puzzleData = nullptr;
 	Common::SharedPtr<RaycastDeferredLoader> _loaderPtr;
 };


Commit: 78522c9122b2c823ffa765c02c655188bcc0dd9c
    https://github.com/scummvm/scummvm/commit/78522c9122b2c823ffa765c02c655188bcc0dd9c
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-09-14T22:54:17+03:00

Commit Message:
NANCY: Silence compiler warning

Changed paths:
    engines/nancy/action/puzzle/orderingpuzzle.cpp


diff --git a/engines/nancy/action/puzzle/orderingpuzzle.cpp b/engines/nancy/action/puzzle/orderingpuzzle.cpp
index 016869569b3..3bb6f021bd3 100644
--- a/engines/nancy/action/puzzle/orderingpuzzle.cpp
+++ b/engines/nancy/action/puzzle/orderingpuzzle.cpp
@@ -75,7 +75,7 @@ void OrderingPuzzle::readData(Common::SeekableReadStream &stream) {
 	Common::Serializer ser(&stream, nullptr);
 	ser.setVersion(g_nancy->getGameType());
 
-	uint16 numElements;
+	uint16 numElements = 5;
 	uint16 maxNumElements = 15;
 	if (ser.getVersion() == kGameTypeVampire) {
 		// Hardcoded in The Vampire Diaries




More information about the Scummvm-git-logs mailing list