Difference between revisions of "Advanced Engine Features"
m (Fullpipe renamed to NGI.) |
|||
(129 intermediate revisions by 19 users not shown) | |||
Line 1: | Line 1: | ||
In the following, some of the more advanced features a ScummVM Engine may implement are described. We describe what advantage each feature gives to the engine author respectively to engine users, and sketch how to implement support for it. Features are roughly grouped and sorted by mutual dependency. | In the following, some of the more advanced features a ScummVM Engine may implement are described. We describe what advantage each feature gives to the engine author respectively to engine users, and sketch how to implement support for it.<br>Features are roughly grouped and sorted by mutual dependency. | ||
==Enhanced user interaction== | ==Enhanced user interaction== | ||
Line 18: | Line 18: | ||
'''Relevant Engine API''' | '''Relevant Engine API''' | ||
< | <syntaxhighlight lang="cpp"> | ||
virtual void pauseEngineIntern(bool pause); | virtual void pauseEngineIntern(bool pause); | ||
void pauseEngine(bool pause); | void pauseEngine(bool pause); | ||
bool isPaused() const; | bool isPaused() const; | ||
</ | </syntaxhighlight> | ||
''' | '''Implemented by:''' | ||
[[AGOS]], [[Gob]], [[Kyra]], [[Lure]], [[SCUMM]], [[Sword2]] | [[AGOS]], [[Draci]], [[Gob]], [[Kyra]], [[Lure]], [[Mohawk]], [[Mortevielle]], [[Parallaction]], [[Pegasus]], [[SAGA]], [[SCI]], [[SCUMM]], [[Supernova]], [[Sword2]], [[Toon]], [[ZVision]] | ||
'''Not implemented by:''' | |||
[[AGI]], [[Avalanche]], [[CGE]], [[Cine]], [[Composer]], [[CruisE]], [[Drascula]], [[Dreamweb]], [[Groovie]], [[Hopkins]], [[Hugo]], [[Lastexpress]], [[MADE]], [[Neverhood]], [[NGI]], [[Queen]], [[Sky]], [[Sword1]], [[Sword25]], [[Tinsel]], [[Toltecs]], [[Tony]], [[Touche]], [[TsAGE]], [[Tucker]], [[Wintermute]] | |||
'''Support not necessary:''' | |||
[[TeenAgent]] | |||
===RTL ("Return to Launcher") support=== | ===RTL ("Return to Launcher") support=== | ||
Line 33: | Line 38: | ||
'''How to implement it'''<br> | '''How to implement it'''<br> | ||
You can implement this by checking for and honoring the | You can implement this by checking for and honoring the EVENT_RETURN_TO_LAUNCHER event. A much easier way, which also gives you some other advantages (e.g. this also covers EVENT_QUIT), is to regularly poll the return value of Engine::shouldQuit(). If it returns true, you should break out from your main game loop and your Engine::run() method should return to the caller. | ||
'''Relevant Engine API''' | '''Relevant Engine API''' | ||
< | <syntaxhighlight lang="cpp"> | ||
void quitGame(); | |||
bool shouldQuit() const; | bool shouldQuit() const; | ||
kSupportsReturnToLauncher feature flag | |||
</syntaxhighlight> | |||
''' | '''Implemented by:''' | ||
[[AGI]], [[AGOS]], [[Cine]], [[Gob]], [[Kyra]], [[Lure]], [[Parallaction]], [[Queen]], [[SAGA]], [[SCUMM]], [[Sky]], [[Sword1]], [[Sword2]], [[Touche]] | [[AGI]], [[AGOS]], [[CGE]], [[Cine]], [[Composer]], [[CruisE]], [[Draci]], [[Drascula]], [[Dreamweb]], [[Gob]], [[Groovie]], [[Hopkins]], [[Hugo]], [[Kyra]], [[Lastexpress]], [[Lure]], [[MADE]], [[Mohawk]], [[Mortevielle]], [[Neverhood]], [[NGI]], [[Parallaction]], [[Pegasus]], [[Queen]], [[SAGA]], [[SCI]], [[SCUMM]], [[Sky]], [[Supernova]], [[Sword1]], [[Sword2]], [[Sword25]], [[TeenAgent]], [[Tinsel]], [[Toltecs]], [[Tony]], [[Toon]], [[Touche]], [[TsAGE]], [[Tucker]], [[Wintermute]], [[ZVision]] | ||
'''Not implemented by:''' [[Avalanche]] | |||
===Global options dialog support=== | ===Global options dialog support=== | ||
Line 53: | Line 61: | ||
'''Relevant Engine API''' | '''Relevant Engine API''' | ||
< | <syntaxhighlight lang="cpp"> | ||
virtual void syncSoundSettings(); | |||
</syntaxhighlight> | |||
'''Implemented by:''' | |||
[[AGOS]], [[Cine]], [[CruisE]], [[Draci]], [[Gob]], [[Groovie]], [[Hopkins]], [[Hugo]], [[Kyra]], [[Lastexpress]], [[Lure]], [[MADE]], [[Queen]], [[SAGA]], [[SCI]], [[SCUMM]], [[Sky]], [[Sword1]], [[Sword2]], [[Toltecs]], [[Tony]], [[Touche]], [[TsAGE]], [[ZVision]] | |||
''' | '''Not implemented by:''' | ||
[[ | [[Avalanche]], [[CGE]], [[Composer]], [[Drascula]], [[Dreamweb]], [[Mohawk]], [[Neverhood]], [[NGI]], [[Parallaction]], [[Pegasus]], [[Sword25]], [[Tucker]], [[Wintermute]] | ||
'''Support not necessary:''' [[AGI]], [[Mortevielle]], [[TeenAgent]], [[Tinsel]], [[Toon]] | |||
===GMM ("Global Main Menu") support=== | ===GMM ("Global Main Menu") support=== | ||
'''What is this about?'''<br> | '''What is this about?'''<br> | ||
This is a special dialog built using the ScummVM native GUI, which can be fired up in any Engine ScummVM supports, at virtually any time (to be precise, whenever the engine is polling for events). Right now, the trigger is | This is a special dialog built using the ScummVM native GUI, which can be fired up in any Engine ScummVM supports, at virtually any time (to be precise, whenever the engine is polling for events). Right now, the trigger is Ctrl F5 globally. | ||
The idea is to give the user a uniform way to access certain functionality everywhere: In particular, access to a small global options dialog; the ability to quit and/or return to the launcher; the about dialog and version information; and to load/save the gamestate. | The idea is to give the user a uniform way to access certain functionality everywhere: In particular, access to a small global options dialog; the ability to quit and/or return to the launcher; the about dialog and version information; and to load/save the gamestate. | ||
Line 72: | Line 86: | ||
None. | None. | ||
''' | '''Implemented by:''' | ||
''not applicable'' | ''not applicable'' | ||
'''Not implemented by:''' | |||
''not applicable'' | |||
==Enhanced load/save support== | ==Enhanced load/save support== | ||
In this section, we present various MetaEngine and Engine APIs which greatly experience | In this section, we present various MetaEngine and Engine APIs which greatly experience | ||
the user experience with regards to savestates. | the user experience with regards to savestates. These days, the bulk of logic for listing, loading, saving, or deleting savegames is provided by the Engine class. As engine writers you only need to override loadGameStream & saveGameStream to read and write data from savefiles. | ||
===Listing savestates via command line or Launcher=== | ===Listing savestates via command line or Launcher=== | ||
'''What is this about?'''<br> | '''What is this about?'''<br> | ||
With this feature, it is possible to build a list of available save slots for a given game target. This can be used by the user to list all saveslots from the command line, as the following example illustrates: | With this feature, it is possible to build a list of available save slots for a given game target. This can be used by the user to list all saveslots from the command line, as the following example illustrates: | ||
< | <syntaxhighlight lang="bash"> | ||
$ ./scummvm --list-saves --game=monkey2 | |||
Saves for target 'monkey2': | Saves for target 'monkey2': | ||
Slot Description | Slot Description | ||
Line 91: | Line 109: | ||
1 Start | 1 Start | ||
2 Quicksave 2 | 2 Quicksave 2 | ||
$ | |||
</syntaxhighlight> | |||
Furthermore, this is used by the load/save dialogs in the Launcher and the GMM to build the list of savestates they show visually to the user. | Furthermore, this is used by the load/save dialogs in the Launcher and the GMM to build the list of savestates they show visually to the user. | ||
'''How to implement it'''<br> | '''How to implement it'''<br> | ||
You | You probably don't. Newly created engines, which use loadGameStream & saveGameStream, automatically use the extended savegame format. With this, the Engine class already provides a default implementation of listSaves for you. However, if for some reason you want to override it, such as if you're providing your own custom savegame implementation from scratch, the method is passed a parameter indicating the target for which the list of savestates is requested. From this, you can (using the config manager) determine the path to the game data, if necessary, or just directly compute all available savestates. Details necessarily depend on your Engine, but looking at existing implementations should give you a fairly good idea how to tackle this. | ||
Another requirement is MetaEngine::getMaximumSaveSlot, which returns the maximum save slot number supported by your engine. This is for example used by the GUI to show up empty slots correctly. | |||
It returns a list of SaveStateDescriptor objects, describing each available savestate. As a minimum, it has to contain a human readable description, and a unique save slot number of the savestate (how this is defined is up to your engine -- you need to decide on one numbering scheme you use in all save/load related (Meta)Engine features, though). | It returns a list of SaveStateDescriptor objects, describing each available savestate. As a minimum, it has to contain a human readable description, and a unique save slot number of the savestate (how this is defined is up to your engine -- you need to decide on one numbering scheme you use in all save/load related (Meta)Engine features, though). | ||
Line 103: | Line 123: | ||
'''Relevant MetaEngine API''' | '''Relevant MetaEngine API''' | ||
< | <syntaxhighlight lang="cpp"> | ||
virtual SaveStateList listSaves(const char *target) const; | |||
virtual int getMaximumSaveSlot() const; | |||
kSupportsListSaves feature flag | |||
</syntaxhighlight> | |||
'''Implemented by:''' | |||
[[AGI]], [[AGOS]], [[Avalanche]], [[CGE]], [[Cine]], [[CruisE]], [[Draci]], [[Drascula]], [[Dreamweb]], [[Groovie]], [[Hopkins]], [[Hugo]], [[Kyra]], [[Lure]], [[Mohawk]], [[Mortevielle]], [[Neverhood]], [[Parallaction]], [[Pegasus]], [[Queen]], [[SAGA]], [[SCI]], [[SCUMM]], [[Sky]], [[Supernova]], [[Sword1]], [[Sword2]], [[Sword25]], [[Tinsel]], [[Toltecs]], [[Tony]], [[Toon]], [[Touche]], [[Tucker]], [[Wintermute]], [[ZVision]] | |||
''' | '''Not implemented by:''' | ||
[[ | [[Composer]], [[Gob]], [[Lastexpress]], [[MADE]], [[NGI]], [[TeenAgent]], [[TsAGE]] | ||
===Loading savestates via command line or Launcher=== | ===Loading savestates via command line or Launcher=== | ||
'''What is this about?'''<br> | '''What is this about?'''<br> | ||
With this feature, the user can load specific savestates directly from the command line, via the "-x SLOT" option . It is also the foundation for the "Load" button in the Launcher. | |||
'''How to implement it'''<br> | '''How to implement it'''<br> | ||
They way this works is quite simple: Each savestate has a unique slot number (as explained in the previous section on listing savestates). This number is passed to the engine during instantiation time in the "save_slot" ConfigMan setting. So, all you have to do is to check during startup of your engine whether that config variable is present, and if it is, use its value to decide which savestate to load. In addition, you should specify the kSupportsLoadingDuringStartup MetaEngine feature flag. | |||
'''Relevant MetaEngine API''' | '''Relevant MetaEngine API''' | ||
< | <syntaxhighlight lang="cpp"> | ||
save_slot ConfigMan setting | |||
kSupportsLoadingDuringStartup feature flag</ | kSupportsLoadingDuringStartup feature flag | ||
</syntaxhighlight> | |||
''' | '''Implemented by:''' | ||
[[AGI]], [[Cine]], [[Kyra]], [[Lure]], [[ | [[AGI]], [[Avalanche]], [[Cine]], [[CGE]], [[CruisE]], [[Draci]], [[Drascula]], [[Dreamweb]], [[Groovie]], [[Hopkins]], [[Hugo]], [[Kyra]], [[Lure]], [[Mohawk]], [[Mortevielle]], [[Neverhood]], [[Pegasus]], [[Queen]], [[SAGA]], [[SCI]], [[SCUMM]], [[Sky]], [[Supernova]], [[Sword1]], [[Sword2]], [[TeenAgent]], [[Tinsel]], [[Toltecs]], [[Tony]], [[Toon]], [[Touche]], [[TsAGE]], [[Tucker]], [[Wintermute]], [[ZVision]] | ||
'''Not implemented by:''' | |||
[[AGOS]], [[Composer]], [[Gob]], [[Lastexpress]], [[MADE]], [[NGI]], [[Parallaction]], [[Sword25]] | |||
===Deleting savestates via the Launcher and GMM=== | ===Deleting savestates via the Launcher and GMM=== | ||
Line 137: | Line 167: | ||
'''Relevant MetaEngine API''' | '''Relevant MetaEngine API''' | ||
< | <syntaxhighlight lang="cpp"> | ||
void removeSaveState(const char *target, int slot) const; | |||
kSupportsDeleteSave feature flag</ | kSupportsDeleteSave feature flag | ||
</syntaxhighlight> | |||
''' | '''Implemented by:''' | ||
[[AGI]], [[Kyra]], [[Lure]], [[Parallaction]], [[Queen]], [[SAGA]], [[SCUMM]], [[Sword2]], [[Touche]] | [[AGI]], [[Avalanche]], [[Cine]], [[CGE]], [[CruisE]], [[Draci]], [[Drascula]], [[Dreamweb]], [[Groovie]], [[Hopkins]], [[Hugo]], [[Kyra]], [[Lure]], [[Mohawk]], [[Neverhood]], [[Parallaction]], [[Pegasus]], [[Queen]], [[SAGA]], [[SCI]], [[SCUMM]], [[Sky]], [[Supernova]], [[Sword1]], [[Sword2]], [[TeenAgent]], [[Tinsel]], [[Toltecs]], [[Tony]], [[Toon]], [[Touche]], [[TsAGE]], [[Tucker]], [[Wintermute]], [[ZVision]] | ||
'''Not implemented by:''' | |||
[[AGOS]], [[Composer]], [[Gob]], [[Lastexpress]], [[MADE]], [[Mortevielle]], [[NGI]], [[Sword25]] | |||
===Savestate metadata support=== | ===Savestate metadata support=== | ||
Line 155: | Line 189: | ||
'''Relevant MetaEngine API''' | '''Relevant MetaEngine API''' | ||
< | <syntaxhighlight lang="cpp"> | ||
virtual SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; | |||
kSavesSupportMetaInfo feature flag | kSavesSupportMetaInfo feature flag | ||
kSavesSupportThumbnail feature flag | kSavesSupportThumbnail feature flag | ||
kSavesSupportCreationDate feature flag | kSavesSupportCreationDate feature flag | ||
kSavesSupportPlayTime feature flag</ | kSavesSupportPlayTime feature flag | ||
</syntaxhighlight> | |||
''' | '''Implemented by:''' | ||
[[Kyra]], [[SCUMM]] | [[AGI]], [[Avalanche]], [[CGE]], [[CruisE]], [[Draci]], [[Drascula]], [[Dreamweb]], [[Groovie]], [[Hopkins]], [[Hugo]], [[Kyra]], [[Mortevielle]], [[Neverhood]], [[SAGA]], [[SCI]], [[SCUMM]], [[Supernova]], [[Sword1]], [[TeenAgent]], [[Toltecs]], [[Tony]], [[Toon]], [[TsAGE]], [[Tucker]], [[Wintermute]], [[ZVision]] | ||
'''Not implemented by:''' | |||
[[AGOS]], [[Cine]], [[Composer]], [[Gob]], [[Lastexpress]], [[Lure]], [[Mohawk]], [[MADE]], [[NGI]], [[Parallaction]], [[Pegasus]], [[Queen]], [[Sky]], [[Sword2]], [[Sword25]], [[Touche]], [[Tinsel]] | |||
===Loading/Saving during run time=== | ===Loading/Saving during run time=== | ||
'''What is this about?'''<br> | '''What is this about?'''<br> | ||
The GMM optionally can display two buttons "Load" and "Save" which permit the user to load/save gamestates at any time the Engine chooses to allow so. This is in addition to whatever other GUI the engine offers for loading/saving. The main purpose is to provide a uniform way to the user to load/save in all games. | |||
In the future, this could also be used to implement generic auto-saving support; or generic quick-save / quick-load support. | |||
'''How to implement it'''<br> | '''How to implement it'''<br> | ||
Unlike the other features presented above, this is an ability of your Engine subclass, not of MetaEngine. First thing, you have to advertise the abilities of your engines with the appropriate engine feature flags. You can implement only loading, or only saving, or both. | |||
Next, for each of the two, you have to implement two Engine methods (so up to four). We focus on adding loading support here: For that, first implement Engine::canLoadGameStateCurrently(). This method should only return true if loading a savestate is possible right now. If this is always possible, just always return true. But if loading is not possible at some points, say while a cutscene is playing, make it return false at these times. | |||
Next, the Engine::loadGameStream() is used to load save file data. Previously, this was loadGameState, but engines using it had to manually open the save files, so it's now deprecated. The GMM will invoke this method with a valid stream from an opened save file, allowing the engine to read from it. If there is an immediate error, you can indicate so with its return value (for details, refer to the doxygen comments), in which case the GMM will show an appropriate error dialog. In | |||
'''Relevant Engine API''' | '''Relevant Engine API''' | ||
< | <syntaxhighlight lang="cpp"> | ||
virtual Common::Error Engine::loadGameStream(Common::SeekableReadStream *stream); | |||
virtual bool canLoadGameStateCurrently(); | virtual bool canLoadGameStateCurrently(); | ||
kSupportsLoadingDuringRuntime feature flag | kSupportsLoadingDuringRuntime feature flag | ||
</syntaxhighlight> | |||
Saving games is similar. Engines can override saveGameStream (rather than the now deprecated saveGameState). In this case, it gets passed in a write stream to a save file opened for saving. Additionally, it also gets passed a flag for whether an autosave is being created vs a save done by the user. Some engines may want to show a message or other indication in the UI after a save is done, but which shouldn't be done for regular autosaves. | |||
<syntaxhighlight lang="cpp"> | |||
virtual Common::Error Engine::saveGameStream(Common::WriteStream *stream, bool isAutosave); | |||
virtual bool canSaveGameStateCurrently(); | |||
kSupportsSavingDuringRuntime feature flag | |||
</syntaxhighlight> | |||
==Misc== | ==Misc== | ||
Line 195: | Line 242: | ||
By implementing the Engine::errorString() method, your engine can add extra info whenever ScummVM is about to print an error message triggered by the error() function. This could be used to print out additional data describing the context, such as the ID of the active script when the error occurred, values of special flags, etc. -- anything that might help debug an error. | By implementing the Engine::errorString() method, your engine can add extra info whenever ScummVM is about to print an error message triggered by the error() function. This could be used to print out additional data describing the context, such as the ID of the active script when the error occurred, values of special flags, etc. -- anything that might help debug an error. | ||
If your engine implements a debug console (which is very easy to do using just subclass GUI::Debugger, and then implement your custom debugger commands and variables), overload Engine::getDebugger() to return a | If your engine implements a debug console (which is very easy to do using just subclass GUI::Debugger, and then implement your custom debugger commands and variables), overload Engine::getDebugger() to return a pointer to it. If you do so, when error() is called, it will open that debug console instead of immediately exiting. This can be useful to perform some additional post-mortem analysis. | ||
'''Relevant Engine API''' | '''Relevant Engine API''' | ||
< | <syntaxhighlight lang="cpp"> | ||
virtual void errorString(const char *buf_input, char *buf_output, int buf_output_size); | |||
</syntaxhighlight> | |||
:'''Implemented by:''' [[SCUMM]] | |||
:'''Not implemented by:''' [[AGI]], [[AGOS]], [[Avalanche]], [[Cine]], [[CGE]], [[Composer]], [[CruisE]], [[Draci]], [[Drascula]], [[Dreamweb]], [[Gob]], [[Groovie]], [[Hopkins]], [[Hugo]], [[Kyra]], [[Lastexpress]], [[Lure]], [[MADE]], [[Mohawk]], [[Mortevielle]], [[Neverhood]], [[NGI]], [[Parallaction]], [[Pegasus]], [[Queen]], [[SAGA]], [[SCI]], [[Sky]], [[Sword1]], [[Sword2]], [[Sword25]], [[TeenAgent]], [[Tinsel]], [[Toltecs]], [[Tony]], [[Toon]], [[Touche]], [[TsAGE]], [[Tucker]], [[Wintermute]], [[ZVision]] | |||
<syntaxhighlight lang="cpp"> | |||
virtual GUI::Debugger *getDebugger(); | |||
</syntaxhighlight> | |||
:'''Implemented by:''' [[AGI]], [[AGOS]], [[Avalanche]], [[Cine]], [[CGE]], [[Composer]], [[CruisE]], [[Draci]], [[Drascula]], [[Dreamweb]], [[Gob]], [[Groovie]], [[Hopkins]], [[Hugo]], [[Kyra]], [[Lastexpress]], [[Lure]], [[MADE]], [[Mohawk]], [[Mortevielle]], [[Neverhood]], [[NGI]], [[Parallaction]], [[Pegasus]], [[Queen]], [[SAGA]], [[SCI]], [[SCUMM]], [[Sky]], [[Sword1]], [[Sword2]], [[Sword25]], [[TeenAgent]], [[Tinsel]], [[Toltecs]], [[Toon]], [[Tony]], [[Touche]], [[TsAGE]], [[Tucker]], [[Wintermute]], [[ZVision]] | |||
''' | :'''Not implemented by:''' | ||
Latest revision as of 06:04, 22 March 2021
In the following, some of the more advanced features a ScummVM Engine may implement are described. We describe what advantage each feature gives to the engine author respectively to engine users, and sketch how to implement support for it.
Features are roughly grouped and sorted by mutual dependency.
Enhanced user interaction
Improved pause support
What is this about?
Sometimes ScummVM needs to pause a currently running engine, for example, to show the GMM ("Global Main Menu"), or maybe on a cellphone to switch it to the background during an incoming call.
A simple way for that is not return from OSystem::pollEvent() until the condition ends. But that is problematic because your internal timers, as well as audio, probably keep running (a port can work around this to an extent, but not always perfectly).
Therefore, we added an Engine API that allows pausing and resuming the currently running engine.
How to implement it
You have to overload the Engine::pauseEngineIntern() method. The default method does exactly one thing: It invokes Mixer::pauseAll() to pause/resume the digital audio mixer.
But if your engine is e.g. using MIDI, then you might want to pause/resume that as well. You also might wish to record the time the pause started, to be able to adjust any internal timers once the pause ends.
That's all you need to implement, but if your engine needs to keep track of a pause state anyway, you can also use the public API built atop this internal function. It supports recursive pausing (if the engine is paused 5 times, it has to be unpaused 5 times before actually resuming) for free.
Relevant Engine API
virtual void pauseEngineIntern(bool pause);
void pauseEngine(bool pause);
bool isPaused() const;
Implemented by: AGOS, Draci, Gob, Kyra, Lure, Mohawk, Mortevielle, Parallaction, Pegasus, SAGA, SCI, SCUMM, Supernova, Sword2, Toon, ZVision
Not implemented by: AGI, Avalanche, CGE, Cine, Composer, CruisE, Drascula, Dreamweb, Groovie, Hopkins, Hugo, Lastexpress, MADE, Neverhood, NGI, Queen, Sky, Sword1, Sword25, Tinsel, Toltecs, Tony, Touche, TsAGE, Tucker, Wintermute
Support not necessary: TeenAgent
RTL ("Return to Launcher") support
What is this about?
This feature allows the user to return from the current game back to the Launcher, instead of having to first quit ScummVM and then restart it (which on some ScummVM ports amounts to having to reboot).
How to implement it
You can implement this by checking for and honoring the EVENT_RETURN_TO_LAUNCHER event. A much easier way, which also gives you some other advantages (e.g. this also covers EVENT_QUIT), is to regularly poll the return value of Engine::shouldQuit(). If it returns true, you should break out from your main game loop and your Engine::run() method should return to the caller.
Relevant Engine API
void quitGame();
bool shouldQuit() const;
kSupportsReturnToLauncher feature flag
Implemented by: AGI, AGOS, CGE, Cine, Composer, CruisE, Draci, Drascula, Dreamweb, Gob, Groovie, Hopkins, Hugo, Kyra, Lastexpress, Lure, MADE, Mohawk, Mortevielle, Neverhood, NGI, Parallaction, Pegasus, Queen, SAGA, SCI, SCUMM, Sky, Supernova, Sword1, Sword2, Sword25, TeenAgent, Tinsel, Toltecs, Tony, Toon, Touche, TsAGE, Tucker, Wintermute, ZVision
Not implemented by: Avalanche
Global options dialog support
What is this about?
The global options dialog is reachable from the GMM and allows the user to modify various settings; settings which might not otherwise be editable while a game is running (depending on the engine). Right now, it only shows sound and subtitle settings, but more settings will be added in the future.
How to implement it
This comes for free with the GMM (see below). But depending on how your engine works, you may have to resync your internal state with the sound and subtitle settings in the config manager. For this, implement the syncSoundSettings() method.
Relevant Engine API
virtual void syncSoundSettings();
Implemented by: AGOS, Cine, CruisE, Draci, Gob, Groovie, Hopkins, Hugo, Kyra, Lastexpress, Lure, MADE, Queen, SAGA, SCI, SCUMM, Sky, Sword1, Sword2, Toltecs, Tony, Touche, TsAGE, ZVision
Not implemented by: Avalanche, CGE, Composer, Drascula, Dreamweb, Mohawk, Neverhood, NGI, Parallaction, Pegasus, Sword25, Tucker, Wintermute
Support not necessary: AGI, Mortevielle, TeenAgent, Tinsel, Toon
GMM ("Global Main Menu") support
What is this about?
This is a special dialog built using the ScummVM native GUI, which can be fired up in any Engine ScummVM supports, at virtually any time (to be precise, whenever the engine is polling for events). Right now, the trigger is Ctrl F5 globally.
The idea is to give the user a uniform way to access certain functionality everywhere: In particular, access to a small global options dialog; the ability to quit and/or return to the launcher; the about dialog and version information; and to load/save the gamestate.
How to implement it
You do not really have to do anything for the GMM to show up in your engine. But for an optimal user experience, you should implement several other features, including pause support, RTL support, Global options dialog support, and support for runtime loading/saving.
Relevant Engine API
None.
Implemented by: not applicable
Not implemented by: not applicable
Enhanced load/save support
In this section, we present various MetaEngine and Engine APIs which greatly experience the user experience with regards to savestates. These days, the bulk of logic for listing, loading, saving, or deleting savegames is provided by the Engine class. As engine writers you only need to override loadGameStream & saveGameStream to read and write data from savefiles.
Listing savestates via command line or Launcher
What is this about?
With this feature, it is possible to build a list of available save slots for a given game target. This can be used by the user to list all saveslots from the command line, as the following example illustrates:
$ ./scummvm --list-saves --game=monkey2
Saves for target 'monkey2':
Slot Description
---- ------------------------------------------------------
0 Autosave 0
1 Start
2 Quicksave 2
$
Furthermore, this is used by the load/save dialogs in the Launcher and the GMM to build the list of savestates they show visually to the user.
How to implement it
You probably don't. Newly created engines, which use loadGameStream & saveGameStream, automatically use the extended savegame format. With this, the Engine class already provides a default implementation of listSaves for you. However, if for some reason you want to override it, such as if you're providing your own custom savegame implementation from scratch, the method is passed a parameter indicating the target for which the list of savestates is requested. From this, you can (using the config manager) determine the path to the game data, if necessary, or just directly compute all available savestates. Details necessarily depend on your Engine, but looking at existing implementations should give you a fairly good idea how to tackle this.
Another requirement is MetaEngine::getMaximumSaveSlot, which returns the maximum save slot number supported by your engine. This is for example used by the GUI to show up empty slots correctly.
It returns a list of SaveStateDescriptor objects, describing each available savestate. As a minimum, it has to contain a human readable description, and a unique save slot number of the savestate (how this is defined is up to your engine -- you need to decide on one numbering scheme you use in all save/load related (Meta)Engine features, though).
Oh, and of course, your MetaEngine::hasFeature() method has to return true for kSupportsListSaves.
Relevant MetaEngine API
virtual SaveStateList listSaves(const char *target) const;
virtual int getMaximumSaveSlot() const;
kSupportsListSaves feature flag
Implemented by: AGI, AGOS, Avalanche, CGE, Cine, CruisE, Draci, Drascula, Dreamweb, Groovie, Hopkins, Hugo, Kyra, Lure, Mohawk, Mortevielle, Neverhood, Parallaction, Pegasus, Queen, SAGA, SCI, SCUMM, Sky, Supernova, Sword1, Sword2, Sword25, Tinsel, Toltecs, Tony, Toon, Touche, Tucker, Wintermute, ZVision
Not implemented by: Composer, Gob, Lastexpress, MADE, NGI, TeenAgent, TsAGE
Loading savestates via command line or Launcher
What is this about?
With this feature, the user can load specific savestates directly from the command line, via the "-x SLOT" option . It is also the foundation for the "Load" button in the Launcher.
How to implement it
They way this works is quite simple: Each savestate has a unique slot number (as explained in the previous section on listing savestates). This number is passed to the engine during instantiation time in the "save_slot" ConfigMan setting. So, all you have to do is to check during startup of your engine whether that config variable is present, and if it is, use its value to decide which savestate to load. In addition, you should specify the kSupportsLoadingDuringStartup MetaEngine feature flag.
Relevant MetaEngine API
save_slot ConfigMan setting
kSupportsLoadingDuringStartup feature flag
Implemented by: AGI, Avalanche, Cine, CGE, CruisE, Draci, Drascula, Dreamweb, Groovie, Hopkins, Hugo, Kyra, Lure, Mohawk, Mortevielle, Neverhood, Pegasus, Queen, SAGA, SCI, SCUMM, Sky, Supernova, Sword1, Sword2, TeenAgent, Tinsel, Toltecs, Tony, Toon, Touche, TsAGE, Tucker, Wintermute, ZVision
Not implemented by: AGOS, Composer, Gob, Lastexpress, MADE, NGI, Parallaction, Sword25
Deleting savestates via the Launcher and GMM
What is this about?
This feature allows the user to delete existing savestates from the Launcher's load dialog, and also from the GMM's load dialog.
How to implement it
First off, your engine must overload Engine::hasFeature() to return true when kSupportsDeleteSave is passed in.
Furthermore, it has to implement MetaEngine::removeSaveState(): This should delete the savestate for the specified via SaveFileManager::removeSavefile(). In addition, if your engine keeps an index of all existing saves (something which we strongly discourage, as it makes it more difficult for the user to transfer savestates from one device to another), then you have to remove references to savestate from that index.
Because of the way it is exposed, the user can only use this feature if you also implement listing savestates.
Relevant MetaEngine API
void removeSaveState(const char *target, int slot) const;
kSupportsDeleteSave feature flag
Implemented by: AGI, Avalanche, Cine, CGE, CruisE, Draci, Drascula, Dreamweb, Groovie, Hopkins, Hugo, Kyra, Lure, Mohawk, Neverhood, Parallaction, Pegasus, Queen, SAGA, SCI, SCUMM, Sky, Supernova, Sword1, Sword2, TeenAgent, Tinsel, Toltecs, Tony, Toon, Touche, TsAGE, Tucker, Wintermute, ZVision
Not implemented by: AGOS, Composer, Gob, Lastexpress, MADE, Mortevielle, NGI, Sword25
Savestate metadata support
What is this about?
The load dialog provided by the Launcher and the GMM supports displaying a thumbnail of an in-game screenshot of a savestate to the user, as well as some other metadata (currently, besides the thumbnail it can display the date the savestate was created, and how long the user already was playing). For this, it needs to query the MetaEngine for this metadata.
How to implement it
Implemented MetaEngine::querySaveMetaInfos() to return a SaveStateDescriptor similar to the one returned by listSaves(). The main differences are that only a single descriptor is returned, and that this descriptor can be extended by additional attributes. Class SaveStateDescriptor provides several methods for this purpose.
The user can only use this feature if you also implement listing savestates.
Relevant MetaEngine API
virtual SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const;
kSavesSupportMetaInfo feature flag
kSavesSupportThumbnail feature flag
kSavesSupportCreationDate feature flag
kSavesSupportPlayTime feature flag
Implemented by: AGI, Avalanche, CGE, CruisE, Draci, Drascula, Dreamweb, Groovie, Hopkins, Hugo, Kyra, Mortevielle, Neverhood, SAGA, SCI, SCUMM, Supernova, Sword1, TeenAgent, Toltecs, Tony, Toon, TsAGE, Tucker, Wintermute, ZVision
Not implemented by: AGOS, Cine, Composer, Gob, Lastexpress, Lure, Mohawk, MADE, NGI, Parallaction, Pegasus, Queen, Sky, Sword2, Sword25, Touche, Tinsel
Loading/Saving during run time
What is this about?
The GMM optionally can display two buttons "Load" and "Save" which permit the user to load/save gamestates at any time the Engine chooses to allow so. This is in addition to whatever other GUI the engine offers for loading/saving. The main purpose is to provide a uniform way to the user to load/save in all games.
In the future, this could also be used to implement generic auto-saving support; or generic quick-save / quick-load support.
How to implement it
Unlike the other features presented above, this is an ability of your Engine subclass, not of MetaEngine. First thing, you have to advertise the abilities of your engines with the appropriate engine feature flags. You can implement only loading, or only saving, or both.
Next, for each of the two, you have to implement two Engine methods (so up to four). We focus on adding loading support here: For that, first implement Engine::canLoadGameStateCurrently(). This method should only return true if loading a savestate is possible right now. If this is always possible, just always return true. But if loading is not possible at some points, say while a cutscene is playing, make it return false at these times. Next, the Engine::loadGameStream() is used to load save file data. Previously, this was loadGameState, but engines using it had to manually open the save files, so it's now deprecated. The GMM will invoke this method with a valid stream from an opened save file, allowing the engine to read from it. If there is an immediate error, you can indicate so with its return value (for details, refer to the doxygen comments), in which case the GMM will show an appropriate error dialog. In
Relevant Engine API
virtual Common::Error Engine::loadGameStream(Common::SeekableReadStream *stream);
virtual bool canLoadGameStateCurrently();
kSupportsLoadingDuringRuntime feature flag
Saving games is similar. Engines can override saveGameStream (rather than the now deprecated saveGameState). In this case, it gets passed in a write stream to a save file opened for saving. Additionally, it also gets passed a flag for whether an autosave is being created vs a save done by the user. Some engines may want to show a message or other indication in the UI after a save is done, but which shouldn't be done for regular autosaves.
virtual Common::Error Engine::saveGameStream(Common::WriteStream *stream, bool isAutosave);
virtual bool canSaveGameStateCurrently();
kSupportsSavingDuringRuntime feature flag
Misc
Enhanced debug/error messages
What is this about?
Providing more detailed error and debug messages to the engine developer, and to people bug testing an engine, respectively wanting to report a bug.
How to implement it
By implementing the Engine::errorString() method, your engine can add extra info whenever ScummVM is about to print an error message triggered by the error() function. This could be used to print out additional data describing the context, such as the ID of the active script when the error occurred, values of special flags, etc. -- anything that might help debug an error.
If your engine implements a debug console (which is very easy to do using just subclass GUI::Debugger, and then implement your custom debugger commands and variables), overload Engine::getDebugger() to return a pointer to it. If you do so, when error() is called, it will open that debug console instead of immediately exiting. This can be useful to perform some additional post-mortem analysis.
Relevant Engine API
virtual void errorString(const char *buf_input, char *buf_output, int buf_output_size);
- Implemented by: SCUMM
- Not implemented by: AGI, AGOS, Avalanche, Cine, CGE, Composer, CruisE, Draci, Drascula, Dreamweb, Gob, Groovie, Hopkins, Hugo, Kyra, Lastexpress, Lure, MADE, Mohawk, Mortevielle, Neverhood, NGI, Parallaction, Pegasus, Queen, SAGA, SCI, Sky, Sword1, Sword2, Sword25, TeenAgent, Tinsel, Toltecs, Tony, Toon, Touche, TsAGE, Tucker, Wintermute, ZVision
virtual GUI::Debugger *getDebugger();
- Implemented by: AGI, AGOS, Avalanche, Cine, CGE, Composer, CruisE, Draci, Drascula, Dreamweb, Gob, Groovie, Hopkins, Hugo, Kyra, Lastexpress, Lure, MADE, Mohawk, Mortevielle, Neverhood, NGI, Parallaction, Pegasus, Queen, SAGA, SCI, SCUMM, Sky, Sword1, Sword2, Sword25, TeenAgent, Tinsel, Toltecs, Toon, Tony, Touche, TsAGE, Tucker, Wintermute, ZVision
- Not implemented by: