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 "graphics/cursorman.h"
24 #include "gnap/gnap.h"
25 #include "gnap/datarchive.h"
26 #include "gnap/gamesys.h"
27 #include "gnap/resource.h"
28 #include "gnap/sound.h"
29
30 #include "common/config-manager.h"
31 #include "common/debug-channels.h"
32 #include "common/timer.h"
33
34 #include "engines/util.h"
35
36 namespace Gnap {
37
38 static const int kCursors[] = {
39 LOOK_CURSOR,
40 GRAB_CURSOR,
41 TALK_CURSOR,
42 PLAT_CURSOR
43 };
44
45 static const int kDisabledCursors[] = {
46 NOLOOK_CURSOR,
47 NOGRAB_CURSOR,
48 NOTALK_CURSOR,
49 NOPLAT_CURSOR
50 };
51
52 static const char *kCursorNames[] = {
53 "LOOK_CURSOR",
54 "GRAB_CURSOR",
55 "TALK_CURSOR",
56 "PLAT_CURSOR",
57 "NOLOOK_CURSOR",
58 "NOGRAB_CURSOR",
59 "NOTALK_CURSOR",
60 "NOPLAT_CURSOR",
61 "EXIT_L_CURSOR",
62 "EXIT_R_CURSOR",
63 "EXIT_U_CURSOR",
64 "EXIT_D_CURSOR",
65 "EXIT_NE_CURSOR",
66 "EXIT_NW_CURSOR",
67 "EXIT_SE_CURSOR",
68 "EXIT_SW_CURSOR",
69 "WAIT_CURSOR"
70 };
71
72
73 static const int kCursorSpriteIds[30] = {
74 0x005, 0x008, 0x00A, 0x004, 0x009, 0x003,
75 0x006, 0x007, 0x00D, 0x00F, 0x00B, 0x00C,
76 0x019, 0x01C, 0x015, 0x014, 0x010, 0x01A,
77 0x018, 0x013, 0x011, 0x012, 0x01B, 0x016,
78 0x017, 0x01D, 0x01E, 0x01F, 0x76A, 0x76B
79 };
80
81 static const char *kSceneNames[] = {
82 "open", "pigpn", "truck", "creek", "mafrm", "frbrn", "inbrn", "crash",
83 "porch", "barbk", "kitch", "bar", "juke", "wash", "john", "jkbox",
84 "brawl", "stret", "frtoy", "intoy", "frgro", "park", "cash", "ingro",
85 "frcir", "booth", "circ", "outcl", "incln", "monk", "elcir", "beer",
86 "pig2", "trk2", "creek", "frbrn", "inbrn", "mafrm", "infrm", "efair",
87 "fair", "souv", "chick", "ship", "kiss", "disco", "boot", "can",
88 "can2", "drive", "tung", "puss", "space", "phone", "can3"
89 };
90
GnapEngine(OSystem * syst,const ADGameDescription * gd)91 GnapEngine::GnapEngine(OSystem *syst, const ADGameDescription *gd) :
92 Engine(syst), _gameDescription(gd) {
93
94 DebugMan.addDebugChannel(kDebugBasic, "basic", "Basic debug level");
95
96 _random = new Common::RandomSource("gnap");
97
98 Engine::syncSoundSettings();
99
100 _exe = nullptr;
101 _dat = nullptr;
102 _spriteCache = nullptr;
103 _soundCache = nullptr;
104 _sequenceCache = nullptr;
105 _gameSys = nullptr;
106 _soundMan = nullptr;
107 _debugger = nullptr;
108 _gnap = nullptr;
109 _plat = nullptr;
110 _font = nullptr;
111 _scene = nullptr;
112 _music = nullptr;
113 _tempThumbnail = nullptr;
114 _menuBackgroundSurface = nullptr;
115 _menuQuitQuerySprite = nullptr;
116 _largeSprite = nullptr;
117 _menuSaveLoadSprite = nullptr;
118 _menuSprite2 = nullptr;
119 _menuSprite1 = nullptr;
120 _spriteHandle = nullptr;
121 _cursorSprite = nullptr;
122 _pauseSprite = nullptr;
123 _backgroundSurface = nullptr;
124
125 _wasSavegameLoaded = false;
126 _isWaiting = false;
127 _sceneWaiting = false;
128 _menuDone = false;
129 _sceneDone = false;
130 _isLeavingScene = false;
131 _isStockDatLoaded = false;
132 _gameDone = false;
133 _isPaused = false;
134 _sceneSavegameLoaded = false;
135
136 for (int i = 0; i < kMaxTimers; ++i)
137 _savedTimers[i] = _timers[i] = 0;
138
139 _mousePos = Common::Point(0, 0);
140 _currGrabCursorX = _currGrabCursorY = 0;
141
142 _idleTimerIndex = -1;
143 _menuStatus = 0;
144 _menuSpritesIndex = -1;
145 _savegameIndex = -1;
146 _gridMinX = 0;
147 _gridMinY = 0;
148 _gridMaxX = 0;
149 _gridMaxY = 0;
150 _toyUfoNextSequenceId = -1;
151 _toyUfoSequenceId = -1;
152 _toyUfoId = -1;
153 _toyUfoActionStatus = -1;
154 _toyUfoX = 0;
155 _toyUfoY = 0;
156 _s18GarbageCanPos = 0;
157
158 for (int i = 0; i < 7; i++)
159 _savegameSprites[i] = nullptr;
160 for (int i = 0; i < 30; i++)
161 _menuInventorySprites[i] = nullptr;
162
163 _newSceneNum = 0;
164 _inventory = 0;
165 _gameFlags = 0;
166 _hotspotsCount = 0;
167 _sceneClickedHotspot = -1;
168 _newCursorValue = 0;
169 _cursorValue = 0;
170 _verbCursor = 0;
171 _cursorIndex = -1;
172 _leftClickMouseX = 0;
173 _leftClickMouseY = 0;
174 _grabCursorSprite = nullptr;
175 _grabCursorSpriteIndex = 0;
176 _newGrabCursorSpriteIndex = 0;
177 _fullScreenSprite = nullptr;
178 _fullScreenSpriteId = 0;
179 _deviceX1 = 0;
180 _deviceY1 = 0;
181 _soundTimerIndexA = 0;
182 _soundTimerIndexB = 0;
183 _soundTimerIndexC = 0;
184 _loadGameSlot = -1;
185 _lastUpdateClock = 0;
186 _prevSceneNum = -1;
187 _currentSceneNum = -1;
188 }
189
~GnapEngine()190 GnapEngine::~GnapEngine() {
191 delete _random;
192 delete _music;
193 delete _tempThumbnail;
194 }
195
run()196 Common::Error GnapEngine::run() {
197 // Initialize the graphics mode to RGBA8888
198 #if defined(SCUMM_BIG_ENDIAN)
199 Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24);
200 #else
201 Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0);
202 #endif
203 initGraphics(800, 600, &format);
204
205 // We do not support color conversion yet
206 if (_system->getScreenFormat() != format)
207 return Common::kUnsupportedColorMode;
208
209 _lastUpdateClock = 0;
210
211 // >>>>> Variable initialization
212 _cursorIndex = -1;
213 _verbCursor = 1;
214
215 if (ConfMan.hasKey("save_slot"))
216 _loadGameSlot = ConfMan.getInt("save_slot");
217
218 invClear();
219 clearFlags();
220
221 _grabCursorSprite = nullptr;
222 _newGrabCursorSpriteIndex = -1;
223 _backgroundSurface = nullptr;
224 _isStockDatLoaded = false;
225 _gameDone = false;
226 _isPaused = false;
227 _pauseSprite = nullptr;
228
229 ////////////////////////////////////////////////////////////////////////////
230
231 _exe = new Common::PEResources();
232 if (!_exe->loadFromEXE("ufos.exe"))
233 error("Could not load ufos.exe");
234
235 #ifdef USE_FREETYPE2
236 Common::SeekableReadStream *stream = _exe->getResource(Common::kWinFont, 2000);
237 _font = Graphics::loadTTFFont(*stream, 24);
238 if (!_font)
239 warning("Unable to load font");
240 delete stream;
241 #else
242 _font = nullptr;
243 #endif
244
245 _dat = new DatManager();
246 _spriteCache = new SpriteCache(_dat);
247 _soundCache = new SoundCache(_dat);
248 _sequenceCache = new SequenceCache(_dat);
249 _gameSys = new GameSys(this);
250 _soundMan = new SoundMan(this);
251 _debugger = new Debugger();
252 _gnap = new PlayerGnap(this);
253 _plat = new PlayerPlat(this);
254
255 _menuBackgroundSurface = nullptr;
256
257 initGlobalSceneVars();
258 mainLoop();
259
260 delete _plat;
261 delete _gnap;
262 delete _soundMan;
263 delete _gameSys;
264 delete _sequenceCache;
265 delete _soundCache;
266 delete _spriteCache;
267 delete _dat;
268 delete _debugger;
269 delete _font;
270 delete _exe;
271
272 return Common::kNoError;
273 }
274
updateEvents()275 void GnapEngine::updateEvents() {
276 Common::Event event;
277
278 while (_eventMan->pollEvent(event)) {
279 switch (event.type) {
280 case Common::EVENT_KEYDOWN:
281 // Check for debugger
282 if (event.kbd.keycode == Common::KEYCODE_d && (event.kbd.flags & Common::KBD_CTRL)) {
283 // Attach to the debugger
284 _debugger->attach();
285 _debugger->onFrame();
286 }
287
288 _keyPressState[event.kbd.keycode] = true;
289 _keyDownState[event.kbd.keycode] = true;
290 break;
291 case Common::EVENT_KEYUP:
292 _keyDownState[event.kbd.keycode] = false;
293 break;
294 case Common::EVENT_MOUSEMOVE:
295 _mousePos = event.mouse;
296 break;
297 case Common::EVENT_LBUTTONUP:
298 _mouseButtonState._left = false;
299 break;
300 case Common::EVENT_LBUTTONDOWN:
301 _leftClickMouseX = event.mouse.x;
302 _leftClickMouseY = event.mouse.y;
303 _mouseButtonState._left = true;
304 _mouseClickState._left = true;
305 break;
306 case Common::EVENT_RBUTTONUP:
307 _mouseButtonState._right = false;
308 break;
309 case Common::EVENT_RBUTTONDOWN:
310 _mouseButtonState._right = true;
311 _mouseClickState._right = true;
312 break;
313 case Common::EVENT_QUIT:
314 quitGame();
315 break;
316 default:
317 break;
318 }
319 }
320 }
321
gameUpdateTick()322 void GnapEngine::gameUpdateTick() {
323 updateEvents();
324
325 if (shouldQuit()) {
326 _gameDone = true;
327 _sceneDone = true;
328 }
329
330 int currClock = _system->getMillis();
331 if (currClock >= _lastUpdateClock + 66) {
332 _gameSys->fatUpdate();
333 _gameSys->drawSprites();
334 _gameSys->updateScreen();
335 _gameSys->updatePlaySounds();
336 _gameSys->_gameSysClock++;
337 updateTimers();
338 _lastUpdateClock = currClock;
339 }
340
341 _soundMan->update();
342 _system->updateScreen();
343 _system->delayMillis(5);
344 }
345
saveTimers()346 void GnapEngine::saveTimers() {
347 for (int i = 0; i < kMaxTimers; ++i )
348 _savedTimers[i] = _timers[i];
349 }
350
restoreTimers()351 void GnapEngine::restoreTimers() {
352 for (int i = 0; i < kMaxTimers; ++i )
353 _timers[i] = _savedTimers[i];
354 }
355
pauseGame()356 void GnapEngine::pauseGame() {
357 if (!_isPaused) {
358 saveTimers();
359 hideCursor();
360 setGrabCursorSprite(-1);
361 _pauseSprite = _gameSys->createSurface(0x1076C);
362 _gameSys->insertSpriteDrawItem(_pauseSprite, (800 - _pauseSprite->w) / 2, (600 - _pauseSprite->h) / 2, 356);
363 _lastUpdateClock = 0;
364 gameUpdateTick();
365 playMidi("pause.mid");
366 _isPaused = true;
367 }
368 }
369
resumeGame()370 void GnapEngine::resumeGame() {
371 if (_isPaused) {
372 restoreTimers();
373 _gameSys->removeSpriteDrawItem(_pauseSprite, 356);
374 _lastUpdateClock = 0;
375 gameUpdateTick();
376 deleteSurface(&_pauseSprite);
377 stopMidi();
378 _isPaused = false;
379 clearAllKeyStatus1();
380 _mouseClickState._left = false;
381 _mouseClickState._right = false;
382 showCursor();
383 _gameSys->_gameSysClock = 0;
384 _gameSys->_lastUpdateClock = 0;
385 }
386 }
387
updatePause()388 void GnapEngine::updatePause() {
389 while (_isPaused && !_gameDone) {
390 gameUpdateTick();
391 if (isKeyStatus1(Common::KEYCODE_p)) {
392 clearKeyStatus1(Common::KEYCODE_p);
393 resumeGame();
394 }
395 }
396 }
397
getRandom(int max)398 int GnapEngine::getRandom(int max) {
399 return _random->getRandomNumber(max - 1);
400 }
401
readSavegameDescription(int savegameNum,Common::String & description)402 int GnapEngine::readSavegameDescription(int savegameNum, Common::String &description) {
403 description = Common::String::format("Savegame %d", savegameNum);
404 return 0;
405 }
406
loadSavegame(int savegameNum)407 int GnapEngine::loadSavegame(int savegameNum) {
408 return 1;
409 }
410
delayTicks(int val,int idx=0,bool updateCursor=false)411 void GnapEngine::delayTicks(int val, int idx = 0, bool updateCursor = false) {
412 int startTick = _timers[idx];
413
414 _timers[idx] = val;
415
416 while (_timers[idx] && !_gameDone) {
417 gameUpdateTick();
418
419 if (updateCursor)
420 updateGrabCursorSprite(0, 0);
421 }
422
423 startTick -= _timers[idx];
424 if (startTick < 0)
425 startTick = 0;
426
427 _timers[idx] = startTick;
428 }
429
delayTicksA(int val,int idx)430 void GnapEngine::delayTicksA(int val, int idx) {
431 delayTicks(val, idx);
432 }
433
delayTicksCursor(int val)434 void GnapEngine::delayTicksCursor(int val) {
435 delayTicks(val, 0, true);
436 }
437
setHotspot(int index,int16 x1,int16 y1,int16 x2,int16 y2,uint16 flags,int16 walkX,int16 walkY)438 void GnapEngine::setHotspot(int index, int16 x1, int16 y1, int16 x2, int16 y2, uint16 flags,
439 int16 walkX, int16 walkY) {
440 _hotspots[index]._rect = Common::Rect(x1, y1, x2, y2);
441 _hotspots[index]._flags = flags;
442 _hotspotsWalkPos[index] = Common::Point(walkX, walkY);
443 }
444
getHotspotIndexAtPos(Common::Point pos)445 int GnapEngine::getHotspotIndexAtPos(Common::Point pos) {
446 for (int i = 0; i < _hotspotsCount; ++i) {
447 if (!_hotspots[i].isFlag(SF_DISABLED) && _hotspots[i].isPointInside(pos))
448 return i;
449 }
450 return -1;
451 }
452
updateCursorByHotspot()453 void GnapEngine::updateCursorByHotspot() {
454 if (!_isWaiting) {
455 int hotspotIndex = getHotspotIndexAtPos(_mousePos);
456
457 if (_debugger->_showHotspotNumber) {
458 // NOTE This causes some display glitches
459 char t[256];
460 sprintf(t, "hotspot = %2d", hotspotIndex);
461 if (!_font)
462 _gameSys->fillSurface(nullptr, 10, 10, 80, 16, 0, 0, 0);
463 else
464 _gameSys->fillSurface(nullptr, 8, 9, _font->getStringWidth(t) + 10, _font->getFontHeight() + 2, 0, 0, 0);
465 _gameSys->drawTextToSurface(nullptr, 10, 10, 255, 255, 255, t);
466 }
467
468 if (hotspotIndex < 0)
469 setCursor(kDisabledCursors[_verbCursor]);
470 else if (_hotspots[hotspotIndex]._flags & SF_EXIT_L_CURSOR)
471 setCursor(EXIT_L_CURSOR);
472 else if (_hotspots[hotspotIndex]._flags & SF_EXIT_R_CURSOR)
473 setCursor(EXIT_R_CURSOR);
474 else if (_hotspots[hotspotIndex]._flags & SF_EXIT_U_CURSOR)
475 setCursor(EXIT_U_CURSOR);
476 else if (_hotspots[hotspotIndex]._flags & SF_EXIT_D_CURSOR)
477 setCursor(EXIT_D_CURSOR);
478 else if (_hotspots[hotspotIndex]._flags & SF_EXIT_NE_CURSOR)
479 setCursor(EXIT_NE_CURSOR);
480 else if (_hotspots[hotspotIndex]._flags & SF_EXIT_NW_CURSOR)
481 setCursor(EXIT_NW_CURSOR);
482 else if (_hotspots[hotspotIndex]._flags & SF_EXIT_SE_CURSOR)
483 setCursor(EXIT_SE_CURSOR);
484 else if (_hotspots[hotspotIndex]._flags & SF_EXIT_SW_CURSOR)
485 setCursor(EXIT_SW_CURSOR);
486 else if (_hotspots[hotspotIndex]._flags & (1 << _verbCursor))
487 setCursor(kCursors[_verbCursor]);
488 else
489 setCursor(kDisabledCursors[_verbCursor]);
490 }
491 // Update platypus hotspot
492 _hotspots[0]._rect = Common::Rect(_gridMinX + 75 * _plat->_pos.x - 30, _gridMinY + 48 * _plat->_pos.y - 100
493 , _gridMinX + 75 * _plat->_pos.x + 30, _gridMinY + 48 * _plat->_pos.y);
494 }
495
getClickedHotspotId()496 int GnapEngine::getClickedHotspotId() {
497 int result = -1;
498 if (_isWaiting)
499 _mouseClickState._left = false;
500 else if (_mouseClickState._left) {
501 int hotspotIndex = getHotspotIndexAtPos(Common::Point(_leftClickMouseX, _leftClickMouseY));
502 if (hotspotIndex >= 0) {
503 _mouseClickState._left = false;
504 _timers[3] = 300;
505 result = hotspotIndex;
506 }
507 }
508 return result;
509 }
510
getInventoryItemSpriteNum(int index)511 int GnapEngine::getInventoryItemSpriteNum(int index) {
512 return kCursorSpriteIds[index];
513 }
514
updateMouseCursor()515 void GnapEngine::updateMouseCursor() {
516 if (_mouseClickState._right) {
517 // Switch through the verb cursors
518 _mouseClickState._right = false;
519 _timers[3] = 300;
520 _verbCursor = (_verbCursor + 1) % 4;
521 if (!isFlag(kGFPlatypus) && _verbCursor == PLAT_CURSOR && _cursorValue == 1)
522 _verbCursor = (_verbCursor + 1) % 4;
523 if (!_isWaiting)
524 setCursor(kDisabledCursors[_verbCursor]);
525 setGrabCursorSprite(-1);
526 }
527 if (_isWaiting && ((_gnap->_actionStatus < 0 && _plat->_actionStatus < 0) || _sceneWaiting)) {
528 setCursor(kDisabledCursors[_verbCursor]);
529 showCursor();
530 _isWaiting = false;
531 } else if (!_isWaiting && (_gnap->_actionStatus >= 0 || _plat->_actionStatus >= 0) && !_sceneWaiting) {
532 setCursor(WAIT_CURSOR);
533 hideCursor();
534 _isWaiting = true;
535 }
536 }
537
setVerbCursor(int verbCursor)538 void GnapEngine::setVerbCursor(int verbCursor) {
539 _verbCursor = verbCursor;
540 if (!_isWaiting)
541 setCursor(kDisabledCursors[_verbCursor]);
542 }
543
setCursor(int cursorIndex)544 void GnapEngine::setCursor(int cursorIndex) {
545 if (_cursorIndex != cursorIndex) {
546 const char *cursorName = kCursorNames[cursorIndex];
547 Graphics::WinCursorGroup *cursorGroup = Graphics::WinCursorGroup::createCursorGroup(*_exe, Common::WinResourceID(cursorName));
548 if (cursorGroup) {
549 Graphics::Cursor *cursor = cursorGroup->cursors[0].cursor;
550 CursorMan.replaceCursor(cursor);
551 delete cursorGroup;
552 }
553 _cursorIndex = cursorIndex;
554 }
555 }
556
showCursor()557 void GnapEngine::showCursor() {
558 CursorMan.showMouse(true);
559 }
560
hideCursor()561 void GnapEngine::hideCursor() {
562 CursorMan.showMouse(false);
563 }
564
setGrabCursorSprite(int index)565 void GnapEngine::setGrabCursorSprite(int index) {
566 freeGrabCursorSprite();
567 if (index >= 0) {
568 createGrabCursorSprite(makeRid(1, kCursorSpriteIds[index]));
569 setVerbCursor(GRAB_CURSOR);
570 }
571 _grabCursorSpriteIndex = index;
572 }
573
createGrabCursorSprite(int spriteId)574 void GnapEngine::createGrabCursorSprite(int spriteId) {
575 _grabCursorSprite = _gameSys->createSurface(spriteId);
576 _gameSys->insertSpriteDrawItem(_grabCursorSprite,
577 _mousePos.x - (_grabCursorSprite->w / 2),
578 _mousePos.y - (_grabCursorSprite->h / 2),
579 300);
580 delayTicks(5);
581 }
582
freeGrabCursorSprite()583 void GnapEngine::freeGrabCursorSprite() {
584 if (_grabCursorSprite) {
585 _gameSys->removeSpriteDrawItem(_grabCursorSprite, 300);
586 _gameSys->removeSpriteDrawItem(_grabCursorSprite, 301);
587 delayTicks(5);
588 deleteSurface(&_grabCursorSprite);
589 }
590 }
591
updateGrabCursorSprite(int x,int y)592 void GnapEngine::updateGrabCursorSprite(int x, int y) {
593 if (_grabCursorSprite) {
594 int newGrabCursorX = _mousePos.x - (_grabCursorSprite->w / 2) - x;
595 int newGrabCursorY = _mousePos.y - (_grabCursorSprite->h / 2) - y;
596 if (_currGrabCursorX != newGrabCursorX || _currGrabCursorY != newGrabCursorY) {
597 _currGrabCursorX = newGrabCursorX;
598 _currGrabCursorY = newGrabCursorY;
599 Common::Rect rect(newGrabCursorX, newGrabCursorY,
600 newGrabCursorX + _grabCursorSprite->w, newGrabCursorY + _grabCursorSprite->h);
601 _gameSys->invalidateGrabCursorSprite(300, rect, _grabCursorSprite, _grabCursorSprite);
602 }
603 }
604 }
605
invClear()606 void GnapEngine::invClear() {
607 _inventory = 0;
608 }
609
invAdd(int itemId)610 void GnapEngine::invAdd(int itemId) {
611 _inventory |= (1 << itemId);
612 }
613
invRemove(int itemId)614 void GnapEngine::invRemove(int itemId) {
615 _inventory &= ~(1 << itemId);
616 }
617
invHas(int itemId)618 bool GnapEngine::invHas(int itemId) {
619 return (_inventory & (1 << itemId)) != 0;
620 }
621
clearFlags()622 void GnapEngine::clearFlags() {
623 _gameFlags = 0;
624 }
625
setFlag(int num)626 void GnapEngine::setFlag(int num) {
627 _gameFlags |= (1 << num);
628 }
629
clearFlag(int num)630 void GnapEngine::clearFlag(int num) {
631 _gameFlags &= ~(1 << num);
632 }
633
isFlag(int num)634 bool GnapEngine::isFlag(int num) {
635 return (_gameFlags & (1 << num)) != 0;
636 }
637
addFullScreenSprite(int resourceId,int id)638 Graphics::Surface *GnapEngine::addFullScreenSprite(int resourceId, int id) {
639 _fullScreenSpriteId = id;
640 _fullScreenSprite = _gameSys->createSurface(resourceId);
641 _gameSys->insertSpriteDrawItem(_fullScreenSprite, 0, 0, id);
642 return _fullScreenSprite;
643 }
644
removeFullScreenSprite()645 void GnapEngine::removeFullScreenSprite() {
646 _gameSys->removeSpriteDrawItem(_fullScreenSprite, _fullScreenSpriteId);
647 deleteSurface(&_fullScreenSprite);
648 }
649
showFullScreenSprite(int resourceId)650 void GnapEngine::showFullScreenSprite(int resourceId) {
651 hideCursor();
652 setGrabCursorSprite(-1);
653 addFullScreenSprite(resourceId, 256);
654 while (!_mouseClickState._left && !isKeyStatus1(Common::KEYCODE_ESCAPE)
655 && !isKeyStatus1(Common::KEYCODE_SPACE) && !isKeyStatus1(Common::KEYCODE_RETURN) && !_gameDone) {
656 gameUpdateTick();
657 }
658 _mouseClickState._left = false;
659 clearKeyStatus1(Common::KEYCODE_ESCAPE);
660 clearKeyStatus1(Common::KEYCODE_RETURN);
661 clearKeyStatus1(Common::KEYCODE_SPACE);
662 removeFullScreenSprite();
663 showCursor();
664 }
665
queueInsertDeviceIcon()666 void GnapEngine::queueInsertDeviceIcon() {
667 _gameSys->insertSequence(0x10849, 20, 0, 0, kSeqNone, 0, _deviceX1, _deviceY1);
668 }
669
insertDeviceIconActive()670 void GnapEngine::insertDeviceIconActive() {
671 _gameSys->insertSequence(0x1084A, 21, 0, 0, kSeqNone, 0, _deviceX1, _deviceY1);
672 }
673
removeDeviceIconActive()674 void GnapEngine::removeDeviceIconActive() {
675 _gameSys->removeSequence(0x1084A, 21, true);
676 }
677
setDeviceHotspot(int hotspotIndex,int x1,int y1,int x2,int y2)678 void GnapEngine::setDeviceHotspot(int hotspotIndex, int x1, int y1, int x2, int y2) {
679 _deviceX1 = x1;
680 _deviceY1 = y1;
681 int deviceX2 = x2;
682 int deviceY2 = y2;
683 if (x1 == -1)
684 _deviceX1 = 730;
685 if (x2 == -1)
686 deviceX2 = 780;
687 if (y1 == -1)
688 _deviceY1 = 14;
689 if (y2 == -1)
690 deviceY2 = 79;
691
692 _hotspots[hotspotIndex]._rect = Common::Rect(_deviceX1, _deviceY1, deviceX2, deviceY2);
693 _hotspots[hotspotIndex]._flags = SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR;
694 }
695
getSequenceTotalDuration(int resourceId)696 int GnapEngine::getSequenceTotalDuration(int resourceId) {
697 SequenceResource *sequenceResource = _sequenceCache->get(resourceId);
698 int maxValue = 0;
699 for (int i = 0; i < sequenceResource->_animationsCount; ++i) {
700 SequenceAnimation *animation = &sequenceResource->_animations[i];
701 if (animation->_additionalDelay + animation->_maxTotalDuration > maxValue)
702 maxValue = animation->_additionalDelay + animation->_maxTotalDuration;
703 }
704 int totalDuration = maxValue + sequenceResource->_totalDuration;
705 _sequenceCache->release(resourceId);
706 return totalDuration;
707 }
708
isSoundPlaying(int resourceId)709 bool GnapEngine::isSoundPlaying(int resourceId) {
710 return _soundMan->isSoundPlaying(resourceId);
711 }
712
playSound(int resourceId,bool looping)713 void GnapEngine::playSound(int resourceId, bool looping) {
714 debugC(kDebugBasic, "playSound(%08X, %d)", resourceId, looping);
715 _soundMan->playSound(resourceId, looping);
716 }
717
stopSound(int resourceId)718 void GnapEngine::stopSound(int resourceId) {
719 _soundMan->stopSound(resourceId);
720 }
721
setSoundVolume(int resourceId,int volume)722 void GnapEngine::setSoundVolume(int resourceId, int volume) {
723 _soundMan->setSoundVolume(resourceId, volume);
724 }
725
updateTimers()726 void GnapEngine::updateTimers() {
727 for (int i = 0; i < kMaxTimers; ++i)
728 if (_timers[i] > 0)
729 --_timers[i];
730 }
731
initGameFlags(int num)732 void GnapEngine::initGameFlags(int num) {
733 invClear();
734 invAdd(kItemMagazine);
735 switch (num) {
736 case 1:
737 setFlag(kGFPlatypusTalkingToAssistant);
738 break;
739 case 2:
740 clearFlags();
741 break;
742 case 3:
743 invAdd(kItemDiceQuarterHole);
744 clearFlags();
745 break;
746 case 4:
747 invAdd(kItemDiceQuarterHole);
748 invAdd(kItemHorn);
749 invAdd(kItemLightbulb);
750 clearFlags();
751 setFlag(kGFPlatypus);
752 setFlag(kGFMudTaken);
753 setFlag(kGFNeedleTaken);
754 setFlag(kGFTwigTaken);
755 setFlag(kGFUnk04);
756 setFlag(kGFKeysTaken);
757 setFlag(kGFGrassTaken);
758 setFlag(kGFBarnPadlockOpen);
759 break;
760 }
761 }
762
loadStockDat()763 void GnapEngine::loadStockDat() {
764 if (!_isStockDatLoaded) {
765 _isStockDatLoaded = true;
766 _dat->open(1, "stock_n.dat");
767 // The pre-loading of data is skipped as it's no longer required on modern hardware
768 }
769 }
770
mainLoop()771 void GnapEngine::mainLoop() {
772 _newCursorValue = 1;
773 _cursorValue = -1;
774 _newSceneNum = 0;
775 _currentSceneNum = 55;
776 _prevSceneNum = 55;
777 invClear();
778 clearFlags();
779 _grabCursorSpriteIndex = -1;
780 _grabCursorSprite = nullptr;
781
782 loadStockDat();
783
784 if (_loadGameSlot != -1) {
785 // Load a savegame
786 int slot = _loadGameSlot;
787 _loadGameSlot = -1;
788 loadGameState(slot);
789 _wasSavegameLoaded = true;
790
791 showCursor();
792 }
793
794 while (!_gameDone) {
795 debugC(kDebugBasic, "New scene: %d", _newSceneNum);
796
797 _prevSceneNum = _currentSceneNum;
798 _currentSceneNum = _newSceneNum;
799
800 debugC(kDebugBasic, "GnapEngine::mainLoop() _prevSceneNum: %d; _currentSceneNum: %d", _prevSceneNum, _currentSceneNum);
801
802 if (_newCursorValue != _cursorValue) {
803 debugC(kDebugBasic, "_newCursorValue: %d", _newCursorValue);
804 _cursorValue = _newCursorValue;
805 if (!_wasSavegameLoaded)
806 initGameFlags(_cursorValue);
807 }
808
809 _sceneSavegameLoaded = _wasSavegameLoaded;
810 _wasSavegameLoaded = false;
811
812 initScene();
813
814 runSceneLogic();
815 afterScene();
816
817 _soundMan->stopAll();
818
819 // Force purge all resources
820 _sequenceCache->purge(true);
821 _soundCache->purge(true);
822 _spriteCache->purge(true);
823 }
824
825 if (_backgroundSurface)
826 deleteSurface(&_backgroundSurface);
827
828 _dat->close(1);
829 }
830
initScene()831 void GnapEngine::initScene() {
832 Common::String datFilename;
833
834 _isLeavingScene = false;
835 _sceneDone = false;
836 _newSceneNum = 55;
837 _gnap->_actionStatus = -1;
838 _plat->_actionStatus = -1;
839 _gnap->initBrainPulseRndValue();
840 hideCursor();
841 clearAllKeyStatus1();
842 _mouseClickState._left = false;
843 _mouseClickState._right = false;
844 _sceneClickedHotspot = -1;
845
846 datFilename = Common::String::format("%s_n.dat", kSceneNames[_currentSceneNum]);
847
848 debugC(kDebugBasic, "GnapEngine::initScene() datFilename: %s", datFilename.c_str());
849
850 _dat->open(0, datFilename.c_str());
851
852 int backgroundId = initSceneLogic();
853
854 if (!_backgroundSurface) {
855 if (_currentSceneNum != 0)
856 _backgroundSurface = _gameSys->loadBitmap(makeRid(1, 0x8AA));
857 else
858 _backgroundSurface = _gameSys->loadBitmap(makeRid(0, backgroundId));
859 _gameSys->setBackgroundSurface(_backgroundSurface, 0, 500, 1, 1000);
860 }
861
862 if (_currentSceneNum != 0 && _currentSceneNum != 16 && _currentSceneNum != 47 &&
863 _currentSceneNum != 48 && _currentSceneNum != 54) {
864 _gameSys->drawBitmap(backgroundId);
865 }
866
867 if ((_cursorValue == 4 && isFlag(kGFGnapControlsToyUFO)) || _currentSceneNum == 41)
868 playSound(makeRid(1, 0x8F6), true);
869
870 }
871
endSceneInit()872 void GnapEngine::endSceneInit() {
873 showCursor();
874 if (_newGrabCursorSpriteIndex >= 0)
875 setGrabCursorSprite(_newGrabCursorSpriteIndex);
876 }
877
afterScene()878 void GnapEngine::afterScene() {
879 if (_gameDone)
880 return;
881
882 if (_newCursorValue == _cursorValue && _newSceneNum != 0 && _newSceneNum != 16 &&
883 _newSceneNum != 47 && _newSceneNum != 48 && _newSceneNum != 54 && _newSceneNum != 49 &&
884 _newSceneNum != 50 && _newSceneNum != 51 && _newSceneNum != 52)
885 _newGrabCursorSpriteIndex = _grabCursorSpriteIndex;
886 else
887 _newGrabCursorSpriteIndex = -1;
888
889 setGrabCursorSprite(-1);
890
891 _gameSys->requestClear2(false);
892 _gameSys->requestClear1();
893 _gameSys->waitForUpdate();
894
895 _gameSys->requestClear2(false);
896 _gameSys->requestClear1();
897 _gameSys->waitForUpdate();
898
899 screenEffect(0, 0, 0, 0);
900
901 _dat->close(0);
902
903 for (int animationIndex = 0; animationIndex < 12; ++animationIndex)
904 _gameSys->setAnimation(0, 0, animationIndex);
905
906 clearKeyStatus1(Common::KEYCODE_p);
907
908 _mouseClickState._left = false;
909 _mouseClickState._right = false;
910
911 }
912
checkGameKeys()913 void GnapEngine::checkGameKeys() {
914 if (isKeyStatus1(Common::KEYCODE_p)) {
915 clearKeyStatus1(Common::KEYCODE_p);
916 pauseGame();
917 updatePause();
918 }
919 }
920
startSoundTimerA(int timerIndex)921 void GnapEngine::startSoundTimerA(int timerIndex) {
922 _soundTimerIndexA = timerIndex;
923 _timers[timerIndex] = getRandom(50) + 100;
924 }
925
playSoundA()926 int GnapEngine::playSoundA() {
927 static const int kSoundIdsA[] = {
928 0x93E, 0x93F, 0x941, 0x942, 0x943, 0x944,
929 0x945, 0x946, 0x947, 0x948, 0x949
930 };
931
932 int soundId = -1;
933
934 if (!_timers[_soundTimerIndexA]) {
935 _timers[_soundTimerIndexA] = getRandom(50) + 100;
936 soundId = kSoundIdsA[getRandom(11)];
937 playSound(soundId | 0x10000, false);
938 }
939 return soundId;
940 }
941
startSoundTimerB(int timerIndex)942 void GnapEngine::startSoundTimerB(int timerIndex) {
943 _soundTimerIndexB = timerIndex;
944 _timers[timerIndex] = getRandom(50) + 150;
945 }
946
playSoundB()947 int GnapEngine::playSoundB() {
948 static const int kSoundIdsB[] = {
949 0x93D, 0x929, 0x92A, 0x92B, 0x92C, 0x92D,
950 0x92E, 0x92F, 0x930, 0x931, 0x932, 0x933,
951 0x934, 0x935, 0x936, 0x937, 0x938, 0x939,
952 0x93A
953 };
954
955 int soundId = -1;
956
957 if (!_timers[_soundTimerIndexB]) {
958 _timers[_soundTimerIndexB] = getRandom(50) + 150;
959 soundId = kSoundIdsB[getRandom(19)];
960 playSound(soundId | 0x10000, false);
961 }
962 return soundId;
963 }
964
startSoundTimerC(int timerIndex)965 void GnapEngine::startSoundTimerC(int timerIndex) {
966 _soundTimerIndexC = timerIndex;
967 _timers[timerIndex] = getRandom(50) + 150;
968 }
969
playSoundC()970 int GnapEngine::playSoundC() {
971 static const int kSoundIdsC[] = {
972 0x918, 0x91F, 0x920, 0x922, 0x923, 0x924,
973 0x926
974 };
975
976 int soundId = -1;
977
978 if (!_timers[_soundTimerIndexC]) {
979 _timers[_soundTimerIndexC] = getRandom(50) + 150;
980 soundId = kSoundIdsC[getRandom(7)];
981 playSound(soundId | 0x10000, false);
982 }
983 return soundId;
984 }
985
startIdleTimer(int timerIndex)986 void GnapEngine::startIdleTimer(int timerIndex) {
987 _idleTimerIndex = timerIndex;
988 _timers[timerIndex] = 3000;
989 }
990
updateIdleTimer()991 void GnapEngine::updateIdleTimer() {
992 if (!_timers[_idleTimerIndex]) {
993 _timers[_idleTimerIndex] = 3000;
994 _gameSys->insertSequence(0x1088B, 255, 0, 0, kSeqNone, 0, 0, 75);
995 }
996 }
997
screenEffect(int dir,byte r,byte g,byte b)998 void GnapEngine::screenEffect(int dir, byte r, byte g, byte b) {
999 int startVal = 0;
1000 if (dir == 1)
1001 startVal = 300;
1002
1003 for (int y = startVal; y < startVal + 300 && !_gameDone; y += 50) {
1004 _gameSys->fillSurface(nullptr, 0, y, 800, 50, r, g, b);
1005 _gameSys->fillSurface(nullptr, 0, 549 - y + 1, 800, 50, r, g, b);
1006 gameUpdateTick();
1007 _system->delayMillis(50);
1008 }
1009 }
1010
isKeyStatus1(int key)1011 bool GnapEngine::isKeyStatus1(int key) {
1012 return _keyPressState[key];
1013 }
1014
isKeyStatus2(int key)1015 bool GnapEngine::isKeyStatus2(int key) {
1016 return _keyDownState[key];
1017 }
1018
clearKeyStatus1(int key)1019 void GnapEngine::clearKeyStatus1(int key) {
1020 _keyPressState[key] = false;
1021 _keyDownState[key] = false;
1022 }
1023
clearAllKeyStatus1()1024 void GnapEngine::clearAllKeyStatus1() {
1025 memset(_keyPressState, 0, sizeof(_keyPressState));
1026 memset(_keyDownState, 0, sizeof(_keyDownState));
1027 }
1028
deleteSurface(Graphics::Surface ** surface)1029 void GnapEngine::deleteSurface(Graphics::Surface **surface) {
1030 if (surface && *surface) {
1031 (*surface)->free();
1032 delete *surface;
1033 *surface = nullptr;
1034 }
1035 }
1036
testWalk(int animationIndex,int someStatus,int gridX1,int gridY1,int gridX2,int gridY2)1037 bool GnapEngine::testWalk(int animationIndex, int someStatus, int gridX1, int gridY1, int gridX2, int gridY2) {
1038 if (_mouseClickState._left && someStatus == _gnap->_actionStatus) {
1039 _isLeavingScene = false;
1040 _gameSys->setAnimation(0, 0, animationIndex);
1041 _gnap->_actionStatus = -1;
1042 _plat->_actionStatus = -1;
1043 _gnap->walkTo(Common::Point(gridX1, gridY1), -1, -1, 1);
1044 _plat->walkTo(Common::Point(gridX2, gridY2), -1, -1, 1);
1045 _mouseClickState._left = false;
1046 return true;
1047 }
1048 return false;
1049 }
1050
doCallback(int callback)1051 void GnapEngine::doCallback(int callback) {
1052 switch (callback) {
1053 case 8:
1054 case 10:
1055 case 20:
1056 _scene->updateAnimationsCb();
1057 break;
1058 }
1059 }
1060
1061 ////////////////////////////////////////////////////////////////////////////////
1062
initGlobalSceneVars()1063 void GnapEngine::initGlobalSceneVars() {
1064 // Shared by scenes 17 && 18
1065 _s18GarbageCanPos = 8;
1066
1067 // Toy UFO
1068 _toyUfoId = 0;
1069 _toyUfoActionStatus = -1;
1070 _toyUfoX = 0;
1071 _toyUfoY = 50;
1072 }
1073
playSequences(int fullScreenSpriteId,int sequenceId1,int sequenceId2,int sequenceId3)1074 void GnapEngine::playSequences(int fullScreenSpriteId, int sequenceId1, int sequenceId2, int sequenceId3) {
1075 setGrabCursorSprite(-1);
1076 _gameSys->setAnimation(sequenceId2, _gnap->_id, 0);
1077 _gameSys->insertSequence(sequenceId2, _gnap->_id,
1078 makeRid(_gnap->_sequenceDatNum, _gnap->_sequenceId), _gnap->_id,
1079 kSeqSyncWait, 0, 15 * (5 * _gnap->_pos.x - 25), 48 * (_gnap->_pos.y - 8));
1080 _gnap->_sequenceId = sequenceId2;
1081 _gnap->_sequenceDatNum = 0;
1082 while (_gameSys->getAnimationStatus(0) != 2 && !_gameDone)
1083 gameUpdateTick();
1084 hideCursor();
1085 addFullScreenSprite(fullScreenSpriteId, 255);
1086 _gameSys->setAnimation(sequenceId1, 256, 0);
1087 _gameSys->insertSequence(sequenceId1, 256, 0, 0, kSeqNone, 0, 0, 0);
1088 while (_gameSys->getAnimationStatus(0) != 2 && !_gameDone)
1089 gameUpdateTick();
1090 _gameSys->setAnimation(sequenceId3, _gnap->_id, 0);
1091 _gameSys->insertSequence(sequenceId3, _gnap->_id,
1092 makeRid(_gnap->_sequenceDatNum, _gnap->_sequenceId), _gnap->_id,
1093 kSeqSyncWait, 0, 15 * (5 * _gnap->_pos.x - 25), 48 * (_gnap->_pos.y - 8));
1094 removeFullScreenSprite();
1095 showCursor();
1096 _gnap->_sequenceId = sequenceId3;
1097 }
1098
toyUfoSetStatus(int flagNum)1099 void GnapEngine::toyUfoSetStatus(int flagNum) {
1100 clearFlag(kGFUnk16);
1101 clearFlag(kGFJointTaken);
1102 clearFlag(kGFUnk18);
1103 clearFlag(kGFGroceryStoreHatTaken);
1104 setFlag(flagNum);
1105 }
1106
toyUfoGetSequenceId()1107 int GnapEngine::toyUfoGetSequenceId() {
1108 if (isFlag(kGFUnk16))
1109 return 0x84E;
1110 if (isFlag(kGFJointTaken))
1111 return 0x84B;
1112 if (isFlag(kGFUnk18))
1113 return 0x84D;
1114 if (isFlag(kGFGroceryStoreHatTaken))
1115 return 0x84C;
1116 return 0x84E;
1117 }
1118
toyUfoCheckTimer()1119 bool GnapEngine::toyUfoCheckTimer() {
1120 if (!isFlag(kGFGnapControlsToyUFO) || isFlag(kGFUnk18) || _timers[9] ||
1121 _toyUfoSequenceId == 0x870 || _toyUfoSequenceId == 0x871 || _toyUfoSequenceId == 0x872 || _toyUfoSequenceId == 0x873)
1122 return false;
1123 _sceneDone = true;
1124 _newSceneNum = 41;
1125 return true;
1126 }
1127
toyUfoFlyTo(int destX,int destY,int minX,int maxX,int minY,int maxY,int animationIndex)1128 void GnapEngine::toyUfoFlyTo(int destX, int destY, int minX, int maxX, int minY, int maxY, int animationIndex) {
1129 GridStruct flyNodes[34];
1130
1131 if (destX == -1)
1132 destX = _leftClickMouseX;
1133
1134 if (destY == -1)
1135 destY = _leftClickMouseY;
1136
1137 int clippedDestX = CLIP(destX, minX, maxX);
1138 int clippedDestY = CLIP(destY, minY, maxY);
1139 int dirX = 0, dirY = 0; // 0, -1 or 1
1140
1141 if (clippedDestX != _toyUfoX)
1142 dirX = (clippedDestX - _toyUfoX) / ABS(clippedDestX - _toyUfoX);
1143
1144 if (clippedDestY != _toyUfoY)
1145 dirY = (clippedDestY - _toyUfoY) / ABS(clippedDestY - _toyUfoY);
1146
1147 int deltaX = ABS(clippedDestX - _toyUfoX);
1148 int deltaY = ABS(clippedDestY - _toyUfoY);
1149
1150 int i = 0;
1151 if (deltaY > deltaX) {
1152 int flyDirYIncr = 32;
1153 int gridDistY = deltaY / flyDirYIncr;
1154 int curMove = 0;
1155 while (curMove < deltaY && i < 34) {
1156 if (gridDistY - 5 >= i) {
1157 flyDirYIncr = MIN(36, 8 * i + 8);
1158 } else {
1159 flyDirYIncr = MAX(6, flyDirYIncr - 3);
1160 }
1161 curMove += flyDirYIncr;
1162 flyNodes[i]._gridX1 = _toyUfoX + dirX * deltaX * curMove / deltaY;
1163 flyNodes[i]._gridY1 = _toyUfoY + dirY * curMove;
1164 ++i;
1165 }
1166 } else {
1167 int flyDirXIncr = 36;
1168 int gridDistX = deltaX / flyDirXIncr;
1169 int curMove = 0;
1170 while (curMove < deltaX && i < 34) {
1171 if (gridDistX - 5 >= i) {
1172 flyDirXIncr = MIN(38, 8 * i + 8);
1173 } else {
1174 flyDirXIncr = MAX(6, flyDirXIncr - 3);
1175 }
1176 curMove += flyDirXIncr;
1177 flyNodes[i]._gridX1 = _toyUfoX + dirX * curMove;
1178 flyNodes[i]._gridY1 = _toyUfoY + dirY * deltaY * curMove / deltaX;
1179 ++i;
1180 }
1181 }
1182
1183 int nodesCount = i - 1;
1184
1185 _toyUfoX = clippedDestX;
1186 _toyUfoY = clippedDestY;
1187
1188 if (nodesCount > 0) {
1189 int seqId = 0;
1190 if (isFlag(kGFUnk16))
1191 seqId = 0x867;
1192 else if (isFlag(kGFJointTaken))
1193 seqId = 0x84F;
1194 else if (isFlag(kGFUnk18))
1195 seqId = 0x85F;
1196 else if (isFlag(kGFGroceryStoreHatTaken))
1197 seqId = 0x857;
1198 else
1199 error("Unhandled flag in GnapEngine::toyUfoFlyTo(): 0x%x", _gameFlags);
1200 flyNodes[0]._sequenceId = seqId;
1201 flyNodes[0]._id = 0;
1202 _gameSys->insertSequence(seqId | 0x10000, 0,
1203 _toyUfoSequenceId | 0x10000, _toyUfoId,
1204 kSeqSyncWait, 0, flyNodes[0]._gridX1 - 365, flyNodes[0]._gridY1 - 128);
1205 for (i = 1; i < nodesCount; ++i) {
1206 flyNodes[i]._sequenceId = seqId + (i % 8);
1207 flyNodes[i]._id = i;
1208 _gameSys->insertSequence(flyNodes[i]._sequenceId | 0x10000, flyNodes[i]._id,
1209 flyNodes[i - 1]._sequenceId | 0x10000, flyNodes[i - 1]._id,
1210 kSeqSyncWait, 0,
1211 flyNodes[i]._gridX1 - 365, flyNodes[i]._gridY1 - 128);
1212 }
1213
1214 _toyUfoSequenceId = flyNodes[nodesCount - 1]._sequenceId;
1215 _toyUfoId = flyNodes[nodesCount - 1]._id;
1216
1217 if (animationIndex >= 0)
1218 _gameSys->setAnimation(_toyUfoSequenceId | 0x10000, _toyUfoId, animationIndex);
1219
1220 }
1221 }
1222
playMidi(const char * name)1223 void GnapEngine::playMidi(const char *name) {
1224 if (_music)
1225 return;
1226
1227 _music = new MusicPlayer(name);
1228 _music->playSMF(true);
1229 }
1230
stopMidi()1231 void GnapEngine::stopMidi() {
1232 if (_music) {
1233 _music->stop();
1234 delete _music;
1235 _music = nullptr;
1236 }
1237 }
1238 } // End of namespace Gnap
1239