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