/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "bladerunner/bladerunner.h" #include "bladerunner/actor.h" #include "bladerunner/adq.h" #include "bladerunner/ambient_sounds.h" #include "bladerunner/audio_player.h" #include "bladerunner/audio_speech.h" #include "bladerunner/chapters.h" #include "bladerunner/combat.h" #include "bladerunner/crimes_database.h" #include "bladerunner/font.h" #include "bladerunner/gameflags.h" #include "bladerunner/gameinfo.h" #include "bladerunner/image.h" #include "bladerunner/item_pickup.h" #include "bladerunner/items.h" #include "bladerunner/lights.h" #include "bladerunner/mouse.h" #include "bladerunner/outtake.h" #include "bladerunner/obstacles.h" #include "bladerunner/scene.h" #include "bladerunner/scene_objects.h" #include "bladerunner/script/init.h" #include "bladerunner/script/script.h" #include "bladerunner/settings.h" #include "bladerunner/shape.h" #include "bladerunner/slice_animations.h" #include "bladerunner/slice_renderer.h" #include "bladerunner/text_resource.h" #include "bladerunner/vqa_decoder.h" #include "bladerunner/waypoints.h" #include "common/array.h" #include "common/error.h" #include "common/events.h" #include "common/system.h" #include "engines/util.h" #include "graphics/pixelformat.h" #include "suspects_database.h" namespace BladeRunner { BladeRunnerEngine::BladeRunnerEngine(OSystem *syst) : Engine(syst), _rnd("bladerunner") { _windowIsActive = true; _gameIsRunning = true; _playerLosesControlCounter = 0; _crimesDatabase = nullptr; _script = new Script(this); _settings = new Settings(this); _lights = new Lights(this); _combat = new Combat(this); _adq = new ADQ(this); _obstacles = new Obstacles(this); _itemPickup = new ItemPickup(this); _playerActorIdle = false; _playerDead = false; _speechSkipped = false; _gameOver = false; _gameAutoSave = 0; _gameIsLoading = false; _sceneIsLoading = false; _walkSoundId = -1; _walkSoundVolume = 0; _walkSoundBalance = 0; } BladeRunnerEngine::~BladeRunnerEngine() { // delete _sliceRenderer; // delete _sliceAnimations; // delete _settings; // delete _script; // delete _scene; // delete[] _gameVars; // delete _gameFlags; // delete _gameInfo; // delete _clues; // delete _chapters; // delete _audioSpeech; // delete _audioPlayer; // delete _ambientSounds; // _surface1.free(); // _surface2.free(); // delete[] _zBuffer1; // delete[] _zBuffer2; delete _itemPickup; delete _obstacles; delete _adq; delete _combat; delete _lights; delete _settings; delete _script; } bool BladeRunnerEngine::hasFeature(EngineFeature f) const { return f == kSupportsRTL; } Common::Error BladeRunnerEngine::run() { Graphics::PixelFormat format = createRGB555(); initGraphics(640, 480, true, &format); _system->showMouse(true); if (!startup()) { shutdown(); return Common::Error(Common::kUnknownError, "Failed to initialize resources"); } if (warnUserAboutUnsupportedGame()) { init2(); /* TODO: Check for save games and enter KIA */ gameLoop(); } shutdown(); return Common::kNoError; } bool BladeRunnerEngine::startup(bool hasSavegames) { bool r; _surface1.create(640, 480, createRGB555()); r = openArchive("STARTUP.MIX"); if (!r) return false; // TODO: Timer _gameInfo = new GameInfo(this); if (!_gameInfo) return false; r = _gameInfo->open("GAMEINFO.DAT"); if (!r) return false; // TODO: Create datetime - not used // TODO: Create graphics surfaces 1-4 // TODO: Allocate audio cache if (hasSavegames) { if (!loadSplash()) { return false; } } _waypoints = new Waypoints(this, _gameInfo->getWaypointCount()); // TODO: Cover waypoints // TODO: Flee waypoints _gameVars = new int[_gameInfo->getGlobalVarCount()]; // TODO: Actor AI DLL init // Seed rand // TODO: Sine and cosine lookup tables for intervals of 1.0, 4.0, and 12.0 _view = new View(); _sceneObjects = new SceneObjects(this, _view); _gameFlags = new GameFlags(); _gameFlags->setFlagCount(_gameInfo->getFlagCount()); _items = new Items(this); // Setup sound output _audioPlayer = new AudioPlayer(this); // TODO: Audio: Music _audioSpeech = new AudioSpeech(this); _ambientSounds = new AmbientSounds(this); // TODO: Read BLADE.INI _chapters = new Chapters(this); if (!_chapters) return false; if (!openArchive("MUSIC.MIX")) return false; if (!openArchive("SFX.MIX")) return false; if (!openArchive("SPCHSFX.TLK")) return false; // TODO: Video overlays // TODO: Proper ZBuf class _zBuffer1 = new uint16[640 * 480]; _zBuffer2 = new uint16[640 * 480]; int actorCount = (int)_gameInfo->getActorCount(); assert(actorCount < 99); for (int i = 0; i != actorCount; ++i) { _actors[i] = new Actor(this, i); _actors[i]->setup(i); } _voiceoverActor = new Actor(this, 99); _playerActor = _actors[_gameInfo->getPlayerId()]; _playerActor->setFPS(15); // TODO: set _playerActor countdown timer 6 // TODO: Set actor ids (redundant?) // TODO: Police Maze _textActorNames = new TextResource(this); if (!_textActorNames->open("ACTORS")) return false; _textCrimes = new TextResource(this); if (!_textCrimes->open("CRIMES")) return false; _textCluetype = new TextResource(this); if (!_textCluetype->open("CLUETYPE")) return false; _textKIA = new TextResource(this); if (!_textKIA->open("KIA")) return false; _textSpindest = new TextResource(this); if (!_textSpindest->open("SPINDEST")) return false; _textVK = new TextResource(this); if (!_textVK->open("VK")) return false; _textOptions = new TextResource(this); if (!_textOptions->open("OPTIONS")) return false; // TODO: Dialogue Menu (DLGMENU.TRE) _suspectsDatabase = new SuspectsDatabase(this, _gameInfo->getSuspectsDatabaseSize()); // TODO: KIA // TODO: Spinner Interface // TODO: Elevators // TODO: Scores _mainFont = new Font(this); _mainFont->open("KIA6PT.FON", 640, 480, -1, 0, 0x252D); _mainFont->setSpacing(1, 0); for (int i = 0; i != 43; ++i) { Shape *shape = new Shape(this); shape->readFromContainer("SHAPES.SHP", i); _shapes.push_back(shape); } // TODO: Esper // TODO: VK _mouse = new Mouse(this); // _mouse->setCursorPosition(320, 240); _mouse->setCursor(0); _sliceAnimations = new SliceAnimations(this); r = _sliceAnimations->open("INDEX.DAT"); if (!r) return false; // TODO: Support cdframes r = _sliceAnimations->openHDFrames(); if (!r) return false; r = _sliceAnimations->openCoreAnim(); if (!r) return false; _sliceRenderer = new SliceRenderer(this); _crimesDatabase = new CrimesDatabase(this, "CLUES", _gameInfo->getClueCount()); // TODO: Scene _scene = new Scene(this); // Load INIT.DLL ScriptInit initScript(this); initScript.SCRIPT_Initialize_Game(); // TODO: Load AI-ACT1.DLL _aiScripts = new AIScripts(this); initChapterAndScene(); return true; } void BladeRunnerEngine::initChapterAndScene() { // TODO: Init actors... for (int i = 0, end = _gameInfo->getActorCount(); i != end; ++i) _aiScripts->Initialize(i); for (int i = 0, end = _gameInfo->getActorCount(); i != end; ++i) _actors[i]->changeAnimationMode(0); _settings->setChapter(1); _settings->setNewSetAndScene(_gameInfo->getInitialSetId(), _gameInfo->getInitialSceneId()); } void BladeRunnerEngine::shutdown() { _mixer->stopAll(); // TODO: Write BLADE.INI // TODO: Shutdown VK // TODO: Shutdown Esper delete _mouse; _mouse = nullptr; for (uint i = 0; i != _shapes.size(); ++i) { delete _shapes[i]; } _shapes.clear(); // TODO: Shutdown Scene delete _scene; if (_chapters) { if (_chapters->hasOpenResources()) _chapters->closeResources(); delete _chapters; _chapters = nullptr; } delete _crimesDatabase; _crimesDatabase = nullptr; delete _sliceRenderer; _sliceRenderer = nullptr; delete _sliceAnimations; _sliceAnimations = nullptr; delete _textActorNames; _textActorNames = nullptr; delete _textCrimes; _textCrimes = nullptr; delete _textCluetype; _textCluetype = nullptr; delete _textKIA; _textKIA = nullptr; delete _textSpindest; _textSpindest = nullptr; delete _textVK; _textVK = nullptr; delete _textOptions; _textOptions = nullptr; // TODO: Delete dialogue menu delete _ambientSounds; // TODO: Delete overlays delete _audioSpeech; // TODO: Delete Audio: Music delete _audioPlayer; // Shutdown sound output if (isArchiveOpen("MUSIC.MIX")) closeArchive("MUSIC.MIX"); if (isArchiveOpen("SFX.MIX")) closeArchive("SFX.MIX"); if (isArchiveOpen("SPCHSFX.TLK")) closeArchive("SPCHSFX.TLK"); if (_mainFont) { _mainFont->close(); delete _mainFont; _mainFont = nullptr; } delete _items; _items = nullptr; delete _gameFlags; _gameFlags = nullptr; delete _view; _view = nullptr; delete _sceneObjects; _sceneObjects = nullptr; // TODO: Delete sine and cosine lookup tables // TODO: Unload AI dll delete[] _gameVars; _gameVars = nullptr; delete _waypoints; _waypoints = nullptr; // TODO: Delete Cover waypoints // TODO: Delete Flee waypoints // TODO: Delete Scores // TODO: Delete Elevators // TODO: Delete Spinner Interface // TODO: Delete KIA delete _suspectsDatabase; _suspectsDatabase = nullptr; // TODO: Delete datetime - not used // TODO: Delete actors // TODO: Delete proper ZBuf class delete[] _zBuffer1; _zBuffer1 = nullptr; delete[] _zBuffer2; _zBuffer2 = nullptr; delete _gameInfo; _gameInfo = nullptr; // TODO: Delete graphics surfaces here _surface1.free(); _surface2.free(); if (isArchiveOpen("STARTUP.MIX")) closeArchive("STARTUP.MIX"); // TODO: Delete MIXArchives here // TODO: Delete Timer } bool BladeRunnerEngine::loadSplash() { Image img(this); if (!img.open("SPLASH.IMG")) return false; img.copyToSurface(&_surface1); _system->copyRectToScreen(_surface1.getPixels(), _surface1.pitch, 0, 0, _surface1.w, _surface1.h); _system->updateScreen(); return true; } bool BladeRunnerEngine::init2() { return true; } void BladeRunnerEngine::gameLoop() { _gameIsRunning = true; do { /* TODO: check player death */ gameTick(); } while (_gameIsRunning); } #if _DEBUG void drawBBox(Vector3 start, Vector3 end, View *view, Graphics::Surface *surface, int color) { Vector3 bfl = view->calculateScreenPosition(Vector3(start.x, start.y, start.z)); Vector3 bfr = view->calculateScreenPosition(Vector3(start.x, end.y, start.z)); Vector3 bbr = view->calculateScreenPosition(Vector3(end.x, end.y, start.z)); Vector3 bbl = view->calculateScreenPosition(Vector3(end.x, start.y, start.z)); Vector3 tfl = view->calculateScreenPosition(Vector3(start.x, start.y, end.z)); Vector3 tfr = view->calculateScreenPosition(Vector3(start.x, end.y, end.z)); Vector3 tbr = view->calculateScreenPosition(Vector3(end.x, end.y, end.z)); Vector3 tbl = view->calculateScreenPosition(Vector3(end.x, start.y, end.z)); surface->drawLine(bfl.x, bfl.y, bfr.x, bfr.y, color); surface->drawLine(bfr.x, bfr.y, bbr.x, bbr.y, color); surface->drawLine(bbr.x, bbr.y, bbl.x, bbl.y, color); surface->drawLine(bbl.x, bbl.y, bfl.x, bfl.y, color); surface->drawLine(tfl.x, tfl.y, tfr.x, tfr.y, color); surface->drawLine(tfr.x, tfr.y, tbr.x, tbr.y, color); surface->drawLine(tbr.x, tbr.y, tbl.x, tbl.y, color); surface->drawLine(tbl.x, tbl.y, tfl.x, tfl.y, color); surface->drawLine(bfl.x, bfl.y, tfl.x, tfl.y, color); surface->drawLine(bfr.x, bfr.y, tfr.x, tfr.y, color); surface->drawLine(bbr.x, bbr.y, tbr.x, tbr.y, color); surface->drawLine(bbl.x, bbl.y, tbl.x, tbl.y, color); } #endif void BladeRunnerEngine::gameTick() { handleEvents(); if (_gameIsRunning && _windowIsActive) { // TODO: Only run if not in Kia, script, nor AI _settings->openNewScene(); // TODO: Autosave // TODO: Kia // TODO: Spinner // TODO: Esper // TODO: VK // TODO: Elevators // TODO: Scores _adq->tick(); if (_scene->didPlayerWalkIn()) { _script->PlayerWalkedIn(); } // TODO: Gun range announcements // TODO: ZBUF repair dirty rects _ambientSounds->tick(); bool backgroundChanged = false; int frame = _scene->advanceFrame(_surface1, _zBuffer1); if (frame >= 0) { _script->SceneFrameAdvanced(frame); backgroundChanged = true; } (void)backgroundChanged; _surface2.copyFrom(_surface1); memcpy(_zBuffer2, _zBuffer1, 640 * 480 * 2); #if 0 { for (int y = 0; y != 480; ++y) { for (int x = 0; x != 640; ++x) { if (_scene->_regions->getRegionAtXY(x, y) >= 0) { uint16 *p = (uint16*)_surface2.getBasePtr(x, y); *p = 0x7C00; } if (_scene->_exits->getRegionAtXY(x, y) >= 0) { uint16 *p = (uint16*)_surface2.getBasePtr(x, y); *p = 0x7C08; } } } } #endif // TODO: Render overlays // TODO: Tick Actor AI and Timers if (_settings->getNewScene() == -1 || _script->_inScriptCounter /* || in_ai */) { _sliceRenderer->setView(*_view); // Tick and draw all actors in current set //int setId = _scene->_setId; for (int i = 0, end = _gameInfo->getActorCount(); i != end; ++i) { //if (_actors[i]->getSetId() == setId) { if (i == 0 || i == 23) { // Currently limited to McCoy and Officer Leroy _actors[i]->tick(backgroundChanged); } } _items->tick(); _itemPickup->tick(); _itemPickup->draw(); // TODO: Draw dialogue menu Common::Point p = _eventMan->getMousePos(); _mouse->tick(p.x, p.y); _mouse->draw(_surface2, p.x, p.y); // TODO: Process AUD // TODO: Footstep sound if (_walkSoundId >= 0) { const char *name = _gameInfo->getSfxTrack(_walkSoundId); _audioPlayer->playAud(name, _walkSoundVolume, _walkSoundBalance, _walkSoundBalance, 50, 0); _walkSoundId = -1; } #if 0 //draw scene objects int count = _sceneObjects->_count; if (count > 0) { for (int i = 0; i < count; i++) { SceneObject *sceneObject = &_sceneObjects->_sceneObjects[_sceneObjects->_sceneObjectsSortedByDistance[i]]; BoundingBox *bbox = &sceneObject->_boundingBox; Vector3 a, b; bbox->getXYZ(&a.x, &a.y, &a.z, &b.x, &b.y, &b.z); int color = 0b111111111111111; if (sceneObject->_sceneObjectType == SceneObjectTypeActor) { color = 0b111110000000000; } if (sceneObject->_sceneObjectType == SceneObjectTypeObject) { color = 0b011110111101111; //if (sceneObject->_isObstacle) // color += 0b100000000000000; if (sceneObject->_isClickable) color = 0b000001111100000; //if (sceneObject->_isTarget) // color += 0b000000000010000; } drawBBox(a, b, _view, &_surface2, color); //_surface2.frameRect(sceneObject->_screenRectangle, color); Vector3 pos = _view->calculateScreenPosition(0.5 * (a + b)); switch (sceneObject->_sceneObjectType) { case SceneObjectTypeActor: _mainFont->drawColor(_textActorNames->getText(sceneObject->_sceneObjectId - SCENE_OBJECTS_ACTORS_OFFSET), _surface2, pos.x, pos.y, color); break; case SceneObjectTypeItem: _mainFont->drawColor("item", _surface2, pos.x, pos.y, color); break; case SceneObjectTypeObject: _mainFont->drawColor(_scene->objectGetName(sceneObject->_sceneObjectId - SCENE_OBJECTS_OBJECTS_OFFSET), _surface2, pos.x, pos.y, color); break; } } } //draw regions for (int i = 0; i < 10; i++) { Region *region = &_scene->_regions->_regions[i]; if (!region->_present) continue; _surface2.frameRect(region->_rectangle, 0b000000000011111); } for (int i = 0; i < 10; i++) { Region *region = &_scene->_exits->_regions[i]; if (!region->_present) continue; _surface2.frameRect(region->_rectangle, 0b111111111111111); } for (int i = 0; i < _scene->_set->_walkboxCount; i++) { Walkbox *walkbox = &_scene->_set->_walkboxes[i]; for (int j = 0; j < walkbox->_vertexCount; j++) { Vector3 start = _view->calculateScreenPosition(walkbox->_vertices[j]); Vector3 end = _view->calculateScreenPosition(walkbox->_vertices[(j + 1) % walkbox->_vertexCount]); //debug("walkbox[%i][%i] = x=%f y=%f x=%f y=%f", i, j, start.x, start.y, end.x, end.y); _surface2.drawLine(start.x, start.y, end.x, end.y, 0b111111111100000); Vector3 pos = _view->calculateScreenPosition(0.5 * (start + end)); _mainFont->drawColor(walkbox->_name, _surface2, pos.x, pos.y, 0b111111111100000); } } for (int i = 0; i < (int)_lights->_lights.size(); i++) { Light *light = _lights->_lights[i]; Matrix4x3 m = light->_matrix; Vector3 pos = Vector3(m(0, 3), m(1, 3), m(2, 3)); Vector3 size = Vector3(5.0f, 5.0f, 5.0f); int colorR = (light->_color.r * 31.0f); int colorG = (light->_color.g * 31.0f); int colorB = (light->_color.b * 31.0f); int color = (colorR << 10) + (colorG << 5) + colorB; drawBBox(pos - size, pos + size, _view, &_surface2, color); } #endif _system->copyRectToScreen((const byte *)_surface2.getBasePtr(0, 0), _surface2.pitch, 0, 0, 640, 480); _system->updateScreen(); _system->delayMillis(10); } } } void BladeRunnerEngine::handleEvents() { if (shouldQuit()) { _gameIsRunning = false; return; } Common::Event event; Common::EventManager *eventMan = _system->getEventManager(); while (eventMan->pollEvent(event)) { switch (event.type) { case Common::EVENT_LBUTTONDOWN: case Common::EVENT_RBUTTONDOWN: handleMouseClick(event.mouse.x, event.mouse.y); default: ; } } } void BladeRunnerEngine::handleMouseClick(int x, int y) { if (!playerHasControl() || _mouse->isDisabled()) return; Vector3 mousePosition = _mouse->getXYZ(x, y); int isClickable; int isObstacle; int isTarget; int sceneObjectId = _sceneObjects->findByXYZ(&isClickable, &isObstacle, &isTarget, mousePosition.x, mousePosition.y, mousePosition.z, 1, 0, 1); int exitIndex = _scene->_exits->getRegionAtXY(x, y); debug("%d %d", sceneObjectId, exitIndex); if ((sceneObjectId < 0 || sceneObjectId > 73) && exitIndex >= 0) { handleMouseClickExit(x, y, exitIndex); return; } int regionIndex = _scene->_regions->getRegionAtXY(x, y); if (regionIndex >= 0) { handleMouseClickRegion(x, y, regionIndex); return; } if (sceneObjectId == -1) { bool isRunning; _playerActor->loopWalkToXYZ(mousePosition, 0, false, false, false, &isRunning); debug("Clicked on nothing %f, %f, %f", mousePosition.x, mousePosition.y, mousePosition.z); return; } else if (sceneObjectId >= 0 && sceneObjectId <= 73) { handleMouseClickActor(x, y, sceneObjectId); return; } else if (sceneObjectId >= 74 && sceneObjectId <= 197) { handleMouseClickItem(x, y, sceneObjectId - 74); return; } else if (sceneObjectId >= 198 && sceneObjectId <= 293) { handleMouseClick3DObject(x, y, sceneObjectId - 198, isClickable, isTarget); return; } } void BladeRunnerEngine::handleMouseClickExit(int x, int y, int exitIndex) { // clickedOnExit(exitType, x, y); debug("clicked on exit %d %d %d", exitIndex, x, y); _script->ClickedOnExit(exitIndex); } void BladeRunnerEngine::handleMouseClickRegion(int x, int y, int regionIndex) { debug("clicked on region %d %d %d", regionIndex, x, y); _script->ClickedOn2DRegion(regionIndex); } void BladeRunnerEngine::handleMouseClick3DObject(int x, int y, int objectId, bool isClickable, bool isTarget) { const char *objectName = _scene->objectGetName(objectId); debug("Clicked on object %s", objectName); _script->ClickedOn3DObject(objectName, false); } void BladeRunnerEngine::handleMouseClickItem(int x, int y, int itemId) { debug("Clicked on item %d", itemId); _script->ClickedOnItem(itemId, false); } void BladeRunnerEngine::handleMouseClickActor(int x, int y, int actorId) { debug("Clicked on actor %d", actorId); _script->ClickedOnActor(actorId); } void BladeRunnerEngine::gameWaitForActive() { while (!_windowIsActive) { handleEvents(); } } void BladeRunnerEngine::loopActorSpeaking() { if (!_audioSpeech->isPlaying()) return; playerLosesControl(); do { gameTick(); } while (_gameIsRunning && _audioSpeech->isPlaying()); playerGainsControl(); } void BladeRunnerEngine::outtakePlay(int id, bool noLocalization, int container) { Common::String name = _gameInfo->getOuttake(id); OuttakePlayer player(this); player.play(name, noLocalization, container); } bool BladeRunnerEngine::openArchive(const Common::String &name) { uint i; // If archive is already open, return true for (i = 0; i != kArchiveCount; ++i) { if (_archives[i].isOpen() && _archives[i].getName() == name) return true; } // Find first available slot for (i = 0; i != kArchiveCount; ++i) { if (!_archives[i].isOpen()) break; } if (i == kArchiveCount) { /* TODO: BLADE.EXE retires the least recently used * archive when it runs out of slots. */ error("openArchive: No more archive slots"); return false; } _archives[i].open(name); return _archives[i].isOpen(); } bool BladeRunnerEngine::closeArchive(const Common::String &name) { for (uint i = 0; i != 10; ++i) { if (_archives[i].isOpen() && _archives[i].getName() == name) { _archives[i].close(); return true; } } debug("closeArchive: Archive %s not open.", name.c_str()); return false; } bool BladeRunnerEngine::isArchiveOpen(const Common::String &name) { for (uint i = 0; i != 10; ++i) { if (_archives[i].isOpen() && _archives[i].getName() == name) return true; } return false; } Common::SeekableReadStream *BladeRunnerEngine::getResourceStream(const Common::String &name) { for (uint i = 0; i != 10; ++i) { if (!_archives[i].isOpen()) continue; if (false) debug("getResource: Searching archive %s for %s.", _archives[i].getName().c_str(), name.c_str()); Common::SeekableReadStream *stream = _archives[i].createReadStreamForMember(name); if (stream) return stream; } debug("getResource: Resource %s not found.", name.c_str()); return 0; } bool BladeRunnerEngine::playerHasControl() { return _playerLosesControlCounter == 0; } void BladeRunnerEngine::playerLosesControl() { if (++_playerLosesControlCounter == 1) { _mouse->disable(); } // debug("Player Lost Control (%d)", _playerLosesControlCounter); } void BladeRunnerEngine::playerGainsControl() { if (_playerLosesControlCounter == 0) { warning("Unbalanced call to BladeRunnerEngine::playerGainsControl"); } if (_playerLosesControlCounter > 0) --_playerLosesControlCounter; // debug("Player Gained Control (%d)", _playerLosesControlCounter); if (_playerLosesControlCounter == 0) { _mouse->enable(); } } void BladeRunnerEngine::ISez(const char *str) { debug("\t%s", str); } } // End of namespace BladeRunner