scummvm/engines/kyra/scene_rpg.cpp

634 lines
17 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 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#if defined(ENABLE_EOB) || defined(ENABLE_LOL)
#include "kyra/kyra_rpg.h"
#include "kyra/resource.h"
#include "kyra/timer.h"
#include "kyra/sound.h"
#include "common/system.h"
namespace Kyra {
void KyraRpgEngine::setLevelShapesDim(int index, int16 &x1, int16 &x2, int dim) {
if (_lvlShapeLeftRight[index << 1] == -1) {
x1 = 0;
x2 = 22;
int16 y1 = 0;
int16 y2 = 120;
int m = index * 18;
for (int i = 0; i < 18; i++) {
uint8 d = _visibleBlocks[i]->walls[_sceneDrawVarDown];
uint8 a = _wllWallFlags[d];
if (a & 8) {
int t = _dscDim2[(m + i) << 1];
if (t > x1) {
x1 = t;
if (!(a & 0x10))
setDoorShapeDim(index, y1, y2, -1);
}
t = _dscDim2[((m + i) << 1) + 1];
if (t < x2) {
x2 = t;
if (!(a & 0x10))
setDoorShapeDim(index, y1, y2, -1);
}
} else {
int t = _dscDim1[m + i];
if (!_wllVmpMap[d] || t == -40)
continue;
if (t == -41) {
x1 = 22;
x2 = 0;
break;
}
if (t > 0 && x2 > t)
x2 = t;
if (t < 0 && x1 < -t)
x1 = -t;
}
if (x2 < x1)
break;
}
x1 += (_sceneXoffset >> 3);
x2 += (_sceneXoffset >> 3);
_lvlShapeTop[index] = y1;
_lvlShapeBottom[index] = y2;
_lvlShapeLeftRight[index << 1] = x1;
_lvlShapeLeftRight[(index << 1) + 1] = x2;
} else {
x1 = _lvlShapeLeftRight[index << 1];
x2 = _lvlShapeLeftRight[(index << 1) + 1];
}
drawLevelModifyScreenDim(dim, x1, 0, x2, 15);
}
void KyraRpgEngine::setDoorShapeDim(int index, int16 &y1, int16 &y2, int dim) {
uint8 a = _dscDimMap[index];
if (_flags.gameID != GI_EOB1 && dim == -1 && a != 3)
a++;
uint8 b = a;
if (_flags.gameID == GI_EOB1) {
a += _dscDoorFrameIndex1[_currentLevel - 1];
b += _dscDoorFrameIndex2[_currentLevel - 1];
}
y1 = _dscDoorFrameY1[a];
y2 = _dscDoorFrameY2[b];
if (dim == -1)
return;
const ScreenDim *cDim = screen()->getScreenDim(dim);
screen()->modifyScreenDim(dim, cDim->sx, y1, cDim->w, y2 - y1);
}
void KyraRpgEngine::drawLevelModifyScreenDim(int dim, int16 x1, int16 y1, int16 x2, int16 y2) {
screen()->modifyScreenDim(dim, x1, y1 << 3, x2 - x1, (y2 - y1) << 3);
}
void KyraRpgEngine::generateBlockDrawingBuffer() {
_sceneDrawVarDown = _dscBlockMap[_currentDirection];
_sceneDrawVarRight = _dscBlockMap[_currentDirection + 4];
_sceneDrawVarLeft = _dscBlockMap[_currentDirection + 8];
/*******************************************
* _visibleBlocks map *
* *
* | | | | | | *
* 00 | 01 | 02 | 03 | 04 | 05 | 06 *
* ____|_____|_____|_____|_____|_____|_____ *
* | | | | | | *
* | 07 | 08 | 09 | 10 | 11 | *
* |_____|_____|_____|_____|_____| *
* | | | | *
* | 12 | 13 | 14 | *
* |_____|_____|_____| *
* | | *
* 15 | 16 | 17 *
* | (P) | *
********************************************/
memset(_blockDrawingBuffer, 0, 660 * sizeof(uint16));
_wllProcessFlag = ((_currentBlock >> 5) + (_currentBlock & 0x1F) + _currentDirection) & 1;
if (_wllProcessFlag) // floor and ceiling
generateVmpTileDataFlipped(0, 15, 1, -330, 22, 15);
else
generateVmpTileData(0, 15, 1, -330, 22, 15);
assignVisibleBlocks(_currentBlock, _currentDirection);
uint8 t = _visibleBlocks[0]->walls[_sceneDrawVarRight];
if (t)
generateVmpTileData(-2, 3, t, 102, 3, 5);
t = _visibleBlocks[6]->walls[_sceneDrawVarLeft];
if (t)
generateVmpTileDataFlipped(21, 3, t, 102, 3, 5);
t = _visibleBlocks[1]->walls[_sceneDrawVarRight];
uint8 t2 = _visibleBlocks[2]->walls[_sceneDrawVarDown];
if (hasWall(t) && !(_wllWallFlags[t2] & 8))
generateVmpTileData(2, 3, t, 102, 3, 5);
else if (t && (_wllWallFlags[t2] & 8))
generateVmpTileData(2, 3, t2, 102, 3, 5);
t = _visibleBlocks[5]->walls[_sceneDrawVarLeft];
t2 = _visibleBlocks[4]->walls[_sceneDrawVarDown];
if (hasWall(t) && !(_wllWallFlags[t2] & 8))
generateVmpTileDataFlipped(17, 3, t, 102, 3, 5);
else if (t && (_wllWallFlags[t2] & 8))
generateVmpTileDataFlipped(17, 3, t2, 102, 3, 5);
t = _visibleBlocks[2]->walls[_sceneDrawVarRight];
if (t)
generateVmpTileData(8, 3, t, 97, 1, 5);
t = _visibleBlocks[4]->walls[_sceneDrawVarLeft];
if (t)
generateVmpTileDataFlipped(13, 3, t, 97, 1, 5);
t = _visibleBlocks[1]->walls[_sceneDrawVarDown];
if (hasWall(t))
generateVmpTileData(-4, 3, t, 129, 6, 5);
t = _visibleBlocks[5]->walls[_sceneDrawVarDown];
if (hasWall(t))
generateVmpTileData(20, 3, t, 129, 6, 5);
t = _visibleBlocks[2]->walls[_sceneDrawVarDown];
if (hasWall(t))
generateVmpTileData(2, 3, t, 129, 6, 5);
t = _visibleBlocks[4]->walls[_sceneDrawVarDown];
if (hasWall(t))
generateVmpTileData(14, 3, t, 129, 6, 5);
t = _visibleBlocks[3]->walls[_sceneDrawVarDown];
if (t)
generateVmpTileData(8, 3, t, 129, 6, 5);
t = _visibleBlocks[7]->walls[_sceneDrawVarRight];
if (t)
generateVmpTileData(0, 3, t, 117, 2, 6);
t = _visibleBlocks[11]->walls[_sceneDrawVarLeft];
if (t)
generateVmpTileDataFlipped(20, 3, t, 117, 2, 6);
t = _visibleBlocks[8]->walls[_sceneDrawVarRight];
if (t)
generateVmpTileData(6, 2, t, 81, 2, 8);
t = _visibleBlocks[10]->walls[_sceneDrawVarLeft];
if (t)
generateVmpTileDataFlipped(14, 2, t, 81, 2, 8);
t = _visibleBlocks[8]->walls[_sceneDrawVarDown];
if (hasWall(t))
generateVmpTileData(-4, 2, t, 159, 10, 8);
t = _visibleBlocks[10]->walls[_sceneDrawVarDown];
if (hasWall(t))
generateVmpTileData(16, 2, t, 159, 10, 8);
t = _visibleBlocks[9]->walls[_sceneDrawVarDown];
if (t)
generateVmpTileData(6, 2, t, 159, 10, 8);
t = _visibleBlocks[12]->walls[_sceneDrawVarRight];
if (t)
generateVmpTileData(3, 1, t, 45, 3, 12);
t = _visibleBlocks[14]->walls[_sceneDrawVarLeft];
if (t)
generateVmpTileDataFlipped(16, 1, t, 45, 3, 12);
t = _visibleBlocks[12]->walls[_sceneDrawVarDown];
if (!(_wllWallFlags[t] & 8))
generateVmpTileData(-13, 1, t, 239, 16, 12);
t = _visibleBlocks[14]->walls[_sceneDrawVarDown];
if (!(_wllWallFlags[t] & 8))
generateVmpTileData(19, 1, t, 239, 16, 12);
t = _visibleBlocks[13]->walls[_sceneDrawVarDown];
if (t)
generateVmpTileData(3, 1, t, 239, 16, 12);
t = _visibleBlocks[15]->walls[_sceneDrawVarRight];
t2 = _visibleBlocks[17]->walls[_sceneDrawVarLeft];
if (t)
generateVmpTileData(0, 0, t, 0, 3, 15);
if (t2)
generateVmpTileDataFlipped(19, 0, t2, 0, 3, 15);
}
void KyraRpgEngine::generateVmpTileData(int16 startBlockX, uint8 startBlockY, uint8 vmpMapIndex, int16 vmpOffset, uint8 numBlocksX, uint8 numBlocksY) {
if (!_wllVmpMap[vmpMapIndex])
return;
uint16 *vmp = &_vmpPtr[(_wllVmpMap[vmpMapIndex] - 1) * 431 + vmpOffset + 330];
for (int i = 0; i < numBlocksY; i++) {
uint16 *bl = &_blockDrawingBuffer[(startBlockY + i) * 22 + startBlockX];
for (int ii = 0; ii < numBlocksX; ii++) {
if ((startBlockX + ii >= 0) && (startBlockX + ii < 22) && *vmp)
*bl = *vmp;
bl++;
vmp++;
}
}
}
void KyraRpgEngine::generateVmpTileDataFlipped(int16 startBlockX, uint8 startBlockY, uint8 vmpMapIndex, int16 vmpOffset, uint8 numBlocksX, uint8 numBlocksY) {
if (!_wllVmpMap[vmpMapIndex])
return;
uint16 *vmp = &_vmpPtr[(_wllVmpMap[vmpMapIndex] - 1) * 431 + vmpOffset + 330];
for (int i = 0; i < numBlocksY; i++) {
for (int ii = 0; ii < numBlocksX; ii++) {
if ((startBlockX + ii) < 0 || (startBlockX + ii) > 21)
continue;
uint16 v = vmp[i * numBlocksX + (numBlocksX - 1 - ii)];
if (!v)
continue;
if (v & 0x4000)
v -= 0x4000;
else
v |= 0x4000;
_blockDrawingBuffer[(startBlockY + i) * 22 + startBlockX + ii] = v;
}
}
}
bool KyraRpgEngine::hasWall(int index) {
if (!index || (_wllWallFlags[index] & 8))
return false;
return true;
}
void KyraRpgEngine::assignVisibleBlocks(int block, int direction) {
for (int i = 0; i < 18; i++) {
uint16 t = (block + _dscBlockIndex[direction * 18 + i]) & 0x3FF;
_visibleBlockIndex[i] = t;
_visibleBlocks[i] = &_levelBlockProperties[t];
_lvlShapeLeftRight[i] = _lvlShapeLeftRight[18 + i] = -1;
}
}
bool KyraRpgEngine::checkSceneUpdateNeed(int block) {
if (_sceneUpdateRequired)
return true;
for (int i = 0; i < 15; i++) {
if (_visibleBlockIndex[i] == block) {
_sceneUpdateRequired = true;
return true;
}
}
if (_currentBlock == block) {
_sceneUpdateRequired = true;
return true;
}
return false;
}
void KyraRpgEngine::drawVcnBlocks() {
uint8 *d = _sceneWindowBuffer;
uint16 *bdb = _blockDrawingBuffer;
for (int y = 0; y < 15; y++) {
for (int x = 0; x < 22; x++) {
bool horizontalFlip = false;
uint16 vcnOffset = *bdb++;
uint16 vcnExtraOffsetWll = 0;
int wllVcnOffset = 0;
int wllVcnRmdOffset = 0;
if (vcnOffset & 0x8000) {
// this renders a wall block over the transparent pixels of a floor/ceiling block
vcnExtraOffsetWll = vcnOffset - 0x8000;
vcnOffset = 0;
wllVcnRmdOffset = _wllVcnOffset;
}
if (vcnOffset & 0x4000) {
horizontalFlip = true;
vcnOffset &= 0x3FFF;
}
uint8 *src = 0;
if (vcnOffset) {
src = &_vcnBlocks[vcnOffset << 5];
wllVcnOffset = _wllVcnOffset;
} else {
// floor/ceiling blocks
vcnOffset = bdb[329];
if (vcnOffset & 0x4000) {
horizontalFlip = true;
vcnOffset &= 0x3FFF;
}
src = (_vcfBlocks ? _vcfBlocks : _vcnBlocks) + (vcnOffset << 5);
}
uint8 shift = _vcnShift ? _vcnShift[vcnOffset] : _blockBrightness;
if (horizontalFlip) {
for (int blockY = 0; blockY < 8; blockY++) {
src += 3;
for (int blockX = 0; blockX < 4; blockX++) {
uint8 bl = *src--;
*d++ = _vcnColTable[((bl & 0x0F) + wllVcnOffset) | shift];
*d++ = _vcnColTable[((bl >> 4) + wllVcnOffset) | shift];
}
src += 5;
d += 168;
}
} else {
for (int blockY = 0; blockY < 8; blockY++) {
for (int blockX = 0; blockX < 4; blockX++) {
uint8 bl = *src++;
*d++ = _vcnColTable[((bl >> 4) + wllVcnOffset) | shift];
*d++ = _vcnColTable[((bl & 0x0F) + wllVcnOffset) | shift];
}
d += 168;
}
}
d -= 1400;
if (vcnExtraOffsetWll) {
d -= 8;
horizontalFlip = false;
if (vcnExtraOffsetWll & 0x4000) {
vcnExtraOffsetWll &= 0x3FFF;
horizontalFlip = true;
}
shift = _vcnShift ? _vcnShift[vcnExtraOffsetWll] : _blockBrightness;
src = &_vcnBlocks[vcnExtraOffsetWll << 5];
uint8 *maskTable = _vcnTransitionMask ? &_vcnTransitionMask[vcnExtraOffsetWll << 5] : 0;
if (horizontalFlip) {
for (int blockY = 0; blockY < 8; blockY++) {
src += 3;
maskTable += 3;
for (int blockX = 0; blockX < 4; blockX++) {
uint8 bl = *src--;
uint8 mask = _vcnTransitionMask ? *maskTable-- : 0;
uint8 h = _vcnColTable[((bl & 0x0F) + wllVcnRmdOffset) | shift];
uint8 l = _vcnColTable[((bl >> 4) + wllVcnRmdOffset) | shift];
if (_vcnTransitionMask)
*d = (*d & (mask & 0x0F)) | h;
else if (h)
*d = h;
d++;
if (_vcnTransitionMask)
*d = (*d & (mask >> 4)) | l;
else if (l)
*d = l;
d++;
}
src += 5;
maskTable += 5;
d += 168;
}
} else {
for (int blockY = 0; blockY < 8; blockY++) {
for (int blockX = 0; blockX < 4; blockX++) {
uint8 bl = *src++;
uint8 mask = _vcnTransitionMask ? *maskTable++ : 0;
uint8 h = _vcnColTable[((bl >> 4) + wllVcnRmdOffset) | shift];
uint8 l = _vcnColTable[((bl & 0x0F) + wllVcnRmdOffset) | shift];
if (_vcnTransitionMask)
*d = (*d & (mask >> 4)) | h;
else if (h)
*d = h;
d++;
if (_vcnTransitionMask)
*d = (*d & (mask & 0x0F)) | l;
else if (l)
*d = l;
d++;
}
d += 168;
}
}
d -= 1400;
}
}
d += 1232;
}
screen()->copyBlockToPage(_sceneDrawPage1, _sceneXoffset, 0, 176, 120, _sceneWindowBuffer);
}
uint16 KyraRpgEngine::calcNewBlockPosition(uint16 curBlock, uint16 direction) {
static const int16 blockPosTable[] = { -32, 1, 32, -1 };
return (curBlock + blockPosTable[direction]) & 0x3FF;
}
int KyraRpgEngine::clickedWallShape(uint16 block, uint16 direction) {
uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]];
if (!clickedShape(v))
return 0;
snd_stopSpeech(true);
runLevelScript(block, 0x40);
return 1;
}
int KyraRpgEngine::clickedLeverOn(uint16 block, uint16 direction) {
uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]];
if (!clickedShape(v))
return 0;
_levelBlockProperties[block].walls[direction]++;
_sceneUpdateRequired = true;
if (_flags.gameID == GI_LOL)
snd_playSoundEffect(30, -1);
runLevelScript(block, _clickedSpecialFlag);
return 1;
}
int KyraRpgEngine::clickedLeverOff(uint16 block, uint16 direction) {
uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]];
if (!clickedShape(v))
return 0;
_levelBlockProperties[block].walls[direction]--;
_sceneUpdateRequired = true;
if (_flags.gameID == GI_LOL)
snd_playSoundEffect(29, -1);
runLevelScript(block, _clickedSpecialFlag);
return 1;
}
int KyraRpgEngine::clickedWallOnlyScript(uint16 block) {
runLevelScript(block, _clickedSpecialFlag);
return 1;
}
void KyraRpgEngine::processDoorSwitch(uint16 block, int openClose) {
if (block == _currentBlock)
return;
if ((_flags.gameID == GI_LOL && (_levelBlockProperties[block].assignedObjects & 0x8000)) || (_flags.gameID != GI_LOL && (_levelBlockProperties[block].flags & 7)))
return;
if (openClose == 0) {
for (int i = 0; i < 3; i++) {
if (_openDoorState[i].block != block)
continue;
openClose = -_openDoorState[i].state;
break;
}
}
if (openClose == 0) {
openClose = (_wllWallFlags[_levelBlockProperties[block].walls[_wllWallFlags[_levelBlockProperties[block].walls[0]] & 8 ? 0 : 1]] & 1) ? 1 : -1;
if (_flags.gameID != GI_LOL)
openClose *= -1;
}
openCloseDoor(block, openClose);
}
void KyraRpgEngine::openCloseDoor(int block, int openClose) {
int s1 = -1;
int s2 = -1;
int c = (_wllWallFlags[_levelBlockProperties[block].walls[0]] & 8) ? 0 : 1;
int v = _levelBlockProperties[block].walls[c];
int flg = (_flags.gameID == GI_EOB1) ? 1 : ((openClose == 1) ? 0x10 : (openClose == -1 ? 0x20 : 0));
if ((_flags.gameID == GI_EOB1 && openClose == -1 && !(_wllWallFlags[v] & flg)) || (!(_flags.gameID == GI_EOB1 && openClose == -1) && (_wllWallFlags[v] & flg)))
return;
for (int i = 0; i < 3; i++) {
if (_openDoorState[i].block == block) {
s1 = i;
break;
} else if (_openDoorState[i].block == 0 && s2 == -1) {
s2 = i;
}
}
if (s1 != -1 || s2 != -1) {
if (s1 == -1)
s1 = s2;
_openDoorState[s1].block = block;
_openDoorState[s1].state = openClose;
_openDoorState[s1].wall = c;
flg = (-openClose == 1) ? 0x10 : (-openClose == -1 ? 0x20 : 0);
if (_wllWallFlags[v] & flg) {
_levelBlockProperties[block].walls[c] += openClose;
_levelBlockProperties[block].walls[c ^ 2] += openClose;
int snd = (openClose == -1) ? 4 : 3;
if (_flags.gameID == GI_LOL) {
snd_processEnvironmentalSoundEffect(snd + 28, _currentBlock);
if (!checkSceneUpdateNeed(block))
updateEnvironmentalSfx(0);
} else {
updateEnvironmentalSfx(snd);
}
}
enableTimer(_flags.gameID == GI_LOL ? 0 : 4);
} else {
while (!(flg & _wllWallFlags[v]))
v += openClose;
_levelBlockProperties[block].walls[c] = _levelBlockProperties[block].walls[c ^ 2] = v;
checkSceneUpdateNeed(block);
}
}
void KyraRpgEngine::completeDoorOperations() {
for (int i = 0; i < 3; i++) {
if (!_openDoorState[i].block)
continue;
uint16 b = _openDoorState[i].block;
do {
_levelBlockProperties[b].walls[_openDoorState[i].wall] += _openDoorState[i].state;
_levelBlockProperties[b].walls[_openDoorState[i].wall ^ 2] += _openDoorState[i].state;
} while (!(_wllWallFlags[_levelBlockProperties[b].walls[_openDoorState[i].wall]] & 0x30));
_openDoorState[i].block = 0;
}
}
} // End of namespace Kyra
#endif // ENABLE_EOB || ENABLE_LOL