diff options
Diffstat (limited to 'engines/kyra/engine/sprites_lol.cpp')
-rw-r--r-- | engines/kyra/engine/sprites_lol.cpp | 1558 |
1 files changed, 1558 insertions, 0 deletions
diff --git a/engines/kyra/engine/sprites_lol.cpp b/engines/kyra/engine/sprites_lol.cpp new file mode 100644 index 0000000000..910447c45a --- /dev/null +++ b/engines/kyra/engine/sprites_lol.cpp @@ -0,0 +1,1558 @@ +/* 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_LOL + +#include "kyra/engine/lol.h" +#include "kyra/graphics/screen_lol.h" + +namespace Kyra { + +void LoLEngine::loadMonsterShapes(const char *file, int monsterIndex, int animType) { + releaseMonsterShapes(monsterIndex); + _screen->loadBitmap(file, 3, 3, 0); + + const uint8 *p = _screen->getCPagePtr(2); + const uint8 *ts[16]; + + for (int i = 0; i < 16; i++) { + ts[i] = _screen->getPtrToShape(p, i); + + bool replaced = false; + int pos = monsterIndex << 4; + + for (int ii = 0; ii < i; ii++) { + if (ts[i] != ts[ii]) + continue; + + _monsterShapes[pos + i] = _monsterShapes[pos + ii]; + replaced = true; + break; + } + + if (!replaced) + _monsterShapes[pos + i] = _screen->makeShapeCopy(p, i); + + int size = _screen->getShapePaletteSize(_monsterShapes[pos + i]) << 3; + _monsterPalettes[pos + i] = new uint8[size]; + memset(_monsterPalettes[pos + i], 0, size); + } + + for (int i = 0; i < 4; i++) { + for (int ii = 0; ii < 16; ii++) { + uint8 **of = &_monsterDecorationShapes[monsterIndex * 192 + i * 48 + ii * 3]; + int s = (i << 4) + ii + 17; + of[0] = _screen->makeShapeCopy(p, s); + of[1] = _screen->makeShapeCopy(p, s + 1); + of[2] = _screen->makeShapeCopy(p, s + 2); + } + } + _monsterAnimType[monsterIndex] = animType & 0xFF; + + uint8 *palShape = _screen->makeShapeCopy(p, 16); + + _screen->clearPage(3); + _screen->drawShape(2, palShape, 0, 0, 0, 0); + + uint8 *tmpPal1 = new uint8[64]; + uint8 *tmpPal2 = new uint8[256]; + uint16 *tmpPal3 = new uint16[256]; + memset(tmpPal1, 0, 64); + + for (int i = 0; i < 64; i++) { + tmpPal1[i] = *p; + p += 320; + } + + p = _screen->getCPagePtr(2); + + for (int i = 0; i < 16; i++) { + int pos = (monsterIndex << 4) + i; + uint16 sz = MIN(_screen->getShapeSize(_monsterShapes[pos]) - 10, 256); + memset(tmpPal2, 0, 256); + memcpy(tmpPal2, _monsterShapes[pos] + 10, sz); + memset(tmpPal3, 0xFF, 256 * sizeof(uint16)); + uint8 numCol = *tmpPal2; + + for (int ii = 0; ii < numCol; ii++) { + uint8 *cl = (uint8 *)memchr(tmpPal1, tmpPal2[1 + ii], 64); + if (!cl) + continue; + tmpPal3[ii] = (uint16)(cl - tmpPal1); + } + + for (int ii = 0; ii < 8; ii++) { + memset(tmpPal2, 0, 256); + memcpy(tmpPal2, _monsterShapes[pos] + 10, sz); + for (int iii = 0; iii < numCol; iii++) { + if (tmpPal3[iii] == 0xFFFF) + continue; + if (p[tmpPal3[iii] * 320 + ii + 1]) + tmpPal2[1 + iii] = p[tmpPal3[iii] * 320 + ii + 1]; + } + memcpy(_monsterPalettes[pos] + ii * numCol, &tmpPal2[1], numCol); + } + } + + delete[] tmpPal1; + delete[] tmpPal2; + delete[] tmpPal3; + delete[] palShape; +} + +void LoLEngine::releaseMonsterShapes(int monsterIndex) { + for (int i = 0; i < 16; i++) { + int pos = (monsterIndex << 4) + i; + int pos2 = (monsterIndex << 4) + 16; + if (_monsterShapes[pos]) { + uint8 *t = _monsterShapes[pos]; + delete[] _monsterShapes[pos]; + for (int ii = pos; ii < pos2; ii++) { + if (_monsterShapes[ii] == t) + _monsterShapes[ii] = 0; + } + } + + if (_monsterPalettes[pos]) { + delete[] _monsterPalettes[pos]; + _monsterPalettes[pos] = 0; + } + } + + for (int i = 0; i < 192; i++) { + int pos = (monsterIndex * 192) + i; + if (_monsterDecorationShapes[pos]) { + delete[] _monsterDecorationShapes[pos]; + _monsterDecorationShapes[pos] = 0; + } + } +} + +int LoLEngine::deleteMonstersFromBlock(int block) { + int i = _levelBlockProperties[block].assignedObjects; + int cnt = 0; + uint16 next = 0; + + while (i) { + next = findObject(i)->nextAssignedObject; + if (!(i & 0x8000)) { + i = next; + continue; + } + + LoLMonster *m = &_monsters[i & 0x7FFF]; + + cnt++; + setMonsterMode(m, 14); + + checkSceneUpdateNeed(m->block); + + placeMonster(m, 0, 0); + + i = next; + } + return cnt; +} + +void LoLEngine::setMonsterMode(LoLMonster *monster, int mode) { + if (monster->mode == 13 && mode != 14) + return; + + if (mode == 7) { + monster->destX = _partyPosX; + monster->destY = _partyPosY; + } + + if (monster->mode == 1 && mode == 7) { + for (int i = 0; i < 30; i++) { + if (monster->mode != 1) + continue; + monster->mode = mode; + monster->fightCurTick = 0; + monster->destX = _partyPosX; + monster->destY = _partyPosY; + setMonsterDirection(monster, calcMonsterDirection(monster->x, monster->y, monster->destX, monster->destY)); + } + } else { + monster->mode = mode; + monster->fightCurTick = 0; + if (mode == 14) + monster->hitPoints = 0; + if (mode == 13 && (monster->flags & 0x20)) { + monster->mode = 0; + monsterDropItems(monster); + if (_currentLevel != 29) + setMonsterMode(monster, 14); + runLevelScriptCustom(0x404, -1, monster->id, monster->id, 0, 0); + checkSceneUpdateNeed(monster->block); + if (monster->mode == 14) + placeMonster(monster, 0, 0); + } + } +} + +bool LoLEngine::updateMonsterAdjustBlocks(LoLMonster *monster) { + static const uint8 dims[] = { 0, 13, 9, 3 }; + if (monster->properties->flags & 8) + return true; + + uint16 x1 = (monster->x & 0xFF00) | 0x80; + uint16 y1 = (monster->y & 0xFF00) | 0x80; + int x2 = _partyPosX; + int y2 = _partyPosY; + + uint16 dir = 0; + if (monster->properties->flags & 1) { + dir = monster->direction >> 1; + } else { + dir = calcMonsterDirection(x1, y1, x2, y2); + if ((monster->properties->flags & 2) && (dir == (monster->direction ^ 4))) + return false; + dir >>= 1; + } + + calcSpriteRelPosition(x1, y1, x2, y2, dir); + x2 >>= 8; + y2 >>= 8; + + if (y2 < 0 || y2 > 3) + return false; + + int t = (x2 < 0) ? -x2 : x2; + if (t > y2) + return false; + + for (int i = 0; i < 18; i++) + _visibleBlocks[i] = &_levelBlockProperties[(monster->block + _dscBlockIndex[dir + i]) & 0x3FF]; + + int16 fx1 = 0; + int16 fx2 = 0; + setLevelShapesDim(x2 + dims[y2], fx1, fx2, 13); + + return fx1 < fx2; +} + +void LoLEngine::placeMonster(LoLMonster *monster, uint16 x, uint16 y) { + bool cont = true; + int t = monster->block; + if (monster->block) { + removeAssignedObjectFromBlock(&_levelBlockProperties[t], ((uint16)monster->id) | 0x8000); + _levelBlockProperties[t].direction = 5; + checkSceneUpdateNeed(t); + } else { + cont = false; + } + + monster->block = calcBlockIndex(x, y); + + if (monster->x != x || monster->y != y) { + monster->x = x; + monster->y = y; + monster->currentSubFrame = (monster->currentSubFrame + 1) & 3; + } + + if (monster->block == 0) + return; + + assignObjectToBlock(&_levelBlockProperties[monster->block].assignedObjects, ((uint16)monster->id) | 0x8000); + _levelBlockProperties[monster->block].direction = 5; + checkSceneUpdateNeed(monster->block); + + // WORKAROUND: Some monsters in the white tower have sound id's of 0xFF. This is definitely a bug, since the + // last valid track number is 249 and there is no specific handling for 0xFF. Nonetheless this wouldn't + // cause problems in the original code, because it just so happens that the invalid memory address points + // to an entry in _ingameGMSoundIndex which just so happens to have a value of -1 + if (monster->properties->sounds[0] == 0 || monster->properties->sounds[0] == 255 || cont == false) + return; + + if ((!(monster->properties->flags & 0x100) || ((monster->currentSubFrame & 1) == 0)) && monster->block == t) + return; + + if (monster->block != t) + runLevelScriptCustom(monster->block, 0x800, -1, monster->id, 0, 0); + + if (_updateFlags & 1) + return; + + snd_processEnvironmentalSoundEffect(monster->properties->sounds[0], monster->block); +} + +int LoLEngine::calcMonsterDirection(uint16 x1, uint16 y1, uint16 x2, uint16 y2) { + int16 r = 0; + + int16 t1 = y1 - y2; + if (t1 < 0) { + r++; + t1 = -t1; + } + + r <<= 1; + + int16 t2 = x2 - x1; + if (t2 < 0) { + r++; + t2 = -t2; + } + + uint8 f = (t1 > t2) ? 1 : 0; + + if (t2 >= t1) + SWAP(t1, t2); + + r = (r << 1) | f; + + t1 = (t1 + 1) >> 1; + + f = (t1 > t2) ? 1 : 0; + r = (r << 1) | f; + + static const uint8 retVal[] = { 1, 2, 1, 0, 7, 6, 7, 0, 3, 2, 3, 4, 5, 6, 5, 4}; + return retVal[r]; +} + +void LoLEngine::setMonsterDirection(LoLMonster *monster, int dir) { + monster->direction = dir; + + if (!(dir & 1) || ((monster->direction - (monster->facing << 1)) >= 2)) + monster->facing = monster->direction >> 1; + + checkSceneUpdateNeed(monster->block); +} + +void LoLEngine::monsterDropItems(LoLMonster *monster) { + uint16 a = monster->assignedItems; + while (a) { + uint16 b = a; + a = _itemsInPlay[a].nextAssignedObject; + setItemPosition(b, monster->x, monster->y, 0, 1); + } +} + +int LoLEngine::checkBlockBeforeObjectPlacement(uint16 x, uint16 y, uint16 objectWidth, uint16 testFlag, uint16 wallFlag) { + _objectLastDirection = 0; + uint16 x2 = 0; + uint16 y2 = 0; + int xOffs = 0; + int yOffs = 0; + int flag = 0; + + int r = testBlockPassability(calcBlockIndex(x, y), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x, y, testFlag); + if (r) + return 4; + + if (x & 0x80) { + if (((x & 0xFF) + objectWidth) & 0xFF00) { + xOffs = 1; + _objectLastDirection = 2; + x2 = x + objectWidth; + + r = testBlockPassability(calcBlockIndex(x2, y), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x + xOffs, y, testFlag); + if (r) + return 4; + + flag = 1; + } + } else { + if (((x & 0xFF) - objectWidth) & 0xFF00) { + xOffs = -1; + _objectLastDirection = 6; + x2 = x - objectWidth; + + r = testBlockPassability(calcBlockIndex(x2, y), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x + xOffs, y, testFlag); + if (r) + return 4; + + flag = 1; + } + } + + if (y & 0x80) { + if (((y & 0xFF) + objectWidth) & 0xFF00) { + yOffs = 1; + _objectLastDirection = 4; + y2 = y + objectWidth; + + r = testBlockPassability(calcBlockIndex(x, y2), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x, y + yOffs, testFlag); + if (r) + return 4; + flag &= 1; + } else { + flag = 0; + } + } else { + if (((y & 0xFF) - objectWidth) & 0xFF00) { + yOffs = -1; + _objectLastDirection = 0; + y2 = y - objectWidth; + + r = testBlockPassability(calcBlockIndex(x, y2), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x, y + yOffs, testFlag); + if (r) + return 4; + flag &= 1; + } else { + flag = 0; + } + } + + if (!flag) + return 0; + + r = testBlockPassability(calcBlockIndex(x2, y2), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x + xOffs, y + yOffs, testFlag); + if (r) + return 4; + + return 0; +} + +int LoLEngine::testBlockPassability(int block, int x, int y, int objectWidth, int testFlag, int wallFlag) { + if (block == _currentBlock) + testFlag &= 0xFFFE; + + if (testFlag & 1) { + _monsterCurBlock = block; + if (testWallFlag(block, -1, wallFlag)) + return 1; + _monsterCurBlock = 0; + } + + if (!(testFlag & 2)) + return 0; + + uint16 obj = _levelBlockProperties[block].assignedObjects; + while (obj & 0x8000) { + LoLMonster *monster = &_monsters[obj & 0x7FFF]; + + if (monster->mode < 13) { + int r = checkDrawObjectSpace(x, y, monster->x, monster->y); + if ((objectWidth + monster->properties->maxWidth) > r) + return 2; + } + + obj = findObject(obj)->nextAssignedObject; + } + + return 0; +} + +int LoLEngine::calcMonsterSkillLevel(int id, int a) { + const uint16 *c = getCharacterOrMonsterStats(id); + int r = (a << 8) / c[4]; + + if (id & 0x8000) { + r = (r * _monsterModifiers2[3 + _monsterDifficulty]) >> 8; + } else { + if (_characters[id].skillLevels[1] > 7) + r = (r - (r >> 1)); + else if (_characters[id].skillLevels[1] > 3 && _characters[id].skillLevels[1] <= 7) + r = (r - (r >> 2)); + } + + return r; +} + +int LoLEngine::checkBlockOccupiedByParty(int x, int y, int testFlag) { + if ((testFlag & 4) && (_currentBlock == calcBlockIndex(x, y))) + return 1; + + return 0; +} + +void LoLEngine::drawBlockObjects(int blockArrayIndex) { + LevelBlockProperty *l = _visibleBlocks[blockArrayIndex]; + uint16 s = l->assignedObjects; + + if (l->direction != _currentDirection) { + l->drawObjects = 0; + l->direction = _currentDirection; + + while (s) { + reassignDrawObjects(_currentDirection, s, l, true); + s = findObject(s)->nextAssignedObject; + } + } + + s = l->drawObjects; + while (s) { + if (s & 0x8000) { + s &= 0x7FFF; + if (blockArrayIndex < 15) + drawMonster(s); + s = _monsters[s].nextDrawObject; + } else { + LoLItem *i = &_itemsInPlay[s]; + int fx = _sceneItemOffs[s & 7] << 1; + int fy = _sceneItemOffs[(s >> 1) & 7] + 5; + + if (i->flyingHeight >= 2 && blockArrayIndex >= 15) { + s = i->nextDrawObject; + continue; + } + + uint8 *shp = 0; + uint16 flg = 0; + + if (i->flyingHeight >= 2) + fy -= ((i->flyingHeight - 1) * 6); + + if ((_itemProperties[i->itemPropertyIndex].flags & 0x1000) && !(i->shpCurFrame_flg & 0xC000)) { + int shpIndex = _itemProperties[i->itemPropertyIndex].flags & 0x800 ? 7 : _itemProperties[i->itemPropertyIndex].shpIndex; + int ii = 0; + for (; ii < 8; ii++) { + if (!_flyingObjects[ii].enable) + continue; + + if (_flyingObjects[ii].item == s) + break; + } + + if (_flyingItemShapes[shpIndex].flipFlags && ((i->x ^ i->y) & 0x20)) + flg |= 0x20; + + flg |= _flyingItemShapes[shpIndex].drawFlags; + + if (ii != 8) { + switch (_currentDirection - (_flyingObjects[ii].direction >> 1) + 3) { + case 1: + case 5: + shpIndex = _flyingItemShapes[shpIndex].shapeFront; + break; + case 3: + shpIndex = _flyingItemShapes[shpIndex].shapeBack; + break; + case 2: + case 6: + flg |= 0x10; + // fall through + case 0: + case 4: + shpIndex = _flyingItemShapes[shpIndex].shapeLeft; + break; + default: + break; + } + + shp = _thrownShapes[shpIndex]; + } + + if (shp) + fy += (shp[2] >> 2); + + } else { + shp = (_itemProperties[i->itemPropertyIndex].flags & 0x40) ? _gameShapes[_itemProperties[i->itemPropertyIndex].shpIndex] : + _itemShapes[_gameShapeMap[_itemProperties[i->itemPropertyIndex].shpIndex << 1]]; + } + + if (shp) + drawItemOrMonster(shp, 0, i->x, i->y, fx, fy, flg, -1, false); + s = i->nextDrawObject; + } + } +} + +void LoLEngine::drawMonster(uint16 id) { + LoLMonster *m = &_monsters[id]; + int16 flg = _monsterDirFlags[(_currentDirection << 2) + m->facing]; + int curFrm = getMonsterCurFrame(m, flg & 0xFFEF); + uint8 *shp = 0; + + if (curFrm == -1) { + shp = _monsterShapes[m->properties->shapeIndex << 4]; + calcDrawingLayerParameters(m->x + _monsterShiftOffs[m->shiftStep << 1], m->y + _monsterShiftOffs[(m->shiftStep << 1) + 1], _shpDmX, _shpDmY, _dmScaleW, _dmScaleH, shp, 0); + } else { + int d = m->flags & 7; + bool flip = m->properties->flags & 0x200 ? true : false; + flg &= 0x10; + shp = _monsterShapes[(m->properties->shapeIndex << 4) + curFrm]; + + if (m->properties->flags & 0x800) + flg |= 0x20; + + uint8 *monsterPalette = d ? _monsterPalettes[(m->properties->shapeIndex << 4) + (curFrm & 0x0F)] + (shp[10] * (d - 1)) : 0; + uint8 *brightnessOverlay = drawItemOrMonster(shp, monsterPalette, m->x + _monsterShiftOffs[m->shiftStep << 1], m->y + _monsterShiftOffs[(m->shiftStep << 1) + 1], 0, 0, flg | 1, -1, flip); + + for (int i = 0; i < 4; i++) { + int v = m->equipmentShapes[i] - 1; + if (v == -1) + break; + + uint8 *shp2 = _monsterDecorationShapes[m->properties->shapeIndex * 192 + v * 48 + curFrm * 3]; + if (!shp2) + continue; + + drawDoorOrMonsterEquipment(shp2, 0, _shpDmX, _shpDmY, flg | 1, brightnessOverlay); + } + } + + if (!m->damageReceived) + return; + + int dW = _screen->getShapeScaledWidth(shp, _dmScaleW) >> 1; + int dH = _screen->getShapeScaledHeight(shp, _dmScaleH) >> 1; + + int bloodAmount = (m->mode == 13) ? (m->fightCurTick << 1) : (m->properties->hitPoints / (m->damageReceived & 0x7FFF)); + + shp = _gameShapes[6]; + + int bloodType = m->properties->flags & 0xC000; + if (bloodType == 0x4000) + bloodType = _flags.use16ColorMode ? 0xBB : 63; + else if (bloodType == 0x8000) + bloodType = _flags.use16ColorMode ? 0x55 : 15; + else if (bloodType == 0xC000) + bloodType = _flags.use16ColorMode ? 0x33 : 74; + else + bloodType = 0; + + uint8 *tbl = new uint8[256]; + if (bloodType) { + for (int i = 0; i < 256; i++) { + tbl[i] = i; + if (i < 2 || i > 7) + continue; + tbl[i] += bloodType; + } + } + + dW += m->hitOffsX; + dH += m->hitOffsY; + + bloodAmount = CLIP(bloodAmount, 1, 4); + + int sW = _dmScaleW / bloodAmount; + int sH = _dmScaleH / bloodAmount; + + _screen->drawShape(_sceneDrawPage1, shp, _shpDmX + dW, _shpDmY + dH, 13, 0x124, tbl, bloodType ? 1 : 0, sW, sH); + + delete[] tbl; +} + +int LoLEngine::getMonsterCurFrame(LoLMonster *m, uint16 dirFlags) { + int tmp = 0; + switch (_monsterAnimType[m->properties->shapeIndex]) { + case 0: + // default + if (dirFlags) { + return (m->mode == 13) ? -1 : (dirFlags + m->currentSubFrame); + } else { + if (m->damageReceived) + return 12; + + switch (m->mode - 5) { + case 0: + return (m->properties->flags & 4) ? 13 : 0; + case 3: + return (m->fightCurTick + 13); + case 6: + return 14; + case 8: + return -1; + default: + return m->currentSubFrame; + } + } + break; + case 1: + // monsters whose outward appearance reflects the damage they have taken + tmp = m->properties->hitPoints; + if (_flags.isTalkie) + tmp = (tmp * _monsterModifiers1[_monsterDifficulty]) >> 8; + if (m->hitPoints > (tmp >> 1)) + tmp = 0; + else if (m->hitPoints > (tmp >> 2)) + tmp = 4; + else + tmp = 8; + + switch (m->mode) { + case 8: + return (m->fightCurTick + tmp); + case 11: + return 12; + case 13: + return (m->fightCurTick + 12); + default: + return tmp; + } + + break; + case 2: + return (m->fightCurTick >= 13) ? 13 : m->fightCurTick; + case 3: + switch (m->mode) { + case 5: + return m->damageReceived ? 5 : 6; + case 8: + return (m->fightCurTick + 6); + case 11: + return 5; + default: + return m->damageReceived ? 5 : m->currentSubFrame; + } + + break; + default: + break; + } + + return 0; +} + +void LoLEngine::reassignDrawObjects(uint16 direction, uint16 itemIndex, LevelBlockProperty *l, bool flag) { + if (l->direction != direction) { + l->direction = 5; + return; + } + + LoLObject *newObject = findObject(itemIndex); + int r = calcObjectPosition(newObject, direction); + uint16 *b = &l->drawObjects; + LoLObject *lastObject = 0; + + while (*b) { + lastObject = findObject(*b); + + if (flag) { + if (calcObjectPosition(lastObject, direction) >= r) + break; + } else { + if (calcObjectPosition(lastObject, direction) > r) + break; + } + + b = &lastObject->nextDrawObject; + } + + newObject->nextDrawObject = *b; + *b = itemIndex; +} + +void LoLEngine::redrawSceneItem() { + assignVisibleBlocks(_currentBlock, _currentDirection); + _screen->fillRect(112, 0, 287, 119, 0); + + static const uint8 sceneClickTileIndex[] = { 13, 16}; + + int16 x1 = 0; + int16 x2 = 0; + + for (int i = 0; i < 2; i++) { + uint8 tile = sceneClickTileIndex[i]; + setLevelShapesDim(tile, x1, x2, 13); + uint16 s = _visibleBlocks[tile]->drawObjects; + + int t = (i << 7) + 1; + while (s) { + if (s & 0x8000) { + s = _monsters[s & 0x7FFF].nextDrawObject; + } else { + LoLItem *item = &_itemsInPlay[s]; + + if (item->shpCurFrame_flg & 0x4000) { + if (checkDrawObjectSpace(item->x, item->y, _partyPosX, _partyPosY) < 320) { + int fx = _sceneItemOffs[s & 7] << 1; + int fy = _sceneItemOffs[(s >> 1) & 7] + 5; + if (item->flyingHeight > 1) + fy -= ((item->flyingHeight - 1) * 6); + + uint8 *shp = (_itemProperties[item->itemPropertyIndex].flags & 0x40) ? _gameShapes[_itemProperties[item->itemPropertyIndex].shpIndex] : + _itemShapes[_gameShapeMap[_itemProperties[item->itemPropertyIndex].shpIndex << 1]]; + + drawItemOrMonster(shp, 0, item->x, item->y, fx, fy, 0, t, 0); + _screen->updateScreen(); + } + } + + s = item->nextDrawObject; + t++; + } + } + } +} + +void LoLEngine::calcSpriteRelPosition(uint16 x1, uint16 y1, int &x2, int &y2, uint16 direction) { + int a = x2 - x1; + int b = y1 - y2; + + if (direction) { + if (direction != 2) + SWAP(a, b); + if (direction != 3) { + a = -a; + if (direction != 1) + b = -b; + } else { + b = -b; + } + } + + x2 = a; + y2 = b; +} + +void LoLEngine::drawDoor(uint8 *shape, uint8 *doorPalette, int index, int unk2, int w, int h, int flags) { + if (!shape) + return; + + uint8 c = _dscDoorY2[(_currentDirection << 5) + unk2]; + int r = (c / 5) + 5 * _dscDimMap[index]; + uint16 d = _dscShapeOvlIndex[r]; + uint16 t = (index << 5) + c; + + _shpDmY = _dscDoorMonsterY[t] + 120; + + if (flags & 1) { + // TODO / UNUSED + flags |= 1; + } + + int u = 0; + + if (flags & 2) { + uint8 dimW = _dscDimMap[index]; + _dmScaleW = _dscDoorMonsterScaleTable[dimW << 1]; + _dmScaleH = _dscDoorMonsterScaleTable[(dimW << 1) + 1]; + u = _dscDoor4[dimW]; + } + + d += 2; + + if (!_dmScaleW || !_dmScaleH) + return; + + int s = _screen->getShapeScaledHeight(shape, _dmScaleH) >> 1; + + if (w) + w = (w * _dmScaleW) >> 8; + + if (h) + h = (h * _dmScaleH) >> 8; + + _shpDmX = _dscDoorMonsterX[t] + w + 200; + _shpDmY = _shpDmY + 4 - s + h - u; + + if (d > 7) + d = 7; + + if (_flags.use16ColorMode) { + uint8 bb = _blockBrightness >> 4; + if (d > bb) + d -= bb; + else + d = 0; + } + + uint8 *brightnessOverlay = _screen->getLevelOverlay(d); + int doorScaledWitdh = _screen->getShapeScaledWidth(shape, _dmScaleW); + + _shpDmX -= (doorScaledWitdh >> 1); + _shpDmY -= s; + + drawDoorOrMonsterEquipment(shape, doorPalette, _shpDmX, _shpDmY, flags, brightnessOverlay); +} + +void LoLEngine::drawDoorOrMonsterEquipment(uint8 *shape, uint8 *objectPalette, int x, int y, int flags, const uint8 *brightnessOverlay) { + int flg = 0; + + if (flags & 0x10) + flg |= 1; + + if (flags & 0x20) + flg |= 0x1000; + + if (flags & 0x40) + flg |= 2; + + if (flg & 0x1000) { + if (objectPalette) + _screen->drawShape(_sceneDrawPage1, shape, x, y, 13, flg | 0x9104, objectPalette, brightnessOverlay, 1, _transparencyTable1, _transparencyTable2, _dmScaleW, _dmScaleH); + else + _screen->drawShape(_sceneDrawPage1, shape, x, y, 13, flg | 0x1104, brightnessOverlay, 1, _transparencyTable1, _transparencyTable2, _dmScaleW, _dmScaleH); + } else { + if (objectPalette) + _screen->drawShape(_sceneDrawPage1, shape, x, y, 13, flg | 0x8104, objectPalette, brightnessOverlay, 1, _dmScaleW, _dmScaleH); + else + _screen->drawShape(_sceneDrawPage1, shape, x, y, 13, flg | 0x104, brightnessOverlay, 1, _dmScaleW, _dmScaleH); + } +} + +uint8 *LoLEngine::drawItemOrMonster(uint8 *shape, uint8 *monsterPalette, int x, int y, int fineX, int fineY, int flags, int tblValue, bool vflip) { + uint8 *ovl2 = 0; + uint8 *brightnessOverlay = 0; + uint8 tmpOvl[16]; + + if (flags & 0x80) { + flags &= 0xFF7F; + ovl2 = monsterPalette; + monsterPalette = 0; + } else { + ovl2 = _screen->getLevelOverlay(_flags.use16ColorMode ? 5 : 4); + } + + int r = calcDrawingLayerParameters(x, y, _shpDmX, _shpDmY, _dmScaleW, _dmScaleH, shape, vflip); + + if (tblValue == -1) { + r = 7 - ((r / 3) - 1); + r = CLIP(r, 0, 7); + if (_flags.use16ColorMode) { + uint8 bb = _blockBrightness >> 4; + if (r > bb) + r -= bb; + else + r = 0; + } + brightnessOverlay = _screen->getLevelOverlay(r); + } else { + memset(tmpOvl + 1, tblValue, 15); + tmpOvl[0] = 0; + monsterPalette = tmpOvl; + brightnessOverlay = _screen->getLevelOverlay(7); + } + + int flg = flags & 0x10 ? 1 : 0; + if (flags & 0x20) + flg |= 0x1000; + if (flags & 0x40) + flg |= 2; + + if (_flags.use16ColorMode) { + if (_currentLevel != 22) + flg &= 0xDFFF; + + } else { + if (_currentLevel == 22) { + if (brightnessOverlay) + brightnessOverlay[255] = 0; + } else { + flg |= 0x2000; + } + } + + _shpDmX += ((_dmScaleW * fineX) >> 8); + _shpDmY += ((_dmScaleH * fineY) >> 8); + + int dH = _screen->getShapeScaledHeight(shape, _dmScaleH) >> 1; + + if (flg & 0x1000) { + if (monsterPalette) + _screen->drawShape(_sceneDrawPage1, shape, _shpDmX, _shpDmY, 13, flg | 0x8124, monsterPalette, brightnessOverlay, 0, _transparencyTable1, _transparencyTable2, _dmScaleW, _dmScaleH, ovl2); + else + _screen->drawShape(_sceneDrawPage1, shape, _shpDmX, _shpDmY, 13, flg | 0x124, brightnessOverlay, 0, _transparencyTable1, _transparencyTable2, _dmScaleW, _dmScaleH, ovl2); + } else { + if (monsterPalette) + _screen->drawShape(_sceneDrawPage1, shape, _shpDmX, _shpDmY, 13, flg | 0x8124, monsterPalette, brightnessOverlay, 1, _dmScaleW, _dmScaleH, ovl2); + else + _screen->drawShape(_sceneDrawPage1, shape, _shpDmX, _shpDmY, 13, flg | 0x124, brightnessOverlay, 1, _dmScaleW, _dmScaleH, ovl2); + } + + _shpDmX -= (_screen->getShapeScaledWidth(shape, _dmScaleW) >> 1); + _shpDmY -= dH; + + return brightnessOverlay; +} + +int LoLEngine::calcDrawingLayerParameters(int x1, int y1, int &x2, int &y2, uint16 &w, uint16 &h, uint8 *shape, int vflip) { + calcSpriteRelPosition(_partyPosX, _partyPosY, x1, y1, _currentDirection); + + if (y1 < 0) { + w = h = x2 = y2 = 0; + return 0; + } + + int l = y1 >> 5; + y2 = _monsterScaleY[l]; + x2 = ((_monsterScaleX[l] * x1) >> 8) + 200; + w = h = (_shpDmY > 120) ? 0x100 : _monsterScaleWH[_shpDmY - 56]; + + if (vflip) + // objects aligned to the ceiling (like the "lobsters" in the mines) + y2 = ((120 - y2) >> 1) + (_screen->getShapeScaledHeight(shape, _dmScaleH) >> 1); + else + y2 -= (_screen->getShapeScaledHeight(shape, _dmScaleH) >> 1); + + return l; +} + +void LoLEngine::updateMonster(LoLMonster *monster) { + static const uint8 flags[] = { 1, 0, 1, 3, 3, 0, 0, 3, 4, 1, 0, 0, 4, 0, 0 }; + if (monster->mode > 14) + return; + + int f = flags[monster->mode]; + if ((monster->speedTick++ < monster->properties->speedTotalWaitTicks) && (!(f & 4))) + return; + + monster->speedTick = 0; + + if (monster->properties->flags & 0x40) { + monster->hitPoints += rollDice(1, 8); + if (monster->hitPoints > monster->properties->hitPoints) + monster->hitPoints = monster->properties->hitPoints; + } + + if (monster->flags & 8) { + monster->destX = _partyPosX; + monster->destY = _partyPosY; + } + + if (f & 2) { + if (updateMonsterAdjustBlocks(monster)) { + setMonsterMode(monster, 7); + f &= 6; + } + } + + if ((f & 1) && (monster->flags & 0x10)) + setMonsterMode(monster, 7); + + if ((monster->mode != 11) && (monster->mode != 14)) { + if (!(_rnd.getRandomNumber(255) & 3)) { + monster->shiftStep = (monster->shiftStep + 1) & 0x0F; + checkSceneUpdateNeed(monster->block); + } + } + + switch (monster->mode) { + case 0: + case 1: + // friendly mode + if (monster->flags & 0x10) { + for (int i = 0; i < 30; i++) { + if (_monsters[i].mode == 1) + setMonsterMode(&_monsters[i], 7); + } + } else if (monster->mode == 1) { + moveMonster(monster); + } + break; + + case 2: + moveMonster(monster); + break; + + case 3: + if (updateMonsterAdjustBlocks(monster)) + setMonsterMode(monster, 7); + for (int i = 0; i < 4; i++) { + if (calcNewBlockPosition(monster->block, i) == _currentBlock) + setMonsterMode(monster, 7); + } + break; + + case 4: + // straying around not tracing the party + moveStrayingMonster(monster); + break; + + case 5: + // second recovery phase after delivering an attack + // monsters will rearrange positions in this phase so as to allow a maximum + // number of monsters possible attacking at the same time + _partyAwake = true; + monster->fightCurTick--; + if ((monster->fightCurTick <= 0) || (checkDrawObjectSpace(_partyPosX, _partyPosY, monster->x, monster->y) > 256) || (monster->flags & 8)) + setMonsterMode(monster, 7); + else + alignMonsterToParty(monster); + break; + + case 6: + // same as mode 5, but without rearranging + if (--monster->fightCurTick <= 0) + setMonsterMode(monster, 7); + break; + + case 7: + // monster destination is set to current party position + // depending on the flag setting this gets updated each round + // monster can't change mode before arriving at destination and/or attacking the party + if (!chasePartyWithDistanceAttacks(monster)) + chasePartyWithCloseAttacks(monster); + checkSceneUpdateNeed(monster->block); + break; + + case 8: + // first recovery phase after delivering an attack + if (++monster->fightCurTick > 2) { + setMonsterMode(monster, 5); + monster->fightCurTick = (int8)((((8 << 8) / monster->properties->fightingStats[4]) * _monsterModifiers3[_monsterDifficulty]) >> 8); + } + checkSceneUpdateNeed(monster->block); + break; + + case 9: + if (--monster->fightCurTick) { + chasePartyWithCloseAttacks(monster); + } else { + setMonsterMode(monster, 7); + monster->flags &= 0xFFF7; + } + break; + + case 12: + checkSceneUpdateNeed(monster->block); + if (++monster->fightCurTick > 13) + runLevelScriptCustom(0x404, -1, monster->id, monster->id, 0, 0); + break; + + case 13: + // monster death + if (++monster->fightCurTick > 2) + killMonster(monster); + checkSceneUpdateNeed(monster->block); + break; + + case 14: + monster->damageReceived = 0; + break; + + default: + break; + } + + if (monster->damageReceived) { + if (monster->damageReceived & 0x8000) + monster->damageReceived &= 0x7FFF; + else + monster->damageReceived = 0; + checkSceneUpdateNeed(monster->block); + } + + monster->flags &= 0xFFEF; +} + +void LoLEngine::moveMonster(LoLMonster *monster) { + static const int8 turnPos[] = { 0, 2, 6, 6, 0, 2, 4, 4, 2, 2, 4, 6, 0, 0, 4, 6, 0 }; + if (monster->x != monster->destX || monster->y != monster->destY) { + walkMonster(monster); + } else if (monster->direction != monster->destDirection) { + int i = (monster->facing << 2) + (monster->destDirection >> 1); + setMonsterDirection(monster, turnPos[i]); + } +} + +void LoLEngine::walkMonster(LoLMonster *monster) { + if (monster->properties->flags & 0x400) + return; + + int s = walkMonsterCalcNextStep(monster); + + if (s == -1) { + if (walkMonsterCheckDest(monster->x, monster->y, monster, 4) != 1) + return; + + _objectLastDirection ^= 4; + setMonsterDirection(monster, _objectLastDirection); + } else { + setMonsterDirection(monster, s); + if (monster->numDistAttacks) { + if (getBlockDistance(monster->block, _currentBlock) >= 2) { + if (checkForPossibleDistanceAttack(monster->block, monster->direction, 3, _currentBlock) != 5) { + if (monster->distAttackTick) + return; + } + } + } + } + + int fx = 0; + int fy = 0; + + getNextStepCoords(monster->x, monster->y, fx, fy, (s == -1) ? _objectLastDirection : s); + placeMonster(monster, fx, fy); +} + +bool LoLEngine::chasePartyWithDistanceAttacks(LoLMonster *monster) { + if (!monster->numDistAttacks) + return false; + + if (monster->distAttackTick > 0) { + monster->distAttackTick--; + return false; + } + + int dir = checkForPossibleDistanceAttack(monster->block, monster->facing, 4, _currentBlock); + if (dir == 5) + return false; + + int s = 0; + + if (monster->flags & 0x10) { + s = monster->properties->numDistWeapons ? rollDice(1, monster->properties->numDistWeapons) : 0; + } else { + s = monster->curDistWeapon++; + if (monster->curDistWeapon >= monster->properties->numDistWeapons) + monster->curDistWeapon = 0; + } + + int flyingObject = monster->properties->distWeapons[s]; + + if (flyingObject & 0xC000) { + if (getBlockDistance(monster->block, _currentBlock) > 1) { + int type = flyingObject & 0x4000 ? 0 : 1; + flyingObject = makeItem(flyingObject & 0x3FFF, 0, 0); + + if (flyingObject) { + if (!launchObject(type, flyingObject, monster->x, monster->y, 12, dir << 1, -1, monster->id | 0x8000, 0x3F)) + deleteItem(flyingObject); + } + } + } else if (!(flyingObject & 0x2000)) { + if (getBlockDistance(monster->block, _currentBlock) > 1) + return false; + + if (flyingObject == 1) { + snd_playSoundEffect(147, -1); + shakeScene(10, 2, 2, 1); + + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1)) + continue; + + int item = removeCharacterItem(i, 15); + if (item) + setItemPosition(item, _partyPosX, _partyPosY, 0, 1); + + inflictDamage(i, 20, 0xFFFF, 0, 2); + } + + } else if (flyingObject == 3) { + // shriek + for (int i = 0; i < 30; i++) { + if (getBlockDistance(monster->block, _monsters[i].block) < 7) + setMonsterMode(monster, 7); + } + _txt->printMessage(2, "%s", getLangString(0x401A)); + + } else if (flyingObject == 4) { + launchMagicViper(); + + } else { + return false; + } + } + + if (monster->numDistAttacks != 255) + monster->numDistAttacks--; + + monster->distAttackTick = (monster->properties->fightingStats[4] * 8) >> 8; + + return true; +} + +void LoLEngine::chasePartyWithCloseAttacks(LoLMonster *monster) { + if (!(monster->flags & 8)) { + int dir = calcMonsterDirection(monster->x & 0xFF00, monster->y & 0xFF00, _partyPosX & 0xFF00, _partyPosY & 0xFF00); + int x1 = _partyPosX; + int y1 = _partyPosY; + + calcSpriteRelPosition(monster->x, monster->y, x1, y1, dir >> 1); + + int t = (x1 < 0) ? -x1 : x1; + if (y1 <= 160 && t <= 80) { + if ((monster->direction == dir) && (monster->facing == (dir >> 1))) { + int dst = getNearestPartyMemberFromPos(monster->x, monster->y); + snd_playSoundEffect(monster->properties->sounds[1], -1); + int m = monster->id | 0x8000; + int hit = battleHitSkillTest(m, dst, 0); + + if (hit) { + int mx = calcInflictableDamage(m, dst, hit); + int dmg = rollDice(2, mx); + inflictDamage(dst, dmg, m, 0, 0); + applyMonsterAttackSkill(monster, dst, dmg); + } + + setMonsterMode(monster, 8); + checkSceneUpdateNeed(monster->block); + + } else { + setMonsterDirection(monster, dir); + checkSceneUpdateNeed(monster->block); + } + return; + } + } + + if (monster->x != monster->destX || monster->y != monster->destY) { + walkMonster(monster); + } else { + setMonsterDirection(monster, monster->destDirection); + setMonsterMode(monster, (rollDice(1, 100) <= 50) ? 4 : 3); + } +} + +int LoLEngine::walkMonsterCalcNextStep(LoLMonster *monster) { + static const int8 walkMonsterTable1[] = { 7, -6, 5, -4, 3, -2, 1, 0 }; + static const int8 walkMonsterTable2[] = { -7, 6, -5, 4, -3, 2, -1, 0 }; + + if (++_monsterStepCounter > 10) { + _monsterStepCounter = 0; + _monsterStepMode ^= 1; + } + + const int8 *tbl = _monsterStepMode ? walkMonsterTable2 : walkMonsterTable1; + + int sx = monster->x; + int sy = monster->y; + int s = monster->direction; + int d = calcMonsterDirection(monster->x, monster->y, monster->destX, monster->destY); + + if (monster->flags & 8) + d ^= 4; + + d = (d - s) & 7; + + if (d >= 5) + s = (s - 1) & 7; + else if (d) + s = (s + 1) & 7; + + for (int i = 7; i > -1; i--) { + s = (s + tbl[i]) & 7; + + int fx = 0; + int fy = 0; + getNextStepCoords(sx, sy, fx, fy, s); + d = walkMonsterCheckDest(fx, fy, monster, 4); + + if (!d) + return s; + + if ((d != 1) || (s & 1) || (!(monster->properties->flags & 0x80))) + continue; + + uint8 w = _levelBlockProperties[_monsterCurBlock].walls[(s >> 1) ^ 2]; + + if (_wllWallFlags[w] & 0x20) { + if (_specialWallTypes[w] == 5) { + openCloseDoor(_monsterCurBlock, 1); + return -1; + } + } + + if (_wllWallFlags[w] & 8) + return -1; + } + + return -1; +} + +int LoLEngine::checkForPossibleDistanceAttack(uint16 monsterBlock, int direction, int distance, uint16 curBlock) { + int mdist = getBlockDistance(curBlock, monsterBlock); + + if (mdist > distance) + return 5; + + int dir = calcMonsterDirection(monsterBlock & 0x1F, monsterBlock >> 5, curBlock & 0x1F, curBlock >> 5); + if ((dir & 1) || (dir != (direction << 1))) + return 5; + + if (((monsterBlock & 0x1F) != (curBlock & 0x1F)) && ((monsterBlock & 0xFFE0) != (curBlock & 0xFFE0))) + return 5; + + if (distance < 0) + return 5; + + int p = monsterBlock; + + for (int i = 0; i < distance; i++) { + p = calcNewBlockPosition(p, direction); + + if (p == curBlock) + return direction; + + if (_wllWallFlags[_levelBlockProperties[p].walls[direction ^ 2]] & 2) + return 5; + + if (_levelBlockProperties[p].assignedObjects & 0x8000) + return 5; + } + + return 5; +} + +int LoLEngine::walkMonsterCheckDest(int x, int y, LoLMonster *monster, int unk) { + uint8 m = monster->mode; + monster->mode = 15; + + int objType = checkBlockBeforeObjectPlacement(x, y, monster->properties->maxWidth, 7, monster->properties->flags & 0x1000 ? 32 : unk); + + monster->mode = m; + return objType; +} + +void LoLEngine::getNextStepCoords(int16 srcX, int16 srcY, int &newX, int &newY, uint16 direction) { + static const int8 stepAdjustX[] = { 0, 32, 32, 32, 0, -32, -32, -32 }; + static const int8 stepAdjustY[] = { -32, -32, 0, 32, 32, 32, 0, -32 }; + + newX = (srcX + stepAdjustX[direction]) & 0x1FFF; + newY = (srcY + stepAdjustY[direction]) & 0x1FFF; +} + +void LoLEngine::alignMonsterToParty(LoLMonster *monster) { + uint8 mdir = monster->direction >> 1; + uint16 mx = monster->x; + uint16 my = monster->y; + uint16 *pos = (mdir & 1) ? &my : &mx; + bool centered = (*pos & 0x7F) == 0; + + bool posFlag = true; + if (monster->properties->maxWidth <= 63) { + if (centered) { + bool r = false; + + if (monster->nextAssignedObject & 0x8000) { + r = true; + } else { + uint16 id = _levelBlockProperties[monster->block].assignedObjects; + id = (id & 0x8000) ? (id & 0x7FFF) : 0xFFFF; + + if (id != monster->id) { + r = true; + } else { + for (int i = 0; i < 3; i++) { + mdir = (mdir + 1) & 3; + id = _levelBlockProperties[calcNewBlockPosition(monster->block, mdir)].assignedObjects; + id = (id & 0x8000) ? (id & 0x7FFF) : 0xFFFF; + if (id != 0xFFFF) { + r = true; + break; + } + } + } + } + + if (r) + posFlag = false; + } else { + posFlag = false; + } + } + + if (centered && posFlag) + return; + + if (posFlag) { + if (*pos & 0x80) + *pos -= 32; + else + *pos += 32; + } else { + if (*pos & 0x80) + *pos += 32; + else + *pos -= 32; + } + + if (walkMonsterCheckDest(mx, my, monster, 4)) + return; + + int fx = _partyPosX; + int fy = _partyPosY; + calcSpriteRelPosition(mx, my, fx, fy, monster->direction >> 1); + + if (fx < 0) + fx = -fx; + + if (fy > 160 || fx > 80) + return; + + placeMonster(monster, mx, my); +} + +void LoLEngine::moveStrayingMonster(LoLMonster *monster) { + int x = 0; + int y = 0; + + if (monster->fightCurTick) { + uint8 d = (monster->direction - monster->fightCurTick) & 6; + uint8 id = monster->id; + + for (int i = 0; i < 7; i++) { + getNextStepCoords(monster->x, monster->y, x, y, d); + + if (!walkMonsterCheckDest(x, y, monster, 4)) { + placeMonster(monster, x, y); + setMonsterDirection(monster, d); + if (!i) { + if (++id > 3) + monster->fightCurTick = 0; + } + return; + } + + d = (d + monster->fightCurTick) & 6; + } + setMonsterMode(monster, 3); + + } else { + monster->direction &= 6; + getNextStepCoords(monster->x, monster->y, x, y, monster->direction); + if (!walkMonsterCheckDest(x, y, monster, 4)) { + placeMonster(monster, x, y); + } else { + monster->fightCurTick = _rnd.getRandomBit() ? 2 : -2; + monster->direction = (monster->direction + monster->fightCurTick) & 6; + } + } +} + +void LoLEngine::killMonster(LoLMonster *monster) { + setMonsterMode(monster, 14); + monsterDropItems(monster); + checkSceneUpdateNeed(monster->block); + + uint8 w = _levelBlockProperties[monster->block].walls[0]; + uint16 f = _levelBlockProperties[monster->block].flags; + if (_wllVmpMap[w] == 0 && _wllShapeMap[w] == 0 && !(f & 0x40) && !(monster->properties->flags & 0x1000)) + _levelBlockProperties[monster->block].flags |= 0x80; + + placeMonster(monster, 0, 0); +} + +} // End of namespace Kyra + +#endif // ENABLE_LOL |