diff options
Diffstat (limited to 'engines/kyra/sprites_eob.cpp')
-rw-r--r-- | engines/kyra/sprites_eob.cpp | 1259 |
1 files changed, 1259 insertions, 0 deletions
diff --git a/engines/kyra/sprites_eob.cpp b/engines/kyra/sprites_eob.cpp new file mode 100644 index 0000000000..5c679f5cb4 --- /dev/null +++ b/engines/kyra/sprites_eob.cpp @@ -0,0 +1,1259 @@ +/* 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/eobcommon.h" +#include "kyra/script_eob.h" +#include "kyra/resource.h" +#include "kyra/timer.h" + +#include "common/system.h" + + +namespace Kyra { + +void EoBCoreEngine::loadMonsterShapes(const char *filename, int monsterIndex, bool hasDecorations, int encodeTableIndex) { + _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) + loadMonsterDecoration(filename, monsterIndex); + + _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; + } +} + +const uint8 *EoBCoreEngine::loadMonsterProperties(const uint8 *data) { + uint8 cmd = *data++; + while (cmd != 0xff) { + EoBMonsterProperty *d = &_monsterProps[cmd]; + d->armorClass = (int8)*data++; + d->hitChance = (int8)*data++; + d->level = (int8)*data++; + d->hpDcTimes = *data++; + d->hpDcPips = *data++; + d->hpDcBase = *data++; + d->attacksPerRound = *data++; + d->dmgDc[0].times = *data++; + d->dmgDc[0].pips = *data++; + d->dmgDc[0].base = (int8)*data++; + d->dmgDc[1].times = *data++; + d->dmgDc[1].pips = *data++; + d->dmgDc[1].base = (int8)*data++; + d->dmgDc[2].times = *data++; + d->dmgDc[2].pips = *data++; + d->dmgDc[2].base = (int8)*data++; + d->immunityFlags = READ_LE_UINT16(data); + data += 2; + d->capsFlags = READ_LE_UINT16(data); + data += 2; + d->typeFlags = READ_LE_UINT16(data); + data += 2; + d->experience = READ_LE_UINT16(data); + data += 2; + + d->u30 = *data++; + d->sound1 = (int8)*data++; + d->sound2 = (int8)*data++; + d->numRemoteAttacks = *data++; + + if (*data++ != 0xff) { + d->remoteWeaponChangeMode = *data++; + d->numRemoteWeapons = *data++; + + for (int i = 0; i < d->numRemoteWeapons; i++) { + d->remoteWeapons[i] = (int8)*data; + data += 2; + } + } + + d->tuResist = (int8)*data++; + d->dmgModifierEvade = *data++; + + for (int i = 0; i < 3; i++) + d->decorations[i] = *data++; + + cmd = *data++; + } + + return data; +} + +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 ((_flags.gameID == GI_EOB1) && (m->type == 21)) { + _playFinale = true; + _runFlag = false; + } + + if (m->mode == 8) + updateAttackingMonsterFlags(); + } +} + +bool EoBCoreEngine::killMonsterExtra(EoBMonsterInPlay *) { + return true; +} + +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->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) + 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 = _screen->scaleShape(_dscItemShapeMap[itm->icon] < _numLargeItemShapes ? _largeItemShapes[_dscItemShapeMap[itm->icon]] : (_dscItemShapeMap[itm->icon] < 15 ? 0 : _smallItemShapes[_dscItemShapeMap[itm->icon] - 15]), scaleSteps); + 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->setShapeFadeMode(1, false); + } + } + + 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->setShapeFadeMode(1, false); + 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) + continue; + 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->setShapeFadeMode(1, false); + } +} + +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->setShapeFadeMode(1, false); + continue; + } + + const uint8 *shp = 0; + bool rstFade = 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) { + shp = shpIx < _numLargeItemShapes ? _largeItemShapes[shpIx] : (shpIx < 15 ? 0 : _smallItemShapes[shpIx - 15]); + flipped = fo->direction == ((_currentDirection + 1) & 3) ? 1 : 0; + } else { + shp = (_flightObjShpMap[shpIx] + dirOffs) < _numThrownItemShapes ? _thrownItemShapes[_flightObjShpMap[shpIx] + dirOffs] : _spellShapes[_flightObjShpMap[shpIx - _numThrownItemShapes] + dirOffs]; + flipped = _flightObjFlipIndex[(fo->direction << 2) + (fo->curPos & 3)]; + } + + } else { + rstFade = true; + shp = (fo->objectType < _numThrownItemShapes) ? _thrownItemShapes[fo->objectType] : _spellShapes[fo->objectType - _numThrownItemShapes]; + flipped = _flightObjFlipIndex[(fo->direction << 2) + (fo->curPos & 3)]; + + if (fo->flags & 0x40) { + x = _dscShapeCoords[(index * 5 + 4) << 1] + 88; + y = 44; + } + } + + shp = _screen->scaleShape(shp, sclValue); + + if (rstFade) { + _screen->setShapeFadeMode(1, false); + rstFade = false; + } + + x -= (shp[2] << 2); + y -= (y == 44 ? (shp[1] >> 1) : shp[1]); + + drawBlockObject(flipped, 2, shp, x, y, 5); + _screen->setShapeFadeMode(1, false); + } +} + +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 = 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) { + 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->f_9 >= 0) { + if (m->f_9 == 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->f_9 == 0) { + if (!flg) + m->f_9 = -1; + return; + } + + if (flg) { + walkMonsterNextStep(m, -1, d); + m->f_9 = -1; + return; + } + } + + if (walkMonsterNextStep(m, calcNewBlockPosition(m->block, m->dir), -1)) { + m->f_9 = 1; + } else { + walkMonsterNextStep(m, -1, (m->dir - a) & 3); + m->f_9 = 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 |