[Scummvm-git-logs] scummvm master -> c515f70e8381b1768693195ed499a4e4f90f82c4
elasota
noreply at scummvm.org
Sun Apr 23 05:16:03 UTC 2023
This automated email contains information about 5 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
3c4b00cb40 VCRUISE: Fix say2 opcode. Add subtitles for speech.
230b0589e6 VCRUISE: Try to use NotoSans-Regular if it's available.
89781b6932 VCRUISE: Add volume ramp opcodes.
5e3acc3514 VCRUISE: Add animation subtitles.
c515f70e83 VCRUISE: Add menus
Commit: 3c4b00cb40cacbe3896527cc8b2d6fa100be5d76
https://github.com/scummvm/scummvm/commit/3c4b00cb40cacbe3896527cc8b2d6fa100be5d76
Author: elasota (ejlasota at gmail.com)
Date: 2023-04-23T01:15:31-04:00
Commit Message:
VCRUISE: Fix say2 opcode. Add subtitles for speech.
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/script.cpp
engines/vcruise/script.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 656fac63d70..4c56fa0ba04 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -213,7 +213,7 @@ void Runtime::GyroState::reset() {
isWaitingForAnimation = false;
}
-Runtime::SubtitleDef::SubtitleDef() : subIndex(0), color{0, 0, 0}, unknownValue1(0), unknownValue2(0) {
+Runtime::SubtitleDef::SubtitleDef() : color{0, 0, 0}, unknownValue1(0), durationInDeciseconds(0) {
}
SfxPlaylistEntry::SfxPlaylistEntry() : frame(0), balance(0), volume(0), isUpdate(false) {
@@ -410,7 +410,7 @@ SoundCache::~SoundCache() {
SoundInstance::SoundInstance()
: id(0), rampStartVolume(0), rampEndVolume(0), rampRatePerMSec(0), rampStartTime(0), rampTerminateOnCompletion(false),
- volume(0), balance(0), effectiveBalance(0), effectiveVolume(0), is3D(false), isLooping(false), isSpeech(false), isSilencedLoop(false), x(0), y(0), endTime(0) {
+ volume(0), balance(0), effectiveBalance(0), effectiveVolume(0), is3D(false), isLooping(false), isSpeech(false), isSilencedLoop(false), x(0), y(0), endTime(0), duration(0) {
}
SoundInstance::~SoundInstance() {
@@ -737,7 +737,7 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
_delayCompletionTime(0),
_panoramaState(kPanoramaStateInactive),
_listenerX(0), _listenerY(0), _listenerAngle(0), _soundCacheIndex(0),
- _subtitleFont(nullptr), _subtitleExpireTime(0), _displayingSubtitles(false), _languageIndex(0) {
+ _subtitleFont(nullptr), _isDisplayingSubtitles(false), _languageIndex(0) {
for (uint i = 0; i < kNumDirections; i++) {
_haveIdleAnimations[i] = false;
@@ -750,7 +750,7 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
_rng.reset(new Common::RandomSource("vcruise"));
- _subtitleFont = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
+ _subtitleFont = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont);
if (!_subtitleFont)
warning("Couldn't load subtitle font, subtitles will be disabled");
}
@@ -890,6 +890,7 @@ bool Runtime::runFrame() {
uint32 timestamp = g_system->getMillis();
updateSounds(timestamp);
+ updateSubtitles();
return true;
}
@@ -1563,6 +1564,7 @@ bool Runtime::runScript() {
DISPATCH_OP(Dup);
DISPATCH_OP(Swap);
DISPATCH_OP(Say1);
+ DISPATCH_OP(Say2);
DISPATCH_OP(Say3);
DISPATCH_OP(Say3Get);
DISPATCH_OP(SetTimer);
@@ -2462,6 +2464,7 @@ void Runtime::triggerSound(bool looping, SoundInstance &snd, uint volume, int32
snd.isSilencedLoop = true;
snd.endTime = 0;
+ snd.duration = 0;
return;
}
@@ -2469,6 +2472,8 @@ void Runtime::triggerSound(bool looping, SoundInstance &snd, uint volume, int32
SoundCache *cache = loadCache(snd);
+ snd.duration = cache->stream->getLength().msecs();
+
// Reset if looping state changes
if (cache->loopingStream && !looping) {
cache->player.reset();
@@ -2519,6 +2524,65 @@ void Runtime::triggerSoundRamp(SoundInstance &snd, uint durationMSec, uint newVo
snd.rampRatePerMSec = 65536 / durationMSec;
}
+void Runtime::triggerWaveSubtitles(const SoundInstance &snd, const Common::String &id) {
+ char appendedCode[4] = {'_', '0', '0', '\0'};
+
+ char digit1 = '0';
+ char digit2 = '0';
+
+ stopSubtitles();
+
+ uint32 currentTime = g_system->getMillis(true);
+
+ uint32 soundEndTime = currentTime + snd.duration;
+
+ for (;;) {
+ if (digit2 == '9') {
+ digit2 = '0';
+
+ if (digit1 == '9')
+ return; // This should never happen
+
+ digit1++;
+ } else
+ digit2++;
+
+ appendedCode[1] = digit1;
+ appendedCode[2] = digit2;
+
+ Common::String subtitleID = id + appendedCode;
+
+ WaveSubtitleMap_t::const_iterator subIt = _waveSubtitles.find(subtitleID);
+
+ if (subIt != _waveSubtitles.end()) {
+ const SubtitleDef &subDef = subIt->_value;
+
+ SubtitleQueueItem queueItem;
+ queueItem.startTime = currentTime;
+ queueItem.endTime = soundEndTime + 1000u;
+
+ if (_subtitleQueue.size() > 0)
+ queueItem.startTime = _subtitleQueue.back().endTime;
+
+ for (int ch = 0; ch < 3; ch++)
+ queueItem.color[ch] = subDef.color[ch];
+
+ if (subDef.durationInDeciseconds != 1)
+ queueItem.endTime = queueItem.startTime + subDef.durationInDeciseconds * 100u;
+
+ queueItem.str = subDef.str.decode(Common::kUtf8);
+
+ _subtitleQueue.push_back(queueItem);
+ }
+ }
+}
+
+void Runtime::stopSubtitles() {
+ _subtitleQueue.clear();
+ _isDisplayingSubtitles = false;
+ redrawTray();
+}
+
void Runtime::stopSound(SoundInstance &sound) {
if (!sound.cache)
return;
@@ -2584,6 +2648,63 @@ void Runtime::updateSounds(uint32 timestamp) {
}
}
+void Runtime::updateSubtitles() {
+ uint32 timestamp = g_system->getMillis(true);
+
+ while (_subtitleQueue.size() > 0) {
+ const SubtitleQueueItem &queueItem = _subtitleQueue[0];
+
+ if (_isDisplayingSubtitles) {
+ assert(_subtitleQueue.size() > 0);
+
+ if (queueItem.endTime <= timestamp) {
+ _subtitleQueue.remove_at(0);
+ _isDisplayingSubtitles = false;
+
+ if (_subtitleQueue.size() == 0)
+ redrawTray();
+ } else
+ break;
+ } else {
+ Graphics::ManagedSurface *surf = _traySection.surf.get();
+
+ Common::Array<Common::U32String> lines;
+
+ uint lineStart = 0;
+ for (;;) {
+ uint lineEnd = queueItem.str.find(static_cast<Common::u32char_type_t>('\\'), lineStart);
+ if (lineEnd == Common::U32String::npos) {
+ lines.push_back(queueItem.str.substr(lineStart));
+ break;
+ }
+
+ lines.push_back(queueItem.str.substr(lineStart, lineEnd - lineStart));
+ lineStart = lineEnd + 1;
+ }
+
+ clearTray();
+
+ if (_subtitleFont) {
+ int lineHeight = _subtitleFont->getFontHeight();
+
+ int topY = (surf->h - lineHeight * static_cast<int>(lines.size())) / 2;
+
+ uint32 textColor = surf->format.RGBToColor(queueItem.color[0], queueItem.color[1], queueItem.color[2]);
+
+ for (uint lineIndex = 0; lineIndex < lines.size(); lineIndex++) {
+ const Common::U32String &line = lines[lineIndex];
+ int lineWidth = _subtitleFont->getStringWidth(line);
+ _subtitleFont->drawString(surf, line, (surf->w - lineWidth) / 2, topY + static_cast<int>(lineIndex) * lineHeight, lineWidth, textColor);
+ }
+ }
+
+ commitSectionToScreen(_traySection, Common::Rect(0, 0, _traySection.rect.width(), _traySection.rect.height()));
+
+ _isDisplayingSubtitles = true;
+ }
+ }
+}
+
void Runtime::update3DSounds() {
for (const Common::SharedPtr<SoundInstance> &sndPtr : _activeSounds) {
SoundInstance &snd = *sndPtr;
@@ -3020,7 +3141,27 @@ void Runtime::inventoryRemoveItem(uint itemID) {
}
}
+void Runtime::redrawTray() {
+ if (_subtitleQueue.size() != 0)
+ return;
+
+ clearTray();
+
+ drawCompass();
+
+ for (uint slot = 0; slot < kNumInventorySlots; slot++)
+ drawInventory(slot);
+}
+
+void Runtime::clearTray() {
+ uint32 blackColor = _traySection.surf->format.RGBToColor(0, 0, 0);
+ _traySection.surf->fillRect(Common::Rect(0, 0, _traySection.surf->w, _traySection.surf->h), blackColor);
+}
+
void Runtime::drawInventory(uint slot) {
+ if (_subtitleQueue.size() > 0)
+ return;
+
Common::Rect trayRect = _traySection.rect;
trayRect.translate(-trayRect.left, -trayRect.top);
@@ -3058,6 +3199,9 @@ void Runtime::drawInventory(uint slot) {
}
void Runtime::drawCompass() {
+ if (_subtitleQueue.size() > 0)
+ return;
+
bool haveHorizontalRotate = false;
bool haveUp = false;
bool haveDown = false;
@@ -3222,7 +3366,7 @@ void Runtime::loadSubtitles(Common::CodePage codePage) {
subDef->color[1] = ((colorCode >> 8) & 0xff);
subDef->color[2] = (colorCode & 0xff);
subDef->unknownValue1 = param1;
- subDef->unknownValue2 = param2;
+ subDef->durationInDeciseconds = param2;
subDef->str = kv.value.substr(22, kv.value.size() - 23).decode(codePage).encode(Common::kUtf8);
}
}
@@ -3439,8 +3583,8 @@ void Runtime::restoreSaveGameSnapshot() {
_havePendingScreenChange = true;
_forceScreenChange = true;
- for (uint slot = 0; slot < kNumInventorySlots; slot++)
- drawInventory(slot);
+ stopSubtitles();
+ redrawTray();
}
void Runtime::saveGame(Common::WriteStream *stream) const {
@@ -4356,8 +4500,28 @@ void Runtime::scriptOpSay1(ScriptArg_t arg) {
SoundInstance *cachedSound = nullptr;
resolveSoundByName(soundIDStr, true, soundID, cachedSound);
- if (cachedSound)
+ if (cachedSound) {
triggerSound(false, *cachedSound, 100, 0, false, true);
+ triggerWaveSubtitles(*cachedSound, soundIDStr);
+ }
+}
+
+void Runtime::scriptOpSay2(ScriptArg_t arg) {
+ TAKE_STACK_INT_NAMED(2, sndParamArgs);
+ TAKE_STACK_STR_NAMED(1, sndNameArgs);
+
+ StackInt_t soundID = 0;
+ SoundInstance *cachedSound = nullptr;
+ resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
+
+ if (cachedSound) {
+ // The third param seems to control sound interruption, but say3 is a Reah-only op and it's only ever 1.
+ if (sndParamArgs[1] != 1)
+ error("Invalid interrupt arg for say2, only 1 is supported.");
+
+ triggerSound(false, *cachedSound, 100, 0, false, true);
+ triggerWaveSubtitles(*cachedSound, sndNameArgs[0]);
+ }
}
void Runtime::scriptOpSay3(ScriptArg_t arg) {
@@ -4380,6 +4544,8 @@ void Runtime::scriptOpSay3(ScriptArg_t arg) {
if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
triggerSound(false, *cachedSound, 100, 0, false, true);
_triggeredOneShots.push_back(oneShot);
+
+ triggerWaveSubtitles(*cachedSound, sndNameArgs[0]);
}
}
}
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index a7c0e9fd542..14754dbbf37 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -230,6 +230,7 @@ struct SoundInstance {
SoundParams3D params3D;
uint32 endTime;
+ uint32 duration;
};
struct RandomAmbientSound {
@@ -596,13 +597,19 @@ private:
struct SubtitleDef {
SubtitleDef();
- uint subIndex;
uint8 color[3];
uint unknownValue1;
- uint unknownValue2;
+ uint durationInDeciseconds;
Common::String str;
};
+ struct SubtitleQueueItem {
+ Common::U32String str;
+ uint8 color[3];
+ uint32 startTime;
+ uint32 endTime;
+ };
+
bool runIdle();
bool runDelay();
bool runHorizontalPan(bool isRight);
@@ -651,10 +658,14 @@ private:
void triggerSoundRamp(SoundInstance &sound, uint durationMSec, uint newVolume, bool terminateOnCompletion);
void stopSound(SoundInstance &sound);
void updateSounds(uint32 timestamp);
+ void updateSubtitles();
void update3DSounds();
bool computeEffectiveVolumeAndBalance(SoundInstance &snd);
void triggerAmbientSounds();
+ void triggerWaveSubtitles(const SoundInstance &sound, const Common::String &id);
+ void stopSubtitles();
+
AnimationDef stackArgsToAnimDef(const StackInt_t *args) const;
void pushAnimDef(const AnimationDef &animDef);
@@ -675,6 +686,8 @@ private:
void inventoryAddItem(uint item);
void inventoryRemoveItem(uint item);
+ void redrawTray();
+ void clearTray();
void drawInventory(uint slot);
void drawCompass();
void resetInventoryHighlights();
@@ -754,6 +767,7 @@ private:
void scriptOpDup(ScriptArg_t arg);
void scriptOpSwap(ScriptArg_t arg);
void scriptOpSay1(ScriptArg_t arg);
+ void scriptOpSay2(ScriptArg_t arg);
void scriptOpSay3(ScriptArg_t arg);
void scriptOpSay3Get(ScriptArg_t arg);
void scriptOpSetTimer(ScriptArg_t arg);
@@ -963,9 +977,7 @@ private:
Common::SharedPtr<SaveGameSnapshot> _saveGame;
const Graphics::Font *_subtitleFont;
- uint32 _subtitleExpireTime;
uint _languageIndex;
- bool _displayingSubtitles;
typedef Common::HashMap<uint, SubtitleDef> FrameToSubtitleMap_t;
typedef Common::HashMap<uint, FrameToSubtitleMap_t> AnimSubtitleMap_t;
@@ -973,6 +985,8 @@ private:
AnimSubtitleMap_t _animSubtitles;
Common::HashMap<Common::String, SubtitleDef> _waveSubtitles;
+ Common::Array<SubtitleQueueItem> _subtitleQueue;
+ bool _isDisplayingSubtitles;
};
} // End of namespace VCruise
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 346eb56ed6d..b11fee6ee5e 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -387,7 +387,7 @@ static ScriptNamedInstruction g_namedInstructions[] = {
{"dup", ProtoOp::kProtoOpScript, ScriptOps::kDup},
{"swap", ProtoOp::kProtoOpScript, ScriptOps::kSwap},
{"say1", ProtoOp::kProtoOpScript, ScriptOps::kSay1},
- {"say2", ProtoOp::kProtoOpScript, ScriptOps::kSay3}, // FIXME: Figure out what the difference is between say2 and say3. I think say2 is repeatable? Maybe works as say1 instead?
+ {"say2", ProtoOp::kProtoOpScript, ScriptOps::kSay2},
{"say3", ProtoOp::kProtoOpScript, ScriptOps::kSay3},
{"say3@", ProtoOp::kProtoOpScript, ScriptOps::kSay3Get},
{"setTimer", ProtoOp::kProtoOpScript, ScriptOps::kSetTimer},
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index 5f69d5aaae4..10f60c93b0c 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -108,6 +108,7 @@ enum ScriptOp {
kDup,
kSwap,
kSay1,
+ kSay2,
kSay3,
kSay3Get,
kSetTimer,
Commit: 230b0589e689f4edc17b3123a1fe3bbf933a39cb
https://github.com/scummvm/scummvm/commit/230b0589e689f4edc17b3123a1fe3bbf933a39cb
Author: elasota (ejlasota at gmail.com)
Date: 2023-04-23T01:15:31-04:00
Commit Message:
VCRUISE: Try to use NotoSans-Regular if it's available.
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 4c56fa0ba04..73b24bfc6ce 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -31,6 +31,7 @@
#include "graphics/cursorman.h"
#include "graphics/font.h"
+#include "graphics/fonts/ttf.h"
#include "graphics/fontman.h"
#include "graphics/wincursor.h"
#include "graphics/managed_surface.h"
@@ -750,7 +751,14 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
_rng.reset(new Common::RandomSource("vcruise"));
- _subtitleFont = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont);
+#ifdef USE_FREETYPE2
+ _subtitleFontKeepalive.reset(Graphics::loadTTFFontFromArchive("NotoSans-Regular.ttf", 16, Graphics::kTTFSizeModeCharacter, 0, Graphics::kTTFRenderModeLight));
+ _subtitleFont = _subtitleFontKeepalive.get();
+#endif
+
+ if (!_subtitleFont)
+ _subtitleFont = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont);
+
if (!_subtitleFont)
warning("Couldn't load subtitle font, subtitles will be disabled");
}
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 14754dbbf37..7942166331b 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -977,6 +977,7 @@ private:
Common::SharedPtr<SaveGameSnapshot> _saveGame;
const Graphics::Font *_subtitleFont;
+ Common::SharedPtr<Graphics::Font> _subtitleFontKeepalive;
uint _languageIndex;
typedef Common::HashMap<uint, SubtitleDef> FrameToSubtitleMap_t;
Commit: 89781b69321a3c6a69fff8628bf6284a11631e22
https://github.com/scummvm/scummvm/commit/89781b69321a3c6a69fff8628bf6284a11631e22
Author: elasota (ejlasota at gmail.com)
Date: 2023-04-23T01:15:32-04:00
Commit Message:
VCRUISE: Add volume ramp opcodes.
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/script.cpp
engines/vcruise/script.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 73b24bfc6ce..d067a6a5a8a 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -570,7 +570,7 @@ void SaveGameSnapshot::Sound::read(Common::ReadStream *stream) {
params3D.read(stream);
}
-SaveGameSnapshot::SaveGameSnapshot() : roomNumber(0), screenNumber(0), direction(0), escOn(false), musicTrack(0), loadedAnimation(0),
+SaveGameSnapshot::SaveGameSnapshot() : roomNumber(0), screenNumber(0), direction(0), escOn(false), musicTrack(0), musicVolume(100), loadedAnimation(0),
animDisplayingFrame(0), listenerX(0), listenerY(0), listenerAngle(0) {
}
@@ -584,6 +584,7 @@ void SaveGameSnapshot::write(Common::WriteStream *stream) const {
stream->writeByte(escOn ? 1 : 0);
stream->writeSint32BE(musicTrack);
+ stream->writeUint32BE(musicVolume);
stream->writeUint32BE(loadedAnimation);
stream->writeUint32BE(animDisplayingFrame);
@@ -655,6 +656,11 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
escOn = (stream->readByte() != 0);
musicTrack = stream->readSint32BE();
+ if (saveVersion >= 5)
+ musicVolume = stream->readUint32BE();
+ else
+ musicVolume = 100;
+
loadedAnimation = stream->readUint32BE();
animDisplayingFrame = stream->readUint32BE();
@@ -730,7 +736,9 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingReturnToIdleState(false), _havePendingCompletionCheck(false),
- _havePendingPlayAmbientSounds(false), _ambientSoundFinishTime(0), _scriptNextInstruction(0), _escOn(false), _debugMode(false), _fastAnimationMode(false), _musicTrack(0), _panoramaDirectionFlags(0),
+ _havePendingPlayAmbientSounds(false), _ambientSoundFinishTime(0), _scriptNextInstruction(0), _escOn(false), _debugMode(false), _fastAnimationMode(false),
+ _musicTrack(0), _musicVolume(100), _musicVolumeRampStartTime(0), _musicVolumeRampStartVolume(0), _musicVolumeRampRatePerMSec(0), _musicVolumeRampEnd(0),
+ _panoramaDirectionFlags(0),
_loadedAnimation(0), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0),
_animStartTime(0), _animFramesDecoded(0), _animDecoderState(kAnimDecoderStateStopped),
_animPlayWhileIdle(false), _idleIsOnInteraction(false), _idleHaveClickInteraction(false), _idleHaveDragInteraction(false), _idleInteractionID(0), _haveIdleStaticAnimation(false),
@@ -1553,8 +1561,7 @@ bool Runtime::runScript() {
DISPATCH_OP(StopSndLO);
DISPATCH_OP(Music);
- DISPATCH_OP(MusicUp);
- DISPATCH_OP(MusicDn);
+ DISPATCH_OP(MusicVolRamp);
DISPATCH_OP(Parm0);
DISPATCH_OP(Parm1);
DISPATCH_OP(Parm2);
@@ -2342,7 +2349,7 @@ void Runtime::changeMusicTrack(int track) {
Common::SharedPtr<Audio::AudioStream> loopingStream(Audio::makeLoopingAudioStream(audioStream, 0));
_musicPlayer.reset(new AudioPlayer(_mixer, loopingStream, Audio::Mixer::kMusicSoundType));
- _musicPlayer->play(100, 0);
+ _musicPlayer->play(_musicVolume, 0);
}
} else {
warning("Music file '%s' is missing", wavFileName.c_str());
@@ -2654,6 +2661,40 @@ void Runtime::updateSounds(uint32 timestamp) {
}
}
}
+
+ if (_musicVolumeRampRatePerMSec != 0) {
+ bool negative = (_musicVolumeRampRatePerMSec < 0);
+
+ uint32 rampMax = 0;
+ uint32 absRampRate = 0;
+ if (negative) {
+ rampMax = _musicVolumeRampStartVolume - _musicVolumeRampEnd;
+ absRampRate = -_musicVolumeRampRatePerMSec;
+ } else {
+ rampMax = _musicVolumeRampEnd - _musicVolumeRampStartVolume;
+ absRampRate = _musicVolumeRampRatePerMSec;
+ }
+
+ uint32 rampTime = timestamp - _musicVolumeRampStartTime;
+
+ uint32 ramp = (rampTime * absRampRate) >> 16;
+ if (ramp > rampMax)
+ ramp = rampMax;
+
+ uint32 newVolume = _musicVolumeRampStartVolume;
+ if (negative)
+ newVolume -= ramp;
+ else
+ newVolume += ramp;
+
+ if (newVolume != _musicVolume) {
+ _musicPlayer->setVolume(static_cast<byte>(newVolume));
+ _musicVolume = newVolume;
+ }
+
+ if (newVolume == _musicVolumeRampEnd)
+ _musicVolumeRampRatePerMSec = 0;
+ }
}
void Runtime::updateSubtitles() {
@@ -3459,6 +3500,12 @@ void Runtime::recordSaveGameSnapshot() {
snapshot->musicTrack = _musicTrack;
+ snapshot->musicVolume = _musicVolume;
+
+ // If music volume is ramping, use the end volume and skip the ramp
+ if (_musicVolumeRampRatePerMSec != 0)
+ snapshot->musicVolume = _musicVolumeRampEnd;
+
snapshot->loadedAnimation = _loadedAnimation;
snapshot->animDisplayingFrame = _animDisplayingFrame;
@@ -3537,6 +3584,12 @@ void Runtime::restoreSaveGameSnapshot() {
_escOn = _saveGame->escOn;
+ _musicVolume = _saveGame->musicVolume;
+ _musicVolumeRampStartTime = 0;
+ _musicVolumeRampStartVolume = 0;
+ _musicVolumeRampRatePerMSec = 0;
+ _musicVolumeRampEnd = _musicVolume;
+
changeMusicTrack(_saveGame->musicTrack);
// Stop all sounds since the player instances are stored in the sound cache.
@@ -4277,20 +4330,31 @@ void Runtime::scriptOpMusic(ScriptArg_t arg) {
changeMusicTrack(stackArgs[0]);
}
-void Runtime::scriptOpMusicUp(ScriptArg_t arg) {
+void Runtime::scriptOpMusicVolRamp(ScriptArg_t arg) {
TAKE_STACK_INT(2);
- warning("Music volume ramp up is not implemented");
- (void)stackArgs;
-}
+ uint32 duration = static_cast<uint32>(stackArgs[0]) * 100u;
+ uint32 newVolume = stackArgs[1];
-void Runtime::scriptOpMusicDn(ScriptArg_t arg) {
- TAKE_STACK_INT(2);
+ _musicVolumeRampRatePerMSec = 0;
- warning("Music volume ramp down is not implemented");
- (void)stackArgs;
+ if (duration == 0) {
+ _musicVolume = newVolume;
+ if (_musicPlayer)
+ _musicPlayer->setVolume(newVolume);
+ } else {
+ if (newVolume != _musicVolume) {
+ uint32 timestamp = g_system->getMillis();
+
+ _musicVolumeRampRatePerMSec = (static_cast<int32>(newVolume) - static_cast<int32>(_musicVolume)) * 65536 / static_cast<int32>(duration);
+ _musicVolumeRampStartTime = timestamp;
+ _musicVolumeRampStartVolume = _musicVolume;
+ _musicVolumeRampEnd = newVolume;
+ }
+ }
}
+
void Runtime::scriptOpParm0(ScriptArg_t arg) {
TAKE_STACK_INT(4);
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 7942166331b..1b9a6e2882c 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -336,7 +336,7 @@ struct SaveGameSnapshot {
LoadGameOutcome read(Common::ReadStream *stream);
static const uint kSaveGameIdentifier = 0x53566372;
- static const uint kSaveGameCurrentVersion = 4;
+ static const uint kSaveGameCurrentVersion = 5;
static const uint kSaveGameEarliestSupportedVersion = 2;
struct InventoryItem {
@@ -377,6 +377,8 @@ struct SaveGameSnapshot {
bool escOn;
int musicTrack;
+ uint musicVolume;
+
uint loadedAnimation;
uint animDisplayingFrame;
@@ -748,8 +750,7 @@ private:
void scriptOpStopSndLO(ScriptArg_t arg);
void scriptOpMusic(ScriptArg_t arg);
- void scriptOpMusicUp(ScriptArg_t arg);
- void scriptOpMusicDn(ScriptArg_t arg);
+ void scriptOpMusicVolRamp(ScriptArg_t arg);
void scriptOpParm0(ScriptArg_t arg);
void scriptOpParm1(ScriptArg_t arg);
void scriptOpParm2(ScriptArg_t arg);
@@ -892,6 +893,13 @@ private:
Common::SharedPtr<AudioPlayer> _musicPlayer;
int _musicTrack;
+ uint _musicVolume;
+
+ uint32 _musicVolumeRampStartTime;
+ uint _musicVolumeRampStartVolume;
+ int32 _musicVolumeRampRatePerMSec;
+ uint _musicVolumeRampEnd;
+
SfxData _sfxData;
Common::SharedPtr<Video::AVIDecoder> _animDecoder;
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index b11fee6ee5e..93e728abefc 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -429,8 +429,8 @@ static ScriptNamedInstruction g_namedInstructions[] = {
{"stopSndLO", ProtoOp::kProtoOpScript, ScriptOps::kStopSndLO},
{"music", ProtoOp::kProtoOpScript, ScriptOps::kMusic},
- {"musicUp", ProtoOp::kProtoOpScript, ScriptOps::kMusicUp},
- {"musicDn", ProtoOp::kProtoOpScript, ScriptOps::kMusicDn},
+ {"musicUp", ProtoOp::kProtoOpScript, ScriptOps::kMusicVolRamp},
+ {"musicDn", ProtoOp::kProtoOpScript, ScriptOps::kMusicVolRamp},
{"parm0", ProtoOp::kProtoOpScript, ScriptOps::kParm0},
{"parm1", ProtoOp::kProtoOpScript, ScriptOps::kParm1},
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index 10f60c93b0c..f0086c07a12 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -90,8 +90,7 @@ enum ScriptOp {
kStopSndLA,
kStopSndLO,
kMusic,
- kMusicUp,
- kMusicDn,
+ kMusicVolRamp,
kParm0,
kParm1,
kParm2,
Commit: 5e3acc35142b7ff2f366fedfb793366af89449ee
https://github.com/scummvm/scummvm/commit/5e3acc35142b7ff2f366fedfb793366af89449ee
Author: elasota (ejlasota at gmail.com)
Date: 2023-04-23T01:15:32-04:00
Commit Message:
VCRUISE: Add animation subtitles.
Changed paths:
engines/vcruise/runtime.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index d067a6a5a8a..4d3bb4b5061 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -1430,6 +1430,36 @@ void Runtime::continuePlayingAnimation(bool loop, bool useStopFrame, bool &outAn
update3DSounds();
+ AnimSubtitleMap_t::const_iterator animSubtitlesIt = _animSubtitles.find(_loadedAnimation);
+ if (animSubtitlesIt != _animSubtitles.end()) {
+ const FrameToSubtitleMap_t &frameMap = animSubtitlesIt->_value;
+
+ FrameToSubtitleMap_t::const_iterator frameIt = frameMap.find(_animDisplayingFrame);
+ if (frameIt != frameMap.end()) {
+ if (!millis)
+ millis = g_system->getMillis();
+
+ const SubtitleDef &subDef = frameIt->_value;
+
+ _subtitleQueue.clear();
+ _isDisplayingSubtitles = false;
+
+ SubtitleQueueItem queueItem;
+ queueItem.startTime = millis;
+ queueItem.endTime = millis + 1000u;
+
+ for (int ch = 0; ch < 3; ch++)
+ queueItem.color[ch] = subDef.color[ch];
+
+ if (subDef.durationInDeciseconds != 1)
+ queueItem.endTime = queueItem.startTime + subDef.durationInDeciseconds * 100u;
+
+ queueItem.str = subDef.str.decode(Common::kUtf8);
+
+ _subtitleQueue.push_back(queueItem);
+ }
+ }
+
if (_animPlaylist) {
uint decodeFrameInPlaylist = _animDisplayingFrame - _animFirstFrame;
for (const SfxPlaylistEntry &playlistEntry : _animPlaylist->entries) {
@@ -2417,6 +2447,8 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
if (twoDtFile.open(twoDtFileName))
loadFrameData2(&twoDtFile);
twoDtFile.close();
+
+ stopSubtitles();
}
if (_animDecoderState == kAnimDecoderStatePlaying) {
@@ -2710,8 +2742,13 @@ void Runtime::updateSubtitles() {
_subtitleQueue.remove_at(0);
_isDisplayingSubtitles = false;
- if (_subtitleQueue.size() == 0)
- redrawTray();
+ if (_subtitleQueue.size() == 0) {
+ // Is this really what we want to be doing?
+ if (_escOn)
+ clearTray();
+ else
+ redrawTray();
+ }
} else
break;
} else {
Commit: c515f70e8381b1768693195ed499a4e4f90f82c4
https://github.com/scummvm/scummvm/commit/c515f70e8381b1768693195ed499a4e4f90f82c4
Author: elasota (ejlasota at gmail.com)
Date: 2023-04-23T01:15:32-04:00
Commit Message:
VCRUISE: Add menus
Changed paths:
A engines/vcruise/menu.cpp
A engines/vcruise/menu.h
engines/vcruise/module.mk
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/vcruise.cpp
engines/vcruise/vcruise.h
diff --git a/engines/vcruise/menu.cpp b/engines/vcruise/menu.cpp
new file mode 100644
index 00000000000..f4da9ec2374
--- /dev/null
+++ b/engines/vcruise/menu.cpp
@@ -0,0 +1,886 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/config-manager.h"
+
+#include "graphics/managed_surface.h"
+
+#include "audio/mixer.h"
+
+#include "vcruise/menu.h"
+#include "vcruise/runtime.h"
+
+namespace VCruise {
+
+class ReahMenuPage : public MenuPage {
+public:
+ ReahMenuPage();
+
+ bool run() override;
+ void start() override;
+
+protected:
+ virtual void onButtonClicked(uint button, bool &outChangedState);
+ virtual void onCheckboxClicked(uint button, bool &outChangedState);
+ virtual void onSliderMoved(uint slider);
+ virtual void eraseSlider(uint sliderIndex) const;
+
+protected:
+ enum ButtonState {
+ kButtonStateDisabled,
+ kButtonStateIdle,
+ kButtonStateHighlighted,
+ kButtonStatePressed,
+ };
+
+ enum CheckboxState {
+ kCheckboxStateOff,
+ kCheckboxStateOffHighlighted,
+ kCheckboxStateOn,
+ kCheckboxStateOnHighlighted,
+ };
+
+ enum InteractionState {
+ kInteractionStateNotInteracting,
+
+ kInteractionStateOverButton,
+ kInteractionStateClickingOnButton,
+ kInteractionStateClickingOffButton,
+
+ kInteractionStateOverSlider,
+ kInteractionStateDraggingSlider,
+
+ kInteractionStateOverCheckbox,
+ kInteractionStateClickingOnCheckbox,
+ kInteractionStateClickingOffCheckbox,
+ };
+
+ struct Button {
+ Button();
+ Button(Graphics::Surface *graphic, const Common::Rect &graphicRect, const Common::Rect &screenRect, const Common::Point &stateOffset, bool enabled);
+
+ Graphics::Surface *_graphic;
+ Common::Rect _graphicRect;
+ Common::Rect _screenRect;
+ Common::Point _stateOffset;
+ bool _enabled;
+ };
+
+ struct Slider {
+ Slider();
+ Slider(Graphics::Surface *graphic, const Common::Rect &baseRect, int value, int maxValue);
+
+ Graphics::Surface *_graphic;
+ Common::Rect _baseRect;
+ int _value;
+ int _maxValue;
+ };
+
+private:
+ void drawButtonInState(uint buttonIndex, ButtonState state) const;
+ void drawCheckboxInState(uint buttonIndex, CheckboxState state) const;
+ void drawSlider(uint sliderIndex) const;
+ void drawButtonFromListInState(const Common::Array<Button> &buttonList, uint buttonIndex, int state) const;
+
+ void handleMouseMove(const Common::Point &pt);
+ void handleMouseDown(const Common::Point &pt, bool &outChangedState);
+ void handleMouseUp(const Common::Point &pt, bool &outChangedState);
+
+protected:
+ Common::Array<Button> _buttons;
+ Common::Array<Button> _checkboxes;
+ Common::Array<Slider> _sliders;
+
+ InteractionState _interactionState;
+ uint _interactionIndex;
+
+ Common::Point _sliderDragStart;
+ int _sliderDragValue;
+};
+
+class ReahMenuBarPage : public ReahMenuPage {
+public:
+ explicit ReahMenuBarPage(uint page);
+
+ void start() override final;
+
+protected:
+ enum MenuBarButtonID {
+ kMenuBarButtonHelp,
+ kMenuBarButtonSave,
+ kMenuBarButtonLoad,
+ kMenuBarButtonSound,
+ kMenuBarButtonQuit,
+
+ kMenuBarButtonReturn,
+ };
+
+ virtual void addPageContents() = 0;
+ void onButtonClicked(uint button, bool &outChangedState) override;
+
+ uint _page;
+};
+
+class ReahHelpMenuPage : public ReahMenuBarPage {
+public:
+ ReahHelpMenuPage();
+
+ void addPageContents() override;
+};
+
+class ReahSoundMenuPage : public ReahMenuBarPage {
+public:
+ ReahSoundMenuPage();
+
+ void addPageContents() override;
+
+protected:
+ void eraseSlider(uint sliderIndex) const override;
+ void onCheckboxClicked(uint button, bool &outChangedState) override;
+ void onSliderMoved(uint slider) override;
+
+private:
+ enum SoundMenuCheckbox {
+ kCheckboxSound = 0,
+ kCheckboxMusic,
+ };
+
+ enum SoundMenuSlider {
+ kSliderSound = 0,
+ kSliderMusic,
+ };
+
+ void applySoundVolume() const;
+ void applyMusicVolume() const;
+
+ Common::SharedPtr<Graphics::ManagedSurface> _sliderKeyGraphic;
+
+ static const int kSoundSliderWidth = 300;
+ static const int kSoundSliderY = 127;
+ static const int kMusicSliderY = 275;
+
+ bool _soundChecked;
+ bool _musicChecked;
+};
+
+class ReahQuitMenuPage : public ReahMenuBarPage {
+public:
+ ReahQuitMenuPage();
+
+ void addPageContents() override;
+ void onButtonClicked(uint button, bool &outChangedState) override;
+
+private:
+ enum QuitMenuButton {
+ kButtonYes = 6,
+ kButtonNo,
+ };
+};
+
+class ReahMainMenuPage : public ReahMenuPage {
+public:
+ void start() override;
+
+protected:
+ void onButtonClicked(uint button, bool &outChangedState) override;
+
+private:
+ enum ButtonID {
+ kButtonContinue,
+ kButtonNew,
+ kButtonLoad,
+ kButtonSound,
+ kButtonCredits,
+ kButtonQuit,
+ };
+};
+
+ReahMenuPage::ReahMenuPage() : _interactionIndex(0), _interactionState(kInteractionStateNotInteracting), _sliderDragValue(0) {
+}
+
+bool ReahMenuPage::run() {
+ bool changedState = false;
+
+ OSEvent evt;
+ while (_menuInterface->popOSEvent(evt)) {
+ switch (evt.type) {
+ case kOSEventTypeLButtonUp:
+ handleMouseMove(evt.pos);
+ handleMouseUp(evt.pos, changedState);
+ if (changedState)
+ return changedState;
+ break;
+ case kOSEventTypeLButtonDown:
+ handleMouseMove(evt.pos);
+ handleMouseDown(evt.pos, changedState);
+ if (changedState)
+ return changedState;
+ break;
+ case kOSEventTypeMouseMove:
+ handleMouseMove(evt.pos);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+void ReahMenuPage::start() {
+ for (uint buttonIndex = 0; buttonIndex < _buttons.size(); buttonIndex++)
+ drawButtonInState(buttonIndex, _buttons[buttonIndex]._enabled ? kButtonStateIdle : kButtonStateDisabled);
+
+ for (uint checkboxIndex = 0; checkboxIndex < _checkboxes.size(); checkboxIndex++)
+ drawCheckboxInState(checkboxIndex, _checkboxes[checkboxIndex]._enabled ? kCheckboxStateOn : kCheckboxStateOff);
+
+ for (uint sliderIndex = 0; sliderIndex < _sliders.size(); sliderIndex++)
+ drawSlider(sliderIndex);
+
+ Common::Point mousePoint = _menuInterface->getMouseCoordinate();
+ handleMouseMove(mousePoint);
+}
+
+void ReahMenuPage::onButtonClicked(uint button, bool &outChangedState) {
+ outChangedState = false;
+}
+
+void ReahMenuPage::onCheckboxClicked(uint button, bool &outChangedState) {
+}
+
+void ReahMenuPage::onSliderMoved(uint slider) {
+}
+
+void ReahMenuPage::eraseSlider(uint sliderIndex) const {
+}
+
+void ReahMenuPage::handleMouseMove(const Common::Point &pt) {
+ switch (_interactionState) {
+ case kInteractionStateNotInteracting:
+ for (uint buttonIndex = 0; buttonIndex < _buttons.size(); buttonIndex++) {
+ const Button &button = _buttons[buttonIndex];
+
+ if (button._enabled && button._screenRect.contains(pt)) {
+ drawButtonInState(buttonIndex, kButtonStateHighlighted);
+
+ _interactionIndex = buttonIndex;
+ _interactionState = kInteractionStateOverButton;
+ break;
+ }
+ }
+
+ for (uint checkboxIndex = 0; checkboxIndex < _checkboxes.size(); checkboxIndex++) {
+ const Button &checkbox = _checkboxes[checkboxIndex];
+
+ if (checkbox._screenRect.contains(pt)) {
+ drawCheckboxInState(checkboxIndex, checkbox._enabled ? kCheckboxStateOnHighlighted : kCheckboxStateOffHighlighted);
+
+ _interactionIndex = checkboxIndex;
+ _interactionState = kInteractionStateOverCheckbox;
+ break;
+ }
+ }
+
+ for (uint sliderIndex = 0; sliderIndex < _sliders.size(); sliderIndex++) {
+ const Slider &slider = _sliders[sliderIndex];
+
+ Common::Rect sliderRect = slider._baseRect;
+ sliderRect.translate(slider._value, 0);
+
+ if (sliderRect.contains(pt)) {
+ _interactionIndex = sliderIndex;
+ _interactionState = kInteractionStateOverSlider;
+ }
+ }
+ break;
+
+ case kInteractionStateOverButton: {
+ const Button &button = _buttons[_interactionIndex];
+ if (!button._screenRect.contains(pt)) {
+ drawButtonInState(_interactionIndex, kButtonStateIdle);
+
+ _interactionState = kInteractionStateNotInteracting;
+ handleMouseMove(pt);
+ }
+ } break;
+
+ case kInteractionStateClickingOnButton: {
+ const Button &button = _buttons[_interactionIndex];
+ if (!button._screenRect.contains(pt)) {
+ drawButtonInState(_interactionIndex, kButtonStateHighlighted);
+
+ _interactionState = kInteractionStateClickingOffButton;
+ }
+ } break;
+
+ case kInteractionStateClickingOffButton: {
+ const Button &button = _buttons[_interactionIndex];
+ if (button._screenRect.contains(pt)) {
+ drawButtonInState(_interactionIndex, kButtonStatePressed);
+
+ _interactionState = kInteractionStateClickingOnButton;
+ }
+ } break;
+
+ case kInteractionStateOverSlider: {
+ const Slider &slider = _sliders[_interactionIndex];
+
+ Common::Rect sliderRect = slider._baseRect;
+ sliderRect.translate(slider._value, 0);
+
+ if (!sliderRect.contains(pt)) {
+ _interactionState = kInteractionStateNotInteracting;
+ handleMouseMove(pt);
+ }
+ } break;
+
+ case kInteractionStateDraggingSlider: {
+ Slider &slider = _sliders[_interactionIndex];
+
+ int newValue = _sliderDragValue + pt.x - _sliderDragStart.x;
+ if (newValue < 0)
+ newValue = 0;
+ else if (newValue >= slider._maxValue)
+ newValue = slider._maxValue;
+
+ if (newValue != slider._value) {
+ eraseSlider(_interactionIndex);
+ slider._value = newValue;
+ drawSlider(_interactionIndex);
+
+ onSliderMoved(_interactionIndex);
+ }
+ } break;
+
+ case kInteractionStateOverCheckbox: {
+ const Button &checkbox = _checkboxes[_interactionIndex];
+ if (!checkbox._screenRect.contains(pt)) {
+ drawCheckboxInState(_interactionIndex, checkbox._enabled ? kCheckboxStateOn : kCheckboxStateOff);
+
+ _interactionState = kInteractionStateNotInteracting;
+ handleMouseMove(pt);
+ }
+ } break;
+
+ case kInteractionStateClickingOnCheckbox: {
+ const Button &checkbox = _checkboxes[_interactionIndex];
+ if (!checkbox._screenRect.contains(pt)) {
+ drawCheckboxInState(_interactionIndex, checkbox._enabled ? kCheckboxStateOnHighlighted : kCheckboxStateOffHighlighted);
+
+ _interactionState = kInteractionStateClickingOffCheckbox;
+ }
+ } break;
+
+ case kInteractionStateClickingOffCheckbox: {
+ const Button &checkbox = _checkboxes[_interactionIndex];
+ if (checkbox._screenRect.contains(pt)) {
+ drawCheckboxInState(_interactionIndex, checkbox._enabled ? kCheckboxStateOffHighlighted : kCheckboxStateOnHighlighted);
+
+ _interactionState = kInteractionStateClickingOnCheckbox;
+ }
+ } break;
+
+ default:
+ error("Unhandled UI state");
+ break;
+ }
+}
+
+void ReahMenuPage::handleMouseDown(const Common::Point &pt, bool &outChangedState) {
+ switch (_interactionState) {
+ case kInteractionStateNotInteracting:
+ case kInteractionStateClickingOnButton:
+ case kInteractionStateClickingOffButton:
+ case kInteractionStateDraggingSlider:
+ case kInteractionStateClickingOnCheckbox:
+ case kInteractionStateClickingOffCheckbox:
+ break;
+
+ case kInteractionStateOverButton:
+ drawButtonInState(_interactionIndex, kButtonStatePressed);
+ _interactionState = kInteractionStateClickingOnButton;
+ break;
+
+ case kInteractionStateOverSlider:
+ _interactionState = kInteractionStateDraggingSlider;
+ _sliderDragStart = pt;
+ _sliderDragValue = _sliders[_interactionIndex]._value;
+ break;
+
+ case kInteractionStateOverCheckbox:
+ drawCheckboxInState(_interactionIndex, _checkboxes[_interactionIndex]._enabled ? kCheckboxStateOffHighlighted : kCheckboxStateOnHighlighted);
+ _interactionState = kInteractionStateClickingOnCheckbox;
+ break;
+
+ default:
+ break;
+ }
+}
+
+void ReahMenuPage::handleMouseUp(const Common::Point &pt, bool &outChangedState) {
+ switch (_interactionState) {
+ case kInteractionStateNotInteracting:
+ case kInteractionStateOverButton:
+ case kInteractionStateOverCheckbox:
+ case kInteractionStateOverSlider:
+ break;
+
+ case kInteractionStateClickingOnButton:
+ drawButtonInState(_interactionIndex, kButtonStateHighlighted);
+ _interactionState = kInteractionStateOverButton;
+
+ onButtonClicked(_interactionIndex, outChangedState);
+ break;
+
+ case kInteractionStateClickingOffButton:
+ drawButtonInState(_interactionIndex, kButtonStateIdle);
+ _interactionState = kInteractionStateNotInteracting;
+ handleMouseMove(pt);
+ break;
+
+ case kInteractionStateDraggingSlider:
+ _interactionState = kInteractionStateNotInteracting;
+ handleMouseMove(pt);
+ break;
+
+ case kInteractionStateClickingOnCheckbox:
+ _checkboxes[_interactionIndex]._enabled = !_checkboxes[_interactionIndex]._enabled;
+ drawCheckboxInState(_interactionIndex, _checkboxes[_interactionIndex]._enabled ? kCheckboxStateOnHighlighted : kCheckboxStateOffHighlighted);
+ _interactionState = kInteractionStateOverCheckbox;
+
+ onCheckboxClicked(_interactionIndex, outChangedState);
+ break;
+
+ case kInteractionStateClickingOffCheckbox:
+ drawCheckboxInState(_interactionIndex, _checkboxes[_interactionIndex]._enabled ? kCheckboxStateOn : kCheckboxStateOff);
+ _interactionState = kInteractionStateNotInteracting;
+ handleMouseMove(pt);
+ break;
+
+ default:
+ break;
+ }
+}
+
+ReahMenuBarPage::ReahMenuBarPage(uint page) : _page(page) {
+}
+
+void ReahMenuBarPage::start() {
+ Graphics::Surface *graphic = _menuInterface->getUIGraphic(4);
+
+ bool menuButtonsEnabled[5] = {true, true, true, true, true};
+
+ menuButtonsEnabled[1] = _menuInterface->canSave();
+ menuButtonsEnabled[_page] = false;
+
+ if (graphic) {
+ for (int buttonIndex = 0; buttonIndex < 5; buttonIndex++) {
+ Common::Rect buttonRect(128 * buttonIndex, 0, 128 * buttonIndex + 128, 44);
+ _buttons.push_back(Button(graphic, buttonRect, buttonRect, Common::Point(0, 44), menuButtonsEnabled[buttonIndex]));
+ }
+ }
+
+ Graphics::Surface *returnButtonGraphic = _menuInterface->getUIGraphic(9);
+ if (returnButtonGraphic)
+ _buttons.push_back(Button(returnButtonGraphic, Common::Rect(0, 0, 112, 44), Common::Rect(519, 423, 631, 467), Common::Point(0, 44), true));
+
+ Graphics::Surface *lowerBarGraphic = _menuInterface->getUIGraphic(8);
+
+ if (lowerBarGraphic) {
+ _menuInterface->getMenuSurface()->blitFrom(*lowerBarGraphic, Common::Point(0, 392));
+ _menuInterface->commitRect(Common::Rect(0, 392, 640, 480));
+ }
+
+ addPageContents();
+
+ ReahMenuPage::start();
+}
+
+void ReahMenuBarPage::onButtonClicked(uint button, bool &outChangedState) {
+ switch (button) {
+ case kMenuBarButtonHelp:
+ _menuInterface->changeMenu(new ReahHelpMenuPage());
+ outChangedState = true;
+ break;
+ case kMenuBarButtonLoad:
+ outChangedState = g_engine->loadGameDialog();
+ break;
+ case kMenuBarButtonSave:
+ g_engine->saveGameDialog();
+ break;
+ case kMenuBarButtonSound:
+ _menuInterface->changeMenu(new ReahSoundMenuPage());
+ outChangedState = true;
+ break;
+ case kMenuBarButtonQuit:
+ _menuInterface->changeMenu(new ReahQuitMenuPage());
+ outChangedState = true;
+ break;
+
+ case kMenuBarButtonReturn:
+ if (_menuInterface->canSave())
+ outChangedState = _menuInterface->reloadFromCheckpoint();
+ else {
+ _menuInterface->changeMenu(new ReahMainMenuPage());
+ outChangedState = true;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void ReahMenuPage::drawButtonInState(uint buttonIndex, ButtonState state) const {
+ drawButtonFromListInState(_buttons, buttonIndex, state);
+}
+
+void ReahMenuPage::drawCheckboxInState(uint buttonIndex, CheckboxState state) const {
+ drawButtonFromListInState(_checkboxes, buttonIndex, state);
+}
+
+void ReahMenuPage::drawSlider(uint sliderIndex) const {
+ const Slider &slider = _sliders[sliderIndex];
+
+ Common::Point screenPoint(slider._baseRect.left + slider._value, slider._baseRect.top);
+
+ _menuInterface->getMenuSurface()->blitFrom(*slider._graphic, screenPoint);
+ _menuInterface->commitRect(Common::Rect(screenPoint.x, screenPoint.y, screenPoint.x + slider._baseRect.width(), screenPoint.y + slider._baseRect.height()));
+}
+
+void ReahMenuPage::drawButtonFromListInState(const Common::Array<Button> &buttonList, uint buttonIndex, int state) const {
+ const Button &button = buttonList[buttonIndex];
+
+ Common::Rect graphicRect = button._graphicRect;
+ graphicRect.translate(button._stateOffset.x * state, button._stateOffset.y * state);
+
+ _menuInterface->getMenuSurface()->blitFrom(*button._graphic, graphicRect, button._screenRect);
+ _menuInterface->commitRect(Common::Rect(button._screenRect.left, button._screenRect.top, button._screenRect.left + graphicRect.width(), button._screenRect.top + graphicRect.height()));
+}
+
+ReahMenuPage::Button::Button() : _graphic(nullptr), _enabled(true) {
+}
+
+ReahMenuPage::Button::Button(Graphics::Surface *graphic, const Common::Rect &graphicRect, const Common::Rect &screenRect, const Common::Point &stateOffset, bool enabled)
+ : _graphic(graphic), _graphicRect(graphicRect), _screenRect(screenRect), _stateOffset(stateOffset), _enabled(enabled) {
+}
+
+ReahMenuPage::Slider::Slider() : _graphic(nullptr), _value(0), _maxValue(1) {
+}
+
+ReahMenuPage::Slider::Slider(Graphics::Surface *graphic, const Common::Rect &baseRect, int value, int maxValue)
+ : _graphic(graphic), _baseRect(baseRect), _value(value), _maxValue(maxValue) {
+ assert(_value >= 0 && _value <= maxValue);
+}
+
+ReahHelpMenuPage::ReahHelpMenuPage() : ReahMenuBarPage(kMenuBarButtonHelp) {
+}
+
+void ReahHelpMenuPage::addPageContents() {
+ Graphics::Surface *helpBG = _menuInterface->getUIGraphic(12);
+ if (helpBG) {
+ _menuInterface->getMenuSurface()->blitFrom(*helpBG, Common::Point(0, 44));
+ _menuInterface->commitRect(Common::Rect(0, 44, helpBG->w, 44 + helpBG->h));
+ }
+}
+
+ReahSoundMenuPage::ReahSoundMenuPage() : ReahMenuBarPage(kMenuBarButtonSound), _soundChecked(false), _musicChecked(false) {
+}
+
+void ReahSoundMenuPage::addPageContents() {
+ Graphics::Surface *soundBG = _menuInterface->getUIGraphic(16);
+ if (soundBG) {
+ _menuInterface->getMenuSurface()->blitFrom(*soundBG, Common::Point(0, 44));
+ _menuInterface->commitRect(Common::Rect(0, 44, soundBG->w, 44 + soundBG->h));
+ }
+
+ int sndVol = 0;
+ if (ConfMan.hasKey("sfx_volume"))
+ sndVol = ConfMan.getInt("sfx_volume");
+
+ int musVol = 0;
+ if (ConfMan.hasKey("music_volume"))
+ musVol = ConfMan.getInt("music_volume");
+
+ _soundChecked = (sndVol != 0);
+ _musicChecked = (musVol != 0);
+
+ // Set defaults so clicking the checkbox does something
+ if (sndVol == 0)
+ sndVol = 3 * Audio::Mixer::kMaxMixerVolume / 4;
+
+ if (musVol == 0)
+ musVol = 3 * Audio::Mixer::kMaxMixerVolume / 4;
+
+ Graphics::Surface *soundGraphics = _menuInterface->getUIGraphic(17);
+ if (soundGraphics) {
+ _checkboxes.push_back(Button(soundGraphics, Common::Rect(0, 0, 112, 44), Common::Rect(77, 90, 77 + 112, 90 + 44), Common::Point(0, 44), _soundChecked));
+ _checkboxes.push_back(Button(soundGraphics, Common::Rect(112, 0, 224, 44), Common::Rect(77, 231, 77 + 112, 231 + 44), Common::Point(0, 44), _musicChecked));
+
+ Common::Point sliderSize(40, 60);
+
+ _sliderKeyGraphic.reset(new Graphics::ManagedSurface(sliderSize.x, sliderSize.y, Graphics::createPixelFormat<8888>()));
+
+ Graphics::PixelFormat srcFormat = soundGraphics->format;
+ Graphics::PixelFormat dstFormat = _sliderKeyGraphic->format;
+
+ for (int y = 0; y < sliderSize.y; y++) {
+ for (int x = 0; x < sliderSize.x; x++) {
+ uint32 maskColor = soundGraphics->getPixel(224 + x, y + 60);
+
+ byte r = 0;
+ byte g = 0;
+ byte b = 0;
+ srcFormat.colorToRGB(maskColor, r, g, b);
+
+ uint32 dstColor = 0;
+ if (r > 128) {
+ dstColor = dstFormat.ARGBToColor(0, 0, 0, 0);
+ } else {
+ uint32 srcColor = soundGraphics->getPixel(224 + x, y);
+ srcFormat.colorToRGB(srcColor, r, g, b);
+ dstColor = dstFormat.ARGBToColor(255, r, g, b);
+ }
+
+ _sliderKeyGraphic->setPixel(x, y, dstColor);
+ }
+ }
+
+ _sliders.push_back(Slider(_sliderKeyGraphic->surfacePtr(), Common::Rect(236, kSoundSliderY, 236 + 40, kSoundSliderY + 60), sndVol * kSoundSliderWidth / Audio::Mixer::kMaxMixerVolume, kSoundSliderWidth));
+ _sliders.push_back(Slider(_sliderKeyGraphic->surfacePtr(), Common::Rect(236, kMusicSliderY, 236 + 40, kMusicSliderY + 60), musVol * kSoundSliderWidth / Audio::Mixer::kMaxMixerVolume, kSoundSliderWidth));
+ }
+}
+
+void ReahSoundMenuPage::eraseSlider(uint sliderIndex) const {
+ Graphics::Surface *soundBG = _menuInterface->getUIGraphic(16);
+
+ if (soundBG) {
+ Common::Rect sliderRect = _sliders[sliderIndex]._baseRect;
+ sliderRect.translate(_sliders[sliderIndex]._value, 0);
+
+ Common::Rect backgroundSourceRect = sliderRect;
+ backgroundSourceRect.translate(0, -44);
+
+ _menuInterface->getMenuSurface()->blitFrom(*soundBG, backgroundSourceRect, Common::Point(sliderRect.left, sliderRect.top));
+ _menuInterface->commitRect(sliderRect);
+ }
+}
+
+void ReahSoundMenuPage::onCheckboxClicked(uint button, bool &outChangedState) {
+ if (button == kCheckboxSound) {
+ _soundChecked = _checkboxes[button]._enabled;
+ applySoundVolume();
+ }
+ if (button == kCheckboxMusic) {
+ _musicChecked = _checkboxes[button]._enabled;
+ applyMusicVolume();
+ }
+
+ outChangedState = false;
+}
+
+void ReahSoundMenuPage::onSliderMoved(uint slider) {
+ if (slider == kSliderSound && _soundChecked)
+ applySoundVolume();
+
+ if (slider == kSliderMusic && _musicChecked)
+ applyMusicVolume();
+}
+
+void ReahSoundMenuPage::applySoundVolume() const {
+ int vol = 0;
+
+ if (_soundChecked)
+ vol = _sliders[kSliderSound]._value * Audio::Mixer::kMaxMixerVolume / _sliders[kSliderSound]._maxValue;
+
+ ConfMan.setInt("sfx_volume", vol, ConfMan.getActiveDomainName());
+
+ if (g_engine->_mixer)
+ g_engine->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, vol);
+}
+
+void ReahSoundMenuPage::applyMusicVolume() const {
+ int vol = 0;
+
+ if (_musicChecked)
+ vol = _sliders[kSliderMusic]._value * Audio::Mixer::kMaxMixerVolume / _sliders[kSliderMusic]._maxValue;
+
+ ConfMan.setInt("music_volume", vol, ConfMan.getActiveDomainName());
+
+ if (g_engine->_mixer)
+ g_engine->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, vol);
+}
+
+ReahQuitMenuPage::ReahQuitMenuPage() : ReahMenuBarPage(kMenuBarButtonQuit) {
+}
+
+void ReahQuitMenuPage::addPageContents() {
+ Graphics::ManagedSurface *menuSurf = _menuInterface->getMenuSurface();
+ menuSurf->fillRect(Common::Rect(0, 44, 640, 392), menuSurf->format.RGBToColor(0, 0, 0));
+
+ Graphics::Surface *borderGraphic = _menuInterface->getUIGraphic(10);
+
+ if (borderGraphic) {
+ Graphics::PixelFormat borderGraphicFmt = borderGraphic->format;
+ Graphics::PixelFormat menuSurfFmt = menuSurf->format;
+ byte r = 0;
+ byte g = 0;
+ byte b = 0;
+
+ const int xOffsets[2] = {0, 640 - 16};
+
+ for (int y = 0; y < borderGraphic->h; y++) {
+ for (int x = 0; x < 16; x++) {
+ uint32 pixels[2] = { borderGraphic->getPixel(x, y), borderGraphic->getPixel(x + 16, y) };
+ int intensities[2] = {(16 - x) * 32, (x + 1) * 32};
+ for (int i = 0; i < 2; i++) {
+ borderGraphicFmt.colorToRGB(pixels[i], r, g, b);
+
+ int intensity = intensities[i];
+ if (intensity < 256) {
+ r = (r * intensity) >> 8;
+ g = (g * intensity) >> 8;
+ b = (b * intensity) >> 8;
+ }
+
+ menuSurf->setPixel(x + xOffsets[i], y + 44, menuSurfFmt.RGBToColor(r, g, b));
+ }
+ }
+ }
+ }
+
+ Graphics::Surface *windowGraphic = _menuInterface->getUIGraphic(13);
+
+ if (windowGraphic)
+ menuSurf->blitFrom(*windowGraphic, Common::Point(82, 114));
+
+ Graphics::Surface *textGraphic = _menuInterface->getUIGraphic(14);
+
+ if (textGraphic)
+ menuSurf->blitFrom(*textGraphic, Common::Rect(0, 72, textGraphic->w, textGraphic->h), Common::Point(82, 174));
+
+ Graphics::Surface *buttonsGraphic = _menuInterface->getUIGraphic(15);
+
+ if (buttonsGraphic) {
+ _buttons.push_back(Button(buttonsGraphic, Common::Rect(224, 0, 336, 44), Common::Rect(174, 246, 286, 290), Common::Point(0, 44), true));
+ _buttons.push_back(Button(buttonsGraphic, Common::Rect(336, 0, 448, 44), Common::Rect(351, 248, 463, 292), Common::Point(0, 44), true));
+ }
+
+ _menuInterface->commitRect(Common::Rect(0, 44, 640, 392));
+
+ // Disable the "Return" button since the "No" button is functionally the same (and Reah does this)
+ _buttons[kMenuBarButtonReturn]._enabled = false;
+}
+
+void ReahQuitMenuPage::onButtonClicked(uint button, bool &outChangedState) {
+ ReahMenuBarPage::onButtonClicked(button, outChangedState);
+
+ if (button == kButtonYes)
+ _menuInterface->quitGame();
+ else if (button == kButtonNo)
+ onButtonClicked(kMenuBarButtonReturn, outChangedState);
+}
+
+void ReahMainMenuPage::start() {
+ Graphics::Surface *bgGraphic = _menuInterface->getUIGraphic(0);
+
+ Graphics::ManagedSurface *menuSurf = _menuInterface->getMenuSurface();
+
+ if (bgGraphic) {
+ menuSurf->blitFrom(*bgGraphic, Common::Point(0, 0));
+ }
+
+ _menuInterface->commitRect(Common::Rect(0, 0, 640, 480));
+
+ Graphics::Surface *buttonGraphic = _menuInterface->getUIGraphic(1);
+
+ Common::Point buttonStateOffset = Common::Point(112, 0);
+ Common::Point buttonTopLeft = Common::Point(492, 66);
+ const int buttonTopYs[6] = {66, 119, 171, 224, 277, 330};
+
+ for (int i = 0; i < 6; i++) {
+ bool isEnabled = true;
+ if (i == kButtonContinue)
+ isEnabled = _menuInterface->hasDefaultSave();
+
+ _buttons.push_back(Button(buttonGraphic, Common::Rect(0, i * 44, 112, i * 44 + 44), Common::Rect(492, buttonTopYs[i], 492 + 112, buttonTopYs[i] + 44), Common::Point(112, 0), isEnabled));
+ }
+
+ ReahMenuPage::start();
+}
+
+void ReahMainMenuPage::onButtonClicked(uint button, bool &outChangedState) {
+ switch (button) {
+ case kButtonContinue: {
+ Common::Error loadError = g_engine->loadGameState(g_engine->getAutosaveSlot());
+ outChangedState = (loadError.getCode() == Common::kNoError);
+ } break;
+
+ case kButtonNew:
+ _menuInterface->restartGame();
+ outChangedState = true;
+ break;
+
+ case kButtonLoad:
+ outChangedState = g_engine->loadGameDialog();
+ break;
+
+ case kButtonSound:
+ _menuInterface->changeMenu(new ReahSoundMenuPage());
+ outChangedState = true;
+ break;
+
+ case kButtonCredits:
+ _menuInterface->goToCredits();
+ outChangedState = true;
+ break;
+
+ case kButtonQuit:
+ _menuInterface->changeMenu(new ReahQuitMenuPage());
+ outChangedState = true;
+ break;
+ }
+}
+
+MenuInterface::~MenuInterface() {
+}
+
+MenuPage::MenuPage() : _menuInterface(nullptr) {
+}
+
+MenuPage::~MenuPage() {
+}
+
+void MenuPage::init(const MenuInterface *menuInterface) {
+ _menuInterface = menuInterface;
+}
+
+void MenuPage::start() {
+}
+
+bool MenuPage::run() {
+ return false;
+}
+
+MenuPage *createMenuReahMain() {
+ return new ReahMainMenuPage();
+}
+
+} // End of namespace VCruise
diff --git a/engines/vcruise/menu.h b/engines/vcruise/menu.h
new file mode 100644
index 00000000000..5fd1beab317
--- /dev/null
+++ b/engines/vcruise/menu.h
@@ -0,0 +1,83 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef VCRUISE_MENU_H
+#define VCRUISE_MENU_H
+
+#include "common/array.h"
+#include "common/ptr.h"
+
+namespace Graphics {
+
+struct Surface;
+class ManagedSurface;
+
+} // End of namespace Graphics
+
+namespace Common {
+
+struct Rect;
+
+} // End of namespace Common
+
+namespace VCruise {
+
+struct OSEvent;
+class MenuPage;
+class Runtime;
+
+class MenuInterface {
+public:
+ virtual ~MenuInterface();
+
+ virtual void commitRect(const Common::Rect &rect) const = 0;
+ virtual bool popOSEvent(OSEvent &evt) const = 0;
+ virtual Graphics::Surface *getUIGraphic(uint index) const = 0;
+ virtual Graphics::ManagedSurface *getMenuSurface() const = 0;
+ virtual bool hasDefaultSave() const = 0;
+ virtual Common::Point getMouseCoordinate() const = 0;
+ virtual void restartGame() const = 0;
+ virtual void goToCredits() const = 0;
+ virtual void changeMenu(MenuPage *newPage) const = 0;
+ virtual void quitGame() const = 0;
+ virtual bool canSave() const = 0;
+ virtual bool reloadFromCheckpoint() const = 0;
+};
+
+class MenuPage {
+public:
+ MenuPage();
+ virtual ~MenuPage();
+
+ void init(const MenuInterface *menuInterface);
+
+ virtual void start();
+ virtual bool run();
+
+protected:
+ const MenuInterface *_menuInterface;
+};
+
+MenuPage *createMenuReahMain();
+
+} // End of namespace VCruise
+
+#endif
diff --git a/engines/vcruise/module.mk b/engines/vcruise/module.mk
index 5360a78e6d6..95134a32ce3 100644
--- a/engines/vcruise/module.mk
+++ b/engines/vcruise/module.mk
@@ -3,6 +3,7 @@ MODULE := engines/vcruise
MODULE_OBJS = \
audio_player.o \
metaengine.o \
+ menu.o \
runtime.o \
script.o \
textparser.o \
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 4d3bb4b5061..40791fd07fa 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -22,10 +22,12 @@
#include "common/formats/winexe.h"
#include "common/config-manager.h"
#include "common/endian.h"
+#include "common/events.h"
#include "common/file.h"
#include "common/math.h"
#include "common/ptr.h"
#include "common/random.h"
+#include "common/savefile.h"
#include "common/system.h"
#include "common/stream.h"
@@ -46,13 +48,114 @@
#include "gui/message.h"
#include "vcruise/audio_player.h"
+#include "vcruise/menu.h"
#include "vcruise/runtime.h"
#include "vcruise/script.h"
#include "vcruise/textparser.h"
+#include "vcruise/vcruise.h"
namespace VCruise {
+class RuntimeMenuInterface : public MenuInterface {
+public:
+ explicit RuntimeMenuInterface(Runtime *runtime);
+
+ void commitRect(const Common::Rect &rect) const override;
+ bool popOSEvent(OSEvent &evt) const override;
+ Graphics::Surface *getUIGraphic(uint index) const override;
+ Graphics::ManagedSurface *getMenuSurface() const override;
+ bool hasDefaultSave() const override;
+ Common::Point getMouseCoordinate() const override;
+ void restartGame() const override;
+ void goToCredits() const override;
+ void changeMenu(MenuPage *newPage) const override;
+ void quitGame() const override;
+ bool canSave() const override;
+ bool reloadFromCheckpoint() const override;
+
+private:
+ Runtime *_runtime;
+};
+
+
+RuntimeMenuInterface::RuntimeMenuInterface(Runtime *runtime) : _runtime(runtime) {
+}
+
+void RuntimeMenuInterface::commitRect(const Common::Rect &rect) const {
+ _runtime->commitSectionToScreen(_runtime->_fullscreenMenuSection, rect);
+}
+
+bool RuntimeMenuInterface::popOSEvent(OSEvent &evt) const {
+ return _runtime->popOSEvent(evt);
+}
+
+Graphics::Surface *RuntimeMenuInterface::getUIGraphic(uint index) const {
+ if (index >= _runtime->_uiGraphics.size())
+ return nullptr;
+ return _runtime->_uiGraphics[index].get();
+}
+
+Graphics::ManagedSurface *RuntimeMenuInterface::getMenuSurface() const {
+ return _runtime->_fullscreenMenuSection.surf.get();
+}
+
+bool RuntimeMenuInterface::hasDefaultSave() const {
+ return static_cast<VCruiseEngine *>(g_engine)->hasDefaultSave();
+}
+
+Common::Point RuntimeMenuInterface::getMouseCoordinate() const {
+ return _runtime->_mousePos;
+}
+
+void RuntimeMenuInterface::restartGame() const {
+ Common::SharedPtr<SaveGameSnapshot> snapshot(new SaveGameSnapshot());
+
+ if (_runtime->_gameID == GID_REAH) {
+ snapshot->roomNumber = 1;
+ snapshot->screenNumber = 0xb0;
+ snapshot->loadedAnimation = 1;
+ } else {
+ error("Don't know what screen to start on for this game");
+ }
+
+ _runtime->_saveGame = snapshot;
+ _runtime->restoreSaveGameSnapshot();
+}
+
+void RuntimeMenuInterface::goToCredits() const {
+ _runtime->clearScreen();
+
+ if (_runtime->_gameID == GID_REAH) {
+ _runtime->changeToScreen(40, 0xa1);
+ } else {
+ error("Don't know what screen to go to for credits for this game");
+ }
+}
+
+void RuntimeMenuInterface::changeMenu(MenuPage *newPage) const {
+ _runtime->changeToMenuPage(newPage);
+}
+
+void RuntimeMenuInterface::quitGame() const {
+ Common::Event evt;
+ evt.type = Common::EVENT_QUIT;
+
+ g_engine->getEventManager()->pushEvent(evt);
+}
+
+bool RuntimeMenuInterface::canSave() const {
+ return _runtime->canSave();
+}
+
+bool RuntimeMenuInterface::reloadFromCheckpoint() const {
+ if (!_runtime->canSave())
+ return false;
+
+ _runtime->restoreSaveGameSnapshot();
+ return true;
+}
+
AnimationDef::AnimationDef() : animNum(0), firstFrame(0), lastFrame(0) {
}
@@ -77,7 +180,10 @@ const MapScreenDirectionDef *MapDef::getScreenDirection(uint screen, uint direct
return screenDirections[screen][direction].get();
}
-ScriptEnvironmentVars::ScriptEnvironmentVars() : lmb(false), lmbDrag(false), panInteractionID(0), fpsOverride(0), lastHighlightedItem(0) {
+ScriptEnvironmentVars::ScriptEnvironmentVars() : lmb(false), lmbDrag(false), esc(false), exitToMenu(false), panInteractionID(0), fpsOverride(0), lastHighlightedItem(0) {
+}
+
+OSEvent::OSEvent() : type(kOSEventTypeInvalid), keyCode(static_cast<Common::KeyCode>(0)) {
}
void Runtime::RenderSection::init(const Common::Rect ¶mRect, const Graphics::PixelFormat &fmt) {
@@ -86,9 +192,6 @@ void Runtime::RenderSection::init(const Common::Rect ¶mRect, const Graphics:
surf->fillRect(Common::Rect(0, 0, surf->w, surf->h), 0xffffffff);
}
-Runtime::OSEvent::OSEvent() : type(kOSEventTypeInvalid), keyCode(static_cast<Common::KeyCode>(0)) {
-}
-
Runtime::StackValue::ValueUnion::ValueUnion() {
}
@@ -739,13 +842,14 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
_havePendingPlayAmbientSounds(false), _ambientSoundFinishTime(0), _scriptNextInstruction(0), _escOn(false), _debugMode(false), _fastAnimationMode(false),
_musicTrack(0), _musicVolume(100), _musicVolumeRampStartTime(0), _musicVolumeRampStartVolume(0), _musicVolumeRampRatePerMSec(0), _musicVolumeRampEnd(0),
_panoramaDirectionFlags(0),
- _loadedAnimation(0), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0),
+ _loadedAnimation(0), _loadedAnimationHasSound(false), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0),
_animStartTime(0), _animFramesDecoded(0), _animDecoderState(kAnimDecoderStateStopped),
_animPlayWhileIdle(false), _idleIsOnInteraction(false), _idleHaveClickInteraction(false), _idleHaveDragInteraction(false), _idleInteractionID(0), _haveIdleStaticAnimation(false),
/*_loadedArea(0), */_lmbDown(false), _lmbDragging(false), _lmbReleaseWasClick(false), _lmbDownTime(0),
_delayCompletionTime(0),
_panoramaState(kPanoramaStateInactive),
_listenerX(0), _listenerY(0), _listenerAngle(0), _soundCacheIndex(0),
+ _isInGame(false),
_subtitleFont(nullptr), _isDisplayingSubtitles(false), _languageIndex(0) {
for (uint i = 0; i < kNumDirections; i++) {
@@ -769,15 +873,18 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
if (!_subtitleFont)
warning("Couldn't load subtitle font, subtitles will be disabled");
+
+ _menuInterface.reset(new RuntimeMenuInterface(this));
}
Runtime::~Runtime() {
}
-void Runtime::initSections(Common::Rect gameRect, Common::Rect menuRect, Common::Rect trayRect, const Graphics::PixelFormat &pixFmt) {
+void Runtime::initSections(const Common::Rect &gameRect, const Common::Rect &menuRect, const Common::Rect &trayRect, const Common::Rect &fullscreenMenuRect, const Graphics::PixelFormat &pixFmt) {
_gameSection.init(gameRect, pixFmt);
_menuSection.init(menuRect, pixFmt);
_traySection.init(trayRect, pixFmt);
+ _fullscreenMenuSection.init(fullscreenMenuRect, pixFmt);
}
void Runtime::loadCursors(const char *exeName) {
@@ -891,6 +998,9 @@ bool Runtime::runFrame() {
case kGameStateGyroAnimation:
moreActions = runGyroAnimation();
break;
+ case kGameStateMenu:
+ moreActions = _menuPage->run();
+ break;
default:
error("Unknown game state");
return false;
@@ -929,8 +1039,7 @@ bool Runtime::bootGame(bool newGame) {
if (newGame) {
if (_gameID == GID_REAH) {
- // TODO: Change to the logo instead (0xb1) instead when menus are implemented
- changeToScreen(1, 0xb0);
+ changeToScreen(1, 0xb1);
} else
error("Couldn't figure out what screen to start on");
}
@@ -1010,6 +1119,15 @@ bool Runtime::bootGame(bool newGame) {
loadSubtitles(codePage);
debug(1, "Subtitles loaded OK");
+ _uiGraphics.resize(24);
+ for (uint i = 0; i < _uiGraphics.size(); i++) {
+ if (_gameID == GID_REAH) {
+ _uiGraphics[i] = loadGraphic(Common::String::format("Image%03u", static_cast<uint>(_languageIndex * 100u + i)), false);
+ if (_languageIndex != 0 && !_uiGraphics[i])
+ _uiGraphics[i] = loadGraphic(Common::String::format("Image%03u", static_cast<uint>(i)), false);
+ }
+ }
+
return true;
}
@@ -1686,6 +1804,14 @@ void Runtime::terminateScript() {
if (_havePendingScreenChange)
changeToScreen(_roomNumber, _screenNumber);
+
+ if (_scriptEnv.exitToMenu && _gameState == kGameStateIdle) {
+ changeToCursor(_cursors[kCursorArrow]);
+ if (_gameID == GID_REAH)
+ changeToMenuPage(createMenuReahMain());
+ else
+ error("Missing main menu behavior for this game");
+ }
}
bool Runtime::checkCompletionConditions() {
@@ -2366,7 +2492,7 @@ void Runtime::loadFrameData2(Common::SeekableReadStream *stream) {
}
void Runtime::changeMusicTrack(int track) {
- if (track == _musicTrack)
+ if (track == _musicTrack && _musicPlayer.get() != nullptr)
return;
_musicPlayer.reset();
@@ -2448,6 +2574,8 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
loadFrameData2(&twoDtFile);
twoDtFile.close();
+ _loadedAnimationHasSound = (_animDecoder->getAudioTrackCount() > 0);
+
stopSubtitles();
}
@@ -2474,7 +2602,7 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
_animFrameRateLock = Fraction(_scriptEnv.fpsOverride, 1);
_scriptEnv.fpsOverride = 0;
} else {
- if (!_fastAnimationMode && _animDecoder && _animDecoder->getAudioTrackCount() == 0)
+ if (!_fastAnimationMode && _animDecoder && !_loadedAnimationHasSound)
_animFrameRateLock = defaultFrameRate;
}
@@ -3227,6 +3355,10 @@ void Runtime::inventoryRemoveItem(uint itemID) {
}
}
+void Runtime::clearScreen() {
+ _system->fillScreen(_system->getScreenFormat().RGBToColor(0, 0, 0));
+}
+
void Runtime::redrawTray() {
if (_subtitleQueue.size() != 0)
return;
@@ -3245,7 +3377,7 @@ void Runtime::clearTray() {
}
void Runtime::drawInventory(uint slot) {
- if (_subtitleQueue.size() > 0)
+ if (_subtitleQueue.size() > 0 || _loadedAnimationHasSound || !_isInGame)
return;
Common::Rect trayRect = _traySection.rect;
@@ -3285,7 +3417,7 @@ void Runtime::drawInventory(uint slot) {
}
void Runtime::drawCompass() {
- if (_subtitleQueue.size() > 0)
+ if (_subtitleQueue.size() > 0 || _loadedAnimationHasSound || !_isInGame)
return;
bool haveHorizontalRotate = false;
@@ -3384,6 +3516,10 @@ Common::SharedPtr<Graphics::Surface> Runtime::loadGraphic(const Common::String &
return nullptr;
}
+ // 1-byte BMPs are placeholders for no file
+ if (f.size() == 1)
+ return nullptr;
+
Image::BitmapDecoder bmpDecoder;
if (!bmpDecoder.loadStream(f)) {
warning("Failed to load BMP file '%s'", filePath.c_str());
@@ -3461,6 +3597,15 @@ void Runtime::loadSubtitles(Common::CodePage codePage) {
}
}
+void Runtime::changeToMenuPage(MenuPage *menuPage) {
+ _menuPage.reset(menuPage);
+
+ _gameState = kGameStateMenu;
+
+ menuPage->init(_menuInterface.get());
+ menuPage->start();
+}
+
void Runtime::onLButtonDown(int16 x, int16 y) {
onMouseMove(x, y);
@@ -3502,10 +3647,13 @@ bool Runtime::canSave() const {
}
bool Runtime::canLoad() const {
- return _gameState == kGameStateIdle;
+ return _gameState == kGameStateIdle || _gameState == kGameStateMenu;
}
void Runtime::recordSaveGameSnapshot() {
+ if (!_isInGame)
+ return;
+
_saveGame.reset();
uint32 timeBase = g_system->getMillis();
@@ -3677,11 +3825,13 @@ void Runtime::restoreSaveGameSnapshot() {
changeAnimation(animDef, false);
_gameState = kGameStateWaitingForAnimation;
+ _isInGame = true;
_havePendingScreenChange = true;
_forceScreenChange = true;
stopSubtitles();
+ clearScreen();
redrawTray();
}
@@ -4919,8 +5069,14 @@ void Runtime::scriptOpEscOff(ScriptArg_t arg) {
_escOn = false;
}
-OPCODE_STUB(EscGet)
-OPCODE_STUB(BackStart)
+void Runtime::scriptOpEscGet(ScriptArg_t arg) {
+ _scriptStack.push_back(StackValue(_scriptEnv.esc ? 1 : 0));
+ _scriptEnv.esc = false;
+}
+
+void Runtime::scriptOpBackStart(ScriptArg_t arg) {
+ _scriptEnv.exitToMenu = true;
+}
void Runtime::scriptOpAnimName(ScriptArg_t arg) {
if (_roomNumber >= _roomDefs.size())
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 1b9a6e2882c..8aed91311ee 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -24,6 +24,7 @@
#include "common/hashmap.h"
#include "common/keyboard.h"
+#include "common/rect.h"
#include "vcruise/detection.h"
@@ -67,6 +68,9 @@ static const uint kNumHighPrecisionDirections = 256;
static const uint kHighPrecisionDirectionMultiplier = kNumHighPrecisionDirections / kNumDirections;
class AudioPlayer;
+class MenuInterface;
+class MenuPage;
+class RuntimeMenuInterface;
class TextParser;
struct ScriptSet;
struct Script;
@@ -86,6 +90,8 @@ enum GameState {
kGameStatePanLeft,
kGameStatePanRight,
+
+ kGameStateMenu,
};
struct AnimationDef {
@@ -135,11 +141,13 @@ struct MapDef {
struct ScriptEnvironmentVars {
ScriptEnvironmentVars();
- bool lmb;
- bool lmbDrag;
uint panInteractionID;
uint fpsOverride;
uint lastHighlightedItem;
+ bool lmb;
+ bool lmbDrag;
+ bool esc;
+ bool exitToMenu;
};
struct SfxSound {
@@ -399,12 +407,33 @@ struct SaveGameSnapshot {
Common::HashMap<uint, uint32> timers;
};
+enum OSEventType {
+ kOSEventTypeInvalid,
+
+ kOSEventTypeMouseMove,
+ kOSEventTypeLButtonDown,
+ kOSEventTypeLButtonUp,
+
+ kOSEventTypeKeyDown,
+};
+
+struct OSEvent {
+ OSEvent();
+
+ OSEventType type;
+ Common::Point pos;
+ Common::KeyCode keyCode;
+ uint32 timestamp;
+};
+
class Runtime {
public:
+ friend class RuntimeMenuInterface;
+
Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID);
virtual ~Runtime();
- void initSections(Common::Rect gameRect, Common::Rect menuRect, Common::Rect trayRect, const Graphics::PixelFormat &pixFmt);
+ void initSections(const Common::Rect &gameRect, const Common::Rect &menuRect, const Common::Rect &trayRect, const Common::Rect &fullscreenMenuRect, const Graphics::PixelFormat &pixFmt);
void loadCursors(const char *exeName);
void setDebugMode(bool debugMode);
@@ -505,16 +534,6 @@ private:
bool isWaitingForAnimation;
};
- enum OSEventType {
- kOSEventTypeInvalid,
-
- kOSEventTypeMouseMove,
- kOSEventTypeLButtonDown,
- kOSEventTypeLButtonUp,
-
- kOSEventTypeKeyDown,
- };
-
enum PanoramaCursorFlags {
kPanCursorDraggableHoriz = (1 << 0),
kPanCursorDraggableUp = (1 << 1),
@@ -552,15 +571,6 @@ private:
static const uint kNumInventorySlots = 6;
- struct OSEvent {
- OSEvent();
-
- OSEventType type;
- Common::Point pos;
- Common::KeyCode keyCode;
- uint32 timestamp;
- };
-
typedef int32 ScriptArg_t;
typedef int32 StackInt_t;
@@ -688,6 +698,7 @@ private:
void inventoryAddItem(uint item);
void inventoryRemoveItem(uint item);
+ void clearScreen();
void redrawTray();
void clearTray();
void drawInventory(uint slot);
@@ -699,6 +710,8 @@ private:
void loadSubtitles(Common::CodePage codePage);
+ void changeToMenuPage(MenuPage *menuPage);
+
// Script things
void scriptOpNumber(ScriptArg_t arg);
void scriptOpRotate(ScriptArg_t arg);
@@ -830,6 +843,8 @@ private:
Common::SharedPtr<Graphics::Surface> _trayHighlightGraphic;
Common::SharedPtr<Graphics::Surface> _trayCornerGraphic;
+ Common::Array<Common::SharedPtr<Graphics::Surface> > _uiGraphics;
+
uint _panCursors[kPanCursorMaxCount];
Common::HashMap<Common::String, StackInt_t> _namedCursors;
@@ -872,10 +887,14 @@ private:
bool _havePendingCompletionCheck;
GameState _gameState;
+ Common::SharedPtr<MenuPage> _menuPage;
+ Common::SharedPtr<MenuInterface> _menuInterface;
+
bool _havePendingPlayAmbientSounds;
uint32 _ambientSoundFinishTime;
bool _escOn;
+ bool _escUsed;
bool _debugMode;
bool _fastAnimationMode;
@@ -915,6 +934,7 @@ private:
uint32 _animStartTime;
uint32 _animFramesDecoded;
uint _loadedAnimation;
+ bool _loadedAnimationHasSound;
bool _animPlayWhileIdle;
Common::Array<FrameData> _frameData;
@@ -937,6 +957,7 @@ private:
RenderSection _gameDebugBackBuffer;
RenderSection _menuSection;
RenderSection _traySection;
+ RenderSection _fullscreenMenuSection;
Common::Point _mousePos;
Common::Point _lmbDownPos;
@@ -983,6 +1004,7 @@ private:
uint _soundCacheIndex;
Common::SharedPtr<SaveGameSnapshot> _saveGame;
+ bool _isInGame;
const Graphics::Font *_subtitleFont;
Common::SharedPtr<Graphics::Font> _subtitleFontKeepalive;
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index 4964e1ad39d..cb51405fa3b 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -24,6 +24,7 @@
#include "common/config-manager.h"
#include "common/events.h"
#include "common/stream.h"
+#include "common/savefile.h"
#include "common/system.h"
#include "common/algorithm.h"
#include "common/translation.h"
@@ -159,7 +160,7 @@ Common::Error VCruiseEngine::run() {
_system->fillScreen(0);
_runtime.reset(new Runtime(_system, _mixer, _rootFSNode, _gameDescription->gameID));
- _runtime->initSections(_videoRect, _menuBarRect, _trayRect, _system->getScreenFormat());
+ _runtime->initSections(_videoRect, _menuBarRect, _trayRect, Common::Rect(640, 480), _system->getScreenFormat());
const char *exeName = _gameDescription->desc.filesDescriptions[0].fileName;
@@ -283,4 +284,12 @@ void VCruiseEngine::initializePath(const Common::FSNode &gamePath) {
_rootFSNode = gamePath;
}
+bool VCruiseEngine::hasDefaultSave() {
+ const Common::String &autoSaveName = getSaveStateName(getMetaEngine()->getAutosaveSlot());
+ bool autoSaveExists = getSaveFileManager()->exists(autoSaveName);
+
+ return autoSaveExists;
+}
+
+
} // End of namespace VCruise
diff --git a/engines/vcruise/vcruise.h b/engines/vcruise/vcruise.h
index 44ccb05b9ad..53b60919c01 100644
--- a/engines/vcruise/vcruise.h
+++ b/engines/vcruise/vcruise.h
@@ -62,6 +62,8 @@ public:
void initializePath(const Common::FSNode &gamePath) override;
+ bool hasDefaultSave();
+
protected:
void pauseEngineIntern(bool pause) override;
More information about the Scummvm-git-logs
mailing list