diff --git a/engines/bladerunner/bladerunner.cpp b/engines/bladerunner/bladerunner.cpp index 560aed08dc6..20fab77cbb8 100644 --- a/engines/bladerunner/bladerunner.cpp +++ b/engines/bladerunner/bladerunner.cpp @@ -1292,11 +1292,13 @@ bool BladeRunnerEngine::isAllowedRepeatedCustomEvent(const Common::Event &currev switch (currevent.type) { case Common::EVENT_CUSTOM_ENGINE_ACTION_START: switch ((BladeRunnerEngineMappableAction)currevent.customType) { - case kMpblActionCutsceneSkip: + case kMpActionCutsceneSkip: // fall through case kMpActionDialogueSkip: // fall through case kMpActionToggleKiaOptions: + // fall through + case kMpConfirmDlg: return true; default: @@ -1324,12 +1326,7 @@ bool BladeRunnerEngine::isAllowedRepeatedKey(const Common::KeyState &currKeyStat // 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 + return currKeyState.keycode == Common::KEYCODE_BACKSPACE || currKeyState.keycode == Common::KEYCODE_SPACE || currKeyState.keycode == Common::KEYCODE_KP_MINUS || currKeyState.keycode == Common::KEYCODE_KP_PLUS @@ -1369,7 +1366,7 @@ void BladeRunnerEngine::handleEvents() { handleMouseAction(event.mouse.x, event.mouse.y, false, false); break; - case kMpblActionCutsceneSkip: + case kMpActionCutsceneSkip: // fall through case kMpActionDialogueSkip: // fall through @@ -1390,6 +1387,10 @@ void BladeRunnerEngine::handleEvents() { case kMpActionOpenKIATabClueDatabase: // fall through case kMpActionOpenKIATabQuitGame: + // fall through + case kMpConfirmDlg: + // fall through + case kMpDeleteSelectedSvdGame: handleCustomEventStop(event); break; @@ -1410,7 +1411,7 @@ void BladeRunnerEngine::handleEvents() { handleMouseAction(event.mouse.x, event.mouse.y, false, true); break; - case kMpblActionCutsceneSkip: + case kMpActionCutsceneSkip: // fall through case kMpActionDialogueSkip: // fall through @@ -1431,6 +1432,10 @@ void BladeRunnerEngine::handleEvents() { case kMpActionOpenKIATabClueDatabase: // fall through case kMpActionOpenKIATabQuitGame: + // fall through + case kMpConfirmDlg: + // fall through + case kMpDeleteSelectedSvdGame: if (isAllowedRepeatedCustomEvent(event) && _activeCustomEvents->size() < kMaxCustomConcurrentRepeatableEvents) { if (_activeCustomEvents->empty()) { @@ -1496,12 +1501,23 @@ void BladeRunnerEngine::handleEvents() { && (timeNow - _customEventRepeatTimeLast >= _customEventRepeatTimeDelay)) { _customEventRepeatTimeLast = timeNow; _customEventRepeatTimeDelay = kKeyRepeatSustainDelay; + uint16 aceSize = _activeCustomEvents->size(); 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); + // This extra check is needed since it's possible that during this for loop + // within the above handleCustomEventStart() execution, + // cleanupPendingRepeatingEvents() is called + // and elements from _activeCustomEvents are removed! + // TODO This is probably an indication that this could be reworked + // as something cleaner and safer. + // Or event repetition could be handled by the keymapper code (outside the engine code) + if (aceSize != _activeCustomEvents->size()) { + break; + } } } else if (isAllowedRepeatedKey(_currentKeyDown) && (timeNow - _keyRepeatTimeLast >= _keyRepeatTimeDelay)) { @@ -1603,6 +1619,10 @@ void BladeRunnerEngine::cleanupPendingRepeatingEvents(const Common::String &keym for (ActiveCustomEventsArray::iterator actIt = _activeCustomEvents->begin(); actIt != _activeCustomEvents->end(); ++actIt) { if ((actIt->type != Common::EVENT_INVALID) && (actIt->customType == (*kmIt)->event.customType)) { _activeCustomEvents->erase(actIt); + // When erasing an element from an array, erase(iterator pos) + // will return an iterator pointing to the next element in the array. + // Thus, we should check if we reached the end() here, to avoid moving + // the iterator in the next loop repetition to an invalid memory location. if (actIt == _activeCustomEvents->end()) { break; } @@ -1633,13 +1653,13 @@ void BladeRunnerEngine::handleCustomEventStop(Common::Event &event) { } void BladeRunnerEngine::handleCustomEventStart(Common::Event &event) { - if (_vqaIsPlaying && (BladeRunnerEngineMappableAction)event.customType == kMpblActionCutsceneSkip) { + if (_vqaIsPlaying && (BladeRunnerEngineMappableAction)event.customType == kMpActionCutsceneSkip) { _vqaStopIsRequested = true; _vqaIsPlaying = false; return; } - if (_vqaStopIsRequested && (BladeRunnerEngineMappableAction)event.customType == kMpblActionCutsceneSkip) { + if (_vqaStopIsRequested && (BladeRunnerEngineMappableAction)event.customType == kMpActionCutsceneSkip) { return; } diff --git a/engines/bladerunner/bladerunner.h b/engines/bladerunner/bladerunner.h index ca19a3d81c5..b987529b664 100644 --- a/engines/bladerunner/bladerunner.h +++ b/engines/bladerunner/bladerunner.h @@ -297,10 +297,16 @@ public: // 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 - kMpblActionCutsceneSkip, // default or or 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) @@ -312,7 +318,9 @@ public: kMpActionOpenKIATabClueDatabase, // default kMpActionOpenKIATabQuitGame, // default kMpActionScrollUp, // ScummVM addition (scroll list up) - kMpActionScrollDown // ScummVM addition (scroll list down) + kMpActionScrollDown, // ScummVM addition (scroll list down) + kMpConfirmDlg, // default or + kMpDeleteSelectedSvdGame // default or }; private: diff --git a/engines/bladerunner/metaengine.cpp b/engines/bladerunner/metaengine.cpp index 3aebf12bb8d..cdd846445df 100644 --- a/engines/bladerunner/metaengine.cpp +++ b/engines/bladerunner/metaengine.cpp @@ -135,7 +135,7 @@ Common::KeymapArray BladeRunnerMetaEngine::initKeymaps(const char *target) const // I18N: This keymap allows skipping video cutscenes act = new Action("SKIPVIDEO", _("Skip cutscene")); - act->setCustomEngineActionEvent(BladeRunnerEngine::kMpblActionCutsceneSkip); + act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionCutsceneSkip); act->addDefaultInputMapping("ESCAPE"); act->addDefaultInputMapping("RETURN"); act->addDefaultInputMapping("KP_ENTER"); @@ -175,6 +175,31 @@ Common::KeymapArray BladeRunnerMetaEngine::initKeymaps(const char *target) const act->addDefaultInputMapping("JOY_LEFT_SHOULDER"); gameplayKeymap->addAction(act); + // I18N: This keymap works within the KIA Save Game screen + // and allows confirming popup dialogue prompts (eg. for save game deletion or overwriting) + // and also submitting a new save game name, or choosing an existing save game for overwriting. + act = new Action("KIACONFIRMDLG", _("Confirm")); + act->setCustomEngineActionEvent(BladeRunnerEngine::kMpConfirmDlg); + act->addDefaultInputMapping("RETURN"); + act->addDefaultInputMapping("KP_ENTER"); + act->addDefaultInputMapping("JOY_B"); + kiaOnlyKeymap->addAction(act); + + // I18N: This keymap works within the KIA Save Game screen + // and allows submitting a selected existing save game for deletion. + act = new Action("KIADELETESVDGAME", _("Delete Selected Saved Game")); + act->setCustomEngineActionEvent(BladeRunnerEngine::kMpDeleteSelectedSvdGame); + act->addDefaultInputMapping("DELETE"); + // TODO In the original KP_PERIOD with NUMLOCK on, would work as a normal '.' character. + // KP_PERIOD with NUMLOCK off, would work as a delete request for the selected saved game. + // However, NUMLOCK is currently not working as a modifier key for keymaps, + // so maybe we should implement this 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. + act->addDefaultInputMapping("KP_PERIOD"); + act->addDefaultInputMapping("JOY_X"); + kiaOnlyKeymap->addAction(act); + // I18N: This keymap allows scrolling texts and lists upwards act = new Action("KIASCROLLUP", _("Scroll Up")); act->setCustomEngineActionEvent(BladeRunnerEngine::kMpActionScrollUp); diff --git a/engines/bladerunner/ui/kia.h b/engines/bladerunner/ui/kia.h index 51b10368cb8..6ade7bfefa5 100644 --- a/engines/bladerunner/ui/kia.h +++ b/engines/bladerunner/ui/kia.h @@ -117,6 +117,10 @@ class KIA { public: + // Indicates when KIA opens after player has died + // or the game just launched and there are existing saved games to load. + // In forced open mode, certain KIA tabs are not available, + // such as the Save Game tab and the Crime Scene, Suspect, Clue database tabs. bool _forceOpen; KIALog *_log; diff --git a/engines/bladerunner/ui/kia_section_save.cpp b/engines/bladerunner/ui/kia_section_save.cpp index c5893d4cbc7..6908510c27f 100644 --- a/engines/bladerunner/ui/kia_section_save.cpp +++ b/engines/bladerunner/ui/kia_section_save.cpp @@ -225,22 +225,6 @@ 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); @@ -248,19 +232,32 @@ void KIASectionSave::handleKeyUp(const Common::KeyState &kbd) { } void KIASectionSave::handleKeyDown(const Common::KeyState &kbd) { + if (_state == kStateNormal) { + _uiContainer->handleKeyDown(kbd); + } +} + +void KIASectionSave::handleCustomEventStop(const Common::Event &evt) { + if (_state == kStateNormal) { + _uiContainer->handleCustomEventStop(evt); + } +} + +void KIASectionSave::handleCustomEventStart(const Common::Event &evt) { if (_state == kStateNormal) { // Delete a saved game entry either with Delete key or numpad's (keypad's) Del key (when Num Lock Off) - if (isKeyRequestDeleteEntry(kbd)) { + if (_selectedLineId != _newSaveLineId + && evt.customType == BladeRunnerEngine::BladeRunnerEngineMappableAction::kMpDeleteSelectedSvdGame) { changeState(kStateDelete); } - _uiContainer->handleKeyDown(kbd); + _uiContainer->handleCustomEventStart(evt); } else if (_state == kStateOverwrite) { - if (isKeyConfirmModalDialogue(kbd)) { + if (evt.customType == BladeRunnerEngine::BladeRunnerEngineMappableAction::kMpConfirmDlg) { save(); changeState(kStateNormal); } } else if (_state == kStateDelete) { - if (isKeyConfirmModalDialogue(kbd)) { + if (evt.customType == BladeRunnerEngine::BladeRunnerEngineMappableAction::kMpConfirmDlg) { deleteSave(); changeState(kStateNormal); } diff --git a/engines/bladerunner/ui/kia_section_save.h b/engines/bladerunner/ui/kia_section_save.h index 77a71039ef2..4f6e2e54772 100644 --- a/engines/bladerunner/ui/kia_section_save.h +++ b/engines/bladerunner/ui/kia_section_save.h @@ -83,6 +83,8 @@ public: void handleMouseUp(bool mainButton) override; void handleMouseScroll(int direction) override; + void handleCustomEventStart(const Common::Event &evt) override; + void handleCustomEventStop(const Common::Event &evt) override; private: static void scrollBoxCallback(void *callbackData, void *source, int lineData, int mouseButton); static void inputBoxCallback(void *callbackData, void *source); @@ -93,9 +95,6 @@ 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 diff --git a/engines/bladerunner/ui/ui_component.h b/engines/bladerunner/ui/ui_component.h index f62ac3cd52f..c1fcf8ebe4f 100644 --- a/engines/bladerunner/ui/ui_component.h +++ b/engines/bladerunner/ui/ui_component.h @@ -24,6 +24,7 @@ namespace Common{ struct KeyState; +struct Event; } namespace Graphics { @@ -55,6 +56,8 @@ public: virtual void handleMouseScroll(int direction) {} // Added by ScummVM team virtual void handleKeyUp(const Common::KeyState &kbd) {} virtual void handleKeyDown(const Common::KeyState &kbd) {} + virtual void handleCustomEventStop(const Common::Event &evt) {} + virtual void handleCustomEventStart(const Common::Event &evt) {} }; } // End of namespace BladeRunner diff --git a/engines/bladerunner/ui/ui_container.cpp b/engines/bladerunner/ui/ui_container.cpp index 3c522aea599..baff51b429c 100644 --- a/engines/bladerunner/ui/ui_container.cpp +++ b/engines/bladerunner/ui/ui_container.cpp @@ -128,6 +128,39 @@ void UIContainer::handleKeyDown(const Common::KeyState &kbd) { } } +void UIContainer::handleCustomEventStop(const Common::Event &evt) { + if (_handleSpecificNumOfTopLayers <= 0) { + for (Common::Array::iterator component = _components.begin(); component != _components.end(); ++component) { + (*component)->handleCustomEventStop(evt); + } + } else { + int countOfTopLayersToHandle = _handleSpecificNumOfTopLayers; + Common::Array::iterator component = _components.end(); + do { + --component; + --countOfTopLayersToHandle; + (*component)->handleCustomEventStop(evt); + } while (component != _components.begin() && countOfTopLayersToHandle != 0); + } +} + +void UIContainer::handleCustomEventStart(const Common::Event &evt) { + if (_handleSpecificNumOfTopLayers <= 0) { + for (Common::Array::iterator component = _components.begin(); component != _components.end(); ++component) { + (*component)->handleCustomEventStart(evt); + } + } else { + int countOfTopLayersToHandle = _handleSpecificNumOfTopLayers; + Common::Array::iterator component = _components.end(); + do { + --component; + --countOfTopLayersToHandle; + (*component)->handleCustomEventStart(evt); + } while (component != _components.begin() && countOfTopLayersToHandle != 0); + + } +} + void UIContainer::add(UIComponent *component) { _components.push_back(component); } diff --git a/engines/bladerunner/ui/ui_container.h b/engines/bladerunner/ui/ui_container.h index 6bfc6ad121f..0e6fe83cf1d 100644 --- a/engines/bladerunner/ui/ui_container.h +++ b/engines/bladerunner/ui/ui_container.h @@ -48,6 +48,8 @@ public: void handleMouseScroll(int direction) override; // Added by ScummVM team void handleKeyUp(const Common::KeyState &kbd) override; void handleKeyDown(const Common::KeyState &kbd) override; + void handleCustomEventStop(const Common::Event &evt) override; + void handleCustomEventStart(const Common::Event &evt) override; void add(UIComponent *component); void clear(); diff --git a/engines/bladerunner/ui/ui_input_box.cpp b/engines/bladerunner/ui/ui_input_box.cpp index 62214804918..de6aa2aae1f 100644 --- a/engines/bladerunner/ui/ui_input_box.cpp +++ b/engines/bladerunner/ui/ui_input_box.cpp @@ -93,15 +93,19 @@ void UIInputBox::handleKeyDown(const Common::KeyState &kbd) { _text += kc; } else if (kbd.keycode == Common::KEYCODE_BACKSPACE) { _text.deleteLastChar(); - } else if ((kbd.keycode == Common::KEYCODE_RETURN || kbd.keycode == Common::KEYCODE_KP_ENTER) - && !_text.empty()) { - if (_valueChangedCallback) { - _valueChangedCallback(_callbackData, this); - } } } } +void UIInputBox::handleCustomEventStart(const Common::Event &evt) { + if (_isVisible + && evt.customType == BladeRunnerEngine::BladeRunnerEngineMappableAction::kMpConfirmDlg + && !_text.empty() + && _valueChangedCallback) { + _valueChangedCallback(_callbackData, this); + } +} + bool UIInputBox::getValidChar(const uint16 &kAscii16bit, uint8 &targetAscii) { if (kAscii16bit != 0) { // The above check for kAscii16bit > 0 gets rid of the tentative warning: diff --git a/engines/bladerunner/ui/ui_input_box.h b/engines/bladerunner/ui/ui_input_box.h index fe705b5e669..bda9f12e468 100644 --- a/engines/bladerunner/ui/ui_input_box.h +++ b/engines/bladerunner/ui/ui_input_box.h @@ -55,6 +55,7 @@ public: void hide(); void handleKeyDown(const Common::KeyState &kbd) override; + void handleCustomEventStart(const Common::Event &evt) override; private: bool getValidChar(const uint16 &kc16bit, uint8 &kc8bit);