OpenLoco/src/openloco/input/keyboard.cpp

505 lines
14 KiB
C++

#include "../CompanyManager.h"
#include "../Config.h"
#include "../Console.h"
#include "../GameCommands.h"
#include "../Input.h"
#include "../Intro.h"
#include "../OpenLoco.h"
#include "../Tutorial.h"
#include "../Ui.h"
#include "../Win32.h"
#include "../audio/audio.h"
#include "../interop/interop.hpp"
#include "../localisation/string_ids.h"
#include "../things/thingmgr.h"
#include "../ui/Screenshot.h"
#include "ShortcutManager.h"
#include <cstdint>
#include <functional>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#define WIN32_LEAN_AND_MEAN
#include <shlobj.h>
#include <windows.h>
#endif
using namespace openloco::interop;
using namespace openloco::ui;
using namespace openloco::game_commands;
namespace openloco::input
{
#pragma pack(push, 1)
struct key
{
uint32_t keyCode;
uint32_t charCode;
};
#pragma pack(pop)
static void normalKey();
static void cheat();
static void loc_4BECDE();
static void loc_4BED04();
static void loc_4BED79();
static loco_global<uint8_t, 0x00508F14> _screenFlags;
static loco_global<int8_t, 0x00508F16> _screenshotCountdown;
static loco_global<uint8_t, 0x00508F18> _keyModifier;
static loco_global<ui::WindowType, 0x005233B6> _modalWindowType;
static loco_global<char[16], 0x0112C826> _commonFormatArgs;
static std::string _cheatBuffer; // 0x0011364A5
static loco_global<uint8_t[256], 0x01140740> _keyboardState;
static loco_global<uint8_t, 0x011364A4> _11364A4;
static std::pair<std::string, std::function<void()>> cheats[] = {
{ "DRIVER", loc_4BECDE },
{ "SHUNT", loc_4BED04 },
{ "FREECASH", loc_4BED79 }
};
bool hasKeyModifier(uint8_t modifier)
{
uint8_t keyModifier = _keyModifier;
return (keyModifier & modifier) != 0;
}
static void loc_4BECDE()
{
_screenFlags |= screen_flags::unknown_6;
audio::playSound(audio::sound_id::click_press, ui::width() / 2);
}
static void loc_4BED04()
{
if ((_screenFlags & screen_flags::unknown_6) == 0)
{
return;
// Only works when DRIVER mode is active
}
for (auto i = (int32_t)WindowManager::count() - 1; i >= 0; i--)
{
auto w = WindowManager::get(i);
if (w->type != WindowType::vehicle)
continue;
auto t = thingmgr::get<openloco::vehicle>(w->number);
if (t->owner != companymgr::getControllingId())
continue;
if (t->mode != TransportMode::rail)
continue;
registers regs;
regs.cx = w->number;
regs.bl = GameCommandFlag::apply;
game_commands::doCommand(77, regs);
audio::playSound(audio::sound_id::click_press, ui::width() / 2);
return;
}
}
static void loc_4BED79()
{
registers regs;
regs.bl = GameCommandFlag::apply;
game_commands::doCommand(78, regs);
audio::playSound(audio::sound_id::click_press, ui::width() / 2);
}
static void loc_4BEFEF()
{
switch (tutorial::state())
{
case tutorial::tutorial_state::none:
break;
case tutorial::tutorial_state::playing:
{
const uint16_t next = tutorial::nextInput();
_keyModifier = next;
if ((_keyModifier & key_modifier::unknown) == 0)
return;
tooltip::closeAndReset();
auto tutStringId = tutorial::nextString();
auto main = WindowManager::getMainWindow();
auto cursor = getMouseLocation();
tooltip::update(main, 0, tutStringId, cursor.x, cursor.y);
break;
}
case tutorial::tutorial_state::recording:
{
call(0x004BF005);
break;
}
}
}
static key* getNextKey()
{
registers regs;
call(0x00407028, regs);
return (key*)regs.eax;
}
static bool tryShortcut(Shortcut sc, uint32_t keyCode, uint8_t modifiers)
{
auto cfg = openloco::config::get();
if (cfg.keyboard_shortcuts[sc].var_0 == keyCode && cfg.keyboard_shortcuts[sc].var_1 == modifiers)
{
ShortcutManager::execute(sc);
return true;
}
return false;
}
// 0x004BEC5B
void processKeyboardInput()
{
cheat();
normalKey();
}
// 0x004BEC5B
static void cheat()
{
// Used to handle INSERT cheat
if ((_keyboardState[DIK_INSERT] & 0x80) != 0)
{
if ((_keyModifier & key_modifier::cheat) != 0)
{
return;
}
else
{
_keyModifier |= key_modifier::cheat;
_cheatBuffer.clear();
return;
}
}
if ((_keyModifier & key_modifier::cheat) == 0)
return;
_keyModifier = _keyModifier & (~key_modifier::cheat);
if (isTitleMode())
return;
for (auto cheat : cheats)
{
if (strcmp(_cheatBuffer.c_str(), cheat.first.c_str()) == 0)
{
cheat.second();
break;
}
}
}
static void editShortcut(key* k)
{
if (k->keyCode == VK_UP)
return;
if (k->keyCode == VK_DOWN)
return;
if (k->keyCode == VK_LEFT)
return;
if (k->keyCode == VK_RIGHT)
return;
if (k->keyCode == VK_NUMLOCK)
return;
if (k->keyCode == VK_LWIN)
return;
if (k->keyCode == VK_RWIN)
return;
auto& cfg = config::get();
for (int i = 0; i < 35; i++)
{
if (cfg.keyboard_shortcuts[i].var_0 == k->keyCode && cfg.keyboard_shortcuts[i].var_1 == _keyModifier)
{
cfg.keyboard_shortcuts[i].var_0 = 0xFF;
cfg.keyboard_shortcuts[i].var_1 = 0xFF;
}
}
cfg.keyboard_shortcuts[_11364A4].var_0 = k->keyCode;
cfg.keyboard_shortcuts[_11364A4].var_1 = _keyModifier;
WindowManager::close(WindowType::editKeyboardShortcut);
WindowManager::invalidate(WindowType::keyboardShortcuts);
config::write();
}
// 0x004BEDA0
static void normalKey()
{
while (true)
{
auto eax = getNextKey();
if (eax == 0)
{
loc_4BEFEF();
break;
}
if (eax->keyCode >= 255)
continue;
if (eax->keyCode == 0x10) // VK_SHIFT
continue;
if (eax->keyCode == 0x11) // VK_CONTROL
continue;
if ((_keyModifier & key_modifier::cheat) != 0)
{
if (eax->charCode >= 'a' && eax->charCode <= 'z')
{
eax->charCode = toupper(eax->charCode);
}
if (eax->charCode >= 'A' && eax->charCode <= 'Z')
{
_cheatBuffer += eax->charCode;
}
continue;
}
auto ti = WindowManager::find(WindowType::textInput);
if (ti != nullptr)
{
if (tryShortcut(Shortcut::screenshot, eax->keyCode, _keyModifier))
continue;
ui::textinput::sub_4CE910(eax->charCode, eax->keyCode);
continue;
}
if (*_modalWindowType == WindowType::fileBrowserPrompt)
{
ti = WindowManager::find(WindowType::fileBrowserPrompt);
if (ti != nullptr)
{
if (tryShortcut(Shortcut::screenshot, eax->keyCode, _keyModifier))
continue;
registers regs;
regs.eax = eax->charCode;
regs.ebx = eax->keyCode;
call(0x0044685C, regs);
continue;
}
}
if (*_modalWindowType == WindowType::confirmationPrompt)
{
ti = WindowManager::find(WindowType::confirmationPrompt);
if (ti != nullptr)
{
registers regs;
regs.eax = eax->charCode;
regs.ebx = eax->keyCode;
call(0x0044685C, regs);
}
}
ti = WindowManager::find(WindowType::editKeyboardShortcut);
if (ti != nullptr)
{
editShortcut(eax);
continue;
}
if (tutorial::state() == tutorial::tutorial_state::playing)
{
tutorial::stop();
continue;
}
if (!isTitleMode())
{
for (int i = 0; i < 35; i++)
{
if (tryShortcut((Shortcut)i, eax->keyCode, _keyModifier))
break;
}
continue;
}
if (intro::state() == (intro::intro_state)9)
{
intro::state(intro::intro_state::end);
continue;
}
if (intro::state() != intro::intro_state::none)
{
intro::state((intro::intro_state)8);
}
if (tryShortcut(Shortcut::sendMessage, eax->keyCode, _keyModifier))
continue;
if (tryShortcut(Shortcut::screenshot, eax->keyCode, _keyModifier))
continue;
}
}
static void edgeScroll()
{
if (tutorial::state() != tutorial::tutorial_state::none)
return;
if (config::get().edge_scrolling == 0)
return;
if (input::state() != input_state::normal && input::state() != input_state::dropdown_active)
return;
if (hasKeyModifier(key_modifier::shift) || hasKeyModifier(key_modifier::control))
return;
gfx::point_t delta = { 0, 0 };
auto cursor = getMouseLocation();
if (cursor.x == 0)
delta.x -= 12;
if (cursor.x == ui::width() - 1)
delta.x += 12;
if (cursor.y == 0)
delta.y -= 12;
if (cursor.y == ui::height() - 1)
delta.y += 12;
if (delta.x == 0 && delta.y == 0)
return;
auto main = WindowManager::getMainWindow();
if ((main->flags & window_flags::viewport_no_scrolling) != 0)
return;
if (openloco::isTitleMode())
return;
auto viewport = main->viewports[0];
if (viewport == nullptr)
return;
delta.x *= 1 << viewport->zoom;
delta.y *= 1 << viewport->zoom;
main->viewport_configurations[0].saved_view_x += delta.x;
main->viewport_configurations[0].saved_view_y += delta.y;
input::setFlag(input_flags::viewport_scrolling);
}
static void keyScroll()
{
if (tutorial::state() != tutorial::tutorial_state::none)
return;
if (*_modalWindowType != WindowType::undefined)
return;
if (WindowManager::find(WindowType::textInput) != nullptr)
return;
gfx::point_t delta = { 0, 0 };
if (_keyboardState[DIK_LEFT] & 0x80)
delta.x -= 8;
if (_keyboardState[DIK_UP] & 0x80)
delta.y -= 8;
if (_keyboardState[DIK_DOWN] & 0x80)
delta.y += 8;
if (_keyboardState[DIK_RIGHT] & 0x80)
delta.x += 8;
if (delta.x == 0 && delta.y == 0)
return;
auto main = WindowManager::getMainWindow();
if ((main->flags & window_flags::viewport_no_scrolling) != 0)
return;
if (openloco::isTitleMode())
return;
auto viewport = main->viewports[0];
if (viewport == nullptr)
return;
delta.x *= 1 << viewport->zoom;
delta.y *= 1 << viewport->zoom;
main->viewport_configurations[0].saved_view_x += delta.x;
main->viewport_configurations[0].saved_view_y += delta.y;
input::setFlag(input_flags::viewport_scrolling);
}
// 0x004BE92A
void handleKeyboard()
{
if (_screenshotCountdown != 0)
{
_screenshotCountdown--;
if (_screenshotCountdown == 0)
{
try
{
std::string fileName = saveScreenshot();
*((const char**)(&_commonFormatArgs[0])) = fileName.c_str();
windows::showError(string_ids::screenshot_saved_as, string_ids::null, false);
}
catch (const std::exception&)
{
windows::showError(string_ids::screenshot_failed);
}
}
}
edgeScroll();
_keyModifier = _keyModifier & ~(key_modifier::shift | key_modifier::control | key_modifier::unknown);
if (addr<0x005251CC, uint8_t>() != 1)
{
return;
}
if (_keyboardState[DIK_LSHIFT] & 0x80)
_keyModifier |= key_modifier::shift;
if (_keyboardState[DIK_RSHIFT] & 0x80)
_keyModifier |= key_modifier::shift;
if (_keyboardState[DIK_LCONTROL] & 0x80)
_keyModifier |= key_modifier::control;
if (_keyboardState[DIK_RCONTROL] & 0x80)
_keyModifier |= key_modifier::control;
keyScroll();
}
}