2090 lines
68 KiB
C++
2090 lines
68 KiB
C++
/* 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/puzzle/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);
|
|
|
|
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;
|
|
|
|
const RCLB *_themeData;
|
|
};
|
|
|
|
RaycastLevelBuilder::RaycastLevelBuilder(uint width, uint height, uint verticalHeight) {
|
|
_themeData = GetEngineData(RCLB);
|
|
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();
|
|
}
|
|
|
|
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);
|
|
const 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) {
|
|
const 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) {
|
|
const 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) {
|
|
const 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
|
|
// 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;
|
|
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 switchID) {
|
|
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] = (switchID << 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) {
|
|
const 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 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 * _mapFullWidth + x] == -1) {
|
|
error("floorMap not complete at coordinates x = %d, y = %d", x, y);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class RaycastDeferredLoader : public DeferredLoader {
|
|
public:
|
|
RaycastDeferredLoader(RaycastPuzzle &owner, uint width, uint height, uint verticalHeight) :
|
|
_owner(owner),
|
|
_builder(width, height, verticalHeight),
|
|
_loadState(kInitDrawSurface),
|
|
_x(0), _y(0),
|
|
_isDone(false) {}
|
|
virtual ~RaycastDeferredLoader() {}
|
|
|
|
bool _isDone;
|
|
|
|
private:
|
|
bool loadInner() override;
|
|
|
|
enum State { kInitDrawSurface, kInitPlayerLocationRotation, kCopyData, kInitMap, kInitTables1, kInitTables2, kLoadTextures };
|
|
|
|
State _loadState;
|
|
|
|
RaycastPuzzle &_owner;
|
|
RaycastLevelBuilder _builder;
|
|
|
|
uint16 _x, _y;
|
|
};
|
|
|
|
bool RaycastDeferredLoader::loadInner() {
|
|
switch(_loadState) {
|
|
case kInitDrawSurface : {
|
|
auto *viewportData = GetEngineData(VIEW);
|
|
assert(viewportData);
|
|
|
|
Common::Rect viewport = viewportData->bounds;
|
|
_owner.moveTo(viewport);
|
|
_owner._drawSurface.create(viewport.width(), viewport.height(), g_nancy->_graphics->getInputPixelFormat());
|
|
_owner.setTransparent(true);
|
|
|
|
_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);
|
|
_owner._floorMap.swap(_builder._floorMap);
|
|
_owner._ceilingMap.swap(_builder._ceilingMap);
|
|
_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 : {
|
|
_owner.drawMap();
|
|
_owner._map.setVisible(false);
|
|
|
|
_loadState = kInitTables1;
|
|
break;
|
|
}
|
|
case kInitTables1 : {
|
|
Common::Rect selectedBounds = _owner._puzzleData->screenViewportSizes[_owner._puzzleData->viewportSizeUsed];
|
|
auto *viewportData = GetEngineData(VIEW);
|
|
assert(viewportData);
|
|
|
|
_owner._wallCastColumnAngles.resize(viewportData->screenPosition.width());
|
|
uint center = selectedBounds.left + (selectedBounds.width() >> 1);
|
|
for (uint i = 0; i < _owner._wallCastColumnAngles.size(); ++i) {
|
|
int32 &angle = _owner._wallCastColumnAngles[i];
|
|
angle = (int32)(atan(((float)i - (float)center) / (float)_owner._fov) * _owner._rotationSingleStep);
|
|
clampRotation(angle);
|
|
}
|
|
|
|
_owner._leftmostAngle = _owner._wallCastColumnAngles[selectedBounds.left];
|
|
_owner._rightmostAngle = _owner._wallCastColumnAngles[selectedBounds.right];
|
|
|
|
_loadState = kInitTables2;
|
|
break;
|
|
}
|
|
case kInitTables2 : {
|
|
auto *viewportData = GetEngineData(VIEW);
|
|
assert(viewportData);
|
|
|
|
_owner._sinTable.resize(4096);
|
|
_owner._cosTable.resize(4096);
|
|
|
|
for (uint i = 0; i < 4096; ++i) {
|
|
double f = (i * _owner._pi * 2) / 4096;
|
|
_owner._cosTable[i] = cos(f);
|
|
_owner._sinTable[i] = sin(f);
|
|
}
|
|
|
|
_owner._maxWorldDistance = sqrt(((128 * _owner._mapFullWidth) - 1) * ((128 * _owner._mapFullHeight) - 1) * 2);
|
|
_owner._depthBuffer.resize(viewportData->bounds.width());
|
|
_owner._zBuffer.resize(viewportData->bounds.width() * viewportData->bounds.height(), 0);
|
|
_owner._lastZDepth = 0;
|
|
|
|
_loadState = kLoadTextures;
|
|
break;
|
|
}
|
|
case kLoadTextures: {
|
|
bool shouldBreak = false;
|
|
|
|
for (; _y < _owner._mapFullHeight; ++_y) {
|
|
if (_x >= _owner._mapFullWidth) {
|
|
_x = 0;
|
|
}
|
|
|
|
for (; _x < _owner._mapFullWidth && !shouldBreak; ++_x) {
|
|
uint32 wallMapVal = _owner._wallMap[_y * _owner._mapFullHeight + _x];
|
|
|
|
for (uint i = 0; i < 3; ++i) {
|
|
byte textureID = wallMapVal & 0xFF;
|
|
|
|
if (textureID & 0x80) {
|
|
if (!_owner._specialWallTextures.contains(textureID & 0x7F)) {
|
|
_owner.createTextureLightSourcing(&_owner._specialWallTextures[textureID & 0x7F], _owner._puzzleData->specialWallNames[(textureID & 0x7F) - 1]);
|
|
shouldBreak = true;
|
|
}
|
|
} else if (textureID) {
|
|
if (!_owner._wallTextures.contains(textureID & 0x7F)) {
|
|
_owner.createTextureLightSourcing(&_owner._wallTextures[textureID], _owner._puzzleData->wallNames[textureID - 1]);
|
|
shouldBreak = true;
|
|
}
|
|
}
|
|
|
|
wallMapVal >>= 8;
|
|
}
|
|
|
|
if (shouldBreak) {
|
|
break;
|
|
}
|
|
|
|
int16 floorMapVal = _owner._floorMap[_y * _owner._mapFullWidth + _x];
|
|
int16 ceilingMapVal = _owner._ceilingMap[_y * _owner._mapFullWidth + _x];
|
|
|
|
if (!_owner._floorTextures.contains(floorMapVal)) {
|
|
_owner.createTextureLightSourcing(&_owner._floorTextures[floorMapVal], _owner._puzzleData->floorNames[floorMapVal]);
|
|
shouldBreak = true;
|
|
break;
|
|
}
|
|
|
|
if (!_owner._ceilingTextures.contains(ceilingMapVal)) {
|
|
_owner.createTextureLightSourcing(&_owner._ceilingTextures[ceilingMapVal], _owner._puzzleData->ceilingNames[ceilingMapVal]);
|
|
shouldBreak = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (shouldBreak) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!shouldBreak) {
|
|
for (auto &a : _owner._specialWallTextures) {
|
|
for (auto &tex : a._value) {
|
|
tex.setTransparentColor(g_nancy->_graphics->getTransColor());
|
|
}
|
|
}
|
|
|
|
_owner.validateMap();
|
|
_isDone = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return _isDone;
|
|
}
|
|
|
|
RaycastPuzzle::~RaycastPuzzle() {
|
|
g_nancy->_input->setKeymapEnabled(Nancy::InputManager::_mazeKeymapID, false);
|
|
}
|
|
|
|
void RaycastPuzzle::init() {
|
|
_puzzleData = GetEngineData(RCPR);
|
|
assert(_puzzleData);
|
|
|
|
RaycastDeferredLoader *loader = _loaderPtr.get();
|
|
if (!loader) {
|
|
_loaderPtr.reset(new RaycastDeferredLoader(*this, _mapWidth, _mapHeight, _wallHeight));
|
|
auto castedPtr = _loaderPtr.dynamicCast<DeferredLoader>();
|
|
g_nancy->addDeferredLoader(castedPtr);
|
|
} else {
|
|
if (loader->_isDone) {
|
|
_loaderPtr.reset();
|
|
registerGraphics();
|
|
|
|
_state = kRun;
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
g_nancy->_input->setKeymapEnabled(Nancy::InputManager::_mazeKeymapID, true);
|
|
break;
|
|
case kRun:
|
|
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::onPause(bool pause) {
|
|
RenderActionRecord::onPause(pause);
|
|
g_nancy->_input->setKeymapEnabled(Nancy::InputManager::_mazeKeymapID, !pause);
|
|
}
|
|
|
|
void RaycastPuzzle::handleInput(NancyInput &input) {
|
|
if (_state != kRun) {
|
|
return;
|
|
}
|
|
|
|
if (input.input & NancyInput::kRaycastMap) {
|
|
_map.setVisible(!_map.isVisible());
|
|
}
|
|
|
|
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);
|
|
|
|
float newX = _playerX;
|
|
float newY = _playerY;
|
|
bool hasMoved = false;
|
|
bool hasMovedSlowdown = false;
|
|
|
|
// Move forward/backwards
|
|
// 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 + dX * 2;
|
|
newY = _playerY + dY * 2;
|
|
} else {
|
|
newX = _playerX + dX;
|
|
newY = _playerY + dY;
|
|
}
|
|
|
|
hasMoved = true;
|
|
}
|
|
|
|
if (input.input & NancyInput::kMoveDown || (input.input & NancyInput::kRightMouseButtonHeld && mouseIsInBounds)) {
|
|
if (input.input & NancyInput::kMoveFastModifier) {
|
|
newX = _playerX - dX * 2;
|
|
newY = _playerY - dY * 2;
|
|
} else {
|
|
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;
|
|
newX += _slowdownDeltaX;
|
|
newY += _slowdownDeltaY;
|
|
hasMoved = true;
|
|
hasMovedSlowdown = true;
|
|
_nextSlowdownMovementTime = time + 40;
|
|
--_slowdownFramesLeft;
|
|
}
|
|
|
|
// Perform collision
|
|
if (hasMoved) {
|
|
uint yGrid = ((int32)newX) >> 7;
|
|
uint xGrid = ((int32)newY) >> 7;
|
|
|
|
int32 xCell = ((int32)newX) & 0x7F;
|
|
int32 yCell = ((int32)newY) & 0x7F;
|
|
|
|
int collisionSize = 48;
|
|
int c = 0;
|
|
|
|
#define ClampCell(x) (c = x, c >= 0 && c < (int)_wallMap.size() && xGrid > 0 && yGrid > 0 && xGrid < _mapFullWidth && yGrid < _mapFullHeight) ? _wallMap[c] : 1;
|
|
|
|
// Check neighboring cells
|
|
uint32 cellLeft = ClampCell(yGrid * _mapFullWidth + xGrid - 1);
|
|
uint32 cellTop = ClampCell((yGrid - 1) * _mapFullWidth + xGrid);
|
|
uint32 cellRight = ClampCell(yGrid * _mapFullWidth + xGrid + 1);
|
|
uint32 cellBottom = ClampCell((yGrid + 1) * _mapFullWidth + xGrid);
|
|
|
|
// Allow passage through doors
|
|
cellLeft = (cellLeft & kDoor) ? 0 : cellLeft;
|
|
cellTop = (cellTop & kDoor) ? 0 : cellTop;
|
|
cellRight = (cellRight & kDoor) ? 0 : cellRight;
|
|
cellBottom = (cellBottom & kDoor) ? 0 : cellBottom;
|
|
|
|
if (cellLeft && yCell < collisionSize) {
|
|
newY = (((int32)newY) & 0xFF80) + collisionSize;
|
|
}
|
|
|
|
if (cellTop && xCell < collisionSize) {
|
|
newX = (((int32)newX) & 0xFF80) + collisionSize;
|
|
}
|
|
|
|
if (cellRight && yCell > (128 - collisionSize)) {
|
|
newY = (((int32)newY) & 0xFF80) + (128 - collisionSize);
|
|
}
|
|
|
|
if (cellBottom && xCell > (128 - collisionSize)) {
|
|
newX = (((int32)newX) & 0xFF80) + (128 - collisionSize);
|
|
}
|
|
|
|
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;
|
|
cellRight = xGrid < _mapFullWidth ? _wallMap[yGrid * _mapFullWidth + xGrid + 1] : 1;
|
|
cellBottom = yGrid < _mapFullHeight ? _wallMap[(yGrid + 1) * _mapFullWidth + xGrid] : 1;
|
|
|
|
uint32 cellTopLeft = ClampCell((yGrid - 1) * _mapFullWidth + xGrid - 1);
|
|
uint32 cellTopRight = ClampCell((yGrid - 1) * _mapFullWidth + xGrid + 1);
|
|
uint32 cellBottomLeft = ClampCell((yGrid + 1) * _mapFullWidth + xGrid - 1);
|
|
uint32 cellBottomRight = ClampCell((yGrid + 1) * _mapFullWidth + xGrid + 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;
|
|
|
|
// Make sure the player doesn't clip diagonally into a wall
|
|
// 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)) {
|
|
if (yCell > xCell) {
|
|
newX = (((int32)newX) & 0xFF80) + collisionSize;
|
|
} else {
|
|
newY = (((int32)newY) & 0xFF80) + collisionSize;
|
|
}
|
|
}
|
|
|
|
if (cellTopRight && !cellRight && !cellTop && (yCell < collisionSize) && (xCell > (128 - collisionSize))) {
|
|
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)) {
|
|
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))) {
|
|
if (128 - yCell > 128 - xCell) {
|
|
newX = (((int32)newX) & 0xFF80) + (128 - collisionSize);
|
|
} else {
|
|
newY = (((int32)newY) & 0xFF80) + (128 - collisionSize);
|
|
}
|
|
}
|
|
|
|
if (!hasMovedSlowdown) {
|
|
_slowdownDeltaX = newX - _playerX;
|
|
_slowdownDeltaY = newY - _playerY;
|
|
_nextSlowdownMovementTime = time + 40;
|
|
_slowdownFramesLeft = 10;
|
|
}
|
|
|
|
_playerX = newX;
|
|
_playerY = newY;
|
|
}
|
|
}
|
|
|
|
void RaycastPuzzle::updateGraphics() {
|
|
if (_state == kRun) {
|
|
drawMaze();
|
|
updateMap();
|
|
}
|
|
}
|
|
|
|
void RaycastPuzzle::drawMap() {
|
|
// Improvement: the original map is drawn upside-down; ours isn't
|
|
auto *bootSummary = GetEngineData(BSUM);
|
|
assert(bootSummary);
|
|
|
|
_mapBaseSurface.create(_mapFullWidth, _mapFullHeight, g_nancy->_graphics->getInputPixelFormat());
|
|
_map._drawSurface.create(_mapFullWidth, _mapFullHeight, g_nancy->_graphics->getInputPixelFormat());
|
|
Common::Rect mapPos(bootSummary->textboxScreenPosition);
|
|
mapPos.setWidth(_mapFullWidth * 2);
|
|
mapPos.setHeight(_mapFullHeight * 2);
|
|
_map.moveTo(mapPos);
|
|
_map.init();
|
|
|
|
uint16 *pixelPtr;
|
|
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]);
|
|
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 *)_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];
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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::Path &textureName) {
|
|
Graphics::PixelFormat format = g_nancy->_graphics->getInputPixelFormat();
|
|
array->resize(8);
|
|
|
|
uint16 transColor = g_nancy->_graphics->getTransColor();
|
|
|
|
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;
|
|
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);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
int32 columnAngleForX = _wallCastColumnAngles[x];
|
|
int32 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.001f;
|
|
}
|
|
|
|
if (angleCos == 0.0) {
|
|
angleCos = 0.001f;
|
|
}
|
|
|
|
float angleAtan = angleCos / angleSin;
|
|
|
|
if (angleAtan == 0.0) {
|
|
angleAtan = 0.001f;
|
|
}
|
|
|
|
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] / 768, 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
|
|
int32 leftAngle = _playerRotation + _leftmostAngle;
|
|
int32 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 floorViewAngle = ((float)_fov / (float)(floorY - viewportCenterY)) * (float)_playerAltitude;
|
|
float ceilingViewAngle = ((float)_fov / (float)(viewportCenterY - ceilingY)) * (float)((_wallHeight * 128) - _playerAltitude);
|
|
|
|
float floorLeftX = _cosTable[leftAngle] * (floorViewAngle / _cosTable[_leftmostAngle]) + (float)_playerY;
|
|
float floorRightX = _cosTable[rightAngle] * (floorViewAngle / _cosTable[_rightmostAngle]) + (float)_playerY;
|
|
float floorLeftY = _sinTable[leftAngle] * -(floorViewAngle / _cosTable[_leftmostAngle]) + (float)_playerX;
|
|
float floorRightY = _sinTable[rightAngle] * -(floorViewAngle / _cosTable[_rightmostAngle]) + (float)_playerX;
|
|
|
|
float ceilingLeftX = _cosTable[leftAngle] * (ceilingViewAngle / _cosTable[_leftmostAngle]) + (float)_playerY;
|
|
float ceilingRightX = _cosTable[rightAngle] * (ceilingViewAngle / _cosTable[_rightmostAngle]) + (float)_playerY;
|
|
float ceilingLeftY = _sinTable[leftAngle] * -(ceilingViewAngle / _cosTable[_leftmostAngle]) + (float)_playerX;
|
|
float ceilingRightY = _sinTable[rightAngle] * -(ceilingViewAngle / _cosTable[_rightmostAngle]) + (float)_playerX;
|
|
|
|
// Casting between negative float and uint is undefined behavior, hence the cast to signed int first
|
|
floorSrcFracX = (uint32)((int32)(floorLeftX * 65536.0));
|
|
floorSrcFracY = (uint32)((int32)(floorLeftY * 65536.0));
|
|
|
|
ceilingSrcFracX = (uint32)((int32)(ceilingLeftX * 65536.0));
|
|
ceilingSrcFracY = (uint32)((int32)(ceilingLeftY * 65536.0));
|
|
|
|
floorSrcIncrementX = (uint32)((int32)(((floorRightX - floorLeftX) / (float)viewBounds.width()) * 65536.0));
|
|
floorSrcIncrementY = (uint32)((int32)(((floorRightY - floorLeftY) / (float)viewBounds.width()) * 65536.0));
|
|
|
|
ceilingSrcIncrementX = (uint32)((int32)(((ceilingRightX - ceilingLeftX) / (float)viewBounds.width()) * 65536.0));
|
|
ceilingSrcIncrementY = (uint32)((int32)(((ceilingRightY - ceilingLeftY) / (float)viewBounds.width()) * 65536.0));
|
|
}
|
|
|
|
for (int x = viewBounds.left; x < viewBounds.right; ++x) {
|
|
if (_zBuffer[floorY * _drawSurface.w + x] != curZBufferDepth) {
|
|
uint16 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) {
|
|
uint16 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() {
|
|
// 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;
|
|
_dummySound.name = _switchSoundName;
|
|
_dummySound.channelID = _switchSoundChannelID;
|
|
g_nancy->_sound->playSound(_dummySound);
|
|
}
|
|
}
|
|
} 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
|
|
} // End of namespace Nancy
|