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 "bladerunner/bladerunner.h"
24 
25 #include "bladerunner/actor.h"
26 #include "bladerunner/actor_dialogue_queue.h"
27 #include "bladerunner/ambient_sounds.h"
28 #include "bladerunner/audio_cache.h"
29 #include "bladerunner/audio_mixer.h"
30 #include "bladerunner/audio_player.h"
31 #include "bladerunner/audio_speech.h"
32 #include "bladerunner/chapters.h"
33 #include "bladerunner/combat.h"
34 #include "bladerunner/crimes_database.h"
35 #include "bladerunner/debugger.h"
36 #include "bladerunner/dialogue_menu.h"
37 #include "bladerunner/framelimiter.h"
38 #include "bladerunner/font.h"
39 #include "bladerunner/game_flags.h"
40 #include "bladerunner/game_info.h"
41 #include "bladerunner/image.h"
42 #include "bladerunner/item_pickup.h"
43 #include "bladerunner/items.h"
44 #include "bladerunner/lights.h"
45 #include "bladerunner/mouse.h"
46 #include "bladerunner/music.h"
47 #include "bladerunner/outtake.h"
48 #include "bladerunner/obstacles.h"
49 #include "bladerunner/overlays.h"
50 #include "bladerunner/regions.h"
51 #include "bladerunner/savefile.h"
52 #include "bladerunner/scene.h"
53 #include "bladerunner/scene_objects.h"
54 #include "bladerunner/screen_effects.h"
55 #include "bladerunner/set.h"
56 #include "bladerunner/script/ai_script.h"
57 #include "bladerunner/script/init_script.h"
58 #include "bladerunner/script/kia_script.h"
59 #include "bladerunner/script/police_maze.h"
60 #include "bladerunner/script/scene_script.h"
61 #include "bladerunner/settings.h"
62 #include "bladerunner/shape.h"
63 #include "bladerunner/slice_animations.h"
64 #include "bladerunner/slice_renderer.h"
65 #include "bladerunner/subtitles.h"
66 #include "bladerunner/suspects_database.h"
67 #include "bladerunner/text_resource.h"
68 #include "bladerunner/time.h"
69 #include "bladerunner/ui/elevator.h"
70 #include "bladerunner/ui/end_credits.h"
71 #include "bladerunner/ui/esper.h"
72 #include "bladerunner/ui/kia.h"
73 #include "bladerunner/ui/scores.h"
74 #include "bladerunner/ui/spinner.h"
75 #include "bladerunner/ui/vk.h"
76 #include "bladerunner/vqa_decoder.h"
77 #include "bladerunner/waypoints.h"
78 #include "bladerunner/zbuffer.h"
79 
80 #include "common/array.h"
81 #include "common/config-manager.h"
82 #include "common/error.h"
83 #include "common/events.h"
84 #include "common/savefile.h"
85 #include "common/system.h"
86 #include "common/debug.h"
87 #include "common/debug-channels.h"
88 #include "common/translation.h"
89 #include "gui/message.h"
90 
91 #include "engines/util.h"
92 #include "engines/advancedDetector.h"
93 
94 #include "graphics/thumbnail.h"
95 #include "audio/mididrv.h"
96 
97 namespace BladeRunner {
98 
BladeRunnerEngine(OSystem * syst,const ADGameDescription * desc)99 BladeRunnerEngine::BladeRunnerEngine(OSystem *syst, const ADGameDescription *desc)
100 	: Engine(syst),
101 	  _rnd("bladerunner") {
102 
103 	_windowIsActive     = true;
104 	_gameIsRunning      = true;
105 	_gameJustLaunched   = true;
106 
107 	_vqaIsPlaying       = false;
108 	_vqaStopIsRequested = false;
109 
110 	_actorIsSpeaking           = false;
111 	_actorSpeakStopIsRequested = false;
112 
113 	_subtitlesEnabled             = false;
114 
115 	_surfaceFrontCreated          = false;
116 	_surfaceBackCreated           = false;
117 
118 	_sitcomMode                   = false;
119 	_shortyMode                   = false;
120 	_noDelayMillisFramelimiter    = false;
121 	_framesPerSecondMax           = false;
122 	_disableStaminaDrain          = false;
123 	_cutContent                   = Common::String(desc->gameId).contains("bladerunner-final");
124 	_validBootParam               = false;
125 
126 	_playerLosesControlCounter = 0;
127 
128 	_playerActorIdle = false;
129 	_playerDead      = false;
130 
131 	_gameOver               = false;
132 	_gameAutoSaveTextId     = -1;
133 	_gameIsAutoSaving       = false;
134 	_gameIsLoading          = false;
135 	_sceneIsLoading         = false;
136 
137 	_runningActorId         = -1;
138 	_isWalkingInterruptible = false;
139 	_interruptWalking       = false;
140 
141 	_walkSoundId      = -1;
142 	_walkSoundVolume  = 0;
143 	_walkSoundPan     = 0;
144 
145 	_language = desc->language;
146 	switch (desc->language) {
147 	case Common::EN_ANY:
148 		_languageCode = "E";
149 		break;
150 	case Common::DE_DEU:
151 		_languageCode = "G";
152 		break;
153 	case Common::FR_FRA:
154 		_languageCode = "F";
155 		break;
156 	case Common::IT_ITA:
157 		_languageCode = "I";
158 		break;
159 	case Common::RU_RUS:
160 		_languageCode = "E"; // Russian version is built on top of English one
161 		break;
162 	case Common::ES_ESP:
163 		_languageCode = "S";
164 		break;
165 	default:
166 		_languageCode = "E";
167 	}
168 
169 	_screenEffects           = nullptr;
170 	_combat                  = nullptr;
171 	_actorDialogueQueue      = nullptr;
172 	_settings                = nullptr;
173 	_itemPickup              = nullptr;
174 	_lights                  = nullptr;
175 	_obstacles               = nullptr;
176 	_sceneScript             = nullptr;
177 	_time                    = nullptr;
178 	_framelimiter            = nullptr;
179 	_gameInfo                = nullptr;
180 	_waypoints               = nullptr;
181 	_gameVars                = nullptr;
182 	_cosTable1024            = nullptr;
183 	_sinTable1024            = nullptr;
184 	_view                    = nullptr;
185 	_sceneObjects            = nullptr;
186 	_gameFlags               = nullptr;
187 	_items                   = nullptr;
188 	_audioCache              = nullptr;
189 	_audioMixer              = nullptr;
190 	_audioPlayer             = nullptr;
191 	_music                   = nullptr;
192 	_audioSpeech             = nullptr;
193 	_ambientSounds           = nullptr;
194 	_chapters                = nullptr;
195 	_overlays                = nullptr;
196 	_zbuffer                 = nullptr;
197 	_playerActor             = nullptr;
198 	_textActorNames          = nullptr;
199 	_textCrimes              = nullptr;
200 	_textClueTypes           = nullptr;
201 	_textKIA                 = nullptr;
202 	_textSpinnerDestinations = nullptr;
203 	_textVK                  = nullptr;
204 	_textOptions             = nullptr;
205 	_dialogueMenu            = nullptr;
206 	_suspectsDatabase        = nullptr;
207 	_kia                     = nullptr;
208 	_endCredits              = nullptr;
209 	_spinner                 = nullptr;
210 	_scores                  = nullptr;
211 	_elevator                = nullptr;
212 	_mainFont                = nullptr;
213 	_subtitles               = nullptr;
214 	_esper                   = nullptr;
215 	_vk                      = nullptr;
216 	_policeMaze              = nullptr;
217 	_mouse                   = nullptr;
218 	_sliceAnimations         = nullptr;
219 	_sliceRenderer           = nullptr;
220 	_crimesDatabase          = nullptr;
221 	_scene                   = nullptr;
222 	_aiScripts               = nullptr;
223 	_shapes                  = nullptr;
224 	for (int i = 0; i != kActorCount; ++i) {
225 		_actors[i]           = nullptr;
226 	}
227 	_debugger                = nullptr;
228 
229 	walkingReset();
230 
231 	_actorUpdateCounter  = 0;
232 	_actorUpdateTimeLast = 0;
233 
234 	_currentKeyDown.keycode = Common::KEYCODE_INVALID;
235 	_keyRepeatTimeLast = 0;
236 	_keyRepeatTimeDelay = 0;
237 }
238 
~BladeRunnerEngine()239 BladeRunnerEngine::~BladeRunnerEngine() {
240 	shutdown();
241 }
242 
hasFeature(EngineFeature f) const243 bool BladeRunnerEngine::hasFeature(EngineFeature f) const {
244 	return
245 		f == kSupportsReturnToLauncher ||
246 		f == kSupportsLoadingDuringRuntime ||
247 		f == kSupportsSavingDuringRuntime;
248 }
249 
canLoadGameStateCurrently()250 bool BladeRunnerEngine::canLoadGameStateCurrently() {
251 	return
252 		playerHasControl() &&
253 		!_sceneScript->isInsideScript() &&
254 		!_aiScripts->isInsideScript() &&
255 		!_kia->isOpen() &&
256 		!_spinner->isOpen() &&
257 		!_vk->isOpen() &&
258 		!_elevator->isOpen();
259 }
260 
loadGameState(int slot)261 Common::Error BladeRunnerEngine::loadGameState(int slot) {
262 	Common::InSaveFile *saveFile = BladeRunner::SaveFileManager::openForLoading(_targetName, slot);
263 	if (saveFile == nullptr || saveFile->err()) {
264 		delete saveFile;
265 		return Common::kReadingFailed;
266 	}
267 
268 	BladeRunner::SaveFileHeader header;
269 	if (!BladeRunner::SaveFileManager::readHeader(*saveFile, header)) {
270 		error("Invalid savegame");
271 	}
272 
273 	setTotalPlayTime(header._playTime);
274 	// this essentially does something similar with setTotalPlayTime
275 	// reseting and updating Blade Runner's _pauseStart and offset before starting a loaded game
276 	_time->resetPauseStart();
277 
278 	loadGame(*saveFile, header._version);
279 
280 	delete saveFile;
281 
282 	return Common::kNoError;
283 }
284 
canSaveGameStateCurrently()285 bool BladeRunnerEngine::canSaveGameStateCurrently() {
286 	return
287 		playerHasControl() &&
288 		!_sceneScript->isInsideScript() &&
289 		!_aiScripts->isInsideScript() &&
290 		!_kia->isOpen() &&
291 		!_spinner->isOpen() &&
292 		!_vk->isOpen() &&
293 		!_elevator->isOpen();
294 }
295 
saveGameState(int slot,const Common::String & desc,bool isAutosave)296 Common::Error BladeRunnerEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
297 	Common::OutSaveFile *saveFile = BladeRunner::SaveFileManager::openForSaving(_targetName, slot);
298 	if (saveFile == nullptr || saveFile->err()) {
299 		delete saveFile;
300 		return Common::kReadingFailed;
301 	}
302 
303 	BladeRunner::SaveFileHeader header;
304 	header._name = desc;
305 	header._playTime = getTotalPlayTime();
306 
307 	BladeRunner::SaveFileManager::writeHeader(*saveFile, header);
308 	_time->pause();
309 	saveGame(*saveFile);
310 	_time->resume();
311 
312 	saveFile->finalize();
313 
314 	delete saveFile;
315 
316 	return Common::kNoError;
317 }
318 
pauseEngineIntern(bool pause)319 void BladeRunnerEngine::pauseEngineIntern(bool pause) {
320 	_mixer->pauseAll(pause);
321 }
322 
run()323 Common::Error BladeRunnerEngine::run() {
324 	Common::Array<Common::String> missingFiles;
325 	if (!checkFiles(missingFiles)) {
326 		Common::String missingFileStr = "";
327 		for (uint i = 0; i < missingFiles.size(); ++i) {
328 			if (i > 0) {
329 				missingFileStr += ", ";
330 			}
331 			missingFileStr += missingFiles[i];
332 		}
333 		// shutting down
334 		return Common::Error(Common::kNoGameDataFoundError, missingFileStr);
335 	}
336 
337 	Common::List<Graphics::PixelFormat> tmpSupportedFormatsList = g_system->getSupportedFormats();
338 	if (!tmpSupportedFormatsList.empty()) {
339 		_screenPixelFormat = tmpSupportedFormatsList.front();
340 	} else {
341 		// Workaround for reported issue whereby in the AndroidSDL port
342 		// some devices would crash with a segmentation fault due to an empty supported formats list.
343 		// TODO: A better fix for getSupportedFormats() - maybe figure why in only some device it might return an empty list
344 		//
345 		// Use this as a fallback format - Should be a format supported by Android port
346 		_screenPixelFormat = Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0);
347 	}
348 	debug("Using pixel format: %s", _screenPixelFormat.toString().c_str());
349 	initGraphics(640, 480, &_screenPixelFormat);
350 
351 	_system->showMouse(true);
352 
353 	bool hasSavegames = !SaveFileManager::list(getMetaEngine(), _targetName).empty();
354 
355 	if (!startup(hasSavegames)) {
356 		// shutting down
357 		return Common::Error(Common::kUnknownError, _("Failed to initialize resources"));
358 	}
359 
360 	// improvement: Use a do-while() loop to handle the normal end-game state
361 	// so that the game won't exit abruptly after end credits
362 	do {
363 		// additional code for gracefully handling end-game after _endCredits->show()
364 		_gameOver         = false;
365 		_gameIsRunning    = true;
366 		_gameJustLaunched = true;
367 		// reset ammo amounts
368 		_settings->reset();
369 		// need to clear kFlagKIAPrivacyAddon to remove Bob's Privacy Addon for KIA
370 		// so it won't appear here after end credits
371 		_gameFlags->reset(kFlagKIAPrivacyAddon);
372 		if (!playerHasControl()) {
373 			// force a player gains control
374 			playerGainsControl(true);
375 		}
376 		if (_mouse->isDisabled()) {
377 			// force a mouse enable here since otherwise, after end-game,
378 			// we need extra call(s) to mouse->enable to get the _disabledCounter to 0
379 			_mouse->enable(true);
380 		}
381 		// end of additional code for gracefully handling end-game
382 
383 		if (_validBootParam) {
384 			// clear the flag, so that after a possible game gameOver / end-game
385 			// it won't be true again; just to be safe and avoid potential side-effects
386 			_validBootParam = false;
387 		} else {
388 			if (ConfMan.hasKey("save_slot") && ConfMan.getInt("save_slot") != -1) {
389 				// when loading from ScummVM main menu, we should emulate
390 				// the Kia pause/resume in order to get a valid "current" time when the game
391 				// is actually loaded (assuming delays can be introduced by a popup warning dialogue)
392 				if (!_time->isLocked()) {
393 					_time->pause();
394 				}
395 				loadGameState(ConfMan.getInt("save_slot"));
396 				ConfMan.set("save_slot", "-1");
397 				if (_time->isLocked()) {
398 					_time->resume();
399 				}
400 			} else if (hasSavegames) {
401 				_kia->_forceOpen = true;
402 				_kia->open(kKIASectionLoad);
403 			}
404 		}
405 		// TODO: why is the game starting a new game here when everything is done in startup?
406 		//  else {
407 		// 	newGame(kGameDifficultyMedium);
408 		// }
409 		gameLoop();
410 
411 		_mouse->disable();
412 
413 		if (_gameOver) {
414 			// In the original game this created a single "END_GAME_STATE.END"
415 			// which had the a valid format of a save game but was never accessed
416 			// from the loading screen. (Due to the .END extension)
417 			// It was also a single file that was overwritten each time the player
418 			// finished the game.
419 			// Maybe its purpose was debugging (?) by renaming it to .SAV and also
420 			// for the game to "know" if the player has already finished the game at least once (?)
421 			// although that latter one seems not to be used for anything, or maybe it was planned
422 			// to be used for a sequel (?). We will never know.
423 			// Disabling as in current state it will only fill-up save slots
424 			// autoSaveGame(4, true);
425 			_endCredits->show();
426 		}
427 	} while (_gameOver); // if main game loop ended and _gameOver == false, then shutdown
428 
429 	// shutting down
430 	return Common::kNoError;
431 }
432 
checkFiles(Common::Array<Common::String> & missingFiles)433 bool BladeRunnerEngine::checkFiles(Common::Array<Common::String> &missingFiles) {
434 	missingFiles.clear();
435 
436 	Common::Array<Common::String> requiredFiles;
437 	requiredFiles.push_back("1.TLK");
438 	requiredFiles.push_back("2.TLK");
439 	requiredFiles.push_back("3.TLK");
440 	requiredFiles.push_back("A.TLK");
441 	requiredFiles.push_back("COREANIM.DAT");
442 	requiredFiles.push_back("MODE.MIX");
443 	requiredFiles.push_back("MUSIC.MIX");
444 	requiredFiles.push_back("OUTTAKE1.MIX");
445 	requiredFiles.push_back("OUTTAKE2.MIX");
446 	requiredFiles.push_back("OUTTAKE3.MIX");
447 	requiredFiles.push_back("OUTTAKE4.MIX");
448 	requiredFiles.push_back("SFX.MIX");
449 	requiredFiles.push_back("SPCHSFX.TLK");
450 	requiredFiles.push_back("STARTUP.MIX");
451 	requiredFiles.push_back("VQA1.MIX");
452 	requiredFiles.push_back("VQA2.MIX");
453 	requiredFiles.push_back("VQA3.MIX");
454 
455 	for (uint i = 0; i < requiredFiles.size(); ++i) {
456 		if (!Common::File::exists(requiredFiles[i])) {
457 			missingFiles.push_back(requiredFiles[i]);
458 		}
459 	}
460 
461 	bool hasHdFrames = Common::File::exists("HDFRAMES.DAT");
462 
463 	if (!hasHdFrames) {
464 		requiredFiles.clear();
465 		requiredFiles.push_back("CDFRAMES1.DAT");
466 		requiredFiles.push_back("CDFRAMES2.DAT");
467 		requiredFiles.push_back("CDFRAMES3.DAT");
468 		requiredFiles.push_back("CDFRAMES4.DAT");
469 
470 		for (uint i = 0; i < requiredFiles.size(); ++i) {
471 			if (!Common::File::exists(requiredFiles[i])) {
472 				missingFiles.push_back(requiredFiles[i]);
473 			}
474 		}
475 	}
476 
477 	return missingFiles.empty();
478 }
479 
startup(bool hasSavegames)480 bool BladeRunnerEngine::startup(bool hasSavegames) {
481 	// Assign default values to the ScummVM configuration manager, in case settings are missing
482 	ConfMan.registerDefault("subtitles", "true");
483 	ConfMan.registerDefault("sfx_volume", 192);
484 	ConfMan.registerDefault("music_volume", 192);
485 	ConfMan.registerDefault("speech_volume", 192);
486 	ConfMan.registerDefault("mute", "false");
487 	ConfMan.registerDefault("speech_mute", "false");
488 	ConfMan.registerDefault("sitcom", "false");
489 	ConfMan.registerDefault("shorty", "false");
490 	ConfMan.registerDefault("nodelaymillisfl", "false");
491 	ConfMan.registerDefault("frames_per_secondfl", "false");
492 	ConfMan.registerDefault("disable_stamina_drain", "false");
493 
494 	_sitcomMode                = ConfMan.getBool("sitcom");
495 	_shortyMode                = ConfMan.getBool("shorty");
496 	_noDelayMillisFramelimiter = ConfMan.getBool("nodelaymillisfl");
497 	_framesPerSecondMax        = ConfMan.getBool("frames_per_secondfl");
498 	_disableStaminaDrain       = ConfMan.getBool("disable_stamina_drain");
499 
500 	// These are static objects in original game
501 	_screenEffects = new ScreenEffects(this, 0x8000);
502 
503 	_endCredits = new EndCredits(this);
504 
505 	_actorDialogueQueue = new ActorDialogueQueue(this);
506 
507 	_settings = new Settings(this);
508 
509 	_itemPickup = new ItemPickup(this);
510 
511 	_lights = new Lights(this);
512 
513 	// outtake player was initialized here in the original game - but this is done bit differently
514 
515 	_obstacles = new Obstacles(this);
516 
517 	_sceneScript = new SceneScript(this);
518 
519 	_debugger = new Debugger(this);
520 	setDebugger(_debugger);
521 
522 	// This is the original startup in the game
523 
524 	_surfaceFront.create(640, 480, screenPixelFormat());
525 	_surfaceFrontCreated = true;
526 	_surfaceBack.create(640, 480, screenPixelFormat());
527 	_surfaceBackCreated = true;
528 
529 	_time = new Time(this);
530 
531 //	debug("_framesPerSecondMax:: %s", _framesPerSecondMax? "true" : "false");
532 	_framelimiter = new Framelimiter(this, _framesPerSecondMax? 120 : 60);
533 
534 	// Try to load the SUBTITLES.MIX first, before Startup.MIX
535 	// allows overriding any identically named resources (such as the original font files and as a bonus also the TRE files for the UI and dialogue menu)
536 	_subtitles = new Subtitles(this);
537 	if (MIXArchive::exists("SUBTITLES.MIX")) {
538 		bool r = openArchive("SUBTITLES.MIX");
539 		if (!r)
540 			return false;
541 
542 		_subtitles->init();
543 	} else {
544 		debug("Download SUBTITLES.MIX from ScummVM's website to enable subtitles");
545 	}
546 
547 	bool r = openArchive("STARTUP.MIX");
548 	if (!r)
549 		return false;
550 
551 	_gameInfo = new GameInfo(this);
552 	if (!_gameInfo)
553 		return false;
554 
555 	r = _gameInfo->open("GAMEINFO.DAT");
556 	if (!r) {
557 		return false;
558 	}
559 
560 	if (hasSavegames) {
561 		if (!loadSplash()) {
562 			return false;
563 		}
564 	}
565 
566 	_waypoints = new Waypoints(this, _gameInfo->getWaypointCount());
567 
568 	_combat = new Combat(this);
569 
570 	_gameVars = new int[_gameInfo->getGlobalVarCount()]();
571 
572 	// Seed rand
573 
574 	_cosTable1024 = new Common::CosineTable(1024); // 10-bits = 1024 points for 2*PI;
575 	_sinTable1024 = new Common::SineTable(1024);
576 
577 	_view = new View();
578 
579 	_sceneObjects = new SceneObjects(this, _view);
580 
581 	_gameFlags = new GameFlags();
582 	_gameFlags->setFlagCount(_gameInfo->getFlagCount());
583 
584 	_items = new Items(this);
585 
586 	_audioCache = new AudioCache();
587 
588 	_audioMixer = new AudioMixer(this);
589 
590 	_audioPlayer = new AudioPlayer(this);
591 
592 	_music = new Music(this);
593 
594 	_audioSpeech = new AudioSpeech(this);
595 
596 	_ambientSounds = new AmbientSounds(this);
597 
598 	// Query the selected music device (defaults to MT_AUTO device).
599 	Common::String selDevStr = ConfMan.hasKey("music_driver") ? ConfMan.get("music_driver") : Common::String("auto");
600 	MidiDriver::DeviceHandle dev = MidiDriver::getDeviceHandle(selDevStr.empty() ? Common::String("auto") : selDevStr);
601 	//
602 	// We just respect the "No Music" choice (or an invalid choice)
603 	//
604 	// We're lenient with all the invalid/ irrelevant choices in the Audio Driver dropdown
605 	// TODO Ideally these controls (OptionsDialog::addAudioControls()) ie. "Music Device" and "Adlib Emulator"
606 	//      should not appear in games like Blade Runner, since they are largely irrelevant
607 	//      and may cause confusion when combined/ conflicting with the global settings
608 	//      which are by default applied, if the user does not explicitly override them.
609 	_noMusicDriver = (MidiDriver::getMusicType(dev) == MT_NULL || MidiDriver::getMusicType(dev) == MT_INVALID);
610 
611 	// BLADE.INI was read here, but it was replaced by ScummVM configuration
612 	//
613 	syncSoundSettings();
614 
615 	_chapters = new Chapters(this);
616 	if (!_chapters)
617 		return false;
618 
619 	if (!openArchive("MUSIC.MIX"))
620 		return false;
621 
622 	if (!openArchive("SFX.MIX"))
623 		return false;
624 
625 	if (!openArchive("SPCHSFX.TLK"))
626 		return false;
627 
628 	_overlays = new Overlays(this);
629 	_overlays->init();
630 
631 	_zbuffer = new ZBuffer();
632 	_zbuffer->init(640, 480);
633 
634 	int actorCount = (int)_gameInfo->getActorCount();
635 	assert(actorCount < kActorCount);
636 	for (int i = 0; i != actorCount; ++i) {
637 		_actors[i] = new Actor(this, i);
638 	}
639 	_actors[kActorVoiceOver] = new Actor(this, kActorVoiceOver);
640 	_playerActor = _actors[_gameInfo->getPlayerId()];
641 
642 	_playerActor->setFPS(15); // this seems redundant
643 #if BLADERUNNER_ORIGINAL_BUGS
644 	_playerActor->timerStart(kActorTimerRunningStaminaFPS, 200);
645 #else
646 	// Make code here similar to the bugfix in newGame in that
647 	// we only start the timer in vanilla game mode (not Restored Content mode)
648 	if (!_cutContent) {
649 		_playerActor->timerStart(kActorTimerRunningStaminaFPS, 200);
650 	}
651 #endif // BLADERUNNER_ORIGINAL_BUGS
652 
653 	_policeMaze = new PoliceMaze(this);
654 
655 	_textActorNames = new TextResource(this);
656 	if (!_textActorNames->open("ACTORS"))
657 		return false;
658 
659 	_textCrimes = new TextResource(this);
660 	if (!_textCrimes->open("CRIMES"))
661 		return false;
662 
663 	_textClueTypes = new TextResource(this);
664 	if (!_textClueTypes->open("CLUETYPE"))
665 		return false;
666 
667 	_textKIA = new TextResource(this);
668 	if (!_textKIA->open("KIA"))
669 		return false;
670 
671 	_textSpinnerDestinations = new TextResource(this);
672 	if (!_textSpinnerDestinations->open("SPINDEST"))
673 		return false;
674 
675 	_textVK = new TextResource(this);
676 	if (!_textVK->open("VK"))
677 		return false;
678 
679 	_textOptions = new TextResource(this);
680 	if (!_textOptions->open("OPTIONS"))
681 		return false;
682 
683 	_russianCP1251 = ((uint8)_textOptions->getText(0)[0]) == 209;
684 
685 	_dialogueMenu = new DialogueMenu(this);
686 	if (!_dialogueMenu->loadResources())
687 		return false;
688 
689 	_suspectsDatabase = new SuspectsDatabase(this, _gameInfo->getSuspectCount());
690 
691 	_kia = new KIA(this);
692 
693 	_spinner = new Spinner(this);
694 
695 	_elevator = new Elevator(this);
696 
697 	_scores = new Scores(this);
698 
699 	_mainFont = Font::load(this, "KIA6PT.FON", 1, false);
700 
701 	_shapes = new Shapes(this);
702 	_shapes->load("SHAPES.SHP");
703 
704 	_esper = new ESPER(this);
705 
706 	_vk = new VK(this);
707 
708 	_mouse = new Mouse(this);
709 	_mouse->setCursor(0);
710 
711 	_sliceAnimations = new SliceAnimations(this);
712 	r = _sliceAnimations->open("INDEX.DAT");
713 	if (!r)
714 		return false;
715 
716 	r = _sliceAnimations->openCoreAnim();
717 	if (!r) {
718 		return false;
719 	}
720 
721 	_sliceRenderer = new SliceRenderer(this);
722 	_sliceRenderer->setScreenEffects(_screenEffects);
723 
724 	_crimesDatabase = new CrimesDatabase(this, "CLUES", _gameInfo->getClueCount());
725 
726 	_scene = new Scene(this);
727 
728 	// Load INIT.DLL
729 	InitScript initScript(this);
730 	initScript.SCRIPT_Initialize_Game();
731 
732 	// Load AI-ACT1.DLL
733 	_aiScripts = new AIScripts(this, actorCount);
734 
735 	initChapterAndScene();
736 	return true;
737 }
738 
initChapterAndScene()739 void BladeRunnerEngine::initChapterAndScene() {
740 	for (int i = 0, end = _gameInfo->getActorCount(); i != end; ++i) {
741 		_aiScripts->initialize(i);
742 	}
743 
744 	for (int i = 0, end = _gameInfo->getActorCount(); i != end; ++i) {
745 		_actors[i]->changeAnimationMode(kAnimationModeIdle);
746 	}
747 
748 	for (int i = 1, end = _gameInfo->getActorCount(); i != end; ++i) { // skip first actor, probably player
749 		_actors[i]->movementTrackNext(true);
750 	}
751 
752 	if (ConfMan.hasKey("boot_param")) {
753 		int param = ConfMan.getInt("boot_param"); // CTTTSSS
754 		if (param < 1000000 || param >= 6000000) {
755 			debug("Invalid boot parameter. Valid format is: CTTTSSS");
756 		} else {
757 			int chapter = param / 1000000;
758 			param -= chapter * 1000000;
759 			int set = param / 1000;
760 			param -= set * 1000;
761 			int scene = param;
762 
763 			// init chapter to default first chapter (required by dbgAttemptToLoadChapterSetScene())
764 			_settings->setChapter(1);
765 			_validBootParam = _debugger->dbgAttemptToLoadChapterSetScene(chapter, set, scene);
766 			if (_validBootParam) {
767 				debug("Explicitly loading Chapter: %d Set: %d Scene: %d", chapter, set, scene);
768 			} else {
769 				debug("Invalid combination of Chapter Set and Scene ids");
770 			}
771 		}
772 	}
773 
774 	if (!_validBootParam) {
775 		_settings->setChapter(1);
776 		_settings->setNewSetAndScene(_gameInfo->getInitialSetId(), _gameInfo->getInitialSceneId());
777 	}
778 }
779 
shutdown()780 void BladeRunnerEngine::shutdown() {
781 	_mixer->stopAll();
782 
783 	// BLADE.INI as updated here
784 
785 	delete _aiScripts;
786 	_aiScripts = nullptr;
787 
788 	delete _scene;
789 	_scene = nullptr;
790 
791 	delete _crimesDatabase;
792 	_crimesDatabase = nullptr;
793 
794 	delete _sliceRenderer;
795 	_sliceRenderer = nullptr;
796 
797 	delete _sliceAnimations;
798 	_sliceAnimations = nullptr;
799 
800 	delete _mouse;
801 	_mouse = nullptr;
802 
803 	delete _vk;
804 	_vk = nullptr;
805 
806 	delete _esper;
807 	_esper = nullptr;
808 
809 	delete _shapes;
810 	_shapes = nullptr;
811 
812 	delete _mainFont;
813 	_mainFont = nullptr;
814 
815 	delete _scores;
816 	_scores = nullptr;
817 
818 	delete _elevator;
819 	_elevator = nullptr;
820 
821 	delete _spinner;
822 	_spinner = nullptr;
823 
824 	delete _kia;
825 	_kia = nullptr;
826 
827 	delete _suspectsDatabase;
828 	_suspectsDatabase = nullptr;
829 
830 	delete _dialogueMenu;
831 	_dialogueMenu = nullptr;
832 
833 	delete _textOptions;
834 	_textOptions = nullptr;
835 
836 	delete _textVK;
837 	_textVK = nullptr;
838 
839 	delete _textSpinnerDestinations;
840 	_textSpinnerDestinations = nullptr;
841 
842 	delete _textKIA;
843 	_textKIA = nullptr;
844 
845 	delete _textClueTypes;
846 	_textClueTypes = nullptr;
847 
848 	delete _textCrimes;
849 	_textCrimes = nullptr;
850 
851 	delete _textActorNames;
852 	_textActorNames = nullptr;
853 
854 	delete _policeMaze;
855 	_policeMaze = nullptr;
856 
857 	// don't delete _playerActor since that is handled
858 	// in the loop over _actors below
859 	_playerActor = nullptr;
860 
861 	delete _actors[kActorVoiceOver];
862 	_actors[kActorVoiceOver] = nullptr;
863 
864 	int actorCount = kActorCount;
865 	if (_gameInfo) {
866 		actorCount = (int)_gameInfo->getActorCount();
867 	}
868 
869 	for (int i = 0; i < actorCount; ++i) {
870 		delete _actors[i];
871 		_actors[i] = nullptr;
872 	}
873 
874 	delete _zbuffer;
875 	_zbuffer = nullptr;
876 
877 	delete _overlays;
878 	_overlays = nullptr;
879 
880 	if (isArchiveOpen("SPCHSFX.TLK")) {
881 		closeArchive("SPCHSFX.TLK");
882 	}
883 
884 #if BLADERUNNER_ORIGINAL_BUGS
885 #else
886 	if (isArchiveOpen("A.TLK")) {
887 		closeArchive("A.TLK");
888 	}
889 #endif // BLADERUNNER_ORIGINAL_BUGS
890 
891 	if (isArchiveOpen("SFX.MIX")) {
892 		closeArchive("SFX.MIX");
893 	}
894 
895 	if (isArchiveOpen("MUSIC.MIX")) {
896 		closeArchive("MUSIC.MIX");
897 	}
898 
899 	// in case player closes the ScummVM window when in ESPER mode or similar
900 	if (isArchiveOpen("MODE.MIX")) {
901 		closeArchive("MODE.MIX");
902 	}
903 
904 	if (_chapters && _chapters->hasOpenResources()) {
905 		_chapters->closeResources();
906 	}
907 	delete _chapters;
908 	_chapters = nullptr;
909 
910 	delete _ambientSounds;
911 	_ambientSounds = nullptr;
912 
913 	delete _audioSpeech;
914 	_audioSpeech = nullptr;
915 
916 	delete _music;
917 	_music = nullptr;
918 
919 	delete _audioPlayer;
920 	_audioPlayer = nullptr;
921 
922 	delete _audioMixer;
923 	_audioMixer = nullptr;
924 
925 	delete _audioCache;
926 	_audioCache = nullptr;
927 
928 	delete _items;
929 	_items = nullptr;
930 
931 	delete _gameFlags;
932 	_gameFlags = nullptr;
933 
934 	delete _sceneObjects;
935 	_sceneObjects = nullptr;
936 
937 	delete _view;
938 	_view = nullptr;
939 
940 	delete _sinTable1024;
941 	_sinTable1024 = nullptr;
942 	delete _cosTable1024;
943 	_cosTable1024 = nullptr;
944 
945 	delete[] _gameVars;
946 	_gameVars = nullptr;
947 
948 	delete _combat;
949 	_combat = nullptr;
950 
951 	delete _waypoints;
952 	_waypoints = nullptr;
953 
954 	delete _gameInfo;
955 	_gameInfo = nullptr;
956 
957 	if (isArchiveOpen("STARTUP.MIX")) {
958 		closeArchive("STARTUP.MIX");
959 	}
960 
961 	if (isArchiveOpen("SUBTITLES.MIX")) {
962 		closeArchive("SUBTITLES.MIX");
963 	}
964 
965 	delete _subtitles;
966 	_subtitles = nullptr;
967 
968 	delete _framelimiter;
969 	_framelimiter = nullptr;
970 
971 	delete _time;
972 	_time = nullptr;
973 
974 	// guard the free() call to Surface::free() will boolean flags
975 	// since according to free() documentation:
976 	// it should only be used, when "the Surface data was created via
977 	// create! Otherwise this function has undefined behavior."
978 	if (_surfaceBackCreated)
979 		_surfaceBack.free();
980 
981 	if (_surfaceFrontCreated)
982 		_surfaceFront.free();
983 
984 	// These are static objects in original game
985 
986 	//delete _debugger;	Debugger deletion is handled by Engine
987 	_debugger = nullptr;
988 
989 	delete _sceneScript;
990 	_sceneScript = nullptr;
991 
992 	delete _obstacles;
993 	_obstacles = nullptr;
994 
995 	delete _lights;
996 	_lights = nullptr;
997 
998 	delete _itemPickup;
999 	_itemPickup = nullptr;
1000 
1001 	delete _settings;
1002 	_settings = nullptr;
1003 
1004 	delete _actorDialogueQueue;
1005 	_actorDialogueQueue = nullptr;
1006 
1007 	delete _endCredits;
1008 	_endCredits = nullptr;
1009 
1010 	delete _screenEffects;
1011 	_screenEffects = nullptr;
1012 }
1013 
loadSplash()1014 bool BladeRunnerEngine::loadSplash() {
1015 	Image img(this);
1016 	if (!img.open("SPLASH.IMG")) {
1017 		return false;
1018 	}
1019 
1020 	img.copyToSurface(&_surfaceFront);
1021 
1022 	blitToScreen(_surfaceFront);
1023 
1024 	return true;
1025 }
1026 
getMousePos() const1027 Common::Point BladeRunnerEngine::getMousePos() const {
1028 	Common::Point p = _eventMan->getMousePos();
1029 	p.x = CLIP(p.x, int16(0), int16(639));
1030 	p.y = CLIP(p.y, int16(0), int16(479));
1031 	return p;
1032 }
1033 
isMouseButtonDown() const1034 bool BladeRunnerEngine::isMouseButtonDown() const {
1035 	return _eventMan->getButtonState() != 0;
1036 }
1037 
gameLoop()1038 void BladeRunnerEngine::gameLoop() {
1039 	_gameIsRunning = true;
1040 	do {
1041 		if (_playerDead) {
1042 			playerDied();
1043 			_playerDead = false;
1044 		}
1045 		gameTick();
1046 	} while (_gameIsRunning);
1047 }
1048 
gameTick()1049 void BladeRunnerEngine::gameTick() {
1050 	handleEvents();
1051 
1052 	if (!_gameIsRunning || !_windowIsActive) {
1053 		return;
1054 	}
1055 
1056 	if (!_kia->isOpen() && !_sceneScript->isInsideScript() && !_aiScripts->isInsideScript()) {
1057 		if (!_settings->openNewScene()) {
1058 			Common::Error runtimeError = Common::Error(Common::kUnknownError, _("A required game resource was not found"));
1059 			GUI::MessageDialog dialog(runtimeError.getDesc());
1060 			dialog.runModal();
1061 			return;
1062 		}
1063 	}
1064 
1065 	if (_gameAutoSaveTextId >= 0) {
1066 		autoSaveGame(_gameAutoSaveTextId, false);
1067 		_gameAutoSaveTextId = -1;
1068 	}
1069 
1070 	//probably not needed, this version of tick is just loading data from buffer
1071 	//_audioMixer->tick();
1072 
1073 	if (_kia->isOpen()) {
1074 		_kia->tick();
1075 		return;
1076 	}
1077 
1078 	if (_spinner->isOpen()) {
1079 		_spinner->tick();
1080 		_ambientSounds->tick();
1081 		return;
1082 	}
1083 
1084 	if (_esper->isOpen()) {
1085 		_esper->tick();
1086 		return;
1087 	}
1088 
1089 	if (_vk->isOpen()) {
1090 		_vk->tick();
1091 		_ambientSounds->tick();
1092 		return;
1093 	}
1094 
1095 	if (_elevator->isOpen()) {
1096 		_elevator->tick();
1097 		_ambientSounds->tick();
1098 		return;
1099 	}
1100 
1101 	if (_scores->isOpen()) {
1102 		_scores->tick();
1103 		_ambientSounds->tick();
1104 		return;
1105 	}
1106 
1107 	_actorDialogueQueue->tick();
1108 	if (_scene->didPlayerWalkIn()) {
1109 		_sceneScript->playerWalkedIn();
1110 	}
1111 
1112 	bool inDialogueMenu = _dialogueMenu->isVisible();
1113 	if  (!inDialogueMenu) {
1114 		for (int i = 0; i < (int)_gameInfo->getActorCount(); ++i) {
1115 			_actors[i]->tickCombat();
1116 		}
1117 	}
1118 
1119 	_policeMaze->tick();
1120 
1121 	_zbuffer->clean();
1122 
1123 	_ambientSounds->tick();
1124 
1125 	bool backgroundChanged = false;
1126 	int frame = _scene->advanceFrame();
1127 	if (frame >= 0) {
1128 //		debug("_sceneScript->sceneFrameAdvanced(%d)", frame);
1129 		_sceneScript->sceneFrameAdvanced(frame);
1130 		backgroundChanged = true;
1131 	}
1132 	blit(_surfaceBack, _surfaceFront);
1133 
1134 	_overlays->tick();
1135 
1136 	if (!inDialogueMenu) {
1137 		// TODO This is probably responsible for actors getting stuck in place
1138 		// after reaching a waypoint when dialoge menu is open
1139 		actorsUpdate();
1140 	}
1141 
1142 	if (_settings->getNewScene() != -1 && !_sceneScript->isInsideScript() && !_aiScripts->isInsideScript()) {
1143 		return;
1144 	}
1145 
1146 	_sliceRenderer->setView(_view);
1147 
1148 	// Tick and draw all actors in current set
1149 	int setId = _scene->getSetId();
1150 	for (int i = 0, end = _gameInfo->getActorCount(); i != end; ++i) {
1151 		if (_actors[i]->getSetId() == setId) {
1152 			Common::Rect screenRect;
1153 			if (_actors[i]->tick(backgroundChanged, &screenRect)) {
1154 				_zbuffer->mark(screenRect);
1155 			}
1156 		}
1157 	}
1158 
1159 	_items->tick();
1160 
1161 	_itemPickup->tick();
1162 	_itemPickup->draw();
1163 
1164 	Common::Point p = getMousePos();
1165 
1166 	if (_dialogueMenu->isVisible()) {
1167 		_dialogueMenu->tick(p.x, p.y);
1168 		_dialogueMenu->draw(_surfaceFront);
1169 	}
1170 
1171 	if (_debugger->_viewZBuffer) {
1172 		// The surface front pixel format is 32 bit now,
1173 		// but the _zbuffer->getData() still returns 16bit pixels
1174 		// We need to copy pixel by pixel, converting each pixel from 16 to 32bit
1175 		for (int y = 0; y < 480; ++y) {
1176 			for (int x = 0; x < 640; ++x) {
1177 				uint8 a, r, g, b;
1178 				getGameDataColor(_zbuffer->getData()[y*640 + x], a, r, g, b);
1179 				void   *dstPixel = _surfaceFront.getBasePtr(x, y);
1180 				drawPixel(_surfaceFront, dstPixel, _surfaceFront.format.ARGBToColor(a, r, g, b));
1181 			}
1182 		}
1183 	}
1184 
1185 	_mouse->tick(p.x, p.y);
1186 	_mouse->draw(_surfaceFront, p.x, p.y);
1187 
1188 	if (_walkSoundId >= 0) {
1189 		_audioPlayer->playAud(_gameInfo->getSfxTrack(_walkSoundId), _walkSoundVolume, _walkSoundPan, _walkSoundPan, 50, 0);
1190 		_walkSoundId = -1;
1191 	}
1192 
1193 	if (_debugger->_isDebuggerOverlay) {
1194 		_debugger->drawDebuggerOverlay();
1195 	}
1196 
1197 	if (_debugger->_viewObstacles) {
1198 		_obstacles->draw();
1199 	}
1200 
1201 	_subtitles->tick(_surfaceFront);
1202 
1203 	 // Without this condition the game may flash back to the game screen
1204 	 // between and ending outtake and the end credits.
1205 	if (!_gameOver) {
1206 		blitToScreen(_surfaceFront);
1207 	}
1208 }
1209 
actorsUpdate()1210 void BladeRunnerEngine::actorsUpdate() {
1211 #if BLADERUNNER_ORIGINAL_BUGS
1212 #else
1213 	uint32 timeNow = _time->current();
1214 	// Don't update actors more than 60 or 120 times per second
1215 	if (timeNow - _actorUpdateTimeLast < 1000u / ( _framesPerSecondMax? 120u : 60u)) {
1216 		return;
1217 	}
1218 	_actorUpdateTimeLast = timeNow;
1219 #endif // BLADERUNNER_ORIGINAL_BUGS
1220 
1221 	int actorCount = (int)_gameInfo->getActorCount();
1222 	int setId = _scene->getSetId();
1223 
1224 	// what a "nice" last minute fix...
1225 	if ( setId == kSetUG18
1226 	 && _gameVars[kVariableChapter] == 4
1227 	 && _gameFlags->query(kFlagCallWithGuzza)
1228 	 && _aiScripts->isInsideScript()
1229 	) {
1230 		return;
1231 	}
1232 
1233 	for (int i = 0; i < actorCount; ++i) {
1234 		Actor *actor = _actors[i];
1235 		if (actor->getSetId() == setId || i == _actorUpdateCounter) {
1236 			_aiScripts->update(i);
1237 			actor->timersUpdate();
1238 		}
1239 	}
1240 	++_actorUpdateCounter;
1241 	if (_actorUpdateCounter >= actorCount) {
1242 		_actorUpdateCounter = 0;
1243 	}
1244 }
1245 
walkingReset()1246 void BladeRunnerEngine::walkingReset() {
1247 	_mouseClickTimeLast   = 0;
1248 	_mouseClickTimeDiff   = 0;
1249 	_walkingToExitId      = -1;
1250 	_isInsideScriptExit   = false;
1251 	_walkingToRegionId    = -1;
1252 	_isInsideScriptRegion = false;
1253 	_walkingToObjectId    = -1;
1254 	_isInsideScriptObject = false;
1255 	_walkingToItemId      = -1;
1256 	_isInsideScriptItem   = false;
1257 	_walkingToEmpty       = false;
1258 	_walkingToEmptyX      = 0;
1259 	_walkingToEmptyY      = 0;
1260 	_isInsideScriptEmpty  = false;
1261 	_walkingToActorId     = -1;
1262 	_isInsideScriptActor  = false;
1263 }
1264 
handleEvents()1265 void BladeRunnerEngine::handleEvents() {
1266 	if (shouldQuit()) {
1267 		_gameIsRunning = false;
1268 		return;
1269 	}
1270 
1271 	// This flag check is to skip the first call of handleEvents() in gameTick().
1272 	// This prevents a "hack" whereby the player could press Esc quickly and enter the KIA screen,
1273 	// even in the case when no save games for the game exist. In such case the game is supposed
1274 	// to immediately play the intro video and subsequently start a new game of medium difficulty.
1275 	// It does not expect the player to enter KIA beforehand, which causes side-effects and unforeseen behavior.
1276 	// Note: eventually we will support the option to launch into KIA in any case,
1277 	// but not via the "hack" way that is fixed here.
1278 	if (_gameJustLaunched) {
1279 		_gameJustLaunched = false;
1280 		return;
1281 	}
1282 
1283 	Common::Event event;
1284 	Common::EventManager *eventMan = _system->getEventManager();
1285 	while (eventMan->pollEvent(event)) {
1286 		switch (event.type) {
1287 		case Common::EVENT_KEYUP:
1288 			handleKeyUp(event);
1289 			break;
1290 
1291 		case Common::EVENT_KEYDOWN:
1292 			// Process the actual key press only and filter out repeats
1293 			if (!event.kbdRepeat) {
1294 				// Only for Esc and Return keys, allow repeated firing emulation
1295 				// First hit (fire) has a bigger delay (kKeyRepeatInitialDelay) before repeated events are fired from the same key
1296 				if (event.kbd.keycode == Common::KEYCODE_ESCAPE || event.kbd.keycode == Common::KEYCODE_RETURN) {
1297 					_currentKeyDown = event.kbd.keycode;
1298 					_keyRepeatTimeLast =  _time->currentSystem();
1299 					_keyRepeatTimeDelay = kKeyRepeatInitialDelay;
1300 				}
1301 				handleKeyDown(event);
1302 			}
1303 			break;
1304 
1305 		case Common::EVENT_LBUTTONUP:
1306 			handleMouseAction(event.mouse.x, event.mouse.y, true, false);
1307 			break;
1308 
1309 		case Common::EVENT_RBUTTONUP:
1310 		case Common::EVENT_MBUTTONUP:
1311 			handleMouseAction(event.mouse.x, event.mouse.y, false, false);
1312 			break;
1313 
1314 		case Common::EVENT_LBUTTONDOWN:
1315 			handleMouseAction(event.mouse.x, event.mouse.y, true, true);
1316 			break;
1317 
1318 		case Common::EVENT_RBUTTONDOWN:
1319 		case Common::EVENT_MBUTTONDOWN:
1320 			handleMouseAction(event.mouse.x, event.mouse.y, false, true);
1321 			break;
1322 
1323 		// Added by ScummVM team
1324 		case Common::EVENT_WHEELUP:
1325 			handleMouseAction(event.mouse.x, event.mouse.y, false, false, -1);
1326 			break;
1327 
1328 		// Added by ScummVM team
1329 		case Common::EVENT_WHEELDOWN:
1330 			handleMouseAction(event.mouse.x, event.mouse.y, false, false, 1);
1331 			break;
1332 
1333 		default:
1334 			; // nothing to do
1335 		}
1336 	}
1337 
1338 	// The switch clause above handles multiple events.
1339 	// Some of those may lead to their own internal gameTick() loops (which will call handleEvents()).
1340 	// Thus, we need to get a new timeNow value here to ensure we're not comparing with a stale version.
1341 	uint32 timeNow = _time->currentSystem();
1342 	if ((_currentKeyDown == Common::KEYCODE_ESCAPE || _currentKeyDown == Common::KEYCODE_RETURN)
1343 	    && (timeNow - _keyRepeatTimeLast >= _keyRepeatTimeDelay)) {
1344 		// create a "new" keydown event
1345 		event.type = Common::EVENT_KEYDOWN;
1346 		// kbdRepeat field will be unused here since we emulate the kbd repeat behavior anyway, but it's good to set it for consistency
1347 		event.kbdRepeat = true;
1348 		event.kbd = _currentKeyDown;
1349 		_keyRepeatTimeLast = timeNow;
1350 		_keyRepeatTimeDelay = kKeyRepeatSustainDelay;
1351 		handleKeyDown(event);
1352 	}
1353 }
1354 
handleKeyUp(Common::Event & event)1355 void BladeRunnerEngine::handleKeyUp(Common::Event &event) {
1356 	if (event.kbd.keycode == _currentKeyDown.keycode) {
1357 		// Only stop firing events if it's the current key
1358 		_currentKeyDown.keycode = Common::KEYCODE_INVALID;
1359 	}
1360 
1361 	if (!playerHasControl() || _isWalkingInterruptible) {
1362 		return;
1363 	}
1364 
1365 	if (_kia->isOpen()) {
1366 		_kia->handleKeyUp(event.kbd);
1367 		return;
1368 	}
1369 }
1370 
handleKeyDown(Common::Event & event)1371 void BladeRunnerEngine::handleKeyDown(Common::Event &event) {
1372 	if (_vqaIsPlaying
1373 	    && (event.kbd.keycode == Common::KEYCODE_RETURN || event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
1374 		// Note: Original only uses the Esc key here
1375 		_vqaStopIsRequested = true;
1376 		_vqaIsPlaying = false;
1377 
1378 		return;
1379 	}
1380 
1381 	if (_vqaStopIsRequested
1382 	    && (event.kbd.keycode == Common::KEYCODE_RETURN || event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
1383 		 return;
1384 	}
1385 
1386 	if (_actorIsSpeaking
1387 	    && (event.kbd.keycode == Common::KEYCODE_RETURN || event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
1388 		// Note: Original only uses the Return key here
1389 		_actorSpeakStopIsRequested = true;
1390 		_actorIsSpeaking = false;
1391 
1392 		return;
1393 	}
1394 
1395 	if (_actorSpeakStopIsRequested
1396 	    && (event.kbd.keycode == Common::KEYCODE_RETURN || event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
1397 		 return;
1398 	}
1399 
1400 	if (!playerHasControl() || _isWalkingInterruptible || _actorIsSpeaking || _vqaIsPlaying) {
1401 		return;
1402 	}
1403 
1404 	if (_kia->isOpen()) {
1405 		_kia->handleKeyDown(event.kbd);
1406 		return;
1407 	}
1408 
1409 	if (_spinner->isOpen()) {
1410 		return;
1411 	}
1412 
1413 	if (_elevator->isOpen()) {
1414 		return;
1415 	}
1416 
1417 	if (_esper->isOpen()) {
1418 		return;
1419 	}
1420 
1421 	if (_vk->isOpen()) {
1422 		return;
1423 	}
1424 
1425 	if (_dialogueMenu->isOpen()) {
1426 		return;
1427 	}
1428 
1429 	if (_scores->isOpen()) {
1430 		_scores->handleKeyDown(event.kbd);
1431 		return;
1432 	}
1433 
1434 	switch (event.kbd.keycode) {
1435 		case Common::KEYCODE_F1:
1436 			_kia->open(kKIASectionHelp);
1437 			break;
1438 		case Common::KEYCODE_F2:
1439 			_kia->open(kKIASectionSave);
1440 			break;
1441 		case Common::KEYCODE_F3:
1442 			_kia->open(kKIASectionLoad);
1443 			break;
1444 		case Common::KEYCODE_F4:
1445 			_kia->open(kKIASectionCrimes);
1446 			break;
1447 		case Common::KEYCODE_F5:
1448 			_kia->open(kKIASectionSuspects);
1449 			break;
1450 		case Common::KEYCODE_F6:
1451 			_kia->open(kKIASectionClues);
1452 			break;
1453 		case Common::KEYCODE_F10:
1454 			_kia->open(kKIASectionQuit);
1455 			break;
1456 		case Common::KEYCODE_TAB:
1457 			_kia->openLastOpened();
1458 			break;
1459 		case Common::KEYCODE_ESCAPE:
1460 			_kia->open(kKIASectionSettings);
1461 			break;
1462 		case Common::KEYCODE_SPACE:
1463 			_combat->change();
1464 			break;
1465 		default:
1466 			break;
1467 	}
1468 }
1469 
handleMouseAction(int x,int y,bool mainButton,bool buttonDown,int scrollDirection)1470 void BladeRunnerEngine::handleMouseAction(int x, int y, bool mainButton, bool buttonDown, int scrollDirection /* = 0 */) {
1471 	x = CLIP(x, 0, 639);
1472 	y = CLIP(y, 0, 479);
1473 
1474 	uint32 timeNow = _time->current();
1475 
1476 	if (buttonDown) {
1477 		// unsigned difference is intentional
1478 		_mouseClickTimeDiff = timeNow - _mouseClickTimeLast;
1479 		_mouseClickTimeLast = timeNow;
1480 	}
1481 
1482 	if (!playerHasControl() || _mouse->isDisabled()) {
1483 		return;
1484 	}
1485 
1486 	if (_kia->isOpen()) {
1487 		if (scrollDirection != 0) {
1488 			_kia->handleMouseScroll(x, y, scrollDirection);
1489 		} else if (buttonDown) {
1490 			_kia->handleMouseDown(x, y, mainButton);
1491 		} else {
1492 			_kia->handleMouseUp(x, y, mainButton);
1493 		}
1494 		return;
1495 	}
1496 
1497 	if (_spinner->isOpen()) {
1498 		if (buttonDown) {
1499 			_spinner->handleMouseDown(x, y);
1500 		} else {
1501 			_spinner->handleMouseUp(x, y);
1502 		}
1503 		return;
1504 	}
1505 
1506 	if (_esper->isOpen()) {
1507 		if (buttonDown) {
1508 			_esper->handleMouseDown(x, y, mainButton);
1509 		} else {
1510 			_esper->handleMouseUp(x, y, mainButton);
1511 		}
1512 		return;
1513 	}
1514 
1515 	if (_vk->isOpen()) {
1516 		if (buttonDown) {
1517 			_vk->handleMouseDown(x, y, mainButton);
1518 		} else {
1519 			_vk->handleMouseUp(x, y, mainButton);
1520 		}
1521 		return;
1522 	}
1523 
1524 	if (_elevator->isOpen()) {
1525 		if (buttonDown) {
1526 			_elevator->handleMouseDown(x, y);
1527 		} else {
1528 			_elevator->handleMouseUp(x, y);
1529 		}
1530 		return;
1531 	}
1532 
1533 	if (_scores->isOpen()) {
1534 		if (buttonDown) {
1535 			_scores->handleMouseDown(x, y);
1536 		} else {
1537 			_scores->handleMouseUp(x, y);
1538 		}
1539 		return;
1540 	}
1541 
1542 	if (_dialogueMenu->waitingForInput()) {
1543 		if (mainButton && !buttonDown) {
1544 			_dialogueMenu->mouseUp();
1545 		}
1546 		return;
1547 	}
1548 
1549 	if (mainButton) {
1550 		Vector3 scenePosition = _mouse->getXYZ(x, y);
1551 
1552 		bool isClickable;
1553 		bool isObstacle;
1554 		bool isTarget;
1555 
1556 		int sceneObjectId = _sceneObjects->findByXYZ(&isClickable, &isObstacle, &isTarget, scenePosition, true, false, true);
1557 		int exitIndex = _scene->_exits->getRegionAtXY(x, y);
1558 		int regionIndex = _scene->_regions->getRegionAtXY(x, y);
1559 
1560 		if (_debugger->_showMouseClickInfo) {
1561 			// Region has highest priority when overlapping
1562 			debug("Mouse: %02.2f, %02.2f, %02.2f at ScreenX: %d ScreenY: %d", scenePosition.x, scenePosition.y, scenePosition.z, x, y);
1563 			if ((sceneObjectId < kSceneObjectOffsetActors || sceneObjectId >= kSceneObjectOffsetItems) && exitIndex >= 0) {
1564 				debug("Clicked on Region-Exit=%d", exitIndex);
1565 			} else if (regionIndex >= 0) {
1566 				debug("Clicked on Region-Regular=%d", regionIndex);
1567 			}
1568 			// In debug mode we're interested in *all* object/actors/items under mouse click
1569 			if (sceneObjectId >= kSceneObjectOffsetActors && sceneObjectId < kSceneObjectOffsetItems) {
1570 				debug("Clicked on Actor: %d", sceneObjectId  - kSceneObjectOffsetActors);
1571 			}
1572 			if (sceneObjectId >= kSceneObjectOffsetItems && sceneObjectId < kSceneObjectOffsetObjects) {
1573 				debug("Clicked on Item: %d", sceneObjectId  - kSceneObjectOffsetItems);
1574 			}
1575 			if (sceneObjectId >= kSceneObjectOffsetObjects && sceneObjectId <= (95 + kSceneObjectOffsetObjects) ) {
1576 				debug("Clicked on Object: %d", sceneObjectId - kSceneObjectOffsetObjects);
1577 			}
1578 		}
1579 
1580 		if ((sceneObjectId < kSceneObjectOffsetActors || sceneObjectId >= kSceneObjectOffsetItems) && exitIndex >= 0) {
1581 			handleMouseClickExit(exitIndex, x, y, buttonDown);
1582 		} else if (regionIndex >= 0) {
1583 			handleMouseClickRegion(regionIndex, x, y, buttonDown);
1584 		} else if (sceneObjectId == -1) {
1585 			handleMouseClickEmpty(x, y, scenePosition, buttonDown);
1586 		} else if (sceneObjectId >= kSceneObjectOffsetActors && sceneObjectId < kSceneObjectOffsetItems) {
1587 			handleMouseClickActor(sceneObjectId - kSceneObjectOffsetActors, mainButton, buttonDown, scenePosition, x, y);
1588 		} else if (sceneObjectId >= kSceneObjectOffsetItems && sceneObjectId < kSceneObjectOffsetObjects) {
1589 			handleMouseClickItem(sceneObjectId - kSceneObjectOffsetItems, buttonDown);
1590 		} else if (sceneObjectId >= kSceneObjectOffsetObjects && sceneObjectId <= (95 + kSceneObjectOffsetObjects)) {
1591 			handleMouseClick3DObject(sceneObjectId - kSceneObjectOffsetObjects, buttonDown, isClickable, isTarget);
1592 		}
1593 	} else if (buttonDown) {
1594 		if (_playerActor->mustReachWalkDestination()) {
1595 			if (!_isWalkingInterruptible) {
1596 				return;
1597 			}
1598 			_playerActor->stopWalking(false);
1599 			_interruptWalking = true;
1600 		}
1601 		_combat->change();
1602 	}
1603 }
1604 
handleMouseClickExit(int exitId,int x,int y,bool buttonDown)1605 void BladeRunnerEngine::handleMouseClickExit(int exitId, int x, int y, bool buttonDown) {
1606 	if (_isWalkingInterruptible && exitId != _walkingToExitId) {
1607 		_isWalkingInterruptible = false;
1608 		_interruptWalking = true;
1609 		walkingReset();
1610 		_walkingToExitId = exitId;
1611 		return;
1612 	}
1613 
1614 	if (buttonDown) {
1615 		return;
1616 	}
1617 
1618 	if (_isInsideScriptExit && exitId == _walkingToExitId) {
1619 		_playerActor->run();
1620 		if (_mouseClickTimeDiff <= 10000) {
1621 			_playerActor->increaseFPS();
1622 		}
1623 	} else {
1624 		_walkingToExitId   = exitId;
1625 		_walkingToRegionId = -1;
1626 		_walkingToObjectId = -1;
1627 		_walkingToItemId   = -1;
1628 		_walkingToEmpty    = false;
1629 		_walkingToActorId  = -1;
1630 
1631 		_isInsideScriptExit = true;
1632 		_sceneScript->clickedOnExit(exitId);
1633 		_isInsideScriptExit = false;
1634 	}
1635 }
1636 
handleMouseClickRegion(int regionId,int x,int y,bool buttonDown)1637 void BladeRunnerEngine::handleMouseClickRegion(int regionId, int x, int y, bool buttonDown) {
1638 	if (_isWalkingInterruptible && regionId != _walkingToRegionId) {
1639 		_isWalkingInterruptible = false;
1640 		_interruptWalking = true;
1641 		walkingReset();
1642 		_walkingToRegionId = regionId;
1643 		return;
1644 	}
1645 
1646 	if (buttonDown || _mouse->isInactive()) {
1647 		return;
1648 	}
1649 
1650 	if (_isInsideScriptRegion && regionId == _walkingToRegionId) {
1651 		_playerActor->run();
1652 		if (_mouseClickTimeDiff <= 10000) {
1653 			_playerActor->increaseFPS();
1654 		}
1655 	} else {
1656 		_walkingToExitId   = -1;
1657 		_walkingToRegionId = regionId;
1658 		_walkingToObjectId = -1;
1659 		_walkingToItemId   = -1;
1660 		_walkingToEmpty    = false;
1661 		_walkingToActorId  = -1;
1662 
1663 		_isInsideScriptRegion = true;
1664 		_sceneScript->clickedOn2DRegion(regionId);
1665 		_isInsideScriptRegion = false;
1666 	}
1667 }
1668 
handleMouseClick3DObject(int objectId,bool buttonDown,bool isClickable,bool isTarget)1669 void BladeRunnerEngine::handleMouseClick3DObject(int objectId, bool buttonDown, bool isClickable, bool isTarget) {
1670 	const Common::String &objectName = _scene->objectGetName(objectId);
1671 
1672 	if (_isWalkingInterruptible && objectId != _walkingToObjectId) {
1673 		_isWalkingInterruptible = false;
1674 		_interruptWalking = true;
1675 		walkingReset();
1676 		_walkingToObjectId = objectId;
1677 		return;
1678 	}
1679 
1680 	if (_mouse->isInactive()) {
1681 		return;
1682 	}
1683 
1684 	if (!_combat->isActive()) {
1685 		if (buttonDown || !isClickable) {
1686 			return;
1687 		}
1688 
1689 		if (_isInsideScriptObject && objectId == _walkingToObjectId) {
1690 			_playerActor->run();
1691 			if (_mouseClickTimeDiff <= 10000) {
1692 				_playerActor->increaseFPS();
1693 			}
1694 		} else {
1695 			_walkingToExitId   = -1;
1696 			_walkingToRegionId = -1;
1697 			_walkingToObjectId = objectId;
1698 			_walkingToItemId   = -1;
1699 			_walkingToEmpty    = false;
1700 			_walkingToActorId  = -1;
1701 
1702 			_isInsideScriptObject = true;
1703 			_sceneScript->clickedOn3DObject(objectName.c_str(), false);
1704 			_isInsideScriptObject = false;
1705 		}
1706 	} else {
1707 		if (!buttonDown || !isTarget) {
1708 			return;
1709 		}
1710 		_playerActor->stopWalking(false);
1711 		_playerActor->faceObject(objectName, false);
1712 		_playerActor->changeAnimationMode(kAnimationModeCombatAttack, false);
1713 		_settings->decreaseAmmo();
1714 		_audioPlayer->playAud(_gameInfo->getSfxTrack(_combat->getHitSound()), 100, 0, 0, 90, 0);
1715 
1716 		_mouse->setMouseJitterUp();
1717 
1718 		_isInsideScriptObject = true;
1719 		_sceneScript->clickedOn3DObject(objectName.c_str(), true);
1720 		_isInsideScriptObject = false;
1721 	}
1722 }
1723 
handleMouseClickEmpty(int x,int y,Vector3 & scenePosition,bool buttonDown)1724 void BladeRunnerEngine::handleMouseClickEmpty(int x, int y, Vector3 &scenePosition, bool buttonDown) {
1725 	if (_isWalkingInterruptible) {
1726 		_isWalkingInterruptible = false;
1727 		_interruptWalking = true;
1728 		walkingReset();
1729 		_walkingToEmpty = false;
1730 		return;
1731 	}
1732 
1733 	_isInsideScriptEmpty = true;
1734 	bool sceneMouseClick = _sceneScript->mouseClick(x, y);
1735 	_isInsideScriptEmpty = false;
1736 
1737 	if (sceneMouseClick) {
1738 		return;
1739 	}
1740 
1741 	int actorId = Actor::findTargetUnderMouse(this, x, y);
1742 	int itemId = _items->findTargetUnderMouse(x, y);
1743 
1744 	if (_combat->isActive() && buttonDown && (actorId >= 0 || itemId >= 0)) {
1745 		_playerActor->stopWalking(false);
1746 		if (actorId >= 0) {
1747 			_playerActor->faceActor(actorId, false);
1748 		} else {
1749 			_playerActor->faceItem(itemId, false);
1750 		}
1751 		_playerActor->changeAnimationMode(kAnimationModeCombatAttack, false);
1752 		_settings->decreaseAmmo();
1753 		_audioPlayer->playAud(_gameInfo->getSfxTrack(_combat->getMissSound()), 100, 0, 0, 90, 0);
1754 
1755 		_mouse->setMouseJitterUp();
1756 
1757 		if (actorId > 0) {
1758 			_aiScripts->shotAtAndMissed(actorId);
1759 		}
1760 	} else {
1761 		if (buttonDown) {
1762 			return;
1763 		}
1764 
1765 		_walkingToExitId   = -1;
1766 		_walkingToRegionId = -1;
1767 		_walkingToObjectId = -1;
1768 		_walkingToItemId   = -1;
1769 		_walkingToEmpty    = true;
1770 		_walkingToActorId  = -1;
1771 
1772 		if (_combat->isActive() && (actorId > 0 || itemId > 0)) {
1773 			return;
1774 		}
1775 
1776 		int xDist = abs(_walkingToEmptyX - x);
1777 		int yDist = abs(_walkingToEmptyY - y);
1778 
1779 		_walkingToEmptyX = x;
1780 		_walkingToEmptyY = y;
1781 
1782 		bool inWalkbox = false;
1783 		float altitude = _scene->_set->getAltitudeAtXZ(scenePosition.x, scenePosition.z, &inWalkbox);
1784 
1785 		if (!inWalkbox || scenePosition.y >= altitude + 6.0f) {
1786 			return;
1787 		}
1788 
1789 		bool shouldRun = _playerActor->isRunning();
1790 		if (_mouseClickTimeDiff <= 10000 && xDist <= 10 && yDist <= 10) {
1791 			shouldRun = true;
1792 		}
1793 
1794 		_playerActor->walkTo(shouldRun, scenePosition, false);
1795 
1796 		if (shouldRun && _playerActor->isWalking()) {
1797 			_playerActor->increaseFPS();
1798 		}
1799 	}
1800 }
1801 
handleMouseClickItem(int itemId,bool buttonDown)1802 void BladeRunnerEngine::handleMouseClickItem(int itemId, bool buttonDown) {
1803 	if (_isWalkingInterruptible && itemId != _walkingToItemId) {
1804 		_isWalkingInterruptible = false;
1805 		_interruptWalking = true;
1806 		walkingReset();
1807 		_walkingToItemId = itemId;
1808 		return;
1809 	}
1810 
1811 	if (_mouse->isInactive()) {
1812 		return;
1813 	}
1814 
1815 	if (!_combat->isActive()) {
1816 		if (buttonDown) {
1817 			return;
1818 		}
1819 
1820 		if (_isInsideScriptItem && itemId == _walkingToItemId) {
1821 			_playerActor->run();
1822 			if (_mouseClickTimeDiff <= 10000) {
1823 				_playerActor->increaseFPS();
1824 			}
1825 		} else {
1826 			_walkingToExitId = -1;
1827 			_walkingToRegionId = -1;
1828 			_walkingToObjectId = -1;
1829 			_walkingToItemId = itemId;
1830 			_walkingToEmpty = false;
1831 			_walkingToActorId = -1;
1832 
1833 			_isInsideScriptItem = true;
1834 			_sceneScript->clickedOnItem(itemId, false);
1835 			_isInsideScriptItem = false;
1836 		}
1837 	} else {
1838 		if (!buttonDown || !_items->isTarget(itemId) /* || _mouse->isRandomized() */) {
1839 			return;
1840 		}
1841 
1842 		_playerActor->stopWalking(false);
1843 		_playerActor->faceItem(itemId, false);
1844 		_playerActor->changeAnimationMode(kAnimationModeCombatAttack, false);
1845 		_settings->decreaseAmmo();
1846 		_audioPlayer->playAud(_gameInfo->getSfxTrack(_combat->getHitSound()), 100, 0, 0, 90, 0);
1847 
1848 		_mouse->setMouseJitterUp();
1849 
1850 		_isInsideScriptItem = true;
1851 		_sceneScript->clickedOnItem(itemId, true);
1852 		_isInsideScriptItem = false;
1853 	}
1854 }
1855 
handleMouseClickActor(int actorId,bool mainButton,bool buttonDown,Vector3 & scenePosition,int x,int y)1856 void BladeRunnerEngine::handleMouseClickActor(int actorId, bool mainButton, bool buttonDown, Vector3 &scenePosition, int x, int y) {
1857 	if (_isWalkingInterruptible && actorId != _walkingToActorId) {
1858 		_isWalkingInterruptible = false;
1859 		_interruptWalking = true;
1860 		walkingReset();
1861 		_walkingToActorId = actorId;
1862 		return;
1863 	}
1864 
1865 	if (_mouse->isInactive()) {
1866 		return;
1867 	}
1868 
1869 	if (!buttonDown) {
1870 		if (actorId == kActorMcCoy) {
1871 			if (mainButton) {
1872 				if (!_combat->isActive()) {
1873 					_kia->openLastOpened();
1874 				}
1875 			} else if (!_playerActor->mustReachWalkDestination()) {
1876 				_combat->change();
1877 			}
1878 			return;
1879 		}
1880 
1881 		if (_isInsideScriptActor && actorId == _walkingToActorId) {
1882 			_playerActor->run();
1883 			if (_mouseClickTimeDiff <= 10000) {
1884 				_playerActor->increaseFPS();
1885 			}
1886 		} else {
1887 			_walkingToExitId = -1;
1888 			_walkingToRegionId = -1;
1889 			_walkingToObjectId = -1;
1890 			_walkingToItemId = -1;
1891 			_walkingToEmpty = false;
1892 			_walkingToActorId = actorId;
1893 
1894 			_isInsideScriptActor = true;
1895 			bool processedBySceneScript = _sceneScript->clickedOnActor(actorId);
1896 			_isInsideScriptActor = false;
1897 
1898 			if (!_combat->isActive() && !processedBySceneScript) {
1899 				_aiScripts->clickedByPlayer(actorId);
1900 			}
1901 		}
1902 	} else {
1903 		Actor *actor = _actors[actorId];
1904 
1905 		if (!_combat->isActive() || actorId == kActorMcCoy || !actor->isTarget() || actor->isRetired() /*|| _mouse->isRandomized()*/) {
1906 			return;
1907 		}
1908 		_playerActor->stopWalking(false);
1909 		_playerActor->faceActor(actorId, false);
1910 		_playerActor->changeAnimationMode(kAnimationModeCombatAttack, false);
1911 		_settings->decreaseAmmo();
1912 
1913 		bool missed = _playerActor->isObstacleBetween(actor->getXYZ());
1914 
1915 		_audioPlayer->playAud(_gameInfo->getSfxTrack(missed ? _combat->getMissSound() : _combat->getHitSound()), 100, 0, 0, 90, 0);
1916 
1917 		_mouse->setMouseJitterUp();
1918 
1919 		if (missed) {
1920 			_aiScripts->shotAtAndMissed(actorId);
1921 		} else {
1922 			_isInsideScriptActor = true;
1923 			bool canShoot = _aiScripts->shotAtAndHit(actorId);
1924 			_isInsideScriptActor = false;
1925 			if (!canShoot) {
1926 				_combat->shoot(actorId, scenePosition, x);
1927 			}
1928 		}
1929 	}
1930 }
1931 
gameWaitForActive()1932 void BladeRunnerEngine::gameWaitForActive() {
1933 	while (!_windowIsActive) {
1934 		handleEvents();
1935 	}
1936 }
1937 
loopActorSpeaking()1938 void BladeRunnerEngine::loopActorSpeaking() {
1939 	if (!_audioSpeech->isPlaying()) {
1940 		return;
1941 	}
1942 
1943 	playerLosesControl();
1944 
1945 	do {
1946 		gameTick();
1947 	} while (_gameIsRunning && _audioSpeech->isPlaying());
1948 
1949 	playerGainsControl();
1950 }
1951 
1952 /**
1953 * To be used only for when there is a chance an ongoing dialogue in a dialogue queue
1954 * might be interrupted AND that is unwanted behavior (sometimes, it's intended that the dialogue
1955 * can be interrupted without necessarily being finished).
1956 */
loopQueuedDialogueStillPlaying()1957 void BladeRunnerEngine::loopQueuedDialogueStillPlaying() {
1958 	if (_actorDialogueQueue->isEmpty()) {
1959 		return;
1960 	}
1961 
1962 	do {
1963 		gameTick();
1964 	} while (_gameIsRunning && !_actorDialogueQueue->isEmpty());
1965 
1966 }
1967 
outtakePlay(int id,bool noLocalization,int container)1968 void BladeRunnerEngine::outtakePlay(int id, bool noLocalization, int container) {
1969 	Common::String name = _gameInfo->getOuttake(id);
1970 
1971 	OuttakePlayer player(this);
1972 
1973 	player.play(name, noLocalization, container);
1974 }
1975 
openArchive(const Common::String & name)1976 bool BladeRunnerEngine::openArchive(const Common::String &name) {
1977 	int i;
1978 
1979 	// If archive is already open, return true
1980 	for (i = 0; i != kArchiveCount; ++i) {
1981 		if (_archives[i].isOpen() && _archives[i].getName() == name) {
1982 			return true;
1983 		}
1984 	}
1985 
1986 	// Find first available slot
1987 	for (i = 0; i != kArchiveCount; ++i) {
1988 		if (!_archives[i].isOpen()) {
1989 			break;
1990 		}
1991 	}
1992 	if (i == kArchiveCount) {
1993 		/* TODO: BLADE.EXE retires the least recently used
1994 		 * archive when it runs out of slots. */
1995 
1996 		error("openArchive: No more archive slots");
1997 	}
1998 
1999 	_archives[i].open(name);
2000 	return _archives[i].isOpen();
2001 }
2002 
closeArchive(const Common::String & name)2003 bool BladeRunnerEngine::closeArchive(const Common::String &name) {
2004 	for (int i = 0; i != kArchiveCount; ++i) {
2005 		if (_archives[i].isOpen() && _archives[i].getName() == name) {
2006 			_archives[i].close();
2007 			return true;
2008 		}
2009 	}
2010 
2011 	warning("closeArchive: Archive %s not open.", name.c_str());
2012 	return false;
2013 }
2014 
isArchiveOpen(const Common::String & name) const2015 bool BladeRunnerEngine::isArchiveOpen(const Common::String &name) const {
2016 	for (int i = 0; i != kArchiveCount; ++i) {
2017 		if (_archives[i].isOpen() && _archives[i].getName() == name)
2018 			return true;
2019 	}
2020 
2021 	return false;
2022 }
2023 
syncSoundSettings()2024 void BladeRunnerEngine::syncSoundSettings() {
2025 	Engine::syncSoundSettings();
2026 
2027 	_subtitlesEnabled = ConfMan.getBool("subtitles");
2028 
2029 	_mixer->setVolumeForSoundType(_mixer->kMusicSoundType, ConfMan.getInt("music_volume"));
2030 	_mixer->setVolumeForSoundType(_mixer->kSFXSoundType, ConfMan.getInt("sfx_volume"));
2031 	_mixer->setVolumeForSoundType(_mixer->kSpeechSoundType, ConfMan.getInt("speech_volume"));
2032 	// By default, if no ambient_volume is found in configuration manager, set ambient volume from sfx volume
2033 	int configAmbientVolume = _mixer->getVolumeForSoundType(_mixer->kSFXSoundType);
2034 	if (ConfMan.hasKey("ambient_volume")) {
2035 		configAmbientVolume = ConfMan.getInt("ambient_volume");
2036 	} else {
2037 		ConfMan.setInt("ambient_volume", configAmbientVolume);
2038 	}
2039 	_mixer->setVolumeForSoundType(_mixer->kPlainSoundType, configAmbientVolume);
2040 	// debug("syncSoundSettings: Volumes synced as Music: %d, Sfx: %d, Speech: %d", ConfMan.getInt("music_volume"), ConfMan.getInt("sfx_volume"), ConfMan.getInt("speech_volume"));
2041 
2042 	if (_noMusicDriver) {
2043 		// This affects *only* the music muting.
2044 		_mixer->muteSoundType(_mixer->kMusicSoundType, true);
2045 	}
2046 
2047 	bool allSoundIsMuted = false;
2048 	if (ConfMan.hasKey("mute")) {
2049 		allSoundIsMuted = ConfMan.getBool("mute");
2050 		if (!_noMusicDriver) {
2051 			_mixer->muteSoundType(_mixer->kMusicSoundType, allSoundIsMuted);
2052 		}
2053 		_mixer->muteSoundType(_mixer->kSFXSoundType, allSoundIsMuted);
2054 		_mixer->muteSoundType(_mixer->kSpeechSoundType, allSoundIsMuted);
2055 		_mixer->muteSoundType(_mixer->kPlainSoundType, allSoundIsMuted);
2056 	}
2057 
2058 	if (ConfMan.hasKey("speech_mute") && !allSoundIsMuted) {
2059 		// if true it means show only subtitles
2060 		// "subtitles" key will already be set appropriately by Engine::syncSoundSettings();
2061 		// but we need to mute the speech
2062 		_mixer->muteSoundType(_mixer->kSpeechSoundType, ConfMan.getBool("speech_mute"));
2063 	}
2064 
2065 	// write-back to ini file for persistence
2066 	ConfMan.flushToDisk(); // TODO Or maybe call this only when game is shut down?
2067 }
2068 
isSubtitlesEnabled()2069 bool BladeRunnerEngine::isSubtitlesEnabled() {
2070 	return _subtitlesEnabled;
2071 }
2072 
setSubtitlesEnabled(bool newVal)2073 void BladeRunnerEngine::setSubtitlesEnabled(bool newVal) {
2074 	ConfMan.setBool("subtitles", newVal);
2075 	syncSoundSettings();
2076 }
2077 
getResourceStream(const Common::String & name)2078 Common::SeekableReadStream *BladeRunnerEngine::getResourceStream(const Common::String &name) {
2079 	// If the file is extracted from MIX files use it directly, it is used by Russian translation patched by Siberian Studio
2080 	if (Common::File::exists(name)) {
2081 		Common::File directFile;
2082 		if (directFile.open(name)) {
2083 			Common::SeekableReadStream *stream = directFile.readStream(directFile.size());
2084 			directFile.close();
2085 			return stream;
2086 		}
2087 	}
2088 
2089 	for (int i = 0; i != kArchiveCount; ++i) {
2090 		if (!_archives[i].isOpen()) {
2091 			continue;
2092 		}
2093 
2094 		// debug("getResource: Searching archive %s for %s.", _archives[i].getName().c_str(), name.c_str());
2095 		Common::SeekableReadStream *stream = _archives[i].createReadStreamForMember(name);
2096 		if (stream) {
2097 			return stream;
2098 		}
2099 	}
2100 
2101 	warning("getResource: Resource %s not found", name.c_str());
2102 	return nullptr;
2103 }
2104 
playerHasControl()2105 bool BladeRunnerEngine::playerHasControl() {
2106 	return _playerLosesControlCounter == 0;
2107 }
2108 
playerLosesControl()2109 void BladeRunnerEngine::playerLosesControl() {
2110 	if (++_playerLosesControlCounter == 1) {
2111 		_mouse->disable();
2112 	}
2113 }
2114 
playerGainsControl(bool force)2115 void BladeRunnerEngine::playerGainsControl(bool force) {
2116 	if (!force && _playerLosesControlCounter == 0) {
2117 		warning("Unbalanced call to BladeRunnerEngine::playerGainsControl");
2118 	}
2119 
2120 	if (force) {
2121 		_playerLosesControlCounter = 0;
2122 		_mouse->enable(force);
2123 	} else {
2124 		if (_playerLosesControlCounter > 0) {
2125 			--_playerLosesControlCounter;
2126 		}
2127 		if (_playerLosesControlCounter == 0) {
2128 			_mouse->enable();
2129 		}
2130 	}
2131 }
2132 
playerDied()2133 void BladeRunnerEngine::playerDied() {
2134 	playerLosesControl();
2135 
2136 #if BLADERUNNER_ORIGINAL_BUGS
2137 #else
2138 	// reset ammo amounts
2139 	_settings->reset();
2140 	// need to clear kFlagKIAPrivacyAddon to remove Bob's Privacy Addon for KIA
2141 	// so it won't appear here after end credits
2142 	_gameFlags->reset(kFlagKIAPrivacyAddon);
2143 
2144 	_ambientSounds->removeAllNonLoopingSounds(true);
2145 	_ambientSounds->removeAllLoopingSounds(4u);
2146 	_music->stop(4u);
2147 	_audioSpeech->stopSpeech();
2148 #endif // BLADERUNNER_ORIGINAL_BUGS
2149 
2150 	uint32 timeWaitStart = _time->current();
2151 	// unsigned difference is intentional
2152 	while (_time->current() - timeWaitStart < 5000u) {
2153 		gameTick();
2154 	}
2155 
2156 	_actorDialogueQueue->flush(1, false);
2157 
2158 	while (_playerLosesControlCounter > 0) {
2159 		playerGainsControl();
2160 	}
2161 
2162 	_kia->_forceOpen = true;
2163 	_kia->open(kKIASectionLoad);
2164 }
2165 
saveGame(Common::WriteStream & stream,Graphics::Surface * thumb,bool origformat)2166 bool BladeRunnerEngine::saveGame(Common::WriteStream &stream, Graphics::Surface *thumb, bool origformat) {
2167 	if ( !_gameIsAutoSaving
2168 	     && ( !playerHasControl() || _sceneScript->isInsideScript() || _aiScripts->isInsideScript())
2169 	) {
2170 		return false;
2171 	}
2172 
2173 	Common::MemoryWriteStreamDynamic memoryStream(DisposeAfterUse::YES);
2174 	SaveFileWriteStream s(memoryStream);
2175 
2176 	if (!origformat) {
2177 		if (thumb)
2178 			Graphics::saveThumbnail(s, *thumb);
2179 		else
2180 			Graphics::saveThumbnail(s);
2181 	} else {
2182 		thumb->convertToInPlace(gameDataPixelFormat());
2183 
2184 		uint16 *thumbnailData = (uint16*)thumb->getPixels();
2185 		for (uint i = 0; i < SaveFileManager::kThumbnailSize / 2; ++i) {
2186 			s.writeUint16LE(thumbnailData[i]);
2187 		}
2188 	}
2189 
2190 	s.writeFloat(1.0f);
2191 	_settings->save(s);
2192 	_scene->save(s);
2193 	_scene->_exits->save(s);
2194 	_scene->_regions->save(s);
2195 	_scene->_set->save(s);
2196 	for (uint i = 0; i != _gameInfo->getGlobalVarCount(); ++i) {
2197 		s.writeInt(_gameVars[i]);
2198 	}
2199 	_music->save(s);
2200 	// _audioPlayer->save(s) // zero func
2201 	// _audioSpeech->save(s) // zero func
2202 	_combat->save(s);
2203 	_gameFlags->save(s);
2204 	_items->save(s);
2205 	_sceneObjects->save(s);
2206 	_ambientSounds->save(s);
2207 	_overlays->save(s);
2208 	_spinner->save(s);
2209 	_scores->save(s);
2210 	_dialogueMenu->save(s);
2211 	_obstacles->save(s);
2212 	_actorDialogueQueue->save(s);
2213 	_waypoints->save(s);
2214 
2215 	for (uint i = 0; i != _gameInfo->getActorCount(); ++i) {
2216 		_actors[i]->save(s);
2217 
2218 		int animationState, animationFrame, animationStateNext, nextAnimation;
2219 		_aiScripts->queryAnimationState(i, &animationState, &animationFrame, &animationStateNext, &nextAnimation);
2220 		s.writeInt(animationState);
2221 		s.writeInt(animationFrame);
2222 		s.writeInt(animationStateNext);
2223 		s.writeInt(nextAnimation);
2224 	}
2225 	_actors[kActorVoiceOver]->save(s);
2226 	_policeMaze->save(s);
2227 	_crimesDatabase->save(s);
2228 
2229 	s.finalize();
2230 
2231 	stream.writeUint32LE(memoryStream.size() + 4);
2232 	stream.write(memoryStream.getData(), memoryStream.size());
2233 	stream.flush();
2234 
2235 	return true;
2236 }
2237 
loadGame(Common::SeekableReadStream & stream,int version)2238 bool BladeRunnerEngine::loadGame(Common::SeekableReadStream &stream, int version) {
2239 	if (!playerHasControl() || _sceneScript->isInsideScript() || _aiScripts->isInsideScript()) {
2240 		return false;
2241 	}
2242 
2243 	SaveFileReadStream s(stream);
2244 
2245 	_ambientSounds->removeAllNonLoopingSounds(true);
2246 #if BLADERUNNER_ORIGINAL_BUGS
2247 	_ambientSounds->removeAllLoopingSounds(1);
2248 	_music->stop(2);
2249 #else
2250 	// loading into another game that also has music would
2251 	// cause two music tracks to overlap and none was stopped
2252 	_ambientSounds->removeAllLoopingSounds(0u);
2253 	_music->stop(0u);
2254 #endif // BLADERUNNER_ORIGINAL_BUGS
2255 	_audioSpeech->stopSpeech();
2256 	_actorDialogueQueue->flush(true, false);
2257 #if BLADERUNNER_ORIGINAL_BUGS
2258 #else
2259 	_screenEffects->toggleEntry(-1, false); // clear the skip list
2260 #endif
2261 	_screenEffects->_entries.clear();
2262 
2263 	int size = s.readInt();
2264 
2265 	if (size != s.size() - s.pos() + 4) {
2266 		_gameIsLoading = false;
2267 		return false;
2268 	}
2269 
2270 	_gameIsLoading = true;
2271 	_settings->setLoadingGame();
2272 
2273 	if (version >= 4)
2274 		Graphics::skipThumbnail(s);
2275 	else
2276 		s.skip(SaveFileManager::kThumbnailSize); // skip the thumbnail
2277 
2278 	s.skip(4);// always float 1.0, but never used, assuming it's the game version
2279 	_settings->load(s);
2280 	_scene->load(s);
2281 	_scene->_exits->load(s);
2282 	_scene->_regions->load(s);
2283 	_scene->_set->load(s);
2284 	for (uint i = 0; i != _gameInfo->getGlobalVarCount(); ++i) {
2285 		_gameVars[i] = s.readInt();
2286 		if (i == 3 && _gameVars[i] != kBladeRunnerScummVMVersion) {
2287 			warning("This game was saved using an older version of the engine (v%d), currently the engine is at v%d", _gameVars[i], kBladeRunnerScummVMVersion);
2288 		}
2289 	}
2290 	_music->load(s);
2291 	// _audioPlayer->load(s) // zero func
2292 	// _audioSpeech->load(s) // zero func
2293 	_combat->load(s);
2294 	_gameFlags->load(s);
2295 
2296 	if ((_gameFlags->query(kFlagGamePlayedInRestoredContentMode) && !_cutContent)
2297 	    || (!_gameFlags->query(kFlagGamePlayedInRestoredContentMode) && _cutContent)
2298 	) {
2299 		Common::U32String warningMsg;
2300 		if (!_cutContent) {
2301 			warningMsg = _("WARNING: This game was saved in Restored Cut Content mode, but you are playing in Original Content mode. The mode will be adjusted to Restored Cut Content for this session until you completely Quit the game.");
2302 		} else {
2303 			warningMsg = _("WARNING: This game was saved in Original Content mode, but you are playing in Restored Cut Content mode. The mode will be adjusted to Original Content mode for this session until you completely Quit the game.");
2304 		}
2305 		GUI::MessageDialog dialog(warningMsg, _("Continue"));
2306 		dialog.runModal();
2307 		_cutContent = !_cutContent;
2308 		// force a Key Down event, since we need it to remove the KIA
2309 		// but it's lost due to the modal dialogue
2310 		Common::EventManager *eventMan = _system->getEventManager();
2311 		Common::Event event;
2312 		event.type = Common::EVENT_KEYDOWN;
2313 		eventMan->pushEvent(event);
2314 	}
2315 
2316 	_items->load(s);
2317 	_sceneObjects->load(s);
2318 	_ambientSounds->load(s);
2319 	_overlays->load(s);
2320 	_spinner->load(s);
2321 	_scores->load(s);
2322 	_dialogueMenu->load(s);
2323 	_obstacles->load(s);
2324 	_actorDialogueQueue->load(s);
2325 	_waypoints->load(s);
2326 	for (uint i = 0; i != _gameInfo->getActorCount(); ++i) {
2327 		_actors[i]->load(s);
2328 
2329 		int animationState = s.readInt();
2330 		int animationFrame = s.readInt();
2331 		int animationStateNext = s.readInt();
2332 		int nextAnimation = s.readInt();
2333 		_aiScripts->setAnimationState(i, animationState, animationFrame, animationStateNext, nextAnimation);
2334 	}
2335 	_actors[kActorVoiceOver]->load(s);
2336 	_policeMaze->load(s);
2337 	_crimesDatabase->load(s);
2338 
2339 	_actorUpdateCounter = 0;
2340 	_actorUpdateTimeLast = 0;
2341 	_gameIsLoading = false;
2342 
2343 	_settings->setNewSetAndScene(_settings->getSet(), _settings->getScene());
2344 	_settings->setChapter(_settings->getChapter());
2345 	return true;
2346 }
2347 
newGame(int difficulty)2348 void BladeRunnerEngine::newGame(int difficulty) {
2349 	_settings->reset();
2350 	_combat->reset();
2351 
2352 	for (uint i = 0; i < _gameInfo->getActorCount(); ++i) {
2353 		_actors[i]->setup(i);
2354 	}
2355 	_actors[kActorVoiceOver]->setup(kActorVoiceOver);
2356 
2357 #if BLADERUNNER_ORIGINAL_BUGS
2358 #else
2359 	// Special settings for McCoy that are in BladeRunnerEngine::startup()
2360 	// but are overridden here, resulting to the stamina counter
2361 	// never being initialized in the original game
2362 	_playerActor->setFPS(15); // this seems redundant
2363 	if (!_cutContent) {
2364 		_playerActor->timerStart(kActorTimerRunningStaminaFPS, 200);
2365 	}
2366 #endif // BLADERUNNER_ORIGINAL_BUGS
2367 
2368 	for (uint i = 0; i < _gameInfo->getSuspectCount(); ++i) {
2369 		_suspectsDatabase->get(i)->reset();
2370 	}
2371 
2372 	_gameFlags->clear();
2373 
2374 	for (uint i = 0; i < _gameInfo->getGlobalVarCount(); ++i) {
2375 		_gameVars[i] = 0;
2376 	}
2377 
2378 	_items->reset();
2379 	_scores->reset();
2380 	_kia->reset();
2381 	_dialogueMenu->clear();
2382 	_scene->_exits->enable();
2383 
2384 	if (difficulty >= 0 && difficulty < 3) {
2385 		_settings->setDifficulty(difficulty);
2386 	}
2387 
2388 	InitScript initScript(this);
2389 	initScript.SCRIPT_Initialize_Game();
2390 	_actorUpdateCounter = 0;
2391 	_actorUpdateTimeLast = 0;
2392 	initChapterAndScene();
2393 
2394 	_settings->setStartingGame();
2395 }
2396 
autoSaveGame(int textId,bool endgame)2397 void BladeRunnerEngine::autoSaveGame(int textId, bool endgame) {
2398 	TextResource textAutoSave(this);
2399 	if (!textAutoSave.open("AUTOSAVE")) {
2400 		return;
2401 	}
2402 	_gameIsAutoSaving = true;
2403 
2404 	SaveStateList saveList = BladeRunner::SaveFileManager::list(getMetaEngine(), getTargetName());
2405 
2406 	// Find first available save slot
2407 	int slot = -1;
2408 	int maxSlot = -1;
2409 	for (int i = 0; i < (int)saveList.size(); ++i) {
2410 		maxSlot = MAX(maxSlot, saveList[i].getSaveSlot());
2411 		if (saveList[i].getSaveSlot() != i) {
2412 			slot = i;
2413 			break;
2414 		}
2415 	}
2416 
2417 	if (slot == -1) {
2418 		slot = maxSlot + 1;
2419 	}
2420 	if (endgame) {
2421 		saveGameState(slot, "END_GAME_STATE");
2422 	} else {
2423 		saveGameState(slot,  textAutoSave.getText(textId));
2424 	}
2425 	_gameIsAutoSaving = false;
2426 }
2427 
ISez(const Common::String & str)2428 void BladeRunnerEngine::ISez(const Common::String &str) {
2429 	debug("\t%s", str.c_str());
2430 }
2431 
blitToScreen(const Graphics::Surface & src) const2432 void BladeRunnerEngine::blitToScreen(const Graphics::Surface &src) const {
2433 	_framelimiter->wait();
2434 	_system->copyRectToScreen(src.getPixels(), src.pitch, 0, 0, src.w, src.h);
2435 	_system->updateScreen();
2436 }
2437 
generateThumbnail() const2438 Graphics::Surface BladeRunnerEngine::generateThumbnail() const {
2439 	Graphics::Surface thumbnail;
2440 	thumbnail.create(640 / 8, 480 / 8, gameDataPixelFormat());
2441 
2442 	for (int y = 0; y < thumbnail.h; ++y) {
2443 		for (int x = 0; x < thumbnail.w; ++x) {
2444 			uint8 r, g, b;
2445 
2446 			uint32  srcPixel = READ_UINT32(_surfaceFront.getBasePtr(CLIP(x * 8, 0, _surfaceFront.w - 1), CLIP(y * 8, 0, _surfaceFront.h - 1)));
2447 			void   *dstPixel = thumbnail.getBasePtr(CLIP(x, 0, thumbnail.w - 1), CLIP(y, 0, thumbnail.h - 1));
2448 
2449 			// Throw away alpha channel as it is not needed
2450 			_surfaceFront.format.colorToRGB(srcPixel, r, g, b);
2451 			drawPixel(thumbnail, dstPixel, thumbnail.format.RGBToColor(r, g, b));
2452 		}
2453 	}
2454 
2455 	return thumbnail;
2456 }
2457 
getTargetName() const2458 Common::String BladeRunnerEngine::getTargetName() const {
2459 	return _targetName;
2460 }
2461 
blit(const Graphics::Surface & src,Graphics::Surface & dst)2462 void blit(const Graphics::Surface &src, Graphics::Surface &dst) {
2463 	dst.copyRectToSurface(src.getPixels(), src.pitch, 0, 0, src.w, src.h);
2464 }
2465 
2466 } // End of namespace BladeRunner
2467