Files
scummvm/engines/agos/vga.cpp
T
Coen Rampen 0fafb0ee19 AGOS: Fix Waxworks crash when playing digital SFX
Waxworks DOS has both digital and limited MIDI SFX. The game scripts use two
different opcodes for trigging digital and MIDI SFX; if a sound effect has both
a digital and a MIDI version, both opcodes are triggered. When digital SFX are
active, ScummVM would try to handle both the digital and the MIDI opcode by
playing a digital SFX. This would crash the game if the MIDI SFX ID was invalid
as a digital SFX ID. Strangely enough, this bug also seems to affect the Amiga
version. Apparently both opcodes are still in the scripts of this version, even
though it does not support MIDI at all AFAIK.

This commit fixes the issue by only playing the MIDI SFX if digital SFX are
turned off and ignoring them otherwise.
2022-08-05 22:16:43 +02:00

1399 lines
33 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/>.
*
*/
// Video script opcodes for Simon1/Simon2
#include "agos/agos.h"
#include "agos/intern.h"
#include "agos/sound.h"
#include "agos/vga.h"
#include "common/debug-channels.h"
#include "common/endian.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "graphics/surface.h"
namespace AGOS {
// Opcode tables
void AGOSEngine::setupVideoOpcodes(VgaOpcodeProc *op) {
op[1] = &AGOSEngine::vc1_fadeOut;
op[2] = &AGOSEngine::vc2_call;
op[3] = &AGOSEngine::vc3_loadSprite;
op[4] = &AGOSEngine::vc4_fadeIn;
op[5] = &AGOSEngine::vc5_ifEqual;
op[6] = &AGOSEngine::vc6_ifObjectHere;
op[7] = &AGOSEngine::vc7_ifObjectNotHere;
op[8] = &AGOSEngine::vc8_ifObjectIsAt;
op[9] = &AGOSEngine::vc9_ifObjectStateIs;
op[10] = &AGOSEngine::vc10_draw;
op[12] = &AGOSEngine::vc12_delay;
op[13] = &AGOSEngine::vc13_addToSpriteX;
op[14] = &AGOSEngine::vc14_addToSpriteY;
op[15] = &AGOSEngine::vc15_sync;
op[16] = &AGOSEngine::vc16_waitSync;
op[18] = &AGOSEngine::vc18_jump;
op[20] = &AGOSEngine::vc20_setRepeat;
op[21] = &AGOSEngine::vc21_endRepeat;
op[23] = &AGOSEngine::vc23_setPriority;
op[24] = &AGOSEngine::vc24_setSpriteXY;
op[25] = &AGOSEngine::vc25_halt_sprite;
op[26] = &AGOSEngine::vc26_setSubWindow;
op[27] = &AGOSEngine::vc27_resetSprite;
op[29] = &AGOSEngine::vc29_stopAllSounds;
op[30] = &AGOSEngine::vc30_setFrameRate;
op[31] = &AGOSEngine::vc31_setWindow;
op[33] = &AGOSEngine::vc33_setMouseOn;
op[34] = &AGOSEngine::vc34_setMouseOff;
op[35] = &AGOSEngine::vc35_clearWindow;
op[36] = &AGOSEngine::vc36_setWindowImage;
op[38] = &AGOSEngine::vc38_ifVarNotZero;
op[39] = &AGOSEngine::vc39_setVar;
op[40] = &AGOSEngine::vc40_scrollRight;
op[41] = &AGOSEngine::vc41_scrollLeft;
op[42] = &AGOSEngine::vc42_delayIfNotEQ;
op[43] = &AGOSEngine::vc43_ifBitSet;
op[44] = &AGOSEngine::vc44_ifBitClear;
op[45] = &AGOSEngine::vc45_setSpriteX;
op[46] = &AGOSEngine::vc46_setSpriteY;
op[47] = &AGOSEngine::vc47_addToVar;
op[49] = &AGOSEngine::vc49_setBit;
op[50] = &AGOSEngine::vc50_clearBit;
op[51] = &AGOSEngine::vc51_enableBox;
op[52] = &AGOSEngine::vc52_playSound;
op[55] = &AGOSEngine::vc55_moveBox;
}
void AGOSEngine_Elvira1::setupVideoOpcodes(VgaOpcodeProc *op) {
op[1] = &AGOSEngine::vc1_fadeOut;
op[2] = &AGOSEngine::vc2_call;
op[3] = &AGOSEngine::vc3_loadSprite;
op[4] = &AGOSEngine::vc4_fadeIn;
op[5] = &AGOSEngine::vc5_ifEqual;
op[6] = &AGOSEngine::vc6_ifObjectHere;
op[7] = &AGOSEngine::vc7_ifObjectNotHere;
op[8] = &AGOSEngine::vc8_ifObjectIsAt;
op[9] = &AGOSEngine::vc9_ifObjectStateIs;
op[10] = &AGOSEngine::vc10_draw;
op[13] = &AGOSEngine::vc12_delay;
op[14] = &AGOSEngine::vc13_addToSpriteX;
op[15] = &AGOSEngine::vc14_addToSpriteY;
op[16] = &AGOSEngine::vc15_sync;
op[17] = &AGOSEngine::vc16_waitSync;
op[18] = &AGOSEngine::vc17_waitEnd;
op[19] = &AGOSEngine::vc18_jump;
op[20] = &AGOSEngine::vc19_loop;
op[21] = &AGOSEngine::vc20_setRepeat;
op[22] = &AGOSEngine::vc21_endRepeat;
op[23] = &AGOSEngine::vc22_setPalette;
op[24] = &AGOSEngine::vc23_setPriority;
op[25] = &AGOSEngine::vc24_setSpriteXY;
op[26] = &AGOSEngine::vc25_halt_sprite;
op[27] = &AGOSEngine::vc26_setSubWindow;
op[28] = &AGOSEngine::vc27_resetSprite;
op[29] = &AGOSEngine::vc28_playSFX;
op[30] = &AGOSEngine::vc29_stopAllSounds;
op[31] = &AGOSEngine::vc30_setFrameRate;
op[32] = &AGOSEngine::vc31_setWindow;
op[33] = &AGOSEngine::vc32_saveScreen;
op[34] = &AGOSEngine::vc33_setMouseOn;
op[35] = &AGOSEngine::vc34_setMouseOff;
op[38] = &AGOSEngine::vc35_clearWindow;
op[40] = &AGOSEngine::vc36_setWindowImage;
op[41] = &AGOSEngine::vc37_pokePalette;
op[51] = &AGOSEngine::vc38_ifVarNotZero;
op[52] = &AGOSEngine::vc39_setVar;
op[53] = &AGOSEngine::vc40_scrollRight;
op[54] = &AGOSEngine::vc41_scrollLeft;
op[56] = &AGOSEngine::vc42_delayIfNotEQ;
}
void AGOSEngine::setupVgaOpcodes() {
memset(_vga_opcode_table, 0, sizeof(_vga_opcode_table));
switch (getGameType()) {
case GType_PN:
case GType_ELVIRA1:
case GType_ELVIRA2:
case GType_WW:
case GType_SIMON1:
case GType_SIMON2:
case GType_FF:
case GType_PP:
setupVideoOpcodes(_vga_opcode_table);
break;
default:
error("setupVgaOpcodes: Unknown game");
}
}
// VGA Script parser
void AGOSEngine::runVgaScript() {
for (;;) {
uint opcode;
if (DebugMan.isDebugChannelEnabled(kDebugVGAOpcode)) {
if (_vcPtr != (const byte *)&_vcGetOutOfCode) {
debugN("%.5d %.5X: %5d %4d ", _vgaTickCounter, (unsigned int)(_vcPtr - _curVgaFile1), _vgaCurSpriteId, _vgaCurZoneNum);
dumpVideoScript(_vcPtr, true);
}
}
if (getGameType() == GType_SIMON2 || getGameType() == GType_FF || getGameType() == GType_PP) {
opcode = *_vcPtr++;
} else {
opcode = READ_BE_UINT16(_vcPtr);
_vcPtr += 2;
}
if (opcode == 0)
return;
if (opcode >= _numVideoOpcodes || !_vga_opcode_table[opcode])
error("runVgaScript: Invalid VGA opcode '%d' encountered", opcode);
(this->*_vga_opcode_table[opcode]) ();
}
}
bool AGOSEngine::ifObjectHere(uint16 a) {
Item *item;
CHECK_BOUNDS(a, _objectArray);
item = _objectArray[a];
if (item == nullptr)
return true;
return me()->parent == item->parent;
}
bool AGOSEngine::ifObjectAt(uint16 a, uint16 b) {
Item *item_a, *item_b;
CHECK_BOUNDS(a, _objectArray);
CHECK_BOUNDS(b, _objectArray);
item_a = _objectArray[a];
item_b = _objectArray[b];
if (item_a == nullptr || item_b == nullptr)
return true;
return derefItem(item_a->parent) == item_b;
}
bool AGOSEngine::ifObjectState(uint16 a, int16 b) {
Item *item;
CHECK_BOUNDS(a, _objectArray);
item = _objectArray[a];
if (item == nullptr)
return true;
return item->state == b;
}
void AGOSEngine::dirtyBackGround() {
AnimTable *animTable = _screenAnim1;
while (animTable->srcPtr) {
if (animTable->id == _vgaCurSpriteId && animTable->zoneNum == _vgaCurZoneNum) {
animTable->windowNum |= 0x8000;
break;
}
animTable++;
}
}
VgaSprite *AGOSEngine::findCurSprite() {
VgaSprite *vsp = _vgaSprites;
while (vsp->id) {
if (vsp->id == _vgaCurSpriteId && vsp->zoneNum == _vgaCurZoneNum)
break;
vsp++;
}
return vsp;
}
bool AGOSEngine::isSpriteLoaded(uint16 id, uint16 zoneNum) {
VgaSprite *vsp = _vgaSprites;
while (vsp->id) {
if (vsp->id == id && vsp->zoneNum == zoneNum)
return true;
vsp++;
}
return false;
}
bool AGOSEngine::getBitFlag(uint bit) {
uint16 *bits = &_bitArray[bit / 16];
return (*bits & (1 << (bit & 15))) != 0;
}
void AGOSEngine::setBitFlag(uint bit, bool value) {
uint16 *bits = &_bitArray[bit / 16];
*bits = (*bits & ~(1 << (bit & 15))) | (value << (bit & 15));
}
int AGOSEngine::vcReadVarOrWord() {
if (getGameType() == GType_PN || getGameType() == GType_ELVIRA1) {
return vcReadNextWord();
} else {
int16 var = vcReadNextWord();
if (var < 0)
var = vcReadVar(-var);
return var;
}
}
uint AGOSEngine::vcReadNextWord(bool forceLERead) {
uint a;
a = readUint16Wrapper(_vcPtr);
if (forceLERead)
a = FROM_BE_16(a);
_vcPtr += 2;
return a;
}
uint AGOSEngine::vcReadNextByte() {
return *_vcPtr++;
}
uint AGOSEngine::vcReadVar(uint var) {
assert(var < _numVars);
return (uint16)_variableArrayPtr[var];
}
void AGOSEngine::vcWriteVar(uint var, int16 value) {
assert(var < _numVars);
_variableArrayPtr[var] = value;
}
void AGOSEngine::vcSkipNextInstruction() {
static const byte opcodeParamLenPN[] = {
0, 6, 2, 10, 6, 4, 2, 2,
4, 4, 8, 2, 0, 2, 2, 2,
0, 2, 2, 2, 0, 4, 2, 2,
2, 8, 0, 10, 0, 8, 0, 2,
2, 0, 0, 0, 0, 2, 4, 2,
4, 4, 0, 0, 2, 2, 2, 4,
4, 0, 18, 2, 4, 4, 4, 0,
4
};
static const byte opcodeParamLenElvira1[] = {
0, 6, 2, 10, 6, 4, 2, 2,
4, 4, 8, 2, 0, 2, 2, 2,
2, 2, 2, 2, 0, 4, 2, 2,
2, 8, 0, 10, 0, 8, 0, 2,
2, 0, 0, 0, 0, 2, 4, 2,
4, 4, 0, 0, 2, 2, 2, 4,
4, 0, 18, 2, 4, 4, 4, 0,
4
};
static const byte opcodeParamLenWW[] = {
0, 6, 2, 10, 6, 4, 2, 2,
4, 4, 8, 2, 2, 2, 2, 2,
2, 2, 2, 0, 4, 2, 2, 2,
8, 0, 10, 0, 8, 0, 2, 2,
0, 0, 0, 4, 4, 4, 2, 4,
4, 4, 4, 2, 2, 4, 2, 2,
2, 2, 2, 2, 2, 4, 6, 6,
0, 0, 0, 0, 2, 2, 0, 0,
};
static const byte opcodeParamLenSimon1[] = {
0, 6, 2, 10, 6, 4, 2, 2,
4, 4, 10, 0, 2, 2, 2, 2,
2, 0, 2, 0, 4, 2, 4, 2,
8, 0, 10, 0, 8, 0, 2, 2,
4, 0, 0, 4, 4, 2, 2, 4,
4, 4, 4, 2, 2, 2, 2, 4,
0, 2, 2, 2, 2, 4, 6, 6,
0, 0, 0, 0, 2, 6, 0, 0,
};
static const byte opcodeParamLenSimon2[] = {
0, 6, 2, 12, 6, 4, 2, 2,
4, 4, 9, 0, 1, 2, 2, 2,
2, 0, 2, 0, 4, 2, 4, 2,
7, 0, 10, 0, 8, 0, 2, 2,
4, 0, 0, 4, 4, 2, 2, 4,
4, 4, 4, 2, 2, 2, 2, 4,
0, 2, 2, 2, 2, 4, 6, 6,
2, 0, 6, 6, 4, 6, 0, 0,
0, 0, 4, 4, 4, 4, 4, 0,
4, 2, 2
};
static const byte opcodeParamLenFeebleFiles[] = {
0, 6, 2, 12, 6, 4, 2, 2,
4, 4, 9, 0, 1, 2, 2, 2,
2, 0, 2, 0, 4, 2, 4, 2,
7, 0, 10, 0, 8, 0, 2, 2,
4, 0, 0, 4, 4, 2, 2, 4,
4, 4, 4, 2, 2, 2, 2, 4,
0, 2, 2, 2, 6, 6, 6, 6,
2, 0, 6, 6, 4, 6, 0, 0,
0, 0, 4, 4, 4, 4, 4, 0,
4, 2, 2, 4, 6, 6, 0, 0,
6, 4, 2, 6, 0
};
uint16 opcode;
if (getGameType() == GType_FF || getGameType() == GType_PP) {
opcode = vcReadNextByte();
_vcPtr += opcodeParamLenFeebleFiles[opcode];
} else if (getGameType() == GType_SIMON2) {
opcode = vcReadNextByte();
_vcPtr += opcodeParamLenSimon2[opcode];
} else if (getGameType() == GType_SIMON1) {
opcode = vcReadNextWord();
_vcPtr += opcodeParamLenSimon1[opcode];
} else if (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) {
opcode = vcReadNextWord();
_vcPtr += opcodeParamLenWW[opcode];
} else if (getGameType() == GType_ELVIRA1) {
opcode = vcReadNextWord();
_vcPtr += opcodeParamLenElvira1[opcode];
} else {
opcode = vcReadNextWord();
_vcPtr += opcodeParamLenPN[opcode];
}
debugCN(kDebugVGAOpcode, "; skipped\n");
}
// VGA Script commands
void AGOSEngine::vc1_fadeOut() {
/* dummy opcode */
_vcPtr += 6;
}
void AGOSEngine::vc2_call() {
uint16 num;
byte *old_file_1, *old_file_2;
if (getGameType() == GType_ELVIRA2) {
num = vcReadNextWord();
} else {
num = vcReadVarOrWord();
}
old_file_1 = _curVgaFile1;
old_file_2 = _curVgaFile2;
setImage(num, true);
_curVgaFile1 = old_file_1;
_curVgaFile2 = old_file_2;
}
void AGOSEngine::vc3_loadSprite() {
uint16 windowNum, zoneNum, palette, vgaSpriteId;
int16 x, y;
byte *old_file_1;
windowNum = vcReadNextWord();
if (getGameType() == GType_SIMON1 && windowNum == 3) {
_window3Flag = 1;
}
if (getGameType() == GType_SIMON2 || getGameType() == GType_FF || getGameType() == GType_PP) {
zoneNum = vcReadNextWord();
vgaSpriteId = vcReadNextWord();
} else {
vgaSpriteId = vcReadNextWord();
zoneNum = (getGameType() == GType_PN) ? 0 : vgaSpriteId / 100;
}
x = vcReadNextWord();
y = vcReadNextWord();
palette = vcReadNextWord();
old_file_1 = _curVgaFile1;
animate(windowNum, zoneNum, vgaSpriteId, x, y, palette, true);
_curVgaFile1 = old_file_1;
}
void AGOSEngine::vc4_fadeIn() {
/* dummy opcode */
_vcPtr += 6;
}
void AGOSEngine::vc5_ifEqual() {
uint16 var;
if (getGameType() == GType_PP)
var = vcReadVarOrWord();
else
var = vcReadNextWord();
uint16 value = vcReadNextWord();
if (vcReadVar(var) != value)
vcSkipNextInstruction();
}
void AGOSEngine::vc6_ifObjectHere() {
if (!ifObjectHere(vcReadNextWord())) {
vcSkipNextInstruction();
}
}
void AGOSEngine::vc7_ifObjectNotHere() {
if (ifObjectHere(vcReadNextWord()))
vcSkipNextInstruction();
}
void AGOSEngine::vc8_ifObjectIsAt() {
uint16 a = vcReadNextWord();
uint16 b = vcReadNextWord();
if (!ifObjectAt(a, b))
vcSkipNextInstruction();
}
void AGOSEngine::vc9_ifObjectStateIs() {
uint16 a = vcReadNextWord();
uint16 b = vcReadNextWord();
if (!ifObjectState(a, b))
vcSkipNextInstruction();
}
byte *AGOSEngine::vc10_uncompressFlip(const byte *src, uint16 w, uint16 h) {
w *= 8;
byte *dst, *dstPtr, *srcPtr;
byte color;
int8 cur = -0x80;
uint i, w_cur = w;
dstPtr = _videoBuf1 + w;
do {
dst = dstPtr;
uint h_cur = h;
if (cur == -0x80)
cur = *src++;
for (;;) {
if (cur >= 0) {
/* rle_same */
color = *src++;
do {
*dst = color;
dst += w;
if (!--h_cur) {
if (--cur < 0)
cur = -0x80;
else
src--;
goto next_line;
}
} while (--cur >= 0);
} else {
/* rle_diff */
do {
*dst = *src++;
dst += w;
if (!--h_cur) {
if (++cur == 0)
cur = -0x80;
goto next_line;
}
} while (++cur != 0);
}
cur = *src++;
}
next_line:
dstPtr++;
} while (--w_cur);
srcPtr = dstPtr = _videoBuf1 + w;
do {
dst = dstPtr;
for (i = 0; i != w; ++i) {
byte b = srcPtr[i];
b = (b >> 4) | (b << 4);
*--dst = b;
}
srcPtr += w;
dstPtr += w;
} while (--h);
return _videoBuf1;
}
byte *AGOSEngine::vc10_flip(const byte *src, uint16 w, uint16 h) {
byte *dstPtr;
uint i;
if (getFeatures() & GF_32COLOR) {
w *= 16;
dstPtr = _videoBuf1 + w;
do {
byte *dst = dstPtr;
for (i = 0; i != w; ++i) {
*--dst = src[i];
}
src += w;
dstPtr += w;
} while (--h);
} else {
w *= 8;
dstPtr = _videoBuf1 + w;
do {
byte *dst = dstPtr;
for (i = 0; i != w; ++i) {
byte b = src[i];
b = (b >> 4) | (b << 4);
*--dst = b;
}
src += w;
dstPtr += w;
} while (--h);
}
return _videoBuf1;
}
void AGOSEngine::vc10_draw() {
uint16 palette, x, y, flags;
int16 image;
image = (int16)vcReadNextWord();
palette = 0;
if (getGameType() == GType_FF || getGameType() == GType_PP) {
palette = _vcPtr[0];
_vcPtr += 2;
} else if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) {
palette = _vcPtr[1];
_vcPtr += 2;
}
x = (int16)vcReadNextWord();
y = (int16)vcReadNextWord();
if (getGameType() == GType_SIMON2 || getGameType() == GType_FF || getGameType() == GType_PP) {
flags = vcReadNextByte();
} else {
flags = vcReadNextWord();
}
drawImage_init(image, palette, x, y, flags);
}
void AGOSEngine::drawImage_init(int16 image, uint16 palette, int16 x, int16 y, uint16 flags) {
if (image == 0)
return;
byte *src;
uint width, height;
VC10_state state;
state.image = image;
if (state.image < 0)
state.image = vcReadVar(-state.image);
state.palette = (getGameType() == GType_PN) ? 0 : palette * 16;
state.paletteMod = 0;
state.x = x - _scrollX;
state.y = y - _scrollY;
state.flags = flags;
src = _curVgaFile2 + state.image * 8;
state.srcPtr = _curVgaFile2 + (getPlatform() == Common::kPlatformPC98 ? READ_LE_UINT32(src) : readUint32Wrapper(src));
if (getGameType() == GType_FF || getGameType() == GType_PP) {
width = READ_LE_UINT16(src + 6);
height = READ_LE_UINT16(src + 4) & 0x7FFF;
flags = src[5];
} else {
width = (getPlatform() == Common::kPlatformPC98 ? READ_LE_UINT16(src + 6) : READ_BE_UINT16(src + 6)) / 16;
height = src[5];
flags = src[4];
}
if (height == 0 || width == 0)
return;
if (DebugMan.isDebugChannelEnabled(kDebugImageDump))
dumpSingleBitmap(_vgaCurZoneNum, state.image, state.srcPtr, width, height,
state.palette);
state.width = state.draw_width = width; /* cl */
state.height = state.draw_height = height; /* ch */
state.depack_cont = -0x80;
state.x_skip = 0; /* colums to skip = bh */
state.y_skip = 0; /* rows to skip = bl */
if (getFeatures() & GF_PLANAR) {
if (getGameType() == GType_PN) {
state.srcPtr = convertAmigaImage(&state, ((state.flags & (kDFCompressed | kDFCompressedFlip)) != 0));
} else
state.srcPtr = convertAmigaImage(&state, ((flags & 0x80) != 0));
// converted planar clip is already uncompressed
if (state.flags & kDFCompressedFlip) {
state.flags &= ~kDFCompressedFlip;
state.flags |= kDFFlip;
}
if (state.flags & kDFCompressed) {
state.flags &= ~kDFCompressed;
}
} else if (getGameType() == GType_FF || getGameType() == GType_PP) {
if (flags & 0x80) {
state.flags |= kDFCompressed;
}
} else {
if (flags & 0x80 && !(state.flags & kDFCompressedFlip)) {
if (state.flags & kDFFlip) {
state.flags &= ~kDFFlip;
state.flags |= kDFCompressedFlip;
} else {
state.flags |= kDFCompressed;
}
}
}
if (getPlatform() == Common::kPlatformPC98)
convertPC98Image(state);
uint maxWidth = (getGameType() == GType_FF || getGameType() == GType_PP) ? 640 : 20;
if ((getGameType() == GType_SIMON2 || getGameType() == GType_FF) && width > maxWidth) {
horizontalScroll(&state);
return;
}
if (getGameType() == GType_FF && height > 480) {
verticalScroll(&state);
return;
}
if (getGameType() != GType_FF && getGameType() != GType_PP) {
if (state.flags & kDFCompressedFlip) {
state.srcPtr = vc10_uncompressFlip(state.srcPtr, width, height);
} else if (state.flags & kDFFlip) {
state.srcPtr = vc10_flip(state.srcPtr, width, height);
}
}
drawImage(&state);
}
void AGOSEngine::checkOnStopTable() {
VgaSleepStruct *vfs = _onStopTable, *vfs_tmp;
while (vfs->ident != 0) {
if (vfs->ident == _vgaCurSpriteId) {
VgaSprite *vsp = findCurSprite();
animate(vsp->windowNum, vsp->zoneNum, vfs->id, vsp->x, vsp->y, vsp->palette, true);
vfs_tmp = vfs;
do {
memcpy(vfs_tmp, vfs_tmp + 1, sizeof(VgaSleepStruct));
vfs_tmp++;
} while (vfs_tmp->ident != 0);
} else {
vfs++;
}
}
}
void AGOSEngine::vc11_onStop() {
uint16 id = vcReadNextWord();
VgaSleepStruct *vfs = _onStopTable;
while (vfs->ident)
vfs++;
vfs->ident = _vgaCurSpriteId;
vfs->codePtr = _vcPtr;
vfs->id = id;
vfs->zoneNum = _vgaCurZoneNum;
}
void AGOSEngine::vc12_delay() {
uint16 num;
if (getGameType() == GType_FF || getGameType() == GType_PP) {
num = vcReadNextByte();
} else if (getGameType() == GType_SIMON2) {
num = vcReadNextByte() * _frameCount;
} else {
num = vcReadVarOrWord() * _frameCount;
}
num += _vgaBaseDelay;
addVgaEvent(num, ANIMATE_EVENT, _vcPtr, _vgaCurSpriteId, _vgaCurZoneNum);
_vcPtr = (byte *)&_vcGetOutOfCode;
}
void AGOSEngine::vc13_addToSpriteX() {
VgaSprite *vsp = findCurSprite();
vsp->x += (int16)vcReadNextWord();
vsp->windowNum |= 0x8000;
dirtyBackGround();
_vgaSpriteChanged++;
}
void AGOSEngine::vc14_addToSpriteY() {
VgaSprite *vsp = findCurSprite();
vsp->y += (int16)vcReadNextWord();
vsp->windowNum |= 0x8000;
dirtyBackGround();
_vgaSpriteChanged++;
}
void AGOSEngine::vc15_sync() {
VgaSleepStruct *vfs = _waitSyncTable, *vfs_tmp;
uint16 id;
if (getGameType() == GType_PN)
id = _vgaCurSpriteId;
else
id = vcReadNextWord();
while (vfs->ident != 0) {
if (vfs->ident == id) {
addVgaEvent(_vgaBaseDelay, ANIMATE_EVENT, vfs->codePtr, vfs->id, vfs->zoneNum);
vfs_tmp = vfs;
do {
memcpy(vfs_tmp, vfs_tmp + 1, sizeof(VgaSleepStruct));
vfs_tmp++;
} while (vfs_tmp->ident != 0);
} else {
vfs++;
}
}
_lastVgaWaitFor = id;
/* clear a wait event */
if (id == _vgaWaitFor)
_vgaWaitFor = 0;
}
void AGOSEngine::vc16_waitSync() {
VgaSleepStruct *vfs = _waitSyncTable;
while (vfs->ident)
vfs++;
vfs->ident = vcReadNextWord();
vfs->codePtr = _vcPtr;
vfs->id = _vgaCurSpriteId;
vfs->zoneNum = _vgaCurZoneNum;
_vcPtr = (byte *)&_vcGetOutOfCode;
}
void AGOSEngine::checkWaitEndTable() {
VgaSleepStruct *vfs = _waitEndTable, *vfs_tmp;
while (vfs->ident != 0) {
if (vfs->ident == _vgaCurSpriteId) {
addVgaEvent(_vgaBaseDelay, ANIMATE_EVENT, vfs->codePtr, vfs->id, vfs->zoneNum);
vfs_tmp = vfs;
do {
memcpy(vfs_tmp, vfs_tmp + 1, sizeof(VgaSleepStruct));
vfs_tmp++;
} while (vfs_tmp->ident != 0);
} else {
vfs++;
}
}
}
void AGOSEngine::vc17_waitEnd() {
uint16 id = vcReadNextWord();
uint16 zoneNum = (getGameType() == GType_PN) ? 0 : id / 100;
VgaSleepStruct *vfs = _waitEndTable;
while (vfs->ident)
vfs++;
if (isSpriteLoaded(id, zoneNum)) {
vfs->ident = id;
vfs->codePtr = _vcPtr;
vfs->id = _vgaCurSpriteId;
vfs->zoneNum = _vgaCurZoneNum;
_vcPtr = (byte *)&_vcGetOutOfCode;
}
}
void AGOSEngine::vc18_jump() {
int16 offs = vcReadNextWord();
_vcPtr += offs;
}
void AGOSEngine::vc19_loop() {
uint16 count;
byte *b, *bb;
bb = _curVgaFile1;
b = _curVgaFile1 + READ_BE_UINT16(bb + 10);
b += 20;
count = READ_BE_UINT16(&((VgaFile1Header_Common *) b)->animationCount);
b = bb + READ_BE_UINT16(&((VgaFile1Header_Common *) b)->animationTable);
while (count--) {
if (READ_BE_UINT16(&((AnimationHeader_WW *) b)->id) == _vgaCurSpriteId)
break;
b += sizeof(AnimationHeader_WW);
}
assert(READ_BE_UINT16(&((AnimationHeader_WW *) b)->id) == _vgaCurSpriteId);
_vcPtr = _curVgaFile1 + READ_BE_UINT16(&((AnimationHeader_WW *) b)->scriptOffs);
}
void AGOSEngine::vc20_setRepeat() {
// Sets counter used by the endRepeat opcode below.
uint16 a = vcReadNextWord();
WRITE_LE_UINT16(const_cast<byte *>(_vcPtr), a);
_vcPtr += 2;
}
void AGOSEngine::vc21_endRepeat() {
int16 a = vcReadNextWord();
const byte *tmp = _vcPtr + a;
if (getGameType() == GType_SIMON2 || getGameType() == GType_FF || getGameType() == GType_PP)
tmp += 3;
else
tmp += 4;
uint16 val = READ_LE_UINT16(tmp);
if (val != 0) {
// Decrement counter
WRITE_LE_UINT16(const_cast<byte *>(tmp), val - 1);
_vcPtr = tmp + 2;
}
}
static const uint8 iconPalette[64] = {
0x00, 0x00, 0x00,
0x77, 0x77, 0x55,
0x55, 0x00, 0x00,
0x77, 0x00, 0x00,
0x22, 0x00, 0x00,
0x00, 0x11, 0x00,
0x11, 0x22, 0x11,
0x22, 0x33, 0x22,
0x44, 0x55, 0x44,
0x33, 0x44, 0x00,
0x11, 0x33, 0x00,
0x00, 0x11, 0x44,
0x77, 0x44, 0x00,
0x66, 0x22, 0x00,
0x00, 0x22, 0x66,
0x77, 0x55, 0x00,
};
void AGOSEngine::vc22_setPalette() {
byte *offs, *palptr, *src;
uint16 b, num;
b = vcReadNextWord();
// PC EGA version of Personal Nightmare uses standard EGA palette
if (getGameType() == GType_PN && (getFeatures() & GF_EGA))
return;
num = 16;
palptr = _displayPalette;
_bottomPalette = 1;
if (getGameType() == GType_PN) {
if (b > 128) {
b -= 128;
palptr = _displayPalette + 3 * 16;
}
} else if (getGameType() == GType_ELVIRA1) {
if (b >= 1000) {
b -= 1000;
_bottomPalette = 0;
} else {
const byte extraColors[19 * 3] = {
40, 0, 0, 24, 24, 16, 48, 48, 40,
0, 0, 0, 16, 0, 0, 8, 8, 0,
48, 24, 0, 56, 40, 0, 0, 0, 24,
8, 16, 24, 24, 32, 40, 16, 24, 0,
24, 8, 0, 16, 16, 0, 40, 40, 32,
32, 32, 24, 40, 0, 0, 24, 24, 16,
48, 48, 40
};
num = 13;
for (int i = 0; i < 19; i++) {
palptr[(13 + i) * 3 + 0] = extraColors[i * 3 + 0] * 4;
palptr[(13 + i) * 3 + 1] = extraColors[i * 3 + 1] * 4;
palptr[(13 + i) * 3 + 2] = extraColors[i * 3 + 2] * 4;
}
}
}
if (getGameType() == GType_ELVIRA2 && getPlatform() == Common::kPlatformAtariST) {
// Custom palette used for icon area
palptr = &_displayPalette[13 * 3 * 16];
for (uint8 c = 0; c < 16; c++) {
palptr[0] = iconPalette[c * 3 + 0] * 2;
palptr[1] = iconPalette[c * 3 + 1] * 2;
palptr[2] = iconPalette[c * 3 + 2] * 2;
palptr += 3;
};
palptr = _displayPalette;
}
offs = _curVgaFile1 + READ_BE_UINT16(_curVgaFile1 + 6);
src = offs + b * 32;
do {
uint16 color = READ_BE_UINT16(src);
palptr[0] = ((color & 0xf00) >> 8) * 32;
palptr[1] = ((color & 0x0f0) >> 4) * 32;
palptr[2] = ((color & 0x00f) >> 0) * 32;
palptr += 3;
src += 2;
} while (--num);
_paletteFlag = 2;
_vgaSpriteChanged++;
}
void AGOSEngine::vc23_setPriority() {
VgaSprite *vsp = findCurSprite(), *vus2;
uint16 pri = vcReadNextWord();
VgaSprite bak;
if (vsp->id == 0)
return;
memcpy(&bak, vsp, sizeof(bak));
bak.priority = pri;
bak.windowNum |= 0x8000;
vus2 = vsp;
if (vsp != _vgaSprites && pri < vsp[-1].priority) {
do {
vsp--;
} while (vsp != _vgaSprites && pri < vsp[-1].priority);
do {
memcpy(vus2, vus2 - 1, sizeof(VgaSprite));
} while (--vus2 != vsp);
memcpy(vus2, &bak, sizeof(VgaSprite));
} else if (vsp[1].id != 0 && pri >= vsp[1].priority) {
do {
vsp++;
} while (vsp[1].id != 0 && pri >= vsp[1].priority);
do {
memcpy(vus2, vus2 + 1, sizeof(VgaSprite));
} while (++vus2 != vsp);
memcpy(vus2, &bak, sizeof(VgaSprite));
} else {
vsp->priority = pri;
}
_vgaSpriteChanged++;
}
void AGOSEngine::vc24_setSpriteXY() {
VgaSprite *vsp = findCurSprite();
if (getGameType() == GType_ELVIRA2) {
vsp->image = vcReadNextWord();
} else {
vsp->image = vcReadVarOrWord();
}
vsp->x += (int16)vcReadNextWord();
vsp->y += (int16)vcReadNextWord();
if (getGameType() == GType_SIMON2 || getGameType() == GType_FF || getGameType() == GType_PP) {
vsp->flags = vcReadNextByte();
} else {
vsp->flags = vcReadNextWord();
}
vsp->windowNum |= 0x8000;
dirtyBackGround();
_vgaSpriteChanged++;
}
void AGOSEngine::vc25_halt_sprite() {
checkWaitEndTable();
checkOnStopTable();
VgaSprite *vsp = findCurSprite();
while (vsp->id != 0) {
memcpy(vsp, vsp + 1, sizeof(VgaSprite));
vsp++;
}
_vcPtr = (byte *)&_vcGetOutOfCode;
dirtyBackGround();
_vgaSpriteChanged++;
}
void AGOSEngine::vc26_setSubWindow() {
uint16 *as = &_videoWindows[vcReadNextWord() * 4]; // number
as[0] = vcReadNextWord(); // x
as[1] = vcReadNextWord(); // y
as[2] = vcReadNextWord(); // width
as[3] = vcReadNextWord(); // height
}
void AGOSEngine::vc27_resetSprite() {
VgaSprite bak, *vsp;
VgaSleepStruct *vfs;
VgaTimerEntry *vte, *vte2;
_videoLockOut |= 8;
_lastVgaWaitFor = 0;
bak.reset();
vsp = _vgaSprites;
while (vsp->id) {
// For animated heart in Elvira 2
if (getGameType() == GType_ELVIRA2 && vsp->id == 100) {
memcpy(&bak, vsp, sizeof(VgaSprite));
}
vsp->id = 0;
vsp++;
}
if (bak.id != 0)
memcpy(_vgaSprites, &bak, sizeof(VgaSprite));
vfs = _waitEndTable;
while (vfs->ident) {
vfs->ident = 0;
vfs++;
}
vfs = _waitSyncTable;
while (vfs->ident) {
vfs->ident = 0;
vfs++;
}
vfs = _onStopTable;
while (vfs->ident) {
vfs->ident = 0;
vfs++;
}
vte = _vgaTimerList;
while (vte->delay) {
// Skip the animateSprites event in earlier games
if (vte->type == ANIMATE_INT) {
vte++;
// For animated heart in Elvira 2
} else if (getGameType() == GType_ELVIRA2 && vte->id == 100) {
vte++;
} else {
vte2 = vte;
while (vte2->delay) {
memcpy(vte2, vte2 + 1, sizeof(VgaTimerEntry));
vte2++;
}
}
}
if (_videoLockOut & 0x20) {
AnimTable *animTable = _screenAnim1;
while (animTable->srcPtr) {
animTable->srcPtr = nullptr;
animTable++;
}
}
if (getGameType() == GType_SIMON2 || getGameType() == GType_FF || getGameType() == GType_PP)
vcWriteVar(254, 0);
// Stop any OmniTV video that is currently been played
if (getGameType() == GType_FF || getGameType() == GType_PP)
setBitFlag(42, true);
_videoLockOut &= ~8;
}
void AGOSEngine::vc28_playSFX() {
uint16 sound = vcReadNextWord();
uint16 chans = vcReadNextWord();
uint16 freq = vcReadNextWord();
uint16 flags = vcReadNextWord();
debug(0, "vc28_playSFX: (sound %d, channels %d, frequency %d, flags %d)", sound, chans, freq, flags);
// Waxworks uses 2 opcodes to play SFX: vc28 for digital SFX and vc52
// for MIDI SFX. If a sound effect has both a MIDI and a digital
// version, both opcodes are triggered. Only one of them should play
// a sound effect.
playSfx(sound, freq, flags, getGameType() == GType_WW);
}
void AGOSEngine::vc29_stopAllSounds() {
if (getGameType() != GType_PP)
_sound->stopVoice();
stopAllSfx();
}
void AGOSEngine::vc30_setFrameRate() {
_frameCount = vcReadNextWord();
}
void AGOSEngine::vc31_setWindow() {
_windowNum = vcReadNextWord();
}
void AGOSEngine::vc32_saveScreen() {
if (getGameType() == GType_PN) {
Graphics::Surface *screen = getBackendSurface();
byte *dst = getBackGround();
byte *src = (byte *)screen->getPixels();
for (int i = 0; i < _screenHeight; i++) {
memcpy(dst, src, _screenWidth);
dst += _backGroundBuf->pitch;
src += screen->pitch;
}
updateBackendSurface();
} else {
uint16 xoffs = _videoWindows[4 * 4 + 0] * 16;
uint16 yoffs = _videoWindows[4 * 4 + 1];
uint16 width = _videoWindows[4 * 4 + 2] * 16;
uint16 height = _videoWindows[4 * 4 + 3];
byte *dst = (byte *)_backGroundBuf->getBasePtr(xoffs, yoffs);
byte *src = (byte *)_window4BackScn->getPixels();
uint16 srcWidth = _videoWindows[4 * 4 + 2] * 16;
for (; height > 0; height--) {
memcpy(dst, src, width);
dst += _backGroundBuf->pitch;
src += srcWidth;
}
}
}
void AGOSEngine::vc33_setMouseOn() {
if (_mouseHideCount != 0) {
_mouseHideCount = 1;
if (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) {
// Set mouse palette
_displayPalette[65 * 3 + 0] = 48 * 4;
_displayPalette[65 * 3 + 1] = 48 * 4;
_displayPalette[65 * 3 + 2] = 40 * 4;
_paletteFlag = 1;
}
mouseOn();
}
}
void AGOSEngine::vc34_setMouseOff() {
mouseOff();
_mouseHideCount = 200;
_leftButtonDown = false;
}
void AGOSEngine::clearVideoBackGround(uint16 num, uint16 color) {
const uint16 *vlut = &_videoWindows[num * 4];
byte *dst = (byte *)_backGroundBuf->getBasePtr(vlut[0] * 16, vlut[1]);
for (uint h = 0; h < vlut[3]; h++) {
memset(dst, color, vlut[2] * 16);
dst += _backGroundBuf->pitch;
}
}
void AGOSEngine::clearVideoWindow(uint16 num, uint16 color) {
if (getGameType() == GType_ELVIRA1) {
if (num == 2 || num == 6)
return;
} else if (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) {
if (num != 4 && num < 10)
return;
} else if (getGameType() == GType_SIMON1) {
if (num != 4)
return;
}
if (getGameType() == GType_ELVIRA1 && num == 3) {
Graphics::Surface *screen = getBackendSurface();
byte *dst = (byte *)screen->getPixels();
for (int i = 0; i < _screenHeight; i++) {
memset(dst, color, _screenWidth);
dst += screen->pitch;
}
clearHiResTextLayer();
updateBackendSurface();
} else {
const uint16 *vlut = &_videoWindows[num * 4];
uint16 xoffs = (vlut[0] - _videoWindows[16]) * 16;
uint16 yoffs = (vlut[1] - _videoWindows[17]);
uint16 dstWidth = _videoWindows[18] * 16;
// TODO: Is there any known connection between dstWidth and the pitch
// of the _window4BackScn Surface? If so, we might be able to pass
// yoffs as proper y parameter to getBasePtr.
byte *dst = (byte *)_window4BackScn->getBasePtr(xoffs, 0) + yoffs * dstWidth;
setMoveRect(0, 0, vlut[2] * 16, vlut[3]);
for (uint h = 0; h < vlut[3]; h++) {
memset(dst, color, vlut[2] * 16);
dst += dstWidth;
}
_window4Flag = 1;
}
}
void AGOSEngine::vc35_clearWindow() {
uint16 num = vcReadNextWord();
uint16 color = vcReadNextWord();
// Clear video background
if (getGameType() == GType_ELVIRA1) {
if (num == 2 || num == 6)
return;
} else if (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) {
if (num != 4 && num < 10)
return;
} else if (getGameType() == GType_SIMON1) {
if (num != 4)
return;
}
// Clear video window
clearVideoWindow(num, color);
clearVideoBackGround(num, color);
_vgaSpriteChanged++;
}
void AGOSEngine::vc36_setWindowImage() {
_displayFlag = 0;
uint16 vga_res = vcReadNextWord();
uint16 windowNum = vcReadNextWord();
setWindowImage(windowNum, vga_res);
}
void AGOSEngine::vc37_pokePalette() {
uint16 offs = vcReadNextWord();
uint16 color = vcReadNextWord();
// PC EGA version of Personal Nightmare uses standard EGA palette
if (getGameType() == GType_PN && (getFeatures() & GF_EGA))
return;
byte *palptr = _displayPalette + offs * 3;
palptr[0] = ((color & 0xf00) >> 8) * 32;
palptr[1] = ((color & 0x0f0) >> 4) * 32;
palptr[2] = ((color & 0x00f) >> 0) * 32;
if (!(_videoLockOut & 0x20)) {
_paletteFlag = 1;
_displayFlag++;
}
}
void AGOSEngine::vc38_ifVarNotZero() {
uint16 var;
if (getGameType() == GType_PP)
var = vcReadVarOrWord();
else
var = vcReadNextWord();
if (vcReadVar(var) == 0)
vcSkipNextInstruction();
}
void AGOSEngine::vc39_setVar() {
uint16 var;
if (getGameType() == GType_PP)
var = vcReadVarOrWord();
else
var = vcReadNextWord();
int16 value = vcReadNextWord();
vcWriteVar(var, value);
}
void AGOSEngine::vc40_scrollRight() {
uint16 var = vcReadNextWord();
int16 value = vcReadVar(var) + vcReadNextWord();
if (getGameType() == GType_SIMON2 && var == 15 && !getBitFlag(80)) {
if ((_scrollCount < 0) || (_scrollCount == 0 && _scrollFlag == 0)) {
_scrollCount = 0;
if (value - _scrollX >= 30) {
_scrollCount = MIN(20, _scrollXMax - _scrollX);
addVgaEvent(6, SCROLL_EVENT, nullptr, 0, 0);
}
}
}
vcWriteVar(var, value);
}
void AGOSEngine::vc41_scrollLeft() {
uint16 var = vcReadNextWord();
int16 value = vcReadVar(var) - vcReadNextWord();
if (getGameType() == GType_SIMON2 && var == 15 && !getBitFlag(80)) {
if ((_scrollCount > 0) || (_scrollCount == 0 && _scrollFlag == 0)) {
_scrollCount = 0;
if ((uint16)(value - _scrollX) < 11) {
_scrollCount = -MIN(20, (int)_scrollX);
addVgaEvent(6, SCROLL_EVENT, nullptr, 0, 0);
}
}
}
vcWriteVar(var, value);
}
void AGOSEngine::vc42_delayIfNotEQ() {
uint16 val = vcReadVar(vcReadNextWord());
if (val != vcReadNextWord()) {
addVgaEvent(_frameCount + 1, ANIMATE_EVENT, _vcPtr - 4, _vgaCurSpriteId, _vgaCurZoneNum);
_vcPtr = (byte *)&_vcGetOutOfCode;
}
}
} // End of namespace AGOS