scummvm/engines/kyra/engine/sprites_eob.cpp
athrxx d84061c036 KYRA: (EOB2/Amiga) - fix German version monster sound
(Monster sounds are broken in the original, because the devs seem to have accidently left the sound track numbers from the DOS version. This will add a workaround.)
2019-04-13 18:55:02 +02:00

1249 lines
32 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.
*
*/
#ifdef ENABLE_EOB
#include "kyra/engine/eobcommon.h"
#include "kyra/script/script_eob.h"
#include "kyra/resource/resource.h"
#include "kyra/engine/timer.h"
#include "common/system.h"
namespace Kyra {
void EoBCoreEngine::loadMonsterShapes(const char *filename, int monsterIndex, bool hasDecorations, int encodeTableIndex) {
if (_flags.platform == Common::kPlatformFMTowns) {
Common::String tmp = Common::String::format("%s.MNT", filename);
Common::SeekableReadStream *s = _res->createReadStream(tmp);
if (!s)
error("Screen_EoB::loadMonsterShapes(): Failed to load file '%s'", tmp.c_str());
for (int i = 0; i < 6; i++)
_monsterShapes[monsterIndex + i] = loadTownsShape(s);
for (int i = 0; i < 6; i++) {
for (int ii = 0; ii < 2; ii++)
s->read(_monsterPalettes[(monsterIndex >= 18 ? i + 6 : i) * 2 + ii], 16);
}
if (hasDecorations)
loadMonsterDecoration(s, monsterIndex);
delete s;
} else {
_screen->loadShapeSetBitmap(filename, 3, 3);
const uint16 *enc = &_encodeMonsterShpTable[encodeTableIndex << 2];
for (int i = 0; i < 6; i++, enc += 4)
_monsterShapes[monsterIndex + i] = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, _cgaMappingDefault);
generateMonsterPalettes(filename, monsterIndex);
if (hasDecorations) {
Common::SeekableReadStream *s = _res->createReadStream(Common::String::format("%s.DCR", filename));
if (s)
loadMonsterDecoration(s, monsterIndex);
delete s;
}
}
_screen->_curPage = 0;
}
void EoBCoreEngine::releaseMonsterShapes(int first, int num) {
for (int i = first; i < first + num; i++) {
delete[] _monsterShapes[i];
_monsterShapes[i] = 0;
delete[] _monsterDecorations[i].shp;
_monsterDecorations[i].shp = 0;
}
}
uint8 *EoBCoreEngine::loadTownsShape(Common::SeekableReadStream *stream) {
uint32 size = stream->readUint32LE();
uint8 *shape= new uint8[size];
stream->read(shape, size);
if (shape[0] == 1)
shape[0]++;
return shape;
}
const uint8 *EoBCoreEngine::loadActiveMonsterData(const uint8 *data, int level) {
for (uint8 p = *data++; p != 0xFF; p = *data++) {
uint8 v = *data++;
_timer->setCountdown(0x20 + (p << 1), v);
_timer->setCountdown(0x21 + (p << 1), v);
}
uint32 ct = _system->getMillis();
for (int i = 0x20; i < 0x24; i++) {
int32 del = _timer->getDelay(i);
_timer->setNextRun(i, (i & 1) ? ct + (del >> 1) * _tickLength : ct + del * _tickLength);
}
_timer->resetNextRun();
if (_hasTempDataFlags & (1 << (level - 1)))
return data + 420;
memset(_monsters, 0, 30 * sizeof(EoBMonsterInPlay));
for (int i = 0; i < 30; i++, data += 14) {
if (*data == 0xFF)
continue;
initMonster(data[0], data[1], READ_LE_UINT16(&data[2]), data[4], (int8)data[5], data[6], data[7], data[8], data[9], READ_LE_UINT16(&data[10]), READ_LE_UINT16(&data[12]));
_monsters[data[0]].flags |= 0x40;
}
return data;
}
void EoBCoreEngine::initMonster(int index, int unit, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int i, int randItem, int fixedItem) {
EoBMonsterInPlay *m = &_monsters[index];
EoBMonsterProperty *p = &_monsterProps[type];
memset(m, 0, sizeof(EoBMonsterInPlay));
if (!block)
return;
unit <<= 1;
if (index & 1)
unit++;
m->stepsTillRemoteAttack = _flags.gameID == GI_EOB2 ? rollDice(1, 3, 0) : 5;
m->type = type;
m->numRemoteAttacks = p->numRemoteAttacks;
m->curRemoteWeapon = 0;
m->unit = unit;
m->pos = pos;
m->shpIndex = shpIndex;
m->mode = mode;
m->spellStatusLeft = i;
m->dir = dir;
m->palette = _flags.gameID == GI_EOB2 ? (index % 3) : 0;
m->hitPointsCur = m->hitPointsMax = _flags.gameID == GI_EOB2 ? rollDice(p->hpDcTimes, p->hpDcPips, p->hpDcBase) : (p->level == -1 ? rollDice(1, 4, 0) : rollDice(p->level, 8, 0));
m->randItem = randItem;
m->fixedItem = fixedItem;
m->sub = _currentSub;
placeMonster(m, block, dir);
}
void EoBCoreEngine::placeMonster(EoBMonsterInPlay *m, uint16 block, int dir) {
if (block != 0xFFFF) {
checkSceneUpdateNeed(m->block);
if (_levelBlockProperties[m->block].flags & 7) {
_levelBlockProperties[m->block].flags--;
if (_flags.gameID == GI_EOB2)
runLevelScript(m->block, 0x400);
}
m->block = block;
_levelBlockProperties[block].flags++;
if (_flags.gameID == GI_EOB2)
runLevelScript(m->block, 0x200);
}
if (dir != -1) {
m->dir = dir;
block = m->block;
}
checkSceneUpdateNeed(block);
}
void EoBCoreEngine::killMonster(EoBMonsterInPlay *m, bool giveExperience) {
m->hitPointsCur = -1;
int pos = (m->pos == 4) ? rollDice(1, 4, -1) : m->pos;
if (m->randItem) {
if (rollDice(1, 10, 0) == 1)
setItemPosition((Item *)&_levelBlockProperties[m->block & 0x3FF].drawObjects, m->block, duplicateItem(m->randItem), pos);
}
if (m->fixedItem)
setItemPosition((Item *)&_levelBlockProperties[m->block & 0x3FF].drawObjects, m->block, duplicateItem(m->fixedItem), pos);
if (giveExperience)
increasePartyExperience(_monsterProps[m->type].experience);
if (killMonsterExtra(m)) {
placeMonster(m, 0, -1);
if (m->mode == 8)
updateAttackingMonsterFlags();
}
}
int EoBCoreEngine::countSpecificMonsters(int type) {
int res = 0;
for (int i = 0; i < 30; i++) {
if (_monsters[i].type != type || _monsters[i].sub != _currentSub || _monsters[i].hitPointsCur < 0)
continue;
res++;
}
return res;
}
void EoBCoreEngine::updateAttackingMonsterFlags() {
EoBMonsterInPlay *m2 = 0;
for (EoBMonsterInPlay *m = _monsters; m < &_monsters[30]; m++) {
if (m->mode != 8)
continue;
m->mode = 0;
m->dest = _currentBlock;
m2 = m;
}
if (!m2)
return;
if (m2->type == 7)
setScriptFlags(4);
if (m2->type == 12)
setScriptFlags(0x800);
}
const int8 *EoBCoreEngine::getMonstersOnBlockPositions(uint16 block) {
memset(_monsterBlockPosArray, -1, sizeof(_monsterBlockPosArray));
for (int8 i = 0; i < 30; i++) {
if (_monsters[i].block != block)
continue;
assert(_monsters[i].pos < sizeof(_monsterBlockPosArray));
_monsterBlockPosArray[_monsters[i].pos] = i;
}
return _monsterBlockPosArray;
}
int EoBCoreEngine::getClosestMonster(int charIndex, int block) {
const int8 *pos = getMonstersOnBlockPositions(block);
if (pos[4] != -1)
return pos[4];
const uint8 *p = &_monsterProximityTable[(_currentDirection << 3) + ((charIndex & 1) << 2)];
for (int i = 0; i < 4; i++) {
if (pos[p[i]] != -1)
return pos[p[i]];
}
return -1;
}
bool EoBCoreEngine::blockHasMonsters(uint16 block) {
return _levelBlockProperties[block].flags & 7 ? true : false;
}
bool EoBCoreEngine::isMonsterOnPos(EoBMonsterInPlay *m, uint16 block, int pos, int checkPos4) {
return (m->block == block && (m->pos == pos || (m->pos == 4 && checkPos4))) ? true : false;
}
const int16 *EoBCoreEngine::findBlockMonsters(uint16 block, int pos, int dir, int blockDamage, int singleTargetCheckAdjacent) {
static const uint8 cpos4[] = { 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1 };
int include4 = (pos < 4) ? cpos4[(dir << 2) + pos] : 1;
int16 *dst = _foundMonstersArray;
if (blockDamage) {
for (int i = 0; i < 30; i++) {
if (_monsters[i].block == block && (_monsters[i].pos != 4 || include4))
*dst++ = i;
}
} else if (singleTargetCheckAdjacent) {
int16 r = -1;
int f = 5;
for (int i = 0; i < 30; i++) {
const uint8 *tbl = &_findBlockMonstersTable[(dir << 4) + (pos << 2)];
if (_monsters[i].block != block)
continue;
if (_monsters[i].pos == pos) {
r = i;
break;
}
for (int ii = 0; ii < 4; ii++) {
if (_monsters[i].pos == tbl[ii] && ii < f) {
f = ii;
r = i;
}
}
}
*dst++ = r;
} else {
for (int i = 0; i < 30; i++) {
if (isMonsterOnPos(&_monsters[i], block, pos, include4))
*dst++ = i;
}
}
*dst = -1;
return _foundMonstersArray;
}
void EoBCoreEngine::drawBlockObject(int flipped, int page, const uint8 *shape, int x, int y, int sd, uint8 *ovl) {
const ScreenDim *d = _screen->getScreenDim(sd);
if (_flags.gameID == GI_EOB1)
x &= ~1;
_screen->drawShape(page, shape, x - (d->sx << 3), y - d->sy, sd, flipped | (ovl ? 2 : 0), ovl);
}
void EoBCoreEngine::drawMonsterShape(const uint8 *shape, int x, int y, int flipped, int flags, int palIndex) {
uint8 *ovl = 0;
if (flags & 2)
ovl = _monsterFlashOverlay;
else if (_flags.gameID == GI_EOB2 && flags & 0x20)
ovl = _monsterStoneOverlay;
else if (palIndex != -1 && _flags.platform != Common::kPlatformAmiga)
ovl = _monsterPalettes[palIndex];
drawBlockObject(flipped, 2, shape, x, y, 5, ovl);
}
void EoBCoreEngine::flashMonsterShape(EoBMonsterInPlay *m) {
disableSysTimer(2);
_flashShapeTimer = 0;
drawScene(1);
m->flags &= 0xFD;
_flashShapeTimer = _system->getMillis() + _tickLength;
enableSysTimer(2);
_sceneUpdateRequired = true;
}
void EoBCoreEngine::updateAllMonsterShapes() {
drawScene(1);
bool updateShp = false;
for (EoBMonsterInPlay *m = _monsters; m < &_monsters[30]; m++) {
if (m->flags & 2) {
m->flags &= ~2;
updateShp = true;
if (m->hitPointsCur <= 0)
killMonster(m, true);
}
}
if (updateShp) {
_sceneUpdateRequired = true;
_flashShapeTimer = _system->getMillis() + _tickLength;
} else {
_sceneUpdateRequired = false;
}
_preventMonsterFlash = false;
}
void EoBCoreEngine::drawBlockItems(int index) {
uint16 o = _visibleBlocks[index]->drawObjects;
uint8 w = _visibleBlocks[index]->walls[_sceneDrawVarDown];
uint8 flg = (index == 16) ? 0x80 : _wllWallFlags[w];
if (_wllVmpMap[w] && !(flg & 0x80))
return;
uint16 o2 = o = _items[o].next;
bool forceLoop = true;
static const int8 itemPosYNiche[] = { 0x25, 0x31, 0x38, 0x00 };
static const int8 itemPosFin[] = { 0, -2, 1, -1, 2, 0, 1, -1 };
int tile2 = 0;
while (o != o2 || forceLoop) {
EoBItem *itm = &_items[o];
if (itm->pos == 8 || itm->pos < 4) {
tile2 = -1;
uint8 ps = (itm->pos == 8) ? 4 : _dscItemPosIndex[(_currentDirection << 2) + (itm->pos & 3)];
uint16 wo = (index * 5 + ps) << 1;
int x = _dscShapeCoords[wo] + 88;
int y = 0;
if (itm->pos == 8) {
x = _dscItemShpX[index];
y = itemPosYNiche[_dscDimMap[index]];
ps = 0;
} else {
y = _dscShapeCoords[wo + 1] + 124;
}
int8 scaleSteps = (int8)_dscItemScaleIndex[(_dscDimMap[index] << 2) + ps];
if ((flg & 8) && ps < 2 && scaleSteps) {
tile2 = _dscItemTileIndex[index];
if (tile2 != -1)
setLevelShapesDim(tile2, _shpDmX1, _shpDmX2, 5);
y -= 4;
}
if (scaleSteps >= 0) {
const uint8 *shp = 0;
if (_flags.gameID == GI_EOB2 || scaleSteps == 0)
shp = _screen->scaleShape(_dscItemShapeMap[itm->icon] < _numLargeItemShapes ? _largeItemShapes[_dscItemShapeMap[itm->icon]] : (_dscItemShapeMap[itm->icon] < 15 ? 0 : _smallItemShapes[_dscItemShapeMap[itm->icon] - 15]), scaleSteps);
else
shp = _dscItemShapeMap[itm->icon] < _numLargeItemShapes ? _largeItemShapesScl[scaleSteps - 1][_dscItemShapeMap[itm->icon]] : (_dscItemShapeMap[itm->icon] < 15 ? 0 : _smallItemShapesScl[scaleSteps - 1][_dscItemShapeMap[itm->icon] - 15]);
x = x + (itemPosFin[o & 7] << 1) - ((shp[2] << 3) >> 1);
y -= shp[1];
if (itm->pos != 8)
y += itemPosFin[(o >> 1) & 7];
drawBlockObject(0, 2, shp, x, y, 5);
_screen->setShapeFadingLevel(0);
}
}
o = itm->next;
forceLoop = false;
if (tile2 != -1)
setLevelShapesDim(index, _shpDmX1, _shpDmX2, 5);
}
}
void EoBCoreEngine::drawDoor(int index) {
int s = _visibleBlocks[index]->walls[_sceneDrawVarDown];
if (_flags.gameID == GI_EOB1 && s == 0x85)
s = 0;
if (s >= _dscDoorShpIndexSize)
return;
int type = _dscDoorShpIndex[s];
int d = _dscDimMap[index];
int w = _dscShapeCoords[(index * 5 + 4) << 1];
int x = 88 + w;
int y = 0;
int16 y1 = 0;
int16 y2 = 0;
setDoorShapeDim(index, y1, y2, 5);
drawDoorIntern(type, index, x, y, w, s, d, y1, y2);
drawLevelModifyScreenDim(5, _shpDmX1, 0, _shpDmX2, 15);
}
void EoBCoreEngine::drawMonsters(int index) {
static const uint8 distMap[] = { 2, 1, 0, 4 };
static const uint8 yAdd[] = { 20, 12, 4, 4, 2, 0, 0 };
int blockDistance = distMap[_dscDimMap[index]];
uint16 bl = _visibleBlockIndex[index];
if (!bl)
return;
int drawObjDirIndex = _currentDirection * 5;
int cDirOffs = _currentDirection << 2;
EoBMonsterInPlay *drawObj[5];
memset(drawObj, 0, 5 * sizeof(EoBMonsterInPlay *));
for (int i = 0; i < 30; i++) {
if (_monsters[i].block != bl)
continue;
drawObj[_drawObjPosIndex[drawObjDirIndex + _monsters[i].pos]] = &_monsters[i];
}
for (int i = 0; i < 5; i++) {
EoBMonsterInPlay *d = drawObj[i];
if (!d)
continue;
EoBMonsterProperty *p = &_monsterProps[d->type];
if (_flags.gameID == GI_EOB2 && (p->capsFlags & 0x100) && !(_partyEffectFlags & 0x220) && !(d->flags & 2))
continue;
int f = (d->animStep << 4) + cDirOffs + d->dir;
f = (p->capsFlags & 2) ? _monsterFrmOffsTable1[f] : _monsterFrmOffsTable2[f];
if (!blockDistance && d->curAttackFrame < 0)
f = d->curAttackFrame + 7;
int subFrame = ABS(f);
int shpIndex = d->shpIndex ? 18 : 0;
int palIndex = d->palette ? ((((shpIndex == 18) ? subFrame + 5 : subFrame - 1) << 1) + (d->palette - 1)) : -1;
const uint8 *shp = _screen->scaleShape(_monsterShapes[subFrame + shpIndex - 1], blockDistance);
int v30 = (subFrame == 1 || subFrame > 3) ? 1 : 0;
int v1e = (d->pos == 4) ? 4 : _dscItemPosIndex[cDirOffs + d->pos];
int posIndex = (index * 5 + v1e) << 1;
int x = _dscShapeCoords[posIndex] + 88;
int y = _dscShapeCoords[posIndex + 1] + 127;
if (p->u30 == 1) {
if (v30) {
if (_flags.gameID == GI_EOB2)
posIndex = ((posIndex >> 1) - v1e) << 1;
y = _dscShapeCoords[posIndex + 1] + 127 + yAdd[blockDistance + ((v1e == 4 || _flags.gameID == GI_EOB1) ? 0 : 3)];
} else {
if (_flags.gameID == GI_EOB2)
posIndex = ((posIndex >> 1) - v1e + 4) << 1;
x = _dscShapeCoords[posIndex] + 88;
}
}
int w = shp[2] << 3;
int h = shp[1];
x = x - (w >> 1) + (d->idleAnimState >> 4);
y = y - h + (d->idleAnimState & 0x0F);
drawMonsterShape(shp, x, y, f >= 0 ? 0 : 1, d->flags, palIndex);
if (_flags.gameID == GI_EOB1) {
_screen->setShapeFadingLevel(0);
continue;
}
for (int ii = 0; ii < 3; ii++) {
if (!p->decorations[ii])
continue;
SpriteDecoration *dcr = &_monsterDecorations[(p->decorations[ii] - 1) * 6 + subFrame + shpIndex - 1];
if (!dcr->shp)
continue;
shp = _screen->scaleShape(dcr->shp, blockDistance);
int dx = dcr->x;
int dy = dcr->y;
for (int iii = 0; iii < blockDistance; iii++) {
dx = (dx << 1) / 3;
dy = (dy << 1) / 3;
}
drawMonsterShape(shp, x + ((f < 0) ? (w - dx - (shp[2] << 3)) : dx), y + dy, f >= 0 ? 0 : 1, d->flags, -1);
}
_screen->setShapeFadingLevel(0);
}
}
void EoBCoreEngine::drawWallOfForce(int index) {
int d = _dscDimMap[index];
assert(d < 3);
int dH = _wallOfForceDsNumH[d];
int dW = _wallOfForceDsNumW[d];
int y = _wallOfForceDsY[d];
int shpId = _wallOfForceShpId[d] + _teleporterPulse;
int h = _wallOfForceShapes[shpId][1];
int w = _wallOfForceShapes[shpId][2] << 3;
for (int i = 0; i < dH; i++) {
int x = _wallOfForceDsX[index];
for (int ii = 0; ii < dW; ii++) {
drawBlockObject(0, 2, _wallOfForceShapes[shpId], x, y, 5);
x += w;
}
y += h;
shpId ^= 1;
}
}
void EoBCoreEngine::drawFlyingObjects(int index) {
LevelBlockProperty *bl = _visibleBlocks[index];
int blockIndex = _visibleBlockIndex[index];
int w = bl->walls[_sceneDrawVarDown];
if (_wllVmpMap[w] && !(_wllWallFlags[w] & 0x80))
return;
EoBFlyingObject *drawObj[5];
memset(drawObj, 0, 5 * sizeof(EoBFlyingObject *));
for (int i = 0; i < 10; i++) {
if (!_flyingObjects[i].enable || blockIndex != _flyingObjects[i].curBlock)
continue;
drawObj[_drawObjPosIndex[_currentDirection * 5 + (_flyingObjects[i].curPos & 3)]] = &_flyingObjects[i];
}
for (int i = 0; i < 5; i++) {
EoBFlyingObject *fo = drawObj[i];
if (!fo)
continue;
int p = _dscItemPosIndex[(_currentDirection << 2) + (fo->curPos & 3)];
int x = _dscShapeCoords[(index * 5 + p) << 1] + 88;
int y = 39;
int sclValue = _flightObjSclIndex[(index << 2) + p];
int flipped = 0;
if (sclValue < 0) {
_screen->setShapeFadingLevel(0);
continue;
}
const uint8 *shp = 0;
bool noFade = false;
if (fo->enable == 1) {
int shpIx = _dscItemShapeMap[_items[fo->item].icon];
int dirOffs = (fo->direction == _currentDirection) ? 0 : ((fo->direction == (_currentDirection ^ 2)) ? 1 : -1);
if (dirOffs == -1 || _flightObjShpMap[shpIx] == -1) {
if (_flags.gameID == GI_EOB2 || sclValue == 0)
shp = shpIx < _numLargeItemShapes ? _largeItemShapes[shpIx] : (shpIx < 15 ? 0 : _smallItemShapes[shpIx - 15]);
else
shp = shpIx < _numLargeItemShapes ? _largeItemShapesScl[sclValue - 1][shpIx] : (shpIx < 15 ? 0 : _smallItemShapesScl[sclValue - 1][shpIx - 15]);
flipped = fo->direction == ((_currentDirection + 1) & 3) ? 1 : 0;
} else {
if (_flags.gameID == GI_EOB2 || sclValue == 0)
shp = (_flightObjShpMap[shpIx] + dirOffs) < _numThrownItemShapes ? _thrownItemShapes[_flightObjShpMap[shpIx] + dirOffs] : _spellShapes[_flightObjShpMap[shpIx - _numThrownItemShapes] + dirOffs];
else
shp = (_flightObjShpMap[shpIx] + dirOffs) < _numThrownItemShapes ? _thrownItemShapesScl[sclValue - 1][_flightObjShpMap[shpIx] + dirOffs] : 0;
flipped = _flightObjFlipIndex[(fo->direction << 2) + (fo->curPos & 3)];
}
} else {
noFade = true;
if (_flags.gameID == GI_EOB2 || sclValue == 0)
shp = (fo->objectType < _numThrownItemShapes) ? _thrownItemShapes[fo->objectType] : _spellShapes[fo->objectType - _numThrownItemShapes];
else
shp = (fo->objectType < _numThrownItemShapes) ? _thrownItemShapesScl[sclValue - 1][fo->objectType] : 0;
flipped = _flightObjFlipIndex[(fo->direction << 2) + (fo->curPos & 3)];
if (fo->flags & 0x40) {
x = _dscShapeCoords[(index * 5 + 4) << 1] + 88;
y = 44;
}
}
assert(shp);
if (_flags.gameID == GI_EOB2 || sclValue == 0)
shp = _screen->scaleShape(shp, sclValue);
if (noFade) {
_screen->setShapeFadingLevel(0);
noFade = false;
}
x -= (shp[2] << 2);
y -= (y == 44 ? (shp[1] >> 1) : shp[1]);
drawBlockObject(flipped, 2, shp, x, y, 5);
_screen->setShapeFadingLevel(0);
}
}
void EoBCoreEngine::drawTeleporter(int index) {
static const uint8 telprtX[] = { 0x28, 0x1C, 0x12 };
static const uint8 telprtY[] = { 0x0D, 0x15, 0x1A };
int t = 2 - _dscDimMap[index];
if (t < 0)
return;
int16 x1 = _dscItemShpX[index] - telprtX[t];
int16 y1 = telprtY[t];
for (int i = 0; i < 2; i++) {
int16 x2 = 0;
int16 y2 = 0;
int d = (t << 1) + i;
if (!d)
x2 = y2 = -4;
const uint8 *shp = _teleporterShapes[d ^ _teleporterPulse];
for (int ii = 0; ii < 13; ii++)
drawBlockObject(0, 2, shp, x1 + x2 + _teleporterShapeCoords[d * 26 + ii * 2], y1 + y2 + _teleporterShapeCoords[d * 26 + ii * 2 + 1], 5);
}
}
void EoBCoreEngine::updateMonsters(int unit) {
for (int i = 0; i < 30; i++) {
EoBMonsterInPlay *m = &_monsters[i];
if (m->unit == unit) {
if (m->hitPointsCur <= 0 || m->flags & 0x20)
continue;
if (m->directionChanged) {
m->directionChanged = 0;
continue;
}
updateMonsterDest(m);
if (m->mode > 0)
updateMonsterAttackMode(m);
switch (m->mode) {
case 0:
updateMoveMonster(m);
break;
case 1:
updateMonsterFollowPath(m, 2);
break;
case 2:
updateMonsterFollowPath(m, -1);
break;
case 3:
updateMonsterFollowPath(m, 1);
break;
case 5:
updateMonstersStraying(m, -1);
break;
case 6:
updateMonstersStraying(m, 1);
break;
case 7:
case 10:
updateMonstersSpellStatus(m);
break;
default:
break;
}
if (m->mode != 4 && m->mode != 7 && m->mode != 8)
m->animStep ^= 1;
if (_monsterProps[m->type].u30 == 1)
setBlockMonsterDirection(m->block, m->dir);
}
}
checkFlyingObjects();
}
void EoBCoreEngine::updateMonsterDest(EoBMonsterInPlay *m) {
if (m->mode >= 7 && m->mode <= 10)
return;
int dist = getBlockDistance(m->block, _currentBlock);
if (dist >= 4)
return;
int s = getNextMonsterDirection(m->block, _currentBlock) - (m->dir << 1) - 3;
if (s < 0)
s += 8;
if (s <= 2 && dist >= 2)
return;
m->mode = 0;
m->dest = _currentBlock;
}
void EoBCoreEngine::updateMonsterAttackMode(EoBMonsterInPlay *m) {
if (!(m->flags & 1) || m->mode == 10)
return;
if (m->mode == 8) {
turnFriendlyMonstersHostile();
return;
}
m->mode = 0;
m->dest = _currentBlock;
}
void EoBCoreEngine::updateAllMonsterDests() {
for (int i = 0; i < 30; i++)
updateMonsterDest(&_monsters[i]);
}
void EoBCoreEngine::turnFriendlyMonstersHostile() {
EoBMonsterInPlay *m = 0;
for (int i = 0; i < 30; i++) {
if (_monsters[i].mode == 8) {
_monsters[i].mode = 0;
_monsters[i].dest = _currentBlock;
m = &_monsters[i];
}
}
if (m) {
if (m->type == 7)
setScriptFlags(0x40000);
else if (m->type == 12)
setScriptFlags(0x8000000);
}
}
int EoBCoreEngine::getNextMonsterDirection(int curBlock, int destBlock) {
uint8 c = destBlock % 32;
uint8 d = destBlock / 32;
uint8 e = curBlock % 32;
uint8 f = curBlock / 32;
int r = 0;
int s1 = (_flags.platform == Common::kPlatformAmiga) ? (d - f) : (f - d);
int d1 = ABS(s1);
s1 <<= 1;
int s2 = c - e;
int d2 = ABS(s2);
s2 <<= 1;
if (s1 >= d2)
r |= 8;
if (-s1 >= d2)
r |= 4;
if (s2 >= d1)
r |= 2;
if (-s2 >= d1)
r |= 1;
return _monsterDirChangeTable[r];
}
int EoBCoreEngine::getNextMonsterPos(EoBMonsterInPlay *m, int block) {
if ((_flags.gameID == GI_EOB1 && _monsterProps[m->type].u30 != 0) || (_flags.gameID == GI_EOB2 && _monsterProps[m->type].u30 == 2))
return -1;
int d = findFreeMonsterPos(block, _monsterProps[m->type].u30);
if (d < 0)
return -1;
int dir = m->dir;
if (_flags.gameID == GI_EOB2) {
if (_monsterProps[m->type].u30 == 1) {
if (d == 9)
return -1;
int v = _monsterCloseAttUnkTable[d];
if (v != -1)
//////
m->dir = 0;
return v;
}
} else {
dir &= 1;
}
for (int i = 0; i < 4; i++) {
int v = m->dir ^ _monsterCloseAttPosTable2[(dir << 2) + i];
if (!(d & (1 << v)))
return v;
}
return -1;
}
int EoBCoreEngine::findFreeMonsterPos(int block, int size) {
int nm = _levelBlockProperties[block].flags & 7;
if (nm == 4)
return -2;
int res = 0;
for (int i = 0; i < 30; i++) {
EoBMonsterInPlay *m = &_monsters[i];
if (m->block != block)
continue;
if (_monsterProps[m->type].u30 != size)
return -1;
if (m->pos == 4 && !(_flags.gameID == GI_EOB2 && m->flags & 0x20))
m->pos = (_flags.gameID == GI_EOB2 && _monsterProps[m->type].u30 == 1) ? 0 : _monsterCloseAttPosTable1[m->dir];
res |= (1 << m->pos);
if (--nm == 0)
break;
}
return res;
}
void EoBCoreEngine::updateMoveMonster(EoBMonsterInPlay *m) {
EoBMonsterProperty *p = &_monsterProps[m->type];
int d = getNextMonsterDirection(m->block, _currentBlock);
if ((_flags.gameID == GI_EOB2) && (p->capsFlags & 0x800) && !(d & 1))
d >>= 1;
else
d = m->dir;
d = calcNewBlockPosition(m->block, d);
if (m->dest == d && _currentBlock != d) {
m->mode = rollDice(1, 2, -1) + 5;
return;
}
if (updateMonsterTryDistanceAttack(m))
return;
if (updateMonsterTryCloseAttack(m, d))
return;
m->curAttackFrame = 0;
walkMonster(m, m->dest);
if (p->capsFlags & 8)
updateMonsterTryCloseAttack(m, -1);
}
bool EoBCoreEngine::updateMonsterTryDistanceAttack(EoBMonsterInPlay *m) {
EoBMonsterProperty *p = &_monsterProps[m->type];
if (!m->numRemoteAttacks || ((_flags.gameID == GI_EOB1) && !(p->capsFlags & 0x40)))
return false;
if ((_flags.gameID == GI_EOB1 && m->stepsTillRemoteAttack < 5) || (_flags.gameID == GI_EOB2 && (rollDice(1, 3) > m->stepsTillRemoteAttack))) {
m->stepsTillRemoteAttack++;
return false;
}
if (getBlockDistance(m->block, _currentBlock) > 3 || getNextMonsterDirection(m->block, _currentBlock) != (m->dir << 1))
return false;
int d = m->dir;
int bl = calcNewBlockPosition(m->block, d);
while (bl != _currentBlock) {
if (!(_wllWallFlags[_levelBlockProperties[bl].walls[d ^ 2]] & 3) || (_levelBlockProperties[bl].flags & 7))
return false;
bl = calcNewBlockPosition(bl, d);
}
Item itm = 0;
if (_flags.gameID == GI_EOB1) {
switch (m->type - 4) {
case 0:
launchMagicObject(-1, 9, m->block, m->pos, m->dir);
snd_processEnvironmentalSoundEffect(31, m->block);
break;
case 10:
launchMagicObject(-1, _enemyMageSpellList[m->numRemoteAttacks], m->block, m->pos, m->dir);
snd_processEnvironmentalSoundEffect(_enemyMageSfx[m->numRemoteAttacks], m->block);
break;
case 11:
itm = duplicateItem(60);
if (itm) {
if (!launchObject(-1, itm, m->block, m->pos, m->dir, _items[itm].type))
_items[itm].block = -1;
}
break;
case 12:
launchMagicObject(-1, 0, m->block, m->pos, m->dir);
snd_processEnvironmentalSoundEffect(85, m->block);
break;
case 13:
snd_processEnvironmentalSoundEffect(83, m->block);
_txt->printMessage(_monsterSpecAttStrings[1]);
for (int i = 0; i < 6; i++)
statusAttack(i, 4, _monsterSpecAttStrings[2], 1, 5, 9, 1);
break;
case 17:
d = rollDice(1, 4, -1);
if (d >= 3) {
for (int i = 0; i < 6; i++) {
if (!testCharacter(i, 3))
continue;
_txt->printMessage(_monsterSpecAttStrings[0], -1, _characters[i].name);
inflictCharacterDamage(i, rollDice(2, 8, 1));
}
snd_processEnvironmentalSoundEffect(108, m->block);
} else {
launchMagicObject(-1, _beholderSpellList[d], m->block, m->pos, m->dir);
snd_processEnvironmentalSoundEffect(_beholderSfx[d], m->block);
}
break;
default:
break;
}
} else {
int cw = 0;
if (p->remoteWeaponChangeMode == 1) {
cw = m->curRemoteWeapon++;
if (m->curRemoteWeapon == p->numRemoteWeapons)
m->curRemoteWeapon = 0;
} else if (p->remoteWeaponChangeMode == 2) {
cw = rollDice(1, p->numRemoteWeapons, -1);
}
int s = p->remoteWeapons[cw];
if (s >= 0) {
if (s < 20) {
monsterSpellCast(m, s);
} else if (s == 20) {
if (_flags.platform == Common::kPlatformAmiga)
snd_processEnvironmentalSoundEffect(39, _currentBlock + 1);
else
snd_processEnvironmentalSoundEffect(103, m->block);
_txt->printMessage(_monsterSpecAttStrings[0]);
for (int i = 0; i < 6; i++)
statusAttack(i, 4, _monsterSpecAttStrings[1], 1, 5, 9, 1);
}
} else {
itm = duplicateItem(-s);
if (itm) {
if (!launchObject(-1, itm, m->block, m->pos, m->dir, _items[itm].type))
_items[itm].block = -1;
}
}
}
if (m->numRemoteAttacks != 255)
m->numRemoteAttacks--;
m->stepsTillRemoteAttack = 0;
return true;
}
bool EoBCoreEngine::updateMonsterTryCloseAttack(EoBMonsterInPlay *m, int block) {
if (block == -1)
block = calcNewBlockPosition(m->block, m->dir);
if (block != _currentBlock)
return false;
int r = (m->pos == 4 || (_flags.gameID == GI_EOB2 && _monsterProps[m->type].u30 == 1)) ? 1 : _monsterCloseAttChkTable1[(m->dir << 2) + m->pos];
if (r) {
m->flags ^= 4;
if (!(m->flags & 4))
return true;
bool facing = (m->block == _visibleBlockIndex[13]);
if (facing) {
disableSysTimer(2);
if (m->type == 4)
updateEnvironmentalSfx(_monsterProps[m->type].sound1);
m->curAttackFrame = -2;
_flashShapeTimer = 0;
drawScene(1);
m->curAttackFrame = -1;
if (m->type != 4)
updateEnvironmentalSfx(_monsterProps[m->type].sound1);
_flashShapeTimer = _system->getMillis() + 8 * _tickLength;
drawScene(1);
} else {
updateEnvironmentalSfx(_monsterProps[m->type].sound1);
}
monsterCloseAttack(m);
if (facing) {
m->curAttackFrame = 0;
m->animStep ^= 1;
_sceneUpdateRequired = 1;
enableSysTimer(2);
_flashShapeTimer = _system->getMillis() + 8 * _tickLength;
}
} else {
int b = m->block;
if ((_levelBlockProperties[b].flags & 7) == 1) {
m->pos = 4;
} else {
b = getNextMonsterPos(m, b);
if (b >= 0)
m->pos = b;
}
checkSceneUpdateNeed(m->block);
}
return true;
}
void EoBCoreEngine::walkMonster(EoBMonsterInPlay *m, int destBlock) {
if (++_monsterStepCounter > 10) {
_monsterStepCounter = 0;
_monsterStepMode ^= 1;
}
const int8 *tbl = _monsterStepMode ? _monsterStepTable3 : _monsterStepTable2;
int s = m->dir << 1;
int b = m->block;
int d = getNextMonsterDirection(b, destBlock);
if (d == -1)
return;
if (m->flags & 8) {
// Interestingly, the fear spell in EOB 1 does not expire.
// I don't know whether this is intended or not.
if (_flags.gameID == GI_EOB1) {
d ^= 4;
} else if (m->spellStatusLeft > 0) {
if (--m->spellStatusLeft == 0)
m->flags &= ~8;
else
d ^= 4;
}
}
int d2 = (d - s) & 7;
if (_flags.gameID == GI_EOB1) {
if ((b + _monsterStepTable0[d >> 1] == _currentBlock) && !(d & 1)) {
if (d2 >= 5)
s = m->dir - 1;
else if (d2 != 0)
s = m->dir + 1;
walkMonsterNextStep(m, -1, s & 3);
return;
}
} else if (_flags.gameID == GI_EOB2) {
if (b + _monsterStepTable0[d] == destBlock) {
if (d & 1) {
int e = _monsterStepTable1[((d - 1) << 1) + m->dir];
if (e && (!(_monsterProps[m->type].capsFlags & 0x200) || (rollDice(1, 4) < 4))) {
if (walkMonsterNextStep(m, b + e, -1))
return;
}
} else {
walkMonsterNextStep(m, -1, d >> 1);
return;
}
}
}
if (d2) {
if (d2 >= 5)
s -= (1 + ((d & 1) ^ 1));
else
s += (1 + ((d & 1) ^ 1));
s &= 7;
}
for (int i = 7; i > -1; i--) {
s = (s + tbl[i]) & 7;
uint16 b2 = (s & 1) ? 0 : calcNewBlockPosition(b, s >> 1);
if (!b2)
continue;
if (walkMonsterNextStep(m, b2, s >> 1))
return;
}
}
bool EoBCoreEngine::walkMonsterNextStep(EoBMonsterInPlay *m, int destBlock, int direction) {
EoBMonsterProperty *p = &_monsterProps[m->type];
int obl = m->block;
if (destBlock != m->block && destBlock != -1) {
if (m->flags & 8) {
if (getBlockDistance(destBlock, _currentBlock) < getBlockDistance(m->block, _currentBlock))
return false;
}
if (destBlock == _currentBlock)
return false;
if (direction == -1)
direction = m->dir;
LevelBlockProperty *l = &_levelBlockProperties[destBlock];
uint8 w = l->walls[direction ^ 2];
if (!(_wllWallFlags[w] & 4)) {
if (_flags.gameID == GI_EOB1 || !(p->capsFlags & 0x1000) || _wllShapeMap[w] != -1)
return false;
if (_wllWallFlags[w] & 0x20) {
if (p->capsFlags & 4 && m->type == 1)
l->walls[direction] = l->walls[direction ^ 2] = 72;
else
openDoor(destBlock);
}
if (direction != -1) {
m->dir = direction;
checkSceneUpdateNeed(m->block);
}
return true;
}
if ((l->flags & 7) && destBlock) {
int pos = getNextMonsterPos(m, destBlock);
if (pos == -1)
return false;
m->pos = pos;
}
placeMonster(m, destBlock, direction);
direction = -1;
}
if (direction != -1)
m->dir = direction;
checkSceneUpdateNeed(obl);
if (!_partyResting && p->sound2 > 0)
snd_processEnvironmentalSoundEffect(p->sound2, m->block);
return true;
}
void EoBCoreEngine::updateMonsterFollowPath(EoBMonsterInPlay *m, int turnSteps) {
if (!walkMonsterNextStep(m, calcNewBlockPosition(m->block, m->dir), -1)) {
m->dir = (m->dir + turnSteps) & 3;
walkMonsterNextStep(m, -1, m->dir);
}
}
void EoBCoreEngine::updateMonstersStraying(EoBMonsterInPlay *m, int a) {
if (m->stray >= 0) {
if (m->stray == 0)
updateMonsterFollowPath(m, -a);
int8 d = (m->dir + a) & 3;
uint16 bl = calcNewBlockPosition(m->block, d);
uint8 flg = _wllWallFlags[_levelBlockProperties[bl].walls[_dscBlockMap[d]]] & 4;
if (m->stray == 0) {
if (!flg)
m->stray = -1;
return;
}
if (flg) {
walkMonsterNextStep(m, -1, d);
m->stray = -1;
return;
}
}
if (walkMonsterNextStep(m, calcNewBlockPosition(m->block, m->dir), -1)) {
m->stray = 1;
} else {
walkMonsterNextStep(m, -1, (m->dir - a) & 3);
m->stray = 0;
}
}
void EoBCoreEngine::updateMonstersSpellStatus(EoBMonsterInPlay *m) {
if (m->spellStatusLeft) {
if (!--m->spellStatusLeft)
m->mode = 0;
}
}
void EoBCoreEngine::setBlockMonsterDirection(int block, int dir) {
for (int i = 0; i < 30; i++) {
if (_monsters[i].block != block || _monsters[i].dir == dir)
continue;
_monsters[i].dir = dir;
_monsters[i].directionChanged = 1;
}
}
} // End of namespace Kyra
#endif // ENABLE_EOB