diff options
-rw-r--r-- | engines/sci/detection_tables.h | 65 | ||||
-rw-r--r-- | engines/sci/engine/features.cpp | 74 | ||||
-rw-r--r-- | engines/sci/engine/features.h | 44 | ||||
-rw-r--r-- | engines/sci/engine/guest_additions.cpp | 866 | ||||
-rw-r--r-- | engines/sci/engine/guest_additions.h | 245 | ||||
-rw-r--r-- | engines/sci/engine/ksound.cpp | 18 | ||||
-rw-r--r-- | engines/sci/engine/object.h | 4 | ||||
-rw-r--r-- | engines/sci/engine/script_patches.cpp | 203 | ||||
-rw-r--r-- | engines/sci/engine/selector.cpp | 14 | ||||
-rw-r--r-- | engines/sci/engine/selector.h | 14 | ||||
-rw-r--r-- | engines/sci/engine/state.cpp | 7 | ||||
-rw-r--r-- | engines/sci/engine/state.h | 1 | ||||
-rw-r--r-- | engines/sci/engine/vm.cpp | 41 | ||||
-rw-r--r-- | engines/sci/engine/vm.h | 20 | ||||
-rw-r--r-- | engines/sci/module.mk | 1 | ||||
-rw-r--r-- | engines/sci/sci.cpp | 160 | ||||
-rw-r--r-- | engines/sci/sci.h | 24 | ||||
-rw-r--r-- | engines/sci/sound/audio32.cpp | 14 | ||||
-rw-r--r-- | engines/sci/sound/audio32.h | 7 | ||||
-rw-r--r-- | engines/sci/sound/soundcmd.cpp | 131 | ||||
-rw-r--r-- | engines/sci/sound/soundcmd.h | 51 |
21 files changed, 1677 insertions, 327 deletions
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index 02ec9aa55b..c37dd4fffb 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -724,7 +724,8 @@ static const struct ADGameDescription SciGameDescriptions[] = { #ifdef ENABLE_SCI32 #define GUIO_GK1_FLOPPY GUIO2(GUIO_NOSPEECH, \ GAMEOPTION_ORIGINAL_SAVELOAD) -#define GUIO_GK1_CD GUIO2(GAMEOPTION_ORIGINAL_SAVELOAD, \ +#define GUIO_GK1_CD GUIO3(GUIO_LINKSPEECHTOSFX, \ + GAMEOPTION_ORIGINAL_SAVELOAD, \ GAMEOPTION_HIGH_RESOLUTION_GRAPHICS) #define GUIO_GK1_MAC GUIO_GK1_FLOPPY @@ -840,13 +841,16 @@ static const struct ADGameDescription SciGameDescriptions[] = { #undef GUIO_GK1_CD #undef GUIO_GK1_MAC -#define GUIO_GK2_DEMO GUIO6(GUIO_NOSUBTITLES, \ +#define GUIO_GK2_DEMO GUIO7(GUIO_NOSUBTITLES, \ GUIO_NOMUSIC, \ + GUIO_NOSFX, \ GUIO_NOSPEECH, \ GUIO_NOMIDI, \ GUIO_NOLAUNCHLOAD, \ GUIO_NOASPECT) -#define GUIO_GK2 GUIO5(GUIO_NOSUBTITLES, \ +#define GUIO_GK2 GUIO7(GUIO_NOSUBTITLES, \ + GUIO_NOSFX, \ + GUIO_NOSPEECHVOLUME, \ GUIO_NOMIDI, \ GUIO_NOASPECT, \ GAMEOPTION_ORIGINAL_SAVELOAD, \ @@ -1134,8 +1138,10 @@ static const struct ADGameDescription SciGameDescriptions[] = { Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, #ifdef ENABLE_SCI32 -#define GUIO_HOYLE5 GUIO3(GUIO_NOMIDI, \ +#define GUIO_HOYLE5 GUIO5(GUIO_NOMIDI, \ GUIO_NOLAUNCHLOAD, \ + GUIO_LINKMUSICTOSFX, \ + GUIO_LINKSPEECHTOSFX, \ GUIO_NOASPECT) // Hoyle 5 (Hoyle Classic Games) - Windows demo @@ -1803,11 +1809,14 @@ static const struct ADGameDescription SciGameDescriptions[] = { #ifdef ENABLE_SCI32 -#define GUIO_KQ7_DEMO GUIO4(GUIO_NOSUBTITLES, \ - GUIO_NOSPEECH, \ +#define GUIO_KQ7_DEMO GUIO5(GUIO_NOSUBTITLES, \ GUIO_NOLAUNCHLOAD, \ + GUIO_LINKMUSICTOSFX, \ + GUIO_LINKSPEECHTOSFX, \ GUIO_NOASPECT) -#define GUIO_KQ7 GUIO2(GUIO_NOASPECT, \ +#define GUIO_KQ7 GUIO4(GUIO_NOASPECT, \ + GUIO_LINKMUSICTOSFX, \ + GUIO_LINKSPEECHTOSFX, \ GAMEOPTION_ORIGINAL_SAVELOAD) // King's Quest 7 - English Windows (from the King's Quest Collection) @@ -2591,7 +2600,8 @@ static const struct ADGameDescription SciGameDescriptions[] = { #ifdef ENABLE_SCI32 -#define GUIO_LSL6HIRES GUIO2(GUIO_NOASPECT, \ +#define GUIO_LSL6HIRES GUIO3(GUIO_NOASPECT, \ + GUIO_LINKSPEECHTOSFX, \ GAMEOPTION_ORIGINAL_SAVELOAD) // Larry 6 - English/German DOS CD - HIRES @@ -2874,7 +2884,9 @@ static const struct ADGameDescription SciGameDescriptions[] = { #ifdef ENABLE_SCI32 -#define GUIO_MOTHERGOOSEHIRES GUIO2(GUIO_NOASPECT, \ +#define GUIO_MOTHERGOOSEHIRES GUIO4(GUIO_NOSUBTITLES, \ + GUIO_NOASPECT, \ + GUIO_LINKSPEECHTOSFX, \ GAMEOPTION_ORIGINAL_SAVELOAD) // Mixed-Up Mother Goose Deluxe - English Windows/DOS CD (supplied by markcoolio in bug report #2723810) @@ -2907,9 +2919,10 @@ static const struct ADGameDescription SciGameDescriptions[] = { #ifdef ENABLE_SCI32 -#define GUIO_PHANTASMAGORIA_DEMO GUIO4(GUIO_NOSUBTITLES, \ +#define GUIO_PHANTASMAGORIA_DEMO GUIO5(GUIO_NOSUBTITLES, \ GUIO_NOASPECT, \ GUIO_NOLAUNCHLOAD, \ + GUIO_LINKSPEECHTOSFX, \ GAMEOPTION_ENABLE_BLACK_LINED_VIDEO) #define GUIO_PHANTASMAGORIA GUIO_PHANTASMAGORIA_DEMO #define GUIO_PHANTASMAGORIA_MAC GUIO_PHANTASMAGORIA_DEMO @@ -3348,7 +3361,8 @@ static const struct ADGameDescription SciGameDescriptions[] = { #define GUIO_PQ4_FLOPPY GUIO2(GUIO_NOSPEECH, \ GAMEOPTION_ORIGINAL_SAVELOAD) -#define GUIO_PQ4_CD GUIO2(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, \ +#define GUIO_PQ4_CD GUIO3(GUIO_LINKSPEECHTOSFX, \ + GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, \ GAMEOPTION_ORIGINAL_SAVELOAD) // Police Quest 4 - English DOS CD (from the Police Quest Collection) @@ -3394,11 +3408,16 @@ static const struct ADGameDescription SciGameDescriptions[] = { #undef GUIO_PQ4_FLOPPY #undef GUIO_PQ4_CD -#define GUIO_PQSWAT_DEMO GUIO4(GUIO_NOSUBTITLES, \ - GUIO_NOSPEECH, \ +#define GUIO_PQSWAT_DEMO GUIO6(GUIO_NOSUBTITLES, \ + GUIO_NOMIDI, \ + GUIO_LINKMUSICTOSFX, \ + GUIO_LINKSPEECHTOSFX, \ GUIO_NOASPECT, \ GUIO_NOLAUNCHLOAD) -#define GUIO_PQSWAT GUIO4(GUIO_NOMIDI, \ +#define GUIO_PQSWAT GUIO7(GUIO_NOSUBTITLES, \ + GUIO_NOMIDI, \ + GUIO_LINKMUSICTOSFX, \ + GUIO_LINKSPEECHTOSFX, \ GUIO_NOASPECT, \ GAMEOPTION_ORIGINAL_SAVELOAD, \ GAMEOPTION_ENABLE_BLACK_LINED_VIDEO) @@ -3797,7 +3816,8 @@ static const struct ADGameDescription SciGameDescriptions[] = { #define GUIO_QFG4_FLOPPY GUIO2(GUIO_NOSPEECH, \ GAMEOPTION_ORIGINAL_SAVELOAD) -#define GUIO_QFG4_CD GUIO1(GAMEOPTION_ORIGINAL_SAVELOAD) +#define GUIO_QFG4_CD GUIO2(GUIO_LINKSPEECHTOSFX, \ + GAMEOPTION_ORIGINAL_SAVELOAD) // Quest for Glory 4 1.1 Floppy - English DOS (supplied by markcool in bug report #2723852) // SCI interpreter version 2.000.000 (a guess?) @@ -3920,12 +3940,15 @@ static const struct ADGameDescription SciGameDescriptions[] = { #endif // ENABLE_SCI3_GAMES -#define GUIO_SHIVERS_DEMO GUIO5(GUIO_NOSUBTITLES, \ - GUIO_NOSPEECH, \ +#define GUIO_SHIVERS_DEMO GUIO6(GUIO_NOSUBTITLES, \ GUIO_NOMIDI, \ GUIO_NOLAUNCHLOAD, \ + GUIO_LINKSPEECHTOSFX, \ + GUIO_LINKMUSICTOSFX, \ GUIO_NOASPECT) -#define GUIO_SHIVERS GUIO4(GUIO_NOMIDI, \ +#define GUIO_SHIVERS GUIO6(GUIO_NOMIDI, \ + GUIO_LINKSPEECHTOSFX, \ + GUIO_LINKMUSICTOSFX, \ GUIO_NOASPECT, \ GAMEOPTION_ORIGINAL_SAVELOAD, \ GAMEOPTION_ENABLE_BLACK_LINED_VIDEO) @@ -4528,9 +4551,11 @@ static const struct ADGameDescription SciGameDescriptions[] = { #ifdef ENABLE_SCI32 -#define GUIO_SQ6_DEMO GUIO2(GUIO_NOLAUNCHLOAD, \ +#define GUIO_SQ6_DEMO GUIO3(GUIO_NOLAUNCHLOAD, \ + GUIO_LINKSPEECHTOSFX, \ GUIO_NOASPECT) -#define GUIO_SQ6 GUIO3(GUIO_NOASPECT, \ +#define GUIO_SQ6 GUIO4(GUIO_LINKSPEECHTOSFX, \ + GUIO_NOASPECT, \ GAMEOPTION_ORIGINAL_SAVELOAD, \ GAMEOPTION_ENABLE_BLACK_LINED_VIDEO) diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp index 40d380195d..1085fec85b 100644 --- a/engines/sci/engine/features.cpp +++ b/engines/sci/engine/features.cpp @@ -534,6 +534,80 @@ SciVersion GameFeatures::detectSci21KernelType() { } #endif +bool GameFeatures::supportsSpeechWithSubtitles() const { + switch (g_sci->getGameId()) { + case GID_SQ4: + case GID_FREDDYPHARKAS: + case GID_ECOQUEST: + case GID_LSL6: + case GID_LAURABOW2: + case GID_KQ6: +#ifdef ENABLE_SCI32 + // TODO: Hoyle5, SCI3 + case GID_GK1: + case GID_KQ7: + case GID_LSL6HIRES: + case GID_PQ4: + case GID_QFG4: + case GID_SQ6: + case GID_TORIN: +#endif + return true; + + default: + return false; + } +} + +bool GameFeatures::audioVolumeSyncUsesGlobals() const { + switch (g_sci->getGameId()) { + case GID_GK1: + case GID_GK2: + case GID_LSL6HIRES: + case GID_PHANTASMAGORIA: + case GID_TORIN: + // TODO: SCI3 + return true; + default: + return false; + } +} + +MessageTypeSyncStrategy GameFeatures::getMessageTypeSyncStrategy() const { + if (getSciVersion() < SCI_VERSION_1_1) { + return kMessageTypeSyncStrategyNone; + } + + if (getSciVersion() == SCI_VERSION_1_1 && g_sci->isCD()) { + return kMessageTypeSyncStrategyDefault; + } + +#ifdef ENABLE_SCI32 + switch (g_sci->getGameId()) { + // TODO: Hoyle5, SCI3 + case GID_GK1: + case GID_KQ7: + case GID_MOTHERGOOSEHIRES: + case GID_PHANTASMAGORIA: + case GID_PQ4: + case GID_QFG4: + case GID_TORIN: + return kMessageTypeSyncStrategyDefault; + + case GID_LSL6HIRES: + return kMessageTypeSyncStrategyLSL6Hires; + + case GID_SHIVERS: + return kMessageTypeSyncStrategyShivers; + + default: + break; + } +#endif + + return kMessageTypeSyncStrategyNone; +} + bool GameFeatures::autoDetectMoveCountType() { // Look up the script address reg_t addr = getDetectionAddr("Motion", SELECTOR(doit)); diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h index 8f84bbacad..044111a43e 100644 --- a/engines/sci/engine/features.h +++ b/engines/sci/engine/features.h @@ -40,6 +40,16 @@ enum PseudoMouseAbilityType { kPseudoMouseAbilityTrue }; +enum MessageTypeSyncStrategy { + kMessageTypeSyncStrategyNone, + kMessageTypeSyncStrategyDefault +#ifdef ENABLE_SCI32 + , + kMessageTypeSyncStrategyLSL6Hires, + kMessageTypeSyncStrategyShivers +#endif +}; + class GameFeatures { public: GameFeatures(SegManager *segMan, Kernel *kernel); @@ -148,6 +158,40 @@ public: #endif /** + * If true, the current game supports simultaneous speech & subtitles. + */ + bool supportsSpeechWithSubtitles() const; + + /** + * If true, the game supports changing text speed. + */ + bool supportsTextSpeed() const { + switch (g_sci->getGameId()) { +#ifdef ENABLE_SCI32 + case GID_GK1: + case GID_SQ6: + return true; +#endif + default: + break; + } + + return false; + } + + /** + * If true, audio volume sync between the game and ScummVM is done by + * monitoring and setting game global variables. + */ + bool audioVolumeSyncUsesGlobals() const; + + /** + * The strategy that should be used when synchronising the message type + * (text/speech/text+speech) between the game and ScummVM. + */ + MessageTypeSyncStrategy getMessageTypeSyncStrategy() const; + + /** * Applies to all versions before 0.000.502 * Old SCI versions used to interpret the third DrawPic() parameter inversely, * with the opposite default value (obviously). diff --git a/engines/sci/engine/guest_additions.cpp b/engines/sci/engine/guest_additions.cpp new file mode 100644 index 0000000000..a2c64239a5 --- /dev/null +++ b/engines/sci/engine/guest_additions.cpp @@ -0,0 +1,866 @@ +/* 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. + * + */ + +#include "audio/mixer.h" +#include "common/config-manager.h" +#include "common/gui_options.h" +#include "sci/engine/features.h" +#include "sci/engine/guest_additions.h" +#include "sci/engine/kernel.h" +#include "sci/engine/state.h" +#include "sci/engine/vm.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/frameout.h" +#endif +#include "sci/sound/music.h" +#include "sci/sci.h" + +namespace Sci { + +enum { + kSoundsMusicType = 0, + kSoundsSoundType = 1 +}; + +enum { + kMessageTypeSubtitles = 1, + kMessageTypeSpeech = 2 +}; + +enum { + kLSL6HiresUIVolumeMax = 13, + kLSL6HiresSubtitleFlag = 105 +}; + + +GuestAdditions::GuestAdditions(EngineState *state, GameFeatures *features) : + _state(state), + _features(features), + _segMan(state->_segMan), + _messageTypeSynced(false) {} + +#pragma mark - + +void GuestAdditions::syncSoundSettings() const { +#ifdef ENABLE_SCI32 + if (_features->audioVolumeSyncUsesGlobals()) + syncAudioVolumeGlobalsFromScummVM(); + else +#endif + syncMasterVolumeFromScummVM(); +} + +void GuestAdditions::syncAudioOptionsFromScummVM() const { +#ifdef ENABLE_SCI32 + if (_features->supportsTextSpeed()) { + syncTextSpeedFromScummVM(); + } +#endif + syncMessageTypeFromScummVM(); +} + +void GuestAdditions::reset() { + _messageTypeSynced = false; +} + +void GuestAdditions::invokeSelector(const reg_t objId, const Selector selector, const int argc, const StackPtr argv) const { + ::Sci::invokeSelector(_state, objId, selector, 0, _state->_executionStack.back().sp, argc, argv); +} + +bool GuestAdditions::shouldSyncAudio() const { + const SciGameId gameId = g_sci->getGameId(); + Common::List<ExecStack>::const_iterator it; + for (it = _state->_executionStack.begin(); it != _state->_executionStack.end(); ++it) { + const ExecStack &call = *it; + const Common::String objName = _segMan->getObjectName(call.sendp); + + if (getSciVersion() < SCI_VERSION_2 && (objName == "TheMenuBar" || + objName == "MenuBar")) { + // SCI16 with menu bar + return true; + } else if (objName == "volumeSlider") { + // SCI16 with icon bar, QFG4, Hoyle5 + return true; + } else if (gameId == GID_MOTHERGOOSE256 && objName == "soundBut") { + return true; + } else if (gameId == GID_SLATER && objName == "volButton") { + return true; + } else if (gameId == GID_LSL6 && objName == "menuBar") { + return true; +#ifdef ENABLE_SCI32 + } else if ((gameId == GID_GK1 || gameId == GID_SQ6) && (objName == "musicBar" || + objName == "soundBar")) { + return true; + } else if (gameId == GID_PQ4 && (objName == "increaseVolume" || + objName == "decreaseVolume")) { + return true; + } else if (gameId == GID_KQ7 && (objName == "volumeUp" || + objName == "volumeDown")) { + return true; + } else if (gameId == GID_LSL6HIRES && (objName == "hiResMenu" || + objName == "volumeDial")) { + return true; + } else if (gameId == GID_MOTHERGOOSEHIRES && objName == "MgButtonBar") { + return true; + } else if (gameId == GID_PQSWAT && (objName == "volumeDownButn" || + objName == "volumeUpButn")) { + return true; + } else if (gameId == GID_SHIVERS && objName == "spVolume") { + return true; + } else if (gameId == GID_GK2 && objName == "soundSlider") { + return true; + } else if (gameId == GID_PHANTASMAGORIA && (objName == "midiVolDown" || + objName == "midiVolUp" || + objName == "dacVolDown" || + objName == "dacVolUp")) { + return true; + } else if (gameId == GID_TORIN && (objName == "oMusicScroll" || + objName == "oSFXScroll" || + objName == "oAudioScroll")) { + return true; +#endif + } + } + + return false; +} + +#pragma mark - +#pragma mark Hooks + +void GuestAdditions::sciEngineRunGameHook() { + _messageTypeSynced = true; +} + +void GuestAdditions::writeVarHook(const int type, const int index, const reg_t value) { + if (type == VAR_GLOBAL) { +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + if (_features->audioVolumeSyncUsesGlobals() && shouldSyncAudio()) { + syncAudioVolumeGlobalsToScummVM(index, value); + } else if (g_sci->getGameId() == GID_GK1) { + syncGK1StartupVolumeFromScummVM(index, value); + } + + if (_features->supportsTextSpeed()) { + syncTextSpeedToScummVM(index, value); + } + } +#endif + syncMessageTypeToScummVM(index, value); + } +} + +bool GuestAdditions::kDoSoundMasterVolumeHook(const int volume) const { + if (!_features->audioVolumeSyncUsesGlobals() && shouldSyncAudio()) { + syncMasterVolumeToScummVM(volume); + return true; + } + return false; +} + +#ifdef ENABLE_SCI32 +void GuestAdditions::sendSelectorHook(const reg_t sendObj, Selector &selector, reg_t *argp) { + if (_features->getMessageTypeSyncStrategy() == kMessageTypeSyncStrategyLSL6Hires) { + syncMessageTypeToScummVMUsingLSL6HiresStrategy(sendObj, selector, argp); + } +} + +bool GuestAdditions::audio32SetVolumeHook(const int16 channelIndex, int16 volume) const { + if (!_features->audioVolumeSyncUsesGlobals() && shouldSyncAudio()) { + volume = volume * Audio::Mixer::kMaxMixerVolume / Audio32::kMaxVolume; + if (Common::checkGameGUIOption(GUIO_LINKMUSICTOSFX, ConfMan.get("guioptions"))) { + ConfMan.setInt("music_volume", volume); + } + ConfMan.setInt("sfx_volume", volume); + ConfMan.setInt("speech_volume", volume); + g_engine->syncSoundSettings(); + return true; + } + + return false; +} + +void GuestAdditions::kDoSoundSetVolumeHook(const reg_t soundObj, const int16 volume) const { + if (g_sci->getGameId() == GID_GK1 && shouldSyncAudio()) { + syncGK1AudioVolumeToScummVM(soundObj, volume); + } +} +#endif + +#pragma mark - +#pragma mark Message type sync + +void GuestAdditions::syncMessageTypeFromScummVM() const { + switch (_features->getMessageTypeSyncStrategy()) { + case kMessageTypeSyncStrategyDefault: + syncMessageTypeFromScummVMUsingDefaultStrategy(); + break; + +#ifdef ENABLE_SCI32 + case kMessageTypeSyncStrategyShivers: + syncMessageTypeFromScummVMUsingShiversStrategy(); + break; + + case kMessageTypeSyncStrategyLSL6Hires: + syncMessageTypeFromScummVMUsingLSL6HiresStrategy(); + break; +#endif + case kMessageTypeSyncStrategyNone: + break; + } +} + +void GuestAdditions::syncMessageTypeFromScummVMUsingDefaultStrategy() const { + uint8 value = 0; + if (ConfMan.getBool("subtitles")) { + value |= kMessageTypeSubtitles; + } + if (!ConfMan.getBool(("speech_mute"))) { + value |= kMessageTypeSpeech; + } + + if (value == kMessageTypeSubtitles + kMessageTypeSpeech && !_features->supportsSpeechWithSubtitles()) { + value &= ~kMessageTypeSubtitles; + } + + if (value) { + _state->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, value); + } + + if (g_sci->getGameId() == GID_GK1) { + if (value == kMessageTypeSubtitles) { + _state->variables[VAR_GLOBAL][kGlobalVarGK1NarratorMode] = NULL_REG; + } else if (value == kMessageTypeSpeech) { + _state->variables[VAR_GLOBAL][kGlobalVarGK1NarratorMode] = TRUE_REG; + } + } +} + +#ifdef ENABLE_SCI32 +void GuestAdditions::syncMessageTypeFromScummVMUsingShiversStrategy() const { + if (ConfMan.getBool("subtitles")) { + _state->variables[VAR_GLOBAL][kGlobalVarShiversFlags] |= 256; + } else { + _state->variables[VAR_GLOBAL][kGlobalVarShiversFlags] &= ~256; + } +} + +void GuestAdditions::syncMessageTypeFromScummVMUsingLSL6HiresStrategy() const { + // LSL6hires synchronisation happens in send_selector, except when + // restoring a game, where it happens here + if (_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresGameFlags].isNull()) { + return; + } + + reg_t params[] = { make_reg(0, kLSL6HiresSubtitleFlag) }; + Selector selector; + reg_t restore; + + if (ConfMan.getBool("subtitles")) { + restore = TRUE_REG; + selector = SELECTOR(clear); + } else { + restore = NULL_REG; + selector = SELECTOR(set); + } + + // Attempting to show or hide the ScrollWindow used for subtitles + // directly (by invoking `show` or `hide`) causes the game to crash with + // an error about passing an invalid ScrollWindow ID. Fortunately, the + // game scripts store a flag that restores the window when a game is + // restored + _state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresRestoreTextWindow] = restore; + invokeSelector(_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresGameFlags], selector, 1, params); +} +#endif + +void GuestAdditions::syncMessageTypeToScummVM(const int index, const reg_t value) { + switch (_features->getMessageTypeSyncStrategy()) { + case kMessageTypeSyncStrategyDefault: + syncMessageTypeToScummVMUsingDefaultStrategy(index, value); + break; + +#ifdef ENABLE_SCI32 + case kMessageTypeSyncStrategyShivers: + syncMessageTypeToScummVMUsingShiversStrategy(index, value); + break; + + case kMessageTypeSyncStrategyLSL6Hires: + // LSL6hires synchronisation happens via send_selector +#endif + case kMessageTypeSyncStrategyNone: + break; + } +} + +void GuestAdditions::syncMessageTypeToScummVMUsingDefaultStrategy(const int index, const reg_t value) { + if (index == kGlobalVarMessageType) { + // ScummVM audio options haven't been applied yet. Use this set call + // as a trigger to apply defaults from ScummVM, ignoring the default + // value that was just received from the game scripts + if (!_messageTypeSynced || _state->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) { + _messageTypeSynced = true; + syncAudioOptionsFromScummVM(); + return; + } + + ConfMan.setBool("subtitles", value.toSint16() & kMessageTypeSubtitles); + ConfMan.setBool("speech_mute", !(value.toSint16() & kMessageTypeSpeech)); + } +} + +#ifdef ENABLE_SCI32 +void GuestAdditions::syncMessageTypeToScummVMUsingShiversStrategy(const int index, const reg_t value) { + if (index == kGlobalVarShiversFlags) { + // ScummVM audio options haven't been applied yet, so apply them + // and ignore the default value that was just received from the + // game scripts + if (!_messageTypeSynced || _state->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) { + _messageTypeSynced = true; + syncAudioOptionsFromScummVM(); + return; + } + + ConfMan.setBool("subtitles", value.toUint16() & 256); + } +} + +void GuestAdditions::syncMessageTypeToScummVMUsingLSL6HiresStrategy(const reg_t sendObj, Selector &selector, reg_t *argp) { + if (_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresGameFlags] == sendObj && + (selector == SELECTOR(clear) || selector == SELECTOR(set))) { + + if (argp[1].toUint16() == kLSL6HiresSubtitleFlag) { + if (_messageTypeSynced) { + ConfMan.setBool("subtitles", selector == SELECTOR(clear)); + } else if (ConfMan.getBool("subtitles")) { + selector = SELECTOR(clear); + argp[-1].setOffset(selector); + _messageTypeSynced = true; + } else { + selector = SELECTOR(set); + argp[-1].setOffset(selector); + _messageTypeSynced = true; + } + } + } +} +#endif + +#pragma mark - +#pragma mark Master volume sync + +void GuestAdditions::syncMasterVolumeFromScummVM() const { + const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * MUSIC_MASTERVOLUME_MAX / Audio::Mixer::kMaxMixerVolume; + + // When the volume changes from the ScummVM launcher, ScummVM automatically + // adjusts the software mixer in Engine::syncSoundSettings, but MIDI may not + // run through the ScummVM mixer so its master volume must be adjusted + // explicitly + if (g_sci->_soundCmd) { + g_sci->_soundCmd->setMasterVolume(ConfMan.getBool("mute") ? 0 : musicVolume); + } + +#ifdef ENABLE_SCI32 + const int16 sfxVolume = (ConfMan.getInt("sfx_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume; + + // Volume was changed from ScummVM during the game, so resync the + // in-game UI + syncInGameUI(musicVolume, sfxVolume); +#endif +} + +void GuestAdditions::syncMasterVolumeToScummVM(const int16 masterVolume) const { + const int scummVMVolume = masterVolume * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX; + ConfMan.setInt("music_volume", scummVMVolume); + + if (Common::checkGameGUIOption(GUIO_LINKMUSICTOSFX, ConfMan.get("guioptions"))) { + ConfMan.setInt("sfx_volume", scummVMVolume); + if (Common::checkGameGUIOption(GUIO_LINKSPEECHTOSFX, ConfMan.get("guioptions"))) { + ConfMan.setInt("speech_volume", scummVMVolume); + } + } + + // In SCI32, digital audio volume is controlled separately by + // kDoAudioVolume + // TODO: In SCI16, the volume slider only changed the music volume. + // Is this non-standard behavior better, or just wrong? + if (getSciVersion() < SCI_VERSION_2) { + ConfMan.setInt("sfx_volume", scummVMVolume); + ConfMan.setInt("speech_volume", scummVMVolume); + } + g_engine->syncSoundSettings(); +} + +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Globals volume sync + +void GuestAdditions::syncAudioVolumeGlobalsFromScummVM() const { + // On muting: Setting the music volume to zero when mute is enabled is done + // only for the games that use MIDI for music playback, since MIDI playback + // does not always run through the ScummVM mixer. Games that use digital + // audio for music do not need any extra code since that always runs + // straight through the audio mixer, which gets muted directly + switch (g_sci->getGameId()) { + case GID_GK1: { + const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * MUSIC_VOLUME_MAX / Audio::Mixer::kMaxMixerVolume; + const int16 dacVolume = (ConfMan.getInt("sfx_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume; + syncGK1VolumeFromScummVM(musicVolume, dacVolume); + syncGK1UI(); + break; + } + + case GID_GK2: { + const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume; + syncGK2VolumeFromScummVM(musicVolume); + syncGK2UI(); + break; + } + + case GID_LSL6HIRES: { + const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * kLSL6HiresUIVolumeMax / Audio::Mixer::kMaxMixerVolume; + syncLSL6HiresVolumeFromScummVM(musicVolume); + syncLSL6HiresUI(musicVolume); + break; + } + + case GID_PHANTASMAGORIA: { + reg_t &musicGlobal = _state->variables[VAR_GLOBAL][kGlobalVarPhant1MusicVolume]; + reg_t &dacGlobal = _state->variables[VAR_GLOBAL][kGlobalVarPhant1DACVolume]; + + const int16 oldMusicVolume = musicGlobal.toSint16(); + const int16 oldDacVolume = dacGlobal.toSint16(); + + const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * MUSIC_MASTERVOLUME_MAX / Audio::Mixer::kMaxMixerVolume; + const int16 dacVolume = (ConfMan.getInt("sfx_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume; + + g_sci->_soundCmd->setMasterVolume(ConfMan.getBool("mute") ? 0 : musicVolume); + + // Phant1 has a fragile volume UI. Global volumes need to be set during + // UI updates to move the volume bars to the correct position + syncPhant1UI(oldMusicVolume, musicVolume, musicGlobal, oldDacVolume, dacVolume, dacGlobal); + break; + } + + case GID_TORIN: { + const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * 100 / Audio::Mixer::kMaxMixerVolume; + const int16 sfxVolume = (ConfMan.getInt("sfx_volume") + 1) * 100 / Audio::Mixer::kMaxMixerVolume; + const int16 speechVolume = (ConfMan.getInt("speech_volume") + 1) * 100 / Audio::Mixer::kMaxMixerVolume; + syncTorinVolumeFromScummVM(musicVolume, sfxVolume, speechVolume); + syncTorinUI(musicVolume, sfxVolume, speechVolume); + break; + } + + default: + error("Trying to sync audio volume globals in a game with no implementation"); + } +} + +void GuestAdditions::syncGK1StartupVolumeFromScummVM(const int index, const reg_t value) const { + if (index == kGlobalVarGK1Music1 || index == kGlobalVarGK1Music2 || + index == kGlobalVarGK1DAC1 || index == kGlobalVarGK1DAC2 || + index == kGlobalVarGK1DAC3) { + + int16 volume; + Selector selector; + + switch (readSelectorValue(_segMan, value, SELECTOR(type))) { + case kSoundsMusicType: { + volume = (ConfMan.getInt("music_volume") + 1) * MUSIC_VOLUME_MAX / Audio::Mixer::kMaxMixerVolume; + selector = SELECTOR(musicVolume); + break; + } + + case kSoundsSoundType: { + volume = (ConfMan.getInt("sound_volume") + 1) * MUSIC_VOLUME_MAX / Audio::Mixer::kMaxMixerVolume; + selector = SELECTOR(soundVolume); + break; + } + + default: + error("Unknown sound type"); + } + + writeSelectorValue(_segMan, value, selector, volume); + writeSelectorValue(_segMan, value, selector, volume); + } +} + +void GuestAdditions::syncGK1VolumeFromScummVM(const int16 musicVolume, const int16 dacVolume) const { + const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds]; + if (!soundsId.isNull()) { + List *sounds = _segMan->lookupList(readSelector(_segMan, soundsId, SELECTOR(elements))); + reg_t soundId = sounds->first; + while (!soundId.isNull()) { + Node *sound = _segMan->lookupNode(soundId); + const int16 type = readSelectorValue(_segMan, sound->value, SELECTOR(type)); + int16 volume; + + if (type == kSoundsMusicType) { + volume = ConfMan.getBool("mute") ? 0 : musicVolume; + writeSelectorValue(_segMan, sound->value, SELECTOR(musicVolume), musicVolume); + } else if (type == kSoundsSoundType) { + volume = dacVolume; + writeSelectorValue(_segMan, sound->value, SELECTOR(soundVolume), dacVolume); + } else { + error("Unknown sound type %d", type); + } + + // `setVolume` will set the `vol` property on the sound object; + // if it did not do this, an invocation of the `setVol` selector + // would need to be here (though doing so would result in + // recursion, so don't) + g_sci->_soundCmd->setVolume(sound->value, volume); + soundId = sound->succ; + } + } +} + +void GuestAdditions::syncGK2VolumeFromScummVM(const int16 musicVolume) const { + _state->variables[VAR_GLOBAL][kGlobalVarGK2MusicVolume] = make_reg(0, musicVolume); + + // Calling `setVol` on all sounds is necessary to propagate the volume + // change to existing sounds, and matches how game scripts propagate + // volume changes when the in-game music slider is moved + const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds]; + if (!soundsId.isNull()) { + List *sounds = _segMan->lookupList(readSelector(_segMan, soundsId, SELECTOR(elements))); + reg_t soundId = sounds->first; + while (!soundId.isNull()) { + Node *sound = _segMan->lookupNode(soundId); + reg_t params[] = { make_reg(0, musicVolume) }; + invokeSelector(sound->value, SELECTOR(setVol), 1, params); + soundId = sound->succ; + } + } +} + +void GuestAdditions::syncLSL6HiresVolumeFromScummVM(const int16 musicVolume) const { + _state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresMusicVolume] = make_reg(0, musicVolume); + g_sci->_soundCmd->setMasterVolume(ConfMan.getBool("mute") ? 0 : (musicVolume * MUSIC_MASTERVOLUME_MAX / kLSL6HiresUIVolumeMax)); +} + +void GuestAdditions::syncTorinVolumeFromScummVM(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const { + _state->variables[VAR_GLOBAL][kGlobalVarTorinMusicVolume] = make_reg(0, musicVolume); + _state->variables[VAR_GLOBAL][kGlobalVarTorinSFXVolume] = make_reg(0, sfxVolume); + _state->variables[VAR_GLOBAL][kGlobalVarTorinSpeechVolume] = make_reg(0, speechVolume); + + // Calling `reSyncVol` on all sounds is necessary to propagate the + // volume change to existing sounds, and matches how game scripts + // propagate volume changes when the in-game volume sliders are moved + const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds]; + if (!soundsId.isNull()) { + const Selector selector = SELECTOR(reSyncVol); + List *sounds = _segMan->lookupList(readSelector(_segMan, soundsId, SELECTOR(elements))); + reg_t soundId = sounds->first; + while (!soundId.isNull()) { + Node *sound = _segMan->lookupNode(soundId); + const reg_t &soundObj = sound->value; + + if (_segMan->isHeapObject(soundObj) && lookupSelector(_segMan, soundObj, selector, nullptr, nullptr) != kSelectorNone) { + invokeSelector(sound->value, SELECTOR(reSyncVol)); + } + soundId = sound->succ; + } + } +} + +void GuestAdditions::syncAudioVolumeGlobalsToScummVM(const int index, const reg_t value) const { + switch (g_sci->getGameId()) { + case GID_GK2: + if (index == kGlobalVarGK2MusicVolume) { + const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / Audio32::kMaxVolume; + ConfMan.setInt("music_volume", musicVolume); + } + break; + + case GID_LSL6HIRES: + if (index == kGlobalVarLSL6HiresMusicVolume) { + const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / kLSL6HiresUIVolumeMax; + ConfMan.setInt("music_volume", musicVolume); + } + break; + + case GID_PHANTASMAGORIA: + if (index == kGlobalVarPhant1MusicVolume) { + const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX; + ConfMan.setInt("music_volume", musicVolume); + } else if (index == kGlobalVarPhant1DACVolume) { + const int16 dacVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / Audio32::kMaxVolume; + ConfMan.setInt("sfx_volume", dacVolume); + ConfMan.setInt("speech_volume", dacVolume); + } + break; + + case GID_TORIN: + if (index == kGlobalVarTorinMusicVolume || + index == kGlobalVarTorinSFXVolume || + index == kGlobalVarTorinSpeechVolume) { + + const int16 volume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / 100; + + switch (index) { + case kGlobalVarTorinMusicVolume: + ConfMan.setInt("music_volume", volume); + break; + case kGlobalVarTorinSFXVolume: + ConfMan.setInt("sfx_volume", volume); + break; + case kGlobalVarTorinSpeechVolume: + ConfMan.setInt("speech_volume", volume); + break; + } + } + break; + + default: + break; + } +} + +void GuestAdditions::syncGK1AudioVolumeToScummVM(const reg_t soundObj, int16 volume) const { + const Common::String objName = _segMan->getObjectName(soundObj); + volume = volume * Audio::Mixer::kMaxMixerVolume / MUSIC_VOLUME_MAX; + + // Using highest-numbered sound objects to sync only after all slots + // have been set by the volume slider + if (objName == "gkMusic2") { + ConfMan.setInt("music_volume", volume); + g_engine->syncSoundSettings(); + } else if (objName == "gkSound3") { + ConfMan.setInt("sfx_volume", volume); + ConfMan.setInt("speech_volume", volume); + g_engine->syncSoundSettings(); + } +} + +#pragma mark - +#pragma mark Audio UI sync + +void GuestAdditions::syncInGameUI(const int16 musicVolume, const int16 sfxVolume) const { + if (_state->abortScriptProcessing != kAbortNone) { + // Attempting to update a UI that is in the process of being destroyed + // will result in a crash + return; + } + + switch (g_sci->getGameId()) { + case GID_PQ4: + syncPQ4UI(musicVolume); + break; + + case GID_PQSWAT: + syncPQSWATUI(); + break; + + case GID_QFG4: + syncQFG4UI(musicVolume); + break; + + case GID_SHIVERS: + syncShivers1UI(sfxVolume); + break; + + case GID_SQ6: + syncSQ6UI(); + break; + + default: + break; + } +} + +void GuestAdditions::syncGK1UI() const { + const reg_t bars[] = { _segMan->findObjectByName("musicBar"), + _segMan->findObjectByName("soundBar") }; + + for (int i = 0; i < ARRAYSIZE(bars); ++i) { + const reg_t barId = bars[i]; + if (!barId.isNull()) { + // Resetting the position to 0 causes the bar to refresh its + // position when it next draws + writeSelectorValue(_segMan, barId, SELECTOR(position), 0); + + // The `signal` property indicates bar visibility (for some + // reason, the normal `-info-` flag is not used) + if (readSelectorValue(_segMan, barId, SELECTOR(signal)) & 0x20) { + // `show` pulls a new value from the underlying sound object + // and refreshes the bar rendering + invokeSelector(barId, SELECTOR(show)); + } + } + } +} + +void GuestAdditions::syncGK2UI() const { + const reg_t sliderId = _segMan->findObjectByName("soundSlider"); + if (!sliderId.isNull() && _segMan->getObject(sliderId)->isInserted()) { + const reg_t oldAcc = _state->r_acc; + invokeSelector(sliderId, SELECTOR(initialOff)); + writeSelector(_segMan, sliderId, SELECTOR(x), _state->r_acc); + _state->r_acc = oldAcc; + } +} + +void GuestAdditions::syncLSL6HiresUI(const int16 musicVolume) const { + const reg_t musicDialId = _segMan->findObjectByName("volumeDial"); + if (!musicDialId.isNull()) { + writeSelectorValue(_segMan, musicDialId, SELECTOR(curPos), musicVolume); + writeSelectorValue(_segMan, musicDialId, SELECTOR(cel), musicVolume); + reg_t params[] = { make_reg(0, musicVolume) }; + invokeSelector(musicDialId, SELECTOR(update), 1, params); + if (_segMan->getObject(musicDialId)->isInserted()) { + g_sci->_gfxFrameout->kernelUpdateScreenItem(musicDialId); + } + } +} + +void GuestAdditions::syncPhant1UI(const int16 oldMusicVolume, const int16 musicVolume, reg_t &musicGlobal, const int16 oldDacVolume, const int16 dacVolume, reg_t &dacGlobal) const { + const reg_t buttonId = _segMan->findObjectByName("dacVolUp"); + if (buttonId.isNull() || !_segMan->getObject(buttonId)->isInserted()) { + // No inserted dacVolUp button means the control panel with the + // volume controls is not visible and we can just update the values + // and leave + musicGlobal.setOffset(musicVolume); + dacGlobal.setOffset(dacVolume); + return; + } + + reg_t thermo = _segMan->findObjectByName("midiVolThermo"); + if (!thermo.isNull()) { + int count = ABS(musicVolume - oldMusicVolume); + const int stepSize = (musicVolume > oldMusicVolume ? 1 : -1); + while (count--) { + musicGlobal.incOffset(stepSize); + invokeSelector(thermo, SELECTOR(doit)); + } + } + + thermo = _segMan->findObjectByName("dacVolThermo"); + if (!thermo.isNull()) { + int count = ABS(dacVolume - oldDacVolume) / 8; + const int stepSize = (dacVolume > oldDacVolume ? 8 : -8); + while (count--) { + dacGlobal.incOffset(stepSize); + invokeSelector(thermo, SELECTOR(doit)); + } + } +} + +void GuestAdditions::syncPQ4UI(const int16 musicVolume) const { + const SegmentId segment = _segMan->getScriptSegment(9, SCRIPT_GET_DONT_LOAD); + if (segment != 0 && _segMan->getScript(segment)->getLocalsCount() > 2) { + const reg_t barId = _segMan->getScript(segment)->getLocalsBegin()[2]; + if (!barId.isNull()) { + reg_t params[] = { make_reg(0, musicVolume) }; + invokeSelector(barId, SELECTOR(setSize), 1, params); + } + } +} + +void GuestAdditions::syncPQSWATUI() const { + const reg_t barId = _segMan->findObjectByName("volumeLed"); + if (!barId.isNull() && _segMan->getObject(barId)->isInserted()) { + invokeSelector(barId, SELECTOR(displayValue)); + } +} + +void GuestAdditions::syncQFG4UI(const int16 musicVolume) const { + const reg_t sliderId = _segMan->findObjectByName("volumeSlider"); + if (!sliderId.isNull()) { + const int16 yPosition = 84 - musicVolume * 34 / 10; + writeSelectorValue(_segMan, sliderId, SELECTOR(y), yPosition); + + // There does not seem to be any good way to learn whether the + // volume slider is visible (and thus eligible for + // kUpdateScreenItem) + const reg_t planeId = readSelector(_segMan, sliderId, SELECTOR(plane)); + if (g_sci->_gfxFrameout->getPlanes().findByObject(planeId) != nullptr) { + g_sci->_gfxFrameout->kernelUpdateScreenItem(sliderId); + } + } +} + +void GuestAdditions::syncShivers1UI(const int16 dacVolume) const { + const reg_t sliderId = _segMan->findObjectByName("spVolume"); + if (!sliderId.isNull()) { + const int16 xPosition = dacVolume * 78 / Audio32::kMaxVolume + 32; + writeSelectorValue(_segMan, sliderId, SELECTOR(x), xPosition); + if (_segMan->getObject(sliderId)->isInserted()) { + g_sci->_gfxFrameout->kernelUpdateScreenItem(sliderId); + } + } +} + +void GuestAdditions::syncSQ6UI() const { + const reg_t bars[] = { _segMan->findObjectByName("musicBar"), + _segMan->findObjectByName("soundBar") }; + for (int i = 0; i < ARRAYSIZE(bars); ++i) { + const reg_t barId = bars[i]; + if (!barId.isNull()) { + invokeSelector(barId, SELECTOR(show)); + } + } +} + +void GuestAdditions::syncTorinUI(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const { + const reg_t sliders[] = { _segMan->findObjectByName("oMusicScroll"), + _segMan->findObjectByName("oSFXScroll"), + _segMan->findObjectByName("oAudioScroll") }; + const int16 values[] = { musicVolume, sfxVolume, speechVolume }; + for (int i = 0; i < ARRAYSIZE(sliders); ++i) { + const reg_t sliderId = sliders[i]; + if (!sliderId.isNull()) { + reg_t params[] = { make_reg(0, values[i]) }; + invokeSelector(sliderId, SELECTOR(setPos), 1, params); + } + } +} + +#pragma mark - +#pragma mark Talk speed sync + +void GuestAdditions::syncTextSpeedFromScummVM() const { + const int16 textSpeed = 8 - (ConfMan.getInt("talkspeed") + 1) * 8 / 255; + + _state->variables[VAR_GLOBAL][kGlobalVarTextSpeed] = make_reg(0, textSpeed); + + if (g_sci->getGameId() == GID_GK1) { + const reg_t textBarId = _segMan->findObjectByName("textBar"); + if (!textBarId.isNull()) { + // Resetting the bar position to 0 causes the game to retrieve the + // new text speed value and re-render + writeSelectorValue(_segMan, textBarId, SELECTOR(position), 0); + } + } +} + +void GuestAdditions::syncTextSpeedToScummVM(const int index, const reg_t value) const { + if (index == kGlobalVarTextSpeed) { + ConfMan.setInt("talkspeed", (8 - value.toSint16()) * 255 / 8); + } +} + +#endif + +} // End of namespace Sci diff --git a/engines/sci/engine/guest_additions.h b/engines/sci/engine/guest_additions.h new file mode 100644 index 0000000000..a6875d43ad --- /dev/null +++ b/engines/sci/engine/guest_additions.h @@ -0,0 +1,245 @@ +/* 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. + * + */ + +#ifndef SCI_ENGINE_GUEST_ADDITIONS_H +#define SCI_ENGINE_GUEST_ADDITIONS_H + +#include "sci/engine/vm_types.h" + +namespace Sci { + +struct EngineState; +class GameFeatures; +class SegManager; + +/** + * The GuestAdditions class hooks into the SCI virtual machine to provide + * enhanced interactions between the ScummVM GUI and the game engine. Currently, + * this enhanced functionality encompasses synchronisation of audio volumes and + * other audio-related settings. + * + * Some parts of the audio sync are applied as script patches using the normal + * ScriptPatcher mechanism. These patches are designed to prevent the game from + * resetting to a default volume when starting up or loading a save. + */ +class GuestAdditions { +public: + GuestAdditions(EngineState *state, GameFeatures *features); + + /** + * Synchronises audio volume settings from ScummVM to the game. Called + * whenever the ScummVM launcher is dismissed. + */ + void syncSoundSettings() const; + + /** + * Synchronises all audio settings from ScummVM to the game. Called when the + * game is first started, and when save games are loaded. + */ + void syncAudioOptionsFromScummVM() const; + + /** + * Clears audio settings synchronisation state. + */ + void reset(); + +private: + EngineState *_state; + GameFeatures *_features; + SegManager *_segMan; + + /** + * Convenience function for invoking selectors that reduces boilerplate code + * required by Sci::invokeSelector. + */ + void invokeSelector(const reg_t objId, const Selector selector, const int argc = 0, const StackPtr argv = nullptr) const; + + /** + * Determines whether the current stack contains calls from audio controls + * that indicate a user-initiated change of audio settings. + */ + bool shouldSyncAudio() const; + +#pragma mark - +#pragma mark Hooks + +public: + /** + * Guest additions hook for SciEngine::runGame. + */ + void sciEngineRunGameHook(); + + /** + * Guest additions hook for write_var. + */ + void writeVarHook(const int type, const int index, const reg_t value); + + /** + * Guest additions hook for kDoSoundMasterVolume. + * + * @returns true if the default action should be prevented + */ + bool kDoSoundMasterVolumeHook(const int volume) const; + +#ifdef ENABLE_SCI32 + /** + * Guest additions hook for send_selector. + */ + void sendSelectorHook(const reg_t sendObj, Selector &selector, reg_t *argp); + + /** + * Guest additions hook for Audio32::setVolume. + * + * @returns true if the default action should be prevented + */ + bool audio32SetVolumeHook(const int16 channelIndex, const int16 volume) const; + + /** + * Guest additions hook for kDoSoundSetVolume. + */ + void kDoSoundSetVolumeHook(const reg_t soundObj, const int16 volume) const; +#endif + +#pragma mark - +#pragma mark Message type sync + +private: + /** + * true if the message type (text/speech/text+speech) has been synchronised + * from ScummVM to the game. + */ + bool _messageTypeSynced; + + /** + * Synchronises the message type (speech/text/speech+text) from a ScummVM to + * a game. + */ + void syncMessageTypeFromScummVM() const; + + void syncMessageTypeFromScummVMUsingDefaultStrategy() const; +#ifdef ENABLE_SCI32 + void syncMessageTypeFromScummVMUsingShiversStrategy() const; + void syncMessageTypeFromScummVMUsingLSL6HiresStrategy() const; +#endif + + /** + * Synchronises the message type (speech/text/speech+text) from a game to + * ScummVM. + */ + void syncMessageTypeToScummVM(const int index, const reg_t value); + + void syncMessageTypeToScummVMUsingDefaultStrategy(const int index, const reg_t value); +#ifdef ENABLE_SCI32 + void syncMessageTypeToScummVMUsingShiversStrategy(const int index, const reg_t value); + void syncMessageTypeToScummVMUsingLSL6HiresStrategy(const reg_t sendObj, Selector &selector, reg_t *argp); +#endif + +#pragma mark - +#pragma mark Master volume sync + +private: + /** + * Synchronises audio volume settings from ScummVM to the game, for games + * that do not store volume themselves and just call to the kernel. + */ + void syncMasterVolumeFromScummVM() const; + + /** + * Synchronises audio volume settings from the game to ScummVM, for games + * that do not store volume themselves and just call to the kernel. + */ + void syncMasterVolumeToScummVM(const int16 masterVolume) const; + +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Globals volume sync + +private: + /** + * Synchronises audio volume settings from ScummVM to the game, for games + * that store volumes in globals. + */ + void syncAudioVolumeGlobalsFromScummVM() const; + + /** + * Synchronises audio volume settings from ScummVM to GK1 at game startup + * time. + */ + void syncGK1StartupVolumeFromScummVM(const int index, const reg_t value) const; + + void syncGK1VolumeFromScummVM(const int16 musicVolume, const int16 dacVolume) const; + void syncGK2VolumeFromScummVM(const int16 musicVolume) const; + void syncLSL6HiresVolumeFromScummVM(const int16 musicVolume) const; + void syncTorinVolumeFromScummVM(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const; + + /** + * Synchronises audio volume settings from a game to ScummVM, for games + * that store volumes in globals. + */ + void syncAudioVolumeGlobalsToScummVM(const int index, const reg_t value) const; + + /** + * Synchronises audio volume settings from GK1 to ScummVM. + */ + void syncGK1AudioVolumeToScummVM(const reg_t soundObj, const int16 volume) const; + +#pragma mark - +#pragma mark Audio UI sync + +private: + /** + * Synchronises the in-game control panel UI in response to a change of + * volume from the ScummVM GUI. The values of the volume parameters passed + * to this function are game-specific. + */ + void syncInGameUI(const int16 musicVolume, const int16 sfxVolume) const; + + void syncGK1UI() const; + void syncGK2UI() const; + void syncLSL6HiresUI(const int16 musicVolume) const; + void syncPhant1UI(const int16 oldMusicVolume, const int16 musicVolume, reg_t &musicGlobal, const int16 oldDacVolume, const int16 dacVolume, reg_t &dacGlobal) const; + void syncPQ4UI(const int16 musicVolume) const; + void syncPQSWATUI() const; + void syncQFG4UI(const int16 musicVolume) const; + void syncShivers1UI(const int16 dacVolume) const; + void syncSQ6UI() const; + void syncTorinUI(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const; + +#pragma mark - +#pragma mark Talk speed sync + +private: + /** + * Synchronises text speed settings from ScummVM to a game. + */ + void syncTextSpeedFromScummVM() const; + + /** + * Synchronises text speed settings from a game to ScummVM. + */ + void syncTextSpeedToScummVM(const int index, const reg_t value) const; +#endif +}; + +} // End of namespace Sci + +#endif // SCI_ENGINE_GUEST_ADDITIONS_H diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp index 4a5e4f3df6..e7cad21d67 100644 --- a/engines/sci/engine/ksound.cpp +++ b/engines/sci/engine/ksound.cpp @@ -46,7 +46,7 @@ reg_t kDoSound(EngineState *s, int argc, reg_t *argv) { error("not supposed to call this"); } -#define CREATE_DOSOUND_FORWARD(_name_) reg_t k##_name_(EngineState *s, int argc, reg_t *argv) { return g_sci->_soundCmd->k##_name_(argc, argv, s->r_acc); } +#define CREATE_DOSOUND_FORWARD(_name_) reg_t k##_name_(EngineState *s, int argc, reg_t *argv) { return g_sci->_soundCmd->k##_name_(s, argc, argv); } CREATE_DOSOUND_FORWARD(DoSoundInit) CREATE_DOSOUND_FORWARD(DoSoundPlay) @@ -77,21 +77,21 @@ reg_t kDoSoundPhantasmagoriaMac(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case 0: - return g_sci->_soundCmd->kDoSoundMasterVolume(argc - 1, argv + 1, s->r_acc); + return g_sci->_soundCmd->kDoSoundMasterVolume(s, argc - 1, argv + 1); case 2: - return g_sci->_soundCmd->kDoSoundInit(argc - 1, argv + 1, s->r_acc); + return g_sci->_soundCmd->kDoSoundInit(s, argc - 1, argv + 1); case 3: - return g_sci->_soundCmd->kDoSoundDispose(argc - 1, argv + 1, s->r_acc); + return g_sci->_soundCmd->kDoSoundDispose(s, argc - 1, argv + 1); case 4: - return g_sci->_soundCmd->kDoSoundPlay(argc - 1, argv + 1, s->r_acc); + return g_sci->_soundCmd->kDoSoundPlay(s, argc - 1, argv + 1); case 5: - return g_sci->_soundCmd->kDoSoundStop(argc - 1, argv + 1, s->r_acc); + return g_sci->_soundCmd->kDoSoundStop(s, argc - 1, argv + 1); case 8: - return g_sci->_soundCmd->kDoSoundSetVolume(argc - 1, argv + 1, s->r_acc); + return g_sci->_soundCmd->kDoSoundSetVolume(s, argc - 1, argv + 1); case 9: - return g_sci->_soundCmd->kDoSoundSetLoop(argc - 1, argv + 1, s->r_acc); + return g_sci->_soundCmd->kDoSoundSetLoop(s, argc - 1, argv + 1); case 10: - return g_sci->_soundCmd->kDoSoundUpdateCues(argc - 1, argv + 1, s->r_acc); + return g_sci->_soundCmd->kDoSoundUpdateCues(s, argc - 1, argv + 1); } error("Unknown kDoSound Phantasmagoria Mac subop %d", argv[0].toUint16()); diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h index 61f942c04a..dca7e97e81 100644 --- a/engines/sci/engine/object.h +++ b/engines/sci/engine/object.h @@ -179,6 +179,10 @@ public: _infoSelectorSci3 &= ~flag; } } + + bool isInserted() const { + return getInfoSelector().toUint16() & kInfoFlagViewInserted; + } #endif reg_t getNameSelector() const { diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index f5ec2d300c..97e53a0742 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -1079,12 +1079,29 @@ static const uint16 gk2InvScrollPatch[] = { PATCH_END }; +// The init code that runs when GK2 starts up unconditionally resets the +// music volume to 63, but the game should always use the volume stored in +// ScummVM. +// Applies to at least: English 1.00 CD +static const uint16 gk2VolumeResetSignature[] = { + SIG_MAGICDWORD, + 0x35, 0x3f, // ldi $3f + 0xa1, 0x4c, // sag $4c (music volume) + SIG_END +}; + +static const uint16 gk2VolumeResetPatch[] = { + 0x33, 0x02, // jmp 2 [past volume changes] + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry gk2Signatures[] = { + { true, 0, "disable volume reset on startup", 1, gk2VolumeResetSignature, gk2VolumeResetPatch }, + { true, 23, "inventory starts scroll down in the wrong direction", 1, gk2InvScrollSignature, gk2InvScrollPatch }, { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, - { true, 23, "inventory starts scroll down in the wrong direction", 1, gk2InvScrollSignature, gk2InvScrollPatch }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -2295,8 +2312,30 @@ static const uint16 larry6HiresPatchSetScale[] = { PATCH_END }; +// The init code that runs when LSL6hires starts up unconditionally resets the +// master music volume to 12 (and the volume dial to 11), but the game should +// always use the volume stored in ScummVM. +// Applies to at least: English CD +static const uint16 larry6HiresSignatureVolumeReset[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x221), // pushi $221 (masterVolume) + 0x78, // push1 + 0x39, 0x0c, // push $0c + 0x81, 0x01, // lag $01 + 0x4a, SIG_UINT16(0x06), // send $6 + 0x35, 0x0b, // ldi $0b + 0xa1, 0xc2, // sag $c2 + SIG_END +}; + +static const uint16 larry6HiresPatchVolumeReset[] = { + 0x32, PATCH_UINT16(12), // jmp 12 [past volume changes] + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry larry6HiresSignatures[] = { + { true, 71, "disable volume reset on startup", 1, larry6HiresSignatureVolumeReset, larry6HiresPatchVolumeReset }, { true, 270, "fix incorrect setScale call", 1, larry6HiresSignatureSetScale, larry6HiresPatchSetScale }, { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, @@ -3151,9 +3190,42 @@ static const SciScriptPatcherEntry mothergooseHiresSignatures[] = { #pragma mark - #pragma mark Phantasmagoria +// Phantasmagoria persists audio volumes in the save games, but ScummVM manages +// game volumes through the launcher, so stop the game from overwriting the +// ScummVM volumes with volumes from save games. +// Applies to at least: English CD +static const uint16 phant1SignatureSavedVolume[] = { + 0x7a, // push2 + 0x39, 0x08, // pushi 8 + 0x38, SIG_UINT16(0x20b), // push $20b (readWord) + 0x76, // push0 + 0x72, SIG_UINT16(0x13c), // lofsa $13c (PREF.DAT) + 0x4a, SIG_UINT16(0x04), // send 4 + SIG_MAGICDWORD, + 0xa1, 0xbc, // sag $bc + 0x36, // push + 0x43, 0x76, SIG_UINT16(0x04), // callk DoAudio[76], 4 + 0x7a, // push2 + 0x76, // push0 + 0x38, SIG_UINT16(0x20b), // push $20b (readWord) + 0x76, // push0 + 0x72, SIG_UINT16(0x13c), // lofsa $13c (PREF.DAT) + 0x4a, SIG_UINT16(0x04), // send 4 + 0xa1, 0xbb, // sag $bb + 0x36, // push + 0x43, 0x75, SIG_UINT16(0x04), // callk DoSound[75], 4 + SIG_END +}; + +static const uint16 phant1PatchSavedVolume[] = { + 0x32, PATCH_UINT16(36), // jmp [to prefFile::close] + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry phantasmagoriaSignatures[] = { { true, 901, "invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch }, + { true, 1111, "ignore audio settings from save game", 1, phant1SignatureSavedVolume, phant1PatchSavedVolume }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -3312,6 +3384,50 @@ static const SciScriptPatcherEntry pq4Signatures[] = { SCI_SIGNATUREENTRY_TERMINATOR }; +#pragma mark - +#pragma mark Police Quest: SWAT + +// The init code that runs when PQ:SWAT starts up unconditionally resets the +// master sound volume to 127, but the game should always use the volume stored +// in ScummVM. +// Applies to at least: English CD +static const uint16 pqSwatSignatureVolumeReset1[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x21a), // pushi $21a (masterVolume) + 0x78, // push1 + 0x39, 0x7f, // push $7f + 0x54, SIG_UINT16(0x06), // self 6 + SIG_END +}; + +static const uint16 pqSwatPatchVolumeReset1[] = { + 0x32, PATCH_UINT16(6), // jmp 6 [past volume reset] + PATCH_END +}; + +// pqInitCode::doit +static const uint16 pqSwatSignatureVolumeReset2[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x21a), // pushi $21a (masterVolume) + 0x78, // push1 + 0x39, 0x0f, // pushi $f + 0x81, 0x01, // lag 1 + 0x4a, SIG_UINT16(0x06), // send 6 + SIG_END +}; + +static const uint16 pqSwatPatchVolumeReset2[] = { + 0x32, PATCH_UINT16(8), // jmp 8 [past volume reset] + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry pqSwatSignatures[] = { + { true, 0, "disable volume reset on startup", 1, pqSwatSignatureVolumeReset1, pqSwatPatchVolumeReset1 }, + { true, 1, "disable volume reset on startup", 1, pqSwatSignatureVolumeReset2, pqSwatPatchVolumeReset2 }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + #endif // =========================================================================== @@ -4284,8 +4400,41 @@ static const SciScriptPatcherEntry qfg3Signatures[] = { #pragma mark - #pragma mark Quest for Glory 4 +// The init code that runs when QFG4 starts up unconditionally resets the +// master music volume to 15, but the game should always use the volume stored +// in ScummVM. +// Applies to at least: English floppy +static const uint16 qfg4SignatureVolumeReset[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x215), // pushi $215 (masterVolume) + 0x78, // push1 + 0x39, 0x0f, // pushi $f + 0x81, 0x01, // lag 1 (Glory object) + 0x4a, SIG_UINT16(0x06), // send 6 + SIG_END +}; + +// Same as above, but with a different masterVolume selector. +// Applies to at least: English CD +static const uint16 qfg4CDSignatureVolumeReset[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x217), // pushi $217 (masterVolume) + 0x78, // push1 + 0x39, 0x0f, // pushi $f + 0x81, 0x01, // lag 1 (Glory object) + 0x4a, SIG_UINT16(0x06), // send 6 + SIG_END +}; + +static const uint16 qfg4PatchVolumeReset[] = { + 0x32, PATCH_UINT16(8), // jmp 8 [past volume changes] + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry qfg4Signatures[] = { + { true, 1, "disable volume reset on startup (floppy)", 1, qfg4SignatureVolumeReset, qfg4PatchVolumeReset }, + { true, 1, "disable volume reset on startup (CD)", 1, qfg4CDSignatureVolumeReset, qfg4PatchVolumeReset }, { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch }, @@ -4948,8 +5097,57 @@ static const SciScriptPatcherEntry sq6Signatures[] = { #pragma mark - #pragma mark Torins Passage +// The init code that runs when Torin starts up unconditionally resets the +// master music volume to defaults, but the game should always use the volume +// stored in ScummVM. +// Applies to at least: English CD +static const uint16 torinVolumeResetSignature1[] = { + SIG_MAGICDWORD, + 0x35, 0x28, // ldi $28 + 0xa1, 0xe3, // sag $e3 (music volume) + 0x35, 0x3c, // ldi $3c + 0xa1, 0xe4, // sag $e4 (sfx volume) + 0x35, 0x64, // ldi $64 + 0xa1, 0xe5, // sag $e5 (speech volume) + SIG_END +}; + +static const uint16 torinVolumeResetPatch1[] = { + 0x33, 0x0a, // jmp [past volume resets] + PATCH_END +}; + +// The init code that runs when Torin starts up unconditionally resets the +// master music volume to values stored in torin.prf, but the game should always +// use the volume stored in ScummVM. +// Applies to at least: English CD +static const uint16 torinVolumeResetSignature2[] = { + SIG_MAGICDWORD, + 0x38, SIG_UINT16(0x20b), // pushi $020b + 0x76, // push0 + SIG_ADDTOOFFSET(6), // advance file stream + 0xa1, 0xe3, // sag $e3 (music volume) + SIG_ADDTOOFFSET(10), // advance file stream + 0xa1, 0xe4, // sag $e4 (sfx volume) + SIG_ADDTOOFFSET(10), // advance file stream + 0xa1, 0xe5, // sag $e5 (speech volume) + SIG_END +}; + +static const uint16 torinVolumeResetPatch2[] = { + PATCH_ADDTOOFFSET(10), // advance file stream + 0x18, 0x18, // waste bytes + PATCH_ADDTOOFFSET(10), // advance file stream + 0x18, 0x18, // waste bytes + PATCH_ADDTOOFFSET(10), // advance file stream + 0x18, 0x18, // waste bytes + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry torinSignatures[] = { + { true, 64000, "disable volume reset on startup 1/2", 1, torinVolumeResetSignature1, torinVolumeResetPatch1 }, + { true, 64000, "disable volume reset on startup 2/2", 1, torinVolumeResetSignature2, torinVolumeResetPatch2 }, { true, 64990, "increase number of save games", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 }, { true, 64990, "increase number of save games", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 }, SCI_SIGNATUREENTRY_TERMINATOR @@ -5469,6 +5667,9 @@ void ScriptPatcher::processScript(uint16 scriptNr, SciSpan<byte> scriptData) { case GID_PQ4: signatureTable = pq4Signatures; break; + case GID_PQSWAT: + signatureTable = pqSwatSignatures; + break; #endif case GID_QFG1: signatureTable = qfg1egaSignatures; diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp index 2bc4051a79..8a1a9e76d4 100644 --- a/engines/sci/engine/selector.cpp +++ b/engines/sci/engine/selector.cpp @@ -202,6 +202,20 @@ void Kernel::mapSelectors() { FIND_SELECTOR(magnifier); FIND_SELECTOR(frameOut); FIND_SELECTOR(casts); + FIND_SELECTOR(setVol); + FIND_SELECTOR(reSyncVol); + FIND_SELECTOR(set); + FIND_SELECTOR(clear); + FIND_SELECTOR(curPos); + FIND_SELECTOR(update); + FIND_SELECTOR(show); + FIND_SELECTOR(position); + FIND_SELECTOR(musicVolume); + FIND_SELECTOR(soundVolume); + FIND_SELECTOR(initialOff); + FIND_SELECTOR(setPos); + FIND_SELECTOR(setSize); + FIND_SELECTOR(displayValue); #endif } diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h index 8d1edeb489..d8c0bf711b 100644 --- a/engines/sci/engine/selector.h +++ b/engines/sci/engine/selector.h @@ -158,6 +158,20 @@ struct SelectorCache { Selector magnifier; Selector frameOut; Selector casts; // needed for sync'ing screen items/planes with scripts, when our save/restore code is patched in (see GfxFrameout::syncWithScripts) + Selector setVol; // for GK2 volume sync on restore + Selector reSyncVol; // for Torin volume sync on restore + Selector set; // for LSL6hires subtitle sync + Selector clear; // for LSL6hires subtitle sync + Selector curPos; // for LSL6hires volume sync + Selector update; // for LSL6hires volume sync + Selector show; // for GK1 volume sync + Selector position; // for GK1 volume sync + Selector musicVolume; // for GK1 volume sync + Selector soundVolume; // for GK1 volume sync + Selector initialOff; // for GK2 volume sync + Selector setPos; // for Torin volume sync + Selector setSize; // for PQ4 volume sync + Selector displayValue; // for PQ:SWAT volume sync #endif }; diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp index 76df322b30..6e2229727f 100644 --- a/engines/sci/engine/state.cpp +++ b/engines/sci/engine/state.cpp @@ -19,14 +19,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ - #include "common/system.h" #include "sci/sci.h" // for INCLUDE_OLDGFX #include "sci/debug.h" // for g_debug_sleeptime_factor -#include "sci/event.h" - #include "sci/engine/file.h" +#include "sci/engine/guest_additions.h" #include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" @@ -84,6 +82,8 @@ void EngineState::reset(bool isRestoring) { _memorySegmentSize = 0; _fileHandles.resize(5); abortScriptProcessing = kAbortNone; + } else { + g_sci->_guestAdditions->reset(); } // reset delayed restore game functionality @@ -120,7 +120,6 @@ void EngineState::reset(bool isRestoring) { scriptGCInterval = GC_INTERVAL; _videoState.reset(); - _syncedAudioOptions = false; } void EngineState::speedThrottler(uint32 neededSleep) { diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h index 5297a176d3..dd6b728716 100644 --- a/engines/sci/engine/state.h +++ b/engines/sci/engine/state.h @@ -214,7 +214,6 @@ public: // TODO: Excise video code from the state manager VideoState _videoState; - bool _syncedAudioOptions; /** * Resets the engine state. diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index 3535b278c0..7e514a2dc6 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -27,7 +27,13 @@ #include "sci/sci.h" #include "sci/console.h" #include "sci/resource.h" +#ifdef ENABLE_SCI32 +#include "audio/mixer.h" +#include "sci/sound/audio32.h" +#include "sci/sound/music.h" +#endif #include "sci/engine/features.h" +#include "sci/engine/guest_additions.h" #include "sci/engine/state.h" #include "sci/engine/kernel.h" #include "sci/engine/object.h" @@ -199,36 +205,7 @@ static void write_var(EngineState *s, int type, int index, reg_t value) { s->variables[type][index] = value; -#ifdef ENABLE_SCI32 - if (type == VAR_GLOBAL && getSciVersion() >= SCI_VERSION_2 && g_sci->getEngineState()->_syncedAudioOptions) { - - switch (g_sci->getGameId()) { - case GID_LSL6HIRES: - if (index == kGlobalVarLSL6HiresTextSpeed) { - ConfMan.setInt("talkspeed", (14 - value.toSint16()) * 255 / 13); - } - break; - default: - if (index == kGlobalVarTextSpeed) { - ConfMan.setInt("talkspeed", (8 - value.toSint16()) * 255 / 8); - } - } - } -#endif - - if (type == VAR_GLOBAL && index == kGlobalVarMessageType) { - // The game is trying to change its speech/subtitle settings - if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) { - // ScummVM audio options haven't been applied yet, so apply them. - // We also force the ScummVM audio options when loading a game from - // the launcher. - g_sci->syncIngameAudioOptions(); - g_sci->getEngineState()->_syncedAudioOptions = true; - } else { - // Update ScummVM's audio options - g_sci->updateScummVMAudioOptions(); - } - } + g_sci->_guestAdditions->writeVarHook(type, index, value); } } @@ -311,6 +288,10 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt if (argc > 0x800) // More arguments than the stack could possibly accomodate for error("send_selector(): More than 0x800 arguments to function call"); +#ifdef ENABLE_SCI32 + g_sci->_guestAdditions->sendSelectorHook(send_obj, selector, argp); +#endif + SelectorType selectorType = lookupSelector(s->_segMan, send_obj, selector, &varp, &funcp); if (selectorType == kSelectorNone) error("Send to invalid selector 0x%x of object at %04x:%04x", 0xffff & selector, PRINT_REG(send_obj)); diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h index 0444cf5ffc..1ac6fed0b6 100644 --- a/engines/sci/engine/vm.h +++ b/engines/sci/engine/vm.h @@ -142,16 +142,32 @@ enum GlobalVar { kGlobalVarCurrentRoom = 2, kGlobalVarSpeed = 3, // SCI16 kGlobalVarQuit = 4, + kGlobalVarSounds = 8, kGlobalVarPlanes = 10, // SCI32 kGlobalVarCurrentRoomNo = 11, kGlobalVarPreviousRoomNo = 12, kGlobalVarNewRoomNo = 13, kGlobalVarScore = 15, + kGlobalVarGK2MusicVolume = 76, // 0 to 127 kGlobalVarFastCast = 84, // SCI16 kGlobalVarMessageType = 90, kGlobalVarTextSpeed = 94, // SCI32; 0 is fastest, 8 is slowest - kGlobalVarLSL6HiresTextSpeed = 167, // 1 is fastest, 14 is slowest - kGlobalVarShivers1Score = 349 + kGlobalVarGK1Music1 = 102, // 0 to 127 + kGlobalVarGK1Music2 = 103, // 0 to 127 + kGlobalVarLSL6HiresGameFlags = 137, + kGlobalVarGK1NarratorMode = 166, // 0 for text, 1 for speech + kGlobalVarPhant1MusicVolume = 187, // 0 to 15 + kGlobalVarPhant1DACVolume = 188, // 0 to 127 + kGlobalVarLSL6HiresMusicVolume = 194, // 0 to 13 + kGlobalVarGK1DAC1 = 207, // 0 to 127 + kGlobalVarGK1DAC2 = 208, // 0 to 127 + kGlobalVarLSL6HiresRestoreTextWindow = 210, + kGlobalVarGK1DAC3 = 211, // 0 to 127 + kGlobalVarShiversFlags = 211, + kGlobalVarTorinMusicVolume = 227, // 0 to 100 + kGlobalVarTorinSFXVolume = 228, // 0 to 100 + kGlobalVarTorinSpeechVolume = 229, // 0 to 100 + kGlobalVarShivers1Score = 349 }; /** Number of kernel calls in between gcs; should be < 50000 */ diff --git a/engines/sci/module.mk b/engines/sci/module.mk index eb2c6a148b..7decff6e7a 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -12,6 +12,7 @@ MODULE_OBJS := \ engine/features.o \ engine/file.o \ engine/gc.o \ + engine/guest_additions.o \ engine/kernel.o \ engine/kevent.o \ engine/kfile.o \ diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 34930a648e..c8e500b700 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -33,6 +33,7 @@ #include "sci/event.h" #include "sci/engine/features.h" +#include "sci/engine/guest_additions.h" #include "sci/engine/message.h" #include "sci/engine/object.h" #include "sci/engine/state.h" @@ -94,6 +95,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam _video32 = nullptr; _gfxCursor32 = nullptr; #endif + _guestAdditions = nullptr; _features = 0; _resMan = 0; _gamestate = 0; @@ -202,6 +204,7 @@ SciEngine::~SciEngine() { delete _kernel; delete _vocabulary; delete _console; + delete _guestAdditions; delete _features; delete _gfxMacIconBar; @@ -280,6 +283,7 @@ Common::Error SciEngine::run() { _vocabulary = new Vocabulary(_resMan, false); _gamestate = new EngineState(segMan); + _guestAdditions = new GuestAdditions(_gamestate, _features); _eventMan = new EventManager(_resMan->detectFontExtended()); #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2_1_EARLY) { @@ -331,7 +335,7 @@ Common::Error SciEngine::run() { _soundCmd = new SoundCommandParser(_resMan, segMan, _kernel, _audio, _features->detectDoSoundType()); syncSoundSettings(); - syncIngameAudioOptions(); + _guestAdditions->syncAudioOptionsFromScummVM(); // Patch in our save/restore code, so that dialogs are replaced patchGameSaveRestore(); @@ -834,14 +838,14 @@ void SciEngine::runGame() { if (DebugMan.isDebugChannelEnabled(kDebugLevelOnStartup)) _console->attach(); - _gamestate->_syncedAudioOptions = false; + _guestAdditions->reset(); do { _gamestate->_executionStackPosChanged = false; run_vm(_gamestate); exitGame(); - _gamestate->_syncedAudioOptions = true; + _guestAdditions->sciEngineRunGameHook(); if (_gamestate->abortScriptProcessing == kAbortRestartGame) { _gamestate->_segMan->resetSegMan(); @@ -854,7 +858,7 @@ void SciEngine::runGame() { if (_gfxMenu) _gfxMenu->reset(); _gamestate->abortScriptProcessing = kAbortNone; - _gamestate->_syncedAudioOptions = false; + _guestAdditions->reset(); } else if (_gamestate->abortScriptProcessing == kAbortLoadGame) { _gamestate->abortScriptProcessing = kAbortNone; _gamestate->_executionStack.clear(); @@ -865,8 +869,7 @@ void SciEngine::runGame() { _gamestate->abortScriptProcessing = kAbortNone; syncSoundSettings(); - syncIngameAudioOptions(); - // Games do not set their audio settings when loading + _guestAdditions->syncAudioOptionsFromScummVM(); } else { break; // exit loop } @@ -1055,150 +1058,7 @@ void SciEngine::pauseEngineIntern(bool pause) { void SciEngine::syncSoundSettings() { Engine::syncSoundSettings(); - - bool mute = false; - if (ConfMan.hasKey("mute")) - mute = ConfMan.getBool("mute"); - - int soundVolumeMusic = (mute ? 0 : ConfMan.getInt("music_volume")); - - if (_gamestate && _soundCmd) { - int vol = (soundVolumeMusic + 1) * MUSIC_MASTERVOLUME_MAX / Audio::Mixer::kMaxMixerVolume; - _soundCmd->setMasterVolume(vol); - } -} - -void SciEngine::syncIngameAudioOptions() { - bool useGlobal90 = false; - - // Sync the in-game speech/subtitles settings for SCI1.1 CD games - if (isCD()) { - switch (getSciVersion()) { - case SCI_VERSION_1_1: - // All SCI1.1 CD games use global 90 - useGlobal90 = true; - break; -#ifdef ENABLE_SCI32 - case SCI_VERSION_2: - case SCI_VERSION_2_1_EARLY: - case SCI_VERSION_2_1_MIDDLE: - case SCI_VERSION_2_1_LATE: - // Only use global 90 for some specific games, not all SCI32 games used this method - switch (_gameId) { - case GID_KQ7: // SCI2.1 - case GID_GK1: // SCI2 - case GID_GK2: // SCI2.1 - case GID_SQ6: // SCI2.1 - case GID_TORIN: // SCI2.1 - case GID_QFG4: // SCI2.1 - case GID_PQ4: // SCI2 - case GID_PHANTASMAGORIA: // SCI2.1 - case GID_MOTHERGOOSEHIRES: // SCI2.1 - useGlobal90 = true; - break; - case GID_LSL6: // SCI2.1 - // TODO: Uses gameFlags array - break; - // Shivers does not use global 90 - // Police Quest: SWAT does not use global 90 - // - // TODO: Unknown at the moment: - // LSL7, Lighthouse, RAMA, Phantasmagoria 2 - default: - return; - } - break; -#endif // ENABLE_SCI32 - default: - return; - } - - bool subtitlesOn = ConfMan.getBool("subtitles"); - bool speechOn = !ConfMan.getBool("speech_mute"); - -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2) { - GlobalVar index; - uint16 textSpeed; - - switch (g_sci->getGameId()) { - case GID_LSL6HIRES: - index = kGlobalVarLSL6HiresTextSpeed; - textSpeed = 14 - ConfMan.getInt("talkspeed") * 14 / 255 + 1; - break; - default: - index = kGlobalVarTextSpeed; - textSpeed = 8 - ConfMan.getInt("talkspeed") * 8 / 255; - } - - _gamestate->variables[VAR_GLOBAL][index] = make_reg(0, textSpeed); - } -#endif - - if (useGlobal90) { - if (subtitlesOn && !speechOn) { - _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 1); // subtitles - } else if (!subtitlesOn && speechOn) { - _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 2); // speech - } else if (subtitlesOn && speechOn) { - // Is it a game that supports simultaneous speech and subtitles? - switch (_gameId) { - case GID_SQ4: - case GID_FREDDYPHARKAS: - case GID_ECOQUEST: - case GID_LSL6: - case GID_LAURABOW2: - case GID_KQ6: -#ifdef ENABLE_SCI32 - // Unsure about Gabriel Knight 2 - case GID_KQ7: // SCI2.1 - case GID_GK1: // SCI2 - case GID_SQ6: // SCI2.1, SQ6 seems to always use subtitles anyway - case GID_TORIN: // SCI2.1 - case GID_QFG4: // SCI2.1 - case GID_PQ4: // SCI2 - // Phantasmagoria does not support simultaneous speech + subtitles - // Mixed Up Mother Goose Deluxe does not support simultaneous speech + subtitles -#endif // ENABLE_SCI32 - _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 3); // speech + subtitles - break; - default: - // Game does not support speech and subtitles, set it to speech - _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 2); // speech - } - } - } - } -} - -void SciEngine::updateScummVMAudioOptions() { - // Update ScummVM's speech/subtitles settings for SCI1.1 CD games, - // depending on the in-game settings - if ((isCD() && getSciVersion() == SCI_VERSION_1_1) || - getSciVersion() >= SCI_VERSION_2) { - - uint16 ingameSetting = _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType].getOffset(); - - switch (ingameSetting) { - case 1: - // subtitles - ConfMan.setBool("subtitles", true); - ConfMan.setBool("speech_mute", true); - break; - case 2: - // speech - ConfMan.setBool("subtitles", false); - ConfMan.setBool("speech_mute", false); - break; - case 3: - // speech + subtitles - ConfMan.setBool("subtitles", true); - ConfMan.setBool("speech_mute", false); - break; - default: - break; - } - } + _guestAdditions->syncSoundSettings(); } void SciEngine::loadMacExecutable() { diff --git a/engines/sci/sci.h b/engines/sci/sci.h index f5ccc19d7c..744a1b4aed 100644 --- a/engines/sci/sci.h +++ b/engines/sci/sci.h @@ -62,6 +62,7 @@ class Vocabulary; class ResourceManager; class Kernel; class GameFeatures; +class GuestAdditions; class Console; class AudioPlayer; class SoundCommandParser; @@ -263,28 +264,6 @@ public: uint32 getTickCount(); void setTickCount(const uint32 ticks); - /** - * Syncs the audio options of the ScummVM launcher (speech, subtitles or - * both) with the in-game audio options of certain CD game versions. For - * some games, this allows simultaneous playing of speech and subtitles, - * even if the original games didn't support this feature. - * - * SCI1.1 games which support simultaneous speech and subtitles: - * - EcoQuest 1 CD - * - Leisure Suit Larry 6 CD - * SCI1.1 games which don't support simultaneous speech and subtitles, - * and we add this functionality in ScummVM: - * - Space Quest 4 CD - * - Freddy Pharkas CD - * - Laura Bow 2 CD - * SCI1.1 games which don't support simultaneous speech and subtitles, - * and we haven't added any extra functionality in ScummVM because extra - * script patches are needed: - * - King's Quest 6 CD - */ - void syncIngameAudioOptions(); - void updateScummVMAudioOptions(); - const SciGameId &getGameId() const { return _gameId; } const char *getGameIdStr() const; int getResourceVersion() const; @@ -398,6 +377,7 @@ public: Sync *_sync; SoundCommandParser *_soundCmd; GameFeatures *_features; + GuestAdditions *_guestAdditions; opcode_format (*_opcode_formats)[4]; diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp index 69a83ebc8e..e9e90e41a7 100644 --- a/engines/sci/sound/audio32.cpp +++ b/engines/sci/sound/audio32.cpp @@ -36,6 +36,8 @@ #include "common/types.h" // for Flag::NO #include "engine.h" // for Engine, g_engine #include "sci/engine/features.h" // for GameFeatures +#include "sci/engine/guest_additions.h" // for GuestAdditions +#include "sci/engine/state.h" // for EngineState #include "sci/engine/vm_types.h" // for reg_t, make_reg, NULL_REG #include "sci/resource.h" // for ResourceId, ResourceType::kResour... #include "sci/sci.h" // for SciEngine, g_sci, getSciVersion @@ -118,6 +120,9 @@ Audio32::Audio32(ResourceManager *resMan) : } _useModifiedAttenuation = g_sci->_features->usesModifiedAudioAttenuation(); + // The mixer stream type is given as `kSFXSoundType` so that audio from + // Audio32 will be mixed at the same standard volume as the video players + // (which must use `kSFXSoundType` as well). _mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); } @@ -980,7 +985,7 @@ reg_t Audio32::kernelPlay(const bool autoPlay, const int argc, const reg_t *cons int16 Audio32::getVolume(const int16 channelIndex) const { if (channelIndex < 0 || channelIndex >= _numActiveChannels) { - return _mixer->getChannelVolume(_handle) * kMaxVolume / Audio::Mixer::kMaxChannelVolume; + return (_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) + 1) * kMaxVolume / Audio::Mixer::kMaxMixerVolume; } Common::StackLock lock(_mutex); @@ -990,10 +995,9 @@ int16 Audio32::getVolume(const int16 channelIndex) const { void Audio32::setVolume(const int16 channelIndex, int16 volume) { volume = MIN<int16>(kMaxVolume, volume); if (channelIndex == kAllChannels) { - ConfMan.setInt("sfx_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume); - ConfMan.setInt("speech_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume); - _mixer->setChannelVolume(_handle, volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume); - g_engine->syncSoundSettings(); + if (!g_sci->_guestAdditions->audio32SetVolumeHook(channelIndex, volume)) { + setMasterVolume(volume); + } } else if (channelIndex != kNoExistingChannel) { Common::StackLock lock(_mutex); getChannel(channelIndex).volume = volume; diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h index 9130cbe687..8a1d8cfb43 100644 --- a/engines/sci/sound/audio32.h +++ b/engines/sci/sound/audio32.h @@ -504,6 +504,13 @@ public: } /** + * Sets the master volume for digital audio playback. + */ + void setMasterVolume(const int16 volume) { + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume); + } + + /** * Initiate an immediate fade of the given channel. */ bool fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade); diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp index b9a764c93a..e0cbb97d19 100644 --- a/engines/sci/sound/soundcmd.cpp +++ b/engines/sci/sound/soundcmd.cpp @@ -29,6 +29,7 @@ #include "sci/sound/soundcmd.h" #include "sci/engine/features.h" +#include "sci/engine/guest_additions.h" #include "sci/engine/kernel.h" #include "sci/engine/object.h" #include "sci/engine/selector.h" @@ -57,10 +58,10 @@ SoundCommandParser::~SoundCommandParser() { delete _music; } -reg_t SoundCommandParser::kDoSoundInit(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundInit(EngineState *s, int argc, reg_t *argv) { debugC(kDebugLevelSound, "kDoSound(init): %04x:%04x", PRINT_REG(argv[0])); processInitSound(argv[0]); - return acc; + return s->r_acc; } int SoundCommandParser::getSoundResourceId(reg_t obj) { @@ -153,13 +154,13 @@ void SoundCommandParser::processInitSound(reg_t obj) { } } -reg_t SoundCommandParser::kDoSoundPlay(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundPlay(EngineState *s, int argc, reg_t *argv) { debugC(kDebugLevelSound, "kDoSound(play): %04x:%04x", PRINT_REG(argv[0])); bool playBed = false; if (argc >= 2 && !argv[1].isNull()) playBed = true; processPlaySound(argv[0], playBed); - return acc; + return s->r_acc; } void SoundCommandParser::processPlaySound(reg_t obj, bool playBed) { @@ -225,10 +226,10 @@ void SoundCommandParser::processPlaySound(reg_t obj, bool playBed) { musicSlot->fadeStep = 0; } -reg_t SoundCommandParser::kDoSoundDispose(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundDispose(EngineState *s, int argc, reg_t *argv) { debugC(kDebugLevelSound, "kDoSound(dispose): %04x:%04x", PRINT_REG(argv[0])); processDisposeSound(argv[0]); - return acc; + return s->r_acc; } void SoundCommandParser::processDisposeSound(reg_t obj) { @@ -248,10 +249,10 @@ void SoundCommandParser::processDisposeSound(reg_t obj) { writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundStopped); } -reg_t SoundCommandParser::kDoSoundStop(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundStop(EngineState *s, int argc, reg_t *argv) { debugC(kDebugLevelSound, "kDoSound(stop): %04x:%04x", PRINT_REG(argv[0])); processStopSound(argv[0], false); - return acc; + return s->r_acc; } void SoundCommandParser::processStopSound(reg_t obj, bool sampleFinishedPlaying) { @@ -282,7 +283,7 @@ void SoundCommandParser::processStopSound(reg_t obj, bool sampleFinishedPlaying) _music->soundStop(musicSlot); } -reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundPause(EngineState *s, int argc, reg_t *argv) { if (argc == 1) debugC(kDebugLevelSound, "kDoSound(pause): %04x:%04x", PRINT_REG(argv[0])); else @@ -333,7 +334,7 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) { if (!musicSlot) { // This happens quite frequently debugC(kDebugLevelSound, "kDoSound(pause): Slot not found (%04x:%04x)", PRINT_REG(obj)); - return acc; + return s->r_acc; } #ifdef ENABLE_SCI32 @@ -354,17 +355,17 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) { #endif _music->soundToggle(musicSlot, shouldPause); } - return acc; + return s->r_acc; } // SCI0 only command // It's called right after restoring a game - it's responsible to kick off playing music again // we don't need this at all, so we don't do anything here -reg_t SoundCommandParser::kDoSoundResumeAfterRestore(int argc, reg_t *argv, reg_t acc) { - return acc; +reg_t SoundCommandParser::kDoSoundResumeAfterRestore(EngineState *s, int argc, reg_t *argv) { + return s->r_acc; } -reg_t SoundCommandParser::kDoSoundMute(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundMute(EngineState *s, int argc, reg_t *argv) { uint16 previousState = _music->soundGetSoundOn(); if (argc > 0) { debugC(kDebugLevelSound, "kDoSound(mute): %d", argv[0].toUint16()); @@ -374,37 +375,33 @@ reg_t SoundCommandParser::kDoSoundMute(int argc, reg_t *argv, reg_t acc) { return make_reg(0, previousState); } -reg_t SoundCommandParser::kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc) { - acc = make_reg(0, _music->soundGetMasterVolume()); +reg_t SoundCommandParser::kDoSoundMasterVolume(EngineState *s, int argc, reg_t *argv) { + s->r_acc = make_reg(0, _music->soundGetMasterVolume()); if (argc > 0) { debugC(kDebugLevelSound, "kDoSound(masterVolume): %d", argv[0].toSint16()); int vol = CLIP<int16>(argv[0].toSint16(), 0, MUSIC_MASTERVOLUME_MAX); - vol = vol * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX; - ConfMan.setInt("music_volume", vol); - // In SCI32, digital audio volume is controlled separately by - // kDoAudioVolume - if (_soundVersion < SCI_VERSION_2_1_EARLY) { - ConfMan.setInt("sfx_volume", vol); + + if (!g_sci->_guestAdditions->kDoSoundMasterVolumeHook(vol)) { + setMasterVolume(vol); } - g_engine->syncSoundSettings(); } - return acc; + return s->r_acc; } -reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundFade(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; // The object can be null in several SCI0 games (e.g. Camelot, KQ1, KQ4, MUMG). // Check bugs #3035149, #3036942 and #3578335. // In this case, we just ignore the call. if (obj.isNull() && argc == 1) - return acc; + return s->r_acc; MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { debugC(kDebugLevelSound, "kDoSound(fade): Slot not found (%04x:%04x)", PRINT_REG(obj)); - return acc; + return s->r_acc; } int volume = musicSlot->volume; @@ -412,7 +409,7 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) { #ifdef ENABLE_SCI32 if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) { g_sci->_audio32->fadeChannel(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj, argv[1].toSint16(), argv[2].toSint16(), argv[3].toSint16(), argc > 4 ? (bool)argv[4].toSint16() : false); - return acc; + return s->r_acc; } #endif @@ -420,7 +417,7 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) { if (musicSlot->status != kSoundPlaying) { debugC(kDebugLevelSound, "kDoSound(fade): %04x:%04x fading requested, but sound is currently not playing", PRINT_REG(obj)); writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); - return acc; + return s->r_acc; } switch (argc) { @@ -439,7 +436,7 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) { // Check if the song is already at the requested volume. If it is, don't // perform any fading. Happens for example during the intro of Longbow. if (musicSlot->fadeTo == musicSlot->volume) - return acc; + return s->r_acc; // Sometimes we get objects in that position, so fix the value (refer to workarounds.cpp) if (!argv[1].getSegment()) @@ -464,14 +461,14 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) { } debugC(kDebugLevelSound, "kDoSound(fade): %04x:%04x to %d, step %d, ticker %d", PRINT_REG(obj), musicSlot->fadeTo, musicSlot->fadeStep, musicSlot->fadeTickerStep); - return acc; + return s->r_acc; } -reg_t SoundCommandParser::kDoSoundGetPolyphony(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundGetPolyphony(EngineState *s, int argc, reg_t *argv) { return make_reg(0, _music->soundGetVoices()); // Get the number of voices } -reg_t SoundCommandParser::kDoSoundUpdate(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundUpdate(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; debugC(kDebugLevelSound, "kDoSound(update): %04x:%04x", PRINT_REG(argv[0])); @@ -479,7 +476,7 @@ reg_t SoundCommandParser::kDoSoundUpdate(int argc, reg_t *argv, reg_t acc) { MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { warning("kDoSound(update): Slot not found (%04x:%04x)", PRINT_REG(obj)); - return acc; + return s->r_acc; } musicSlot->loop = readSelectorValue(_segMan, obj, SELECTOR(loop)); @@ -489,12 +486,12 @@ reg_t SoundCommandParser::kDoSoundUpdate(int argc, reg_t *argv, reg_t acc) { int16 objPrio = readSelectorValue(_segMan, obj, SELECTOR(priority)); if (objPrio != musicSlot->priority) _music->soundSetPriority(musicSlot, objPrio); - return acc; + return s->r_acc; } -reg_t SoundCommandParser::kDoSoundUpdateCues(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv) { processUpdateCues(argv[0]); - return acc; + return s->r_acc; } void SoundCommandParser::processUpdateCues(reg_t obj) { @@ -608,7 +605,7 @@ void SoundCommandParser::processUpdateCues(reg_t obj) { } } -reg_t SoundCommandParser::kDoSoundSendMidi(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv) { // The 4 parameter variant of this call is used in at least LSL1VGA, room // 110 (Lefty's bar), to distort the music when Larry is drunk and stands // up - bug #3614447. @@ -637,13 +634,13 @@ reg_t SoundCommandParser::kDoSoundSendMidi(int argc, reg_t *argv, reg_t acc) { // if so, allow it //_music->sendMidiCommand(_midiCommand); warning("kDoSound(sendMidi): Slot not found (%04x:%04x)", PRINT_REG(obj)); - return acc; + return s->r_acc; } _music->sendMidiCommand(musicSlot, midiCommand); - return acc; + return s->r_acc; } -reg_t SoundCommandParser::kDoSoundGlobalReverb(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundGlobalReverb(EngineState *s, int argc, reg_t *argv) { byte prevReverb = _music->getCurrentReverb(); byte reverb = argv[0].toUint16() & 0xF; @@ -656,7 +653,7 @@ reg_t SoundCommandParser::kDoSoundGlobalReverb(int argc, reg_t *argv, reg_t acc) return make_reg(0, prevReverb); } -reg_t SoundCommandParser::kDoSoundSetHold(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundSetHold(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; debugC(kDebugLevelSound, "doSoundSetHold: %04x:%04x, %d", PRINT_REG(argv[0]), argv[1].toUint16()); @@ -664,24 +661,24 @@ reg_t SoundCommandParser::kDoSoundSetHold(int argc, reg_t *argv, reg_t acc) { MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { warning("kDoSound(setHold): Slot not found (%04x:%04x)", PRINT_REG(obj)); - return acc; + return s->r_acc; } // Set the special hold marker ID where the song should be looped at. musicSlot->hold = argv[1].toSint16(); - return acc; + return s->r_acc; } -reg_t SoundCommandParser::kDoSoundGetAudioCapability(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv) { // Tests for digital audio support return make_reg(0, 1); } -reg_t SoundCommandParser::kDoSoundStopAll(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundStopAll(EngineState *s, int argc, reg_t *argv) { // TODO: this can't be right, this gets called in kq1 - e.g. being in witch house, getting the note // now the point jingle plays and after a messagebox they call this - and would stop the background effects with it // this doesn't make sense, so i disable it for now - return acc; + return s->r_acc; Common::StackLock(_music->_mutex); @@ -697,10 +694,10 @@ reg_t SoundCommandParser::kDoSoundStopAll(int argc, reg_t *argv, reg_t acc) { (*i)->dataInc = 0; _music->soundStop(*i); } - return acc; + return s->r_acc; } -reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; int16 value = argv[1].toSint16(); @@ -711,7 +708,7 @@ reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) { // the drum sounds of the energizer bunny at the beginning), so this is // normal behavior. //warning("cmdSetSoundVolume: Slot not found (%04x:%04x)", PRINT_REG(obj)); - return acc; + return s->r_acc; } debugC(kDebugLevelSound, "kDoSound(setVolume): %d", value); @@ -724,15 +721,20 @@ reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) { _music->soundSetVolume(musicSlot, value); } #endif + if (musicSlot->volume != value) { musicSlot->volume = value; _music->soundSetVolume(musicSlot, value); writeSelectorValue(_segMan, obj, SELECTOR(vol), value); +#ifdef ENABLE_SCI32 + g_sci->_guestAdditions->kDoSoundSetVolumeHook(obj, value); +#endif } - return acc; + + return s->r_acc; } -reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundSetPriority(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; int16 value = argv[1].toSint16(); @@ -741,7 +743,7 @@ reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc) MusicEntry *musicSlot = _music->getSlot(obj); if (!musicSlot) { debugC(kDebugLevelSound, "kDoSound(setPriority): Slot not found (%04x:%04x)", PRINT_REG(obj)); - return acc; + return s->r_acc; } if (value == -1) { @@ -759,10 +761,10 @@ reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc) _music->soundSetPriority(musicSlot, value); } - return acc; + return s->r_acc; } -reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundSetLoop(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; int16 value = argv[1].toSint16(); @@ -780,7 +782,7 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) { } else { // Doesn't really matter } - return acc; + return s->r_acc; } #ifdef ENABLE_SCI32 @@ -805,13 +807,13 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) { } #endif - return acc; + return s->r_acc; } -reg_t SoundCommandParser::kDoSoundSuspend(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundSuspend(EngineState *s, int argc, reg_t *argv) { // TODO warning("kDoSound(suspend): STUB"); - return acc; + return s->r_acc; } void SoundCommandParser::updateSci0Cues() { @@ -879,6 +881,17 @@ void SoundCommandParser::setMasterVolume(int vol) { _music->soundSetMasterVolume(vol); } +#ifdef ENABLE_SCI32 +void SoundCommandParser::setVolume(const reg_t obj, const int volume) { + MusicEntry *slot = _music->getSlot(obj); + if (slot != nullptr) { + slot->volume = volume; + writeSelectorValue(_segMan, obj, SELECTOR(vol), volume); + _music->soundSetVolume(slot, volume); + } +} +#endif + void SoundCommandParser::pauseAll(bool pause) { _music->pauseAll(pause); } diff --git a/engines/sci/sound/soundcmd.h b/engines/sci/sound/soundcmd.h index 5bb7cf2cb1..928c9b1acb 100644 --- a/engines/sci/sound/soundcmd.h +++ b/engines/sci/sound/soundcmd.h @@ -46,7 +46,7 @@ public: SoundCommandParser(ResourceManager *resMan, SegManager *segMan, Kernel *kernel, AudioPlayer *audio, SciVersion soundVersion); ~SoundCommandParser(); - //reg_t parseCommand(int argc, reg_t *argv, reg_t acc); + //reg_t parseCommand(EngineState *s, int argc, reg_t *argv); // Functions used for game state loading void clearPlayList(); @@ -56,6 +56,9 @@ public: // Functions used for the ScummVM menus void setMasterVolume(int vol); void pauseAll(bool pause); +#ifdef ENABLE_SCI32 + void setVolume(const reg_t obj, const int vol); +#endif // Debug console functions void startNewSound(int number); @@ -78,29 +81,29 @@ public: */ void updateSci0Cues(); - reg_t kDoSoundInit(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundPlay(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundRestore(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundMute(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundPause(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundResumeAfterRestore(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundStop(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundStopAll(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundDispose(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundFade(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundGetPolyphony(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundUpdate(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundUpdateCues(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundSendMidi(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundGlobalReverb(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundSetHold(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundDummy(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundGetAudioCapability(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc); - reg_t kDoSoundSuspend(int argc, reg_t *argv, reg_t acc); + reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundPlay(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundRestore(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundMute(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundPause(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundResumeAfterRestore(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundStop(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundStopAll(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundDispose(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundMasterVolume(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundFade(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundGetPolyphony(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundUpdate(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundGlobalReverb(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundSetHold(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundDummy(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundSetPriority(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundSetLoop(EngineState *s, int argc, reg_t *argv); + reg_t kDoSoundSuspend(EngineState *s, int argc, reg_t *argv); private: //typedef Common::Array<MusicEntryCommand *> SoundCommandContainer; |