BLADERUNNER: Use keymapper with proper events for the game

This commit is contained in:
antoniou79 2022-04-03 22:47:04 +03:00
parent 6994d79e59
commit 6c300519b1
11 changed files with 598 additions and 97 deletions

View File

@ -1,3 +1,4 @@
engines/bladerunner/bladerunner.cpp
engines/bladerunner/detection.cpp
engines/bladerunner/detection_tables.h
engines/bladerunner/metaengine.cpp

View File

@ -76,6 +76,9 @@
#include "bladerunner/waypoints.h"
#include "bladerunner/zbuffer.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
#include "common/array.h"
#include "common/config-manager.h"
#include "common/error.h"
@ -95,6 +98,10 @@
namespace BladeRunner {
const char *BladeRunnerEngine::kGameplayKeymapId = "bladerunner-gameplay";
const char *BladeRunnerEngine::kKiaKeymapId = "bladerunner-kia";
const char *BladeRunnerEngine::kCommonKeymapId = "bladerunner-common";
BladeRunnerEngine::BladeRunnerEngine(OSystem *syst, const ADGameDescription *desc)
: Engine(syst),
_rnd("bladerunner") {
@ -233,6 +240,10 @@ BladeRunnerEngine::BladeRunnerEngine(OSystem *syst, const ADGameDescription *des
_currentKeyDown.keycode = Common::KEYCODE_INVALID;
_keyRepeatTimeLast = 0;
_keyRepeatTimeDelay = 0;
_activeCustomEvents->clear();
_customEventRepeatTimeLast = 0;
_customEventRepeatTimeDelay = 0;
}
BladeRunnerEngine::~BladeRunnerEngine() {
@ -380,6 +391,21 @@ Common::Error BladeRunnerEngine::run() {
}
// end of additional code for gracefully handling end-game
if (getEventManager()->getKeymapper() != nullptr) {
if (getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kCommonKeymapId) != nullptr)
getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kCommonKeymapId)->setEnabled(true);
if (getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kGameplayKeymapId) != nullptr)
getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kGameplayKeymapId)->setEnabled(true);
if (getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kKiaKeymapId) != nullptr) {
// When disabling a keymap, make sure all their active events in the _activeCustomEvents array
// are cleared, because as they won't get an explicit "EVENT_CUSTOM_ENGINE_ACTION_END" event.
cleanupPendingRepeatingEvents(BladeRunnerEngine::kKiaKeymapId);
getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kKiaKeymapId)->setEnabled(false);
}
}
if (_validBootParam) {
// clear the flag, so that after a possible game gameOver / end-game
// it won't be true again; just to be safe and avoid potential side-effects
@ -1262,18 +1288,46 @@ void BladeRunnerEngine::walkingReset() {
_isInsideScriptActor = false;
}
bool BladeRunnerEngine::isAllowedRepeatedCustomEvent(const Common::Event &currevent) {
switch (currevent.type) {
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
switch ((BladeRunnerEngineMappableAction)currevent.customType) {
case kMpblActionCutsceneSkip:
// fall through
case kMpActionDialogueSkip:
// fall through
case kMpActionToggleKiaOptions:
return true;
default:
return false;
}
break;
default:
break;
}
return false;
}
// The original allowed a few keyboard keys to be repeated ("key spamming")
// namely, Esc, Return and Space during normal gameplay.
// "Space" spamming results in McCoy quickly switching between combat mode and normal mode,
// which is not very useful but it's the original's behavior.
// Spamming Space, backspace, latin letter keys and symbols is allowed
// in KIA mode, particularly in the Save Panel when writing the name for a save game.
// "Spacebar" spamming would result in McCoy quickly switching between combat mode and normal mode,
// which is not very useful -- by introducing the keymapper with custom action events this behavior is no longer replicated.
// Spamming Space, backspace, latin letter keys and symbols is allowed in KIA mode,
// particularly in the Save Panel when typing in the name for a save game.
// For simplicity, we allow everything after the 0x20 (space ascii code) up to 0xFF.
// The UIInputBox::charIsValid() will filter out any unsupported characters.
// F-keys are not repeated.
bool BladeRunnerEngine::isAllowedRepeatedKey(const Common::KeyState &currKeyState) {
return currKeyState.keycode == Common::KEYCODE_ESCAPE
|| currKeyState.keycode == Common::KEYCODE_RETURN
// Return and KP_Enter keys are repeatable in KIA.
// This is noticable when choosing an already saved game to overwrite
// and holding down Enter would cause the confirmation dialogue to pop up
// and it would subsequently confirm it as well.
// TODO if we introduce a custom confirm action for KIA, then that action should be repeatable
// and KEYCODE_RETURN and KEYCODE_KP_ENTER should be removed from this clause;
// the action should be added to the switch cases in isAllowedRepeatedCustomEvent()
return currKeyState.keycode == Common::KEYCODE_RETURN
|| currKeyState.keycode == Common::KEYCODE_KP_ENTER
|| currKeyState.keycode == Common::KEYCODE_BACKSPACE
|| currKeyState.keycode == Common::KEYCODE_SPACE
@ -1295,7 +1349,7 @@ void BladeRunnerEngine::handleEvents() {
// even in the case when no save games for the game exist. In such case the game is supposed
// to immediately play the intro video and subsequently start a new game of medium difficulty.
// It does not expect the player to enter KIA beforehand, which causes side-effects and unforeseen behavior.
// Note: eventually we will support the option to launch into KIA in any case,
// NOTE Eventually, we will support the option to launch into KIA in any case,
// but not via the "hack" way that is fixed here.
if (_gameJustLaunched) {
_gameJustLaunched = false;
@ -1306,12 +1360,109 @@ void BladeRunnerEngine::handleEvents() {
Common::EventManager *eventMan = _system->getEventManager();
while (eventMan->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_CUSTOM_ENGINE_ACTION_END:
if (shouldDropRogueCustomEvent(event)) {
return;
}
switch ((BladeRunnerEngineMappableAction)event.customType) {
case kMpActionToggleCombat:
handleMouseAction(event.mouse.x, event.mouse.y, false, false);
break;
case kMpblActionCutsceneSkip:
// fall through
case kMpActionDialogueSkip:
// fall through
case kMpActionToggleKiaOptions:
// fall through
case kMpActionOpenKiaDatabase:
// fall through
case kMpActionOpenKIATabHelp:
// fall through
case kMpActionOpenKIATabSaveGame:
// fall through
case kMpActionOpenKIATabLoadGame:
// fall through
case kMpActionOpenKIATabCrimeSceneDatabase:
// fall through
case kMpActionOpenKIATabSuspectDatabase:
// fall through
case kMpActionOpenKIATabClueDatabase:
// fall through
case kMpActionOpenKIATabQuitGame:
handleCustomEventStop(event);
break;
default:
break;
}
break;
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
if (shouldDropRogueCustomEvent(event)) {
return;
}
// Process the initial/actual custom event only here, filter out repeats
// TODO Does this actually filter repeats?
if (!event.kbdRepeat) {
switch ((BladeRunnerEngineMappableAction)event.customType) {
case kMpActionToggleCombat:
handleMouseAction(event.mouse.x, event.mouse.y, false, true);
break;
case kMpblActionCutsceneSkip:
// fall through
case kMpActionDialogueSkip:
// fall through
case kMpActionToggleKiaOptions:
// fall through
case kMpActionOpenKiaDatabase:
// fall through
case kMpActionOpenKIATabHelp:
// fall through
case kMpActionOpenKIATabSaveGame:
// fall through
case kMpActionOpenKIATabLoadGame:
// fall through
case kMpActionOpenKIATabCrimeSceneDatabase:
// fall through
case kMpActionOpenKIATabSuspectDatabase:
// fall through
case kMpActionOpenKIATabClueDatabase:
// fall through
case kMpActionOpenKIATabQuitGame:
if (isAllowedRepeatedCustomEvent(event)
&& _activeCustomEvents->size() < kMaxCustomConcurrentRepeatableEvents) {
if (_activeCustomEvents->empty()) {
_customEventRepeatTimeLast = _time->currentSystem();
_customEventRepeatTimeDelay = kKeyRepeatInitialDelay;
}
_activeCustomEvents->push_back(event);
}
handleCustomEventStart(event);
break;
case kMpActionScrollUp:
handleMouseAction(event.mouse.x, event.mouse.y, false, false, -1);
break;
case kMpActionScrollDown:
handleMouseAction(event.mouse.x, event.mouse.y, false, false, 1);
break;
default:
break;
}
}
break;
case Common::EVENT_KEYUP:
handleKeyUp(event);
break;
case Common::EVENT_KEYDOWN:
// Process the actual key press only and filter out repeats
// Process the initial/actual key press only here, filter out repeats
// TODO Does this actually filter repeats?
if (!event.kbdRepeat) {
// Only for some keys, allow repeated firing emulation
// First hit (fire) has a bigger delay (kKeyRepeatInitialDelay) before repeated events are fired from the same key
@ -1328,30 +1479,10 @@ void BladeRunnerEngine::handleEvents() {
handleMouseAction(event.mouse.x, event.mouse.y, true, false);
break;
case Common::EVENT_RBUTTONUP:
case Common::EVENT_MBUTTONUP:
handleMouseAction(event.mouse.x, event.mouse.y, false, false);
break;
case Common::EVENT_LBUTTONDOWN:
handleMouseAction(event.mouse.x, event.mouse.y, true, true);
break;
case Common::EVENT_RBUTTONDOWN:
case Common::EVENT_MBUTTONDOWN:
handleMouseAction(event.mouse.x, event.mouse.y, false, true);
break;
// Added by ScummVM team
case Common::EVENT_WHEELUP:
handleMouseAction(event.mouse.x, event.mouse.y, false, false, -1);
break;
// Added by ScummVM team
case Common::EVENT_WHEELDOWN:
handleMouseAction(event.mouse.x, event.mouse.y, false, false, 1);
break;
default:
; // nothing to do
}
@ -1361,11 +1492,23 @@ void BladeRunnerEngine::handleEvents() {
// Some of those may lead to their own internal gameTick() loops (which will call handleEvents()).
// Thus, we need to get a new timeNow value here to ensure we're not comparing with a stale version.
uint32 timeNow = _time->currentSystem();
if (isAllowedRepeatedKey(_currentKeyDown)
&& (timeNow - _keyRepeatTimeLast >= _keyRepeatTimeDelay)) {
if (!_activeCustomEvents->empty()
&& (timeNow - _customEventRepeatTimeLast >= _customEventRepeatTimeDelay)) {
_customEventRepeatTimeLast = timeNow;
_customEventRepeatTimeDelay = kKeyRepeatSustainDelay;
for (ActiveCustomEventsArray::iterator it = _activeCustomEvents->begin(); it != _activeCustomEvents->end(); it++) {
// kbdRepeat field will be unused here since we emulate the kbd repeat behavior anyway,
// but maybe it's good to set it for consistency
it->kbdRepeat = true;
// reissue the custom start event
handleCustomEventStart(*it);
}
} else if (isAllowedRepeatedKey(_currentKeyDown)
&& (timeNow - _keyRepeatTimeLast >= _keyRepeatTimeDelay)) {
// create a "new" keydown event
event.type = Common::EVENT_KEYDOWN;
// kbdRepeat field will be unused here since we emulate the kbd repeat behavior anyway, but it's good to set it for consistency
// kbdRepeat field will be unused here since we emulate the kbd repeat behavior anyway,
// but it's good to set it for consistency
event.kbdRepeat = true;
event.kbd = _currentKeyDown;
_keyRepeatTimeLast = timeNow;
@ -1391,44 +1534,6 @@ void BladeRunnerEngine::handleKeyUp(Common::Event &event) {
}
void BladeRunnerEngine::handleKeyDown(Common::Event &event) {
if (_vqaIsPlaying
&& (event.kbd.keycode == Common::KEYCODE_RETURN
|| event.kbd.keycode == Common::KEYCODE_KP_ENTER
|| event.kbd.keycode == Common::KEYCODE_SPACE
|| event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
// Note: Original allows Esc, Spacebar and Return/KP_Enter to skip cutscenes
_vqaStopIsRequested = true;
_vqaIsPlaying = false;
return;
}
if (_vqaStopIsRequested
&& (event.kbd.keycode == Common::KEYCODE_RETURN
|| event.kbd.keycode == Common::KEYCODE_KP_ENTER
|| event.kbd.keycode == Common::KEYCODE_SPACE
|| event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
return;
}
if (_actorIsSpeaking
&& (event.kbd.keycode == Common::KEYCODE_RETURN
|| event.kbd.keycode == Common::KEYCODE_KP_ENTER
|| event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
// Note: Original only uses the Return/KP_Enter key here
_actorSpeakStopIsRequested = true;
_actorIsSpeaking = false;
return;
}
if (_actorSpeakStopIsRequested
&& (event.kbd.keycode == Common::KEYCODE_RETURN
|| event.kbd.keycode == Common::KEYCODE_KP_ENTER
|| event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
return;
}
if (!playerHasControl() || _isWalkingInterruptible || _actorIsSpeaking || _vqaIsPlaying) {
return;
}
@ -1462,38 +1567,153 @@ void BladeRunnerEngine::handleKeyDown(Common::Event &event) {
_scores->handleKeyDown(event.kbd);
return;
}
}
switch (event.kbd.keycode) {
case Common::KEYCODE_F1:
// Check if an polled event belongs to a currently disabled keymap and, if so, drop it.
bool BladeRunnerEngine::shouldDropRogueCustomEvent(const Common::Event &evt) {
if (getEventManager()->getKeymapper() != nullptr) {
Common::KeymapArray kmpsArr = getEventManager()->getKeymapper()->getKeymaps();
for (Common::KeymapArray::iterator kmpsIt = kmpsArr.begin(); kmpsIt != kmpsArr.end(); ++kmpsIt) {
if (!(*kmpsIt)->isEnabled()) {
Common::Keymap::ActionArray actionsInKm = (*kmpsIt)->getActions();
for (Common::Keymap::ActionArray::iterator kmIt = actionsInKm.begin(); kmIt != actionsInKm.end(); ++kmIt) {
if ((evt.type != Common::EVENT_INVALID) && (evt.customType == (*kmIt)->event.customType)) {
return true;
}
}
}
}
}
return false;
}
void BladeRunnerEngine::cleanupPendingRepeatingEvents(const Common::String &keymapperId) {
// Also clean up any currently repeating key down here.
// This prevents a bug where holding down Enter key in save screen with a filled in save game
// or a selected game to overwrite, would complete the save and then maintain Enter as a repeating key
// in the main gameplay (without ever sending a key up event to stop it).
_currentKeyDown.keycode = Common::KEYCODE_INVALID;
if (getEventManager()->getKeymapper() != nullptr
&& getEventManager()->getKeymapper()->getKeymap(keymapperId) != nullptr
&& !_activeCustomEvents->empty()) {
Common::Keymap::ActionArray actionsInKm = getEventManager()->getKeymapper()->getKeymap(keymapperId)->getActions();
for (Common::Keymap::ActionArray::iterator kmIt = actionsInKm.begin(); kmIt != actionsInKm.end(); ++kmIt) {
for (ActiveCustomEventsArray::iterator actIt = _activeCustomEvents->begin(); actIt != _activeCustomEvents->end(); ++actIt) {
if ((actIt->type != Common::EVENT_INVALID) && (actIt->customType == (*kmIt)->event.customType)) {
_activeCustomEvents->erase(actIt);
if (actIt == _activeCustomEvents->end()) {
break;
}
}
}
}
}
}
void BladeRunnerEngine::handleCustomEventStop(Common::Event &event) {
if (!_activeCustomEvents->empty()) {
for (ActiveCustomEventsArray::iterator it = _activeCustomEvents->begin(); it != _activeCustomEvents->end(); it++) {
if ((it->type != Common::EVENT_INVALID) && (it->customType == event.customType)) {
_activeCustomEvents->erase(it);
break;
}
}
}
if (!playerHasControl() || _isWalkingInterruptible) {
return;
}
if (_kia->isOpen()) {
_kia->handleCustomEventStop(event);
return;
}
}
void BladeRunnerEngine::handleCustomEventStart(Common::Event &event) {
if (_vqaIsPlaying && (BladeRunnerEngineMappableAction)event.customType == kMpblActionCutsceneSkip) {
_vqaStopIsRequested = true;
_vqaIsPlaying = false;
return;
}
if (_vqaStopIsRequested && (BladeRunnerEngineMappableAction)event.customType == kMpblActionCutsceneSkip) {
return;
}
if (_actorIsSpeaking && (BladeRunnerEngineMappableAction)event.customType == kMpActionDialogueSkip) {
_actorSpeakStopIsRequested = true;
_actorIsSpeaking = false;
}
if (_actorSpeakStopIsRequested && (BladeRunnerEngineMappableAction)event.customType == kMpActionDialogueSkip) {
return;
}
if (!playerHasControl() || _isWalkingInterruptible || _actorIsSpeaking || _vqaIsPlaying) {
return;
}
if (_kia->isOpen()) {
_kia->handleCustomEventStart(event);
return;
}
if (_spinner->isOpen()) {
return;
}
if (_elevator->isOpen()) {
return;
}
if (_esper->isOpen()) {
return;
}
if (_vk->isOpen()) {
return;
}
if (_dialogueMenu->isOpen()) {
return;
}
if (_scores->isOpen()) {
_scores->handleCustomEventStart(event);
return;
}
switch ((BladeRunnerEngineMappableAction)event.customType) {
case kMpActionOpenKIATabHelp:
_kia->open(kKIASectionHelp);
break;
case Common::KEYCODE_F2:
case kMpActionOpenKIATabSaveGame:
_kia->open(kKIASectionSave);
break;
case Common::KEYCODE_F3:
case kMpActionOpenKIATabLoadGame:
_kia->open(kKIASectionLoad);
break;
case Common::KEYCODE_F4:
case kMpActionOpenKIATabCrimeSceneDatabase:
_kia->open(kKIASectionCrimes);
break;
case Common::KEYCODE_F5:
case kMpActionOpenKIATabSuspectDatabase:
_kia->open(kKIASectionSuspects);
break;
case Common::KEYCODE_F6:
case kMpActionOpenKIATabClueDatabase:
_kia->open(kKIASectionClues);
break;
case Common::KEYCODE_F10:
case kMpActionOpenKIATabQuitGame:
_kia->open(kKIASectionQuit);
break;
case Common::KEYCODE_TAB:
case kMpActionOpenKiaDatabase:
_kia->openLastOpened();
break;
case Common::KEYCODE_ESCAPE:
case kMpActionToggleKiaOptions:
_kia->open(kKIASectionSettings);
break;
case Common::KEYCODE_SPACE:
_combat->change();
break;
default:
break;
}

View File

@ -30,6 +30,7 @@
#include "common/sinetables.h"
#include "common/stream.h"
#include "common/keyboard.h"
#include "common/events.h"
#include "engines/engine.h"
@ -110,6 +111,7 @@ 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;
// 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
@ -120,6 +122,10 @@ public:
// 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;
@ -265,6 +271,50 @@ public:
uint32 _keyRepeatTimeLast;
uint32 _keyRepeatTimeDelay;
uint32 _customEventRepeatTimeLast;
uint32 _customEventRepeatTimeDelay;
typedef Common::Array<Common::Event> 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[kMaxCustomConcurrentRepeatableEvents];
// 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 <SPACEBAR> 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 <SPACEBAR> feels more like a bug anyway.
enum BladeRunnerEngineMappableAction {
// kMpActionLeftClick, // default <left click> (select, walk-to, run-to, look-at, talk-to, use, shoot (combat mode), KIA (click on McCoy))
kMpActionToggleCombat, // default <right click> or <Spacebar>
kMpblActionCutsceneSkip, // default <Return> or <KP_Enter> or <Esc> or <Spacebar>
kMpActionDialogueSkip, // default <Return> or <KP_Enter>
kMpActionToggleKiaOptions, // default <Esc> opens/closes KIA, in Options tab
kMpActionOpenKiaDatabase, // default <Tab> - only opens KIA (if closed), in one of the database tabs (the last active one, or else the first)
kMpActionOpenKIATabHelp, // default <F1>
kMpActionOpenKIATabSaveGame, // default <F2>
kMpActionOpenKIATabLoadGame, // default <F3>
kMpActionOpenKIATabCrimeSceneDatabase, // default <F4>
kMpActionOpenKIATabSuspectDatabase, // default <F5>
kMpActionOpenKIATabClueDatabase, // default <F6>
kMpActionOpenKIATabQuitGame, // default <F10>
kMpActionScrollUp, // ScummVM addition (scroll list up)
kMpActionScrollDown // ScummVM addition (scroll list down)
};
private:
MIXArchive _archives[kArchiveCount];
@ -326,6 +376,13 @@ public:
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();

View File

@ -23,10 +23,15 @@
#include "bladerunner/bladerunner.h"
#include "bladerunner/savefile.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymap.h"
#include "backends/keymapper/standard-actions.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/savefile.h"
#include "common/serializer.h"
#include "common/translation.h"
#include "engines/advancedDetector.h"
@ -36,6 +41,7 @@ public:
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
bool hasFeature(MetaEngineFeature f) const override;
Common::KeymapArray initKeymaps(const char *target) const override;
SaveStateList listSaves(const char *target) const override;
int getMaximumSaveSlot() const override;
@ -66,6 +72,141 @@ bool BladeRunnerMetaEngine::hasFeature(MetaEngineFeature f) const {
f == kSimpleSavesNames;
}
Common::KeymapArray BladeRunnerMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
using namespace BladeRunner;
Common::String gameId = ConfMan.get("gameid", target);
Common::U32String gameDesc;
Keymap *commonKeymap;
Keymap *gameplayKeymap;
Keymap *kiaOnlyKeymap;
if (gameId == "bladerunner") {
gameDesc = "Blade Runner";
} else if (gameId == "bladerunner-final") {
gameDesc = "Blade Runner (Restored Content)";
}
if (gameDesc.empty()) {
return AdvancedMetaEngine::initKeymaps(target);
}
// We use 3 keymaps: common (main game and KIA), gameplay (main game only) and kia (KIA only).
// This helps us with disabling unneeded keymaps, which is especially useful in KIA, when typing in a saved game.
// In general, Blade Runner by default, can bind a key (eg. spacebar) to multiple actions
// (eg. skip cutscene, toggle combat, enter a blank space in save game input field).
// We need to be able to disable the conflicting keymaps, while keeping others that should still work in KIA
// (eg. "Esc" (by default) toggling KIA should work in normal gameplay and also within KIA).
// Another related issue we tackle is that a custom action event does not maintain the keycode and ascii value
// (if it was associated with a keyboard key), and there's no obvious way to retrieve those from it.
// Thus, a custom action event cannot be somehow utilised to produce keyboard key presses
// (again if a keyboard key is mapped to that action), so it cannot by itself be used
// for text entering in the save file name input field, or for typing the Easter Egg strings.
commonKeymap = new Keymap(Keymap::kKeymapTypeGame, BladeRunnerEngine::kCommonKeymapId, gameDesc + Common::U32String(" - ") + _("common shortcuts"));
gameplayKeymap = new Keymap(Keymap::kKeymapTypeGame, BladeRunnerEngine::kGameplayKeymapId, gameDesc + Common::U32String(" - ") + _("main game shortcuts"));
kiaOnlyKeymap = new Keymap(Keymap::kKeymapTypeGame, BladeRunnerEngine::kKiaKeymapId, gameDesc + Common::U32String(" - ") + _("KIA only shortcuts"));
Action *act;
// Look at backends\keymapper\hardware-input.cpp for the strings that can be used in InputMapping
act = new Action(kStandardActionLeftClick, _("Walk / Look / Talk / Select / Shoot"));
act->setLeftClickEvent();
act->addDefaultInputMapping("MOUSE_LEFT");
act->addDefaultInputMapping("JOY_A");
commonKeymap->addAction(act);
act = new Action("COMBAT", _("Toggle Combat"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionToggleCombat);
act->addDefaultInputMapping("MOUSE_RIGHT");
act->addDefaultInputMapping("MOUSE_MIDDLE");
act->addDefaultInputMapping("JOY_B");
act->addDefaultInputMapping("SPACE");
gameplayKeymap->addAction(act);
act = new Action("SKIPVIDEO", _("Skip cutscene"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpblActionCutsceneSkip);
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("KP_ENTER");
act->addDefaultInputMapping("SPACE");
act->addDefaultInputMapping("JOY_Y");
gameplayKeymap->addAction(act);
act = new Action("SKIPDLG", _("Skip dialogue"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionDialogueSkip);
act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("KP_ENTER");
act->addDefaultInputMapping("JOY_X");
gameplayKeymap->addAction(act);
act = new Action("KIAOPTS", _("Game Options"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionToggleKiaOptions);
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("JOY_Y");
commonKeymap->addAction(act);
act = new Action("KIADB", _("Open KIA Database"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKiaDatabase);
act->addDefaultInputMapping("TAB");
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
gameplayKeymap->addAction(act);
act = new Action("KIASCROLLUP", _("Scroll Up"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionScrollUp);
act->addDefaultInputMapping("MOUSE_WHEEL_UP");
act->addDefaultInputMapping("JOY_UP");
kiaOnlyKeymap->addAction(act);
act = new Action("KIASCROLLDOWN", _("Scroll Down"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionScrollDown);
act->addDefaultInputMapping("MOUSE_WHEEL_DOWN");
act->addDefaultInputMapping("JOY_DOWN");
kiaOnlyKeymap->addAction(act);
act = new Action("KIAHLP", _("Help"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabHelp);
act->addDefaultInputMapping("F1");
commonKeymap->addAction(act);
act = new Action("KIASAVE", _("Save Game"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabSaveGame);
act->addDefaultInputMapping("F2");
commonKeymap->addAction(act);
act = new Action("KIALOAD", _("Load Game"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabLoadGame);
act->addDefaultInputMapping("F3");
commonKeymap->addAction(act);
act = new Action("KIACRIMES", _("Crime Scene Database"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabCrimeSceneDatabase);
act->addDefaultInputMapping("F4");
commonKeymap->addAction(act);
act = new Action("KIASUSPECTS", _("Suspect Database"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabSuspectDatabase);
act->addDefaultInputMapping("F5");
commonKeymap->addAction(act);
act = new Action("KIACLUES", _("Clue Database"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabClueDatabase);
act->addDefaultInputMapping("F6");
commonKeymap->addAction(act);
act = new Action("KIAQUIT", _("Quit Game"));
act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionOpenKIATabQuitGame);
act->addDefaultInputMapping("F10");
commonKeymap->addAction(act);
KeymapArray keymaps(3);
keymaps[0] = commonKeymap;
keymaps[1] = gameplayKeymap;
keymaps[2] = kiaOnlyKeymap;
return keymaps;
}
SaveStateList BladeRunnerMetaEngine::listSaves(const char *target) const {
return BladeRunner::SaveFileManager::list(this, target);
}

View File

@ -56,6 +56,8 @@
#include "common/str.h"
#include "common/keyboard.h"
#include "common/debug.h"
#include "backends/keymapper/keymap.h"
#include "backends/keymapper/keymapper.h"
#include "graphics/scaler.h"
@ -154,6 +156,18 @@ void KIA::openLastOpened() {
}
void KIA::open(KIASections sectionId) {
if (_vm->getEventManager()->getKeymapper() != nullptr) {
if ( _vm->getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kGameplayKeymapId) != nullptr) {
// When disabling a keymap, make sure all their active events in the _activeCustomEvents array
// are cleared, because as they won't get an explicit "EVENT_CUSTOM_ENGINE_ACTION_END" event.
_vm->cleanupPendingRepeatingEvents(BladeRunnerEngine::kGameplayKeymapId);
_vm->getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kGameplayKeymapId)->setEnabled(false);
}
if (_vm->getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kKiaKeymapId) != nullptr)
_vm->getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kKiaKeymapId)->setEnabled(true);
}
if (_currentSectionId == sectionId) {
return;
}
@ -476,31 +490,56 @@ void KIA::handleKeyDown(const Common::KeyState &kbd) {
}
}
switch (kbd.keycode) {
case Common::KEYCODE_ESCAPE:
if (_currentSection) {
_currentSection->handleKeyDown(kbd);
}
if (_currentSection && _currentSection->_scheduledSwitch) {
open(kKIASectionNone);
}
}
void KIA::handleCustomEventStop(const Common::Event &evt) {
if (!isOpen()) {
return;
}
if (_currentSection) {
_currentSection->handleCustomEventStop(evt);
}
}
void KIA::handleCustomEventStart(const Common::Event &evt) {
if (!isOpen()) {
return;
}
switch ((BladeRunnerEngine::BladeRunnerEngineMappableAction)evt.customType) {
case BladeRunnerEngine::BladeRunnerEngineMappableAction::kMpActionToggleKiaOptions:
if (!_forceOpen) {
open(kKIASectionNone);
}
break;
case Common::KEYCODE_F1:
case BladeRunnerEngine::BladeRunnerEngineMappableAction::kMpActionOpenKIATabHelp:
open(kKIASectionHelp);
break;
case Common::KEYCODE_F2:
case BladeRunnerEngine::BladeRunnerEngineMappableAction::kMpActionOpenKIATabSaveGame:
if (!_forceOpen) {
open(kKIASectionSave);
}
break;
case Common::KEYCODE_F3:
case BladeRunnerEngine::BladeRunnerEngineMappableAction::kMpActionOpenKIATabLoadGame:
open(kKIASectionLoad);
break;
case Common::KEYCODE_F10:
case BladeRunnerEngine::BladeRunnerEngineMappableAction::kMpActionOpenKIATabQuitGame:
open(kKIASectionQuit);
break;
case Common::KEYCODE_F4:
case BladeRunnerEngine::BladeRunnerEngineMappableAction::kMpActionOpenKIATabCrimeSceneDatabase:
if (_currentSectionId != kKIASectionCrimes) {
if (!_forceOpen) {
open(kKIASectionCrimes);
@ -510,7 +549,7 @@ void KIA::handleKeyDown(const Common::KeyState &kbd) {
}
break;
case Common::KEYCODE_F5:
case BladeRunnerEngine::BladeRunnerEngineMappableAction::kMpActionOpenKIATabSuspectDatabase:
if (_currentSectionId != kKIASectionSuspects) {
if (!_forceOpen) {
open(kKIASectionSuspects);
@ -520,7 +559,7 @@ void KIA::handleKeyDown(const Common::KeyState &kbd) {
}
break;
case Common::KEYCODE_F6:
case BladeRunnerEngine::BladeRunnerEngineMappableAction::kMpActionOpenKIATabClueDatabase:
if (_currentSectionId != kKIASectionClues) {
if (!_forceOpen) {
open(kKIASectionClues);
@ -532,7 +571,7 @@ void KIA::handleKeyDown(const Common::KeyState &kbd) {
default:
if (_currentSection) {
_currentSection->handleKeyDown(kbd);
_currentSection->handleCustomEventStart(evt);
}
break;
}
@ -759,6 +798,18 @@ void KIA::init() {
}
void KIA::unload() {
if (_vm->getEventManager()->getKeymapper() != nullptr) {
if ( _vm->getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kGameplayKeymapId) != nullptr)
_vm->getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kGameplayKeymapId)->setEnabled(true);
if (_vm->getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kKiaKeymapId) != nullptr) {
// When disabling a keymap, make sure all their active events in the _activeCustomEvents array
// are cleared, because as they won't get an explicit "EVENT_CUSTOM_ENGINE_ACTION_END" event.
_vm->cleanupPendingRepeatingEvents(BladeRunnerEngine::kKiaKeymapId);
_vm->getEventManager()->getKeymapper()->getKeymap(BladeRunnerEngine::kKiaKeymapId)->setEnabled(false);
}
}
_thumbnail.free();
if (!isOpen()) {

View File

@ -28,6 +28,7 @@
namespace Common {
struct KeyState;
struct Event;
}
namespace BladeRunner {
@ -143,6 +144,8 @@ public:
void handleMouseScroll(int mouseX, int mouseY, int direction); // Added by ScummVM team
void handleKeyUp(const Common::KeyState &kbd);
void handleKeyDown(const Common::KeyState &kbd);
void handleCustomEventStop(const Common::Event &evt);
void handleCustomEventStart(const Common::Event &evt);
void playerReset();
void playActorDialogue(int actorId, int sentenceId);

View File

@ -26,6 +26,7 @@
namespace Common {
struct KeyState;
struct Event;
}
namespace Graphics {
@ -53,6 +54,10 @@ public:
virtual void handleKeyUp(const Common::KeyState &kbd) {}
virtual void handleKeyDown(const Common::KeyState &kbd) {}
virtual void handleCustomEventStart(const Common::Event &evt) {}
virtual void handleCustomEventStop(const Common::Event &evt) {}
virtual void handleMouseMove(int mouseX, int mouseY) {}
virtual void handleMouseDown(bool mainButton) {}
virtual void handleMouseUp(bool mainButton) {}

View File

@ -225,6 +225,22 @@ void KIASectionSave::draw(Graphics::Surface &surface) {
_buttons->drawTooltip(surface, _mouseX, _mouseY);
}
bool KIASectionSave::isKeyConfirmModalDialogue(const Common::KeyState &kbd) {
if (kbd.keycode == Common::KEYCODE_RETURN || kbd.keycode == Common::KEYCODE_KP_ENTER) {
return true;
}
return false;
}
bool KIASectionSave::isKeyRequestDeleteEntry(const Common::KeyState &kbd) {
if (_selectedLineId != _newSaveLineId
&& ( kbd.keycode == Common::KEYCODE_DELETE
|| (kbd.keycode == Common::KEYCODE_KP_PERIOD && !(kbd.flags & Common::KBD_NUM)))) {
return true;
}
return false;
}
void KIASectionSave::handleKeyUp(const Common::KeyState &kbd) {
if (_state == kStateNormal) {
_uiContainer->handleKeyUp(kbd);
@ -234,19 +250,17 @@ void KIASectionSave::handleKeyUp(const Common::KeyState &kbd) {
void KIASectionSave::handleKeyDown(const Common::KeyState &kbd) {
if (_state == kStateNormal) {
// Delete a saved game entry either with Delete key or numpad's (keypad's) Del key (when Num Lock Off)
if (_selectedLineId != _newSaveLineId
&& ( kbd.keycode == Common::KEYCODE_DELETE
|| (kbd.keycode == Common::KEYCODE_KP_PERIOD && !(kbd.flags & Common::KBD_NUM)))) {
if (isKeyRequestDeleteEntry(kbd)) {
changeState(kStateDelete);
}
_uiContainer->handleKeyDown(kbd);
} else if (_state == kStateOverwrite) {
if (kbd.keycode == Common::KEYCODE_RETURN || kbd.keycode == Common::KEYCODE_KP_ENTER) {
if (isKeyConfirmModalDialogue(kbd)) {
save();
changeState(kStateNormal);
}
} else if (_state == kStateDelete) {
if (kbd.keycode == Common::KEYCODE_RETURN || kbd.keycode == Common::KEYCODE_KP_ENTER) {
if (isKeyConfirmModalDialogue(kbd)) {
deleteSave();
changeState(kStateNormal);
}

View File

@ -93,6 +93,9 @@ private:
void changeState(State state);
void save();
void deleteSave();
bool isKeyConfirmModalDialogue(const Common::KeyState &kbd);
bool isKeyRequestDeleteEntry(const Common::KeyState &kbd);
};
} // End of namespace BladeRunner

View File

@ -105,6 +105,10 @@ void Scores::set(int index, int value) {
_lastScoreValue = value;
}
void Scores::handleCustomEventStart(const Common::Event &evt) {
close();
}
void Scores::handleKeyDown(const Common::KeyState &kbd) {
close();
}

View File

@ -26,6 +26,7 @@
namespace Common {
struct KeyState;
struct Event;
}
namespace BladeRunner {
@ -64,6 +65,7 @@ public:
int query(int index) { return _scores[index]; }
void set(int index, int value);
void handleCustomEventStart(const Common::Event &evt);
void handleKeyDown(const Common::KeyState &kbd);
int handleMouseUp(int x, int y);
int handleMouseDown(int x, int y);