From 3fc7a85a525ad98e44268fed3f3852ba4e6e7336 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Sun, 2 Jul 2023 08:16:14 +1000 Subject: [PATCH] More work on the Input system --- assets/opensb/binds/opensb.binds | 16 ++++ source/game/StarInput.cpp | 155 ++++++++++++++++++++++++++++++- source/game/StarInput.hpp | 20 ++-- 3 files changed, 175 insertions(+), 16 deletions(-) create mode 100644 assets/opensb/binds/opensb.binds diff --git a/assets/opensb/binds/opensb.binds b/assets/opensb/binds/opensb.binds new file mode 100644 index 0000000..f493148 --- /dev/null +++ b/assets/opensb/binds/opensb.binds @@ -0,0 +1,16 @@ +{ + "opensb": { + "name": "OpenStarbound", + "bannerName" : "Open^#ebd74a;Starbound", + "binds": { + "test": { + "default": [{ + "type": "key", + "value": "C", + "mods": ["LShift"] + }], + "name": "Test Bind" + } + } + } +} \ No newline at end of file diff --git a/source/game/StarInput.cpp b/source/game/StarInput.cpp index 0a9b56d..89b2ed3 100644 --- a/source/game/StarInput.cpp +++ b/source/game/StarInput.cpp @@ -4,6 +4,39 @@ namespace Star { +Json keyModsToJson(KeyMod mod) { + JsonArray array; + + if ((bool)(mod & KeyMod::LShift)) array.emplace_back("LShift"); + if ((bool)(mod & KeyMod::RShift)) array.emplace_back("RShift"); + if ((bool)(mod & KeyMod::LCtrl )) array.emplace_back("LCtrl" ); + if ((bool)(mod & KeyMod::RCtrl )) array.emplace_back("RCtrl" ); + if ((bool)(mod & KeyMod::LAlt )) array.emplace_back("LAlt" ); + if ((bool)(mod & KeyMod::RAlt )) array.emplace_back("RAlt" ); + if ((bool)(mod & KeyMod::LGui )) array.emplace_back("LGui" ); + if ((bool)(mod & KeyMod::RGui )) array.emplace_back("RGui" ); + if ((bool)(mod & KeyMod::Num )) array.emplace_back("Num" ); + if ((bool)(mod & KeyMod::Caps )) array.emplace_back("Caps" ); + if ((bool)(mod & KeyMod::AltGr )) array.emplace_back("AltGr" ); + if ((bool)(mod & KeyMod::Scroll)) array.emplace_back("Scroll"); + + return move(array); +} + +// Optional pointer argument to output calculated priority +KeyMod keyModsFromJson(Json const& json, uint8_t* priority = nullptr) { + KeyMod mod = KeyMod::NoMod; + if (!json.isType(Json::Type::Array)) + return mod; + + for (Json const& jMod : json.toArray()) { + if (priority && mod != (mod |= KeyModNames.getLeft(jMod.toString()))) + ++*priority; + } + + return mod; +} + size_t hash::operator()(InputVariant const& v) const { size_t indexHash = hashOf(v.typeIndex()); if (auto key = v.ptr()) @@ -16,6 +49,99 @@ size_t hash::operator()(InputVariant const& v) const { return indexHash; } +Input::Bind Input::bindFromJson(Json const& json) { + Bind bind; + + String type = json.getString("type"); + Json value = json.get("value"); + + if (type == "key") { + KeyBind keyBind; + keyBind.key = KeyNames.getLeft(value.toString()); + keyBind.mods = keyModsFromJson(json.getArray("mods", {}), &keyBind.priority); + bind = move(keyBind); + } + else if (type == "mouse") { + MouseBind mouseBind; + mouseBind.button = MouseButtonNames.getLeft(value.toString()); + mouseBind.mods = keyModsFromJson(json.getArray("mods", {}), &mouseBind.priority); + bind = move(mouseBind); + } + else if (type == "controller") { + ControllerBind controllerBind; + controllerBind.button = ControllerButtonNames.getLeft(value.toString()); + controllerBind.controller = json.getUInt("controller", 0); + bind = move(controllerBind); + } + + return bind; +} + +Json Input::bindToJson(Bind const& bind) { + if (auto keyBind = bind.ptr()) { + return JsonObject{ + {"type", "key"}, + {"value", KeyNames.getRight(keyBind->key)}, + {"mods", keyModsToJson(keyBind->mods)} + }; + } + else if (auto mouseBind = bind.ptr()) { + return JsonObject{ + {"type", "mouse"}, + {"value", MouseButtonNames.getRight(mouseBind->button)}, + {"mods", keyModsToJson(mouseBind->mods)} + }; + } + else if (auto controllerBind = bind.ptr()) { + return JsonObject{ + {"type", "controller"}, + {"value", ControllerButtonNames.getRight(controllerBind->button)} + }; + } + + return Json(); +} + +Input::BindEntry::BindEntry(String entryId, Json const& config, BindCategory const& parentCategory) { + category = &parentCategory; + id = move(entryId); + name = config.getString("name", id); + + for (Json const& jBind : config.getArray("default", {})) { + try + { defaultBinds.emplace_back(bindFromJson(jBind)); } + catch (JsonException const& e) + { Logger::error("Binds: Error loading default bind in {}.{}: {}", parentCategory.id, id, e.what()); } + } +} + +Input::BindCategory::BindCategory(String categoryId, Json const& categoryConfig) { + id = move(categoryId); + name = config.getString("name", id); + config = categoryConfig; + + ConfigurationPtr userConfig = Root::singletonPtr()->configuration(); + auto userBindings = userConfig->get("modBindings"); + + for (auto& pair : config.getObject("binds", {})) { + String const& bindId = pair.first; + Json const& bindConfig = pair.second; + if (!bindConfig.isType(Json::Type::Object)) + continue; + + BindEntry& entry = entries.insert(bindId, BindEntry(bindId, bindConfig, *this)).first->second; + + if (userBindings.isType(Json::Type::Object)) { + for (auto& jBind : userBindings.queryArray(strf("{}.{}", id, bindId), {})) { + try + { entry.customBinds.emplace_back(bindFromJson(jBind)); } + catch (JsonException const& e) + { Logger::error("Binds: Error loading user bind in {}.{}: {}", id, bindId, e.what()); } + } + } + } +} + Input* Input::s_singleton; Input* Input::singletonPtr() { @@ -35,6 +161,13 @@ Input::Input() { s_singleton = this; + reload(); + + m_rootReloadListener = make_shared([&]() { + reload(); + }); + + Root::singletonPtr()->registerReloadListener(m_rootReloadListener); } Input::~Input() { @@ -42,17 +175,33 @@ Input::~Input() { } void Input::reset() { - + m_inputStates.clear(); + m_bindStates.clear(); } void Input::reload() { reset(); - + m_bindCategories.clear(); + m_inputsToBinds.clear(); + auto assets = Root::singleton().assets(); for (auto& bindPath : assets->scanExtension("binds")) { - Json config = assets->json(bindPath); + for (auto& pair : assets->json(bindPath).toObject()) { + String const& categoryId = pair.first; + Json const& categoryConfig = pair.second; + if (!categoryConfig.isType(Json::Type::Object)) + continue; + + m_bindCategories.insert(categoryId, BindCategory(categoryId, categoryConfig)); + } } + + size_t count = 0; + for (auto& pair : m_bindCategories) + count += pair.second.entries.size(); + + Logger::info("Binds: Loaded {} bind{}", count, count == 1 ? "" : "s"); } } \ No newline at end of file diff --git a/source/game/StarInput.hpp b/source/game/StarInput.hpp index f329cc2..efe68fb 100644 --- a/source/game/StarInput.hpp +++ b/source/game/StarInput.hpp @@ -20,12 +20,6 @@ namespace Star { class Input { public: - struct NoBind { - String error; - - NoBind(String err); - }; - struct KeyBind { Key key = Key::Zero; KeyMod mods = KeyMod::NoMod; @@ -51,27 +45,27 @@ namespace Star { ControllerButton button = ControllerButton::Invalid; }; - typedef Variant Bind; + typedef MVariant Bind; static Bind bindFromJson(Json const& json); static Json bindToJson(Bind const& bind); - struct Category; + struct BindCategory; struct BindEntry { // The internal ID of this entry. String id; - // The category this entry belongs to. - String category; // The user-facing name of this entry. String name; + // The category this entry belongs to. + BindCategory const* category; // The default binds. List defaultBinds; // The user-configured binds. List customBinds; - BindEntry(Json const& config, Category const& category); + BindEntry(String entryId, Json const& config, BindCategory const& parentCategory); }; struct BindRef { @@ -91,11 +85,11 @@ namespace Star { struct BindCategory { String id; String name; - Json meta; + Json config; StringMap entries; - BindCategory(Json const& data); + BindCategory(String categoryId, Json const& categoryConfig); }; struct InputState {