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