1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
|
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "audio/mididrv.h"
#include "audio/mixer.h"
#include "groovie/groovie.h"
#include "groovie/cursor.h"
#include "groovie/detection.h"
#include "groovie/graphics.h"
#include "groovie/music.h"
#include "groovie/resource.h"
#include "groovie/roq.h"
#include "groovie/stuffit.h"
#include "groovie/vdx.h"
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/events.h"
#include "common/file.h"
#include "common/macresman.h"
#include "common/textconsole.h"
#include "backends/audiocd/audiocd.h"
#include "engines/util.h"
#include "graphics/fontman.h"
#include "audio/mixer.h"
namespace Groovie {
GroovieEngine::GroovieEngine(OSystem *syst, const GroovieGameDescription *gd) :
Engine(syst), _gameDescription(gd), _debugger(NULL), _script(NULL),
_resMan(NULL), _grvCursorMan(NULL), _videoPlayer(NULL), _musicPlayer(NULL),
_graphicsMan(NULL), _macResFork(NULL), _waitingForInput(false), _font(NULL),
_spookyMode(false) {
// Adding the default directories
const Common::FSNode gameDataDir(ConfMan.get("path"));
SearchMan.addSubDirectoryMatching(gameDataDir, "groovie");
SearchMan.addSubDirectoryMatching(gameDataDir, "media");
SearchMan.addSubDirectoryMatching(gameDataDir, "system");
SearchMan.addSubDirectoryMatching(gameDataDir, "MIDI");
_modeSpeed = kGroovieSpeedNormal;
if (ConfMan.hasKey("fast_movie_speed") && ConfMan.getBool("fast_movie_speed"))
_modeSpeed = kGroovieSpeedFast;
// Initialize the custom debug levels
DebugMan.addDebugChannel(kGroovieDebugAll, "All", "Debug everything");
DebugMan.addDebugChannel(kGroovieDebugVideo, "Video", "Debug video and audio playback");
DebugMan.addDebugChannel(kGroovieDebugResource, "Resource", "Debug resouce management");
DebugMan.addDebugChannel(kGroovieDebugScript, "Script", "Debug the scripts");
DebugMan.addDebugChannel(kGroovieDebugUnknown, "Unknown", "Report values of unknown data in files");
DebugMan.addDebugChannel(kGroovieDebugHotspots, "Hotspots", "Show the hotspots");
DebugMan.addDebugChannel(kGroovieDebugCursor, "Cursor", "Debug cursor decompression / switching");
DebugMan.addDebugChannel(kGroovieDebugMIDI, "MIDI", "Debug MIDI / XMIDI files");
DebugMan.addDebugChannel(kGroovieDebugScriptvars, "Scriptvars", "Print out any change to script variables");
DebugMan.addDebugChannel(kGroovieDebugCell, "Cell", "Debug the cell game (in the microscope)");
DebugMan.addDebugChannel(kGroovieDebugFast, "Fast", "Play videos quickly, with no sound (unstable)");
}
GroovieEngine::~GroovieEngine() {
// Delete the remaining objects
delete _debugger;
delete _resMan;
delete _grvCursorMan;
delete _videoPlayer;
delete _musicPlayer;
delete _graphicsMan;
delete _script;
delete _macResFork;
}
Common::Error GroovieEngine::run() {
if (_gameDescription->version == kGroovieV2 && getPlatform() == Common::kPlatformMacintosh) {
// Load the Mac installer with the lowest priority (in case the user has installed
// the game and has the MIDI folder present; faster to just load them)
Common::Archive *archive = createStuffItArchive("The 11th Hour Installer");
if (archive)
SearchMan.add("The 11th Hour Installer", archive);
}
_script = new Script(this, _gameDescription->version);
// Initialize the graphics
switch (_gameDescription->version) {
case kGroovieV2: {
// Request the mode with the highest precision available
Graphics::PixelFormat format(4, 8, 8, 8, 8, 24, 16, 8, 0);
initGraphics(640, 480, true, &format);
if (_system->getScreenFormat() != format)
return Common::kUnsupportedColorMode;
// Save the enabled mode
_pixelFormat = format;
break;
}
case kGroovieT7G:
initGraphics(640, 480, true);
_pixelFormat = Graphics::PixelFormat::createFormatCLUT8();
break;
}
// Create debugger. It requires GFX to be initialized
_debugger = new Debugger(this);
_script->setDebugger(_debugger);
// Create the graphics manager
_graphicsMan = new GraphicsMan(this);
// Create the resource and cursor managers and the video player
// Prepare the font too
switch (_gameDescription->version) {
case kGroovieT7G:
if (getPlatform() == Common::kPlatformMacintosh) {
_macResFork = new Common::MacResManager();
if (!_macResFork->open(_gameDescription->desc.filesDescriptions[0].fileName))
error("Could not open %s as a resource fork", _gameDescription->desc.filesDescriptions[0].fileName);
// The Macintosh release used system fonts. We use GUI fonts.
_font = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont);
} else {
Common::File fontfile;
if (!fontfile.open("sphinx.fnt")) {
error("Couldn't open sphinx.fnt");
return Common::kNoGameDataFoundError;
} else if (!_sphinxFont.load(fontfile)) {
error("Error loading sphinx.fnt");
return Common::kUnknownError;
}
fontfile.close();
_font = &_sphinxFont;
}
_resMan = new ResMan_t7g(_macResFork);
_grvCursorMan = new GrvCursorMan_t7g(_system, _macResFork);
_videoPlayer = new VDXPlayer(this);
break;
case kGroovieV2:
_resMan = new ResMan_v2();
_grvCursorMan = new GrvCursorMan_v2(_system);
_videoPlayer = new ROQPlayer(this);
break;
}
// Detect ScummVM Music Enhancement Project presence (T7G only)
if (Common::File::exists("gu16.ogg") && _gameDescription->version == kGroovieT7G) {
// Load player for external files
_musicPlayer = new MusicPlayerIOS(this);
} else {
// Create the music player
switch (getPlatform()) {
case Common::kPlatformMacintosh:
if (_gameDescription->version == kGroovieT7G)
_musicPlayer = new MusicPlayerMac_t7g(this);
else
_musicPlayer = new MusicPlayerMac_v2(this);
break;
case Common::kPlatformIOS:
_musicPlayer = new MusicPlayerIOS(this);
break;
default:
_musicPlayer = new MusicPlayerXMI(this, _gameDescription->version == kGroovieT7G ? "fat" : "sample");
break;
}
}
// Load volume levels
syncSoundSettings();
// Get the name of the main script
Common::String filename = _gameDescription->desc.filesDescriptions[0].fileName;
if (_gameDescription->version == kGroovieT7G) {
// Run The 7th Guest's demo if requested
if (ConfMan.hasKey("demo_mode") && ConfMan.getBool("demo_mode"))
filename = "demo.grv";
else if (getPlatform() == Common::kPlatformMacintosh)
filename = "script.grv"; // Stored inside the executable's resource fork
} else if (_gameDescription->version == kGroovieV2) {
// Open the disk index
Common::File disk;
if (!disk.open(filename)) {
error("Couldn't open %s", filename.c_str());
return Common::kNoGameDataFoundError;
}
// Search the entry
bool found = false;
int index = 0;
while (!found && !disk.eos()) {
Common::String line = disk.readLine();
if (line.hasPrefix("title: ")) {
// A new entry
index++;
} else if (line.hasPrefix("boot: ") && index == _gameDescription->indexEntry) {
// It's the boot of the entry we're looking for,
// get the script filename
filename = line.c_str() + 6;
found = true;
}
}
// Couldn't find the entry
if (!found) {
error("Couldn't find entry %d in %s", _gameDescription->indexEntry, filename.c_str());
return Common::kUnknownError;
}
}
// Check the script file extension
if (!filename.hasSuffix(".grv")) {
error("%s isn't a valid script filename", filename.c_str());
return Common::kUnknownError;
}
// Load the script
if (!_script->loadScript(filename)) {
error("Couldn't load the script file %s", filename.c_str());
return Common::kUnknownError;
}
// Should I load a saved game?
if (ConfMan.hasKey("save_slot")) {
// Get the requested slot
int slot = ConfMan.getInt("save_slot");
_script->directGameLoad(slot);
}
// Game timer counter
uint16 tmr = 0;
// Check that the game files and the audio tracks aren't together run from
// the same cd
if (getPlatform() != Common::kPlatformIOS) {
checkCD();
// Initialize the CD
int cd_num = ConfMan.getInt("cdrom");
if (cd_num >= 0)
_system->getAudioCDManager()->openCD(cd_num);
}
while (!shouldQuit()) {
// Give the debugger a chance to act
_debugger->onFrame();
// Handle input
Common::Event ev;
while (_eventMan->pollEvent(ev)) {
switch (ev.type) {
case Common::EVENT_KEYDOWN:
// CTRL-D: Attach the debugger
if ((ev.kbd.flags & Common::KBD_CTRL) && ev.kbd.keycode == Common::KEYCODE_d)
_debugger->attach();
// Send the event to the scripts
_script->setKbdChar(ev.kbd.ascii);
// Continue the script execution to handle the key
_waitingForInput = false;
break;
case Common::EVENT_MAINMENU:
// Closing the GMM
case Common::EVENT_MOUSEMOVE:
// Continue the script execution, the mouse pointer
// may fall inside a different hotspot now
_waitingForInput = false;
break;
case Common::EVENT_LBUTTONDOWN:
// Send the event to the scripts
_script->setMouseClick(1);
// Continue the script execution to handle
// the click
_waitingForInput = false;
break;
case Common::EVENT_RBUTTONDOWN:
// Send the event to the scripts (to skip the video)
_script->setMouseClick(2);
break;
case Common::EVENT_QUIT:
quitGame();
break;
default:
break;
}
}
// The event loop may have triggered the quit status. In this case,
// stop the execution.
if (shouldQuit()) {
continue;
}
if (_waitingForInput) {
// Still waiting for input, just update the mouse, game timer and then wait a bit more
_grvCursorMan->animate();
_system->updateScreen();
tmr++;
// Wait a little bit between increments. While mouse is moving, this triggers
// only negligably slower.
if (tmr > 4) {
_script->timerTick();
tmr = 0;
}
_system->delayMillis(50);
} else if (_graphicsMan->isFading()) {
// We're waiting for a fading to end, let the CPU rest
// for a while and continue
_system->delayMillis(30);
} else {
// Everything's fine, execute another script step
_script->step();
}
// Update the screen if required
_graphicsMan->update();
}
return Common::kNoError;
}
Common::Platform GroovieEngine::getPlatform() const {
return _gameDescription->desc.platform;
}
bool GroovieEngine::hasFeature(EngineFeature f) const {
return
(f == kSupportsRTL) ||
(f == kSupportsLoadingDuringRuntime);
}
void GroovieEngine::syncSoundSettings() {
Engine::syncSoundSettings();
bool mute = ConfMan.getBool("mute");
// Set the music volume
_musicPlayer->setUserVolume(mute ? 0 : ConfMan.getInt("music_volume"));
// Videos just contain one digital audio track, which can be used for
// both SFX or Speech, but the engine doesn't know what they contain, so
// we have to use just one volume setting for videos.
// We use "speech" because most users will want to change the videos
// volume when they can't hear the speech because of the music.
_mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType,
mute ? 0 : ConfMan.getInt("speech_volume"));
}
bool GroovieEngine::canLoadGameStateCurrently() {
// TODO: verify the engine has been initialized
return true;
}
Common::Error GroovieEngine::loadGameState(int slot) {
_script->directGameLoad(slot);
// TODO: Use specific error codes
return Common::kNoError;
}
void GroovieEngine::waitForInput() {
_waitingForInput = true;
}
} // End of namespace Groovie
|