/* 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 3 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, see . * */ #ifndef BLADERUNNER_BLADERUNNER_H #define BLADERUNNER_BLADERUNNER_H #include "bladerunner/archive.h" #include "common/array.h" #include "common/random.h" #include "common/stream.h" #include "common/keyboard.h" #include "common/events.h" #include "engines/engine.h" #include "graphics/surface.h" #include "math/cosinetables.h" #include "math/sinetables.h" //TODO: change this to debugflag #define BLADERUNNER_DEBUG_CONSOLE 0 #define BLADERUNNER_ORIGINAL_SETTINGS 0 #define BLADERUNNER_ORIGINAL_BUGS 0 namespace Common { class Archive; struct Event; } namespace GUI { class Debugger; } struct ADGameDescription; namespace BladeRunner { enum DebugLevels { kDebugScript = 1, kDebugSound, kDebugAnimation, }; class Actor; class ActorDialogueQueue; class ScreenEffects; class AIScripts; class AmbientSounds; class AudioCache; class AudioMixer; class AudioPlayer; class AudioSpeech; class Chapters; class CrimesDatabase; class Combat; class Debugger; class DialogueMenu; class Elevator; class EndCredits; class ESPER; class Framelimiter; class Font; class GameFlags; class GameInfo; class ItemPickup; class Items; class KIA; class Lights; class Mouse; class Music; class Obstacles; class Overlays; class PoliceMaze; class Scene; class SceneObjects; class SceneScript; class Scores; class Settings; class Shapes; class SliceAnimations; class SliceRenderer; class Spinner; class Subtitles; class SuspectsDatabase; class TextResource; class Time; class Vector3; class View; class VK; class Waypoints; class ZBuffer; class BladeRunnerEngine : public Engine { public: static const int kArchiveCount = 12; // +2 to original value (10) to accommodate for SUBTITLES.MIX and one extra resource file, to allow for capability of loading all VQAx.MIX and the MODE.MIX file (debug purposes) static const int kActorCount = 100; static const int kActorVoiceOver = kActorCount - 1; static const int kMaxCustomConcurrentRepeatableEvents = 20; static const int16 kOriginalGameWidth = 640; static const int16 kOriginalGameHeight = 480; static const int16 kDemoGameWidth = 320; static const int16 kDemoGameHeight = 200; // Incremental number to keep track of significant revisions of the ScummVM bladerunner engine // that could potentially introduce incompatibilities with old save files or require special actions to restore compatibility // This is stored in game global variable "kVariableGameVersion" // Original (classic) save game files will have version number of 0 // Values: // 1: alpha testing (from May 15, 2019 to July 17, 2019) // 2: all time code uses uint32 (since July 17 2019), static const int kBladeRunnerScummVMVersion = 2; static const char *kGameplayKeymapId; static const char *kKiaKeymapId; static const char *kCommonKeymapId; bool _gameIsRunning; bool _windowIsActive; int _playerLosesControlCounter; int _extraCPos; uint8 _extraCNotify; Common::String _languageCode; Common::Language _language; bool _russianCP1251; bool _noMusicDriver; // If "Music Device" is set to "No Music" from Audio tab ActorDialogueQueue *_actorDialogueQueue; ScreenEffects *_screenEffects; AIScripts *_aiScripts; AmbientSounds *_ambientSounds; AudioCache *_audioCache; AudioMixer *_audioMixer; AudioPlayer *_audioPlayer; AudioSpeech *_audioSpeech; Chapters *_chapters; CrimesDatabase *_crimesDatabase; Combat *_combat; DialogueMenu *_dialogueMenu; Elevator *_elevator; EndCredits *_endCredits; ESPER *_esper; GameFlags *_gameFlags; GameInfo *_gameInfo; ItemPickup *_itemPickup; Items *_items; KIA *_kia; Lights *_lights; Font *_mainFont; Subtitles *_subtitles; Mouse *_mouse; Music *_music; Obstacles *_obstacles; Overlays *_overlays; PoliceMaze *_policeMaze; Scene *_scene; SceneObjects *_sceneObjects; SceneScript *_sceneScript; Scores *_scores; Settings *_settings; SliceAnimations *_sliceAnimations; SliceRenderer *_sliceRenderer; Spinner *_spinner; SuspectsDatabase *_suspectsDatabase; Time *_time; View *_view; Framelimiter *_framelimiter; VK *_vk; Waypoints *_waypoints; int *_gameVars; TextResource *_textActorNames; TextResource *_textCrimes; TextResource *_textClueTypes; TextResource *_textKIA; TextResource *_textSpinnerDestinations; TextResource *_textVK; TextResource *_textOptions; Shapes *_shapes; Actor *_actors[kActorCount]; Actor *_playerActor; Graphics::PixelFormat _screenPixelFormat; Graphics::Surface _surfaceFront; Graphics::Surface _surfaceBack; bool _surfaceFrontCreated; bool _surfaceBackCreated; ZBuffer *_zbuffer; Common::RandomSource _rnd; uint32 _newGameRandomSeed; Debugger *_debugger; Math::CosineTable *_cosTable1024; Math::SineTable *_sinTable1024; bool _isWalkingInterruptible; bool _interruptWalking; bool _playerActorIdle; bool _playerDead; bool _actorIsSpeaking; bool _actorSpeakStopIsRequested; bool _gameOver; bool _gameJustLaunched; int _gameAutoSaveTextId; bool _gameIsAutoSaving; bool _gameIsLoading; bool _sceneIsLoading; bool _vqaIsPlaying; bool _vqaStopIsRequested; bool _subtitlesEnabled; // tracks the state of whether subtitles are enabled or disabled from ScummVM GUI option or KIA checkbox (the states are synched) bool _showSubtitlesForTextCrawl; bool _sitcomMode; bool _shortyMode; bool _noDelayMillisFramelimiter; bool _framesPerSecondMax; bool _disableStaminaDrain; bool _spanishCreditsCorrection; bool _cutContent; bool _enhancedEdition; bool _validBootParam; int _walkSoundId; int _walkSoundVolume; int _walkSoundPan; int _runningActorId; uint32 _mouseClickTimeLast; uint32 _mouseClickTimeDiff; int _walkingToExitId; bool _isInsideScriptExit; int _walkingToRegionId; bool _isInsideScriptRegion; int _walkingToObjectId; bool _isInsideScriptObject; int _walkingToItemId; bool _isInsideScriptItem; bool _walkingToEmpty; int _walkingToEmptyX; int _walkingToEmptyY; bool _isInsideScriptEmpty; int _walkingToActorId; bool _isInsideScriptActor; int _actorUpdateCounter; uint32 _actorUpdateTimeLast; uint32 _timeOfMainGameLoopTickPrevious; bool _isNonInteractiveDemo; // This addon is to emulate keeping a keyboard key pressed (continuous / repeated firing of the event) // -- code is pretty much identical from our common\events.cpp (KeyboardRepeatEventSourceWrapper) // for continuous events (keyDown) enum { kKeyRepeatInitialDelay = 400, kKeyRepeatSustainDelay = 100 }; Common::KeyState _currentKeyDown; uint32 _keyRepeatTimeLast; uint32 _keyRepeatTimeDelay; uint32 _customEventRepeatTimeLast; uint32 _customEventRepeatTimeDelay; typedef Common::Array ActiveCustomEventsArray; // We do allow keys mapped to the same event, // so eg. a key (Enter) could cause 2 or more events to fire, // However, we should probably restrict the active events // (that can be repeated while holding the mapped keys down) // to a maximum of kMaxCustomConcurrentRepeatableEvents ActiveCustomEventsArray _activeCustomEvents; // NOTE We still need keyboard functionality for naming saved games and also for the KIA Easter eggs. // In KIA keyboard events should be accounted where possible - however some keymaps are still needed // which is why we have the three separate common, gameplay-only and kia-only keymaps. // If a valid keyboard key character eg. ("A") for text input (or Easter egg input) // is also mapped to a common or KIA only custom event, then the custom event will be effected and not the key input. // NOTE We don't use a custom action for left click -- we just use the standard left click action event (kStandardActionLeftClick) // NOTE Dialogue Skip does not work for dialogue replayed when clicking on KIA clues (this is the original's behavior too) // NOTE Toggle KIA options does not work when McCoy is walking towards a character when the player clicks on McCoy // (this is the original's behavior too). // "Esc" (by default) or the mapped key to this action still works though. // NOTE A drawback of using customized keymapper for the game is that we can no longer replicate the original's behavior // whereby holding down would cause McCoy to keep switching quickly between combat mode and normal mode. // This is because the original, when holding down right mouse button, would just toggle McCoy's mode once. // We keep the behavior for "right mouse button". // The continuous fast toggle behavior when holding down feels more like a bug anyway. // NOTE In the original, the KP_PERIOD key with NUMLOCK on, would work as a normal '.' character // in the KIA Save Game screen. With NUMLOCK off, it would work as a delete request for the selected entry. // However, NUMLOCK is currently not working as a modifier key for keymaps, // so maybe we can implement the original behavior more accurately, // when that is fixed in the keymapper or hardware-input code. // For now, KP_PERIOD will work (by default) as a delete request. enum BladeRunnerEngineMappableAction { // kMpActionLeftClick, // default (select, walk-to, run-to, look-at, talk-to, use, shoot (combat mode), KIA (click on McCoy)) kMpActionToggleCombat, // default or kMpActionCutsceneSkip, // default or or or kMpActionDialogueSkip, // default or kMpActionToggleKiaOptions, // default opens/closes KIA, in Options tab kMpActionOpenKiaDatabase, // default - only opens KIA (if closed), in one of the database tabs (the last active one, or else the first) kMpActionOpenKIATabHelp, // default kMpActionOpenKIATabSaveGame, // default kMpActionOpenKIATabLoadGame, // default kMpActionOpenKIATabCrimeSceneDatabase, // default kMpActionOpenKIATabSuspectDatabase, // default kMpActionOpenKIATabClueDatabase, // default kMpActionOpenKIATabQuitGame, // default kMpActionScrollUp, // ScummVM addition (scroll list up) kMpActionScrollDown, // ScummVM addition (scroll list down) kMpConfirmDlg, // default or kMpDeleteSelectedSvdGame, // default or kMpActionToggleCluePrivacy // default }; private: MIXArchive _archives[kArchiveCount]; Common::Archive *_archive; public: BladeRunnerEngine(OSystem *syst, const ADGameDescription *desc); ~BladeRunnerEngine() override; bool hasFeature(EngineFeature f) const override; bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override; Common::Error loadGameState(int slot) override; bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override; Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override; /** * NOTE: Disable support for external autosave (ScummVM's feature). * Main reason is that it's not easy to properly label this autosave, * since currently it would translate "Autosave" to the ScummVM GUI language * which ends up showing as "?????? ??????" on non-latin languages (eg. Greek), * and in addition is inconsistent with the game's own GUI language. * Secondary reason is that the game already has an autosaving mechanism, * albeit only at the start of a new Act. * And final reason would be to prevent an autosave at an unforeseen moment, * if we've failed to account for all cases that the game should not save by itself; * currently those are listed in BladeRunnerEngine::canSaveGameStateCurrently(). */ int getAutosaveSlot() const override { return -1; } void pauseEngineIntern(bool pause) override; Common::Error run() override; bool checkFiles(Common::Array &missingFiles); bool startup(bool hasSavegames = false); void initChapterAndScene(); void shutdown(); bool loadSplash(); Common::Point getMousePos() const; bool isMouseButtonDown() const; void gameLoop(); void gameTick(); void actorsUpdate(); void walkingReset(); void handleEvents(); void handleKeyUp(Common::Event &event); void handleKeyDown(Common::Event &event); void handleMouseAction(int x, int y, bool mainButton, bool buttonDown, int scrollDirection = 0); void handleMouseClickExit(int exitId, int x, int y, bool buttonDown); void handleMouseClickRegion(int regionId, int x, int y, bool buttonDown); void handleMouseClickItem(int itemId, bool buttonDown); void handleMouseClickActor(int actorId, bool mainButton, bool buttonDown, Vector3 &scenePosition, int x, int y); void handleMouseClick3DObject(int objectId, bool buttonDown, bool isClickable, bool isTarget); void handleMouseClickEmpty(int x, int y, Vector3 &scenePosition, bool buttonDown); bool isAllowedRepeatedKey(const Common::KeyState &currKeyState); void handleCustomEventStart(Common::Event &event); void handleCustomEventStop(Common::Event &event); bool isAllowedRepeatedCustomEvent(const Common::Event &currEvent); bool shouldDropRogueCustomEvent(const Common::Event &evt); void cleanupPendingRepeatingEvents(const Common::String &keymapperId); void gameWaitForActive(); void loopActorSpeaking(); void loopQueuedDialogueStillPlaying(); void outtakePlay(int id, bool no_localization, int container = -1); void outtakePlay(const Common::String &basenameNoExt, bool no_localization, int container = -3); bool openArchive(const Common::String &name); bool closeArchive(const Common::String &name); bool isArchiveOpen(const Common::String &name) const; bool openArchiveEnhancedEdition(); void syncSoundSettings() override; bool isSubtitlesEnabled(); void setSubtitlesEnabled(bool newVal); Common::SeekableReadStream *getResourceStream(const Common::String &name); bool playerHasControl(); void playerLosesControl(); void playerGainsControl(bool force = false); void playerDied(); bool saveGame(Common::WriteStream &stream, Graphics::Surface *thumb = NULL, bool origformat = false); bool loadGame(Common::SeekableReadStream &stream, int version); void newGame(int difficulty); void autoSaveGame(int textId, bool endgame); void ISez(const Common::String &str); void blitToScreen(const Graphics::Surface &src) const; Graphics::Surface generateThumbnail() const; Common::String getTargetName() const; uint8 getExtraCNotify(); void setExtraCNotify(uint8 val); }; static inline const Graphics::PixelFormat gameDataPixelFormat() { return Graphics::PixelFormat(2, 5, 5, 5, 1, 10, 5, 0, 15); } static inline void getGameDataColor(uint16 color, uint8 &a, uint8 &r, uint8 &g, uint8 &b) { // gameDataPixelFormat().colorToARGB(vqaColor, a, r, g, b); // using pixel format functions is too slow on some ports because of runtime checks uint8 r5 = (color >> 10) & 0x1F; uint8 g5 = (color >> 5) & 0x1F; uint8 b5 = (color ) & 0x1F; a = color >> 15; r = (r5 << 3) | (r5 >> 2); g = (g5 << 3) | (g5 >> 2); b = (b5 << 3) | (b5 >> 2); } static inline const Graphics::PixelFormat screenPixelFormat() { return ((BladeRunnerEngine*)g_engine)->_screenPixelFormat; } static inline void drawPixel(Graphics::Surface &surface, void* dst, uint32 value) { switch (surface.format.bytesPerPixel) { case 1: *(uint8*)dst = (uint8)value; break; case 2: *(uint16*)dst = (uint16)value; break; case 4: *(uint32*)dst = (uint32)value; break; default: break; } } static inline void getPixel(Graphics::Surface &surface, void* dst, uint32 &value) { switch (surface.format.bytesPerPixel) { case 1: value = (uint8)(*(uint8*)dst); break; case 2: value = (uint16)(*(uint16*)dst); break; case 4: value = (uint32)(*(uint32*)dst); break; default: break; } } void blit(const Graphics::Surface &src, Graphics::Surface &dst); } // End of namespace BladeRunner #endif