1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "common/scummsys.h"
24 #include "common/config-manager.h"
25 #include "common/memstream.h"
26 #include "common/serializer.h"
27 #include "graphics/palette.h"
28 #include "graphics/scaler.h"
29 #include "graphics/thumbnail.h"
30 #include "mads/mads.h"
31 #include "mads/compression.h"
32 #include "mads/game.h"
33 #include "mads/game_data.h"
34 #include "mads/events.h"
35 #include "mads/screen.h"
36 #include "mads/msurface.h"
37 #include "mads/resources.h"
38 #include "mads/dragonsphere/game_dragonsphere.h"
39 #include "mads/nebular/game_nebular.h"
40 #include "mads/phantom/game_phantom.h"
41
42 namespace MADS {
43
init(MADSEngine * vm)44 Game *Game::init(MADSEngine *vm) {
45 switch (vm->getGameID()) {
46 case GType_RexNebular:
47 return new Nebular::GameNebular(vm);
48 case GType_Dragonsphere:
49 return new Dragonsphere::GameDragonsphere(vm);
50 case GType_Phantom:
51 return new Phantom::GamePhantom(vm);
52 default:
53 error("Game: Unknown game");
54 }
55
56 return nullptr;
57 }
58
Game(MADSEngine * vm)59 Game::Game(MADSEngine *vm)
60 : _vm(vm), _surface(nullptr), _objects(vm), _scene(vm), _screenObjects(vm), _player(vm), _camX(vm), _camY(vm) {
61 _sectionNumber = 1;
62 _priorSectionNumber = 0;
63 _loadGameSlot = -1;
64 _lastSave = -1;
65 _saveFile = nullptr;
66 _saveThumb = nullptr;
67 _statusFlag = 0;
68 _sectionHandler = nullptr;
69 _sectionNumber = 1;
70 _priorSectionNumber = 0;
71 _currentSectionNumber = -1;
72 _kernelMode = KERNEL_GAME_LOAD;
73 _quoteEmergency = false;
74 _vocabEmergency = false;
75 _aaName = "*I0.AA";
76 _priorFrameTimer = 0;
77 _anyEmergency = false;
78 _triggerMode = SEQUENCE_TRIGGER_PARSER;
79 _triggerSetupMode = SEQUENCE_TRIGGER_DAEMON;
80 _trigger = 0;
81 _winStatus = 0;
82 _widepipeCtr = 0;
83 _fx = kTransitionNone;
84 _panningSpeed = 1; // Medium speed
85
86 // Load the inventory object list
87 _objects.load();
88 if (_objects._inventoryList.size() > 0)
89 // At least one item in default inventory, so select first item for display
90 _scene._userInterface._selectedInvIndex = 0;
91
92 // Load the quotes
93 loadQuotes();
94 }
95
~Game()96 Game::~Game() {
97 if (_saveThumb) {
98 _saveThumb->free();
99 delete _saveThumb;
100 }
101
102 delete _saveFile;
103 _surface->free();
104 delete _surface;
105 delete _sectionHandler;
106 }
107
run()108 void Game::run() {
109 // If requested, load a savegame instead of showing the intro
110 if (ConfMan.hasKey("save_slot")) {
111 int saveSlot = ConfMan.getInt("save_slot");
112 if (saveSlot >= 0 && saveSlot <= 999)
113 _loadGameSlot = saveSlot;
114 }
115
116 _statusFlag = true;
117
118 while (!_vm->shouldQuit()) {
119 if (_loadGameSlot == -1) {
120 startGame();
121 }
122
123 // Get the initial starting time for the first scene
124 _scene._frameStartTime = _vm->_events->getFrameCounter();
125
126 if (!_vm->shouldQuit())
127 gameLoop();
128 }
129 }
130
splitQuote(const Common::String & source,Common::String & line1,Common::String & line2)131 void Game::splitQuote(const Common::String &source, Common::String &line1, Common::String &line2) {
132 // Make the first line up the end of the word at the half-way point
133 const char *strP = source.c_str() + source.size() / 2;
134 while (*strP != ' ') ++strP;
135
136 line1 = Common::String(source.c_str(), strP);
137
138 // The rest of the string goes in the second line
139 while (*strP == ' ') ++strP;
140 line2 = Common::String(strP);
141 }
142
gameLoop()143 void Game::gameLoop() {
144 while (!_vm->shouldQuit() && _statusFlag && !_winStatus) {
145 if (_loadGameSlot != -1) {
146 loadGame(_loadGameSlot);
147 _loadGameSlot = -1;
148 }
149
150 setSectionHandler();
151 _sectionHandler->preLoadSection();
152 initSection(_sectionNumber);
153 _vm->_sound->init(_sectionNumber);
154 _sectionHandler->postLoadSection();
155
156 _scene._spriteSlots.reset();
157
158 if (_sectionNumber == _currentSectionNumber)
159 sectionLoop();
160
161 _player.releasePlayerSprites();
162 assert(_scene._sprites.size() == 0);
163
164 _vm->_palette->unlock();
165 _vm->_events->waitCursor();
166 _vm->_events->freeCursors();
167 _vm->_sound->closeDriver();
168 }
169 }
170
sectionLoop()171 void Game::sectionLoop() {
172 while (!_vm->shouldQuit() && _statusFlag && !_winStatus &&
173 (_sectionNumber == _currentSectionNumber)) {
174 _kernelMode = KERNEL_ROOM_PRELOAD;
175 _player._spritesChanged = true;
176 _quoteEmergency = false;
177 _vocabEmergency = false;
178 _vm->_events->waitCursor();
179
180 _scene.clearVocab();
181 _scene._dynamicHotspots.clear();
182 _scene.loadSceneLogic();
183
184 _player._walkAnywhere = false;
185 _player._stepEnabled = true;
186 _player._visible = true;
187 _vm->_dialogs->_defaultPosition = Common::Point(-1, -1);
188 _visitedScenes.add(_scene._nextSceneId);
189
190 // Reset the user interface
191 _screenObjects._forceRescan = true;
192 _screenObjects._inputMode = kInputBuildingSentences;
193 _scene._userInterface._scrollbarActive = SCROLLBAR_NONE;
194
195 _player._loadsFirst = true;
196
197 _scene._sceneLogic->setup();
198 if (_player._spritesChanged || _player._loadsFirst) {
199 if (_player._spritesLoaded)
200 _player.releasePlayerSprites();
201 _vm->_palette->resetGamePalette(18, 10);
202 _scene._spriteSlots.reset();
203 } else {
204 _vm->_palette->initPalette();
205 }
206
207 // Set up scene palette usage
208 _scene._scenePaletteUsage.clear();
209 _scene._scenePaletteUsage.push_back(PaletteUsage::UsageEntry(0xF0));
210 _scene._scenePaletteUsage.push_back(PaletteUsage::UsageEntry(0xF1));
211 _scene._scenePaletteUsage.push_back(PaletteUsage::UsageEntry(0xF2));
212 _vm->_palette->_paletteUsage.load(&_scene._scenePaletteUsage);
213
214 if (!_player._spritesLoaded && _player._loadsFirst) {
215 if (_player.loadSprites(""))
216 _vm->quitGame();
217 _player._loadedFirst = true;
218 }
219
220 _scene.loadScene(_scene._nextSceneId, _aaName, 0);
221 camInitDefault();
222 camSetSpeed();
223
224
225 _vm->_sound->pauseNewCommands();
226
227 if (!_player._spritesLoaded) {
228 if (_player.loadSprites(""))
229 _vm->quitGame();
230 _player._loadedFirst = false;
231 }
232
233 _vm->_events->initVars();
234 _scene._userInterface._highlightedCommandIndex = -1;
235 _scene._userInterface._highlightedInvIndex = -1;
236 _scene._userInterface._highlightedItemVocabIndex = -1;
237
238 _scene._action.clear();
239 _player.setFinalFacing();
240 _player._facing = _player._turnToFacing;
241 _player.cancelCommand();
242 _kernelMode = KERNEL_ROOM_INIT;
243
244 switch (_vm->_screenFade) {
245 case SCREEN_FADE_SMOOTH:
246 _fx = kTransitionFadeOutIn;
247 break;
248 case SCREEN_FADE_FAST:
249 _fx = kNullPaletteCopy;
250 break;
251 default:
252 _fx = kTransitionNone;
253 break;
254 }
255
256 _trigger = 0;
257 _priorFrameTimer = _scene._frameStartTime;
258
259 // If in the middle of restoring a game, handle the rest of the loading
260 if (_saveFile != nullptr) {
261 Common::Serializer s(_saveFile, nullptr);
262 synchronize(s, false);
263 delete _saveFile;
264 _saveFile = nullptr;
265 }
266
267 // Call the scene logic for entering the given scene
268 _triggerSetupMode = SEQUENCE_TRIGGER_DAEMON;
269 _scene._sceneLogic->enter();
270
271 // Set player data
272 _player._targetPos = _player._playerPos;
273 _player._turnToFacing = _player._facing;
274 _player._targetFacing = _player._facing;
275 _player.selectSeries();
276 _player.updateFrame();
277
278 _player._beenVisible = _player._visible;
279 _player._special = _scene.getDepthHighBits(_player._playerPos);
280 _player._priorTimer = _scene._frameStartTime - _player._ticksAmount;
281 _player.idle();
282
283 if (_scene._userInterface._selectedInvIndex >= 0) {
284 _scene._userInterface.loadInventoryAnim(
285 _objects._inventoryList[_scene._userInterface._selectedInvIndex]);
286 } else {
287 _scene._userInterface.noInventoryAnim();
288 }
289
290 _kernelMode = KERNEL_ACTIVE_CODE;
291 _scene._roomChanged = false;
292
293 if ((_quoteEmergency || _vocabEmergency) && !_anyEmergency) {
294 _scene._currentSceneId = _scene._priorSceneId;
295 _anyEmergency = true;
296 } else {
297 _anyEmergency = false;
298 _scene.loop();
299 }
300
301 _vm->_events->waitCursor();
302 _kernelMode = KERNEL_ROOM_PRELOAD;
303
304 for (int i = 0; i < 10; i++) {
305 delete _scene._animation[i];
306 _scene._animation[i] = nullptr;
307 }
308
309 _scene._reloadSceneFlag = false;
310
311 _scene._userInterface.noInventoryAnim();
312 _scene.removeSprites();
313
314 if (!_player._loadedFirst) {
315 _player._spritesLoaded = false;
316 _player._spritesChanged = true;
317 }
318
319 // Clear the scene
320 _scene.freeCurrentScene();
321 _sectionNumber = _scene._nextSceneId / 100;
322
323 // Check whether to show a dialog
324 checkShowDialog();
325 }
326 }
327
initSection(int sectionNumber)328 void Game::initSection(int sectionNumber) {
329 _priorSectionNumber = _currentSectionNumber;
330 _currentSectionNumber = sectionNumber;
331
332 _vm->_palette->resetGamePalette(18, 10);
333 _vm->_palette->setLowRange();
334
335 if (_scene._mode == SCREENMODE_VGA)
336 _vm->_palette->setPalette(_vm->_palette->_mainPalette, 0, 4);
337
338 _vm->_events->loadCursors("*CURSOR.SS");
339
340 assert(_vm->_events->_cursorSprites);
341 _vm->_events->setCursor2((_vm->_events->_cursorSprites->getCount() <= 1) ?
342 CURSOR_ARROW : CURSOR_WAIT);
343 }
344
loadQuotes()345 void Game::loadQuotes() {
346 File f("*QUOTES.DAT");
347
348 Common::String msg;
349 while (true) {
350 uint8 b = f.readByte();
351
352 msg += b;
353 if (f.eos() || b == '\0') {
354 // end of string, add it to the strings list
355 _quotes.push_back(msg);
356 msg = "";
357 }
358
359 if (f.eos()) break;
360 }
361
362 f.close();
363 }
364
getMessage(uint32 id)365 Common::StringArray Game::getMessage(uint32 id) {
366 File f("*MESSAGES.DAT");
367 int count = f.readUint16LE();
368
369 for (int idx = 0; idx < count; ++idx) {
370 uint32 itemId = f.readUint32LE();
371 uint32 offset = f.readUint32LE();
372 uint16 size = f.readUint16LE();
373
374 if (itemId == id) {
375 // Get the source buffer size
376 uint16 sizeIn;
377 if (idx == (count - 1)) {
378 sizeIn = f.size() - offset;
379 } else {
380 f.skip(4);
381 uint32 nextOffset = f.readUint32LE();
382 sizeIn = nextOffset - offset;
383 }
384
385 // Get the compressed data
386 f.seek(offset);
387 byte *bufferIn = new byte[sizeIn];
388 f.read(bufferIn, sizeIn);
389
390 // Decompress it
391 char *bufferOut = new char[size];
392 FabDecompressor fab;
393 fab.decompress(bufferIn, sizeIn, (byte *)bufferOut, size);
394
395 // Form the output string list
396 Common::StringArray result;
397 const char *p = bufferOut;
398 while (p < (bufferOut + size)) {
399 result.push_back(p);
400 p += strlen(p) + 1;
401 }
402
403 delete[] bufferIn;
404 delete[] bufferOut;
405 return result;
406 }
407 }
408
409 error("Invalid message Id specified");
410 }
411
412 static const char *const DEBUG_STRING = "WIDEPIPE";
413
handleKeypress(const Common::KeyState & kbd)414 void Game::handleKeypress(const Common::KeyState &kbd) {
415 if (kbd.flags & Common::KBD_CTRL) {
416 if (_widepipeCtr == 8) {
417 // Implement original game cheating keys here someday
418 } else {
419 if (kbd.keycode == (Common::KEYCODE_a +
420 (DEBUG_STRING[_widepipeCtr] - 'a'))) {
421 if (++_widepipeCtr == 8) {
422 MessageDialog *dlg = new MessageDialog(_vm, 2,
423 "CHEATING ENABLED", "(for your convenience).");
424 dlg->show();
425 delete dlg;
426 }
427 }
428 }
429 }
430
431 Scene &scene = _vm->_game->_scene;
432 switch (kbd.keycode) {
433 case Common::KEYCODE_F1:
434 _vm->_dialogs->_pendingDialog = DIALOG_GAME_MENU;
435 break;
436 case Common::KEYCODE_F5:
437 _vm->_dialogs->_pendingDialog = DIALOG_SAVE;
438 break;
439 case Common::KEYCODE_F7:
440 _vm->_dialogs->_pendingDialog = DIALOG_RESTORE;
441 break;
442 case Common::KEYCODE_PAGEUP:
443 scene._userInterface._scrollbarStrokeType = SCROLLBAR_UP;
444 scene._userInterface.changeScrollBar();
445 break;
446 case Common::KEYCODE_PAGEDOWN:
447 scene._userInterface._scrollbarStrokeType = SCROLLBAR_DOWN;
448 scene._userInterface.changeScrollBar();
449 break;
450
451
452 default:
453 break;
454 }
455 }
456
synchronize(Common::Serializer & s,bool phase1)457 void Game::synchronize(Common::Serializer &s, bool phase1) {
458 if (phase1) {
459 s.syncAsSint16LE(_fx);
460 s.syncAsSint16LE(_trigger);
461 s.syncAsUint16LE(_triggerSetupMode);
462 s.syncAsUint16LE(_triggerMode);
463 s.syncString(_aaName);
464 s.syncAsSint16LE(_lastSave);
465
466 _scene.synchronize(s);
467 _objects.synchronize(s);
468 _visitedScenes.synchronize(s, _scene._nextSceneId);
469 _player.synchronize(s);
470 _screenObjects.synchronize(s);
471 } else {
472 // Load scene specific data for the loaded scene
473 _scene._sceneLogic->synchronize(s);
474 }
475 }
476
loadGame(int slotNumber)477 void Game::loadGame(int slotNumber) {
478 _saveFile = g_system->getSavefileManager()->openForLoading(
479 _vm->generateSaveName(slotNumber));
480
481 Common::Serializer s(_saveFile, nullptr);
482
483 // Load the savaegame header
484 MADSSavegameHeader header;
485 if (!readSavegameHeader(_saveFile, header))
486 error("Invalid savegame");
487
488 // Load most of the savegame data with the exception of scene specific info
489 synchronize(s, true);
490
491 // Set up section/scene and other initial states for post-load
492 _currentSectionNumber = -2;
493 _scene._currentSceneId = -2;
494 _sectionNumber = _scene._nextSceneId / 100;
495 _scene._frameStartTime = _vm->_events->getFrameCounter();
496 _vm->_screen->_shakeCountdown = -1;
497
498 // Default the selected inventory item to the first one, if the player has any
499 _scene._userInterface._selectedInvIndex = _objects._inventoryList.size() > 0 ? 0 : -1;
500
501 // Set player sprites sets flags
502 _player._spritesLoaded = false;
503 _player._spritesChanged = true;
504 }
505
saveGame(int slotNumber,const Common::String & saveName)506 void Game::saveGame(int slotNumber, const Common::String &saveName) {
507 Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving(
508 _vm->generateSaveName(slotNumber));
509
510 MADSSavegameHeader header;
511 header._saveName = saveName;
512 writeSavegameHeader(out, header);
513
514 Common::Serializer s(nullptr, out);
515 synchronize(s, true);
516 synchronize(s, false);
517
518 out->finalize();
519 delete out;
520 }
521
522 const char *const SAVEGAME_STR = "MADS";
523 #define SAVEGAME_STR_SIZE 4
524
readSavegameHeader(Common::InSaveFile * in,MADSSavegameHeader & header,bool skipThumbnail)525 WARN_UNUSED_RESULT bool Game::readSavegameHeader(Common::InSaveFile *in, MADSSavegameHeader &header, bool skipThumbnail) {
526 char saveIdentBuffer[SAVEGAME_STR_SIZE + 1];
527
528 // Validate the header Id
529 in->read(saveIdentBuffer, SAVEGAME_STR_SIZE + 1);
530 if (strncmp(saveIdentBuffer, SAVEGAME_STR, SAVEGAME_STR_SIZE))
531 return false;
532
533 header._version = in->readByte();
534 if (header._version > MADS_SAVEGAME_VERSION)
535 return false;
536
537 // Read in the string
538 header._saveName.clear();
539 char ch;
540 while ((ch = (char)in->readByte()) != '\0') header._saveName += ch;
541
542 // Get the thumbnail
543 if (!Graphics::loadThumbnail(*in, header._thumbnail, skipThumbnail)) {
544 return false;
545 }
546
547 // Read in save date/time
548 header._year = in->readSint16LE();
549 header._month = in->readSint16LE();
550 header._day = in->readSint16LE();
551 header._hour = in->readSint16LE();
552 header._minute = in->readSint16LE();
553 header._totalFrames = in->readUint32LE();
554
555 return true;
556 }
557
writeSavegameHeader(Common::OutSaveFile * out,MADSSavegameHeader & header)558 void Game::writeSavegameHeader(Common::OutSaveFile *out, MADSSavegameHeader &header) {
559 // Write out a savegame header
560 out->write(SAVEGAME_STR, SAVEGAME_STR_SIZE + 1);
561
562 out->writeByte(MADS_SAVEGAME_VERSION);
563
564 // Write savegame name
565 out->write(header._saveName.c_str(), header._saveName.size());
566 out->writeByte('\0');
567
568 // Handle the thumbnail. If there's already one set by the game, create one
569 if (!_saveThumb)
570 createThumbnail();
571 Graphics::saveThumbnail(*out, *_saveThumb);
572
573 _saveThumb->free();
574 delete _saveThumb;
575 _saveThumb = nullptr;
576
577 // Write out the save date/time
578 TimeDate td;
579 g_system->getTimeAndDate(td);
580 out->writeSint16LE(td.tm_year + 1900);
581 out->writeSint16LE(td.tm_mon + 1);
582 out->writeSint16LE(td.tm_mday);
583 out->writeSint16LE(td.tm_hour);
584 out->writeSint16LE(td.tm_min);
585 out->writeUint32LE(_vm->_events->getFrameCounter());
586 }
587
createThumbnail()588 void Game::createThumbnail() {
589 if (_saveThumb) {
590 _saveThumb->free();
591 delete _saveThumb;
592 }
593
594 uint8 thumbPalette[PALETTE_SIZE];
595 _vm->_palette->grabPalette(thumbPalette, 0, PALETTE_COUNT);
596 _saveThumb = new Graphics::Surface();
597 ::createThumbnail(_saveThumb, (const byte *)_vm->_screen->getPixels(),
598 MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT, thumbPalette);
599 }
600
syncTimers(SyncType slaveType,int slaveId,SyncType masterType,int masterId)601 void Game::syncTimers(SyncType slaveType, int slaveId, SyncType masterType, int masterId) {
602 uint32 syncTime = 0;
603
604 switch (masterType) {
605 case SYNC_SEQ:
606 syncTime = _scene._sequences[masterId]._timeout;
607 break;
608
609 case SYNC_ANIM:
610 syncTime = _scene._animation[masterId]->getNextFrameTimer();
611 break;
612
613 case SYNC_CLOCK:
614 syncTime = _scene._frameStartTime + masterId;
615 break;
616
617 case SYNC_PLAYER:
618 syncTime = _player._priorTimer;
619 break;
620 }
621
622
623 switch (slaveType) {
624 case SYNC_SEQ:
625 _scene._sequences[slaveId]._timeout = syncTime;
626 break;
627
628 case SYNC_PLAYER:
629 _player._priorTimer = syncTime;
630 break;
631
632 case SYNC_ANIM:
633 _scene._animation[slaveId]->setNextFrameTimer(syncTime);
634 break;
635
636 case SYNC_CLOCK:
637 error("syncTimer is trying to force _frameStartTime");
638 }
639 }
640
camInitDefault()641 void Game::camInitDefault() {
642 _camX.setDefaultPanX();
643 _camY.setDefaultPanY();
644 }
645
camSetSpeed()646 void Game::camSetSpeed() {
647 switch (_panningSpeed) {
648 case 1:
649 _camX._speed = 8;
650 _camY._speed = 4;
651 break;
652
653 case 2:
654 _camX._speed = 320;
655 _camY._speed = 160;
656 break;
657
658 default:
659 _camX._speed = 4;
660 _camY._speed = 2;
661 break;
662 }
663 }
664
camUpdate()665 void Game::camUpdate() {
666 bool any_pan = _camX.camPan(&_scene._posAdjust.x, &_player._playerPos.x, 320, _scene._sceneInfo->_width);
667 any_pan |= _camY.camPan(&_scene._posAdjust.y, &_player._playerPos.y, 156, _scene._sceneInfo->_height);
668
669 if (any_pan) {
670 _scene.setCamera(_scene._posAdjust);
671 _screenObjects._forceRescan = true;
672 }
673 }
674
675 } // End of namespace MADS
676