aboutsummaryrefslogtreecommitdiff
path: root/engines/agi/global.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/agi/global.cpp')
-rw-r--r--engines/agi/global.cpp308
1 files changed, 286 insertions, 22 deletions
diff --git a/engines/agi/global.cpp b/engines/agi/global.cpp
index 7d55316d7b..6f83f02a4e 100644
--- a/engines/agi/global.cpp
+++ b/engines/agi/global.cpp
@@ -20,45 +20,309 @@
*
*/
+#include "common/config-manager.h"
+#include "audio/mixer.h"
+
#include "agi/agi.h"
+#include "agi/graphics.h"
namespace Agi {
-int AgiBase::getflag(int n) {
- uint8 *set = (uint8 *)&_game.flags;
+bool AgiBase::getFlag(int16 flagNr) {
+ uint8 *flagPtr = _game.flags;
- set += n >> 3;
- return (*set & (1 << (n & 0x07))) != 0;
+ flagPtr += flagNr >> 3;
+ return (*flagPtr & (1 << (flagNr & 0x07))) != 0;
}
-void AgiBase::setflag(int n, int v) {
- uint8 *set = (uint8 *)&_game.flags;
+void AgiBase::setFlag(int16 flagNr, bool newState) {
+ uint8 *flagPtr = _game.flags;
- set += n >> 3;
- if (v)
- *set |= 1 << (n & 0x07); // set bit
+ flagPtr += flagNr >> 3;
+ if (newState)
+ *flagPtr |= 1 << (flagNr & 0x07); // set bit
else
- *set &= ~(1 << (n & 0x07)); // clear bit
+ *flagPtr &= ~(1 << (flagNr & 0x07)); // clear bit
+}
+
+void AgiBase::flipFlag(int16 flagNr) {
+ uint8 *flagPtr = _game.flags;
+
+ flagPtr += flagNr >> 3;
+ *flagPtr ^= 1 << (flagNr & 0x07); // flip bit
+}
+
+void AgiEngine::setVar(int16 varNr, byte newValue) {
+ _game.vars[varNr] = newValue;
+
+ switch (varNr) {
+ case VM_VAR_SECONDS:
+ setVarSecondsTrigger(newValue);
+ break;
+ case VM_VAR_VOLUME:
+ setVolumeViaScripts(newValue);
+ break;
+ }
}
-void AgiBase::flipflag(int n) {
- uint8 *set = (uint8 *)&_game.flags;
+byte AgiEngine::getVar(int16 varNr) {
+ switch (varNr) {
+ case VM_VAR_SECONDS:
+ getVarSecondsHeuristicTrigger();
+ // is supposed to fall through
+ case VM_VAR_MINUTES:
+ case VM_VAR_HOURS:
+ case VM_VAR_DAYS:
+ // Timer Update is necessary in here, because of at least Manhunter 1 script 153
+ // Sierra AGI updated the timer via a timer procedure
+ inGameTimerUpdate();
+ break;
+ default:
+ break;
+ }
+ return _game.vars[varNr];
+}
+
+// sets volume based on script value
+// 0 - maximum volume
+// 15 - mute
+void AgiEngine::setVolumeViaScripts(byte newVolume) {
+ newVolume = CLIP<byte>(newVolume, 0, 15);
+
+ if (_veryFirstInitialCycle) {
+ // WORKAROUND:
+ // The very first cycle is currently running and volume got changed
+ // This is surely the initial value. For plenty of fan games, a default of 15 is set
+ // Which actually means "mute" in AGI, but AGI on PC used PC speaker, which did not use
+ // volume setting. We do. So we detect such a situation and set a flag, so that the
+ // volume will get interpreted "correctly" for those fan games.
+ // Note: not all fan games are broken in that regard!
+ // See bug #7035
+ if (getFeatures() & GF_FANMADE) {
+ // We only check for fan games, Sierra always did it properly of course
+ if (newVolume == 15) {
+ // Volume gets set to mute at the start?
+ // Probably broken fan game detected, set flag
+ debug("Broken volume in fan game detected, enabling workaround");
+ _setVolumeBrokenFangame = true;
+ }
+ }
+ }
+
+ if (!_setVolumeBrokenFangame) {
+ // In AGI 15 is mute, 0 is loudest
+ // Some fan games set this incorrectly as 15 for loudest, 0 for mute
+ newVolume = 15 - newVolume; // turn volume around
+ }
+
+ int scummVMVolume = newVolume * Audio::Mixer::kMaxMixerVolume / 15;
+ bool scummVMMute = false;
+
+ // Set ScummVM setting
+ // We do not set "mute". In case "mute" is set, we will not apply the scripts wishes
+ ConfMan.setInt("music_volume", scummVMVolume);
+ ConfMan.setInt("sfx_volume", scummVMVolume);
+
+ if (ConfMan.hasKey("mute"))
+ scummVMMute = ConfMan.getBool("mute");
+
+ if (!scummVMMute) {
+ // Also change volume directly
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, scummVMVolume);
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, scummVMVolume);
+ }
+}
+
+void AgiEngine::setVolumeViaSystemSetting() {
+ int scummVMVolumeMusic = ConfMan.getInt("music_volume");
+ int scummVMVolumeSfx = ConfMan.getInt("sfx_volume");
+ bool scummVMMute = false;
+ int internalVolume = 0;
+
+ if (ConfMan.hasKey("mute"))
+ scummVMMute = ConfMan.getBool("mute");
+
+ // Clip user system setting
+ scummVMVolumeMusic = CLIP<int>(scummVMVolumeMusic, 0, Audio::Mixer::kMaxMixerVolume);
+ scummVMVolumeSfx = CLIP<int>(scummVMVolumeSfx, 0, Audio::Mixer::kMaxMixerVolume);
+
+ if (scummVMMute) {
+ scummVMVolumeMusic = 0;
+ scummVMVolumeSfx = 0;
+ }
+
+ // Now actually set it
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, scummVMVolumeMusic);
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, scummVMVolumeSfx);
+
+ // Take lowest volume to the scripts
+ if (scummVMVolumeMusic < scummVMVolumeSfx) {
+ internalVolume = scummVMVolumeMusic;
+ } else {
+ internalVolume = scummVMVolumeSfx;
+ }
+ // Change it to 0-15 range
+ internalVolume = (internalVolume + 1) * 15 / Audio::Mixer::kMaxMixerVolume;
+ // Reverse it
+ internalVolume = 15 - internalVolume;
+ // Put it into the VM variable. Directly set it, otherwise it would call a volume set call
+ _game.vars[VM_VAR_VOLUME] = internalVolume;
+}
- set += n >> 3;
- *set ^= 1 << (n & 0x07); // flip bit
+void AgiEngine::resetGetVarSecondsHeuristic() {
+ _getVarSecondsHeuristicLastInstructionCounter = 0;
+ _getVarSecondsHeuristicCounter = 0;
}
-void AgiEngine::setvar(int var, int val) {
- _game.vars[var] = val;
+// Called, when the scripts read VM_VAR_SECONDS
+void AgiEngine::getVarSecondsHeuristicTrigger() {
+ uint32 counterDifference = _instructionCounter - _getVarSecondsHeuristicLastInstructionCounter;
+
+ if (counterDifference <= 3) {
+ // Seconds were read within 3 instructions
+ _getVarSecondsHeuristicCounter++;
+ if (_getVarSecondsHeuristicCounter > 20) {
+ // More than 20 times in a row? This really seems to be an inner loop waiting for seconds to change
+ // This happens in at least:
+ // Police Quest 1 - Poker game (room 75, responsible script 81)
- if (var == vVolume) {
- _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, val * 17);
- _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, val * 17);
+ // Wait a few milliseconds, get events and update screen
+ // We MUST NOT process AGI events in here
+ wait(10);
+ processScummVMEvents();
+ _gfx->updateScreen();
+
+ _getVarSecondsHeuristicCounter = 0;
+ }
+ } else {
+ _getVarSecondsHeuristicCounter = 0;
}
+ _getVarSecondsHeuristicLastInstructionCounter = _instructionCounter;
+}
+
+// In-Game timer, used for timer VM Variables
+void AgiEngine::inGameTimerReset(uint32 newPlayTime) {
+ _lastUsedPlayTimeInCycles = newPlayTime / 50;
+ _lastUsedPlayTimeInSeconds = newPlayTime / 1000;
+ _playTimeInSecondsAdjust = 0; // no adjust for now
+ setTotalPlayTime(newPlayTime);
+ inGameTimerResetPassedCycles();
+}
+void AgiEngine::inGameTimerResetPassedCycles() {
+ _passedPlayTimeCycles = 0;
+}
+void AgiEngine::inGameTimerPause() {
+ pauseEngine(true);
+}
+void AgiEngine::inGameTimerResume() {
+ pauseEngine(false);
}
+uint32 AgiEngine::inGameTimerGet() {
+ return getTotalPlayTime();
+}
+uint32 AgiEngine::inGameTimerGetPassedCycles() {
+ return _passedPlayTimeCycles;
+}
+
+// Seconds got set by the game
+// This happens in Mixed Up Mother Goose. The game syncs the songs to VM_VAR_SECONDS, but instead
+// of only reading them, it sets it to 0 and then checks if it reached a certain second.
+// The original interpreter didn't reset the internal cycles counter. Which means the timing was never accurate,
+// because the cycles counter may just overflow right after setting the seconds, which means a second
+// increase almost immediately happened. We even fix this issue by adjusting for it.
+void AgiEngine::setVarSecondsTrigger(byte newSeconds) {
+ // Adjust in game timer, so that VM timer variables are accurate
+ inGameTimerUpdate();
+
+ // Adjust VM seconds again
+ _game.vars[VM_VAR_SECONDS] = newSeconds;
+
+ // Calculate milliseconds adjust (see comment above)
+ uint32 curPlayTimeMilliseconds = inGameTimerGet();
+ _playTimeInSecondsAdjust = curPlayTimeMilliseconds % 1000;
+}
+
+// This is called, when one of the timer variables is read
+// We calculate the latest variables, according to current official playtime
+// This is also called in the main loop, because the game needs to be sync'd to 20 cycles per second
+void AgiEngine::inGameTimerUpdate() {
+ uint32 curPlayTimeMilliseconds = inGameTimerGet();
+ uint32 curPlayTimeCycles = curPlayTimeMilliseconds / 50;
+
+ if (curPlayTimeCycles == _lastUsedPlayTimeInCycles) {
+ // No difference, skip updating
+ return;
+ }
+
+ // Increase passed cycles accordingly
+ int32 playTimeCycleDelta = curPlayTimeCycles - _lastUsedPlayTimeInCycles;
+ if (playTimeCycleDelta > 0) {
+ _passedPlayTimeCycles += playTimeCycleDelta;
+ }
+ _lastUsedPlayTimeInCycles = curPlayTimeCycles;
+
+ // Now calculate current play time in seconds
+ if (_playTimeInSecondsAdjust) {
+ // Apply adjust from setVarSecondsTrigger()
+ if (curPlayTimeMilliseconds >= _playTimeInSecondsAdjust) {
+ curPlayTimeMilliseconds -= _playTimeInSecondsAdjust;
+ } else {
+ curPlayTimeMilliseconds = 0;
+ }
+ }
+ uint32 curPlayTimeSeconds = curPlayTimeMilliseconds / 1000;
+
+ if (curPlayTimeSeconds == _lastUsedPlayTimeInSeconds) {
+ // No difference, skip updating
+ return;
+ }
+
+ int32 playTimeSecondsDelta = curPlayTimeSeconds - _lastUsedPlayTimeInSeconds;
+
+ if (playTimeSecondsDelta > 0) {
+ // Read and write to VM vars directly to avoid endless loop
+ uint32 secondsLeft = playTimeSecondsDelta;
+ byte curSeconds = _game.vars[VM_VAR_SECONDS];
+ byte curMinutes = _game.vars[VM_VAR_MINUTES];
+ byte curHours = _game.vars[VM_VAR_HOURS];
+ byte curDays = _game.vars[VM_VAR_DAYS];
+
+ // Add delta to VM variables
+ if (secondsLeft >= 86400) {
+ curDays += secondsLeft / 86400;
+ secondsLeft = secondsLeft % 86400;
+ }
+ if (secondsLeft >= 3600) {
+ curHours += secondsLeft / 3600;
+ secondsLeft = secondsLeft % 3600;
+ }
+ if (secondsLeft >= 60) {
+ curMinutes += secondsLeft / 60;
+ secondsLeft = secondsLeft % 60;
+ }
+ curSeconds += secondsLeft;
+
+ while (curSeconds > 59) {
+ curSeconds -= 60;
+ curMinutes++;
+ }
+ while (curMinutes > 59) {
+ curMinutes -= 60;
+ curHours++;
+ }
+ while (curHours > 23) {
+ curHours -= 24;
+ curDays++;
+ }
+
+ // directly set them
+ _game.vars[VM_VAR_SECONDS] = curSeconds;
+ _game.vars[VM_VAR_MINUTES] = curMinutes;
+ _game.vars[VM_VAR_HOURS] = curHours;
+ _game.vars[VM_VAR_DAYS] = curDays;
+ }
-int AgiEngine::getvar(int var) {
- return _game.vars[var];
+ _lastUsedPlayTimeInSeconds = curPlayTimeSeconds;
}
void AgiEngine::decrypt(uint8 *mem, int len) {
@@ -66,7 +330,7 @@ void AgiEngine::decrypt(uint8 *mem, int len) {
int i;
key = (getFeatures() & GF_AGDS) ? (const uint8 *)CRYPT_KEY_AGDS
- : (const uint8 *)CRYPT_KEY_SIERRA;
+ : (const uint8 *)CRYPT_KEY_SIERRA;
for (i = 0; i < len; i++)
*(mem + i) ^= *(key + (i % 11));