Files
scummvm/engines/agos/script_s1.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

577 lines
14 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/>.
*
*/
#include "common/system.h"
#include "common/localization.h"
#include "graphics/palette.h"
#include "agos/agos.h"
#include "agos/intern.h"
#include "agos/sound.h"
namespace AGOS {
#define OPCODE(x) _OPCODE(AGOSEngine_Simon1, x)
void AGOSEngine_Simon1::setupOpcodes() {
static const OpcodeEntrySimon1 opcodes[] = {
/* 00 */
OPCODE(o_invalid),
OPCODE(o_at),
OPCODE(o_notAt),
OPCODE(o_invalid),
/* 04 */
OPCODE(o_invalid),
OPCODE(o_carried),
OPCODE(o_notCarried),
OPCODE(o_isAt),
/* 08 */
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_zero),
/* 12 */
OPCODE(o_notZero),
OPCODE(o_eq),
OPCODE(o_notEq),
OPCODE(o_gt),
/* 16 */
OPCODE(o_lt),
OPCODE(o_eqf),
OPCODE(o_notEqf),
OPCODE(o_ltf),
/* 20 */
OPCODE(o_gtf),
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_chance),
/* 24 */
OPCODE(o_invalid),
OPCODE(o_isRoom),
OPCODE(o_isObject),
OPCODE(o_state),
/* 28 */
OPCODE(o_oflag),
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_destroy),
/* 32 */
OPCODE(o_invalid),
OPCODE(o_place),
OPCODE(o_invalid),
OPCODE(o_invalid),
/* 36 */
OPCODE(o_copyff),
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_invalid),
/* 40 */
OPCODE(o_invalid),
OPCODE(o_clear),
OPCODE(o_let),
OPCODE(o_add),
/* 44 */
OPCODE(o_sub),
OPCODE(o_addf),
OPCODE(o_subf),
OPCODE(o_mul),
/* 48 */
OPCODE(o_div),
OPCODE(o_mulf),
OPCODE(o_divf),
OPCODE(o_mod),
/* 52 */
OPCODE(o_modf),
OPCODE(o_random),
OPCODE(o_invalid),
OPCODE(o_goto),
/* 56 */
OPCODE(o_oset),
OPCODE(o_oclear),
OPCODE(o_putBy),
OPCODE(o_inc),
/* 60 */
OPCODE(o_dec),
OPCODE(o_setState),
OPCODE(o_print),
OPCODE(o_message),
/* 64 */
OPCODE(o_msg),
OPCODE(oww_addTextBox),
OPCODE(oww_setShortText),
OPCODE(oww_setLongText),
/* 68 */
OPCODE(o_end),
OPCODE(o_done),
OPCODE(oww_printLongText),
OPCODE(o_process),
/* 72 */
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_invalid),
/* 76 */
OPCODE(o_when),
OPCODE(o_if1),
OPCODE(o_if2),
OPCODE(o_isCalled),
/* 80 */
OPCODE(o_is),
OPCODE(o_invalid),
OPCODE(o_debug),
OPCODE(oe1_rescan),
/* 84 */
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_comment),
/* 88 */
OPCODE(o_haltAnimation),
OPCODE(o_restartAnimation),
OPCODE(o_getParent),
OPCODE(o_getNext),
/* 92 */
OPCODE(o_getChildren),
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_invalid),
/* 96 */
OPCODE(o_picture),
OPCODE(o_loadZone),
OPCODE(os1_animate),
OPCODE(oe1_stopAnimate),
/* 100 */
OPCODE(o_killAnimate),
OPCODE(o_defWindow),
OPCODE(o_window),
OPCODE(o_cls),
/* 104 */
OPCODE(o_closeWindow),
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_addBox),
/* 108 */
OPCODE(o_delBox),
OPCODE(o_enableBox),
OPCODE(o_disableBox),
OPCODE(o_moveBox),
/* 112 */
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_doIcons),
OPCODE(o_isClass),
/* 116 */
OPCODE(o_setClass),
OPCODE(o_unsetClass),
OPCODE(o_invalid),
OPCODE(o_waitSync),
/* 120 */
OPCODE(o_sync),
OPCODE(o_defObj),
OPCODE(o_invalid),
OPCODE(o_invalid),
/* 124 */
OPCODE(o_invalid),
OPCODE(o_here),
OPCODE(o_doClassIcons),
OPCODE(o_playTune),
/* 128 */
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_setAdjNoun),
OPCODE(o_invalid),
/* 132 */
OPCODE(o_saveUserGame),
OPCODE(o_loadUserGame),
OPCODE(o_invalid),
OPCODE(os1_pauseGame),
/* 136 */
OPCODE(o_copysf),
OPCODE(o_restoreIcons),
OPCODE(o_freezeZones),
OPCODE(o_placeNoIcons),
/* 140 */
OPCODE(o_clearTimers),
OPCODE(o_setDollar),
OPCODE(o_isBox),
OPCODE(oe2_doTable),
/* 144 */
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_invalid),
/* 148 */
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(oe2_storeItem),
/* 152 */
OPCODE(oe2_getItem),
OPCODE(oe2_bSet),
OPCODE(oe2_bClear),
OPCODE(oe2_bZero),
/* 156 */
OPCODE(oe2_bNotZero),
OPCODE(oe2_getOValue),
OPCODE(oe2_setOValue),
OPCODE(o_invalid),
/* 160 */
OPCODE(oe2_ink),
OPCODE(os1_screenTextBox),
OPCODE(os1_screenTextMsg),
OPCODE(os1_playEffect),
/* 164 */
OPCODE(oe2_getDollar2),
OPCODE(oe2_isAdjNoun),
OPCODE(oe2_b2Set),
OPCODE(oe2_b2Clear),
/* 168 */
OPCODE(oe2_b2Zero),
OPCODE(oe2_b2NotZero),
OPCODE(o_invalid),
OPCODE(o_invalid),
/* 172 */
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(o_invalid),
OPCODE(oww_lockZones),
/* 176 */
OPCODE(oww_unlockZones),
OPCODE(os1_screenTextPObj),
OPCODE(os1_getPathPosn),
OPCODE(os1_scnTxtLongText),
/* 180 */
OPCODE(os1_mouseOn),
OPCODE(os1_mouseOff),
OPCODE(os1_loadBeard),
OPCODE(os1_unloadBeard),
/* 184 */
OPCODE(os1_unloadZone),
OPCODE(os1_loadStrings),
OPCODE(os1_unfreezeZones),
OPCODE(os1_specialFade),
};
_opcodesSimon1 = opcodes;
_numOpcodes = 188;
}
void AGOSEngine_Simon1::executeOpcode(int opcode) {
OpcodeProcSimon1 op = _opcodesSimon1[opcode].proc;
(this->*op) ();
}
// -----------------------------------------------------------------------
// Simon 1 Opcodes
// -----------------------------------------------------------------------
void AGOSEngine_Simon1::os1_animate() {
// 98: animate
uint16 vgaSpriteId = getVarOrWord();
uint16 windowNum = getVarOrByte();
int16 x = getVarOrWord();
int16 y = getVarOrWord();
uint16 palette = (getVarOrWord() & 15);
if (getFeatures() & GF_TALKIE && vgaSpriteId >= 400) {
_lastVgaWaitFor = 0;
}
_videoLockOut |= 0x40;
animate(windowNum, vgaSpriteId / 100, vgaSpriteId, x, y, palette);
_videoLockOut &= ~0x40;
}
void AGOSEngine_Simon1::os1_pauseGame() {
// 135: pause game
_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
Common::KeyCode keyYes, keyNo;
Common::getLanguageYesNo(_language, keyYes, keyNo);
while (!shouldQuit()) {
delay(1);
if (_keyPressed.keycode == keyYes)
quitGame();
else if (_keyPressed.keycode == keyNo)
break;
}
_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
}
void AGOSEngine_Simon1::os1_screenTextBox() {
// 161: setup text
TextLocation *tl = getTextLocation(getVarOrByte());
tl->x = getVarOrWord();
tl->y = getVarOrByte();
tl->width = getVarOrWord();
}
void AGOSEngine_Simon1::os1_screenTextMsg() {
// 162: print string
uint vgaSpriteId = getVarOrByte();
uint color = getVarOrByte();
uint stringId = getNextStringID();
const byte *stringPtr = nullptr;
uint speechId = 0;
TextLocation *tl;
if (stringId != 0xFFFF)
stringPtr = getStringPtrByID(stringId);
if (getFeatures() & GF_TALKIE) {
if (getGameType() == GType_FF || getGameType() == GType_PP)
speechId = (uint16)getVarOrWord();
else
speechId = (uint16)getNextWord();
}
if (getGameType() == GType_FF || getGameType() == GType_PP)
vgaSpriteId = 1;
tl = getTextLocation(vgaSpriteId);
if (_speech && speechId != 0)
playSpeech(speechId, vgaSpriteId);
if (((getGameType() == GType_SIMON2 && (getFeatures() & GF_TALKIE)) || getGameType() == GType_FF) &&
speechId == 0) {
stopAnimateSimon2(2, vgaSpriteId + 2);
}
// WORKAROUND: Several strings in the French version of Simon the Sorcerer 1 set the incorrect width,
// causing crashes, or glitches in subtitles. See bug #6014 for example.
if (getGameType() == GType_SIMON1 && _language == Common::FR_FRA) {
if ((getFeatures() & GF_TALKIE) && stringId == 33219)
tl->width = 96;
if (!(getFeatures() & GF_TALKIE) && stringId == 33245)
tl->width = 96;
}
if (stringPtr != nullptr && stringPtr[0] != 0 && (speechId == 0 || _subtitles))
printScreenText(vgaSpriteId, color, (const char *)stringPtr, tl->x, tl->y, tl->width);
}
void AGOSEngine_Simon1::os1_playEffect() {
// 163: play sound
uint16 soundId = getVarOrWord();
if (getGameId() == GID_SIMON1DOS)
playSfx(soundId, 0, 0);
else
_sound->playEffects(soundId);
}
void AGOSEngine_Simon1::os1_screenTextPObj() {
// 177: inventory descriptions
uint vgaSpriteId = getVarOrByte();
uint color = getVarOrByte();
SubObject *subObject = (SubObject *)findChildOfType(getNextItemPtr(), kObjectType);
if (getFeatures() & GF_TALKIE) {
if (subObject != nullptr && subObject->objectFlags & kOFVoice) {
uint offs = getOffsetOfChild2Param(subObject, kOFVoice);
playSpeech(subObject->objectFlagValue[offs], vgaSpriteId);
} else if (subObject != nullptr && subObject->objectFlags & kOFNumber) {
uint offs = getOffsetOfChild2Param(subObject, kOFNumber);
playSpeech(subObject->objectFlagValue[offs] + 3550, vgaSpriteId);
}
}
if (subObject != nullptr && subObject->objectFlags & kOFText && _subtitles) {
const char *stringPtr = (const char *)getStringPtrByID(subObject->objectFlagValue[0]);
TextLocation *tl = getTextLocation(vgaSpriteId);
char buf[256];
int j, k;
if (subObject->objectFlags & kOFNumber) {
if (_language == Common::HE_ISR) {
j = subObject->objectFlagValue[getOffsetOfChild2Param(subObject, kOFNumber)];
k = (j % 10) * 10;
k += j / 10;
if (!(j % 10))
sprintf(buf,"0%d%s", k, stringPtr);
else
sprintf(buf,"%d%s", k, stringPtr);
} else {
sprintf(buf,"%d%s", subObject->objectFlagValue[getOffsetOfChild2Param(subObject, kOFNumber)], stringPtr);
}
stringPtr = buf;
}
if (stringPtr != nullptr && stringPtr[0] != 0)
printScreenText(vgaSpriteId, color, stringPtr, tl->x, tl->y, tl->width);
}
}
void AGOSEngine_Simon1::os1_getPathPosn() {
// 178: path find
uint x = getVarOrWord();
uint y = getVarOrWord();
uint var_1 = getVarOrByte();
uint var_2 = getVarOrByte();
const uint16 *p;
uint i, j;
uint prev_i;
uint x_diff, y_diff;
uint best_i = 0, best_j = 0, best_dist = 0xFFFFFFFF;
uint maxPath = (getGameType() == GType_FF || getGameType() == GType_PP) ? 100 : 20;
if (getGameType() == GType_FF || getGameType() == GType_PP) {
x += _scrollX;
y += _scrollY;
} else if (getGameType() == GType_SIMON2) {
x += _scrollX * 8;
}
int end = (getGameType() == GType_FF) ? 9999 : 999;
prev_i = maxPath + 1 - readVariable(12);
for (i = maxPath; i != 0; --i) {
p = (const uint16 *)_pathFindArray[maxPath - i];
if (!p)
continue;
for (j = 0; readUint16Wrapper(&p[0]) != end; j++, p += 2) {
x_diff = ABS((int16)(readUint16Wrapper(&p[0]) - x));
y_diff = ABS((int16)(readUint16Wrapper(&p[1]) - 12 - y));
if (x_diff < y_diff) {
x_diff /= 4;
y_diff *= 4;
}
x_diff += y_diff /= 4;
if ((x_diff < best_dist) || ((x_diff == best_dist) && (prev_i == i))) {
best_dist = x_diff;
best_i = maxPath + 1 - i;
best_j = j;
}
}
}
writeVariable(var_1, best_i);
writeVariable(var_2, best_j);
}
void AGOSEngine_Simon1::os1_scnTxtLongText() {
// 179: conversation responses and room descriptions
uint vgaSpriteId = getVarOrByte();
uint color = getVarOrByte();
uint stringId = getVarOrByte();
uint speechId = 0;
TextLocation *tl;
const char *stringPtr = (const char *)getStringPtrByID(_longText[stringId]);
if (getFeatures() & GF_TALKIE)
speechId = _longSound[stringId];
if (getGameType() == GType_FF || getGameType() == GType_PP)
vgaSpriteId = 1;
tl = getTextLocation(vgaSpriteId);
if (_speech && speechId != 0)
playSpeech(speechId, vgaSpriteId);
if (stringPtr != nullptr && stringPtr[0] != 0 && _subtitles)
printScreenText(vgaSpriteId, color, stringPtr, tl->x, tl->y, tl->width);
}
void AGOSEngine_Simon1::os1_mouseOn() {
// 180: force mouseOn
_mouseHideCount = 0;
}
void AGOSEngine_Simon1::os1_mouseOff() {
// 181: force mouseOff
scriptMouseOff();
}
void AGOSEngine_Simon1::os1_loadBeard() {
// 182: load beard
if (_beardLoaded == false) {
_beardLoaded = true;
_videoLockOut |= 0x8000;
loadVGABeardFile(328);
_videoLockOut &= ~0x8000;
}
}
void AGOSEngine_Simon1::os1_unloadBeard() {
// 183: unload beard
if (_beardLoaded == true) {
_beardLoaded = false;
_videoLockOut |= 0x8000;
loadVGABeardFile(23);
_videoLockOut &= ~0x8000;
}
}
void AGOSEngine_Simon1::os1_unloadZone() {
// 184: unload zone
uint a = getVarOrWord();
VgaPointersEntry *vpe = &_vgaBufferPointers[a];
vpe->sfxFile = nullptr;
vpe->vgaFile1 = nullptr;
vpe->vgaFile2 = nullptr;
}
void AGOSEngine_Simon1::os1_loadStrings() {
// 185: load sound files
_soundFileId = getVarOrWord();
if (getPlatform() == Common::kPlatformAmiga && (getFeatures() & GF_TALKIE)) {
char buf[13];
sprintf(buf, "%d%s", _soundFileId, "Effects");
_sound->readSfxFile(buf);
sprintf(buf, "%d%s", _soundFileId, "simon");
_sound->readVoiceFile(buf);
}
}
void AGOSEngine_Simon1::os1_unfreezeZones() {
// 186: freeze zone
unfreezeBottom();
}
void AGOSEngine_Simon1::os1_specialFade() {
// 187: fade to black
uint i;
for (i = 32; i != 0; --i) {
paletteFadeOut(_currentPalette, 32, 8);
paletteFadeOut(_currentPalette + 3 * 48, 144, 8);
paletteFadeOut(_currentPalette + 3 * 208, 48, 8);
_system->getPaletteManager()->setPalette(_currentPalette, 0, 256);
delay(5);
}
memcpy(_displayPalette, _currentPalette, sizeof(_currentPalette));
}
void AGOSEngine::scriptMouseOff() {
_videoLockOut |= 0x8000;
vc34_setMouseOff();
_videoLockOut &= ~0x8000;
}
} // End of namespace AGOS