diff --git a/src/gameui/CGGameUI.cpp b/src/gameui/CGGameUI.cpp index b539816..b82d09f 100644 --- a/src/gameui/CGGameUI.cpp +++ b/src/gameui/CGGameUI.cpp @@ -13,6 +13,7 @@ #include "gameui/CGDressUpModelFrame.hpp" #include "gameui/CGTabardModelFrame.hpp" #include "gameui/CGQuestPOIFrame.hpp" +#include "gameui/CGUIBindings.hpp" #include "gx/Coordinate.hpp" #include "gx/Device.hpp" #include "ui/FrameScript.hpp" @@ -84,8 +85,8 @@ void CGGameUI::Initialize() { LoadScriptFunctions(); FrameScript_CreateEvents(g_scriptEvents, 722); //CGGameUI::RegisterGameCVars(); - //CGUIBindings::Initialize(); + CGUIBindings::Initialize(); CGGameUI::RegisterFrameFactories(); // STORM_ASSERT(GetDataInterfaceVersion() == GetCodeInterfaceVersion()) @@ -139,8 +140,9 @@ void CGGameUI::Initialize() { FrameXML_FreeHashNodes(); FrameXML_CreateFrames("Interface\\FrameXML\\FrameXML.toc", 0, &md5, &status); - //if (SFile__FileExistsEx((void*)"Interface\\FrameXML\\Bindings.xml", 1)) - // CGUIBindings::Load("Interface\\FrameXML\\Bindings.xml", &md5, &status); + if (SFile::FileExistsEx("Interface\\FrameXML\\Bindings.xml", 1)) { + CGUIBindings::s_bindings->Load("Interface\\FrameXML\\Bindings.xml", &md5, &status); + } unsigned char digest2[16]; MD5Final(digest2, &md5); diff --git a/src/gameui/CGUIBindings.cpp b/src/gameui/CGUIBindings.cpp index a5e6404..5e17020 100644 --- a/src/gameui/CGUIBindings.cpp +++ b/src/gameui/CGUIBindings.cpp @@ -6,15 +6,125 @@ #include "util/StringTo.hpp" #include +#include +#include +#include static CStatus s_nullStatus; +static uint16_t s_initRound = 0; +CGUIBindings* CGUIBindings::s_bindings = nullptr; + + +static bool ValidateKeyString(const char* key) { + static std::pair s_prefixes[3] = { + { "SHIFT-", 6 }, + { "CTRL-", 5 }, + { "ALT-", 4 } + }; + static std::pair s_numberedKeys[6] = { + { "F", 1 }, + { "NUMPAD", 6 }, + { "BUTTON", 6 }, + { "JOYSTICK", 8 }, + { "JOYAXIS", 7 }, + { "JOYBUTTON", 9 }, + }; + + static const char* s_namedKeys[31] = { + "SPACE", + "NUMPADPLUS", + "NUMPADMINUS", + "NUMPADMULTIPLY", + "NUMPADDIVIDE", + "NUMPADDECIMAL", + "ESCAPE", + "ENTER", + "BACKSPACE", + "TAB", + "LEFT", + "UP", + "RIGHT", + "DOWN", + "INSERT", + "DELETE", + "HOME", + "END", + "PAGEUP", + "PAGEDOWN", + "NUMLOCK", + "CAPSLOCK", + "PRINTSCREEN", + "NUMPADEQUALS", + "MOUSEWHEELDOWN", + "MOUSEWHEELUP", + "JOYHAT", + "JOYHATUP", + "JOYHATRIGHT", + "JOYHATDOWN", + "JOYHATLEFT" + }; + + for (uint32_t i = 0; i < 3; ++i) { + if (SStrCmp(s_prefixes[i].first, key, s_prefixes[i].second)) { + break; + } + + key += s_prefixes[i].second; + } + + int32_t chars = 0; + sgetu8(reinterpret_cast(key), &chars); + if (!key[chars]) { + return true; + } + + uint32_t index; + for (index = 0; index < 6; ++index) { + if (!SStrCmp(s_numberedKeys[index].first, key, s_numberedKeys[index].second)) { + auto ch = key[s_numberedKeys[index].second]; + if (ch >= '0' && ch <= '9') { + break; + } + } + } + + if (index >= 6) { + for (uint32_t i = 0; i < 31; ++i) { + if (!SStrCmp(key, s_namedKeys[i], STORM_MAX_STR)) { + return true; + } + } + return false; + } + + const char* tail = &key[s_numberedKeys[index].second]; + while (*tail >= '0' && *tail <= '9') { + ++tail; + } + + if (*tail && + (SStrCmp(s_numberedKeys[index].first, "JOYAXIS", STORM_MAX_STR) + || SStrCmp(tail, "POS", STORM_MAX_STR) + && SStrCmp(tail, "NEG", STORM_MAX_STR))) { + return false; + } + + return true; +} void MODIFIEDCLICK::SetBinding(BINDING_SET a1, const char* binding) { } +void CGUIBindings::Initialize() { + CGUIBindings::s_bindings = NEW(CGUIBindings); + while (!s_initRound) { + ++s_initRound; + } +} + bool CGUIBindings::Load(const char* commandsFile, MD5_CTX* md5, CStatus* status) { if (!status) { status = &s_nullStatus; @@ -134,8 +244,8 @@ void CGUIBindings::LoadBinding(const char* commandsFile, XMLNode* node, CStatus* const char* binding = node->GetAttributeByName("default"); if (binding && *binding) { - if (!this->m_bindings.Ptr(binding)) { - this->Bind(BINDING_SET_0, BINDING_MODE_0, binding, name); + if (!this->m_bindings[BINDING_DEFAULT].Ptr(binding)) { + this->Bind(BINDING_DEFAULT, BINDING_MODE_0, binding, name); } } } @@ -157,7 +267,7 @@ void CGUIBindings::LoadModifiedClick(const char* commandsFile, XMLNode* node, CS const char* binding = node->GetAttributeByName("default"); if (binding && *binding) { - modifiedClick->SetBinding(BINDING_SET_0, binding); + modifiedClick->SetBinding(BINDING_DEFAULT, binding); } } @@ -209,20 +319,95 @@ bool CGUIBindings::Bind(BINDING_SET set, BINDING_MODE mode, const char* keystrin key = s_character; } - // TODO: if (!sub_55DAB0) - - auto binding = this->m_bindings.Ptr(key); - if (!binding) { - binding = this->m_bindings.New(key, 0, 0); + if (!ValidateKeyString(key)) { + return false; } - if (set != BINDING_SET_0) { + auto binding = this->m_bindings[set].Ptr(key); + if (!binding) { + binding = this->m_bindings[set].New(key, 0, 0); + } + + if (set != BINDING_DEFAULT) { binding->flags &= ~1u; } else { binding->flags |= 1u; } - // TODO + auto bindingCommand = this->GetBindingCommand(binding, mode); + if (!bindingCommand || !command || SStrCmp(bindingCommand, command, STORM_MAX_STR)) { + if (bindingCommand) { + auto index = this->GetBindingIndex(binding, mode); + this->AdjustCommandKeyIndices(set, mode, bindingCommand, index); + } + auto index = this->GetNumCommandKeys(set, mode, command); + binding->data[mode].command.Copy(command); + binding->data[mode].index = index; + } return true; } + +const char* CGUIBindings::GetBindingCommand(KEYBINDING* binding, BINDING_MODE mode) const { + if (mode != BINDING_MODE_4) { + return binding->data[mode].command.GetString(); + } + + for (int32_t m = BINDING_MODE_3; m >= BINDING_MODE_0; --m) { + // TODO + auto result = binding->data[m].command.GetString(); + if (result) + return result; + } + + return nullptr; +} + +int32_t CGUIBindings::GetBindingIndex(KEYBINDING* binding, BINDING_MODE mode) const { + if (mode != BINDING_MODE_4) { + return binding->data[mode].index; + } + + for (int32_t m = BINDING_MODE_3; m >= BINDING_MODE_0; --m) { + // TODO + if (binding->data[m].command.GetString()) + return binding->data[m].index; + } + + return -1; +} + +int32_t CGUIBindings::GetNumCommandKeys(BINDING_SET set, BINDING_MODE mode, const char* command) { + auto binding = this->m_bindings[set].Head(); + + int32_t result = 0; + + while (binding) { + auto bindingCommand = this->GetBindingCommand(binding, mode); + if (bindingCommand && !SStrCmpI(bindingCommand, command, STORM_MAX_STR)) { + ++result; + } + + binding = this->m_bindings[set].Next(binding); + } + + return result; +} + +void CGUIBindings::AdjustCommandKeyIndices(BINDING_SET set, BINDING_MODE mode, const char* command, int32_t index) { + auto binding = this->m_bindings[set].Head(); + + int32_t result = 0; + + while (binding) { + auto bindingCommand = this->GetBindingCommand(binding, mode); + if (bindingCommand && !SStrCmpI(bindingCommand, command, STORM_MAX_STR)) { + auto bindingIndex = this->GetBindingIndex(binding, mode); + if (bindingIndex > index) { + --binding->data[mode].index; + } + } + + binding = this->m_bindings[set].Next(binding); + } +} diff --git a/src/gameui/CGUIBindings.hpp b/src/gameui/CGUIBindings.hpp index 839cfc2..b2b484f 100644 --- a/src/gameui/CGUIBindings.hpp +++ b/src/gameui/CGUIBindings.hpp @@ -3,28 +3,33 @@ #include #include +#include class CStatus; class XMLNode; enum BINDING_SET { - BINDING_SET_0 = 0, + BINDING_DEFAULT = 0, BINDING_SET_1, BINDING_SET_2, - BINDING_SET_3, + BINDING_SCRIPT, }; enum BINDING_MODE { BINDING_MODE_0 = 0, BINDING_MODE_1, BINDING_MODE_2, - BINDING_MODE_3 + BINDING_MODE_3, + BINDING_MODE_4 }; class KEYBINDING : public TSHashObject { public: uint32_t flags; - char* command; + struct { + int32_t index; + RCString command; + } data[4]; }; class KEYCOMMAND : public TSHashObject { @@ -45,17 +50,26 @@ class MODIFIEDCLICK : public TSHashObject { class CGUIBindings { public: + static CGUIBindings* s_bindings; + + static void Initialize(); + + CGUIBindings() = default; bool Load(const char* commandsFile, MD5_CTX* md5, CStatus* status); void LoadBinding(const char* commandsFile, XMLNode* node, CStatus* status); void LoadModifiedClick(const char* commandsFile, XMLNode* node, CStatus* status); bool Bind(BINDING_SET set, BINDING_MODE mode, const char* keystring, const char* command); + const char* GetBindingCommand(KEYBINDING* binding, BINDING_MODE mode) const; + int32_t GetBindingIndex(KEYBINDING* binding, BINDING_MODE mode) const; + int32_t GetNumCommandKeys(BINDING_SET set, BINDING_MODE mode, const char* command); + void AdjustCommandKeyIndices(BINDING_SET set, BINDING_MODE mode, const char* command, int32_t index); int32_t m_numCommands; int32_t m_numHiddenCommands; int32_t m_numModifiedClicks; - TSHashTable m_bindings; + TSHashTable m_bindings[4]; TSHashTable m_commands; TSHashTable m_modifiedClicks; }; diff --git a/src/gameui/scripts/GameScriptFunctionsUIBindings.cpp b/src/gameui/scripts/GameScriptFunctionsUIBindings.cpp index 7c13947..32127f6 100644 --- a/src/gameui/scripts/GameScriptFunctionsUIBindings.cpp +++ b/src/gameui/scripts/GameScriptFunctionsUIBindings.cpp @@ -1,4 +1,5 @@ #include "gameui/GameScriptFunctions.hpp" +#include "gameui/CGUIBindings.hpp" #include "ui/FrameScript.hpp" #include "util/Lua.hpp" #include "util/Unimplemented.hpp" @@ -13,23 +14,144 @@ static int32_t Script_GetBinding(lua_State* L) { } static int32_t Script_SetBinding(lua_State* L) { - WHOA_UNIMPLEMENTED(0); + if (!lua_isstring(L, 1)) { + return luaL_error(L, "Usage: SetBinding(\"KEY\"[, \"COMMAND\"][, mode])"); + } + + int32_t mode = BINDING_MODE_0; + if (lua_isnumber(L, 3)) { + mode = static_cast(lua_tonumber(L, 3)) - 1; + if (mode < BINDING_MODE_0 || mode > BINDING_MODE_3) { + mode = BINDING_MODE_0; + } + } + auto key = lua_tolstring(L, 1, 0); + auto command = lua_tolstring(L, 2, 0); + if (CGUIBindings::s_bindings->Bind(BINDING_SCRIPT, static_cast(mode), key, command)) { + FrameScript_SignalEvent(0x177u, 0); + lua_pushnumber(L, 1.0); + } else { + lua_pushnil(L); + } + return 1; } static int32_t Script_SetBindingSpell(lua_State* L) { - WHOA_UNIMPLEMENTED(0); + if (!lua_isstring(L, 1) || !lua_isstring(L, 2)) { + return luaL_error(L, "Usage: SetBindingSpell(\"KEY\", \"spellname\"[, mode])"); + } + + int32_t mode = BINDING_MODE_0; + if (lua_isnumber(L, 3)) { + mode = static_cast(lua_tonumber(L, 3)) - 1; + if (mode < BINDING_MODE_0 || mode > BINDING_MODE_3) { + mode = BINDING_MODE_0; + } + } + auto key = lua_tolstring(L, 1, 0); + + auto spellName = lua_tolstring(L, 2, 0); + auto length = SStrLen(spellName) + 7; + auto command = static_cast(alloca(length)); + SStrPrintf(command, length, "SPELL %s", spellName); + + if (CGUIBindings::s_bindings->Bind(BINDING_SCRIPT, static_cast(mode), key, command)) { + FrameScript_SignalEvent(0x177u, 0); + lua_pushnumber(L, 1.0); + } else { + lua_pushnil(L); + } + return 1; } static int32_t Script_SetBindingItem(lua_State* L) { - WHOA_UNIMPLEMENTED(0); + if (!lua_isstring(L, 1) || !lua_isstring(L, 2)) { + return luaL_error(L, "Usage: SetBindingItem(\"KEY\", \"itemname\"[, mode])"); + } + + int32_t mode = BINDING_MODE_0; + if (lua_isnumber(L, 3)) { + mode = static_cast(lua_tonumber(L, 3)) - 1; + if (mode < BINDING_MODE_0 || mode > BINDING_MODE_3) { + mode = BINDING_MODE_0; + } + } + auto key = lua_tolstring(L, 1, 0); + + auto itemName = lua_tolstring(L, 2, 0); + auto length = SStrLen(itemName) + 7; + auto command = static_cast(alloca(length)); + SStrPrintf(command, length, "ITEM %s", itemName); + + if (CGUIBindings::s_bindings->Bind(BINDING_SCRIPT, static_cast(mode), key, command)) { + FrameScript_SignalEvent(0x177u, 0); + lua_pushnumber(L, 1.0); + } else { + lua_pushnil(L); + } + return 1; } static int32_t Script_SetBindingMacro(lua_State* L) { - WHOA_UNIMPLEMENTED(0); + if (!lua_isstring(L, 1) || !lua_isstring(L, 2)) { + return luaL_error(L, "Usage: SetBindingMacro(\"KEY\", \"macroname\"|macroid[, mode])"); + } + + int32_t mode = BINDING_MODE_0; + if (lua_isnumber(L, 3)) { + mode = static_cast(lua_tonumber(L, 3)) - 1; + if (mode < BINDING_MODE_0 || mode > BINDING_MODE_3) { + mode = BINDING_MODE_0; + } + } + auto key = lua_tolstring(L, 1, 0); + + auto macroName = lua_tolstring(L, 2, 0); + auto length = SStrLen(macroName) + 7; + auto command = static_cast(alloca(length)); + SStrPrintf(command, length, "MACRO %s", macroName); + + if (CGUIBindings::s_bindings->Bind(BINDING_SCRIPT, static_cast(mode), key, command)) { + FrameScript_SignalEvent(0x177u, 0); + lua_pushnumber(L, 1.0); + } else { + lua_pushnil(L); + } + return 1; } static int32_t Script_SetBindingClick(lua_State* L) { - WHOA_UNIMPLEMENTED(0); + if (!lua_isstring(L, 1) || !lua_isstring(L, 2)) { + return luaL_error(L, "Usage: SetBindingClick(\"KEY\", \"buttonName\"[, \"mouseButton\"][, mode])"); + } + + int32_t mode = BINDING_MODE_0; + if (lua_isnumber(L, 4)) { + mode = static_cast(lua_tonumber(L, 4)) - 1; + if (mode < BINDING_MODE_0 || mode > BINDING_MODE_3) { + mode = BINDING_MODE_0; + } + } + auto key = lua_tolstring(L, 1, 0); + + auto buttonName = lua_tolstring(L, 2, 0); + + auto mouseButton = lua_tolstring(L, 3, 0); + if (!mouseButton) { + mouseButton = "LeftButton"; + } + + auto length = SStrLen(buttonName) + SStrLen(mouseButton) + 8; + auto command = static_cast(alloca(length)); + SStrPrintf(command, length, "CLICK %s:%s", buttonName, mouseButton); + + if (CGUIBindings::s_bindings->Bind(BINDING_SCRIPT, static_cast(mode), key, command)) { + FrameScript_SignalEvent(0x177u, 0); + lua_pushnumber(L, 1.0); + } else { + lua_pushnil(L); + } + return 1; } static int32_t Script_SetOverrideBinding(lua_State* L) {