scummvm/engines/gob/mult_v2.cpp
Eugene Sandulenko 5dcdfd2600
GOB: Dual-license the gob engine under LGPL
The major contributors provided their consent:
DrMcCoy, Strangerke, sdelamarre, sev.

The goal is to allow the re-release of this code under
Switch, which is incompatible with pure GPL
2024-01-08 01:44:04 +01:00

1418 lines
38 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* This file is dual-licensed.
* In addition to the GPLv3 license mentioned above, this code is also
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
* full text of the license.
*
*/
#include "common/endian.h"
#include "common/stream.h"
#include "gob/gob.h"
#include "gob/mult.h"
#include "gob/global.h"
#include "gob/util.h"
#include "gob/draw.h"
#include "gob/game.h"
#include "gob/script.h"
#include "gob/resources.h"
#include "gob/goblin.h"
#include "gob/inter.h"
#include "gob/scenery.h"
#include "gob/map.h"
#include "gob/video.h"
#include "gob/videoplayer.h"
namespace Gob {
Mult_v2::Mult_v2(GobEngine *vm) : Mult_v1(vm) {
_renderObjs = nullptr;
_multData = nullptr;
for (int i = 0; i < 8; i++)
_multDatas[i] = nullptr;
}
Mult_v2::~Mult_v2() {
freeMultKeys();
for (int i = 0; i < 8; i++) {
_multData = _multDatas[i];
freeMultKeys();
}
}
void Mult_v2::loadMult(int16 resId) {
int8 index;
uint8 staticCount;
uint8 animCount;
bool hasImds;
index = (resId & 0x8000) ? _vm->_game->_script->readByte() : 0;
resId &= 0x7FFF;
debugC(4, kDebugGameFlow, "Loading mult %d", index);
_multData = new Mult_Data;
memset(_multData, 0, sizeof(Mult_Data));
_multDatas[index] = _multData;
for (int i = 0; i < 4; i++)
_multData->animObjs[0][i] = i;
_multData->sndSlotsCount = 0;
_multData->frameStart = 0;
Resource *resource = _vm->_game->_resources->getResource(resId);
if (!resource)
return;
Common::SeekableReadStream &data = *resource->stream();
_multData->staticCount = staticCount = data.readSByte();
_multData->animCount = animCount = data.readSByte();
staticCount++;
animCount++;
hasImds = (staticCount & 0x80) != 0;
staticCount &= 0x7F;
debugC(7, kDebugGraphics, "statics: %u, anims: %u, imds: %u",
staticCount, animCount, hasImds);
for (int i = 0; i < 10; i++) {
_multData->staticLoaded[i] = false;
_multData->animLoaded[i] = false;
}
for (int i = 0; i < staticCount; i++, data.seek(14, SEEK_CUR)) {
_multData->staticIndices[i] = _vm->_scenery->loadStatic(1);
if (_multData->staticIndices[i] >= 100) {
_multData->staticIndices[i] -= 100;
_multData->staticLoaded[i] = true;
}
}
for (int i = 0; i < animCount; i++, data.seek(14, SEEK_CUR)) {
_multData->animIndices[i] = _vm->_scenery->loadAnim(1);
if (_multData->animIndices[i] >= 100) {
_multData->animIndices[i] -= 100;
_multData->animLoaded[i] = true;
}
}
_multData->frameRate = data.readSint16LE();
_multData->staticKeysCount = data.readSint16LE();
_multData->staticKeys = new Mult_StaticKey[_multData->staticKeysCount];
for (int i = 0; i < _multData->staticKeysCount; i++) {
_multData->staticKeys[i].frame = data.readSint16LE();
_multData->staticKeys[i].layer = data.readSint16LE();
}
for (int i = 0; i < 4; i++) {
_multData->imdKeysCount[i] = 0;
_multData->imdKeys[i] = nullptr;
_multData->imdIndices[i] = -1;
for (int j = 0; j < 4; j++) {
_multData->animKeysIndices[i][j] = 0;
_multData->imdKeysIndices[i][j] = 0;
}
_multData->animKeysFrames[i] = -1;
_multData->animKeysCount[i] = data.readSint16LE();
_multData->animKeys[i] = new Mult_AnimKey[_multData->animKeysCount[i]];
for (int j = 0; j < _multData->animKeysCount[i]; j++) {
_multData->animKeys[i][j].frame = data.readSint16LE();
_multData->animKeys[i][j].layer = data.readSint16LE();
_multData->animKeys[i][j].posX = data.readSint16LE();
_multData->animKeys[i][j].posY = data.readSint16LE();
_multData->animKeys[i][j].order = data.readSint16LE();
}
}
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 16; j++) {
_multData->fadePal[i][j].red = data.readByte();
_multData->fadePal[i][j].green = data.readByte();
_multData->fadePal[i][j].blue = data.readByte();
}
}
_multData->palFadeKeysCount = data.readSint16LE();
_multData->palFadeKeys = new Mult_PalFadeKey[_multData->palFadeKeysCount];
for (int i = 0; i < _multData->palFadeKeysCount; i++) {
_multData->palFadeKeys[i].frame = data.readSint16LE();
_multData->palFadeKeys[i].fade = data.readSint16LE();
_multData->palFadeKeys[i].palIndex = data.readSint16LE();
_multData->palFadeKeys[i].flag = data.readSByte();
}
_multData->palKeysCount = data.readSint16LE();
_multData->palKeys = new Mult_PalKey[_multData->palKeysCount];
for (int i = 0; i < _multData->palKeysCount; i++) {
_multData->palKeys[i].frame = data.readSint16LE();
_multData->palKeys[i].cmd = data.readSint16LE();
_multData->palKeys[i].rates[0] = data.readSint16LE();
_multData->palKeys[i].rates[1] = data.readSint16LE();
_multData->palKeys[i].rates[2] = data.readSint16LE();
_multData->palKeys[i].rates[3] = data.readSint16LE();
_multData->palKeys[i].unknown0 = data.readSint16LE();
_multData->palKeys[i].unknown1 = data.readSint16LE();
data.read(_multData->palKeys[i].subst, 64);
}
_multData->textKeysCount = data.readSint16LE();
_multData->textKeys = new Mult_TextKey[_multData->textKeysCount];
for (int i = 0; i < _multData->textKeysCount; i++) {
_multData->textKeys[i].frame = data.readSint16LE();
_multData->textKeys[i].cmd = data.readSint16LE();
if (!hasImds)
data.seek(24, SEEK_CUR);
}
_multData->sndKeysCount = data.readSint16LE();
_multData->sndKeys = new Mult_SndKey[_multData->sndKeysCount];
for (int i = 0; i < _multData->sndKeysCount; i++) {
int j;
_multData->sndKeys[i].frame = data.readSint16LE();
_multData->sndKeys[i].cmd = data.readSint16LE();
_multData->sndKeys[i].freq = data.readSint16LE();
_multData->sndKeys[i].fadeLength = data.readSint16LE();
_multData->sndKeys[i].repCount = data.readSint16LE();
_multData->sndKeys[i].soundIndex = -1;
_multData->sndKeys[i].resId = -1;
data.seek(2, SEEK_CUR);
if (!hasImds)
data.seek(24, SEEK_CUR);
switch (_multData->sndKeys[i].cmd) {
case 1:
case 4:
_multData->sndKeys[i].resId = _vm->_game->_script->peekUint16();
for (j = 0; j < i; j++) {
if (_multData->sndKeys[j].resId ==
_multData->sndKeys[i].resId) {
_multData->sndKeys[i].soundIndex =
_multData->sndKeys[j].soundIndex;
_vm->_game->_script->skip(2);
break;
}
}
if (i == j) {
_multData->sndSlot[_multData->sndSlotsCount] =
_vm->_inter->loadSound(1);
_multData->sndKeys[i].soundIndex =
_multData->sndSlot[_multData->sndSlotsCount] & 0x7FFF;
_multData->sndSlotsCount++;
}
break;
case 3:
_vm->_game->_script->skip(4);
break;
case -1:
break;
default:
warning("Mult_v2::loadMult(): Unknown sound key command (%d)",
_multData->sndKeys[i].cmd);
}
}
_multData->imdFiles = nullptr;
_multData->somepointer10 = nullptr;
if (hasImds)
loadImds(data);
delete resource;
}
void Mult_v2::loadImds(Common::SeekableReadStream &data) {
int16 size;
size = _vm->_game->_script->readInt16();
_multData->execPtr = _vm->_game->_script->getData() + _vm->_game->_script->pos();
_vm->_game->_script->skip(size * 2);
if (_vm->_game->_script->getVersionMinor() < 3)
return;
size = data.readSint16LE();
if (size > 0) {
_multData->somepointer10 = new char[size * 20];
data.read(_multData->somepointer10, size * 20);
}
size = _vm->_game->_script->readInt16();
if (size <= 0)
return;
_multData->imdFiles = new char[size * 14];
memcpy(_multData->imdFiles,
_vm->_game->_script->getData() + _vm->_game->_script->pos(), size * 14);
// WORKAROUND: The Windows versions of Lost in Time and Gob3 have VMD not
// IMD files, but they are still referenced as IMD.
if (((_vm->getGameType() == kGameTypeLostInTime) || (_vm->getGameType() == kGameTypeGob3)) &&
(_vm->getPlatform() == Common::kPlatformWindows)) {
for (int i = 0; i < size; i++) {
char *dot = strrchr(_multData->imdFiles + (i * 14), '.');
if (dot)
*dot = '\0';
}
}
_vm->_game->_script->skip(size * 14);
data.seek(2, SEEK_CUR);
for (int i = 0; i < 4; i++) {
_multData->imdKeysCount[i] = data.readSint16LE();
_multData->imdKeys[i] = new Mult_ImdKey[_multData->imdKeysCount[i]];
for (int j = 0; j < _multData->imdKeysCount[i]; j++) {
_multData->imdKeys[i][j].frame = data.readSint16LE();
_multData->imdKeys[i][j].imdFile = data.readSint16LE();
_multData->imdKeys[i][j].field_4 = data.readSint16LE();
_multData->imdKeys[i][j].field_6 = data.readSint16LE();
_multData->imdKeys[i][j].flags = data.readUint16LE();
_multData->imdKeys[i][j].palFrame = data.readSint16LE();
_multData->imdKeys[i][j].lastFrame = data.readSint16LE();
_multData->imdKeys[i][j].palStart = data.readSByte();
_multData->imdKeys[i][j].palEnd = data.readSByte();
}
}
}
void Mult_v2::freeMultKeys() {
uint8 animCount;
uint8 staticCount;
if (!_multData)
return;
staticCount = (_multData->staticCount + 1) & 0x7F;
animCount = _multData->animCount + 1;
for (int i = 0; i < staticCount; i++)
if (_multData->staticLoaded[i])
_vm->_scenery->freeStatic(_multData->staticIndices[i]);
for (int i = 0; i < animCount; i++)
if (_multData->animLoaded[i])
_vm->_scenery->freeAnim(_multData->animIndices[i]);
delete[] _multData->staticKeys;
for (int i = 0; i < 4; i++) {
delete[] _multData->animKeys[i];
delete[] _multData->imdKeys[i];
}
delete[] _multData->palFadeKeys;
delete[] _multData->palKeys;
delete[] _multData->textKeys;
for (int i = 0; i < _multData->sndSlotsCount; i++)
if (!(_multData->sndSlot[i] & 0x8000))
_vm->_game->freeSoundSlot(_multData->sndSlot[i]);
delete[] _multData->sndKeys;
delete[] _multData->imdFiles;
delete[] _multData->somepointer10;
if (_animDataAllocated) {
freeMult();
delete _animArrayX;
delete _animArrayY;
delete[] _animArrayData;
_animArrayX = nullptr;
_animArrayY = nullptr;
_animArrayData = nullptr;
_animDataAllocated = false;
}
for (int i = 0; i < 8; i++)
if (_multDatas[i] == _multData)
_multDatas[i] = nullptr;
delete _multData;
_multData = nullptr;
}
bool Mult_v2::hasMultData(uint16 multIndex) {
if (multIndex > 7)
error("Multindex out of range");
return _multDatas[multIndex] != nullptr;
}
void Mult_v2::setMultData(uint16 multIndex) {
if (multIndex > 7)
error("Multindex out of range");
debugC(4, kDebugGameFlow, "Switching to mult %d", multIndex);
_multData = _multDatas[multIndex];
}
void Mult_v2::zeroMultData(uint16 multIndex) {
if (multIndex > 7)
error("Multindex out of range");
_multDatas[multIndex] = nullptr;
}
void Mult_v2::multSub(uint16 multIndex) {
uint16 flags;
int16 expr;
int16 index;
int16 startFrame, stopFrame, firstFrame;
flags = multIndex;
multIndex = (multIndex >> 12) & 0xF;
if (multIndex > 7)
error("Multindex out of range");
_vm->_util->notifyNewAnim();
debugC(4, kDebugGameFlow, "Sub mult %d", multIndex);
_multData = _multDatas[multIndex];
if (!_multData) {
_vm->_game->_script->readValExpr();
_vm->_game->_script->readValExpr();
_vm->_game->_script->readValExpr();
_vm->_game->_script->readValExpr();
return;
}
if (flags & 0x200)
index = 3;
else if (flags & 0x100)
index = 2;
else if (flags & 0x80)
index = 1;
else
index = 0;
if (flags & 0x400) {
flags = 0x400;
_multData->animDirection = -1;
} else {
_multData->animDirection = 1;
flags &= 0x7F;
}
_multData->animObjs[index][0] = flags;
for (int i = 1; i < 4; i++)
_multData->animObjs[index][i] = _vm->_game->_script->readValExpr();
expr = _vm->_game->_script->readValExpr();
_multData->animKeysFrames[index] = expr;
_multData->animKeysStartFrames[index] = expr;
WRITE_VAR(18 + index, expr);
if (expr == -1) {
if (!_objects)
return;
for (int i = 0; i < 4; i++) {
int obj = _multData->animObjs[index][i];
if ((obj == -1) || (obj == 1024))
continue;
Mult_AnimData &animData = *(_objects[obj].pAnimData);
animData.animType = animData.animTypeBak;
}
return;
}
startFrame = _multData->animKeysStartFrames[index];
stopFrame = _multData->animKeysStopFrames[index];
if (_multData->animDirection == 1) {
stopFrame = 32000;
for (int i = 0; i < _multData->textKeysCount; i++) {
int16 textFrame = _multData->textKeys[i].frame;
if ((textFrame > startFrame) && (textFrame < stopFrame))
stopFrame = textFrame;
}
} else {
stopFrame = 0;
for (int i = 0; i < _multData->textKeysCount; i++) {
int16 textFrame = _multData->textKeys[i].frame;
if ((textFrame < startFrame) && (textFrame > stopFrame))
stopFrame = textFrame;
}
}
if (_objects) {
for (int i = 0; i < 4; i++) {
int obj = _multData->animObjs[index][i];
if ((obj != -1) && (obj != 1024))
_objects[obj].pAnimData->animTypeBak = _objects[obj].pAnimData->animType;
}
}
for (int i = 0; i < 4; i++) {
_multData->animKeysIndices[index][i] = 0;
for (int j = 0; j < _multData->animKeysCount[i]; j++)
if (_multData->animKeys[i][j].frame >= startFrame) {
_multData->animKeysIndices[index][i] = j;
break;
}
}
if (_multData->animDirection == -1) {
int i = 0;
while (_multData->imdKeys[index][i].frame <= startFrame)
i++;
_multData->imdIndices[index] = i - 1;
}
firstFrame = (_multData->animDirection == 1) ? startFrame : stopFrame;
for (int i = 0; i < 4; i++) {
_multData->imdKeysIndices[index][i] = 0;
for (int j = 0; j < _multData->imdKeysCount[i]; j++)
if (_multData->imdKeys[i][j].frame >= firstFrame) {
_multData->imdKeysIndices[index][i] = j;
break;
}
}
_multData->animKeysStartFrames[index] = startFrame;
_multData->animKeysStopFrames[index] = stopFrame;
}
void Mult_v2::playMultInit() {
_doPalSubst = false;
_palFadingRed = 0;
_palFadingGreen = 0;
_palFadingBlue = 0;
_oldPalette = _vm->_global->_pPaletteDesc->vgaPal;
if (!_animSurf) {
int16 width, height;
if (_objects)
for (int i = 0; i < _objCount; i++) {
delete _objects[i].pPosX;
delete _objects[i].pPosY;
}
delete[] _objects;
_vm->_util->setFrameRate(_multData->frameRate);
_animTop = 0;
_animLeft = 0;
_animWidth = _vm->_video->_surfWidth;
_animHeight = _vm->_video->_surfHeight;
_objCount = 4;
delete[] _orderArray;
delete[] _renderObjs;
delete _animArrayX;
delete _animArrayY;
delete[] _animArrayData;
_objects = new Mult_Object[_objCount]();
_orderArray = new int8[_objCount]();
_renderObjs = new Mult_Object*[_objCount]();
_animArrayX = new VariablesLE(_objCount * 4);
_animArrayY = new VariablesLE(_objCount * 4);
_animArrayData = new Mult_AnimData[_objCount]();
for (_counter = 0; _counter < _objCount; _counter++) {
Mult_Object &multObj = _objects[_counter];
Mult_AnimData &animData = _animArrayData[_counter];
multObj.pPosX = new VariableReference(*_animArrayX, _counter * 4);
multObj.pPosY = new VariableReference(*_animArrayY, _counter * 4);
multObj.pAnimData = &animData;
animData.isStatic = 1;
multObj.lastLeft = -1;
multObj.lastTop = -1;
multObj.lastRight = -1;
multObj.lastBottom = -1;
}
width = _animWidth;
height = _animHeight;
_vm->_draw->adjustCoords(0, &width, &height);
_vm->_draw->initSpriteSurf(Draw::kAnimSurface, width, height, 0);
_animSurf = _vm->_draw->_spritesArray[Draw::kAnimSurface];
_vm->_draw->_spritesArray[Draw::kAnimSurface]->blit(*_vm->_draw->_spritesArray[Draw::kBackSurface],
0, 0, _vm->_video->_surfWidth, _vm->_video->_surfHeight, 0, 0);
for (_counter = 0; _counter < _objCount; _counter++)
_multData->palAnimIndices[_counter] = _counter;
_animDataAllocated = true;
} else
_animDataAllocated = false;
_frame = 0;
}
void Mult_v2::drawStatics(bool &stop) {
int staticIndex;
if (_multData->staticKeys[_multData->staticKeysCount - 1].frame > _frame)
stop = false;
for (_counter = 0; _counter < _multData->staticKeysCount; _counter++) {
if ((_multData->staticKeys[_counter].frame != _frame)
|| (_multData->staticKeys[_counter].layer == -1))
continue;
if (_multData->staticKeys[_counter].layer >= 0) {
int i = 0;
_vm->_scenery->_curStatic = 0;
_vm->_scenery->_curStaticLayer =
_multData->staticKeys[_counter].layer;
staticIndex = _multData->staticIndices[i];
while (_vm->_scenery->getStaticLayersCount(staticIndex) <=
_vm->_scenery->_curStaticLayer) {
_vm->_scenery->_curStaticLayer -=
_vm->_scenery->getStaticLayersCount(staticIndex);
staticIndex = _multData->staticIndices[++i];
_vm->_scenery->_curStatic++;
}
_vm->_scenery->_curStatic =
_multData->staticIndices[_vm->_scenery->_curStatic];
_vm->_scenery->renderStatic(_vm->_scenery->_curStatic,
_vm->_scenery->_curStaticLayer);
} else {
int layer = -_multData->staticKeys[_counter].layer - 2;
_vm->_draw->_spriteLeft =
READ_LE_UINT16(_multData->execPtr + layer * 2);
_vm->_draw->_destSpriteX = 0;
_vm->_draw->_destSpriteY = 0;
_vm->_draw->_destSurface = Draw::kBackSurface;
_vm->_draw->_transparency = 0;
_vm->_draw->spriteOperation(DRAW_LOADSPRITE);
_vm->_scenery->_curStatic = -1;
}
_vm->_draw->_spritesArray[Draw::kAnimSurface]->blit(*_vm->_draw->_spritesArray[Draw::kBackSurface],
0, 0, _vm->_video->_surfWidth, _vm->_video->_surfHeight, 0, 0);
}
}
void Mult_v2::drawAnims(bool &stop) {
int16 count;
int animIndex;
for (int i = 0; i < 4; i++) {
int16 animKeysCount = _multData->animKeysCount[i];
if ((animKeysCount > 0) && ((uint16) _multData->animKeys[i][animKeysCount - 1].frame > _frame))
stop = false;
}
for (_index = 0; _index < 4; _index++) {
int16 animKeysCount = _multData->animKeysCount[_index];
for (_counter = 0; _counter < animKeysCount; _counter++) {
Mult_AnimKey &key = _multData->animKeys[_index][_counter];
Mult_Object &animObj = _objects[_multData->animObjs[0][_index]];
Mult_AnimData &animData = *(animObj.pAnimData);
if (key.frame != _frame)
continue;
if (key.layer != -1) {
*(animObj.pPosX) = key.posX;
*(animObj.pPosY) = key.posY;
animData.frame = 0;
animData.order = key.order;
animData.animType = 1;
animData.isPaused = 0;
animData.isStatic = 0;
animData.maxTick = 0;
animObj.tick = 0;
animData.layer = key.layer;
int i = 0;
animIndex = _multData->animIndices[i];
count = _vm->_scenery->getAnimLayersCount(animIndex);
while (animData.layer >= count) {
animData.layer -= count;
animIndex = _multData->animIndices[++i];
count = _vm->_scenery->getAnimLayersCount(animIndex);
}
animData.animation = animIndex;
} else
animData.isStatic = 1;
}
}
}
void Mult_v2::newCycleAnim(Mult_Object &animObj) {
Mult_AnimData &animData = *(animObj.pAnimData);
Scenery::AnimLayer *animLayer = nullptr;
if (animData.animation >= 0) {
int nAnim = animData.animation, nLayer = animData.layer;
if (_vm->_scenery->getAnimLayersCount(nAnim) <= nLayer)
return;
animLayer = _vm->_scenery->getAnimLayer(nAnim, nLayer);
} else {
if (animObj.videoSlot > 0) {
if (_vm->getGameType() == kGameTypeAdibou2) {
int expectedFrame = _vm->_vidPlayer->getExpectedFrameFromCurrentTime(animObj.videoSlot - 1);
if (expectedFrame >= 0 &&
expectedFrame < animData.frame) {
return; // We are in advance, do not further increment the frame
}
_vm->_vidPlayer->waitEndFrame(animObj.videoSlot - 1, true);
} else {
_vm->_video->retrace();
_vm->_vidPlayer->waitEndFrame(animObj.videoSlot - 1, true);
}
}
}
if (animData.animType == 4) {
animData.frame = 0;
animData.isPaused = 1;
if ((animData.animation < 0) && (animObj.videoSlot > 0)) {
_vm->_vidPlayer->closeVideo(animObj.videoSlot - 1);
animObj.videoSlot = 0;
animObj.animName[0] = 0;
}
return;
}
if (animData.animType == 12)
animData.animType = 11;
if (animData.animType == 11) {
if (animData.isBusy != 0) {
if (animData.animTypeBak == 1) {
if (animData.framesLeft != 0) {
--animData.framesLeft;
} else {
_vm->_goblin->setGoblinState(&animObj, animData.isBusy);
animData.isBusy = 0;
animData.animTypeBak = 0;
animData.animType = 10;
}
}
}
return;
}
if (animData.animType != 8) {
animData.frame++;
if (_vm->getGameType() == kGameTypeAdibou2
&&
animData.animation < 0
&&
animObj.videoSlot > 0) {
// Workaround to improve audio sync of video objects in Adibou 2
// They easily get out of sync when the timing is done by hotspots::evaluate, which sometimes does not call animate()
// as often as needed for good sync (mouse events processing, in particular, can delay the call).
// The original game seems to use also some kind of frame skipping to address this problem.
int32 expectedFrame = _vm->_vidPlayer->getExpectedFrameFromCurrentTime(animObj.videoSlot - 1);
expectedFrame = CLIP<int32>(expectedFrame, -1, _vm->_vidPlayer->getFrameCount(animObj.videoSlot - 1) - 1);
if (expectedFrame > animData.frame + 5) {
// We are too far behind, skip frames
animData.frame = expectedFrame;
}
}
}
if (animData.animation < 0) {
if ((animObj.videoSlot > 0) &&
((_vm->_vidPlayer->getCurrentFrame(animObj.videoSlot - 1) + 1) <
_vm->_vidPlayer->getFrameCount(animObj.videoSlot - 1))) {
animData.newCycle = 0;
return;
}
} else {
if (animData.frame < animLayer->framesCount) {
animData.newCycle = 0;
return;
}
}
switch (animData.animType) {
case 0:
animData.frame = 0;
break;
case 1:
animData.frame = 0;
if (animLayer) {
*(animObj.pPosX) += animLayer->animDeltaX;
*(animObj.pPosY) += animLayer->animDeltaY;
}
break;
case 2:
animData.frame = 0;
animData.animation = animData.newAnimation;
animData.layer = animData.newLayer;
break;
case 3:
animData.animType = 4;
animData.frame = 0;
break;
case 5:
animData.isStatic = 1;
animData.frame = 0;
if ((animData.animation < 0) && (animObj.videoSlot > 0)) {
closeObjVideo(animObj);
}
break;
case 6:
case 7:
animData.frame--;
animData.isPaused = 1;
if ((animData.animation < 0) && (animObj.videoSlot > 0)) {
if (_vm->_vidPlayer->getFlags(animObj.videoSlot - 1) & 0x1000) {
_vm->_vidPlayer->closeVideo(animObj.videoSlot - 1);
animObj.videoSlot = 0;
animObj.animName[0] = 0;
}
}
break;
case 10:
if (_vm->_map->_usesObliqueCoordinates) {
int8 deltaY = animObj.destY - animObj.goblinY;
if (deltaY == -1) {
*animObj.pPosX = *animObj.pPosX - _vm->_map->getTilesWidth();
} else if (deltaY == 1) {
*animObj.pPosX = *animObj.pPosX + _vm->_map->getTilesWidth();
}
} else {
warning("STUB: newCycleAnim: _map->_usesObliqueCoordinates == false");
}
animData.frame = 0;
animData.destXBak = animData.destX;
animData.destYBak = animData.destY;
animObj.goblinX = animObj.destX;
animData.destX = animObj.goblinX;
animObj.goblinY = animObj.destY;
animData.destY = animObj.goblinY;
if (animData.pathExistence) {
animObj.gobDestX = animObj.goblinX;
animObj.gobDestY = animObj.goblinY;
}
_vm->_goblin->initiateMove(&animObj);
break;
default:
break;
}
animData.newCycle = 1;
}
void Mult_v2::animate() {
int8 minOrder = 100;
int8 maxOrder = 0;
int8 *orderArray;
int orderArrayPos = 0;
int8 animIndices[150];
int numAnims = 0;
if (!_objects)
return;
if (_objCount > 0) {
if (!_orderArray)
return;
orderArray = _orderArray;
} else
orderArray = nullptr;
advanceAllObjects();
// Find relevant objects
int8 currentOrder = (int8) _objCount;
for (int i = 0; i < _objCount; i++) {
Mult_Object &animObj = _objects[i];
Mult_AnimData &animData = *(animObj.pAnimData);
if (_vm->_map->_usesObliqueCoordinates && !animData.isStatic && animData.order < 100) {
animData.order = currentOrder;
animData.field_22 = 0;
animData.field_21 = 0;
if (animData.animType == 10 || animData.animType == 3) {
animData.field_21 = 1;
if (animData.curLookDir > 10 || animData.animType == 3) {
animData.field_22 = 1;
}
}
for (int j = 0; j < i; j++) {
Mult_Object &previousAnimObject = _objects[j];
Mult_AnimData &previousAnimData = *(previousAnimObject.pAnimData);
if (previousAnimData.isStatic || previousAnimData.order > 100)
continue;
int8 orderCorrection = 0;
if (previousAnimData.destY > animData.destY
&& previousAnimData.destX < animData.destX) {
orderCorrection = -1;
} else if (previousAnimData.destY < animData.destY
&& previousAnimData.destX > animData.destX) {
orderCorrection = 1;
} else if (animData.destX + animData.field_1F > previousAnimData.destX
&& animData.destY - animData.field_20 < previousAnimData.destY) {
orderCorrection = -1;
} else if (previousAnimData.destX + previousAnimData.field_1F > animData.destX
&& previousAnimData.destY - previousAnimData.field_20 < animData.destY) {
orderCorrection = 1;
}
animData.order += orderCorrection;
previousAnimData.order -= orderCorrection;
}
}
animData.intersected = 200;
if (animData.isStatic != 2) {
if ((animData.isStatic == 0) || (animObj.lastLeft != -1)) {
animIndices[numAnims] = i;
_renderObjs[numAnims] = &animObj;
numAnims++;
}
}
}
// Find dirty areas
for (int i = 0; i < numAnims; i++) {
Mult_Object &animObj = *_renderObjs[i];
Mult_AnimData &animData = *(animObj.pAnimData);
animObj.needRedraw = 0;
animObj.newTop = 1000;
animObj.newLeft = 1000;
animObj.newBottom = 0;
animObj.newRight = 0;
if (animData.isStatic == 2)
continue;
if (!animData.isStatic && !animData.isPaused &&
(animData.maxTick == animObj.tick)) {
animObj.needRedraw = 1;
_vm->_scenery->updateAnim(animData.layer, animData.frame,
animData.animation, 8, *animObj.pPosX, *animObj.pPosY, 0);
if (animObj.lastLeft == -1) {
animObj.newLeft = _vm->_scenery->_toRedrawLeft;
animObj.newTop = _vm->_scenery->_toRedrawTop;
animObj.newRight = _vm->_scenery->_toRedrawRight;
animObj.newBottom = _vm->_scenery->_toRedrawBottom;
} else {
animObj.newLeft =
MIN(animObj.lastLeft, _vm->_scenery->_toRedrawLeft);
animObj.newTop =
MIN(animObj.lastTop, _vm->_scenery->_toRedrawTop);
animObj.newRight =
MAX(animObj.lastRight, _vm->_scenery->_toRedrawRight);
animObj.newBottom =
MAX(animObj.lastBottom, _vm->_scenery->_toRedrawBottom);
if ((_vm->_game->_script->getVersionMinor() > 2) &&
(animObj.newLeft == animObj.lastLeft) &&
(animObj.newTop == animObj.lastTop) &&
(animObj.newRight == animObj.lastRight) &&
(animObj.newBottom == animObj.lastBottom) &&
(animData.redrawLayer == animData.layer) &&
(animData.redrawFrame == animData.frame) &&
(animData.redrawAnimation == animData.animation)) {
animObj.needRedraw = 0;
}
}
} else if (!animData.isStatic) {
if (animObj.lastLeft == -1) {
animObj.needRedraw = 1;
_vm->_scenery->updateAnim(animData.layer, animData.frame,
animData.animation, 8, *animObj.pPosX, *animObj.pPosY, 0);
animObj.newLeft = _vm->_scenery->_toRedrawLeft;
animObj.newTop = _vm->_scenery->_toRedrawTop;
animObj.newRight = _vm->_scenery->_toRedrawRight;
animObj.newBottom = _vm->_scenery->_toRedrawBottom;
} else {
animObj.newLeft = animObj.lastLeft;
animObj.newTop = animObj.lastTop;
animObj.newRight = animObj.lastRight;
animObj.newBottom = animObj.lastBottom;
}
} else if (animObj.lastLeft != -1) {
animObj.needRedraw = 1;
animObj.newLeft = animObj.lastLeft;
animObj.newTop = animObj.lastTop;
animObj.newRight = animObj.lastRight;
animObj.newBottom = animObj.lastBottom;
}
animData.redrawLayer = animData.layer;
animData.redrawFrame = animData.frame;
animData.redrawAnimation = animData.animation;
if (animObj.needRedraw || !animData.isStatic) {
minOrder = MIN(minOrder, animData.order);
maxOrder = MAX(maxOrder, animData.order);
}
}
// Restore dirty areas
for (int i = 0; i < numAnims; i++) {
Mult_Object &animObj = *_renderObjs[i];
if (!animObj.needRedraw || (animObj.lastLeft == -1))
continue;
animObj.lastLeft = -1;
int maxleft = MAX(animObj.newLeft, _animLeft);
int maxtop = MAX(animObj.newTop, _animTop);
int right = animObj.newRight - maxleft + 1;
int bottom = animObj.newBottom - maxtop + 1;
if ((right <= 0) || (bottom <= 0))
continue;
_vm->_draw->_sourceSurface = Draw::kAnimSurface;
_vm->_draw->_destSurface = Draw::kBackSurface;
_vm->_draw->_spriteLeft = maxleft - _animLeft;
_vm->_draw->_spriteTop = maxtop - _animTop;
_vm->_draw->_spriteRight = right;
_vm->_draw->_spriteBottom = bottom;
_vm->_draw->_destSpriteX = maxleft;
_vm->_draw->_destSpriteY = maxtop;
_vm->_draw->_transparency = 0;
_vm->_draw->spriteOperation(DRAW_BLITSURF);
}
// Figure out the correct drawing order
for (int i = minOrder; i <= maxOrder; i++) {
for (int j = 0; j < numAnims; j++) {
Mult_Object &animObj = *_renderObjs[j];
Mult_AnimData &animData = *(animObj.pAnimData);
if (animData.order == i)
if (animObj.needRedraw || !animData.isStatic)
orderArray[orderArrayPos++] = j;
}
}
// Put the goblins in correct drawing order as well
if (_vm->_goblin->_gobsCount >= 0) {
for (int i = 0; i < orderArrayPos; i++) {
Mult_Object &animObj1 = *_renderObjs[orderArray[i]];
Mult_AnimData &animData1 = *(animObj1.pAnimData);
if (!animObj1.goblinStates)
continue;
for (int j = i+1; j < orderArrayPos; j++) {
Mult_Object &animObj2 = *_renderObjs[orderArray[j]];
Mult_AnimData &animData2 = *(animObj2.pAnimData);
if ((animData1.order == animData2.order) &&
((animObj1.newBottom > animObj2.newBottom) ||
((animObj1.newBottom == animObj2.newBottom) &&
(animData1.isBusy == 1))))
SWAP(orderArray[i], orderArray[j]);
}
}
}
// Update view
for (int i = 0; i < orderArrayPos; i++) {
Mult_Object &animObj1 = *_renderObjs[orderArray[i]];
Mult_AnimData &animData1 = *(animObj1.pAnimData);
if (!animObj1.needRedraw) {
if (!animData1.isStatic) {
for (int j = 0; j < orderArrayPos; j++) {
Mult_Object &animObj2 = *_renderObjs[orderArray[j]];
if (!animObj2.needRedraw)
continue;
if ((animObj1.newRight >= animObj2.newLeft) &&
(animObj2.newRight >= animObj1.newLeft) &&
(animObj1.newBottom >= animObj2.newTop) &&
(animObj2.newBottom >= animObj1.newTop)) {
_vm->_scenery->_toRedrawLeft = animObj2.newLeft;
_vm->_scenery->_toRedrawRight = animObj2.newRight;
_vm->_scenery->_toRedrawTop = animObj2.newTop;
_vm->_scenery->_toRedrawBottom = animObj2.newBottom;
_vm->_scenery->updateAnim(animData1.layer, animData1.frame,
animData1.animation, 12, *animObj1.pPosX, *animObj1.pPosY, 1);
_vm->_scenery->updateStatic(animData1.order + 1);
}
}
}
} else {
if (animData1.isStatic != 0) {
_vm->_scenery->_toRedrawLeft = animObj1.newLeft;
_vm->_scenery->_toRedrawRight = animObj1.newRight;
_vm->_scenery->_toRedrawTop = animObj1.newTop;
_vm->_scenery->_toRedrawBottom = animObj1.newBottom;
} else {
_vm->_scenery->updateAnim(animData1.layer, animData1.frame,
animData1.animation, 10, *animObj1.pPosX, *animObj1.pPosY, 1);
if (_vm->_scenery->_toRedrawLeft != -12345) {
animObj1.lastLeft = _vm->_scenery->_toRedrawLeft;
animObj1.lastRight = _vm->_scenery->_toRedrawRight;
animObj1.lastTop = _vm->_scenery->_toRedrawTop;
animObj1.lastBottom = _vm->_scenery->_toRedrawBottom;
} else {
animObj1.lastLeft = -1;
}
}
_vm->_scenery->updateStatic(animData1.order + 1);
}
}
// Advance animations
for (int i = 0; i < numAnims; i++) {
Mult_Object &animObj = *_renderObjs[i];
Mult_AnimData &animData = *(animObj.pAnimData);
if (animData.isStatic)
continue;
if ((animData.animType == 7) && (animData.newState != -1)) {
animData.layer = animData.newState;
animData.frame = 0;
animData.newState = -1;
animData.isPaused = 0;
}
if (animData.isPaused)
continue;
if (animData.maxTick == animObj.tick) {
animObj.tick = 0;
if ((animData.animType < 100) || (_vm->_goblin->_gobsCount < 0))
newCycleAnim(animObj);
else if (animData.animType == 100)
_vm->_goblin->moveAdvance(&animObj, nullptr, 0, 0);
else if (animData.animType == 101)
_vm->_goblin->animate(&animObj);
} else
animObj.tick++;
}
// Find intersections
for (int i = 0; i < numAnims; i++) {
Mult_Object &animObj1 = *_renderObjs[i];
Mult_AnimData &animData1 = *(animObj1.pAnimData);
if (animData1.isStatic || (animObj1.lastLeft == -1))
continue;
for (int j = 0; j < numAnims; j++) {
Mult_Object &animObj2 = *_renderObjs[j];
Mult_AnimData &animData2 = *(animObj2.pAnimData);
if (i == j)
continue;
if ((animData2.isStatic) || (animObj2.lastLeft == -1))
continue;
if ((animObj2.lastRight >= animObj1.lastLeft) &&
(animObj2.lastLeft <= animObj1.lastRight) &&
(animObj2.lastBottom >= animObj1.lastTop) &&
(animObj2.lastTop <= animObj1.lastBottom))
animData2.intersected = animIndices[i];
}
}
}
void Mult_v2::playImd(const char *imdFile, Mult::Mult_ImdKey &key, int16 dir,
int16 startFrame) {
VideoPlayer::Properties props;
if (_vm->_draw->_renderFlags & 0x100) {
props.x = VAR(55);
props.y = VAR(56);
}
if (key.imdFile == -1) {
_vm->_vidPlayer->closeVideo();
return;
}
props.flags = (key.flags >> 8) & 0xFF;
if (props.flags & 0x20)
props.flags = (props.flags & 0x9F) | 0x80;
props.palStart = key.palStart;
props.palEnd = key.palEnd;
props.palFrame = key.palFrame;
props.lastFrame = key.lastFrame;
if ((props.palFrame != -1) && (props.lastFrame != -1))
if ((props.lastFrame - props.palFrame) < props.startFrame)
if (!(key.flags & 0x4000)) {
_vm->_vidPlayer->closeVideo();
return;
}
_vm->_vidPlayer->evaluateFlags(props);
int slot;
if ((slot = _vm->_vidPlayer->openVideo(true, imdFile, props)) < 0)
return;
if (props.palFrame == -1)
props.palFrame = 0;
if (props.lastFrame == -1)
props.lastFrame = _vm->_vidPlayer->getFrameCount() - 1;
uint32 baseFrame = startFrame % (props.lastFrame - props.palFrame + 1);
props.endFrame = props.lastFrame;
props.startFrame = baseFrame + props.palFrame;
props.lastFrame = baseFrame + props.palFrame;
props.flags &= 0x7F;
debugC(2, kDebugVideo, "Playing mult video \"%s\" @ %d+%d, frame %d, "
"paletteCmd %d (%d - %d; %d), flags %X", imdFile,
props.x, props.y, props.startFrame,
props.palCmd, props.palStart, props.palEnd, props.endFrame, props.flags);
_vm->_vidPlayer->play(slot, props);
}
void Mult_v2::advanceObjects(int16 index) {
int16 frame;
bool stop = false;
frame = _multData->animKeysFrames[index];
if (frame == -1)
return;
for (int i = 0; i < 4; i++) {
int obj = _multData->animObjs[index][i];
if ((obj != -1) && (obj != 1024)) {
int keyIndex = _multData->animKeysIndices[index][i];
int count = _multData->animKeysCount[i];
for (int j = keyIndex; j < count; j++) {
Mult_AnimKey &key = _multData->animKeys[i][j];
Mult_Object &animObj = _objects[obj];
Mult_AnimData &animData = *(animObj.pAnimData);
if (key.frame > frame)
break;
else if (key.frame < frame)
continue;
if (key.layer > -1) {
int16 layer;
int16 layers;
int16 curAnim;
_multData->animKeysIndices[index][i] = j;
*(animObj.pPosX) = key.posX;
*(animObj.pPosY) = key.posY;
animData.frame = 0;
animData.animType = 1;
animData.isStatic = 0;
animData.isPaused = 0;
animData.maxTick = 0;
animData.animation = 0;
animObj.tick = 0;
curAnim = _multData->animIndices[0];
layer = key.layer;
layers = _vm->_scenery->getAnimLayersCount(curAnim);
while (layer >= layers) {
layer -= layers;
animData.animation++;
curAnim = _multData->animIndices[animData.animation];
layers = _vm->_scenery->getAnimLayersCount(curAnim);
}
animData.layer = layer;
animData.animation =
_multData->animIndices[animData.animation];
break;
} else
animData.isStatic = 1;
}
}
if (obj != -1) {
int keyIndex = _multData->imdKeysIndices[index][i];
int count = _multData->imdKeysCount[i];
for (int j = keyIndex; j < count; j++) {
Mult_ImdKey &key1 = _multData->imdKeys[i][j];
Mult_ImdKey &key2 = _multData->imdKeys[i][j - 1];
if (key1.frame > frame)
break;
else if (key1.frame < frame)
continue;
if (key1.imdFile != -1) {
_multData->imdIndices[0] = -1;
_multData->imdIndices[1] = -1;
_multData->imdIndices[2] = -1;
_multData->imdIndices[3] = -1;
if ((_multData->animDirection == 1) || (key2.imdFile == -1))
_multData->imdIndices[i] = j;
else if (_multData->animKeysStopFrames[index] == frame)
_multData->imdIndices[i] = -1;
else
_multData->imdIndices[i] = j - 1;
} else
_multData->imdIndices[i] = -1;
}
}
if (_multData->imdIndices[i] != -1) {
int fileN;
char *imdFile;
int dir;
int startFrame;
Mult_ImdKey &key = _multData->imdKeys[i][_multData->imdIndices[i]];
fileN = -key.imdFile - 2;
if (fileN < 0)
return;
imdFile = _multData->imdFiles + fileN * 14;
dir = _multData->animDirection;
startFrame = frame - key.frame;
if ((dir != 1) && (--startFrame < 0))
startFrame = 0;
playImd(imdFile, key, dir, startFrame);
}
}
doSoundAnim(stop, frame);
WRITE_VAR(22, frame);
if (_multData->animKeysStopFrames[index] == frame) {
_multData->imdIndices[0] = -1;
_multData->imdIndices[1] = -1;
_multData->imdIndices[2] = -1;
_multData->imdIndices[3] = -1;
frame = -1;
for (int i = 0; i < 4; i++) {
int obj = _multData->animObjs[index][i];
if ((obj == -1) || (obj == 1024))
continue;
Mult_Object &animObj = _objects[_multData->animObjs[index][i]];
animObj.pAnimData->animType = animObj.pAnimData->animTypeBak;
}
} else if (_multData->animDirection == 1)
frame++;
else
frame--;
_multData->animKeysFrames[index] = frame;
WRITE_VAR(18 + index, frame);
}
void Mult_v2::advanceAllObjects() {
Mult_Data *multData = _multData;
for (int i = 0; i < 8; i++) {
if (_multDatas[i]) {
_multData = _multDatas[i];
for (int j = 0; j < 4; j++)
advanceObjects(j);
}
}
_multData = multData;
}
} // End of namespace Gob