[Scummvm-git-logs] scummvm master -> a7e063e71faa4b332d5f67bee6e1130ef11213c7

fracturehill noreply at scummvm.org
Sun Aug 6 16:32:56 UTC 2023


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

Summary:
a7e063e71f NANCY: Partially implement RaycastPuzzle


Commit: a7e063e71faa4b332d5f67bee6e1130ef11213c7
    https://github.com/scummvm/scummvm/commit/a7e063e71faa4b332d5f67bee6e1130ef11213c7
Author: Kaloyan Chehlarski (strahy at outlook.com)
Date: 2023-08-06T19:29:10+03:00

Commit Message:
NANCY: Partially implement RaycastPuzzle

Added code to support nancy3's raycasted maze minigame.
The minigame (mostly) works but is not yet completable,
and needs further optimization work.

Changed paths:
  A engines/nancy/action/raycastpuzzle.cpp
  A engines/nancy/action/raycastpuzzle.h
    engines/nancy/action/arfactory.cpp
    engines/nancy/enginedata.cpp
    engines/nancy/enginedata.h
    engines/nancy/module.mk
    engines/nancy/nancy.cpp
    engines/nancy/nancy.h


diff --git a/engines/nancy/action/arfactory.cpp b/engines/nancy/action/arfactory.cpp
index 099a4e2af8f..db5cc6caaee 100644
--- a/engines/nancy/action/arfactory.cpp
+++ b/engines/nancy/action/arfactory.cpp
@@ -37,6 +37,7 @@
 #include "engines/nancy/action/bombpuzzle.h"
 #include "engines/nancy/action/soundequalizerpuzzle.h"
 #include "engines/nancy/action/setplayerclock.h"
+#include "engines/nancy/action/raycastpuzzle.h"
 
 #include "engines/nancy/state/scene.h"
 
@@ -168,6 +169,8 @@ ActionRecord *ActionManager::createActionRecord(uint16 type) {
 		return new OverrideLockPuzzle();
 	case 205:
 		return new RiddlePuzzle();
+	case 206:
+		return new RaycastPuzzle();
 	default:
 		error("Action Record type %i is invalid!", type);
 		return nullptr;
diff --git a/engines/nancy/action/raycastpuzzle.cpp b/engines/nancy/action/raycastpuzzle.cpp
new file mode 100644
index 00000000000..f4d44508e2c
--- /dev/null
+++ b/engines/nancy/action/raycastpuzzle.cpp
@@ -0,0 +1,1839 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "engines/nancy/graphics.h"
+#include "engines/nancy/nancy.h"
+#include "engines/nancy/sound.h"
+#include "engines/nancy/input.h"
+#include "engines/nancy/util.h"
+#include "engines/nancy/enginedata.h"
+#include "engines/nancy/resource.h"
+
+#include "engines/nancy/action/raycastpuzzle.h"
+#include "engines/nancy/state/scene.h"
+
+#include "common/stack.h"
+#include "common/random.h"
+
+namespace Nancy {
+namespace Action {
+
+enum WallFlags {
+	kWall 				= 0x01000000,
+	kVertical			= 0x02000000,
+	kHorizontal			= 0x04000000,
+	kDoor				= 0x08000000,
+	kHasBlankWalls		= 0x10000000,
+	kTransparentWall 	= 0x20000000,
+};
+
+static const uint16 wallLightmapValues[8] = {
+	1 << 0 | 0 << 4 | 0 << 8,
+	1 << 0 | 0 << 4 | 0 << 8,
+	2 << 0 | 1 << 4 | 0 << 8,
+	4 << 0 | 2 << 4 | 0 << 8,
+	5 << 0 | 3 << 4 | 2 << 8,
+	5 << 0 | 4 << 4 | 3 << 8,
+	6 << 0 | 5 << 4 | 4 << 8,
+	7 << 0 | 6 << 4 | 5 << 8
+};
+
+static const byte floorCeilingLightmapValues[8] = {
+	0 << 0 | 0 << 4,
+	1 << 0 | 0 << 4,
+	2 << 0 | 0 << 4,
+	3 << 0 | 1 << 4,
+	4 << 0 | 2 << 4,
+	5 << 0 | 3 << 4,
+	6 << 0 | 4 << 4,
+	7 << 0 | 5 << 4
+};
+
+void clampRotation(int32 &rot) {
+	rot = rot < 0 ? rot + 4096 : rot;
+	rot = rot > 4095 ? rot - 4096 : rot;
+}
+
+class RaycastLevelBuilder {
+public:
+	RaycastLevelBuilder(uint width, uint height, uint verticalHeight);
+
+	void fillCells();
+	void fillWalls();
+	void fillLocalWallAndInfo();
+	void writeThemesAndExitFloor();
+	uint writeTheme(uint startX, uint startY, uint quadrant);
+	void writeTransparentWalls(uint startX, uint startY, uint themeID);
+	void writeObjectWalls(uint startX, uint startY, uint themeID);
+	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;
+	Common::Array<uint16> _wallLightMap, _floorCeilingLightMap, _heightMap;
+
+	uint _inputWidth, _inputHeight, _inputNumCells;
+	uint _halfWidth, _halfHeight, _halfNumCells;
+	uint _fullWidth, _fullHeight, _fullNumCells;
+	uint _verticalHeight;
+	float _objectsBaseDensity;
+
+	uint _startX, _startY;
+
+	Common::Array<uint16> _cells;
+	Common::Array<byte> _walls;
+
+	RCLB *_themeData;
+};
+
+RaycastLevelBuilder::RaycastLevelBuilder(uint width, uint height, uint verticalHeight) {
+	_themeData = g_nancy->_raycastPuzzleLevelBuilderData;
+
+	assert(_themeData);
+
+	_verticalHeight = verticalHeight;
+
+	_inputWidth = width;
+	_inputHeight = height;
+	_inputNumCells = width * height;
+
+	_halfWidth = width * 2 + 1;
+	_halfHeight = height * 2 + 1;
+	_halfNumCells = _halfWidth * _halfHeight;
+
+	_fullWidth = _halfWidth * 2;
+	_fullHeight = _halfHeight * 2;
+	_fullNumCells = _fullWidth * _fullHeight;
+
+	_objectsBaseDensity = (float)_fullNumCells / 1764.0;
+
+	_cells.resize(_inputNumCells, 0xF);
+	_walls.resize(_halfNumCells, 0);
+	_wallMap.resize(_fullNumCells, 0);
+	_floorMap.resize(_fullNumCells, -1);
+	_ceilingMap.resize(_fullNumCells, -1);
+	_infoMap.resize(_fullNumCells, 0);
+	_wallLightMap.resize(_fullNumCells, 0);
+	_floorCeilingLightMap.resize(_fullNumCells, 0);
+	_heightMap.resize(_fullNumCells, verticalHeight * 128);
+
+	fillCells();
+	fillWalls();
+	fillLocalWallAndInfo();
+	writeThemesAndExitFloor();
+	validateMap();
+}
+
+void RaycastLevelBuilder::fillCells() {
+	Common::Stack<Common::Point> cellStack;
+	Common::Array<byte> switchStack;
+
+	Common::Point curCell(0, 0);
+	
+	for (uint i = 1; i < _inputNumCells;) {
+		switchStack.clear();
+
+		if ( (curCell.y > 0) && ((_cells[(curCell.y - 1) * _inputWidth + curCell.x] & 0xF) == 0xF) ) {
+			switchStack.push_back(2);
+		}
+		
+		if ( (curCell.y + 1 <= (int)_inputHeight - 1) && ((_cells[(curCell.y + 1) * _inputWidth + curCell.x] & 0xF) == 0xF) ) {
+			switchStack.push_back(3);
+		}
+
+		if ( (curCell.x > 0) && ((_cells[curCell.y * _inputWidth + curCell.x - 1] & 0xF) == 0xF) ) {
+			switchStack.push_back(0);
+		}
+
+		if ( (curCell.x + 1 <= (int)_inputWidth - 1) && ((_cells[curCell.y * _inputWidth + curCell.x + 1] & 0xF) == 0xF) ) {
+			switchStack.push_back(1);
+		}
+
+		uint numOptions = switchStack.size();
+		if (numOptions) {
+			switch (switchStack[g_nancy->_randomSource->getRandomNumber(numOptions - 1)]) {
+			case 0:
+				_cells[curCell.y * _inputWidth + curCell.x] 		&= 0xFFF7;
+				_cells[curCell.y * _inputWidth + curCell.x - 1] 	&= 0xFFFB;
+				cellStack.push(curCell);
+				--curCell.x;
+				break;
+			case 1:
+				_cells[curCell.y * _inputWidth + curCell.x] 		&= 0xFFFB;
+				_cells[curCell.y * _inputWidth + curCell.x + 1] 	&= 0xFFF7;
+				cellStack.push(curCell);
+				++curCell.x;
+				break;
+			case 2:
+				_cells[curCell.y * _inputWidth + curCell.x] 		&= 0xFFFD;
+				_cells[(curCell.y - 1) * _inputWidth + curCell.x] 	&= 0xFFFE;
+				cellStack.push(curCell);
+				--curCell.y;
+				break;
+			case 3:
+				_cells[curCell.y * _inputWidth + curCell.x] 		&= 0xFFFE;
+				_cells[(curCell.y + 1) * _inputWidth + curCell.x] 	&= 0xFFFD;
+				cellStack.push(curCell);
+				++curCell.y;
+				break;
+			}
+
+			++i;
+		} else {
+			curCell = cellStack.pop();
+		}
+	}
+}
+
+void RaycastLevelBuilder::fillWalls() {
+	// Surround the whole map with walls
+	for (uint y = 0; y < _halfHeight; ++y) {
+		if (y == 0 || y == _halfHeight - 1) {
+			for (uint x = 0; x < _halfWidth; ++x) {
+				_walls[y * _halfWidth + x] |= 1;
+			}
+		} else {
+			_walls[y * _halfWidth] |= 1;
+			_walls[y * _halfWidth + _halfWidth - 1] |= 1;
+		}
+	}
+
+	uint y = 1;
+	for (uint j = 0; j < _inputHeight; ++j) {
+		uint x = 1;
+		for (uint i = 0; i < _inputWidth; ++i) {
+			if (j == _inputHeight - 1 && i == _inputHeight - 1) {
+				_walls[y * _halfWidth + x] |= 2;
+			}
+
+			if (j == 0 && i == 0) {
+				_walls[y * _halfWidth + x] |= 4;
+			}
+
+			if (_cells[j * _inputWidth + i] & 4) {
+				_walls[y * _halfWidth + x + 1] |= 1;
+			}
+
+			if (_cells[j * _inputWidth + i] & 1) {
+				_walls[(y + 1) * _halfWidth + x] |= 1;
+			}
+
+			_walls[(y + 1) * _halfWidth + x + 1] |= 1;
+			x += 2;
+		}
+
+		y += 2;
+	}
+}
+
+void RaycastLevelBuilder::fillLocalWallAndInfo() {
+	uint y = 0;
+	for (uint j = 0; j < _halfHeight; ++j) {
+		uint x = 0;
+		for (uint i = 0; i < _halfWidth; ++i) {
+			byte curCell = _walls[j * _halfWidth + i];
+
+			if (curCell & 2) { // end point flag
+				_infoMap[y * _fullWidth + x] = 1;
+				_infoMap[y * _fullWidth + x + 1] = 1;
+				_infoMap[(y + 1) * _fullWidth + x] = 1;
+				_infoMap[(y + 1) * _fullWidth + x + 1] = 1;
+			}
+
+			if (curCell & 4) { // start point flag
+				_startX = x;
+				_startY = y;
+			}
+
+			if (curCell & 1) {
+				_wallMap[y * _fullWidth + x] = 1;
+				_wallMap[y * _fullWidth + x + 1] = 1;
+				_wallMap[(y + 1) * _fullWidth + x] = 1;
+				_wallMap[(y + 1) * _fullWidth + x + 1] = 1;
+			}
+
+			x += 2;
+		}
+
+		y += 2;
+	}
+}
+
+void RaycastLevelBuilder::writeThemesAndExitFloor() {
+	writeTheme(0, 0, 1);
+	writeTheme(_halfWidth, 0, 2);
+	writeTheme(0, _halfHeight, 3);
+	uint exitThemeID = writeTheme(_halfWidth, _halfHeight, 4);
+
+	writeExitFloorTexture(exitThemeID);
+}
+
+uint RaycastLevelBuilder::writeTheme(uint startX, uint startY, uint quadrant) {
+	uint themeID = g_nancy->_randomSource->getRandomNumber(_themeData->themes.size() - 1);
+	RCLB::Theme &theme = _themeData->themes[themeID];
+
+	uint themeHalfWidth, themeHalfHeight;
+
+	themeHalfWidth = _inputWidth + 1;
+	themeHalfHeight = _inputHeight + 1;
+	themeHalfWidth = themeHalfWidth < 2 ? 1 : themeHalfWidth;
+	themeHalfHeight = themeHalfHeight < 2 ? 1 : themeHalfHeight;
+
+	for (uint i = 0; i < 4; ++i) {
+		uint selectedWallIDs = theme.wallIDs[g_nancy->_randomSource->getRandomNumber(theme.wallIDs.size() - 1)];
+		uint halfY = 0;
+		for (uint fullY = startY + (themeHalfHeight * (i % 2)); halfY < themeHalfHeight && fullY < _fullHeight; ++fullY) {
+			uint halfX = 0;
+			for (uint fullX = startX + (themeHalfWidth * (i / 2)); halfX < themeHalfWidth && fullX < _fullWidth; ++fullX) {
+				if (_wallMap[fullY * _fullWidth + fullX] == 1) {
+					_wallMap[fullY * _fullWidth + fullX] = selectedWallIDs | kWall;
+					_wallLightMap[fullY * _fullWidth + fullX] = wallLightmapValues[theme.generalLighting] | (quadrant << 0xC);
+				}
+				++halfX;
+			}
+			++halfY;
+		}
+	}
+
+	uint selectedFloorID = theme.floorIDs[g_nancy->_randomSource->getRandomNumber(theme.floorIDs.size() - 1)];
+	uint selectedCeilingID = theme.ceilingIDs[g_nancy->_randomSource->getRandomNumber(theme.ceilingIDs.size() - 1)];
+
+	for (uint y = 0; y < _halfHeight; ++y) {
+		for (uint x = 0; x < _halfWidth; ++x) {
+			_floorMap[(startY + y) * _fullWidth + startX + x] = selectedFloorID;
+			_ceilingMap[(startY + y) * _fullWidth + startX + x] = selectedCeilingID;
+			_floorCeilingLightMap[(startY + y) * _fullWidth + startX + x] = floorCeilingLightmapValues[theme.generalLighting] | (quadrant << 0xC);
+		}
+	}
+
+	if (theme.transparentWallDensity > 0) {
+		writeTransparentWalls(startX, startY, themeID);
+	}
+
+	if (theme.objectWallDensity > 0) {
+		writeObjectWalls(startX, startY, themeID);
+	}
+
+	if (theme.doorDensity > 0) {
+		writeDoors(startX, startY, themeID);
+	}
+
+	if (theme.hasLightSwitch) {
+		// This is called with quadrant, NOT themeID
+		writeLightSwitch(startX, startY, quadrant);
+	}
+
+	return themeID;
+}
+
+void RaycastLevelBuilder::writeTransparentWalls(uint startX, uint startY, uint themeID) {
+	RCLB::Theme &theme = _themeData->themes[themeID];
+	uint numWallsToWrite = (int)((float)theme.objectWallDensity * _objectsBaseDensity);
+
+	for (uint numWrittenWalls = 0; numWrittenWalls < numWallsToWrite;) {
+		bool vertical = g_nancy->_randomSource->getRandomBit();
+		bool foundWallLocation = false;
+
+		uint x = 0;
+		uint y = 0;
+		uint otherX = 0;
+		uint otherY = 0;
+
+		for (uint checkedCells = 0; checkedCells < _fullNumCells && !foundWallLocation; ++checkedCells) {
+			x = g_nancy->_randomSource->getRandomNumberRng(MAX<uint>(4, startX), MIN(startX + _halfWidth, _fullWidth - 5));
+			y = g_nancy->_randomSource->getRandomNumberRng(MAX<uint>(4, startY), MIN(startY + _halfHeight, _fullHeight - 5));
+
+			if (_wallMap[y * _fullWidth + x] & kWall) {
+				if (vertical) {
+					if ( 	!(_wallMap[(y - 1) * _fullWidth + x] & kWall) 	||
+							(_wallMap[(y - 2) * _fullWidth + x])				||
+							(_wallMap[(y + 1) * _fullWidth + x]) ) {
+
+						if (	(_wallMap[(y + 1) * _fullWidth + x] & kWall)	&&
+								!(_wallMap[(y + 2) * _fullWidth + x])				&&
+								!(_wallMap[(y - 1) * _fullWidth + x]) ) {
+
+							otherY = y + 1;
+							otherX = x;
+
+							if (	(_wallMap[y * _fullWidth + x - 1] & kWall)		&&
+									(_wallMap[y * _fullWidth + x + 1] & kWall)		&&
+									(_wallMap[otherY * _fullWidth + x - 1] & kWall)	&&
+									(_wallMap[otherY * _fullWidth + x + 1] & kWall) ) {
+
+								foundWallLocation = true;
+							}
+						}
+					} else {
+						otherY = y - 1;
+						otherX = x;
+
+						if (	(_wallMap[y * _fullWidth + x - 1] & kWall)		&&
+								(_wallMap[y * _fullWidth + x + 1] & kWall)		&&
+								(_wallMap[otherY * _fullWidth + x - 1] & kWall)	&&
+								(_wallMap[otherY * _fullWidth + x + 1] & kWall) ) {
+
+							foundWallLocation = true;
+						}
+					}
+				} else {
+					if ( 	!(_wallMap[y * _fullWidth + x - 1] & kWall)	||
+							(_wallMap[y * _fullWidth + x - 2])				||
+							(_wallMap[y * _fullWidth + x + 1]) ) {
+
+						if (	(_wallMap[y * _fullWidth + x + 1] & kWall)	&&
+								!(_wallMap[y * _fullWidth + x + 2])				&&
+								!(_wallMap[y * _fullWidth + x - 1]) ) {
+
+							otherY = y;
+							otherX = x + 1;
+
+							if (	(_wallMap[(y - 1) * _fullWidth + x] & kWall)		&&
+									(_wallMap[(y + 1) * _fullWidth + x] & kWall)		&&
+									(_wallMap[(y - 1) * _fullWidth + otherX] & kWall)	&&
+									(_wallMap[(y + 1) * _fullWidth + otherX] & kWall) ) {
+
+								foundWallLocation = true;
+							}
+						}
+					} else {
+						otherY = y;
+						otherX = x - 1;
+
+						if (	(_wallMap[(y - 1) * _fullWidth + x] & kWall)		&&
+								(_wallMap[(y + 1) * _fullWidth + x] & kWall)		&&
+								(_wallMap[(y - 1) * _fullWidth + otherX] & kWall)	&&
+								(_wallMap[(y + 1) * _fullWidth + otherX] & kWall) ) {
+
+							foundWallLocation = true;
+						}
+					}
+				}
+			}
+		}
+
+		// No more suitable locations, exit
+		if (!foundWallLocation) {
+			break;
+		}
+
+		uint selectedTransparentWallIDs = theme.transparentwallIDs[theme.transparentwallIDs.size() - 1];
+
+		if (vertical) {
+			selectedTransparentWallIDs |= (kVertical | kHasBlankWalls | kTransparentWall);
+		} else {
+			selectedTransparentWallIDs |= (kHorizontal | kHasBlankWalls | kTransparentWall);
+		}
+
+		_wallMap[y * _fullWidth + x] = selectedTransparentWallIDs;
+		_wallMap[otherY * _fullWidth + otherX] = selectedTransparentWallIDs;
+
+		++numWrittenWalls;
+	}
+}
+
+void RaycastLevelBuilder::writeObjectWalls(uint startX, uint startY, uint themeID) {
+	RCLB::Theme &theme = _themeData->themes[themeID];
+	uint numWallsToWrite = (int)((float)theme.objectWallDensity * _objectsBaseDensity);
+
+	uint textureVerticalHeight = _verticalHeight * 128; // 128 is a constant inside RayCast
+
+	for (uint numWrittenWalls = 0; numWrittenWalls < numWallsToWrite;) {
+		bool vertical = g_nancy->_randomSource->getRandomBit();
+		bool foundWallLocation = false;
+		
+		uint x = 0;
+		uint y = 0;
+
+		for (uint checkedCells = 0; checkedCells < _fullNumCells && !foundWallLocation; ++checkedCells) {
+			x = g_nancy->_randomSource->getRandomNumberRng(MAX(startX, 1U), MIN(startX + _halfWidth, _fullWidth - 2));
+			y = g_nancy->_randomSource->getRandomNumberRng(MAX(startY, 1U), MIN(startY + _halfHeight, _fullHeight - 2));
+
+			if (_wallMap[y * _fullWidth + x] & kWall) {
+				if (vertical) {
+					if (	!(_wallMap[(y - 1) * _fullWidth + x] & kWall) 		||
+							!(_wallMap[y * _fullWidth + x - 1] & kWall) 		||
+							!(_wallMap[y * _fullWidth + x + 1] & kWall) 		||
+							!(_wallMap[(y - 1) * _fullWidth + x - 1] & kWall) 	||
+							!(_wallMap[(y - 1) * _fullWidth + x + 1] & kWall) 	||
+							_wallMap[(y + 1) * _fullWidth + x] ) {
+
+						if (	(_wallMap[(y + 1) * _fullWidth + x] & kWall)		&&
+								(_wallMap[y * _fullWidth + x - 1] & kWall)			&&
+								(_wallMap[y * _fullWidth + x + 1] & kWall)			&&
+								(_wallMap[(y + 1) * _fullWidth + x - 1] & kWall) 	&&
+								(_wallMap[(y + 1) * _fullWidth + x + 1] & kWall)	&&
+								!_wallMap[(y - 1) * _fullWidth + x] ) {
+
+							foundWallLocation = true;
+						}
+					} else {
+						foundWallLocation = true;
+					}
+				} else {
+					if (	!(_wallMap[y * _fullWidth + x - 1] & kWall) 		||
+							!(_wallMap[(y - 1) * _fullWidth + x] & kWall) 		||
+							!(_wallMap[(y + 1) * _fullWidth + x] & kWall) 		||
+							!(_wallMap[(y - 1) * _fullWidth + x - 1] & kWall) 	||
+							!(_wallMap[(y + 1) * _fullWidth + x - 1] & kWall) 	||
+							_wallMap[y * _fullWidth + x + 1] ) {
+
+						if (	(_wallMap[y * _fullWidth + x + 1] & kWall)			&&
+								(_wallMap[(y - 1) * _fullWidth + x] & kWall)		&&
+								(_wallMap[(y + 1) * _fullWidth + x] & kWall)		&&
+								(_wallMap[(y - 1) * _fullWidth + x + 1] & kWall) 	&&
+								(_wallMap[(y + 1) * _fullWidth + x + 1] & kWall)	&&
+								!_wallMap[y * _fullWidth + x - 1] ) {
+
+							foundWallLocation = true;
+						}
+					} else {
+						foundWallLocation = true;
+					}
+				}
+			}
+		}
+
+		// No more suitable locations, exit
+		if (!foundWallLocation) {
+			break;
+		}
+
+		// Found a suitable location, pick random object wall
+		uint r = g_nancy->_randomSource->getRandomNumber(theme.objectwallIDs.size() - 1);
+		uint32 selectedObjectWalls = theme.objectwallIDs[r];
+		uint16 selectedWallsHeight = theme.objectWallHeights[r];
+
+		byte lowWall, midWall, highWall;
+		lowWall = selectedObjectWalls & 0xFF;
+		midWall = (selectedObjectWalls >> 8) & 0xFF;
+		highWall = (selectedObjectWalls >> 16) & 0xFF;
+
+		if (textureVerticalHeight - 1 <= selectedWallsHeight) {
+			selectedWallsHeight = textureVerticalHeight - 1;
+		}
+
+		selectedObjectWalls &= 0xFFFFFF;
+
+		if (lowWall == 0 || (midWall == 0 && selectedWallsHeight > 128) || (highWall == 0 && selectedWallsHeight > 256 )) {
+			selectedObjectWalls |= kHasBlankWalls;
+		}
+
+		if (vertical) {
+			selectedObjectWalls |= 0x22000000;
+		} else {
+			selectedObjectWalls |= 0x24000000;
+		}
+
+		// Place the selected object wall on the map
+		_wallMap[y * _fullWidth + x] = selectedObjectWalls;
+		_heightMap[y * _fullWidth + x] = selectedWallsHeight;
+
+		// Subtract 2 from all lightmap values when an object wall is added
+		uint16 lightmapValue = _wallLightMap[y * _fullWidth + x];
+
+		lowWall = lightmapValue & 0xF;
+		midWall = (lightmapValue >> 4) & 0xF;
+		highWall = (lightmapValue >> 8) & 0xF;
+
+		lowWall = (int)lowWall - 2 < 0 ? 0 : lowWall - 2;
+		midWall = (int)midWall - 2 < 0 ? 0 : midWall - 2;
+		highWall = (int)highWall - 2 < 0 ? 0 : highWall - 2;
+
+		_wallLightMap[y * _fullWidth + x] = (lightmapValue & 0xF000) | (highWall << 8) | (midWall << 4) | lowWall;
+
+		lightmapValue = _floorCeilingLightMap[y * _fullWidth + x];
+
+		byte floor, ceiling;
+		floor = lightmapValue & 0xF;
+		ceiling = (lightmapValue >> 4) & 0xF;
+
+		floor = (int)floor - 2 < 0 ? 0 : floor - 2;
+		ceiling = (int)ceiling - 2 < 0 ? 0 : ceiling - 2;
+
+		_floorCeilingLightMap[y * _fullWidth + x] = (lightmapValue & 0xF000) | (ceiling << 4) | floor;
+
+		++numWrittenWalls;
+	}
+}
+
+void RaycastLevelBuilder::writeDoors(uint startX, uint startY, uint themeID) {
+	RCLB::Theme &theme = _themeData->themes[themeID];
+	uint numDoorsToWrite = (int)((float)theme.doorDensity * _objectsBaseDensity);
+
+	for (uint numWrittenWalls = 0; numWrittenWalls < numDoorsToWrite;) {
+		bool vertical = g_nancy->_randomSource->getRandomBit();
+		bool foundDoorLocation = false;
+		
+		uint x = 0;
+		uint y = 0;
+		uint otherX = 0;
+		uint otherY = 0;
+
+		for (uint checkedCells = 0; checkedCells < _fullNumCells && !foundDoorLocation; ++checkedCells) {
+			x = g_nancy->_randomSource->getRandomNumberRng(startX, MIN(startX + _halfWidth, _fullWidth - 1));
+			y = g_nancy->_randomSource->getRandomNumberRng(startY, MIN(startY + _halfHeight, _fullHeight - 1));
+
+			if (!_wallMap[y * _fullWidth + x]) {
+				if (vertical) {
+					if (	!(_wallMap[y * _fullWidth + x - 1])				&&
+							(_wallMap[y * _fullWidth + x + 1] & kWall)	&&
+							(_wallMap[y * _fullWidth + x - 2] & kWall)	&&
+							!(_wallMap[(y - 1) * _fullWidth + x])			&&
+							!(_wallMap[(y - 2) * _fullWidth + x])			&&
+							!(_wallMap[(y + 1) * _fullWidth + x])			&&
+							!(_wallMap[(y + 2) * _fullWidth + x])			&&
+							!(_wallMap[(y - 1) * _fullWidth + x - 1])		&&
+							!(_wallMap[(y - 2) * _fullWidth + x - 1])		&&
+							!(_wallMap[(y + 1) * _fullWidth + x - 1])		&&
+							!(_wallMap[(y + 2) * _fullWidth + x - 1]) ) {
+
+						otherX = x - 1;
+						otherY = y;
+
+						if (	!(_infoMap[y * _fullWidth + x])			&&
+								!(_infoMap[y * _fullWidth + otherX])	&&
+								(y != _startY || x != _startX)			&&
+								(y != _startY || otherX != _startX) ) {
+
+							foundDoorLocation = true;
+						}
+					} else if (	!(_wallMap[y * _fullWidth + x + 1])					&&
+								(_wallMap[y * _fullWidth + x - 1] & kWall)		&&
+								(_wallMap[y * _fullWidth + x + 2] & kWall)		&&
+								!(_wallMap[(y - 1) * _fullWidth + x])				&&
+								!(_wallMap[(y - 2) * _fullWidth + x])				&&
+								!(_wallMap[(y + 1) * _fullWidth + x])				&&
+								!(_wallMap[(y + 2) * _fullWidth + x])				&&
+								!(_wallMap[(y - 1) * _fullWidth + x + 1])			&&
+								!(_wallMap[(y - 2) * _fullWidth + x + 1])			&&
+								!(_wallMap[(y + 1) * _fullWidth + x + 1])			&&
+								!(_wallMap[(y + 2) * _fullWidth + x + 1]) ) {
+
+						otherX = x + 1;
+						otherY = y;
+
+						if (	!(_infoMap[y * _fullWidth + x])			&&
+								!(_infoMap[y * _fullWidth + otherX])	&&
+								(y != _startY || x != _startX)			&&
+								(y != _startY || otherX != _startX) ) {
+							
+							foundDoorLocation = true;
+						}
+					}
+				} else {
+					if (	!(_wallMap[(y - 1) * _fullWidth + x])				&&
+							(_wallMap[(y + 1) * _fullWidth + x] & kWall)	&&
+							(_wallMap[(y - 2) * _fullWidth + x] & kWall)	&&
+							!(_wallMap[y * _fullWidth + x - 1])					&&
+							!(_wallMap[y * _fullWidth + x - 2])					&&
+							!(_wallMap[y * _fullWidth + x + 1])					&&
+							!(_wallMap[y * _fullWidth + x + 2])					&&
+							!(_wallMap[(y - 1) * _fullWidth + x - 1])			&&
+							!(_wallMap[(y - 1) * _fullWidth + x - 2])			&&
+							!(_wallMap[(y - 1) * _fullWidth + x + 1])			&&
+							!(_wallMap[(y - 1) * _fullWidth + x + 2]) ) {
+
+						otherY = y - 1;
+						otherX = x;
+
+						if (	!(_infoMap[y * _fullWidth + x])			&&
+								!(_infoMap[otherY * _fullWidth + x])	&&
+								(y != _startY || x != _startX)			&&
+								(otherY != _startY || x != _startX) ) {
+							
+							foundDoorLocation = true;
+						}
+					} else if (	!(_wallMap[(y + 1) * _fullWidth + x])				&&
+								(_wallMap[(y - 1) * _fullWidth + x] & kWall)	&&
+								(_wallMap[(y + 2) * _fullWidth + x] & kWall)	&&
+								!(_wallMap[y * _fullWidth + x + 1])					&&
+								!(_wallMap[y * _fullWidth + x - 2])					&&
+								!(_wallMap[y * _fullWidth + x + 1])					&&
+								!(_wallMap[y * _fullWidth + x + 2])					&&
+								!(_wallMap[(y + 1) * _fullWidth + x - 1])			&&
+								!(_wallMap[(y + 1) * _fullWidth + x - 2])			&&
+								!(_wallMap[(y + 1) * _fullWidth + x + 1])			&&
+								!(_wallMap[(y + 1) * _fullWidth + x + 2]) ) {
+
+						otherY = y + 1;
+						otherX = x;
+							
+						if (	!(_infoMap[y * _fullWidth + x])			&&
+								!(_infoMap[otherY * _fullWidth + x])	&&
+								(y != _startY || x != _startX)			&&
+								(otherY != _startY || x != _startX) ) {
+
+							foundDoorLocation = true;
+						}
+					}
+				}
+			}
+		}
+
+		// No more suitable locations, exit
+		if (!foundDoorLocation) {
+			break;
+		}
+
+		uint32 selectedDoorIDs = theme.doorIDs[g_nancy->_randomSource->getRandomNumber(theme.doorIDs.size() - 1)];
+		bool leftOrTop = g_nancy->_randomSource->getRandomBit();
+
+		uint32 doorX, doorY;
+		uint lightmapValue;
+
+		if (vertical) {
+			selectedDoorIDs |= (kDoor | kVertical | kHasBlankWalls | kTransparentWall);
+			doorY = y;
+
+			if (leftOrTop) {
+				if (x < otherX) {
+					doorX = x;
+				} else {
+					doorX = otherX;
+				}
+
+				_wallMap[y * _fullWidth + doorX] = selectedDoorIDs;
+				_wallMap[y * _fullWidth + doorX + 1] = _wallMap[y * _fullWidth + doorX + 2];
+				lightmapValue = _wallLightMap[y * _fullWidth + doorX - 1];
+				_wallLightMap[y * _fullWidth + doorX] = lightmapValue;
+				_wallLightMap[y * _fullWidth + doorX + 1] = _wallLightMap[y * _fullWidth + doorX + 2];
+			} else {
+				if (x > otherX) {
+					doorX = x;
+				} else {
+					doorX = otherX;
+				}
+
+				_wallMap[y * _fullWidth + doorX] = selectedDoorIDs;
+				_wallMap[y * _fullWidth + doorX - 1] = _wallMap[y * _fullWidth + doorX - 2];
+				lightmapValue = _wallLightMap[y * _fullWidth + doorX + 1];
+				_wallLightMap[y * _fullWidth + doorX] = lightmapValue;
+				_wallLightMap[y * _fullWidth + doorX - 1] = _wallLightMap[y * _fullWidth + doorX - 2];
+			}
+		} else {
+			selectedDoorIDs |= (kDoor | kHorizontal | kHasBlankWalls | kTransparentWall);
+			doorX = x;
+
+			if (leftOrTop) {
+				if (y > otherY) {
+					doorY = y;
+				} else {
+					doorY = otherY;
+				}
+
+				_wallMap[doorY * _fullWidth + x] = selectedDoorIDs;
+				_wallMap[(doorY - 1) * _fullWidth + x] = _wallMap[(doorY - 2) * _fullWidth + x];
+				lightmapValue = (uint)_wallLightMap[(doorY + 1) * _fullWidth + x];
+				_wallLightMap[doorY * _fullWidth + x] = _wallLightMap[(doorY + 1) * _fullWidth + x];
+				_wallLightMap[(doorY - 1) * _fullWidth + x] = _wallLightMap[(doorY - 2) * _fullWidth + x];
+			} else {
+				if (y < otherY) {
+					doorY = y;
+				} else {
+					doorY = otherY;
+				}
+
+				_wallMap[doorY * _fullWidth + x] = selectedDoorIDs;
+				_wallMap[(doorY + 1) * _fullWidth + x] = _wallMap[(doorY + 2) * _fullWidth + x];
+				lightmapValue = _wallLightMap[(doorY - 1) * _fullWidth + x];
+				_wallLightMap[doorY * _fullWidth + x] = _wallLightMap[(doorY - 1) * _fullWidth + x];
+				_wallLightMap[(doorY + 1) * _fullWidth + x] = _wallLightMap[(doorY + 2) * _fullWidth + x];
+			}
+		}
+
+		// Subtract 2 from all lightmap values when a door is added
+		byte lowWall, midWall, highWall;
+		lowWall = lightmapValue & 0xF;
+		midWall = (lightmapValue >> 4) & 0xF;
+		highWall = (lightmapValue >> 8) & 0xF;
+
+		lowWall = (int)lowWall - 2 < 0 ? 0 : lowWall - 2;
+		midWall = (int)midWall - 2 < 0 ? 0 : midWall - 2;
+		highWall = (int)highWall - 2 < 0 ? 0 : highWall - 2;
+
+		_wallLightMap[y * _fullWidth + x] = (lightmapValue & 0xF000) | (highWall << 8) | (midWall << 4) | lowWall;
+
+		lightmapValue = _floorCeilingLightMap[doorY * _fullWidth + doorX];
+
+		byte floor, ceiling;
+		floor = lightmapValue & 0xF;
+		ceiling = (lightmapValue >> 4) & 0xF;
+
+		floor = (int)floor - 2 < 0 ? 0 : floor - 2;
+		ceiling = (int)ceiling - 2 < 0 ? 0 : ceiling - 2;
+
+		_floorCeilingLightMap[doorY * _fullWidth + doorX] = (lightmapValue & 0xF000) | (ceiling << 4) | floor;
+	}
+}
+
+void RaycastLevelBuilder::writeLightSwitch(uint startX, uint startY, uint quadrant) {
+	bool foundSwitchLocation = false;
+
+	for (uint checkedCells = 0; checkedCells < _fullNumCells && !foundSwitchLocation; ++checkedCells) {
+		uint x = g_nancy->_randomSource->getRandomNumberRng(startX, MIN(startX + _halfWidth, _fullWidth - 1));
+		uint y = g_nancy->_randomSource->getRandomNumberRng(startY, MIN(startY + _halfHeight, _fullHeight - 1));
+
+		if (!(_wallMap[y * _fullWidth + x]) && !(_infoMap[y * _fullWidth + x]) && (y != _startY || x != _startX)) {
+			foundSwitchLocation = true;
+		}
+
+		if (foundSwitchLocation) {
+			_infoMap[y * _fullWidth + x] = (quadrant << 8) | 2;
+
+			uint lightmapValue = _floorCeilingLightMap[y * _fullWidth + x];
+
+			// Ceiling remains unchanged
+			byte floor, ceiling;
+			floor = lightmapValue & 0xF;
+			ceiling = (lightmapValue >> 4) & 0xF;
+			floor = (int)floor - 2 < 0 ? 0 : floor - 2;
+
+			_floorCeilingLightMap[y * _fullWidth + x] = (lightmapValue & 0xF000) | (ceiling << 4) | floor;
+			_floorMap[y * _fullWidth + x] = _themeData->lightSwitchID;
+		}
+	}
+}
+
+void RaycastLevelBuilder::writeExitFloorTexture(uint themeID) {
+	RCLB::Theme &theme = _themeData->themes[themeID];
+	int16 selectedFloorID = theme.exitFloorIDs[g_nancy->_randomSource->getRandomNumber(theme.exitFloorIDs.size() - 1)];
+	uint addToID = 0;
+	
+	for (uint y = 0; y < _fullHeight; ++y) {
+		for (uint x = 0; x < _fullWidth; ++x) {
+			if (_infoMap[y * _fullWidth + x] == 1) {
+				uint lightmapValue = _floorCeilingLightMap[y * _fullWidth + x];
+
+				// Ceiling remains unchanged
+				byte floor, ceiling;
+				floor = lightmapValue & 0xF;
+				ceiling = (lightmapValue >> 4) & 0xF;
+
+				floor = (int)floor - 2 < 0 ? 0 : floor - 2;
+
+				_floorCeilingLightMap[y * _fullWidth + x] = (lightmapValue & 0xF000) | (ceiling << 4) | floor;
+				_floorMap[y * _fullWidth + x] = selectedFloorID + addToID;
+
+				++addToID;
+
+				if (addToID == 4) {
+					return;
+				}
+			}
+		}
+	}
+}
+
+void RaycastLevelBuilder::validateMap() {
+	for (uint y = 0; y < _fullHeight; ++y) {
+		for (uint x = 0; x < _fullWidth; ++x) {
+			if (_wallMap[y * _fullWidth + x] == 1) {
+				error("wallMap not complete at coordinates x = %d, y = %d", x, y);
+			}
+			
+			if (_floorMap[y * _fullWidth + x] == -1) {
+				error("floorMap not complete at coordinates x = %d, y = %d", x, y);
+			}
+
+			if (_ceilingMap[y * _fullWidth + x] == -1) {
+				error("wallMap not complete at coordinates x = %d, y = %d", x, y);
+			}
+		}
+	}
+}
+
+void RaycastPuzzle::init() {
+	_puzzleData = g_nancy->_raycastPuzzleData;
+	assert(_puzzleData);
+
+	RaycastLevelBuilder levelBuilder(_mapWidth, _mapHeight, _wallHeight);
+	
+	_wallMap.push_back(levelBuilder._wallMap);
+	_infoMap.push_back(levelBuilder._infoMap);
+	_floorMap.push_back(levelBuilder._floorMap);
+	_ceilingMap.push_back(levelBuilder._ceilingMap);
+	_heightMap.push_back(levelBuilder._heightMap);
+	_wallLightMap.push_back(levelBuilder._wallLightMap);
+	_floorCeilingLightMap.push_back(levelBuilder._floorCeilingLightMap);
+
+	_mapFullWidth = levelBuilder._fullWidth;
+	_mapFullHeight = levelBuilder._fullHeight;
+
+	Common::Rect viewport = g_nancy->_viewportData->bounds;
+	moveTo(viewport);
+	_drawSurface.create(viewport.width(), viewport.height(), g_nancy->_graphicsManager->getInputPixelFormat());
+	setTransparent(true);
+
+	// TODO map is a debug feature, make sure to hide it
+	// Also, fix the fact that it's rendered upside-down
+	_map._drawSurface.create(_mapFullWidth, _mapFullHeight, g_nancy->_graphicsManager->getInputPixelFormat());
+	Common::Rect mapPos(g_nancy->_bootSummary->textboxScreenPosition);
+	mapPos.setWidth(_mapFullWidth * 2);
+	mapPos.setHeight(_mapFullHeight * 2);
+	_map.moveTo(mapPos);
+	_map.init();
+
+	drawMap();
+
+	Common::Rect selectedBounds = _puzzleData->screenViewportSizes[_puzzleData->viewportSizeUsed];
+
+	_wallCastColumnAngles.resize(g_nancy->_viewportData->screenPosition.width());
+	uint center = selectedBounds.left + (selectedBounds.width() >> 1);
+	for (uint i = 0; i < _wallCastColumnAngles.size(); ++i) {
+		int32 &angle = _wallCastColumnAngles[i];
+		angle = (int32)(atan(((float)i - (float)center) / (float)_fov) * _rotationSingleStep);
+		clampRotation(angle);
+	}
+
+	_leftmostAngle = _wallCastColumnAngles[selectedBounds.left];
+	_rightmostAngle = _wallCastColumnAngles[selectedBounds.right];
+
+	_sinTable.resize(4096);
+	_cosTable.resize(4096);
+
+	for (uint i = 0; i < 4096; ++i) {
+		double f = (i * _pi * 2) / 4096;
+		_cosTable[i] = cos(f);
+		_sinTable[i] = sin(f);
+	}
+
+	_maxWorldDistance = sqrt(((128 * _mapFullWidth) - 1) * ((128 * _mapFullHeight) - 1) * 2);
+	_depthBuffer.resize(viewport.width());
+	_zBuffer.resize(viewport.width() * viewport.height());
+
+	loadTextures();
+	clearZBuffer();
+
+	// TODO these need to be set according to the start position in _infoMap
+	_playerRotation = 2048;
+	_playerX = _playerY = 320;
+}
+
+void RaycastPuzzle::registerGraphics() {
+	_map.registerGraphics();
+	RenderActionRecord::registerGraphics();
+}
+
+void RaycastPuzzle::readData(Common::SeekableReadStream &stream) {
+	_mapWidth = stream.readUint16LE();
+	_mapHeight = stream.readUint16LE();
+	_wallHeight = stream.readByte();
+
+	readFilename(stream, _switchSoundName);
+	_switchSoundChannelID = stream.readUint16LE();
+	readFilename(stream, _unknownSoundName);
+	_unknownSoundChannelID = stream.readUint16LE();
+
+	_dummySound.readNormal(stream);
+
+	_solveScene.readData(stream);
+	_solveSound.readNormal(stream);
+}
+
+void RaycastPuzzle::execute() {
+	switch (_state) {
+	case kBegin:
+		init();
+		registerGraphics();
+
+		_state = kRun;
+
+		break;
+	case kRun:
+		// TODO check light switches
+		// TODO check exit
+
+		break;
+	case kActionTrigger:
+		break;
+	}
+}
+
+void RaycastPuzzle::handleInput(NancyInput &input) {
+	uint32 time = g_nancy->getTotalPlayTime();
+	uint32 deltaTime = time - _lastMovementTime;
+	_lastMovementTime = time;
+	deltaTime *= 1000;
+	bool mouseIsInBounds = false;
+
+	float deltaRotation = ((float)deltaTime * 0.0006);
+	float deltaPosition = ((float)deltaTime * 0.0002);
+
+	if (NancySceneState.getViewport().convertViewportToScreen(_puzzleData->screenViewportSizes[_puzzleData->viewportSizeUsed]).contains(input.mousePos)) {
+		mouseIsInBounds = true;
+	}
+
+	// Mouse was just clicked, make sure we don't rotate
+	if (mouseIsInBounds && ((input.input & NancyInput::kLeftMouseButtonDown) || (input.input & NancyInput::kRightMouseButtonDown))) {
+		_lastMouseX = input.mousePos.x;
+	}
+
+	// Rotate the camera according to the left-right movement of the mouse
+	if (mouseIsInBounds && ((input.input & NancyInput::kLeftMouseButtonHeld) || (input.input & NancyInput::kRightMouseButtonHeld))) {
+		if (input.mousePos.x < _lastMouseX) {
+			_playerRotation -= (_lastMouseX - input.mousePos.x) * 5;
+		}
+
+		if (input.mousePos.x > _lastMouseX) {
+			_playerRotation -= (_lastMouseX - input.mousePos.x) * 5;
+		}
+
+		_lastMouseX = input.mousePos.x;
+	}
+
+	// Rotate the camera if the arrow keys were pressed
+	if (input.input & NancyInput::kMoveLeft) {
+		if (input.input & NancyInput::kMoveFastModifier) {
+			_playerRotation -= deltaRotation * 2;
+		} else {
+			_playerRotation -= deltaRotation;
+		}
+	}
+
+	if (input.input & NancyInput::kMoveRight) {
+		if (input.input & NancyInput::kMoveFastModifier) {
+			_playerRotation += deltaRotation * 2;
+		} else {
+			_playerRotation += deltaRotation;
+		}
+	}
+
+	clampRotation(_playerRotation);
+
+	int32 newX = _playerX;
+	int32 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);
+
+	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;
+		} else {
+			newX = _playerX - (int32)(_sinTable[invertedRotation] * deltaPosition);
+			newY = _playerY - (int32)(_cosTable[invertedRotation] * deltaPosition);
+		}
+
+		hasMoved = true;
+	}
+
+	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;
+		} else {
+			newX = _playerX + (int32)(_sinTable[invertedRotation] * deltaPosition);
+			newY = _playerY + (int32)(_cosTable[invertedRotation] * deltaPosition);
+		}
+		
+		hasMoved = true;
+	}
+
+	// Perform slowdown
+	if (!hasMoved && _nextSlowdownMovementTime < time && _slowdownFramesLeft > 0) {
+		_slowdownDeltaX = (float)_slowdownDeltaX * 9.0 / 10.0;
+		_slowdownDeltaY = (float)_slowdownDeltaY * 9.0 / 10.0;
+		newX += _slowdownDeltaX;
+		newY += _slowdownDeltaY;
+		hasMoved = true;
+		hasMovedSlowdown = true;
+		_nextSlowdownMovementTime = time + 40;
+		--_slowdownFramesLeft;
+	}
+
+	// Perform collision
+	if (hasMoved) {
+		uint yGrid = newX >> 7;
+		uint xGrid = newY >> 7;
+
+		// Check neighboring cells
+		uint32 cellLeft = xGrid > 0 ? _wallMap[yGrid * _mapFullWidth + xGrid - 1] : 1;
+		uint32 cellTop = yGrid > 0 ? _wallMap[(yGrid - 1) * _mapFullWidth + xGrid] : 1;
+		uint32 cellRight = xGrid < _mapFullWidth ? _wallMap[yGrid * _mapFullWidth + xGrid + 1] : 1;
+		uint32 cellBottom = yGrid < _mapFullHeight ? _wallMap[(yGrid + 1) * _mapFullWidth + xGrid] : 1;
+
+		// Allow passage through doors
+		cellLeft = (cellLeft & kDoor) ? 0 : cellLeft;
+		cellTop = (cellTop & kDoor) ? 0 : cellTop;
+		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;
+		}
+
+		if (cellTop && xCell < collisionSize) {
+			newX = (newX & 0xFF80) + collisionSize;
+		}
+
+		if (cellRight && yCell > (128 - collisionSize)) {
+			newY = (newY & 0xFF80) + (128 - collisionSize);
+		}
+
+		if (cellBottom && xCell > (128 - collisionSize)) {
+			newX = (newX & 0xFF80) + (128 - collisionSize);
+		}
+
+		yGrid = newX >> 7;
+		xGrid = newY >> 7;
+		yCell = newX & 0x7F;
+		xCell = newY & 0x7F;
+
+		cellLeft = xGrid > 0 ? _wallMap[yGrid * _mapFullWidth + xGrid - 1] : 1;
+		cellTop = yGrid > 0 ? _wallMap[(yGrid - 1) * _mapFullWidth + xGrid] : 1;
+		cellRight = xGrid < _mapFullWidth ? _wallMap[yGrid * _mapFullWidth + xGrid + 1] : 1;
+		cellBottom = yGrid < _mapFullHeight ? _wallMap[(yGrid + 1) * _mapFullWidth + xGrid] : 1;
+
+		uint32 cellTopLeft = (xGrid > 0 && yGrid > 0) ? _wallMap[(yGrid - 1) * _mapFullWidth + xGrid - 1] : 1;
+		uint32 cellTopRight = (xGrid < _mapFullWidth && yGrid > 0) ? _wallMap[(yGrid - 1) * _mapFullWidth + xGrid + 1] : 1;
+		uint32 cellBottomLeft = (xGrid > 0 && yGrid < _mapFullHeight) ? _wallMap[(yGrid + 1) * _mapFullWidth + xGrid - 1] : 1;
+		uint32 cellBottomRight = (xGrid < _mapFullWidth && yGrid < _mapFullHeight) ? _wallMap[(yGrid + 1) * _mapFullWidth + xGrid + 1] : 1;
+
+		cellLeft = (cellLeft & kDoor) ? 0 : cellLeft;
+		cellTop = (cellTop & kDoor) ? 0 : cellTop;
+		cellRight = (cellRight & kDoor) ? 0 : cellRight;
+		cellBottom = (cellBottom & kDoor) ? 0 : cellBottom;
+
+		cellTopLeft = (cellTopLeft & kDoor) ? 0 : cellTopLeft;
+		cellTopRight = (cellTopRight & kDoor) ? 0 : cellTopRight;
+		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
+		if (cellTopLeft && !cellLeft && !cellTop && (yCell < collisionSize) && (xCell < collisionSize)) {
+			return;
+		}
+
+		if (cellTopRight && !cellRight && !cellTop && (yCell < collisionSize) && (xCell > (128 - collisionSize))) {
+			return;
+		}
+
+		if (cellBottomLeft && !cellLeft && !cellBottom && (yCell > (128 - collisionSize)) && (xCell < collisionSize)) {
+			return;
+		}
+
+		if (cellBottomRight && !cellRight && !cellBottom && (yCell > (128 - collisionSize)) && (xCell > (128 - collisionSize))) {
+			return;
+		}
+
+		if (!hasMovedSlowdown) {
+			_slowdownDeltaX = newX - _playerX;
+			_slowdownDeltaY = newY - _playerY;
+			_nextSlowdownMovementTime = time + 40;
+			_slowdownFramesLeft = 10;
+		}
+
+		_playerX = newX;
+		_playerY = newY;
+
+		debug("x = %u, y = %u", _playerX, _playerY);
+	}
+}
+
+void RaycastPuzzle::updateGraphics() {
+	if (_state == kRun) {
+		drawMaze();
+	}
+}
+
+void RaycastPuzzle::drawMap() {
+	_map._drawSurface.clear();
+
+	uint16 *pixelPtr;
+	Graphics::PixelFormat &format = _map._drawSurface.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]);
+	uint16 uColor7 = format.RGBToColor(_puzzleData->uColor7[0], _puzzleData->uColor7[1], _puzzleData->uColor7[2]);
+	uint16 uColor8 = format.RGBToColor(_puzzleData->uColor8[0], _puzzleData->uColor8[1], _puzzleData->uColor8[2]);
+	uint16 transparentWallColor = format.RGBToColor(_puzzleData->transparentWallColor[0], _puzzleData->transparentWallColor[1], _puzzleData->transparentWallColor[2]);
+	uint16 lightSwitchColor = format.RGBToColor(_puzzleData->lightSwitchColor[0], _puzzleData->lightSwitchColor[1], _puzzleData->lightSwitchColor[2]);
+	uint16 uColor10 = format.RGBToColor(_puzzleData->uColor10[0], _puzzleData->uColor10[1], _puzzleData->uColor10[2]);
+	uint16 doorColor = format.RGBToColor(_puzzleData->doorColor[0], _puzzleData->doorColor[1], _puzzleData->doorColor[2]);
+	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);
+		for (uint x = 0; x < _mapFullWidth; ++x) {
+			uint32 wallMapCell = _wallMap[y * _mapFullHeight + x];
+			uint32 infoMapCell = _infoMap[y * _mapFullHeight + x];
+			if (wallMapCell & kWall) {
+				*pixelPtr = wallColor;
+			}
+			
+			if ((wallMapCell != 0) && !(wallMapCell & kWall)) {
+				*pixelPtr = uColor6;
+			}
+
+			if ((wallMapCell & kVertical) || (wallMapCell & kHorizontal)) {
+				*pixelPtr = uColor7;
+			}
+
+			if (wallMapCell & kHasBlankWalls) {
+				*pixelPtr = uColor8;
+			}
+
+			if (wallMapCell & kTransparentWall) {
+				*pixelPtr = transparentWallColor;
+			}
+
+			if ((infoMapCell & 0xFF) == 2) {
+				*pixelPtr = lightSwitchColor;
+			}
+
+			if ((infoMapCell & 0xFF) == 3) {
+				*pixelPtr = uColor10;
+			}
+
+			if (wallMapCell & kDoor) {
+				*pixelPtr = doorColor;
+			}
+
+			if ((infoMapCell & 0xFF) == 1) {
+				*pixelPtr = exitColor;
+			}
+
+			++pixelPtr;
+		}
+	}
+
+	_map.setVisible(true);
+}
+
+void RaycastPuzzle::loadTextures() {
+	// TODO this is slow and freezes the engine for a few seconds
+	for (uint y = 0; y < _mapFullHeight; ++y) {
+		for (uint x = 0; x < _mapFullWidth; ++x) {
+			uint32 wallMapVal = _wallMap[y * _mapFullHeight + x];
+
+			for (uint i = 0; i < 3; ++i) {
+				byte textureID = wallMapVal & 0xFF;
+
+				if (textureID & 0x80) {
+					if (!_specialWallTextures.contains(textureID)) {
+						createTextureLightSourcing(&_specialWallTextures[textureID & 0x7F], _puzzleData->specialWallNames[(textureID & 0x7F) - 1]);
+					}
+				} else if (textureID) {
+					if (!_wallTextures.contains(textureID)) {
+						createTextureLightSourcing(&_wallTextures[textureID], _puzzleData->wallNames[textureID - 1]);
+					}
+				}
+
+				wallMapVal >>= 8;
+			}
+
+			int16 floorMapVal = _floorMap[y * _mapFullWidth + x];
+			int16 ceilingMapVal = _ceilingMap[y * _mapFullWidth + x];
+
+			if (!_floorTextures.contains(floorMapVal)) {
+				createTextureLightSourcing(&_floorTextures[floorMapVal], _puzzleData->floorNames[floorMapVal]);
+			}
+
+			if (!_ceilingTextures.contains(ceilingMapVal)) {
+				createTextureLightSourcing(&_ceilingTextures[ceilingMapVal], _puzzleData->ceilingNames[ceilingMapVal]);
+			}
+		}
+	}
+
+	for (auto &a : _specialWallTextures) {
+		for (auto &tex : a._value) {
+			tex.setTransparentColor(g_nancy->_graphicsManager->getTransColor());
+		}
+	}
+}
+
+void RaycastPuzzle::createTextureLightSourcing(Common::Array<Graphics::ManagedSurface> *array, Common::String &textureName) {
+	Graphics::PixelFormat format = g_nancy->_graphicsManager->getInputPixelFormat();
+	array->resize(8);
+
+	g_nancy->_resource->loadImage(textureName, (*array)[0]);
+
+	uint width = (*array)[0].w;
+	uint height = (*array)[0].h;
+
+	// Keep the original texture as the first array element
+	for (uint i = 1; i < 8; ++i) {
+		(*array)[i].create(width, height, format);
+	}
+
+	// Make 7 copies, each one 1/8th darker than the last
+	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);
+			}
+		}
+	}
+}
+
+void RaycastPuzzle::drawMaze() {
+	// TODO rendering needs further optimization
+	Common::Rect viewBounds = _puzzleData->screenViewportSizes[_puzzleData->viewportSizeUsed];
+	uint viewportCenterY = viewBounds.top + (viewBounds.height() / 2);
+	uint16 transColor = (uint16)_drawSurface.getTransparentColor();
+	float depth = 1.0;
+
+	byte curZBufferDepth = _lastZDepth + 1;
+
+	_drawSurface.clear(_drawSurface.getTransparentColor());
+
+	// Draw walls
+	for (int x = viewBounds.left; x <= viewBounds.right; ++x) {
+		int columnAngleForX = _wallCastColumnAngles[x];
+		int rotatedColumnAngleForX = columnAngleForX + _playerRotation;
+		clampRotation(rotatedColumnAngleForX);
+
+		float rayStartX = _playerX;
+		float rayStartY = _playerY;
+
+		float angleSin = (float)_sinTable[rotatedColumnAngleForX] * -1024.0;
+		float angleCos = (float)_cosTable[rotatedColumnAngleForX] * 1024.0;
+
+		if (angleSin == 0.0) {
+			angleSin = 0.001;
+		}
+
+		if (angleCos == 0.0) {
+			angleCos = 0.001;
+		}
+
+		float angleAtan = angleCos / angleSin;
+
+		if (angleAtan == 0.0) {
+			angleAtan = 0.001;
+		}
+
+		bool isBehindTransparentWall = false;
+		int viewBottom = viewBounds.bottom;
+
+		for (bool shouldBreak = false; shouldBreak == false;) {
+			int xEdge, yEdge;
+			float xDist, yDist;
+
+			if (angleSin <= 0.0) {
+				xEdge = ((uint)rayStartX & 0xFF80) - 1;
+			} else {
+				xEdge = ((uint)rayStartX & 0xFF80) + 128;
+			}
+
+			if (angleCos <= 0.0) {
+				yEdge = ((uint)rayStartY & 0xFF80) - 1;
+			} else {
+				yEdge = ((uint)rayStartY & 0xFF80) + 128;
+			}
+
+			float xRayX = xEdge;
+			float xRayY = ((float)xEdge - rayStartX) * angleAtan + rayStartY;
+			float yRayX = ((float)yEdge - rayStartY) / angleAtan + rayStartX;
+			float yRayY = yEdge;
+
+			byte quadrant = rotatedColumnAngleForX >> 9;
+
+			if ((quadrant & 3) == 0 || (quadrant & 3) == 3) {
+				xDist = abs(((xRayY - (float)_playerY) / _cosTable[(rotatedColumnAngleForX + (quadrant * -2048)) & 0xFFF]) * _cosTable[columnAngleForX]);
+				yDist = abs(((yRayY - (float)_playerY) / _cosTable[(rotatedColumnAngleForX + (quadrant * -2048)) & 0xFFF]) * _cosTable[columnAngleForX]);
+			} else {
+				xDist = abs(((xRayX - (float)_playerX) / _sinTable[(rotatedColumnAngleForX + (quadrant * -2048)) & 0xFFF]) * _cosTable[columnAngleForX]);
+				yDist = abs(((yRayX - (float)_playerX) / _sinTable[(rotatedColumnAngleForX + (quadrant * -2048)) & 0xFFF]) * _cosTable[columnAngleForX]);
+			}
+
+			int xGridTile = -1;
+			int yGridTile = -1;
+			Common::Point xGrid(-1, -1);
+			Common::Point yGrid(-1, -1);
+
+			if (xDist < _maxWorldDistance) {
+				xGrid.y = (int)xRayX >> 7;
+				xGrid.x = (int)(xRayY / 128.0);
+
+				if (xGrid.x < _mapFullWidth && xGrid.y < _mapFullHeight && xGrid.x >= 0 && xGrid.y >= 0) {
+					xGridTile = _wallMap[xGrid.y * _mapFullWidth + xGrid.x];
+				}
+			}
+
+			if (yDist < _maxWorldDistance) {
+				yGrid.y = (int)(yRayX / 128.0);
+				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];
+				}
+			}
+
+			if (xGridTile == -1 && yGridTile == -1) {
+				break;
+			}
+
+			if (yGridTile > 0 && (yGridTile & kHorizontal) != 0) {
+				if (angleCos <= 0.0) {
+					yEdge = ((uint)yRayY & 0xFF80) - 1;
+				} else {
+					yEdge = ((uint)yRayY & 0xFF80) + 128;
+				}
+
+				yRayX += (((float)yEdge - yRayY) / angleAtan) / 2.0;
+				yRayY += ((float)yEdge - yRayY) / 2.0;
+
+				if ((quadrant & 3) == 0 || (quadrant & 3) == 3) {
+					yDist = abs(((yRayY - (float)_playerY) / _cosTable[(rotatedColumnAngleForX + (quadrant * -2048)) & 0xFFF]) * _cosTable[columnAngleForX]);
+				} else {
+					yDist = abs(((yRayX - (float)_playerX) / _sinTable[(rotatedColumnAngleForX + (quadrant * -2048)) & 0xFFF]) * _cosTable[columnAngleForX]);
+				}
+
+				if (xGridTile == yGridTile) {
+					if (angleSin <= 0.0) {
+						xEdge = ((uint)xRayX & 0xFF80) - 1;
+					} else {
+						xEdge = ((uint)xRayX & 0xFF80) + 128;
+					}
+
+					xRayY += (((float)xEdge - xRayX) * angleAtan) / 2.0;
+					xRayX += ((float)xEdge - xRayX) / 2.0;
+
+					if ((quadrant & 3) == 0 || (quadrant & 3) == 3) {
+						xDist = abs(((xRayY - (float)_playerY) / _cosTable[(rotatedColumnAngleForX + (quadrant * -2048)) & 0xFFF]) * _cosTable[columnAngleForX]);
+					} else {
+						xDist = abs(((xRayX - (float)_playerX) / _sinTable[(rotatedColumnAngleForX + (quadrant * -2048)) & 0xFFF]) * _cosTable[columnAngleForX]);
+					}
+				}
+
+				if (yDist < xDist && xDist - yDist < 12.0 && (((uint)yRayX & 0x7F) == 0 || ((uint)yRayX & 0x7F) == 0x7F)) {
+					yDist = xDist + 1.0;
+				}
+			}
+
+			if (xGridTile > 0 && (xGridTile & kVertical) != 0) {
+				if (angleSin <= 0.0) {
+					xEdge = ((uint)xRayX & 0xFF80) - 1;
+				} else {
+					xEdge = ((uint)xRayX & 0xFF80) + 128;
+				}
+
+				xRayY += (((float)xEdge - xRayX) * angleAtan) / 2.0;
+				xRayX += ((float)xEdge - xRayX) / 2.0;
+
+				if ((quadrant & 3) == 0 || (quadrant & 3) == 3) {
+					xDist = abs(((float)(xRayY - _playerY) / _cosTable[(rotatedColumnAngleForX + (quadrant * -2048)) & 0xFFF]) * _cosTable[columnAngleForX]);
+				} else {
+					xDist = abs(((float)(xRayX - _playerX) / _sinTable[(rotatedColumnAngleForX + (quadrant * -2048)) & 0xFFF]) * _cosTable[columnAngleForX]);
+				}
+
+				if (xGridTile == yGridTile) {
+					if (angleCos <= 0.0) {
+						yEdge = ((uint)yRayY & 0xFF80) - 1;
+					} else {
+						yEdge = ((uint)yRayY & 0xFF80) + 128;
+					}
+
+					yRayX += (((float)yEdge - yRayY) / angleAtan) / 2.0;
+					yRayY += ((float)yEdge - yRayY) / 2.0;
+
+					if ((quadrant & 3) == 0 || (quadrant & 3) == 3) {
+						yDist = abs(((yRayY - (float)_playerY) / _cosTable[(rotatedColumnAngleForX + (quadrant * -2048)) & 0xFFF]) * _cosTable[columnAngleForX]);
+					} else {
+						yDist = abs(((yRayX - (float)_playerX) / _sinTable[(rotatedColumnAngleForX + (quadrant * -2048)) & 0xFFF]) * _cosTable[columnAngleForX]);
+					}
+				}
+
+				if (xDist < yDist && yDist - xDist < 12.0 && (((uint)xRayY & 0x7F) == 0 || ((uint)xRayY & 0x7F) == 0x7F)) {
+					xDist = yDist + 1.0;
+				}
+			}
+
+			byte wallIDs[3], wallIsSpecial[3];
+			uint32 hitWallValue, hitWallLightValue;
+			uint textureColumn, cellHeight;
+
+			if (yDist <= xDist) {
+				rayStartX = yRayX;
+				rayStartY = yRayY;
+
+				if (yGridTile > 0 && !(yGridTile & kVertical)) {
+					hitWallValue = yGridTile;
+					hitWallLightValue = _wallLightMap[yGrid.y * _mapFullWidth + yGrid.x];
+
+					depth = yDist < 1.0 ? 1.0 : yDist;
+					_depthBuffer[x] = depth;
+
+					textureColumn = (uint)rayStartX & 0x7F;
+					if (rotatedColumnAngleForX < 1024 || rotatedColumnAngleForX > 3072) {
+						textureColumn = 127 - textureColumn;
+					}
+
+					if (!(yGridTile & kWall) || yGridTile & kHasBlankWalls || yGridTile & kTransparentWall) {
+						cellHeight = _heightMap[yGrid.y * _mapFullWidth + yGrid.x];
+					} else {
+						shouldBreak = true;
+						cellHeight = _wallHeight * 128;
+					}
+				} else {
+					continue;
+				}
+			} else {
+				rayStartX = xRayX;
+				rayStartY = xRayY;
+
+				if (xGridTile > 0 && !(xGridTile & kHorizontal)) {
+					hitWallValue = xGridTile;
+					hitWallLightValue = _wallLightMap[xGrid.y * _mapFullWidth + xGrid.x];
+
+					depth = xDist < 1.0 ? 1.0 : xDist;
+					_depthBuffer[x] = depth;
+
+					textureColumn = (uint)rayStartY & 0x7F;
+					if (rotatedColumnAngleForX > 0 && rotatedColumnAngleForX < 2048) {
+						textureColumn = 127 - textureColumn;
+					}
+
+					if (!(xGridTile & kWall) || xGridTile & kHasBlankWalls || xGridTile & kTransparentWall) {
+						cellHeight = _heightMap[xGrid.y * _mapFullWidth + xGrid.x];
+					} else {
+						shouldBreak = true;
+						cellHeight = _wallHeight * 128;
+					}
+				} else {
+					continue;
+				}
+			}
+
+			// Draw the column
+			byte lightValues[3];
+			lightValues[0] = hitWallLightValue & 0xF;
+			lightValues[1] = (hitWallLightValue >> 4) & 0xF;
+			lightValues[2] = (hitWallLightValue >> 8) & 0xF;
+			
+			wallIDs[0] = hitWallValue & 0xFF;
+			wallIDs[1] = (hitWallValue >> 8) & 0xFF;
+			wallIDs[2] = (hitWallValue >> 16) & 0xFF;
+
+			for (uint i = 0; i < 3; ++i) {
+				wallIsSpecial[i] = wallIDs[i] & 0x80;
+				wallIDs[i] &= 0x7F;
+			}
+
+			int drawnWallHeight = (int)((float)(_fov * cellHeight) / depth) + 1;
+			if (drawnWallHeight == 0) {
+				drawnWallHeight = 1;
+			}
+
+			int drawnWallBottom = (int)((float)(_fov * _playerAltitude) / depth + viewportCenterY) + 1;
+			int drawnWallTop = drawnWallBottom - drawnWallHeight + 1;
+			int numSrcPixelsToDraw = cellHeight;
+			uint32 srcY = _wallHeight * 128 - 1;
+			float heightRatio = (float)(cellHeight) / (float)drawnWallHeight;
+
+			// Clip top of wall
+			if (drawnWallTop < viewBounds.top) {
+				drawnWallTop = viewBounds.top - drawnWallTop;
+				drawnWallHeight -= drawnWallTop;
+				numSrcPixelsToDraw = cellHeight - (int)((float)drawnWallTop * heightRatio) - 1;
+				drawnWallTop = viewBounds.top;
+			}
+
+			// Clip bottom of wall
+			if (drawnWallBottom > viewBottom) {
+				drawnWallBottom -= viewBottom;
+				drawnWallHeight -= drawnWallBottom;
+				numSrcPixelsToDraw -= (int)((float)drawnWallBottom * heightRatio) + 1;
+				srcY -= (int)((float)drawnWallBottom * heightRatio);
+				drawnWallBottom = viewBottom;
+			}
+
+			if (_wallHeight != 3) {
+				// Other cases not implemented since the nancy3 data only has a height of 3
+				warning("Raycast rendering for _wallHeight != 3 not implemented");
+			}
+
+			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);
+				uint srcYSubtractVal = (uint)(((float)numSrcPixelsToDraw / (float)drawnWallHeight) * 65536.0);
+
+				srcY <<= 16;
+
+				for (uint i = 0; i < 3; ++i) {
+					lightValues[i] = MIN<byte>(lightValues[i] + baseLightVal, 7);
+				}
+
+				uint16 *srcPixels[3];
+
+				for (uint i = 0; i < 3; ++i) {
+					if (!wallIDs[i]) {
+						srcPixels[i] = nullptr;
+					} else {
+						srcPixels[i] = wallIsSpecial[i] ? 	(uint16 *)_specialWallTextures[wallIDs[i]][lightValues[i]].getBasePtr(textureColumn, 0) :
+															(uint16 *)_wallTextures[wallIDs[i]][lightValues[i]].getBasePtr(textureColumn, 0);
+					}
+				}
+
+				if (!(hitWallValue & kHasBlankWalls) && !(hitWallValue & kTransparentWall)) {
+					if (isBehindTransparentWall) {
+						for (int y = 0; y < drawnWallHeight; ++y) {
+							uint32 sY = srcY >> 16;
+							uint16 *srcPixel = srcPixels[2 - (sY >> 7)] ? &srcPixels[2 - (sY >> 7)][128 * (sY & 0x7F)] : nullptr;
+
+							if (srcPixel && *zBufferDestPixel != curZBufferDepth) {
+								*destPixel = *srcPixel;
+								*zBufferDestPixel = curZBufferDepth;
+							}
+
+							srcY -= srcYSubtractVal;
+							destPixel -= _drawSurface.w;
+							zBufferDestPixel -= _drawSurface.w;
+						}
+					} else {
+						for (int y = 0; y < drawnWallHeight; ++y) {
+							uint32 sY = srcY >> 16;
+							uint16 *srcPixel = srcPixels[2 - (sY >> 7)] ? &srcPixels[2 - (sY >> 7)][128 * (sY & 0x7F)] : nullptr;
+							
+							if (srcPixel) {
+								*destPixel = *srcPixel;
+							}
+							
+							srcY -= srcYSubtractVal;
+							destPixel -= _drawSurface.w;
+							*zBufferDestPixel = curZBufferDepth;
+							zBufferDestPixel -= _drawSurface.w;
+						}
+					}
+				} else if (!(hitWallValue & kHasBlankWalls) && hitWallValue & kTransparentWall) {
+					if (isBehindTransparentWall) {
+						for (int y = 0; y < drawnWallHeight; ++y) {
+							uint32 sY = srcY >> 16;
+							uint16 *srcPixel = srcPixels[2 - (sY >> 7)] ? &srcPixels[2 - (sY >> 7)][128 * (sY & 0x7F)] : nullptr;
+
+							if (srcPixel && *zBufferDestPixel != curZBufferDepth && *srcPixel != transColor) {
+								*destPixel = *srcPixel;
+								*zBufferDestPixel = curZBufferDepth;
+							}
+
+							srcY -= srcYSubtractVal;
+							destPixel -= _drawSurface.w;
+							zBufferDestPixel -= _drawSurface.w;
+						}
+					} else {
+						for (int y = 0; y < drawnWallHeight; ++y) {
+							uint32 sY = srcY >> 16;
+							uint16 *srcPixel = srcPixels[2 - (sY >> 7)] ? &srcPixels[2 - (sY >> 7)][128 * (sY & 0x7F)] : nullptr;
+
+							if (srcPixel && *srcPixel != transColor) {
+								*destPixel = *srcPixel;
+								*zBufferDestPixel = curZBufferDepth;
+							}
+							
+							srcY -= srcYSubtractVal;
+							destPixel -= _drawSurface.w;
+							zBufferDestPixel -= _drawSurface.w;
+						}
+					}
+				} else if (hitWallValue & kHasBlankWalls && hitWallValue & kTransparentWall) {
+					if (isBehindTransparentWall) {
+						for (int y = 0; y < drawnWallHeight; ++y) {
+							uint32 sY = srcY >> 16;
+							uint16 *srcPixel = srcPixels[2 - (sY >> 7)] ? &srcPixels[2 - (sY >> 7)][128 * (sY & 0x7F)] : nullptr;
+
+							if (srcPixel && wallIDs[2 - (sY >> 7)] != 0 && *zBufferDestPixel != curZBufferDepth && *srcPixel != transColor) {
+								*destPixel = *srcPixel;
+								*zBufferDestPixel = curZBufferDepth;
+							}
+
+							srcY -= srcYSubtractVal;
+							destPixel -= _drawSurface.w;
+							zBufferDestPixel -= _drawSurface.w;
+						}
+					} else {
+						for (int y = 0; y < drawnWallHeight; ++y) {
+							uint32 sY = srcY >> 16;
+							uint16 *srcPixel = srcPixels[2 - (sY >> 7)] ? &srcPixels[2 - (sY >> 7)][128 * (sY & 0x7F)] : nullptr;
+
+							if (srcPixel && wallIDs[2 - (sY >> 7)] != 0 && *srcPixel != transColor) {
+								*destPixel = *srcPixel;
+								*zBufferDestPixel = curZBufferDepth;
+							}
+							
+							srcY -= srcYSubtractVal;
+							destPixel -= _drawSurface.w;
+							zBufferDestPixel -= _drawSurface.w;
+						}
+					}
+				} else {
+					if (isBehindTransparentWall) {
+						for (int y = 0; y < drawnWallHeight; ++y) {
+							uint32 sY = srcY >> 16;
+							uint16 *srcPixel = srcPixels[2 - (sY >> 7)] ? &srcPixels[2 - (sY >> 7)][128 * (sY & 0x7F)] : nullptr;
+
+							if (srcPixel && wallIDs[2 - (sY >> 7)] != 0 && *zBufferDestPixel != curZBufferDepth) {
+								*destPixel = *srcPixel;
+								*zBufferDestPixel = curZBufferDepth;
+							}
+
+							srcY -= srcYSubtractVal;
+							destPixel -= _drawSurface.w;
+							zBufferDestPixel -= _drawSurface.w;
+						}
+					} else {
+						for (int y = 0; y < drawnWallHeight; ++y) {
+							uint32 sY = srcY >> 16;
+							uint16 *srcPixel = srcPixels[2 - (sY >> 7)] ? &srcPixels[2 - (sY >> 7)][128 * (sY & 0x7F)] : nullptr;
+
+							if (srcPixel && wallIDs[2 - (sY >> 7)] != 0) {
+								*destPixel = *srcPixel;
+								*zBufferDestPixel = curZBufferDepth;
+							}
+							
+							srcY -= srcYSubtractVal;
+							destPixel -= _drawSurface.w;
+							zBufferDestPixel -= _drawSurface.w;
+						}
+					}
+				}
+			}
+
+			if (!(hitWallValue & kHasBlankWalls) && !(hitWallValue & kTransparentWall)) {
+				if (drawnWallTop < viewBottom) {
+					viewBottom = drawnWallTop;
+				}
+				isBehindTransparentWall = false;
+			} else {
+				isBehindTransparentWall = true;
+			}
+
+			if (viewBounds.bottom < viewBottom) {
+				viewBottom = viewBounds.bottom;
+			}
+		}
+	}
+
+	// Draw floors and ceilings
+	// TODO exit does not get rendered correctly
+	int leftAngle = _playerRotation + _leftmostAngle;
+	int rightAngle = _playerRotation + _rightmostAngle;
+
+	clampRotation(leftAngle);
+	clampRotation(rightAngle);
+
+	for (int floorY = viewportCenterY + 5; floorY < viewBounds.bottom; ++floorY) {
+		int ceilingY = viewportCenterY - (floorY - viewportCenterY) + 1;
+		uint32 	floorSrcFracX, floorSrcFracY,
+				ceilingSrcFracX, ceilingSrcFracY,
+				floorSrcIncrementX, floorSrcIncrementY,
+				ceilingSrcIncrementX, ceilingSrcIncrementY;
+
+		uint16 *floorDest = (uint16 *)_drawSurface.getBasePtr(viewBounds.left, floorY);
+		uint16 *ceilingDest = (uint16 *)_drawSurface.getBasePtr(viewBounds.left, ceilingY);
+
+		{
+			float floorSrcX, floorSrcY, ceilingSrcX, ceilingSrcY;
+
+			float floorViewAngle = ((float)_fov / (float)(floorY - viewportCenterY)) * (float)_playerAltitude;
+			float ceilingViewAngle = ((float)_fov / (float)(viewportCenterY - ceilingY)) * (float)((_wallHeight * 128) - _playerAltitude);
+
+			floorSrcX = _cosTable[leftAngle] * (floorViewAngle / _cosTable[_leftmostAngle]) + (float)_playerY;
+			floorSrcY = _sinTable[leftAngle] * -(floorViewAngle / _cosTable[_leftmostAngle]) + (float)_playerX;
+			ceilingSrcX = _cosTable[leftAngle] * (ceilingViewAngle / _cosTable[_leftmostAngle]) + (float)_playerY;
+			ceilingSrcY = _sinTable[leftAngle] * -(ceilingViewAngle / _cosTable[_leftmostAngle]) + (float)_playerX;
+
+			floorSrcFracX = (uint32)(floorSrcX * 65536.0);
+			floorSrcFracY = (uint32)(floorSrcY * 65536.0);
+
+			ceilingSrcFracX = (uint32)(ceilingSrcX * 65536.0);
+			ceilingSrcFracY = (uint32)(ceilingSrcY * 65536.0);
+
+			floorViewAngle /= _cosTable[_rightmostAngle];
+			ceilingViewAngle /= _cosTable[_rightmostAngle];
+
+			floorSrcIncrementX = (uint32)(((_cosTable[rightAngle] * floorViewAngle + (float)_playerY - floorSrcX) / (float)viewBounds.width()) * 65536.0);
+			floorSrcIncrementY = (uint32)(((_sinTable[rightAngle] * -(floorViewAngle) + (float)_playerX - floorSrcY) / (float)viewBounds.width()) * 65536.0);
+
+			ceilingSrcIncrementX = (uint32)(((_cosTable[rightAngle] * ceilingViewAngle + (float)_playerY - ceilingSrcX) / (float)viewBounds.width()) * 65536.0);
+			ceilingSrcIncrementY = (uint32)(((_sinTable[rightAngle] * -(ceilingViewAngle) + (float)_playerX - ceilingSrcY) / (float)viewBounds.width()) * 65536.0);
+		}
+
+		for (int x = viewBounds.left; x < viewBounds.right; ++x) {
+			if (_zBuffer[floorY * _drawSurface.w + x] != curZBufferDepth) {
+				byte offset = (floorSrcFracY >> 23) * _mapFullWidth + (floorSrcFracX >> 23);
+				*floorDest = *(int16 *)_floorTextures[_floorMap[offset]][_floorCeilingLightMap[offset] & 0xF].getBasePtr((floorSrcFracX >> 16) & 0x7F, (floorSrcFracY >> 16) & 0x7F);
+			}
+
+			++floorDest;
+			floorSrcFracX += floorSrcIncrementX;
+			floorSrcFracY += floorSrcIncrementY;
+
+			if (_zBuffer[ceilingY * _drawSurface.w + x] != curZBufferDepth) {
+				byte offset = (ceilingSrcFracY >> 23) * _mapFullWidth + (ceilingSrcFracX >> 23);
+				*ceilingDest = *(int16 *)_ceilingTextures[_ceilingMap[offset]][(_floorCeilingLightMap[offset] >> 4) & 0xF].getBasePtr((ceilingSrcFracX >> 16) & 0x7F, (ceilingSrcFracY >> 16) & 0x7F);
+			}
+
+			++ceilingDest;
+			ceilingSrcFracX += ceilingSrcIncrementX;
+			ceilingSrcFracY += ceilingSrcIncrementY;
+		}
+	}
+
+	_lastZDepth += 2;
+	if (_lastZDepth > 250) {
+		clearZBuffer();
+	}
+
+	_needsRedraw = true;
+}
+
+void RaycastPuzzle::clearZBuffer() {
+	for (uint i = 0; i < _zBuffer.size(); ++i) {
+		_zBuffer[i] = 0;
+	}
+
+	_lastZDepth = 0;
+}
+
+void RaycastPuzzle::checkSwitch() {
+	
+}
+
+} // End of namespace Action
+} // End of namespace Nancy
diff --git a/engines/nancy/action/raycastpuzzle.h b/engines/nancy/action/raycastpuzzle.h
new file mode 100644
index 00000000000..76781d960d9
--- /dev/null
+++ b/engines/nancy/action/raycastpuzzle.h
@@ -0,0 +1,121 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef NANCY_ACTION_RAYCASTPUZZLE_H
+#define NANCY_ACTION_RAYCASTPUZZLE_H
+
+#include "engines/nancy/action/actionrecord.h"
+
+namespace Nancy {
+
+class RCPR;
+
+namespace Action {
+
+// Action record implementing nancy3's maze minigame
+class RaycastPuzzle : public RenderActionRecord {
+public:
+	RaycastPuzzle()  : RenderActionRecord(7), _map(7) {}
+	~RaycastPuzzle() override {}
+
+	void init() override;
+	void registerGraphics() override;
+
+	void readData(Common::SeekableReadStream &stream) override;
+	void execute() override;
+	void handleInput(NancyInput &input) override;
+	void updateGraphics() override;
+
+	uint16 _mapWidth = 0;
+	uint16 _mapHeight = 0;
+	byte _wallHeight = 0;
+
+	Common::String _switchSoundName;
+	uint16 _switchSoundChannelID = 0;
+	Common::String _unknownSoundName;
+	uint16 _unknownSoundChannelID = 0;
+	SoundDescription _dummySound;
+
+	SceneChangeWithFlag _solveScene;
+	SoundDescription _solveSound;
+
+	Common::Array<uint32> _wallMap, _infoMap;
+	Common::Array<int16> _floorMap, _ceilingMap;
+	Common::Array<uint16> _wallLightMap, _floorCeilingLightMap, _heightMap;
+
+	uint16 _mapFullWidth = 0;
+	uint16 _mapFullHeight = 0;
+
+	RenderObject _map;
+
+	double _pi = 3.141592653589793;
+	uint _fov = 192;
+	Common::Array<float> _sinTable, _cosTable;
+
+	Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _wallTextures;
+	Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _specialWallTextures;
+	Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _ceilingTextures;
+	Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _floorTextures;
+	Common::HashMap<uint16, Common::Array<Graphics::ManagedSurface>> _exitFloorTextures;
+
+	Common::Array<int32> _wallCastColumnAngles;
+	Common::Array<byte> _zBuffer;
+	byte _lastZDepth = 0;
+	Common::Array<float> _depthBuffer;
+	int32 _leftmostAngle = -1;
+	int32 _rightmostAngle = -1;
+
+	int32 _playerX = -1;				// Player position with precision 1/128th of cell width/height
+	int32 _playerY = -1;
+	int32 _playerRotation; 				// Rotation of player (0 - 4096)
+	uint32 _playerAltitude = 88;		// Z position of "camera"; only modified in god mode
+
+	float _rotationSingleStep = 4096.0 / (_pi * 2);
+	float _maxWorldDistance;
+
+	uint32 _lastMovementTime;
+	int _lastMouseX = -1;
+
+	uint32 _nextSlowdownMovementTime = 0;
+	byte _slowdownFramesLeft = 0;
+	int32 _slowdownDeltaX = -1;
+	int32 _slowdownDeltaY = -1;
+
+	RCPR *_puzzleData = nullptr;
+
+protected:
+	Common::String getRecordTypeName() const override { return "RaycastPuzzle"; }
+	bool isViewportRelative() const override { return true; }
+
+	void loadTextures();
+	void createTextureLightSourcing(Common::Array<Graphics::ManagedSurface> *array, Common::String &textureName);
+
+	void drawMap();
+	void drawMaze();
+	void clearZBuffer();
+	
+	void checkSwitch();
+};
+
+} // End of namespace Action
+} // End of namespace Nancy
+
+#endif // NANCY_ACTION_RAYCASTPUZZLE_H
diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index 2b4fa2a553d..8e08044c38f 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -427,6 +427,150 @@ SPEC::SPEC(Common::SeekableReadStream *chunkStream) {
 	crossDissolveNumFrames = chunkStream->readUint16LE();
 }
 
+RCLB::RCLB(Common::SeekableReadStream *chunkStream) {
+	assert(chunkStream);
+
+	chunkStream->seek(0);
+
+	lightSwitchID = chunkStream->readUint16LE();
+	unk2 = chunkStream->readUint16LE();
+
+	char buf[100];
+
+	while (chunkStream->pos() < chunkStream->size()) {
+		themes.push_back(Theme());
+		Theme &theme = themes.back();
+
+		chunkStream->read(buf, 100);
+		theme.themeName = buf;
+
+		for (uint i = 0; i < 10; ++i) {
+			int32 val = chunkStream->readSint32LE();
+			if (val != -1) {
+				theme.wallIDs.push_back(val);
+			}
+		}
+
+		for (uint i = 0; i < 10; ++i) {
+			int16 val = chunkStream->readUint16LE();
+			if (val != -1) {
+				theme.floorIDs.push_back(val);
+			}
+		}
+
+		for (uint i = 0; i < 10; ++i) {
+			int16 val = chunkStream->readSint16LE();
+			if (val != -1) {
+				theme.exitFloorIDs.push_back(val);
+			}
+		}
+
+		for (uint i = 0; i < 10; ++i) {
+			int16 val = chunkStream->readSint16LE();
+			if (val != -1) {
+				theme.ceilingIDs.push_back(val);
+			}
+		}
+
+		for (uint i = 0; i < 10; ++i) {
+			int32 val = chunkStream->readSint32LE();
+			if (val != -1) {
+				theme.doorIDs.push_back(val);
+			}
+		}
+
+		for (uint i = 0; i < 10; ++i) {
+			int32 val = chunkStream->readSint32LE();
+			if (val != -1) {
+				theme.transparentwallIDs.push_back(val);
+			}
+		}
+
+		for (uint i = 0; i < 10; ++i) {
+			int32 val = chunkStream->readSint32LE();
+			if (val != -1) {
+				theme.objectwallIDs.push_back(val);
+			}
+		}
+
+		for (uint i = 0; i < 10; ++i) {
+			int16 val = chunkStream->readSint16LE();
+			if (val != -1) {
+				theme.objectWallHeights.push_back(val);
+			}
+		}
+
+		theme.generalLighting = chunkStream->readUint16LE();
+		theme.hasLightSwitch = chunkStream->readUint16LE();
+		theme.transparentWallDensity = chunkStream->readSint16LE();
+		theme.objectWallDensity = chunkStream->readSint16LE();
+		theme.doorDensity = chunkStream->readSint16LE();
+	}
+}
+
+RCPR::RCPR(Common::SeekableReadStream *chunkStream) {
+	assert(chunkStream);
+
+	chunkStream->seek(0);
+
+	readRectArray(*chunkStream, screenViewportSizes, 6);
+	viewportSizeUsed = chunkStream->readUint16LE();
+
+	wallColor[0] = chunkStream->readByte();
+	wallColor[1] = chunkStream->readByte();
+	wallColor[2] = chunkStream->readByte();
+
+	playerColor[0] = chunkStream->readByte();
+	playerColor[1] = chunkStream->readByte();
+	playerColor[2] = chunkStream->readByte();
+
+	doorColor[0] = chunkStream->readByte();
+	doorColor[1] = chunkStream->readByte();
+	doorColor[2] = chunkStream->readByte();
+
+	lightSwitchColor[0] = chunkStream->readByte();
+	lightSwitchColor[1] = chunkStream->readByte();
+	lightSwitchColor[2] = chunkStream->readByte();
+
+	exitColor[0] = chunkStream->readByte();
+	exitColor[1] = chunkStream->readByte();
+	exitColor[2] = chunkStream->readByte();
+
+	uColor6[0] = chunkStream->readByte();
+	uColor6[1] = chunkStream->readByte();
+	uColor6[2] = chunkStream->readByte();
+
+	uColor7[0] = chunkStream->readByte();
+	uColor7[1] = chunkStream->readByte();
+	uColor7[2] = chunkStream->readByte();
+
+	uColor8[0] = chunkStream->readByte();
+	uColor8[1] = chunkStream->readByte();
+	uColor8[2] = chunkStream->readByte();
+
+	transparentWallColor[0] = chunkStream->readByte();
+	transparentWallColor[1] = chunkStream->readByte();
+	transparentWallColor[2] = chunkStream->readByte();
+
+	uColor10[0] = chunkStream->readByte();
+	uColor10[1] = chunkStream->readByte();
+	uColor10[2] = chunkStream->readByte();
+
+	Common::String tmp;
+	while (chunkStream->pos() < chunkStream->size()) {
+		readFilename(*chunkStream, tmp);
+		if (tmp.hasPrefix("Wall")) {
+			wallNames.push_back(tmp);
+		} else if (tmp.hasPrefix("SpW")) {
+			specialWallNames.push_back(tmp);
+		} else if (tmp.hasPrefix("Ceil")) {
+			ceilingNames.push_back(tmp);
+		} else if (tmp.hasPrefix("Floor")) {
+			floorNames.push_back(tmp);
+		}
+	}
+}
+
 ImageChunk::ImageChunk(Common::SeekableReadStream *chunkStream) {
 	assert(chunkStream);
 
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index fd50bd14469..0fc8b690c87 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -216,6 +216,59 @@ struct SPEC {
 	byte crossDissolveNumFrames;
 };
 
+struct RCLB {
+	struct Theme {
+		Common::String themeName;
+
+		Common::Array<uint32> wallIDs;
+
+		Common::Array<uint16> exitFloorIDs;
+		Common::Array<uint16> floorIDs;
+		Common::Array<uint16> ceilingIDs;
+		
+		Common::Array<uint32> doorIDs;
+		Common::Array<uint32> transparentwallIDs;
+		Common::Array<uint32> objectwallIDs;
+		Common::Array<uint16> objectWallHeights;
+
+		uint16 generalLighting;
+		uint16 hasLightSwitch;
+		
+		int16 transparentWallDensity;
+		int16 objectWallDensity;
+		int16 doorDensity;
+	};
+
+	RCLB(Common::SeekableReadStream *chunkStream);
+
+	uint16 lightSwitchID;
+	uint16 unk2;
+	Common::Array<Theme> themes;
+};
+
+struct RCPR {
+	RCPR(Common::SeekableReadStream *chunkStream);
+
+	Common::Array<Common::Rect> screenViewportSizes;
+	uint16 viewportSizeUsed;
+
+	byte wallColor[3];
+	byte playerColor[3];
+	byte doorColor[3];
+	byte lightSwitchColor[3];
+	byte exitColor[3];
+	byte uColor6[3];
+	byte uColor7[3];
+	byte uColor8[3];
+	byte transparentWallColor[3];
+	byte uColor10[3];
+
+	Common::Array<Common::String> wallNames;
+	Common::Array<Common::String> specialWallNames;
+	Common::Array<Common::String> ceilingNames;
+	Common::Array<Common::String> floorNames;
+};
+
 struct ImageChunk {
 	ImageChunk() : width(0), height(0) {}
 	ImageChunk(Common::SeekableReadStream *chunkStream);
diff --git a/engines/nancy/module.mk b/engines/nancy/module.mk
index c2fc6266216..ad6670c7463 100644
--- a/engines/nancy/module.mk
+++ b/engines/nancy/module.mk
@@ -11,10 +11,11 @@ MODULE_OBJS = \
   action/overlay.o \
   action/overridelockpuzzle.o \
   action/passwordpuzzle.o \
+  action/raycastpuzzle.o \
   action/recordtypes.o \
+  action/riddlepuzzle.o \
   action/rippedletterpuzzle.o \
   action/rotatinglockpuzzle.o \
-  action/riddlepuzzle.o \
   action/secondarymovie.o \
   action/secondaryvideo.o \
   action/setplayerclock.o \
diff --git a/engines/nancy/nancy.cpp b/engines/nancy/nancy.cpp
index dff85eb5f86..13950e84a8b 100644
--- a/engines/nancy/nancy.cpp
+++ b/engines/nancy/nancy.cpp
@@ -81,6 +81,8 @@ NancyEngine::NancyEngine(OSystem *syst, const NancyGameDescription *gd) :
 	_sliderPuzzleData = nullptr;
 	_clockData = nullptr;
 	_specialEffectData = nullptr;
+	_raycastPuzzleData = nullptr;
+	_raycastPuzzleLevelBuilderData = nullptr;
 }
 
 NancyEngine::~NancyEngine() {
@@ -102,6 +104,8 @@ NancyEngine::~NancyEngine() {
 	delete _sliderPuzzleData;
 	delete _clockData;
 	delete _specialEffectData;
+	delete _raycastPuzzleData;
+	delete _raycastPuzzleLevelBuilderData;
 }
 
 NancyEngine *NancyEngine::create(GameType type, OSystem *syst, const NancyGameDescription *gd) {
@@ -395,6 +399,16 @@ void NancyEngine::bootGameEngine() {
 		_specialEffectData = new SPEC(chunkStream);
 	}
 
+	chunkStream = boot->getChunkStream("RCPR");
+	if (chunkStream) {
+		_raycastPuzzleData = new RCPR(chunkStream);
+	}
+
+	chunkStream = boot->getChunkStream("RCLB");
+	if (chunkStream) {
+		_raycastPuzzleLevelBuilderData = new RCLB(chunkStream);
+	}
+
 	_sound->loadCommonSounds(boot);
 
 	delete boot;
diff --git a/engines/nancy/nancy.h b/engines/nancy/nancy.h
index c76ef1de754..a26a7c64f62 100644
--- a/engines/nancy/nancy.h
+++ b/engines/nancy/nancy.h
@@ -127,6 +127,8 @@ public:
 	SPUZ *_sliderPuzzleData;
 	CLOK *_clockData;
 	SPEC *_specialEffectData;
+	RCPR *_raycastPuzzleData;
+	RCLB *_raycastPuzzleLevelBuilderData;
 
 	Common::HashMap<Common::String, ImageChunk> _imageChunks;
 




More information about the Scummvm-git-logs mailing list