diff --git a/.gitmodules b/.gitmodules index 0459fca8..0a1d6d6f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -44,3 +44,6 @@ [submodule "deps/iw4-open-formats"] path = deps/iw4-open-formats url = https://github.com/iw4x/iw4-open-formats.git +[submodule "deps/dualsense-windows"] + path = deps/dualsense-windows + url = git@github.com:Ohjurot/DualSense-Windows.git diff --git a/deps/dualsense-windows b/deps/dualsense-windows new file mode 160000 index 00000000..0b869f4e --- /dev/null +++ b/deps/dualsense-windows @@ -0,0 +1 @@ +Subproject commit 0b869f4e34e4a6b608023a2f1e19bc28c037b64f diff --git a/deps/premake/dualsense-windows.lua b/deps/premake/dualsense-windows.lua new file mode 100644 index 00000000..59f7f6bb --- /dev/null +++ b/deps/premake/dualsense-windows.lua @@ -0,0 +1,42 @@ +dlwin = { + source = path.join(dependencies.basePath, "dualsense-windows/VS19_Solution/DualSenseWindows"), +} + +function dlwin.import() + links {"dualsense-windows"} + + dlwin.includes() +end + +function dlwin.includes() + includedirs { + path.join(dlwin.source, "include"), + } +end + + +function dlwin.project() + project "dualsense-windows" + language "C++" + + defines { + "DS5W_BUILD_LIB" + } + + includedirs { + path.join(dlwin.source, "include"), + path.join(dlwin.source, "src"), + } + + files + { + path.join(dlwin.source, "**.cpp"), + path.join(dlwin.source, "**.h"), + } + + warnings "Off" + + kind "StaticLib" +end + +table.insert(dependencies, dlwin) diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 7a6905fb..1bda9e68 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -47,6 +47,7 @@ #include "Modules/RawFiles.hpp" #include "Modules/RawMouse.hpp" #include "Modules/RCon.hpp" +#include "Modules/Rumble.hpp" #include "Modules/Security.hpp" #include "Modules/ServerCommands.hpp" #include "Modules/ServerInfo.hpp" @@ -141,6 +142,7 @@ namespace Components Register(new FileSystem()); Register(new Friends()); Register(new Gamepad()); + Register(new Rumble()); Register(new Huffman()); Register(new Lean()); Register(new Localization()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 41c14b78..5219725c 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -80,5 +80,6 @@ namespace Components #include "Modules/Renderer.hpp" #include "Modules/Scheduler.hpp" #include "Modules/Zones.hpp" +#include "Modules/Rumble.hpp" #include "Modules/GSC/GSC.hpp" diff --git a/src/Components/Modules/ConfigStrings.cpp b/src/Components/Modules/ConfigStrings.cpp index 020093d5..892a78cf 100644 --- a/src/Components/Modules/ConfigStrings.cpp +++ b/src/Components/Modules/ConfigStrings.cpp @@ -22,7 +22,7 @@ namespace Components constexpr auto EXTRA_MODELCACHE_LAST = EXTRA_MODELCACHE_FIRST + ModelCache::ADDITIONAL_GMODELS; constexpr auto RUMBLE_FIRST = EXTRA_MODELCACHE_LAST + 1; - constexpr auto RUMBLE_LAST = RUMBLE_FIRST + 31; // TODO + constexpr auto RUMBLE_LAST = RUMBLE_FIRST + Gamepad::RUMBLE_CONFIGSTRINGS_COUNT - 1; // TODO void ConfigStrings::PatchConfigStrings() { @@ -156,6 +156,21 @@ namespace Components { SV_SetConfigString(index, data, Game::CS_WEAPONFILES_LAST, EXTRA_WEAPONS_FIRST); } + + const char* ConfigStrings::CL_GetRumbleConfigString(int index) + { + return CL_GetConfigString(RUMBLE_FIRST + index, Game::CS_LAST, Game::MAX_CONFIGSTRINGS); + } + + unsigned int ConfigStrings::SV_GetRumbleConfigStringConst(int index) + { + return SV_GetConfigString(RUMBLE_FIRST +index, Game::CS_LAST, Game::MAX_CONFIGSTRINGS); + } + + void ConfigStrings::SV_SetRumbleConfigString(int index, const char* data) + { + SV_SetConfigString(RUMBLE_FIRST + index, data, Game::CS_LAST, Game::MAX_CONFIGSTRINGS); + } void ConfigStrings::SV_SetCachedModelConfigString(int index, const char* data) { @@ -251,6 +266,13 @@ namespace Components index = index - EXTRA_MODELCACHE_FIRST + ModelCache::BASE_GMODEL_COUNT; ModelCache::gameModels_reallocated[index] = Utils::Hook::Call(R_RegisterModel)(name); } + // Rumble! + else if (index >= RUMBLE_FIRST && index <= RUMBLE_LAST) + { + // Apparently, there is nothing to do here. At least the game doesn't look like + // it needs anything to be done. If there was to do rumble string replication + // between server and client, it would happen here + } else { // Unknown for now? diff --git a/src/Components/Modules/ConfigStrings.hpp b/src/Components/Modules/ConfigStrings.hpp index 26ff78a3..87cdad28 100644 --- a/src/Components/Modules/ConfigStrings.hpp +++ b/src/Components/Modules/ConfigStrings.hpp @@ -21,6 +21,11 @@ namespace Components ConfigStrings(); + static const char* CL_GetRumbleConfigString(int index); + static unsigned int SV_GetRumbleConfigStringConst(int index); + static void SV_SetRumbleConfigString(int index, const char* data); + + private: static void PatchConfigStrings(); diff --git a/src/Components/Modules/GSC/Script.cpp b/src/Components/Modules/GSC/Script.cpp index 2c2c2888..ba7c343c 100644 --- a/src/Components/Modules/GSC/Script.cpp +++ b/src/Components/Modules/GSC/Script.cpp @@ -6,6 +6,9 @@ namespace Components::GSC std::vector Script::CustomScrFunctions; std::vector Script::CustomScrMethods; + std::vector Script::CommonOverridenFunctions; + std::vector Script::CommonOverridenMethods; + std::unordered_map Script::ScriptMainHandles; std::unordered_map Script::ScriptInitHandles; @@ -117,24 +120,38 @@ namespace Components::GSC Game::GScr_LoadGameTypeScript(); } - void Script::AddFunction(const std::string& name, const Game::BuiltinFunction func, const bool type) + void Script::AddFunction(const std::string& name, const Game::BuiltinFunction func, const bool type, const bool builtIn) { ScriptFunction toAdd; toAdd.actionFunc = func; toAdd.type = type; - toAdd.aliases.push_back({Utils::String::ToLower(name)}); + toAdd.aliases.push_back({ Utils::String::ToLower(name) }); - CustomScrFunctions.emplace_back(toAdd); + if (builtIn) + { + CommonOverridenFunctions.emplace_back(toAdd); + } + else + { + CustomScrFunctions.emplace_back(toAdd); + } } - void Script::AddMethod(const std::string& name, const Game::BuiltinMethod func, const bool type) + void Script::AddMethod(const std::string& name, const Game::BuiltinMethod func, const bool type, const bool builtIn) { ScriptMethod toAdd; toAdd.actionFunc = func; toAdd.type = type; - toAdd.aliases.push_back({Utils::String::ToLower(name)}); - - CustomScrMethods.emplace_back(toAdd); + toAdd.aliases.push_back({ Utils::String::ToLower(name) }); + + if (builtIn) + { + CommonOverridenMethods.emplace_back(toAdd); + } + else + { + CustomScrMethods.emplace_back(toAdd); + } } void Script::AddFuncMultiple(Game::BuiltinFunction func, bool type, scriptNames aliases) @@ -161,6 +178,34 @@ namespace Components::GSC CustomScrMethods.emplace_back(toAdd); } + Game::BuiltinFunction Script::Common_GetFunctionStub(const char** pName, int* type) + { + if (pName != nullptr) + { + const auto name = Utils::String::ToLower(*pName); + for (const auto& funcs : CommonOverridenFunctions) + { + if (std::ranges::find(funcs.aliases, name) != funcs.aliases.end()) + { + *type = funcs.type; + return funcs.actionFunc; + } + } + } + else + { + // "func" here is a struct that contains several things + for (const auto& func : CommonOverridenFunctions) + { + const auto& name = func.aliases.at(0); + Game::Scr_RegisterFunction(reinterpret_cast(func.actionFunc), name.data()); + } + } + + // If no function was found let's call game's function + return Utils::Hook::Call(0x4BCF80)(pName, type); // Common_GetFunction + } + Game::BuiltinFunction Script::BuiltIn_GetFunctionStub(const char** pName, int* type) { if (pName != nullptr) @@ -188,6 +233,32 @@ namespace Components::GSC return Utils::Hook::Call(0x5FA2B0)(pName, type); // BuiltIn_GetFunction } + Game::BuiltinMethod Script::Common_GetMethodStub(const char** pName) + { + if (pName != nullptr) + { + const auto name = Utils::String::ToLower(*pName); + for (const auto& meths : CommonOverridenMethods) + { + if (std::ranges::find(meths.aliases, name) != meths.aliases.end()) + { + return meths.actionFunc; + } + } + } + else + { + for (const auto& meth : CommonOverridenMethods) + { + const auto& name = meth.aliases.at(0); + Game::Scr_RegisterFunction(reinterpret_cast(meth.actionFunc), name.data()); + } + } + + // If no method was found let's call game's function + return Utils::Hook::Call(0x4DBA50)(pName); // Player_GetMethod + } + Game::BuiltinMethod Script::BuiltIn_GetMethodStub(const char** pName, int* type) { if (pName != nullptr) @@ -282,6 +353,10 @@ namespace Components::GSC // Fetch custom functions Utils::Hook(0x44E72E, BuiltIn_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_GetFunction Utils::Hook(0x4EC8DD, BuiltIn_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_GetMethod + Utils::Hook(0x4EC881, Common_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_GetMethod + + Utils::Hook(0x44E712, Common_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_GetFunction + Utils::Hook(0x5F41A3, SetExpFogStub, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/GSC/Script.hpp b/src/Components/Modules/GSC/Script.hpp index 6cfe112c..82630f73 100644 --- a/src/Components/Modules/GSC/Script.hpp +++ b/src/Components/Modules/GSC/Script.hpp @@ -8,12 +8,13 @@ namespace Components::GSC Script(); using scriptNames = std::vector; - static void AddFunction(const std::string& name, Game::BuiltinFunction func, bool type = false); - static void AddMethod(const std::string& name, Game::BuiltinMethod func, bool type = false); + static void AddFunction(const std::string& name, Game::BuiltinFunction func, bool type = false, bool isBuitIn= false); + static void AddMethod(const std::string& name, Game::BuiltinMethod func, bool type = false, bool isBuitIn= false); static void AddFuncMultiple(Game::BuiltinFunction func, bool type, scriptNames); static void AddMethMultiple(Game::BuiltinMethod func, bool type, scriptNames); + static Game::client_s* GetClient(const Game::gentity_s* gentity); // Probably a macro 'originally' but this is fine static Game::gentity_s* Scr_GetPlayerEntity(Game::scr_entref_t entref); @@ -33,6 +34,9 @@ namespace Components::GSC scriptNames aliases; }; + static std::vector CommonOverridenFunctions; + static std::vector CommonOverridenMethods; + static std::vector CustomScrFunctions; static std::vector CustomScrMethods; @@ -46,6 +50,9 @@ namespace Components::GSC static void Scr_StartupGameType_Stub(); static void GScr_LoadGameTypeScript_Stub(); + static Game::BuiltinFunction Common_GetFunctionStub(const char** pName, int* type); + static Game::BuiltinMethod Common_GetMethodStub(const char** pName); + static Game::BuiltinFunction BuiltIn_GetFunctionStub(const char** pName, int* type); static Game::BuiltinMethod BuiltIn_GetMethodStub(const char** pName, int* type); diff --git a/src/Components/Modules/GSC/ScriptError.cpp b/src/Components/Modules/GSC/ScriptError.cpp index edf1795a..8b081e7f 100644 --- a/src/Components/Modules/GSC/ScriptError.cpp +++ b/src/Components/Modules/GSC/ScriptError.cpp @@ -800,7 +800,7 @@ namespace Components::GSC unsigned int ScriptError::Scr_LoadScriptInternal_Hk(const char* filename, Game::PrecacheEntry* entries, int entriesCount) { char extFilename[MAX_QPATH]; - Game::sval_u parseData; + Game::sval_u parseData{}; assert(Game::scrCompilePub->script_loading); assert(std::strlen(filename) < MAX_QPATH); diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp index 05388d7c..8641ff69 100644 --- a/src/Components/Modules/Gamepad.cpp +++ b/src/Components/Modules/Gamepad.cpp @@ -151,9 +151,10 @@ namespace Components {"^\x01\x32\x32\x0D""dpad_ps3_left", Game::K_DPAD_LEFT}, {"^\x01\x32\x32\x0E""dpad_ps3_right", Game::K_DPAD_RIGHT}, }; - Game::keyname_t Gamepad::combinedKeyNames[Game::KEY_NAME_COUNT + std::extent_v + 1]; - Game::keyname_t Gamepad::combinedLocalizedKeyNamesXenon[Game::KEY_NAME_COUNT + std::extent_v + 1]; - Game::keyname_t Gamepad::combinedLocalizedKeyNamesPs3[Game::KEY_NAME_COUNT + std::extent_v + 1]; + + Game::keyname_t Gamepad::combinedKeyNames[Game::KEY_NAME_COUNT + std::extent_v +1]; + Game::keyname_t Gamepad::combinedLocalizedKeyNamesXenon[Game::KEY_NAME_COUNT + std::extent_v +1]; + Game::keyname_t Gamepad::combinedLocalizedKeyNamesPs3[Game::KEY_NAME_COUNT + std::extent_v +1]; Gamepad::ControllerMenuKeyMapping Gamepad::controllerMenuKeyMappings[] { @@ -171,12 +172,11 @@ namespace Components {Game::K_APAD_RIGHT, Game::K_RIGHTARROW}, }; - Gamepad::GamePad Gamepad::gamePads[Game::MAX_GPAD_COUNT]{}; - Gamepad::GamePadGlobals Gamepad::gamePadGlobals[Game::MAX_GPAD_COUNT]{{}}; + GamepadControls::Controller Gamepad::gamePads[Game::MAX_GPAD_COUNT]{}; + Gamepad::GamePadGlobals Gamepad::gamePadGlobals[Game::MAX_GPAD_COUNT]{ {} }; int Gamepad::gamePadBindingsModifiedFlags = 0; Dvar::Var Gamepad::gpad_enabled; - Dvar::Var Gamepad::gpad_debug; Dvar::Var Gamepad::gpad_present; Dvar::Var Gamepad::gpad_in_use; Dvar::Var Gamepad::gpad_style; @@ -185,13 +185,6 @@ namespace Components Dvar::Var Gamepad::gpad_menu_scroll_delay_first; Dvar::Var Gamepad::gpad_menu_scroll_delay_rest; Dvar::Var Gamepad::gpad_rumble; - Dvar::Var Gamepad::gpad_stick_pressed_hysteresis; - Dvar::Var Gamepad::gpad_stick_pressed; - Dvar::Var Gamepad::gpad_stick_deadzone_max; - Dvar::Var Gamepad::gpad_stick_deadzone_min; - Dvar::Var Gamepad::gpad_button_deadzone; - Dvar::Var Gamepad::gpad_button_rstick_deflect_max; - Dvar::Var Gamepad::gpad_button_lstick_deflect_max; Dvar::Var Gamepad::gpad_use_hold_time; Dvar::Var Gamepad::gpad_lockon_enabled; Dvar::Var Gamepad::gpad_slowdown_enabled; @@ -223,7 +216,7 @@ namespace Components Gamepad::GamePadGlobals::GamePadGlobals() : axes{}, - nextScrollTime(0) + nextScrollTime(0) { for (auto& virtualAxis : axes.virtualAxes) { @@ -243,12 +236,12 @@ namespace Components mov dl, byte ptr[edi + 1Ah] // to_forwardMove mov dh, byte ptr[edi + 1Bh] // to_rightMove - mov [esp + 30h], dx // to_buttons + mov[esp + 30h], dx // to_buttons - mov dl, byte ptr [ebp + 1Ah] // from_forwardMove - mov dh, byte ptr [ebp + 1Bh] // from_rightMove + mov dl, byte ptr[ebp + 1Ah] // from_forwardMove + mov dh, byte ptr[ebp + 1Bh] // from_rightMove - mov [esp + 2Ch], dx // from_buttons + mov[esp + 2Ch], dx // from_buttons // return back push 0x60E40E @@ -320,15 +313,8 @@ namespace Components auto& gamePad = gamePads[localClientNum]; - if (XInputGetCapabilities(portIndex, XINPUT_FLAG_GAMEPAD, &gamePad.caps) == ERROR_SUCCESS) - { - gamePad.enabled = true; - gamePad.portIndex = portIndex; - return true; - } - - gamePad.enabled = false; - return false; + return gamePad.get_enabled() || + gamePad.PlugIn(static_cast(portIndex)); } void Gamepad::GPad_RefreshAll() @@ -428,7 +414,7 @@ namespace Components } const Game::AimScreenTarget* Gamepad::AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight, - const int prevTargetEnt) + const int prevTargetEnt) { const auto screenTarget = AimAssist_GetTargetFromEntity(aaGlob, prevTargetEnt); @@ -509,10 +495,10 @@ namespace Components - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[2][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[2][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[2][2])) / arcLength * 180.0f * aim_lockon_pitch_strength.get(); - const auto yawTurnRate = + const auto yawTurnRate = (screenTarget->velocity[0] * aaGlob.viewAxis[1][0] + screenTarget->velocity[1] * aaGlob.viewAxis[1][1] + screenTarget->velocity[2] * aaGlob.viewAxis[1][2] - - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[1][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[1][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[1][2])) - / arcLength * 180.0f * aim_lockon_strength.get(); + - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[1][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[1][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[1][2])) + / arcLength * 180.0f * aim_lockon_strength.get(); output->pitch -= pitchTurnRate * input->deltaTime; output->yaw += yawTurnRate * input->deltaTime; @@ -617,7 +603,7 @@ namespace Components *pitchScale = 1.0f; *yawScale = 1.0f; - + if (!AimAssist_IsSlowdownActive(&aaGlob.ps)) { return; @@ -767,10 +753,10 @@ namespace Components bool Gamepad::CG_HandleLocationSelectionInput_GamePad(const int localClientNum, Game::usercmd_s* /*cmd*/) { // Buttons are already handled by keyboard input handler - - const auto frameTime = static_cast(Game::cgArray[0].frametime) * 0.001f; - const auto mapAspectRatio = Game::cgArray[0].compassMapWorldSize[0] / Game::cgArray[0].compassMapWorldSize[1]; - const auto selectionRequiresAngle = (Game::cgArray[0].predictedPlayerState.locationSelectionInfo & 0x80) != 0; + const auto clientGlob = Game::CL_GetLocalClientGlobals(localClientNum); + const auto frameTime = static_cast(clientGlob->frametime) * 0.001f; + const auto mapAspectRatio = clientGlob->compassMapWorldSize[0] / clientGlob->compassMapWorldSize[1]; + const auto selectionRequiresAngle = (clientGlob->predictedPlayerState.locationSelectionInfo & 0x80) != 0; auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); @@ -824,15 +810,15 @@ namespace Components call CG_HandleLocationSelectionInput - test al,al + test al, al jz exit_handling // Call our function, the args were already prepared earlier call CG_HandleLocationSelectionInput_GamePad - exit_handling: + exit_handling : add esp, 0x8 - ret + ret } } @@ -903,8 +889,8 @@ namespace Components if (std::fabs(side) > 0.0f || std::fabs(forward) > 0.0f) { const auto length = std::fabs(side) <= std::fabs(forward) - ? side / forward - : forward / side; + ? side / forward + : forward / side; moveScale = std::sqrt((length * length) + 1.0f) * moveScale; } @@ -971,7 +957,7 @@ namespace Components { Game::CL_MouseMove(localClientNum, cmd, frametime_base); } - else if (gpad_enabled.get() && gamePad.enabled) + else if (gpad_enabled.get() && gamePad.get_enabled()) { CL_GamepadMove(localClientNum, frametime_base, cmd); } @@ -983,7 +969,7 @@ namespace Components { pushad - push [esp + 0x20 + 0x4] // frametime_base + push[esp + 0x20 + 0x4] // frametime_base push ebx // cmd push eax // localClientNum call CL_MouseMove @@ -1016,7 +1002,7 @@ namespace Components push edi call Gamepad_ShouldUse add esp, 8h - mov [esp + 0x20], eax + mov[esp + 0x20], eax popad pop eax @@ -1028,9 +1014,9 @@ namespace Components push 0x5FE39B ret - skipUse: + skipUse : push 0x5FE3AF - ret + ret } } @@ -1077,21 +1063,21 @@ namespace Components assert(stickIndex < 4); const auto& mapping = analogStickList[stickIndex]; - if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_POS]) + if (gamePad.get_stickDown(stickIndex, Game::GPAD_STICK_POS)) { - const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; + const Game::GamePadButtonEvent event = gamePad.get_stickDownLast(stickIndex, Game::GPAD_STICK_POS) ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; CL_GamepadButtonEvent(localClientNum, mapping.posCode, event, time); } - else if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_NEG]) + else if (gamePad.get_stickDown(stickIndex, Game::GPAD_STICK_NEG)) { - const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; + const Game::GamePadButtonEvent event = gamePad.get_stickDownLast(stickIndex, Game::GPAD_STICK_NEG) ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; CL_GamepadButtonEvent(localClientNum, mapping.negCode, event, time); } - else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS]) + else if (gamePad.get_stickDownLast(stickIndex, Game::GPAD_STICK_POS)) { CL_GamepadButtonEvent(localClientNum, mapping.posCode, Game::GPAD_BUTTON_RELEASED, time); } - else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG]) + else if (gamePad.get_stickDownLast(stickIndex, Game::GPAD_STICK_NEG)) { CL_GamepadButtonEvent(localClientNum, mapping.negCode, Game::GPAD_BUTTON_RELEASED, time); } @@ -1230,11 +1216,11 @@ namespace Components if (Game::Key_IsCatcherActive(localClientNum, Game::KEYCATCH_LOCATION_SELECTION) && pressedOrUpdated) { - if (key == Game::K_BUTTON_B || keyState.keys[key].binding && std::strcmp(keyState.keys[key].binding, "+actionslot 4") == 0) + if (key == Game::K_BUTTON_B || keyState.keys[key].binding && keyState.keys[key].binding == "+actionslot 4"s) { keyState.locSelInputState = Game::LOC_SEL_INPUT_CANCEL; } - else if (key == Game::K_BUTTON_A || keyState.keys[key].binding && std::strcmp(keyState.keys[key].binding, "+attack") == 0) + else if (key == Game::K_BUTTON_A || keyState.keys[key].binding && keyState.keys[key].binding == "+attack"s) { keyState.locSelInputState = Game::LOC_SEL_INPUT_CONFIRM; } @@ -1308,285 +1294,68 @@ namespace Components CL_GamepadButtonEvent(localClientNum, key, buttonEvent, time); } - void Gamepad::GPad_ConvertStickToFloat(const short x, const short y, float& outX, float& outY) + void Gamepad::GPad_SetLowRumble(int gamePadIndex, double rumble) { - if (x == 0 && y == 0) - { - outX = 0.0f; - outY = 0.0f; - return; - } - - Game::vec2_t stickVec; - stickVec[0] = static_cast(x) / static_cast(std::numeric_limits::max()); - stickVec[1] = static_cast(y) / static_cast(std::numeric_limits::max()); - - const auto deadZoneTotal = gpad_stick_deadzone_min.get() + gpad_stick_deadzone_max.get(); - auto len = Game::Vec2Normalize(stickVec); - - if (gpad_stick_deadzone_min.get() <= len) + if (Gamepad::gpad_rumble.get()) { - if (1.0f - gpad_stick_deadzone_max.get() >= len) - { - len = (len - gpad_stick_deadzone_min.get()) / (1.0f - deadZoneTotal); - } - else - { - len = 1.0f; - } - } - else - { - len = 0.0f; - } + AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); - outX = stickVec[0] * len; - outY = stickVec[1] * len; - } - - float Gamepad::GPad_GetStick(const int localClientNum, const Game::GamePadStick stick) - { - AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - assert(stick & Game::GPAD_STICK_MASK); - - auto& gamePad = gamePads[localClientNum]; - return gamePad.sticks[stick]; - } - - float Gamepad::GPad_GetButton(const int localClientNum, Game::GamePadButton button) - { - AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - auto& gamePad = gamePads[localClientNum]; - - float value = 0.0f; - - if (button & Game::GPAD_DIGITAL_MASK) - { - const auto buttonValue = button & Game::GPAD_VALUE_MASK; - value = buttonValue & gamePad.digitals ? 1.0f : 0.0f; + auto& gamePad = gamePads[gamePadIndex]; + gamePad.SetLowRumble(rumble); } - else if (button & Game::GPAD_ANALOG_MASK) - { - const auto analogIndex = button & Game::GPAD_VALUE_MASK; - if (analogIndex < std::extent_v) - { - value = gamePad.analogs[analogIndex]; - } - } - - return value; } - bool Gamepad::GPad_IsButtonPressed(const int localClientNum, Game::GamePadButton button) + void Gamepad::GPad_SetHighRumble(int gamePadIndex, double rumble) { - AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - assert(button & (Game::GPAD_DIGITAL_MASK | Game::GPAD_ANALOG_MASK)); - - auto& gamePad = gamePads[localClientNum]; - - bool down = false; - bool lastDown = false; - - if (button & Game::GPAD_DIGITAL_MASK) - { - const auto buttonValue = button & Game::GPAD_VALUE_MASK; - down = (buttonValue & gamePad.digitals) != 0; - lastDown = (buttonValue & gamePad.lastDigitals) != 0; - } - else if (button & Game::GPAD_ANALOG_MASK) - { - const auto analogIndex = button & Game::GPAD_VALUE_MASK; - assert(analogIndex < std::extent_v); + AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); - if (analogIndex < std::extent_v) - { - down = gamePad.analogs[analogIndex] > 0.0f; - lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; - } - } - - return down && !lastDown; + auto& gamePad = gamePads[gamePadIndex]; + gamePad.SetHighRumble(rumble); } - bool Gamepad::GPad_ButtonRequiresUpdates(const int localClientNum, Game::GamePadButton button) + void Gamepad::GPad_StopRumbles(int gamePadIndex) { - return (button & Game::GPAD_ANALOG_MASK || button & Game::GPAD_DPAD_MASK) && GPad_GetButton(localClientNum, button) > 0.0f; + AssertIn(gamePadIndex, Game::MAX_GPAD_COUNT); + gamePads[gamePadIndex].StopRumbles(); } - bool Gamepad::GPad_IsButtonReleased(int localClientNum, Game::GamePadButton button) + void Gamepad::GPad_UpdateAll() { - AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - - auto& gamePad = gamePads[localClientNum]; - - bool down = false; - bool lastDown = false; - - if (button & Game::GPAD_DIGITAL_MASK) - { - const auto buttonValue = button & Game::GPAD_VALUE_MASK; + GPad_RefreshAll(); - down = (gamePad.digitals & buttonValue) != 0; - lastDown = (gamePad.lastDigitals & buttonValue) != 0; - } - else if (button & Game::GPAD_ANALOG_MASK) + for (auto localClientNum = 0; localClientNum < Game::MAX_GPAD_COUNT; ++localClientNum) { - const auto analogIndex = button & Game::GPAD_VALUE_MASK; - assert(analogIndex < std::extent_v); - - if (analogIndex < std::extent_v) + const auto& gamePad = gamePads[localClientNum]; + if (!gamePad.get_enabled()) { - down = gamePad.analogs[analogIndex] > 0.0f; - lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; + continue; } - } - return !down && lastDown; - } - - void Gamepad::GPad_UpdateSticksDown(const int localClientNum) - { - AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - - for (auto stickIndex = 0u; stickIndex < std::extent_v; stickIndex++) - { - for (auto dir = 0; dir < Game::GPAD_STICK_DIR_COUNT; dir++) + if (!Gamepad::gpad_rumble.get()) { - auto& gamePad = gamePads[localClientNum]; - gamePad.stickDownLast[stickIndex][dir] = gamePad.stickDown[stickIndex][dir]; - - auto threshold = gpad_stick_pressed.get(); - - if (gamePad.stickDownLast[stickIndex][dir]) - { - threshold -= gpad_stick_pressed_hysteresis.get(); - } - else - { - threshold += gpad_stick_pressed_hysteresis.get(); - } - - if (dir == Game::GPAD_STICK_POS) - { - gamePad.stickDown[stickIndex][dir] = gamePad.sticks[stickIndex] > threshold; - } - else - { - assert(dir == Game::GPAD_STICK_NEG); - gamePad.stickDown[stickIndex][dir] = gamePad.sticks[stickIndex] < -threshold; - } + gamePads[localClientNum].StopRumbles(); } - } - } - - void Gamepad::GPad_UpdateSticks(const int localClientNum, const XINPUT_GAMEPAD& state) - { - AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - - auto& gamePad = gamePads[localClientNum]; - - Game::vec2_t lVec, rVec; - GPad_ConvertStickToFloat(state.sThumbLX, state.sThumbLY, lVec[0], lVec[1]); - GPad_ConvertStickToFloat(state.sThumbRX, state.sThumbRY, rVec[0], rVec[1]); - - gamePad.lastSticks[0] = gamePad.sticks[0]; - gamePad.sticks[0] = lVec[0]; - gamePad.lastSticks[1] = gamePad.sticks[1]; - gamePad.sticks[1] = lVec[1]; - gamePad.lastSticks[2] = gamePad.sticks[2]; - gamePad.sticks[2] = rVec[0]; - gamePad.lastSticks[3] = gamePad.sticks[3]; - gamePad.sticks[3] = rVec[1]; - - GPad_UpdateSticksDown(localClientNum); - - if (gpad_debug.get()) - { - Logger::Debug("Left: X: {:f} Y: {:f}", lVec[0], lVec[1]); - Logger::Debug("Right: X: {:f} Y: {:f}", rVec[0], rVec[1]); - Logger::Debug("Down: {}:{} {}:{} {}:{} {}:{}", gamePad.stickDown[0][Game::GPAD_STICK_POS], gamePad.stickDown[0][Game::GPAD_STICK_NEG], - gamePad.stickDown[1][Game::GPAD_STICK_POS], gamePad.stickDown[1][Game::GPAD_STICK_NEG], - gamePad.stickDown[2][Game::GPAD_STICK_POS], gamePad.stickDown[2][Game::GPAD_STICK_NEG], - gamePad.stickDown[3][Game::GPAD_STICK_POS], gamePad.stickDown[3][Game::GPAD_STICK_NEG]); - } - } - void Gamepad::GPad_UpdateDigitals(const int localClientNum, const XINPUT_GAMEPAD& state) - { - AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - - auto& gamePad = gamePads[localClientNum]; - - gamePad.lastDigitals = gamePad.digitals; - gamePad.digitals = state.wButtons; - - const auto leftDeflect = gpad_button_lstick_deflect_max.get(); - if (std::fabs(gamePad.sticks[0]) > leftDeflect || std::fabs(gamePad.sticks[1]) > leftDeflect) - { - gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_LEFT_THUMB); - } - - const auto rightDeflect = gpad_button_rstick_deflect_max.get(); - if (std::fabs(gamePad.sticks[2]) > leftDeflect || std::fabs(gamePad.sticks[3]) > rightDeflect) - { - gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_RIGHT_THUMB); - } - - if (gpad_debug.get()) - { - Logger::Debug("Buttons: {:#x}", gamePad.digitals); - } - } - - void Gamepad::GPad_UpdateAnalogs(const int localClientNum, const XINPUT_GAMEPAD& state) - { - AssertIn(localClientNum, Game::MAX_GPAD_COUNT); - - auto& gamePad = gamePads[localClientNum]; - - const auto buttonDeadZone = gpad_button_deadzone.get(); - - gamePad.lastAnalogs[0] = gamePad.analogs[0]; - gamePad.analogs[0] = static_cast(state.bLeftTrigger) / static_cast(std::numeric_limits::max()); - if (gamePad.analogs[0] < buttonDeadZone) - { - gamePad.analogs[0] = 0.0f; - } - - gamePad.lastAnalogs[1] = gamePad.analogs[1]; - gamePad.analogs[1] = static_cast(state.bRightTrigger) / static_cast(std::numeric_limits::max()); - if (gamePad.analogs[1] < buttonDeadZone) - { - gamePad.analogs[1] = 0.0f; - } - - if (gpad_debug.get()) - { - Logger::Debug("Triggers: {:f} {:f}", gamePad.analogs[0], gamePad.analogs[1]); + gamePads[localClientNum].UpdateState(); } } - void Gamepad::GPad_UpdateAll() + void Gamepad::GPad_UpdateFeedbacks() { - GPad_RefreshAll(); - for (auto localClientNum = 0; localClientNum < Game::MAX_GPAD_COUNT; ++localClientNum) { const auto& gamePad = gamePads[localClientNum]; - if (!gamePad.enabled) + if (!gamePad.get_enabled()) { continue; } - XINPUT_STATE inputState; - if (XInputGetState(gamePad.portIndex, &inputState) != ERROR_SUCCESS) + if (!Gamepad::gpad_rumble.get()) { - continue; + gamePads[localClientNum].StopRumbles(); } - GPad_UpdateSticks(localClientNum, inputState.Gamepad); - GPad_UpdateDigitals(localClientNum, inputState.Gamepad); - GPad_UpdateAnalogs(localClientNum, inputState.Gamepad); + gamePads[localClientNum].PushUpdates(); } } @@ -1601,19 +1370,19 @@ namespace Components bool gpadPresent = false; for (auto localClientNum = 0; localClientNum < Game::MAX_GPAD_COUNT; ++localClientNum) { - const auto& gamePad = gamePads[localClientNum]; - if (!gamePad.enabled) + auto& gamePad = gamePads[localClientNum]; + if (!gamePad.get_enabled()) { continue; } gpadPresent = true; - const auto lx = GPad_GetStick(localClientNum, Game::GPAD_LX); - const auto ly = GPad_GetStick(localClientNum, Game::GPAD_LY); - const auto rx = GPad_GetStick(localClientNum, Game::GPAD_RX); - const auto ry = GPad_GetStick(localClientNum, Game::GPAD_RY); - const auto leftTrig = GPad_GetButton(localClientNum, Game::GPAD_L_TRIG); - const auto rightTrig = GPad_GetButton(localClientNum, Game::GPAD_R_TRIG); + const auto lx = gamePad.GetStick(Game::GPAD_LX); + const auto ly = gamePad.GetStick(Game::GPAD_LY); + const auto rx = gamePad.GetStick(Game::GPAD_RX); + const auto ry = gamePad.GetStick(Game::GPAD_RY); + const auto leftTrig = gamePad.GetButton(Game::GPAD_L_TRIG); + const auto rightTrig = gamePad.GetButton(Game::GPAD_R_TRIG); CL_GamepadEvent(localClientNum, Game::GPAD_PHYSAXIS_LSTICK_X, lx, time); CL_GamepadEvent(localClientNum, Game::GPAD_PHYSAXIS_LSTICK_Y, ly, time); @@ -1624,19 +1393,21 @@ namespace Components for (const auto& buttonMapping : buttonList) { - if (GPad_IsButtonPressed(localClientNum, buttonMapping.padButton)) + if (gamePad.IsButtonPressed(buttonMapping.padButton)) { CL_GamepadButtonEventForPort(localClientNum, buttonMapping.code, Game::GPAD_BUTTON_PRESSED, time); } - else if (GPad_ButtonRequiresUpdates(localClientNum, buttonMapping.padButton)) + else if (gamePad.ButtonRequiresUpdates(buttonMapping.padButton)) { CL_GamepadButtonEventForPort(localClientNum, buttonMapping.code, Game::GPAD_BUTTON_UPDATE, time); } - else if (GPad_IsButtonReleased(localClientNum, buttonMapping.padButton)) + else if (gamePad.IsButtonReleased(buttonMapping.padButton)) { - CL_GamepadButtonEventForPort(localClientNum, buttonMapping.code, Game::GPAD_BUTTON_RELEASED, time); + CL_GamepadButtonEventForPort(localClientNum, buttonMapping.code, Game::GPAD_BUTTON_RELEASED, time); } } + + UpdateForceFeedback(gamePad); } gpad_present.setRaw(gpadPresent); @@ -1696,9 +1467,9 @@ namespace Components push 0x60B26E retn - endMethod: + endMethod : push 0x60B298 - retn + retn } } @@ -1829,10 +1600,10 @@ namespace Components } } + void Gamepad::InitDvars() { gpad_enabled = Dvar::Register("gpad_enabled", false, Game::DVAR_ARCHIVE, "Game pad enabled"); - gpad_debug = Dvar::Register("gpad_debug", false, Game::DVAR_NONE, "Game pad debugging"); gpad_present = Dvar::Register("gpad_present", false, Game::DVAR_ROM, "Game pad present"); gpad_in_use = Dvar::Register("gpad_in_use", false, Game::DVAR_ROM, "A game pad is in use"); gpad_style = Dvar::Register("gpad_style", false, Game::DVAR_ARCHIVE, "Switch between Xbox and PS HUD"); @@ -1840,16 +1611,11 @@ namespace Components gpad_buttonConfig = Dvar::Register("gpad_buttonConfig", "", Game::DVAR_ARCHIVE, "Game pad button configuration"); gpad_menu_scroll_delay_first = Dvar::Register("gpad_menu_scroll_delay_first", 420, 0, 1000, Game::DVAR_ARCHIVE, "Menu scroll key-repeat delay, for the first repeat, in milliseconds"); gpad_menu_scroll_delay_rest = Dvar::Register("gpad_menu_scroll_delay_rest", 210, 0, 1000, Game::DVAR_ARCHIVE, - "Menu scroll key-repeat delay, for repeats after the first, in milliseconds"); + "Menu scroll key-repeat delay, for repeats after the first, in milliseconds"); gpad_rumble = Dvar::Register("gpad_rumble", true, Game::DVAR_ARCHIVE, "Enable game pad rumble"); - gpad_stick_pressed_hysteresis = Dvar::Register("gpad_stick_pressed_hysteresis", 0.1f, 0.0f, 1.0f, Game::DVAR_ARCHIVE, - "Game pad stick pressed no-change-zone around gpad_stick_pressed to prevent bouncing"); - gpad_stick_pressed = Dvar::Register("gpad_stick_pressed", 0.4f, 0.0, 1.0, Game::DVAR_ARCHIVE, "Game pad stick pressed threshhold"); - gpad_stick_deadzone_max = Dvar::Register("gpad_stick_deadzone_max", 0.01f, 0.0f, 1.0f, Game::DVAR_ARCHIVE, "Game pad maximum stick deadzone"); - gpad_stick_deadzone_min = Dvar::Register("gpad_stick_deadzone_min", 0.2f, 0.0f, 1.0f, Game::DVAR_ARCHIVE, "Game pad minimum stick deadzone"); - gpad_button_deadzone = Dvar::Register("gpad_button_deadzone", 0.13f, 0.0f, 1.0f, Game::DVAR_ARCHIVE, "Game pad button deadzone threshhold"); - gpad_button_lstick_deflect_max = Dvar::Register("gpad_button_lstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_ARCHIVE, "Game pad maximum pad stick pressed value"); - gpad_button_rstick_deflect_max = Dvar::Register("gpad_button_rstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_ARCHIVE, "Game pad maximum pad stick pressed value"); + + GamepadControls::Controller::InitializeDvars(); + gpad_use_hold_time = Dvar::Register("gpad_use_hold_time", 250, 0, std::numeric_limits::max(), Game::DVAR_ARCHIVE, "Time to hold the 'use' button on gamepads to activate use"); gpad_lockon_enabled = Dvar::Register("gpad_lockon_enabled", true, Game::DVAR_ARCHIVE, "Game pad lockon aim assist enabled"); gpad_slowdown_enabled = Dvar::Register("gpad_slowdown_enabled", true, Game::DVAR_ARCHIVE, "Game pad slowdown aim assist enabled"); @@ -1904,14 +1670,14 @@ namespace Components return command; } - int Gamepad::Key_GetCommandAssignmentInternal([[maybe_unused]] int localClientNum, const char* cmd, int (*keys)[2]) + int Gamepad::Key_GetCommandAssignmentInternal(int localClientNum, const char* cmd, int(*keys)[2]) { auto keyCount = 0; (*keys)[0] = -1; (*keys)[1] = -1; - if (gamePads[0].inUse) + if (gamePads[localClientNum].inUse) { const auto gamePadCmd = GetGamePadCommand(cmd); for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) @@ -1921,7 +1687,7 @@ namespace Components continue; } - if (Game::playerKeys[0].keys[keyNum].binding && std::strcmp(Game::playerKeys[0].keys[keyNum].binding, gamePadCmd) == 0) + if (Game::playerKeys[localClientNum].keys[keyNum].binding && std::strcmp(Game::playerKeys[0].keys[keyNum].binding, gamePadCmd) == 0) { (*keys)[keyCount++] = keyNum; @@ -1941,7 +1707,7 @@ namespace Components continue; } - if (Game::playerKeys[0].keys[keyNum].binding && std::strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) + if (Game::playerKeys[localClientNum].keys[keyNum].binding && std::strcmp(Game::playerKeys[localClientNum].keys[keyNum].binding, cmd) == 0) { (*keys)[keyCount++] = keyNum; @@ -1962,9 +1728,9 @@ namespace Components { push eax pushad - - push [esp + 0x20 + 0x4 + 0x8] // keyNums - push [esp + 0x20 + 0x4 + 0x8] // command + + push[esp + 0x20 + 0x4 + 0x8] // keyNums + push[esp + 0x20 + 0x4 + 0x8] // command push eax // localClientNum call Key_GetCommandAssignmentInternal add esp, 0xC @@ -1990,7 +1756,7 @@ namespace Components void Gamepad::CL_KeyEvent_Hk(const int localClientNum, const int key, const int down, const unsigned time) { // A keyboard key has been pressed. Mark controller as unused. - gamePads[0].inUse = false; + gamePads[localClientNum].inUse = false; gpad_in_use.setRaw(false); // Call original function @@ -2042,7 +1808,7 @@ namespace Components pushad call GetLocalizedKeyNameMap - mov [esp + 0x20], eax + mov[esp + 0x20], eax popad pop eax @@ -2057,17 +1823,17 @@ namespace Components { std::memcpy(combinedKeyNames, Game::keyNames, sizeof(Game::keyname_t) * Game::KEY_NAME_COUNT); std::memcpy(&combinedKeyNames[Game::KEY_NAME_COUNT], extendedKeyNames, sizeof(Game::keyname_t) * std::extent_v); - combinedKeyNames[std::extent_v - 1] = {nullptr, 0}; + combinedKeyNames[std::extent_v -1] = { nullptr, 0 }; std::memcpy(combinedLocalizedKeyNamesXenon, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); std::memcpy(&combinedLocalizedKeyNamesXenon[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesXenon, sizeof(Game::keyname_t) * std::extent_v); - combinedLocalizedKeyNamesXenon[std::extent_v - 1] = {nullptr, 0}; + combinedLocalizedKeyNamesXenon[std::extent_v -1] = { nullptr, 0 }; std::memcpy(combinedLocalizedKeyNamesPs3, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); std::memcpy(&combinedLocalizedKeyNamesPs3[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesPs3, sizeof(Game::keyname_t) * std::extent_v); - combinedLocalizedKeyNamesPs3[std::extent_v - 1] = {nullptr, 0}; + combinedLocalizedKeyNamesPs3[std::extent_v -1] = { nullptr, 0 }; Utils::Hook::Set(0x4A780A, combinedKeyNames); Utils::Hook::Set(0x4A7810, combinedKeyNames); @@ -2075,6 +1841,160 @@ namespace Components Utils::Hook(0x435C97, GetLocalizedKeyName_Stub, HOOK_CALL).install()->quick(); } + void Gamepad::GetTriggerRoles(TriggerRole& leftTrigger, TriggerRole& rightTrigger) + { + constexpr Game::keyNum_t triggers[] = { + Game::K_BUTTON_LTRIG, + Game::K_BUTTON_RTRIG + }; + + static TriggerRole* buttonBits[ARRAYSIZE(triggers)]; + + buttonBits[0] = &leftTrigger; + buttonBits[1] = &rightTrigger; + + for (size_t i = 0; i < ARRAYSIZE(triggers); i++) + { + if (Game::playerKeys[0].keys[triggers[i]].binding) + { + const auto& binding = Game::playerKeys[0].keys[triggers[i]].binding; + + if (binding == "+attack"s) + { + *buttonBits[i] = TriggerRole::Shooting; + } + else if (binding == "+toggleads_throw"s || binding == "+speed_throw"s) + { + *buttonBits[i] = TriggerRole::Aiming; + } + else if (binding == "+frag"s ) + { + *buttonBits[i] = TriggerRole::PrimaryOffHand; + } + else if (binding == "+smoke"s) + { + *buttonBits[i] = TriggerRole::SecondaryOffHand; + } + else + { + *buttonBits[i] = TriggerRole::None; + } + } + } + } + + void Gamepad::GetTriggerFeedbackForShooting(const Game::playerState_s* playerState, GamepadControls::GamepadAPI::TriggerFeedback& feedback) + { + feedback.resistance = GamepadControls::GamepadAPI::TriggerFeedback::NoResistance; + + const auto viewModelWeaponIndex = Game::BG_GetViewModelWeaponIndex(playerState); + + if (viewModelWeaponIndex) + { + const auto weap = Game::BG_GetWeaponDef(viewModelWeaponIndex); + + switch (weap->weapClass) + { + case Game::WEAPCLASS_MG: + case Game::WEAPCLASS_RIFLE: + case Game::WEAPCLASS_TURRET: + feedback.resistance = GamepadControls::GamepadAPI::TriggerFeedback::ContinuousHeavyResistance; + break; + + + case Game::WEAPCLASS_SMG: + feedback.resistance = GamepadControls::GamepadAPI::TriggerFeedback::ContinuousSlightResistance; + break; + + case Game::WEAPCLASS_PISTOL: + feedback.resistance = GamepadControls::GamepadAPI::TriggerFeedback::SectionSlightResistance; + break; + + case Game::WEAPCLASS_SPREAD: + case Game::WEAPCLASS_SNIPER: + case Game::WEAPCLASS_ROCKETLAUNCHER: + feedback.resistance = GamepadControls::GamepadAPI::TriggerFeedback::SectionHeavyResistance; + break; + } + } + } + + void Gamepad::GetTriggerFeedbackForEquipment(const Game::playerState_s* playerState, bool primary, GamepadControls::GamepadAPI::TriggerFeedback& feedback) + { + feedback.resistance = GamepadControls::GamepadAPI::TriggerFeedback::NoResistance; + + feedback.resistance = GamepadControls::GamepadAPI::TriggerFeedback::SectionSlightResistance; + if (Game::cgArray->snap) + { + if ((primary ? playerState->weapCommon.offhandPrimary : playerState->weapCommon.offhandSecondary) != Game::OFFHAND_CLASS_NONE) + { + feedback.resistance = GamepadControls::GamepadAPI::TriggerFeedback::SectionSlightResistance; + } + } + } + + void Gamepad::UpdateForceFeedback(GamepadControls::Controller& api) + { + constexpr uint8_t LEFT = 0; + constexpr uint8_t RIGHT = 1; + + GamepadControls::GamepadAPI::TriggerFeedback triggerFeedbacks[2]{}; + + TriggerRole leftRole; + TriggerRole rightRole; + + GetTriggerRoles(leftRole, rightRole); + if (Game::cgArray->snap) + { + const auto playerState = &Game::cgArray->snap->ps; + if (playerState) + { + for (size_t i = 0; i < 2; i++) + { + const TriggerRole& role = i == LEFT ? leftRole : rightRole; + + if (role == TriggerRole::Shooting) + { + GetTriggerFeedbackForShooting(playerState, triggerFeedbacks[i]); + + const auto viewModelWeaponIndex = Game::BG_GetViewModelWeaponIndex(playerState); + + if (viewModelWeaponIndex) + { + const auto* equippedWeapon = Game::BG_GetEquippedWeaponState(playerState, viewModelWeaponIndex); + if (equippedWeapon) + { + const auto& otherRole = (1-i) == LEFT ? leftRole : rightRole; + if (equippedWeapon->dualWielding && otherRole == role) + { + // Both triggers the same + triggerFeedbacks[1 - i] = triggerFeedbacks[i]; + break; + } + } + } + } + else if (role == TriggerRole::Aiming) + { + // No resistance + triggerFeedbacks[i].resistance = GamepadControls::GamepadAPI::TriggerFeedback::NoResistance; + } + else if (role == TriggerRole::PrimaryOffHand) + { + GetTriggerFeedbackForEquipment(playerState, true, triggerFeedbacks[i]); + } + else if (role == TriggerRole::SecondaryOffHand) + { + GetTriggerFeedbackForEquipment(playerState, true, triggerFeedbacks[i]); + } + } + } + } + + + api.SetForceFeedback(triggerFeedbacks[LEFT], triggerFeedbacks[RIGHT]); + } + Gamepad::Gamepad() { if (ZoneBuilder::IsEnabled()) diff --git a/src/Components/Modules/Gamepad.hpp b/src/Components/Modules/Gamepad.hpp index f0f14145..93c0a957 100644 --- a/src/Components/Modules/Gamepad.hpp +++ b/src/Components/Modules/Gamepad.hpp @@ -1,5 +1,7 @@ #pragma once +#include "Gamepad/Controller.hpp" + namespace Components { class Gamepad : public Component @@ -10,26 +12,6 @@ namespace Components Game::keyNum_t pcKey; }; - struct GamePad - { - bool enabled; - bool inUse; - int portIndex; - unsigned short digitals; - unsigned short lastDigitals; - float analogs[2]; - float lastAnalogs[2]; - float sticks[4]; - float lastSticks[4]; - bool stickDown[4][Game::GPAD_STICK_DIR_COUNT]; - bool stickDownLast[4][Game::GPAD_STICK_DIR_COUNT]; - float lowRumble; - float highRumble; - - XINPUT_VIBRATION rumble; - XINPUT_CAPABILITIES caps; - }; - struct GamePadGlobals { Game::GpadAxesGlob axes; @@ -39,15 +21,29 @@ namespace Components }; public: - static const int RUMBLE_CONFIGSTRINGS_COUNT = 31; + static const int RUMBLE_CONFIGSTRINGS_COUNT = 32; Gamepad(); static void OnMouseMove(int x, int y, int dx, int dy); + static void GPad_SetLowRumble(int gamePadIndex, double rumble); + static void GPad_SetHighRumble(int gamePadIndex, double rumble); + static void GPad_StopRumbles(int gamePadIndex); + static void GPad_UpdateFeedbacks(); + static Dvar::Var sv_allowAimAssist; private: + enum TriggerRole + { + None, + Shooting, + Aiming, + PrimaryOffHand, + SecondaryOffHand + }; + static Game::ButtonToCodeMap_t buttonList[]; static Game::StickToCodeMap_t analogStickList[4]; static Game::GamePadStick stickForAxis[]; @@ -64,13 +60,12 @@ namespace Components static Game::keyname_t combinedLocalizedKeyNamesPs3[]; static ControllerMenuKeyMapping controllerMenuKeyMappings[]; - static GamePad gamePads[Game::MAX_GPAD_COUNT]; + static GamepadControls::Controller gamePads[Game::MAX_GPAD_COUNT]; static GamePadGlobals gamePadGlobals[Game::MAX_GPAD_COUNT]; static int gamePadBindingsModifiedFlags; static Dvar::Var gpad_enabled; - static Dvar::Var gpad_debug; static Dvar::Var gpad_present; static Dvar::Var gpad_in_use; static Dvar::Var gpad_style; @@ -79,13 +74,6 @@ namespace Components static Dvar::Var gpad_menu_scroll_delay_first; static Dvar::Var gpad_menu_scroll_delay_rest; static Dvar::Var gpad_rumble; - static Dvar::Var gpad_stick_pressed_hysteresis; - static Dvar::Var gpad_stick_pressed; - static Dvar::Var gpad_stick_deadzone_max; - static Dvar::Var gpad_stick_deadzone_min; - static Dvar::Var gpad_button_deadzone; - static Dvar::Var gpad_button_rstick_deflect_max; - static Dvar::Var gpad_button_lstick_deflect_max; static Dvar::Var gpad_use_hold_time; static Dvar::Var gpad_lockon_enabled; static Dvar::Var gpad_slowdown_enabled; @@ -114,6 +102,8 @@ namespace Components static Dvar::Var aim_lockon_pitch_strength; static Dvar::Var aim_lockon_strength; + static DS5W::DeviceContext dualSenseContext; + static void MSG_WriteDeltaUsercmdKeyStub(); static void ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to); @@ -150,26 +140,14 @@ namespace Components static void Player_UseEntity_Stub(); static bool Key_IsValidGamePadChar(int key); - static void CL_GamepadResetMenuScrollTime(int localClientNum, int key, bool down, unsigned int time); + static void CL_GamepadResetMenuScrollTime(const int localClientNum, const int key, const bool down, const unsigned time); static bool Scoreboard_HandleInput(int localClientNum, int key); - static bool CL_CheckForIgnoreDueToRepeat(int localClientNum, int key, int repeatCount, unsigned int time); + static bool CL_CheckForIgnoreDueToRepeat(const int localClientNum, const int key, const int repeatCount, const unsigned time); static void UI_GamepadKeyEvent(int localClientNum, int key, bool down); static void CL_GamepadGenerateAPad(int localClientNum, Game::GamepadPhysicalAxis physicalAxis, unsigned time); static void CL_GamepadEvent(int localClientNum, Game::GamepadPhysicalAxis physicalAxis, float value, unsigned time); static void CL_GamepadButtonEvent(int localClientNum, int key, Game::GamePadButtonEvent buttonEvent, unsigned time); - static void CL_GamepadButtonEventForPort(int localClientNum, int key, Game::GamePadButtonEvent buttonEvent, unsigned int time); - - static void GPad_ConvertStickToFloat(short x, short y, float& outX, float& outY); - static float GPad_GetStick(int localClientNum, Game::GamePadStick stick); - static float GPad_GetButton(int localClientNum, Game::GamePadButton button); - static bool GPad_IsButtonPressed(int localClientNum, Game::GamePadButton button); - static bool GPad_ButtonRequiresUpdates(int localClientNum, Game::GamePadButton button); - static bool GPad_IsButtonReleased(int localClientNum, Game::GamePadButton button); - - static void GPad_UpdateSticksDown(int localClientNum); - static void GPad_UpdateSticks(int localClientNum, const XINPUT_GAMEPAD& state); - static void GPad_UpdateDigitals(int localClientNum, const XINPUT_GAMEPAD& state); - static void GPad_UpdateAnalogs(int localClientNum, const XINPUT_GAMEPAD& state); + static void CL_GamepadButtonEventForPort(const int localClientNum, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time); static bool GPad_Check(int localClientNum, int portIndex); static void GPad_RefreshAll(); @@ -198,13 +176,19 @@ namespace Components static int Key_GetCommandAssignmentInternal(int localClientNum, const char* cmd, int (*keys)[2]); static void Key_GetCommandAssignmentInternal_Stub(); static void Key_SetBinding_Hk(int localClientNum, int keyNum, const char* binding); + static void CL_KeyEvent_Hk(const int localClientNum, const int key, const int down, const unsigned time); static bool IsGamePadInUse(); - static void CL_KeyEvent_Hk(int localClientNum, int key, int down, unsigned int time); static int CL_MouseEvent_Hk(int x, int y, int dx, int dy); static bool UI_RefreshViewport_Hk(); static Game::keyname_t* GetLocalizedKeyNameMap(); static void GetLocalizedKeyName_Stub(); static void CreateKeyNameMap(); + + static void GetTriggerRoles(TriggerRole& leftTrigger, TriggerRole& rightTrigger); + static void GetTriggerFeedbackForShooting(const Game::playerState_s* playerState, GamepadControls::GamepadAPI::TriggerFeedback& feedback); + static void GetTriggerFeedbackForEquipment(const Game::playerState_s* playerState, bool primary, GamepadControls::GamepadAPI::TriggerFeedback& feedback); + + static void UpdateForceFeedback(GamepadControls::Controller& api); }; } diff --git a/src/Components/Modules/Gamepad/Controller.cpp b/src/Components/Modules/Gamepad/Controller.cpp new file mode 100644 index 00000000..fe6ac771 --- /dev/null +++ b/src/Components/Modules/Gamepad/Controller.cpp @@ -0,0 +1,376 @@ +#include "STDInclude.hpp" +#include "Controller.hpp" + +namespace Components::GamepadControls +{ + Dvar::Var Controller::gpad_debug; + + Dvar::Var Controller::gpad_allow_force_feedback; + Dvar::Var Controller::gpad_force_xinput_only; + + Dvar::Var Controller::gpad_stick_pressed_hysteresis; + Dvar::Var Controller::gpad_stick_pressed; + Dvar::Var Controller::gpad_stick_deadzone_max; + Dvar::Var Controller::gpad_stick_deadzone_min; + Dvar::Var Controller::gpad_button_deadzone; + Dvar::Var Controller::gpad_button_rstick_deflect_max; + Dvar::Var Controller::gpad_button_lstick_deflect_max; + + bool Controller::PlugIn(uint8_t index) + { + enabled = false; + + if (gpad_force_xinput_only.get() == false) + { + // Dualsense first + if (!enabled) + { + api.reset(new GamepadControls::DualSenseGamePadAPI()); + + if (api->PlugIn(index)) + { + portIndex = index; + enabled = true; + } + } + } + + // Xinput + if (!enabled) + { + api.reset(new GamepadControls::XInputGamePadAPI()); + + if (api->PlugIn(index)) + { + portIndex = index; + enabled = true; + } + } + + if (!enabled) + { + // Clear memory + api.reset(nullptr); + } + + return enabled; + } + + void Controller::SetLowRumble(double rumble) + { + lowRumble = static_cast(rumble); + } + + void Controller::SetHighRumble(double rumble) + { + highRumble = static_cast(rumble); + } + + void Controller::SetForceFeedback(const GamepadAPI::TriggerFeedback& left, const GamepadAPI::TriggerFeedback& right) + { + if (gpad_allow_force_feedback.get()) + { + leftForceFeedback = left; + rightForceFeedback = right; + } + else + { + leftForceFeedback.resistance = GamepadAPI::TriggerFeedback::NoResistance; + rightForceFeedback.resistance = GamepadAPI::TriggerFeedback::NoResistance; + } + } + + void Controller::StopRumbles() + { + lowRumble = 0.f; + highRumble = 0.f; + } + + void Controller::UpdateState() + { + // We read + if (api) + { + if (api->Fetch()) + { + UpdateSticks(); + UpdateAnalogs(); + UpdateDigitals(); + } + else + { + enabled = false; + } + } + else + { + enabled = false; + } + } + + void Controller::PushUpdates() + { + // We write + if (api) + { + api->UpdateLights(RGB(50, 237, 40)); + api->UpdateForceFeedback(leftForceFeedback, rightForceFeedback); + api->UpdateRumbles(lowRumble, highRumble); + api->Send(); + } + } + + + void Controller::UpdateDigitals() + { + lastDigitals = digitals; + + if (api) + { + api->ReadDigitals(digitals); + } + + const auto leftDeflect = gpad_button_lstick_deflect_max.get(); + if (std::fabs(sticks[0]) > leftDeflect || std::fabs(sticks[1]) > leftDeflect) + { + digitals &= ~static_cast(XINPUT_GAMEPAD_LEFT_THUMB); + } + + const auto rightDeflect = gpad_button_rstick_deflect_max.get(); + if (std::fabs(sticks[2]) > leftDeflect || std::fabs(sticks[3]) > rightDeflect) + { + digitals &= ~static_cast(XINPUT_GAMEPAD_RIGHT_THUMB); + } + + if (gpad_debug.get()) + { + Logger::Debug("Buttons: {:#x}", digitals); + } + } + + void Controller::UpdateAnalogs() + { + const auto buttonDeadZone = gpad_button_deadzone.get(); + + lastAnalogs[0] = analogs[0]; + lastAnalogs[1] = analogs[1]; + + if (api) + { + api->ReadAnalogs(analogs[0], analogs[1]); + } + + if (analogs[0] < buttonDeadZone) + { + analogs[0] = 0.0f; + } + + if (analogs[1] < buttonDeadZone) + { + analogs[1] = 0.0f; + } + + if (gpad_debug.get()) + { + Logger::Debug("Triggers: {:f} {:f}", analogs[0], analogs[1]); + } + } + + void Controller::UpdateSticks() + { + + lastSticks[0] = sticks[0]; + lastSticks[1] = sticks[1]; + lastSticks[2] = sticks[2]; + lastSticks[3] = sticks[3]; + + Game::vec2_t lVec, rVec; + + if (api) + { + api->ReadSticks(lVec, rVec); + } + + ApplyDeadzone(lVec); + ApplyDeadzone(rVec); + + sticks[0] = lVec[0]; + sticks[1] = lVec[1]; + sticks[2] = rVec[0]; + sticks[3] = rVec[1]; + + UpdateSticksDown(); + + if (gpad_debug.get()) + { + Logger::Debug("Left: X: {:f} Y: {:f}", lVec[0], lVec[1]); + Logger::Debug("Right: X: {:f} Y: {:f}", rVec[0], rVec[1]); + Logger::Debug("Down: {}:{} {}:{} {}:{} {}:{}", stickDown[0][Game::GPAD_STICK_POS], stickDown[0][Game::GPAD_STICK_NEG], + stickDown[1][Game::GPAD_STICK_POS], stickDown[1][Game::GPAD_STICK_NEG], + stickDown[2][Game::GPAD_STICK_POS], stickDown[2][Game::GPAD_STICK_NEG], + stickDown[3][Game::GPAD_STICK_POS], stickDown[3][Game::GPAD_STICK_NEG]); + } + } + + void Controller::ApplyDeadzone(Game::vec2_t& stick) + { + const auto deadZoneTotal = gpad_stick_deadzone_min.get() + gpad_stick_deadzone_max.get(); + auto len = Game::Vec2Normalize(stick); + + if (gpad_stick_deadzone_min.get() <= len) + { + if (1.0f - gpad_stick_deadzone_max.get() >= len) + { + len = (len - gpad_stick_deadzone_min.get()) / (1.0f - deadZoneTotal); + } + else + { + len = 1.0f; + } + } + else + { + len = 0.0f; + } + + stick[0] *= len; + stick[1] *= len; + } + + float Controller::GetStick(const Game::GamePadStick stick) + { + assert(stick & Game::GPAD_STICK_MASK); + return sticks[stick]; + } + + float Controller::GetButton(Game::GamePadButton button) + { + float value = 0.0f; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + value = buttonValue & digitals ? 1.0f : 0.0f; + } + else if (button & Game::GPAD_ANALOG_MASK) + { + const auto analogIndex = button & Game::GPAD_VALUE_MASK; + if (analogIndex < std::extent_v) + { + value = analogs[analogIndex]; + } + } + + return value; + } + + + bool Controller::ButtonRequiresUpdates(Game::GamePadButton button) + { + return (button & Game::GPAD_ANALOG_MASK || button & Game::GPAD_DPAD_MASK) && GetButton(button) > 0.0f; + } + + bool Controller::IsButtonReleased(Game::GamePadButton button) + { + bool down = false; + bool lastDown = false; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + + down = (digitals & buttonValue) != 0; + lastDown = (lastDigitals & buttonValue) != 0; + } + else if (button & Game::GPAD_ANALOG_MASK) + { + const auto analogIndex = button & Game::GPAD_VALUE_MASK; + assert(analogIndex < std::extent_v); + + if (analogIndex < std::extent_v) + { + down = analogs[analogIndex] > 0.0f; + lastDown = lastAnalogs[analogIndex] > 0.0f; + } + } + + return !down && lastDown; + } + + + bool Controller::IsButtonPressed(Game::GamePadButton button) + { + bool down = false; + bool lastDown = false; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + down = (buttonValue & digitals) != 0; + lastDown = (buttonValue & lastDigitals) != 0; + } + else if (button & Game::GPAD_ANALOG_MASK) + { + const auto analogIndex = button & Game::GPAD_VALUE_MASK; + assert(analogIndex < std::extent_v); + + if (analogIndex < std::extent_v) + { + down = analogs[analogIndex] > 0.0f; + lastDown = lastAnalogs[analogIndex] > 0.0f; + } + } + + return down && !lastDown; + } + + void Controller::UpdateSticksDown() + { + for (auto stickIndex = 0u; stickIndex < std::extent_v; stickIndex++) + { + for (auto dir = 0; dir < Game::GPAD_STICK_DIR_COUNT; dir++) + { + stickDownLast[stickIndex][dir] = stickDown[stickIndex][dir]; + + auto threshold = gpad_stick_pressed.get(); + + if (stickDownLast[stickIndex][dir]) + { + threshold -= gpad_stick_pressed_hysteresis.get(); + } + else + { + threshold += gpad_stick_pressed_hysteresis.get(); + } + + if (dir == Game::GPAD_STICK_POS) + { + stickDown[stickIndex][dir] = sticks[stickIndex] > threshold; + } + else + { + assert(dir == Game::GPAD_STICK_NEG); + stickDown[stickIndex][dir] = sticks[stickIndex] < -threshold; + } + } + } + } + + void Controller::InitializeDvars() + { + gpad_debug = Dvar::Register("gpad_debug", false, Game::DVAR_NONE, "Game pad debugging"); + + gpad_allow_force_feedback = Dvar::Register("gpad_allow_force_feedback", true, Game::DVAR_NONE, "Allow force feedback if the game pad supports it"); + + gpad_force_xinput_only = Dvar::Register("gpad_force_xinput_only", false, Game::DVAR_ARCHIVE, "Only listen for XInput controllers and ignore the rest"); + + gpad_stick_pressed_hysteresis = Dvar::Register("gpad_stick_pressed_hysteresis", 0.1f, 0.0f, 1.0f, Game::DVAR_ARCHIVE, + "Game pad stick pressed no-change-zone around gpad_stick_pressed to prevent bouncing"); + gpad_stick_pressed = Dvar::Register("gpad_stick_pressed", 0.4f, 0.0, 1.0, Game::DVAR_ARCHIVE, "Game pad stick pressed threshhold"); + gpad_stick_deadzone_max = Dvar::Register("gpad_stick_deadzone_max", 0.01f, 0.0f, 1.0f, Game::DVAR_ARCHIVE, "Game pad maximum stick deadzone"); + gpad_stick_deadzone_min = Dvar::Register("gpad_stick_deadzone_min", 0.2f, 0.0f, 1.0f, Game::DVAR_ARCHIVE, "Game pad minimum stick deadzone"); + gpad_button_deadzone = Dvar::Register("gpad_button_deadzone", 0.13f, 0.0f, 1.0f, Game::DVAR_ARCHIVE, "Game pad button deadzone threshhold"); + gpad_button_lstick_deflect_max = Dvar::Register("gpad_button_lstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_ARCHIVE, "Game pad maximum pad stick pressed value"); + gpad_button_rstick_deflect_max = Dvar::Register("gpad_button_rstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_ARCHIVE, "Game pad maximum pad stick pressed value"); + } +} diff --git a/src/Components/Modules/Gamepad/Controller.hpp b/src/Components/Modules/Gamepad/Controller.hpp new file mode 100644 index 00000000..15853841 --- /dev/null +++ b/src/Components/Modules/Gamepad/Controller.hpp @@ -0,0 +1,98 @@ +#include "DualSenseGamepad.hpp" +#include "XInputGamepad.hpp" + +#define PUBLIC_GET_PRIVATE_SET(type, name) \ + private:\ + type name;\ + public:\ + type get_##name () const { return name; } + +// type (*name(void))dimensions {\ won't work :( +#define PUBLIC_GET_PRIVATE_SET_ARRAY(type, name, dimensions) \ + private:\ + type name dimensions;\ + public:\ + const type* get_##name() {\ + return name; \ +} + +namespace Components::GamepadControls +{ + class Controller + { + + public: + static void InitializeDvars(); + + bool PlugIn(uint8_t); + + void SetLowRumble(double rumble); + + void SetHighRumble(double rumble); + + void SetForceFeedback(const GamepadAPI::TriggerFeedback& left, const GamepadAPI::TriggerFeedback& right); + + void StopRumbles(); + + void UpdateState(); + void PushUpdates(); + + float GetStick(const Game::GamePadStick stick); + float GetButton(Game::GamePadButton button); + bool ButtonRequiresUpdates(Game::GamePadButton button); + bool IsButtonReleased(Game::GamePadButton button); + bool IsButtonPressed(Game::GamePadButton button); + + static Dvar::Var gpad_debug; + + bool inUse; + + private: + + static Dvar::Var gpad_stick_pressed_hysteresis; + static Dvar::Var gpad_stick_pressed; + static Dvar::Var gpad_stick_deadzone_max; + static Dvar::Var gpad_stick_deadzone_min; + static Dvar::Var gpad_button_deadzone; + static Dvar::Var gpad_button_rstick_deflect_max; + static Dvar::Var gpad_button_lstick_deflect_max; + static Dvar::Var gpad_allow_force_feedback; + static Dvar::Var gpad_force_xinput_only; + + void UpdateDigitals(); + void UpdateAnalogs(); + void UpdateSticks(); + void ApplyDeadzone(Game::vec2_t& stick); + void UpdateSticksDown(); + + bool stickDown[4][Game::GPAD_STICK_DIR_COUNT]; + bool stickDownLast[4][Game::GPAD_STICK_DIR_COUNT]; + + public: + bool get_stickDown(int stickIndex, Game::GamePadStickDir dir) { return stickDown[stickIndex][dir];} + bool get_stickDownLast(int stickIndex, Game::GamePadStickDir dir) { return stickDownLast[stickIndex][dir];} + + PUBLIC_GET_PRIVATE_SET(GamepadAPI::TriggerFeedback, leftForceFeedback); + PUBLIC_GET_PRIVATE_SET(GamepadAPI::TriggerFeedback, rightForceFeedback); + + PUBLIC_GET_PRIVATE_SET(bool, enabled); + PUBLIC_GET_PRIVATE_SET(int, portIndex); + PUBLIC_GET_PRIVATE_SET(unsigned short, digitals); + PUBLIC_GET_PRIVATE_SET(unsigned short, lastDigitals); + PUBLIC_GET_PRIVATE_SET(float, lowRumble); + PUBLIC_GET_PRIVATE_SET(float, highRumble); + + PUBLIC_GET_PRIVATE_SET_ARRAY(float, analogs, [2]); + PUBLIC_GET_PRIVATE_SET_ARRAY(float, lastAnalogs, [2]); + PUBLIC_GET_PRIVATE_SET_ARRAY(float, sticks, [4]); + PUBLIC_GET_PRIVATE_SET_ARRAY(float, lastSticks, [4]); + + + private: + std::unique_ptr api; + }; +} + + +#undef PUBLIC_GET_PRIVATE_SET +#undef PUBLIC_GET_PRIVATE_SET_ARRAY diff --git a/src/Components/Modules/Gamepad/DualSenseAPI.hpp b/src/Components/Modules/Gamepad/DualSenseAPI.hpp new file mode 100644 index 00000000..7c6213fd --- /dev/null +++ b/src/Components/Modules/Gamepad/DualSenseAPI.hpp @@ -0,0 +1,7 @@ +#pragma once + +#define DS5W_BUILD_LIB + +#include +#include +#include diff --git a/src/Components/Modules/Gamepad/DualSenseGamepad.cpp b/src/Components/Modules/Gamepad/DualSenseGamepad.cpp new file mode 100644 index 00000000..01cf13b5 --- /dev/null +++ b/src/Components/Modules/Gamepad/DualSenseGamepad.cpp @@ -0,0 +1,257 @@ +#include +#include "DualSenseGamepad.hpp" + + +bool Components::GamepadControls::DualSenseGamePadAPI::PlugIn(uint8_t index) +{ + dirty = true; + ZeroMemory(&context, sizeof(DS5W::DeviceContext)); + ZeroMemory(&outState, sizeof(DS5W::DS5OutputState)); + + DS5W::DeviceEnumInfo infos[Game::MAX_GPAD_COUNT]; + unsigned int controllersCount = 0; + DS5W_ReturnValue rv = DS5W::enumDevices(infos, Game::MAX_GPAD_COUNT, &controllersCount); + + this->portIndex = index; + + if (DS5W_SUCCESS(rv) && controllersCount > 0) + { + if (DS5W_SUCCESS(DS5W::initDeviceContext(&infos[index], &context))) { + // Ok! + return true; + } + else + { + Components::Logger::Error(Game::ERR_SCRIPT, "A DualSense™ controller was detected, but could not be connected to.\n"); + } + } + + return false; +} + +void Components::GamepadControls::DualSenseGamePadAPI::UpdateRumbles(float left, float right) +{ + if (EnsureConnected()) + { + static uint8_t leftRumble; + static uint8_t rightRumble; + + if (ToSCE(left, leftRumble) && ToSCE(right, rightRumble)) + { + // Ready + if (leftRumble != outState.leftRumble || rightRumble != outState.rightRumble) + { + outState.leftRumble = leftRumble; + outState.rightRumble = rightRumble; + dirty = true; + } + } + } +} + +bool Components::GamepadControls::DualSenseGamePadAPI::EnsureConnected() +{ + if (context._internal.connected) + { + return true; + } + else + { + ZeroMemory(&inputState, sizeof(DS5W::DS5InputState)); + if (PlugIn(this->portIndex)) + { + return true; // We reconnected? + } + else + { + return false; + } + } +} + +bool Components::GamepadControls::DualSenseGamePadAPI::ToSCE(const float& amount01, unsigned char &amount) +{ + amount = static_cast(std::clamp(amount01, 0.F, 1.F) * std::numeric_limits().max()); + + return true; +} + +bool Components::GamepadControls::DualSenseGamePadAPI::ToSCE(const TriggerFeedback& triggerFeedback, DS5W::TriggerEffect* sce) +{ + if (sce) + { + switch (triggerFeedback.resistance) + { + default: + case TriggerFeedback::Resistance::NoResistance: + sce->effectType = DS5W::TriggerEffectType::NoResitance; + return true; + + case TriggerFeedback::Resistance::ContinuousSlightResistance: + sce->effectType = DS5W::TriggerEffectType::ContinuousResitance; + sce->Continuous.force = 70; + sce->Continuous.startPosition = 20; + return true; + + case TriggerFeedback::Resistance::ContinuousHeavyResistance: + sce->effectType = DS5W::TriggerEffectType::ContinuousResitance; + sce->Continuous.force = 170; + sce->Continuous.startPosition = 20; + return true; + + case TriggerFeedback::Resistance::SectionSlightResistance: + sce->effectType = DS5W::TriggerEffectType::SectionResitance; + sce->Section.startPosition = 0x00; + sce->Section.endPosition = 0x30; + return true; + + case TriggerFeedback::Resistance::SectionHeavyResistance: + sce->effectType = DS5W::TriggerEffectType::SectionResitance; + sce->Section.startPosition = 0x00; + sce->Section.endPosition = 0x60; + return true; + } + } + + return false; +} + +#define EQUALS(member) a.##member == b.##member + +bool Components::GamepadControls::DualSenseGamePadAPI::AreEqual(const DS5W::Color& a, const DS5W::Color& b) +{ + return EQUALS(r) && EQUALS(g) && EQUALS(b); +} + +bool Components::GamepadControls::DualSenseGamePadAPI::AreEqual(const DS5W::TriggerEffect& a, const DS5W::TriggerEffect& b) +{ + return + EQUALS(effectType) && + EQUALS(Continuous.startPosition) && + EQUALS(Continuous.force) && + EQUALS(Section.startPosition) && + EQUALS(Section.endPosition) && + EQUALS(EffectEx.startPosition) && + EQUALS(EffectEx.keepEffect) && + EQUALS(EffectEx.middleForce) && + EQUALS(EffectEx.endForce) && + EQUALS(EffectEx.frequency); +} + +#undef EQUALS + + +void Components::GamepadControls::DualSenseGamePadAPI::UpdateForceFeedback(const TriggerFeedback& left, const TriggerFeedback& right) +{ + if (EnsureConnected()) + { + static DS5W::TriggerEffect leftTE; + static DS5W::TriggerEffect rightTE; + + if (ToSCE(left, &leftTE) && ToSCE(right, &rightTE)) + { + // Ready + bool leftEqual = AreEqual(leftTE, outState.leftTriggerEffect); + bool rightEqual = AreEqual(rightTE, outState.rightTriggerEffect); + + if (!leftEqual || !rightEqual) + { + outState.leftTriggerEffect = leftTE; + outState.rightTriggerEffect = rightTE; + dirty = true; + } + } + } +} + +void Components::GamepadControls::DualSenseGamePadAPI::UpdateLights(uint32_t color) +{ + if (EnsureConnected()) + { + // Remove white led + outState.playerLeds.playerLedFade = false; + outState.playerLeds.bitmask = 0; + outState.playerLeds.brightness = DS5W::LedBrightness::LOW; + + // And update + static DS5W::Color dsColor{}; + dsColor.r = static_cast(color & 0xFF); + dsColor.g = static_cast((color >> 8) & 0xFF); + dsColor.b = static_cast((color >> 16) & 0xFF); + + bool areEqual = AreEqual(dsColor, outState.lightbar); + if (!areEqual) + { + outState.lightbar = dsColor; + dirty = true; + } + } +} + + +bool Components::GamepadControls::DualSenseGamePadAPI::Fetch() +{ + if (EnsureConnected()) + { + if (DS5W_SUCCESS(DS5W::getDeviceInputState(&context, &inputState))) + { + return true; + } + } + + return false; +} + +void Components::GamepadControls::DualSenseGamePadAPI::ReadSticks(Game::vec2_t& leftStick, Game::vec2_t& rightStick) +{ + ConvertStickToFloat(inputState.leftStick.x, inputState.leftStick.y, leftStick[0], leftStick[1]); + ConvertStickToFloat(inputState.rightStick.x, inputState.rightStick.y, rightStick[0], rightStick[1]); +} + +void Components::GamepadControls::DualSenseGamePadAPI::ReadDigitals(unsigned short& digitals) +{ + auto buttons = inputState.buttonsAndDpad; + + digitals = 0; + +#define TRANSLATE(x, ds5Button)\ + if ((buttons & ds5Button) != 0) \ + {\ + digitals |= Game::GamePadButton::x;\ + } + + TRANSLATE(GPAD_UP, DS5W_ISTATE_DPAD_UP); + TRANSLATE(GPAD_DOWN, DS5W_ISTATE_DPAD_DOWN); + TRANSLATE(GPAD_RIGHT, DS5W_ISTATE_DPAD_RIGHT); + TRANSLATE(GPAD_LEFT, DS5W_ISTATE_DPAD_LEFT); + TRANSLATE(GPAD_X, DS5W_ISTATE_BTX_SQUARE); + TRANSLATE(GPAD_Y, DS5W_ISTATE_BTX_TRIANGLE); + TRANSLATE(GPAD_B, DS5W_ISTATE_BTX_CIRCLE); + TRANSLATE(GPAD_A, DS5W_ISTATE_BTX_CROSS); + + buttons = inputState.buttonsA; + TRANSLATE(GPAD_START, DS5W_ISTATE_BTN_A_MENU); + TRANSLATE(GPAD_BACK, DS5W_ISTATE_BTN_A_SELECT); + TRANSLATE(GPAD_L3, DS5W_ISTATE_BTN_A_LEFT_STICK); + TRANSLATE(GPAD_R3, DS5W_ISTATE_BTN_A_RIGHT_STICK); + + TRANSLATE(GPAD_L_SHLDR, DS5W_ISTATE_BTN_A_LEFT_BUMPER); + TRANSLATE(GPAD_R_SHLDR, DS5W_ISTATE_BTN_A_RIGHT_BUMPER); + +#undef TRANSLATE +} + +void Components::GamepadControls::DualSenseGamePadAPI::ReadAnalogs(float& leftTrigger, float& rightTrigger) +{ + ConvertStickToFloat(inputState.leftTrigger, inputState.rightTrigger, leftTrigger, rightTrigger); +} + +void Components::GamepadControls::DualSenseGamePadAPI::Send() +{ + if (dirty) + { + DS5W::setDeviceOutputState(&context, &outState); + dirty = false; + } +} + diff --git a/src/Components/Modules/Gamepad/DualSenseGamepad.hpp b/src/Components/Modules/Gamepad/DualSenseGamepad.hpp new file mode 100644 index 00000000..02f4f978 --- /dev/null +++ b/src/Components/Modules/Gamepad/DualSenseGamepad.hpp @@ -0,0 +1,39 @@ +#pragma once +#include "GamepadAPI.hpp" +#include "DualSenseAPI.hpp" + +namespace Components::GamepadControls +{ + class DualSenseGamePadAPI final : public GamepadAPI + { + public: + bool PlugIn(uint8_t portIndex) override; + void UpdateRumbles(float left, float right) override; + void UpdateForceFeedback(const TriggerFeedback& left, const TriggerFeedback& right) override; + void UpdateLights(uint32_t color) override; + + bool Fetch() override; + void ReadSticks(Game::vec2_t& leftStick, Game::vec2_t& rightStick) override; + void ReadDigitals(unsigned short& digitals) override; + void ReadAnalogs(float& leftTrigger, float& rightTrigger) override; + + void Send() override; + + bool SupportsForceFeedback() const override {return true;}; + + private: + DS5W::DeviceContext context; + DS5W::DS5InputState inputState; + DS5W::DS5OutputState outState; + + uint8_t portIndex; + + bool dirty; + + bool EnsureConnected(); + bool ToSCE(const TriggerFeedback& triggerFeedback, DS5W::TriggerEffect* sce); + inline bool AreEqual(const DS5W::TriggerEffect& a, const DS5W::TriggerEffect& b); + inline bool AreEqual(const DS5W::Color& a, const DS5W::Color& b); + bool ToSCE(const float & amount01, unsigned char & amount); + }; +} diff --git a/src/Components/Modules/Gamepad/GamepadAPI.hpp b/src/Components/Modules/Gamepad/GamepadAPI.hpp new file mode 100644 index 00000000..b8ab85cc --- /dev/null +++ b/src/Components/Modules/Gamepad/GamepadAPI.hpp @@ -0,0 +1,58 @@ +#pragma once + +namespace Components::GamepadControls +{ + class GamepadAPI + { + public: + + struct TriggerFeedback + { + enum Resistance + { + NoResistance, + ContinuousSlightResistance, + ContinuousHeavyResistance, + SectionSlightResistance, + SectionHeavyResistance + }; + + Resistance resistance; + }; + + virtual bool Fetch() { return false; }; + virtual void ReadSticks([[maybe_unused]] Game::vec2_t& leftStick, [[maybe_unused]] Game::vec2_t& rightStick) {}; + virtual void ReadDigitals([[maybe_unused]] unsigned short& digitals) {}; + virtual void ReadAnalogs([[maybe_unused]] float& leftTrigger, [[maybe_unused]] float& rightTrigger) {}; + + virtual bool PlugIn([[maybe_unused]] uint8_t portIndex) { return false; }; + virtual bool SupportsForceFeedback() const { return false; }; + + virtual void UpdateRumbles([[maybe_unused]] float left, [[maybe_unused]] float right) {}; + virtual void UpdateLights([[maybe_unused]] uint32_t color) {}; + virtual void StopRumbles() {}; + virtual void Send() {}; + + virtual void UpdateForceFeedback([[maybe_unused]] const TriggerFeedback& left, [[maybe_unused]] const TriggerFeedback& right) {}; + + protected: + template + void ConvertStickToFloat(const T x, const T y, float& outX, float& outY) + { + if (x == 0 && y == 0) + { + outX = 0.0f; + outY = 0.0f; + return; + } + + Game::vec2_t stickVec; + stickVec[0] = static_cast(x) / static_cast(std::numeric_limits::max()); + stickVec[1] = static_cast(y) / static_cast(std::numeric_limits::max()); + + outX = stickVec[0]; + outY = stickVec[1]; + } + + }; +} diff --git a/src/Components/Modules/Gamepad/XInputGamepad.cpp b/src/Components/Modules/Gamepad/XInputGamepad.cpp new file mode 100644 index 00000000..dcb8ef3e --- /dev/null +++ b/src/Components/Modules/Gamepad/XInputGamepad.cpp @@ -0,0 +1,61 @@ +#include +#include "XInputGamepad.hpp" + +namespace Components::GamepadControls +{ + bool XInputGamePadAPI::PlugIn(uint8_t portIndex) + { + if (XInputGetCapabilities(portIndex, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS) + { + return true; + } + + return false; + } + + void XInputGamePadAPI::UpdateRumbles(float left, float right) + { + rumble.wRightMotorSpeed = static_cast(right * 65535.0); + rumble.wLeftMotorSpeed = static_cast(left * 65535.0); + } + + void XInputGamePadAPI::StopRumbles() + { + rumble.wLeftMotorSpeed = 0; + rumble.wRightMotorSpeed = 0; + XInputSetState(gamePadIndex, &rumble); + } + + bool XInputGamePadAPI::Fetch() + { + if (XInputGetState(gamePadIndex, &state) == ERROR_SUCCESS) + { + // OK + return true; + } + + return false; + } + + void XInputGamePadAPI::ReadSticks(Game::vec2_t& leftStick, Game::vec2_t& rightStick) + { + ConvertStickToFloat(state.Gamepad.sThumbLX, state.Gamepad.sThumbLY, leftStick[0], leftStick[1]); + ConvertStickToFloat(state.Gamepad.sThumbRX, state.Gamepad.sThumbRY, rightStick[0], rightStick[1]); + } + + void XInputGamePadAPI::ReadDigitals(unsigned short& digitals) + { + digitals = state.Gamepad.wButtons; + } + + void XInputGamePadAPI::ReadAnalogs(float& leftTrigger, float& rightTrigger) + { + leftTrigger = static_cast(state.Gamepad.bLeftTrigger) / static_cast(std::numeric_limits::max()); + rightTrigger = static_cast(state.Gamepad.bRightTrigger) / static_cast(std::numeric_limits::max()); + } + void XInputGamePadAPI::Send() + { + // We have nothing else to set here + XInputSetState(gamePadIndex, &rumble); + } +} diff --git a/src/Components/Modules/Gamepad/XInputGamepad.hpp b/src/Components/Modules/Gamepad/XInputGamepad.hpp new file mode 100644 index 00000000..1a1c4553 --- /dev/null +++ b/src/Components/Modules/Gamepad/XInputGamepad.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "GamepadAPI.hpp" + +namespace Components::GamepadControls +{ + class XInputGamePadAPI final : public GamepadAPI + { + public: + bool PlugIn(uint8_t plugIndex) override; + + void UpdateRumbles(float left, float right) override; + void StopRumbles() override; + + bool Fetch() override; + void ReadSticks(Game::vec2_t& leftStick, Game::vec2_t& rightStick) override; + void ReadDigitals(unsigned short& digitals) override; + void ReadAnalogs(float& leftTrigger, float& rightTrigger) override; + + void Send() override; + + private: + DWORD gamePadIndex; + XINPUT_VIBRATION rumble; + XINPUT_CAPABILITIES caps; + XINPUT_STATE state; + }; + +} diff --git a/src/Components/Modules/RawFiles.cpp b/src/Components/Modules/RawFiles.cpp index aecb2cde..8bde5696 100644 --- a/src/Components/Modules/RawFiles.cpp +++ b/src/Components/Modules/RawFiles.cpp @@ -122,7 +122,7 @@ namespace Components Utils::Hook(0x4DA0D0, ReadRawFile, HOOK_JUMP).install()->quick(); Utils::Hook(0x631640, GetMenuBuffer, HOOK_JUMP).install()->quick(); - Utils::Hook(0x463500, Com_LoadInfoString_Hk, HOOK_JUMP).install()->quick(); + Utils::Hook(Game::Com_LoadInfoString, Com_LoadInfoString_Hk, HOOK_JUMP).install()->quick(); Command::Add("dumpraw", [](const Command::Params* params) { diff --git a/src/Components/Modules/Rumble.cpp b/src/Components/Modules/Rumble.cpp new file mode 100644 index 00000000..d0fc7d22 --- /dev/null +++ b/src/Components/Modules/Rumble.cpp @@ -0,0 +1,1403 @@ +#include + +#include "ConfigStrings.hpp" +#include "Events.hpp" + +#include "GSC/Script.hpp" + +namespace Components +{ + + static Game::RumbleGlobals rumbleGlobArray[Game::MAX_GPAD_COUNT]{}; // We're only gonna use #0 anyway cause only one client + + // Normally these would be defined per-map, but let's just load all of them for good measure + static const std::string rumbleStrings[] = { + "riotshield_impact" + ,"damage_heavy" + ,"defaultweapon_fire" + ,"pistol_fire" + ,"defaultweapon_melee" + ,"viewmodel_small" + ,"viewmodel_medium" + ,"viewmodel_large" + ,"silencer_fire" + ,"smg_fire" + ,"assault_fire" + ,"shotgun_fire" + ,"heavygun_fire" + ,"sniper_fire" + ,"artillery_rumble" + ,"grenade_rumble" + ,"ac130_25mm_fire" + ,"ac130_40mm_fire" + ,"ac130_105mm_fire" + ,"minigun_rumble" + }; + + const static Game::cspField_t rumbleFields[4] = + { + {"duration", 4, 7}, + {"range", 8, 7}, + {"fadeWithDistance", 0x14, 5}, + {"broadcast", 0x18, 5} + }; + + Dvar::Var Rumble::cl_debug_rumbles; + Dvar::Var Rumble::cl_rumbleScale; + + int Rumble::GetRumbleInfoIndexFromName(const char* rumbleName) + { + for (size_t i = 0; i < Gamepad::RUMBLE_CONFIGSTRINGS_COUNT-1; i++) + { + const char* configStringArr = ConfigStrings::CL_GetRumbleConfigString(i); + if (configStringArr && *configStringArr) + { + const std::string& configString = configStringArr; + + if (configString == rumbleName) + { + return i; + } + } + } + + return -1; + } + + Game::ActiveRumble* Rumble::GetDuplicateRumbleIfExists([[maybe_unused]] Game::cg_s* cgameGlob, Game::ActiveRumble* arArray, Game::RumbleInfo* info, bool loop, Game::RumbleSourceType type, int entityNum, const float* pos) + { + assert(cgameGlob); + assert(arArray); + assert(type != Game::RUMBLESOURCE_INVALID); + + for (auto i = 0; i < Rumble::MAX_ACTIVE_RUMBLES; i++) + { + Game::ActiveRumble* duplicateRumble = &arArray[i]; + if (duplicateRumble->rumbleInfo != info || duplicateRumble->loop != loop || duplicateRumble->sourceType != type) + continue; + + bool isSame = false; + if (type == Game::RUMBLESOURCE_ENTITY) + { + isSame = duplicateRumble->source.entityNum == entityNum; + } + else + { + if (type != Game::RUMBLESOURCE_POS) + return duplicateRumble; + if (duplicateRumble->source.pos[0] != *pos || duplicateRumble->source.pos[1] != pos[1]) + continue; + isSame = duplicateRumble->source.pos[2] == pos[2]; + } + + if (isSame) + return duplicateRumble; + }; + + return nullptr; + } + + int Rumble::FindClosestToDyingActiveRumble(Game::cg_s* cgameGlob, Game::ActiveRumble* activeRumbleArray) + { + float oldestRumbleAge = 0.0f; + int oldestRumbleIndex = 0; + for (int i = 0; i < Rumble::MAX_ACTIVE_RUMBLES; i++) + { + const Game::ActiveRumble* ar = &activeRumbleArray[i]; + assert(ar->rumbleInfo); + assert(ar->sourceType != Game::RUMBLESOURCE_INVALID); + + if (ar->rumbleInfo && (ar->sourceType != Game::RUMBLESOURCE_ENTITY || ar->source.entityNum != cgameGlob->predictedPlayerState.clientNum)) + { + auto rumbleInfo = ar->rumbleInfo; + float timeDiff = static_cast(cgameGlob->time - ar->startTime); + float timeLived01 = timeDiff / rumbleInfo->duration; + if (timeLived01 > oldestRumbleAge) + { + oldestRumbleIndex = i; + oldestRumbleAge = timeLived01; + } + } + }; + + if (oldestRumbleAge == 0.0f) + { + Logger::Warning(Game::CON_CHANNEL_SYSTEM, "FindClosestToDyingActiveRumble(): Couldn't find a suitable rumble to stop, defaulting to index zero.\n"); + } + + return oldestRumbleIndex; + } + + Game::ActiveRumble* Rumble::NextAvailableRumble(Game::cg_s* cgameGlob, Game::ActiveRumble* arArray) + { + for (auto i = 0; i < Rumble::MAX_ACTIVE_RUMBLES; i++) + { + Game::ActiveRumble* candidate = &arArray[i]; + + // Extreme guesswork™ + if (candidate->rumbleInfo == nullptr) + { + return candidate; + } + + if (candidate->sourceType == Game::RUMBLESOURCE_INVALID) + { + return candidate; + } + + if (candidate->startTime + candidate->rumbleInfo->duration < cgameGlob->time) + { + return candidate; + } + }; + + auto index = FindClosestToDyingActiveRumble(cgameGlob, arArray); + assert(index != Rumble::MAX_ACTIVE_RUMBLES); + + return &arArray[index]; + } + + void Rumble::InvalidateActiveRumble(Game::ActiveRumble* ar) + { + ar->sourceType = Game::RUMBLESOURCE_INVALID; + ar->rumbleInfo = nullptr; + ar->startTime = -1; + } + + void Rumble::CalcActiveRumbles(int localClientNum, Game::ActiveRumble* activeRumbleArray, const float* rumbleReceiverPos) + { + auto cg = Game::CL_GetLocalClientGlobals(localClientNum); // CG ? + + float finalRumbleHigh = -1.f; + float finalRumbleLow = -1.f; + bool anyRumble = false; + + for (auto i = 0; i < Rumble::MAX_ACTIVE_RUMBLES; i++) + { + float scale; + + auto activeRumble = &activeRumbleArray[i]; + + if (!activeRumble->rumbleInfo) + { + continue; + } + + assert(activeRumble->sourceType != Game::RUMBLESOURCE_INVALID); + + if (activeRumble->rumbleInfo->broadcast) + { + if (activeRumble->sourceType == Game::RUMBLESOURCE_ENTITY && activeRumble->source.entityNum != cg->predictedPlayerState.clientNum) + { + continue; + } + + // Don't fade with distance + scale = 1.f; + } + else + { + float distance = 0.f; + + // Compute rumble distance + { + if (activeRumble->sourceType == Game::RUMBLESOURCE_ENTITY) + { + auto entity = Game::CG_GetEntity(localClientNum, activeRumble->source.entityNum); + auto receiver = Game::CG_GetEntity(localClientNum, rumbleGlobArray[localClientNum].receiverEntNum); + auto x = receiver->pose.origin[0] - entity->pose.origin[0]; + auto y = receiver->pose.origin[1] - entity->pose.origin[1]; + auto z = receiver->pose.origin[2] - entity->pose.origin[2]; + + distance = std::sqrtf((x * x) + (y * y) + (z * z)); + + } + else + { + auto x = (*rumbleReceiverPos - activeRumble->source.pos[0]); + auto y = (rumbleReceiverPos[1] - activeRumble->source.pos[1]); + auto z = (rumbleReceiverPos[2] - activeRumble->source.pos[2]); + + distance = std::sqrtf((x * x) + (y * y) + (z * z)); + } + } + + if (distance <= activeRumble->rumbleInfo->range) + { + if (activeRumble->rumbleInfo->fadeWithDistance) + { + assert(activeRumble->rumbleInfo->range > 0.f); + + // Complete guesswork + scale = 1.f - distance / activeRumble->rumbleInfo->range; + } + else + { + scale = 1.f; + } + } + else + { + continue; + } + } + + assert(scale <= 1.f); + assert(scale >= 0.f); + + scale *= activeRumble->scale / static_cast(std::numeric_limits().max()); + + // Guesswork + float duration01 = (cg->time - activeRumble->startTime) / activeRumble->rumbleInfo->duration; + assert(duration01 >= 0.f); + assert(duration01 <= 1.f); + + auto highGraph = activeRumble->rumbleInfo->highRumbleGraph; + auto highValue = Game::GraphGetValueFromFraction(highGraph->knotCount, highGraph->knots, duration01); + + auto lowGraph = activeRumble->rumbleInfo->lowRumbleGraph; + auto lowValue = Game::GraphGetValueFromFraction(lowGraph->knotCount, lowGraph->knots, duration01); + + finalRumbleHigh = std::max(finalRumbleHigh, highValue * scale); + finalRumbleLow = std::max(finalRumbleLow, lowValue * scale); + + anyRumble = true; + } + + if (anyRumble) + { + assert(finalRumbleHigh >= 0.F); + assert(finalRumbleLow >= 0.F); + Gamepad::GPad_SetHighRumble(localClientNum, finalRumbleHigh); + Gamepad::GPad_SetLowRumble(localClientNum, finalRumbleLow); + } + else + { + Gamepad::GPad_SetHighRumble(localClientNum, 0.f); + Gamepad::GPad_SetLowRumble(localClientNum, 0.f); + } + } + + void Rumble::PlayRumbleInternal(int localClientNum, const char* rumbleName, bool loop, Game::RumbleSourceType type, int entityNum, const float* pos, double scale, bool updateDuplicates) + { + assert(type != Game::RumbleSourceType::RUMBLESOURCE_INVALID); + assert(rumbleName); + assert(*rumbleName); + + int rumbleIndex = GetRumbleInfoIndexFromName(rumbleName); + + const auto logError = [&](const std::string& view) + { + if ((*Game::com_sv_running)->current.value) + { + Components::Logger::Error(Game::ERR_DROP, view); + } + else + { + Components::Logger::Warning(Game::CON_CHANNEL_SCRIPT, view); + } + }; + + if (rumbleIndex < 0) + { + // Should we play it anyway? + logError(std::format("Could not play rumble {} because it was not registered!\n", rumbleName)); + return; + } + + auto rumbleInfo = &rumbleGlobArray[localClientNum].infos[rumbleIndex]; + + assert(rumbleInfo); + + if (rumbleInfo->rumbleNameIndex < 0) + { + logError(std::format("Could not play rumble {} because it was not registered and loaded. Make sure to precache rumble before playing from script!", rumbleName)); + return; + } + + auto cg = Game::CL_GetLocalClientGlobals(localClientNum); // should be CG? + + auto activeRumble = GetDuplicateRumbleIfExists(cg, rumbleGlobArray[localClientNum].activeRumbles, rumbleInfo, loop, type, entityNum, pos); + bool rumbleIsDuplicate = activeRumble; + + if (activeRumble) + { + // All good + } + else + { + activeRumble = NextAvailableRumble(cg, rumbleGlobArray[localClientNum].activeRumbles); + assert(activeRumble); + } + + if (!rumbleIsDuplicate || updateDuplicates) + { + if (type == Game::RUMBLESOURCE_ENTITY) + { + auto entity = Game::CG_GetEntity(localClientNum, entityNum); + if (!rumbleInfo->broadcast) + { + if ((entity->nextValid & 1) == 0) + { + // Next snap is not valid + return; + } + + if (entity->nextState.eType != 1) + { + logError( + std::format( + "Non-player entity #{} of type {} at ({}, {}, {}) is trying to play non-broadcasting rumble \"{}\" on themselves.\n", + entityNum, + entity->nextState.eType, + entity->prevState.pos.trBase[0], + entity->prevState.pos.trBase[1], + entity->prevState.pos.trBase[2], + rumbleName + ) + ); + return; + } + } + + activeRumble->source.entityNum = entityNum; + } + else if (type == Game::RUMBLESOURCE_POS) + { + std::memcpy(activeRumble->source.pos, pos, ARRAYSIZE(activeRumble->source.pos) * sizeof(float)); + } + else + { + assert(false); // Wrong type + } + } + + if (scale < 0.0 || scale > 1.0) + { + Logger::Warning(Game::CON_CHANNEL_SYSTEM, "Rumble \"{}\" has invalid scale value of {}.\n", rumbleName, scale); + scale = 1.0; + } + activeRumble->sourceType = type; + activeRumble->startTime = cg->time; + activeRumble->rumbleInfo = rumbleInfo; + activeRumble->loop = loop; + activeRumble->scale = static_cast(scale * 255.0); + + if (!cg->nextSnap || cg->predictedPlayerState.clientNum == cg->localClientNum && cg->predictedPlayerState.pm_type != 5) + CalcActiveRumbles( + localClientNum, + rumbleGlobArray[localClientNum].activeRumbles, + rumbleGlobArray[localClientNum].receiverPos); + } + + void Rumble::CG_PlayRumbleOnEntity(int localClientNum, const char* rumbleName, int entityNum) + { + PlayRumbleInternal(localClientNum, rumbleName, 0, Game::RUMBLESOURCE_ENTITY, entityNum, nullptr, cl_rumbleScale.get(), false); + } + + void Rumble::CG_PlayRumbleOnPosition(int localClientNum, const char* rumbleName, const float* pos) + { + PlayRumbleInternal(localClientNum, rumbleName, 0, Game::RUMBLESOURCE_POS, 0, pos, cl_rumbleScale.get(), false); + } + + void Rumble::CG_PlayRumbleLoopOnEntity(int localClientNum, const char* rumbleName, int entityNum) + { + PlayRumbleInternal(localClientNum, rumbleName, true, Game::RUMBLESOURCE_ENTITY, entityNum, nullptr, cl_rumbleScale.get(), false); + } + + void Rumble::CG_PlayRumbleLoopOnPosition(int localClientNum, const char* rumbleName, const float* pos) + { + PlayRumbleInternal(localClientNum, rumbleName, true, Game::RUMBLESOURCE_POS, 0, pos, cl_rumbleScale.get(), false); + } + + void Rumble::CG_PlayRumbleOnClient(int localClientNum, const char* rumbleName) + { + auto clientGlob = Game::CL_GetLocalClientGlobals(localClientNum); + + assert(clientGlob->nextSnap); + + if (clientGlob->nextSnap) + { + PlayRumbleInternal( + localClientNum, + rumbleName, + 0, + Game::RUMBLESOURCE_ENTITY, + clientGlob->predictedPlayerState.clientNum, + nullptr, + cl_rumbleScale.get(), + false + ); + } + } + + void Rumble::CG_PlayRumbleOnClientSafe(int localClientNum, const char* rumbleName) + { + if (GetRumbleInfoIndexFromName(rumbleName) >= 0) + { + PlayRumbleInternal(localClientNum, rumbleName, 0, Game::RUMBLESOURCE_ENTITY, Game::CL_GetLocalClientGlobals(localClientNum)->predictedPlayerState.clientNum, 0, cl_rumbleScale.get(), false); + } + else + { + Game::Com_PrintWarning(14, "Can't play rumble asset '%s' because it is not registered.\n", rumbleName); + } + } + + void Rumble::Rumble_Strcpy(char* member, char* keyValue) + { + strcpy(member, keyValue); + } + + bool Rumble::ParseRumbleGraph(Game::RumbleGraph* graph, const char* buffer, const char* fileName) + { +#define MAX_RUMBLE_GRAPH_KNOTS 16 + + auto buffer_ = buffer; + assert(graph); + Game::Com_BeginParseSession(fileName); + auto knotCountStr = Game::Com_Parse(&buffer_); + auto parsedKnotCount = atoi(knotCountStr); + if (parsedKnotCount <= MAX_RUMBLE_GRAPH_KNOTS) + { + if (parsedKnotCount >= 0) + { + graph->knotCount = static_cast(parsedKnotCount); + + if (graph->knotCount) + { + for (auto i = 0; i < graph->knotCount; i++) + { + auto knot = &graph->knots[i]; + + const char* parsedCharacterA = Game::Com_Parse(&buffer_); + if (!*parsedCharacterA) + break; + if (*parsedCharacterA == '}') + break; + + float floatA = static_cast(atof(parsedCharacterA)); + + const char* parsedCharacterB = Game::Com_Parse(&buffer_); + if (!*parsedCharacterB || *parsedCharacterB == '}') + break; + float floatB = static_cast(atof(parsedCharacterB)); + + if (i >= MAX_RUMBLE_GRAPH_KNOTS) + { + Logger::Error(Game::ERR_DROP, "knotCountIndex doesn't index MAX_RUMBLE_GRAPH_KNOTS: {} not in [0, {}])", i, MAX_RUMBLE_GRAPH_KNOTS); + } + + (*knot)[0] = floatA; + (*knot)[1] = floatB; + }; + } + + Game::Com_EndParseSession(); + + return true; + } + else + { + Game::Com_EndParseSession(); + Logger::Error(Game::ERR_DROP, "Negative graph nots on {}", fileName); + return false; + } + } + else + { + Game::Com_EndParseSession(); + Logger::Error(Game::ERR_DROP, "Too many graph nots on {}", fileName); + return false; + } + } + + void Rumble::ReadRumbleGraph(Game::RumbleGraph* graph, const char* rumbleFileName) + { + assert(graph); + assert(rumbleFileName); + + char buff[256]{}; + std::string path = std::format("rumble/{}", rumbleFileName); + + [[maybe_unused]] auto graphBefore = graph; + + strncpy(graph->graphName, rumbleFileName, 64); + auto data = Game::Com_LoadInfoString(path.data(), "rumble graph file", "RUMBLEGRAPHFILE", buff); + + assert(graph == graphBefore); + + graph->knotCount = 0; + if (!ParseRumbleGraph(graph, data, rumbleFileName)) + { + Logger::Error(Game::ERR_DROP, "Error in parsing rumble file {}", rumbleFileName); + } + + } + + int Rumble::LoadRumbleGraph(Game::RumbleGraph* rumbleGraphArray, Game::RumbleInfo* info, const char* highRumbleFileName, const char* lowRumbleFileName) + { + info->highRumbleGraph = 0; + info->lowRumbleGraph = 0; + + auto i = 0; + + for (i = 0; i < 64; ++i) + { + auto rumbleGraph = &rumbleGraphArray[i]; + if (!rumbleGraph->knotCount) + break; + if (!_strnicmp(rumbleGraph->graphName, highRumbleFileName, 0x7FFFFFFF)) // TODO change that + info->highRumbleGraph = rumbleGraph; + if (!_strnicmp(rumbleGraph->graphName, lowRumbleFileName, 0x7FFFFFFF)) + info->lowRumbleGraph = rumbleGraph; + } + if (!info->highRumbleGraph || !info->lowRumbleGraph) + { + if (i == 64) + Components::Logger::Error(Game::ERR_DROP, "No more room to allocate rumble graph"); + + auto rumbleGraph = &rumbleGraphArray[i]; + + while (i < 64) + { + if (i == 64) + { + Components::Logger::Error(Game::ERR_DROP, "No more room to allocate rumble graph"); + } + else if (!info->highRumbleGraph) + { + ReadRumbleGraph(rumbleGraph, highRumbleFileName); + info->highRumbleGraph = rumbleGraph; + i++; + } + else if (!info->lowRumbleGraph) + { + ReadRumbleGraph(rumbleGraph, lowRumbleFileName); + info->lowRumbleGraph = rumbleGraph; + i++; + } + else + { + break; + } + } + + // There's more stuff that should be happening here + } + + return 1; + } + + int Rumble::CG_LoadRumble(Game::RumbleGraph* rumbleGraphArray, Game::RumbleInfo* info, const char* rumbleName, int rumbleNameIndex) + { + assert(info); + assert(rumbleName); + + std::string path = std::format("rumble/{}", rumbleName); + char buff[256]{}; // should be 64 but it ALWAYS goes overboard! + + [[maybe_unused]] auto infoPtr = info; + const char* str = Game::Com_LoadInfoString(path.data(), "rumble info file", "RUMBLE", buff); + assert(infoPtr == info); + + const std::string highRumbleFile = Game::Info_ValueForKey(str, "highRumbleFile"); + const std::string lowRumbleFile = Game::Info_ValueForKey(str, "lowRumbleFile"); + + if (!Game::ParseConfigStringToStruct(info, rumbleFields, 4, str, 0, 0, Rumble_Strcpy)) + { + return 0; + } + + if (info->broadcast) + { + if (info->range == 0.0) + { + Components::Logger::Error(Game::ERR_DROP, "Rumble file {} cannot have broadcast because its range is zero\n", rumbleName); + } + } + + if (!LoadRumbleGraph(rumbleGraphArray, info, highRumbleFile.data(), lowRumbleFile.data())) + return 0; + + info->rumbleNameIndex = rumbleNameIndex; + info->duration = info->duration * 1000.f; + + return 1; + } + + void Rumble::CG_RegisterRumbles(int localClientNum) + { + const auto myRumbleGlobal = &rumbleGlobArray[localClientNum]; + const auto maxRumbleGraphIndex = Gamepad::RUMBLE_CONFIGSTRINGS_COUNT; + + for (int i = 1; i < maxRumbleGraphIndex; i++) + { + auto rumbleConf = ConfigStrings::CL_GetRumbleConfigString(i - 1); + if (*rumbleConf) + { + CG_LoadRumble(myRumbleGlobal->graphs, &rumbleGlobArray[localClientNum].infos[i - 1], rumbleConf, i); + } + } + } + + void Rumble::CG_RegisterGraphics_Hk(int localClientNum, int b) + { + // Call original function + Utils::Hook::Call(0x5895D0)(localClientNum, b); + + CG_RegisterRumbles(localClientNum); + } + + int Rumble::G_RumbleIndex(const char* name) + { + assert(name); + + if (*name) + { + auto rumbleToLookFor = Game::SL_FindLowercaseString(name); + int i; + + for (i = 1; i <= Gamepad::RUMBLE_CONFIGSTRINGS_COUNT; ++i) + { + auto rumble = ConfigStrings::SV_GetRumbleConfigStringConst(i - 1); + if (rumble == Game::scr_const->_) + break; + if (rumble == rumbleToLookFor) + return i; + } + + if (i >= Gamepad::RUMBLE_CONFIGSTRINGS_COUNT) + { + Logger::Print("WARNING: Rumble not registered, {}\n", name); + } + else + { + ConfigStrings::SV_SetRumbleConfigString(i - 1, name); + return i; + } + } + + return 0; + } + + void Rumble::RegisterWeaponRumbles(Game::WeaponDef* weapDef) + { + assert(weapDef); + + auto fireRumble = weapDef->fireRumble; + if (fireRumble && *fireRumble) + { + G_RumbleIndex(fireRumble); + } + + auto meleeImpactRumble = weapDef->meleeImpactRumble; + if (meleeImpactRumble && *meleeImpactRumble) + { + G_RumbleIndex(meleeImpactRumble); + } + + auto turretBarrelSpinRumble = weapDef->turretBarrelSpinRumble; + if (turretBarrelSpinRumble && *turretBarrelSpinRumble) + { + G_RumbleIndex(turretBarrelSpinRumble); + } + + + for (auto i = 0; i < 16; ++i) + { + if (!weapDef->notetrackRumbleMapKeys[i]) + break; + + auto noteTrackRumbleMap = weapDef->notetrackRumbleMapValues; + if (noteTrackRumbleMap[i]) + { + auto str = Game::SL_ConvertToString(noteTrackRumbleMap[i]); + G_RumbleIndex(str); + } + } + } + + void Rumble::CG_FireWeapon_Rumble(int localClientNum, Game::entityState_s* ent, Game::WeaponDef* weaponDef, bool isPlayerView) + { + assert(ent); + assert(weaponDef); + + bool freeView = true; + + if (weaponDef) + { + auto rumbleName = weaponDef->fireRumble; + if (rumbleName && *rumbleName) + { + auto cg = Game::CL_GetLocalClientGlobals(localClientNum); // should be CG instead + + if (ent->eType != 12 + || (cg->predictedPlayerState.eFlags & Game::EF_VEHICLE_ACTIVE) == 0 + || cg->predictedPlayerState.viewlocked_entNum != ent->number) + { + freeView = false; + } + + if (isPlayerView || freeView) + { + CG_PlayRumbleOnClient(localClientNum, weaponDef->fireRumble); + } + } + } + } + + void __declspec(naked) Rumble::CG_FireWeapon_FireSoundHk() + { + __asm + { + pushad; + + push bx + push[esp + 0x20 + 0x28 + 0x2] // weapon + push esi // cent + push ebp + + call CG_FireWeapon_Rumble + + add esp, 0x4 * 3 + 0x2 + + popad; + + // OG code + sub esp, 0x10; + push ebp; + mov ebp, [esp + 0x24]; + + // Return + push 0x59D7D8; + retn; + } + } + + Game::WeaponDef* Rumble::BG_GetWeaponDef_RegisterRumble_Hk(unsigned int weapIndex) + { + auto weapDef = Game::BG_GetWeaponDef(weapIndex); + + RegisterWeaponRumbles(weapDef); + + return weapDef; + } + + void Rumble::SCR_UpdateRumble() + { + auto connectionState = Game::CL_GetLocalClientConnectionState(0); + + int controllerIndex = Game::CL_ControllerIndexFromClientNum(0); + if (connectionState != 9 || (*Game::cl_paused)->current.enabled) + { + Gamepad::GPad_StopRumbles(controllerIndex); + } + else + { + Gamepad::GPad_UpdateFeedbacks(); + } + } + + void Rumble::RemoveInactiveRumbles(int localClientNum, Game::ActiveRumble* activeRumbleArray) + { + auto cg = Game::CL_GetLocalClientGlobals(localClientNum); + + for (int i = 0; i < Rumble::MAX_ACTIVE_RUMBLES; i++) + { + auto ar = &activeRumbleArray[i]; + + if (!ar->rumbleInfo) + { + continue; + } + + assert(ar->sourceType != Game::RUMBLESOURCE_INVALID); + + // This is not what the game does but... it sounds logical + if (ar->rumbleInfo->duration < cg->time - ar->startTime) + { + InvalidateActiveRumble(ar); + continue; + } + + if (ar->sourceType == Game::RUMBLESOURCE_ENTITY && ar->source.pos) + { + auto entity = Game::CG_GetEntity(localClientNum, ar->source.entityNum); + + //auto snap = &cg->predictedPlayerState; + //auto eFlags = + // ar->source.entityNum == snap->clientNum ? + // snap->eFlags : + // entity->nextState.lerp.eFlags; + + + // EF_LOOP_RUMBLE Seems to never be set + // I don't know where to look for it + // So we need to comment it out in the meantime otherwise no rumble ever plays + if (!entity->nextValid/* || (eFlags & Game::EF_LOOP_RUMBLE) == 0*/) + { + InvalidateActiveRumble(ar); + continue; + } + } + } + } + + void Rumble::CG_UpdateRumble(int localClientNum) + { + auto cg = Game::CL_GetLocalClientGlobals(localClientNum); + if (cg->nextSnap && (cg->predictedPlayerState.clientNum != cg->localClientNum || cg->predictedPlayerState.pm_type == 5)) + { + for (int i = 0; i < Rumble::MAX_ACTIVE_RUMBLES; i++) + { + auto ar = &rumbleGlobArray[localClientNum].activeRumbles[i]; + + if (ar->startTime < 0) + { + break; + } + + InvalidateActiveRumble(ar); + } + + Gamepad::GPad_SetLowRumble(localClientNum, 0.0); + Gamepad::GPad_SetHighRumble(localClientNum, 0.0); + } + else + { + RemoveInactiveRumbles(localClientNum, rumbleGlobArray[localClientNum].activeRumbles); + CalcActiveRumbles(localClientNum, rumbleGlobArray[localClientNum].activeRumbles, rumbleGlobArray[localClientNum].receiverPos); + } + } + + void Rumble::CG_SetRumbleReceiver() + { + constexpr int localClientIndex = 0; // :( + + rumbleGlobArray[localClientIndex].receiverEntNum = Game::CL_GetLocalClientGlobals(localClientIndex)->predictedPlayerState.clientNum; + + std::memcpy( + rumbleGlobArray[localClientIndex].receiverPos, + Game::CL_GetLocalClientGlobals(localClientIndex)->refdef.view.org, + sizeof(float) * ARRAYSIZE(rumbleGlobArray[localClientIndex].receiverPos + ) + ); + + // R_EndDobjScene + Utils::Hook::Call(0x50BB30)(); + } + + void Rumble::CG_UpdateEntInfo_Hk() + { + Utils::Hook::Call(0X5994B0)(); // Call original + CG_UpdateRumble(0); // Local client has to be zero i guess :< + } + + void Rumble::DebugRumbles() + { + Game::Font_s* font = Game::R_RegisterFont("fonts/smallFont", 0); + auto height = Game::R_TextHeight(font); + auto scale = 0.55f; + + auto activeRumbles = rumbleGlobArray[0].activeRumbles; + + for (std::size_t i = 0; i < Rumble::MAX_ACTIVE_RUMBLES; ++i) + { + float color[4] = { 1.0f, 0.0f, 0.0f, 1.0f }; + std::stringstream str; + str << std::format("{} => ", i); + + if (activeRumbles[i].rumbleInfo == nullptr) + { + str << "INACTIVE"; + } + else + { + auto activeRumble = &activeRumbles[i]; + auto cg = Game::CL_GetLocalClientGlobals(0); // CG ? + float duration01 = (cg->time - activeRumble->startTime) / activeRumble->rumbleInfo->duration; + + auto highGraph = activeRumble->rumbleInfo->highRumbleGraph; + auto highValue = Game::GraphGetValueFromFraction(highGraph->knotCount, highGraph->knots, duration01); + + auto lowGraph = activeRumble->rumbleInfo->lowRumbleGraph; + auto lowValue = Game::GraphGetValueFromFraction(lowGraph->knotCount, lowGraph->knots, duration01); + + str << std::format("HIGH: {} / LOW: {} (Time left: {:.0f}%)", highValue * scale, lowValue * scale, duration01 * 100); + + color[0] = 0.f; + color[2] = 1.f; + } + + Game::R_AddCmdDrawText(str.str().data(), std::numeric_limits::max(), font, 15.0f, (height * scale + 1) * (i + 1) + 4.0f, scale, scale, 0.0f, color, Game::ITEM_TEXTSTYLE_NORMAL); + } + } + + + void Rumble::LoadConstantRumbleConfigStrings() + { + static_assert(ARRAYSIZE(rumbleStrings) < Gamepad::RUMBLE_CONFIGSTRINGS_COUNT); + + for (size_t i = 0; i < ARRAYSIZE(rumbleStrings); i++) + { + // this registers the config string as constant + ConfigStrings::SV_SetRumbleConfigString(i, rumbleStrings[i].data()); + } + } + + int Rumble::CCS_GetChecksum_Hk() + { + LoadConstantRumbleConfigStrings(); + return Utils::Hook::Call(0x4A0060)(); + } + + void Rumble::SV_InitGameProgs_Hk(int arg) + { + LoadConstantRumbleConfigStrings(); + + return Utils::Hook::Call(0x445940)(arg); + } + + void Rumble::CG_GetImpactEffectForWeapon_Hk(int localClientNum, const int sourceEntityNum, const int weaponIndex, const int surfType, const int impactFlags, Game::FxEffectDef** outFx, Game::snd_alias_list_t** outSnd) + { + CG_PlayRumbleOnClient(localClientNum, "riotshield_impact"); + Utils::Hook::Call(0x4E43E0)(localClientNum, sourceEntityNum, weaponIndex, surfType, impactFlags, outFx, outSnd); + } + + void Rumble::CG_ExplosiveImpactOnShieldEvent(int localClientNum) + { + CG_PlayRumbleOnClient(localClientNum, "riotshield_impact"); + Utils::Hook::Call(0x4FBCB0)(localClientNum); + } + + void Rumble::CG_ExplosiveSplashOnShieldEvent(int localClientNum, int weaponIndex) + { + CG_PlayRumbleOnClient(localClientNum, "riotshield_impact"); + Utils::Hook::Call(0x4F2EA0)(localClientNum, weaponIndex); + } + + void Rumble::PlayNoteMappedRumbleAliases(int localClientNum, const char* noteName, Game::WeaponDef* weapDef) + { + if (*weapDef->notetrackRumbleMapKeys) + { + const auto stringID = Game::SL_FindLowercaseString(noteName); + if (stringID) + { + for (auto i = 0; i < 16; ++i) + { + if (!weapDef->notetrackRumbleMapKeys[i]) + break; + + const auto values = weapDef->notetrackRumbleMapValues; + if (values[i] && weapDef->notetrackRumbleMapKeys[i] == stringID) + { + const auto rumbleName = Game::SL_ConvertToString(values[i]); + if (rumbleName) + { + CG_PlayRumbleOnClientSafe(localClientNum, rumbleName); + } + } + } + } + } + } + + void __declspec(naked) Rumble::PlayNoteMappedSoundAliases_Stub() + { + __asm + { + pushad + push edi // WeapDeff + push ecx // NoteName + push edx // LocalClientNum + + call PlayNoteMappedRumbleAliases + + pop edx + pop ecx + pop edi + popad + + // original code + mov eax, [edi + 0x18] + cmp word ptr[eax], 0 + + // Go back + push 0x59C447 + retn + } + } + + void Rumble::InitDvars() + { + cl_debug_rumbles = Dvar::Register("cl_debug_rumbles", false, Game::DVAR_SAVED, "Debug rumbles on the screen"); + cl_rumbleScale = Dvar::Register("cl_rumbleScale", 0.6f, 0.f, 1.f, Game::DVAR_SAVED, "Rumble multiplier for the controller"); + } + + void Rumble::CG_StopRumble(int localClientNum, int entityNum, const char* rumbleName) + { + const auto activeRumbles = rumbleGlobArray[localClientNum].activeRumbles; + for (size_t i = 0; i < MAX_ACTIVE_RUMBLES; i++) + { + auto activeRumble = &activeRumbles[i]; + + if (activeRumble->startTime > 0 && activeRumble->sourceType == Game::RumbleSourceType::RUMBLESOURCE_ENTITY) + { + assert(activeRumble->rumbleInfo); + if (activeRumble->source.entityNum == entityNum) + { + const std::string& otherRumbleName = ConfigStrings::CL_GetRumbleConfigString(activeRumble->rumbleInfo->rumbleNameIndex); + if (otherRumbleName == rumbleName) + { + InvalidateActiveRumble(activeRumble); + return; + } + } + } + } + } + + bool Rumble::CG_EntityEvents_Hk(Game::centity_s* entity, rumble_entity_event_t event) + { + const auto rumbleIndex = entity->nextState.eventParm; + + switch (event) + { + case EV_PLAY_RUMBLE_ON_ENT: + { + const auto rumbleName = ConfigStrings::CL_GetRumbleConfigString(rumbleIndex); + CG_PlayRumbleOnEntity(0, rumbleName, entity->nextState.clientNum); + return true; + } + + case EV_PLAY_RUMBLE_ON_POS: + { + const auto rumbleName = ConfigStrings::CL_GetRumbleConfigString(rumbleIndex); + CG_PlayRumbleOnPosition(0, rumbleName, entity->pose.origin); + return true; + } + + case EV_PLAY_RUMBLELOOP_ON_ENT: + { + const auto rumbleName = ConfigStrings::CL_GetRumbleConfigString(rumbleIndex); + CG_PlayRumbleLoopOnEntity(0, rumbleName, entity->nextState.clientNum); + return true; + } + + case EV_PLAY_RUMBLELOOP_ON_POS: + { + const auto rumbleName = ConfigStrings::CL_GetRumbleConfigString(rumbleIndex); + CG_PlayRumbleLoopOnPosition(0, rumbleName, entity->pose.origin); + return true; + } + + case EV_STOP_RUMBLE: + { + const auto rumbleName = ConfigStrings::CL_GetRumbleConfigString(rumbleIndex); + CG_StopRumble(0, entity->nextState.clientNum, rumbleName); + return true; + } + + case EV_STOP_ALL_RUMBLES: + CG_StopAllRumbles(); + return true; + } + + return false; + } + + __declspec(naked) void Rumble::CG_EntityEvents_Stub() + { + __asm + { + // We store EAX around cause we will need to restore it + push eax + pushad + + push ebx // event + push[esp + 0xA8 + 0x20 + 0x4] + + call CG_EntityEvents_Hk + + add esp, 8 + mov[esp + 0x20], eax + + popad + pop eax + + test al, al + jz processCgEvents + + push 0x4DED0A + retn + + processCgEvents : + + // original code + mov edx, [0x9F5CE4] + + // go back + push 0x4DCF8A + retn + } + } + + void Rumble::CG_StopAllRumbles() + { + for (size_t i = 0; i < ARRAYSIZE(rumbleGlobArray[0].activeRumbles); i++) + { + InvalidateActiveRumble(&rumbleGlobArray[0].activeRumbles[i]); + } + + Gamepad::GPad_SetHighRumble(0, 0.0); + Gamepad::GPad_SetLowRumble(0, 0.0); + Gamepad::GPad_StopRumbles(0); + } + + void Rumble::Scr_PlayRumbleOnEntity(Game::scr_entref_t entref) + { + Scr_PlayRumbleOnEntity_Internal(entref, EV_PLAY_RUMBLE_ON_ENT); + } + + void Rumble::Scr_PlayRumbleLoopOnEntity(Game::scr_entref_t entref) + { + Scr_PlayRumbleOnEntity_Internal(entref, EV_PLAY_RUMBLELOOP_ON_ENT); + } + + void Rumble::Scr_PlayRumbleOnEntity_Internal(Game::scr_entref_t entref, rumble_entity_event_t event) + { + auto entity = Game::GetEntity(entref); + const auto rumbleName = Game::Scr_GetString(0); + const auto index = G_RumbleIndex(rumbleName); + + if (!index) + { + Logger::Error(Game::ERR_SCRIPT, "unknown rumble name '{}'", rumbleName); + return; + } + + entity->r.svFlags &= 0xFEu; + if (Game::Scr_GetNumParam() == 1) + { + if (event == EV_PLAY_RUMBLELOOP_ON_ENT) + { + auto client = entity->client; + if (client) + { + const auto newFlags = client->ps.eFlags | Game::EF_LOOP_RUMBLE; + client->ps.eFlags = newFlags; + } + else + { + entity->s.lerp.eFlags |= Game::EF_LOOP_RUMBLE; + } + } + + Game::G_AddEvent(entity, static_cast(event), index - 1); + } + else + { + Game::Scr_Error("Incorrect number of parameters.\n"); + } + } + + void Rumble::Scr_PlayRumbleOnPosition_Internal(rumble_entity_event_t event) + { + const auto rumbleName = Game::Scr_GetString(0); + const auto index = G_RumbleIndex(rumbleName); + + if (!index) + { + Logger::Error(Game::ERR_SCRIPT, "unknown rumble name '{}'", rumbleName); + return; + } + + float vec[3]{}; + Game::Scr_GetVector(1u, vec); + + const auto entity = Game::G_TempEntity(vec, static_cast(event)); + entity->s.eventParm = index - 1; + } + + void Rumble::Scr_PlayRumbleLoopOnPosition() + { + if (Game::Scr_GetNumParam() != 2) + { + Game::Scr_ParamError(0, "PlayRumbleLoopOnPosition [rumble name] [pos]"); + } + + Scr_PlayRumbleOnPosition_Internal(EV_PLAY_RUMBLELOOP_ON_POS); + } + + void Rumble::Scr_PlayRumbleOnPosition() + { + if (Game::Scr_GetNumParam() != 2) + { + Game::Scr_ParamError(0, "PlayRumbleOnPosition [rumble name] [pos]"); + } + + Scr_PlayRumbleOnPosition_Internal(EV_PLAY_RUMBLE_ON_POS); + } + + void Rumble::CG_Turret_UpdateBarrelSpinRumble(int localClientNum, Game::centity_s* cent) + { + // Update barrel spin sound + Utils::Hook::Call(0x4E3090)(localClientNum, cent); + + // Then rumble + const auto weapon = Game::BG_GetWeaponDef(cent->nextState.weapon); + + if (weapon->turretBarrelSpinEnabled) + { + const auto rumble = weapon->turretBarrelSpinRumble; + if (rumble) + { + if (*rumble && cent->pose.___u10.turret.playerUsing) + { + const auto time = Game::cgArray->time; + const auto BG_Turret_ComputeBarrelSpinRate = Utils::Hook::Call(0x4D5770); + + const auto spinRate = BG_Turret_ComputeBarrelSpinRate(weapon, ¢->nextState.lerp.u.turret, time); + + if (spinRate > 0.f) + { + PlayRumbleInternal( + localClientNum, + rumble, + 0, + Game::RUMBLESOURCE_ENTITY, + Game::cgArray->predictedPlayerState.clientNum, + 0, + spinRate * cl_rumbleScale.get(), + true + ); + } + } + } + } + } + + void Rumble::MeleeRumble_Hook(Game::gentity_s* targetEntity, Game::WeaponDef* weaponDef) + { + if (targetEntity && targetEntity->client) + { + if (weaponDef->meleeImpactRumble && *weaponDef->meleeImpactRumble) + { + targetEntity->r.svFlags &= 0xFEu; + const auto index = G_RumbleIndex(weaponDef->meleeImpactRumble); + Game::G_AddEvent(targetEntity, static_cast(EV_PLAY_RUMBLE_ON_ENT), index); + } + } + } + + __declspec(naked) void Rumble::MeleeRumble_Stub() + { + __asm + { + pushad + + push ebx + push esi + call MeleeRumble_Hook + add esp, 8 + + popad + + // Original code + cmp dword ptr[esi + 0x158], 0 + je goBack + + // go back + push 0x05FCD7D + retn + + // other condition + goBack : + push 0x5FCDA0 + retn + } + } + + Rumble::Rumble() + { + if (ZoneBuilder::IsEnabled()) + return; + + // WeaponMelee rumble + Utils::Hook(0x5FCD74, MeleeRumble_Stub, HOOK_JUMP).install()->quick(); + + // Parse CG_EntityEvents for new events + Utils::Hook(0x4DCF84, CG_EntityEvents_Stub, HOOK_JUMP).install()->quick(); + + // CG_setRumbleReceiver + Utils::Hook(0x486DEC, CG_SetRumbleReceiver, HOOK_CALL).install()->quick(); + + // Client & server + Utils::Hook(0x5AC2F3, CCS_GetChecksum_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x4A75CC, SV_InitGameProgs_Hk, HOOK_CALL).install()->quick(); + + // Rumble action + Utils::Hook(0x59D7D0, CG_FireWeapon_FireSoundHk, HOOK_JUMP).install()->quick(); + + // CG_BulletHitClientShield + Utils::Hook(0x42A611, CG_GetImpactEffectForWeapon_Hk, HOOK_CALL).install()->quick(); + + // CG_ExplosiveImpactOnShield + Utils::Hook(0x4DE156, CG_ExplosiveImpactOnShieldEvent, HOOK_CALL).install()->quick(); + + // CG_ExplosiveSplashOnShieldEvent + Utils::Hook(0x4DE17D, CG_ExplosiveSplashOnShieldEvent, HOOK_CALL).install()->quick(); + + // PlayNoteMappedRumbleAliases + Utils::Hook(0x59C440, PlayNoteMappedSoundAliases_Stub, HOOK_JUMP).install()->quick(); + + + // CG_Turret_UpdateBarrelSpinRumble + Utils::Hook(0x5861B8, CG_Turret_UpdateBarrelSpinRumble, HOOK_CALL).install()->quick(); + + Events::OnDvarInit([]() { + InitDvars(); + }); + + // Frame rumble update + Utils::Hook(0x47E035, SCR_UpdateRumble, HOOK_CALL).install()->quick(); + Utils::Hook(0x486BB6, CG_UpdateEntInfo_Hk, HOOK_CALL).install()->quick(); + + + // rumble loading + Utils::Hook(0x43E1F8, BG_GetWeaponDef_RegisterRumble_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x4E37D3, CG_RegisterGraphics_Hk, HOOK_CALL).install()->quick(); + + // CG_PlayRumble_f + Command::Add("playrumble", [](const Command::Params* params) { + if (Game::CL_GetLocalClientGlobals(0)->nextSnap) + { + if (params->size() == 2) + { + auto rumbleName = params->get(1); + CG_PlayRumbleOnClient(0, rumbleName); + } + else + { + Game::Com_Printf(0, "USAGE: playrumble \n"); + } + } + }); + + GSC::Script::AddFunction("PlayRumbleOnPosition", Scr_PlayRumbleOnPosition, false, true); + GSC::Script::AddFunction("PlayRumbleLoopOnPosition", Scr_PlayRumbleLoopOnPosition, false, true); + + GSC::Script::AddMethod("PlayRumbleOnEntity", Scr_PlayRumbleOnEntity, false, true); + GSC::Script::AddMethod("PlayRumbleLoopOnEntity", Scr_PlayRumbleLoopOnEntity, false, true); + + // Debug + Scheduler::Loop([]() { + if (cl_debug_rumbles.get()) + { + DebugRumbles(); + } + }, Scheduler::Pipeline::RENDERER); + + } + +} diff --git a/src/Components/Modules/Rumble.hpp b/src/Components/Modules/Rumble.hpp new file mode 100644 index 00000000..50939844 --- /dev/null +++ b/src/Components/Modules/Rumble.hpp @@ -0,0 +1,96 @@ +#pragma once + +namespace Components +{ + class Rumble : public Component + { + + public: + static constexpr unsigned int MAX_ACTIVE_RUMBLES = 32; + + static Dvar::Var cl_debug_rumbles; + + Rumble(); + + private: + + enum rumble_entity_event_t + { + EV_PLAY_RUMBLE_ON_ENT = Game::EV_MAX_EVENTS + 1, + EV_PLAY_RUMBLE_ON_POS, + EV_PLAY_RUMBLELOOP_ON_ENT, + EV_PLAY_RUMBLELOOP_ON_POS, + EV_STOP_RUMBLE, + EV_STOP_ALL_RUMBLES, + }; + + static Dvar::Var cl_rumbleScale; + + static int GetRumbleInfoIndexFromName(const char* rumbleName); + static Game::ActiveRumble* GetDuplicateRumbleIfExists(Game::cg_s* cgameGlob, Game::ActiveRumble* arArray, Game::RumbleInfo* info, bool loop, Game::RumbleSourceType type, int entityNum, const float* pos); + static int FindClosestToDyingActiveRumble(Game::cg_s* cgameGlob, Game::ActiveRumble* activeRumbleArray); + static Game::ActiveRumble* NextAvailableRumble(Game::cg_s* cgameGlob, Game::ActiveRumble* arArray); + static void InvalidateActiveRumble(Game::ActiveRumble* ar); + static void CalcActiveRumbles(int localClientNum, Game::ActiveRumble* activeRumbleArray, const float* rumbleReceiverPos); + static void PlayRumbleInternal(int localClientNum, const char* rumbleName, bool loop, Game::RumbleSourceType type, int entityNum, const float* pos, double scale, bool updateDuplicates); + static void Rumble_Strcpy(char* member, char* keyValue); + static bool ParseRumbleGraph(Game::RumbleGraph* graph, const char* buffer, const char* fileName); + static void ReadRumbleGraph(Game::RumbleGraph* graph, const char* rumbleFileName); + static int LoadRumbleGraph(Game::RumbleGraph* rumbleGraphArray, Game::RumbleInfo* info, const char* highRumbleFileName, const char* lowRumbleFileName); + static void RegisterWeaponRumbles(Game::WeaponDef* weapDef); + static void RemoveInactiveRumbles(int localClientNum, Game::ActiveRumble* activeRumbleArray); + + static int G_RumbleIndex(const char* name); + + static void DebugRumbles(); + static void LoadConstantRumbleConfigStrings(); + static void SV_InitGameProgs_Hk(int arg); + + static Game::WeaponDef* BG_GetWeaponDef_RegisterRumble_Hk(unsigned int weapIndex); + + static void SCR_UpdateRumble(); + + static void Scr_PlayRumbleOnEntity(Game::scr_entref_t entref); + static void Scr_PlayRumbleLoopOnEntity(Game::scr_entref_t entref); + static void Scr_PlayRumbleOnEntity_Internal(Game::scr_entref_t entref, rumble_entity_event_t event); + static void Scr_PlayRumbleOnPosition(); + static void Scr_PlayRumbleLoopOnPosition(); + static void Scr_PlayRumbleOnPosition_Internal(rumble_entity_event_t); + + static void PlayNoteMappedRumbleAliases(int localClientNum, const char* noteName, Game::WeaponDef* weapDef); + static void PlayNoteMappedSoundAliases_Stub(); + + static void MeleeRumble_Hook(Game::gentity_s* targetEntity, Game::WeaponDef* weaponDef); + static void MeleeRumble_Stub(); + + + static void CG_Turret_UpdateBarrelSpinRumble(int localClientNum, Game::centity_s* cent); + static void CG_UpdateRumble(int localClientNum); + static void CG_GetImpactEffectForWeapon_Hk(int localClientNum, const int sourceEntityNum, const int weaponIndex, const int surfType, const int impactFlags, Game::FxEffectDef** outFx, Game::snd_alias_list_t** outSnd); + static void CG_ExplosiveImpactOnShieldEvent(int localClientNum); + static void CG_ExplosiveSplashOnShieldEvent(int localClientNum, int weaponIndex); + static void CG_SetRumbleReceiver(); + static void CG_UpdateEntInfo_Hk(); + static void CG_FireWeapon_Rumble(int localClientNum, Game::entityState_s* ent, Game::WeaponDef* weaponDef, bool isPlayerView); + static void CG_FireWeapon_FireSoundHk(); + static int CG_LoadRumble(Game::RumbleGraph* rumbleGraphArray, Game::RumbleInfo* info, const char* rumbleName, int rumbleNameIndex); + static void CG_RegisterRumbles(int localClientNum); + static void CG_RegisterGraphics_Hk(int localClientNum, int b); + static void CG_PlayRumbleOnPosition(int localClientNum, const char* rumbleName, const float* pos); + static void CG_PlayRumbleOnEntity(int localClientNum, const char* rumbleName, int entityIndex); + static void CG_PlayRumbleLoopOnPosition(int localClientNum, const char* rumbleName, const float* pos); + static void CG_PlayRumbleLoopOnEntity(int localClientNum, const char* rumbleName, int entityIndex); + static void CG_PlayRumbleOnClient(int localClientNum, const char* rumbleName); + static void CG_PlayRumbleOnClientSafe(int localClientNum, const char* rumbleName); + static void CG_EntityEvents_Stub(); + static void CG_StopRumble(int localClientNum, int entityNum, const char* rumbleName); + static bool CG_EntityEvents_Hk(Game::centity_s* entity, rumble_entity_event_t event); + static void CG_StopAllRumbles(); + + static int CCS_GetChecksum_Hk(); + + static void InitDvars(); + + + }; +} diff --git a/src/Components/Modules/Weapon.cpp b/src/Components/Modules/Weapon.cpp index 0224edae..1a1d5025 100644 --- a/src/Components/Modules/Weapon.cpp +++ b/src/Components/Modules/Weapon.cpp @@ -12,6 +12,78 @@ namespace Components { if (auto* rawWeaponFile = Game::BG_LoadWeaponCompleteDefInternal("mp", name)) { + // Fix for rumbles being wrong in the raw files.. This should not happen normally + // But in effect, it happens very often, so we need to fix it to avoid rumble errors + { + if (rawWeaponFile->weapDef->notetrackRumbleMapKeys && + rawWeaponFile->weapDef->notetrackRumbleMapKeys[0] == rawWeaponFile->weapDef->notetrackSoundMapKeys[0]) + { + // This means it's wrong and the dump is bad (it gave sound names as rumble names, which is not possible) + // The zone has it right so let's take it from there + const auto zoneWeapon = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_WEAPON, name).weapon; + + if (zoneWeapon) + { + // Restore rumble from zone + std::memcpy(rawWeaponFile->weapDef->notetrackRumbleMapKeys, zoneWeapon->weapDef->notetrackRumbleMapKeys, 16 * sizeof(unsigned short)); + std::memcpy(rawWeaponFile->weapDef->notetrackRumbleMapValues, zoneWeapon->weapDef->notetrackRumbleMapValues, 16 * sizeof(unsigned short)); + + rawWeaponFile->weapDef->fireRumble = zoneWeapon->weapDef->fireRumble; + rawWeaponFile->weapDef->meleeImpactRumble = zoneWeapon->weapDef->meleeImpactRumble; + rawWeaponFile->weapDef->turretBarrelSpinRumble = zoneWeapon->weapDef->turretBarrelSpinRumble; + } + + + // Oh well. You'll have to fix your weapon file :) +#define VALIDATE(weap, rumble)\ + if (weap->weapDef->rumble && *weap->weapDef->rumble)\ + {\ + std::string path = std::format("rumble/{}", weap->weapDef->rumble);\ + const auto rawfile = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_RAWFILE, path.c_str());\ + if (rawfile.data == nullptr)\ + {\ + Logger::Warning(Game::CON_CHANNEL_FILES, "Weapon {} has INVALID RUMBLE {} '{}'! Remove or replace it.\n", weap->szInternalName, #rumble, weap->weapDef->rumble);\ + weap->weapDef->rumble = nullptr;\ + }\ + } + VALIDATE(rawWeaponFile, fireRumble); + VALIDATE(rawWeaponFile, meleeImpactRumble); + VALIDATE(rawWeaponFile, turretBarrelSpinRumble); + + for (auto i = 0; i < 16; ++i) + { + if (!rawWeaponFile->weapDef->notetrackRumbleMapKeys[i]) + break; + + auto noteTrackRumbleMap = rawWeaponFile->weapDef->notetrackRumbleMapValues; + if (noteTrackRumbleMap[i]) + { + auto str = Game::SL_ConvertToString(noteTrackRumbleMap[i]); + if (str && *str) + { + std::string path = std::format("rumble/{}", str); + const auto rawfile = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_RAWFILE, path.c_str()); + if (rawfile.data == nullptr || !strnlen(rawfile.rawfile->buffer, 1)) + { + // Void it and warn the user + noteTrackRumbleMap[i] = 0; + rawWeaponFile->weapDef->notetrackRumbleMapKeys[i] = 0; + + Logger::Warning( + Game::CON_CHANNEL_FILES, + "Weapon {} has INVALID RUMBLE {} notetrackRumbleMap #'{}'! Remove or replace it.\n", + rawWeaponFile->szInternalName, + str, + i + ); + } + } + } + } +#undef VALIDATE + } + } + return rawWeaponFile; } @@ -267,9 +339,9 @@ namespace Components push 0x59E349 retn - null: + null : mov al, 1 - ret + ret } } @@ -288,9 +360,9 @@ namespace Components push 0x48BB2D ret - null: + null : push 0x48BB1F // Exit function - ret + ret } } @@ -300,27 +372,27 @@ namespace Components __asm { - cmp dword ptr [esp + 0x8], 0x0 + cmp dword ptr[esp + 0x8], 0x0 jz touched push 0x56E82C ret - touched: - test dword ptr [edi + 0x2BC], PWF_DISABLE_WEAPON_PICKUP - jnz exit_func + touched : + test dword ptr[edi + 0x2BC], PWF_DISABLE_WEAPON_PICKUP + jnz exit_func - // Game code - test eax, eax - jz continue_func + // Game code + test eax, eax + jz continue_func - exit_func: + exit_func : xor eax, eax - ret + ret - continue_func: + continue_func : push 0x56E84C - ret + ret } } @@ -334,18 +406,18 @@ namespace Components push eax mov eax, BGWeaponOffHandFix - cmp byte ptr [eax + 0x10], 1 + cmp byte ptr[eax + 0x10], 1 pop eax jne safeReturn - mov dword ptr [esi + 0x34], 0 // playerState_s.grenadeTimeLeft + mov dword ptr[esi + 0x34], 0 // playerState_s.grenadeTimeLeft - safeReturn: + safeReturn: pop edi - pop esi - pop ebx - ret + pop esi + pop ebx + ret } } @@ -425,25 +497,25 @@ namespace Components void Weapon::AddScriptMethods() { GSC::Script::AddMethod("DisableWeaponPickup", [](const Game::scr_entref_t entref) - { - const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); + { + const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); - ent->client->ps.weapCommon.weapFlags |= Game::PWF_DISABLE_WEAPON_PICKUP; - }); + ent->client->ps.weapCommon.weapFlags |= Game::PWF_DISABLE_WEAPON_PICKUP; + }); GSC::Script::AddMethod("EnableWeaponPickup", [](const Game::scr_entref_t entref) - { - const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); + { + const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); - ent->client->ps.weapCommon.weapFlags &= ~Game::PWF_DISABLE_WEAPON_PICKUP; - }); + ent->client->ps.weapCommon.weapFlags &= ~Game::PWF_DISABLE_WEAPON_PICKUP; + }); // PlayerCmd_AreControlsFrozen GSC function from Black Ops 2 GSC::Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); - { - const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); - Game::Scr_AddBool((ent->client->flags & Game::CF_BIT_FROZEN) != 0); - }); + { + const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); + Game::Scr_AddBool((ent->client->flags & Game::CF_BIT_FROZEN) != 0); + }); GSC::Script::AddMethod("InitialWeaponRaise", PlayerCmd_InitialWeaponRaise); GSC::Script::AddMethod("FreezeControlsAllowLook", PlayerCmd_FreezeControlsAllowLook); diff --git a/src/Game/BothGames.cpp b/src/Game/BothGames.cpp index 023be38a..0bc8f142 100644 --- a/src/Game/BothGames.cpp +++ b/src/Game/BothGames.cpp @@ -10,6 +10,7 @@ namespace Game BG_GetEntityTypeName_t BG_GetEntityTypeName = BG_GetEntityTypeName_t(0x43A0E0); BG_IsWeaponValid_t BG_IsWeaponValid = BG_IsWeaponValid_t(0x415BA0); BG_GetEquippedWeaponIndex_t BG_GetEquippedWeaponIndex = BG_GetEquippedWeaponIndex_t(0x4D8BA0); + BG_GetViewModelWeaponIndex_t BG_GetViewModelWeaponIndex = BG_GetViewModelWeaponIndex_t(0x4BE4D0); BG_GetEquippedWeaponState_t BG_GetEquippedWeaponState = BG_GetEquippedWeaponState_t(0x4E79E0); BG_PlayerHasWeapon_t BG_PlayerHasWeapon = BG_PlayerHasWeapon_t(0x4AB530); BG_GetWeaponCompleteDef_t BG_GetWeaponCompleteDef = BG_GetWeaponCompleteDef_t(0x44CE00); diff --git a/src/Game/BothGames.hpp b/src/Game/BothGames.hpp index d8b6707d..264dddb2 100644 --- a/src/Game/BothGames.hpp +++ b/src/Game/BothGames.hpp @@ -26,6 +26,9 @@ namespace Game typedef int(*BG_GetEquippedWeaponIndex_t)(const playerState_s* ps, unsigned int weaponIndex); extern BG_GetEquippedWeaponIndex_t BG_GetEquippedWeaponIndex; + typedef int(*BG_GetViewModelWeaponIndex_t)(const playerState_s* ps); + extern BG_GetViewModelWeaponIndex_t BG_GetViewModelWeaponIndex; + typedef PlayerEquippedWeaponState*(*BG_GetEquippedWeaponState_t)(playerState_s* ps, unsigned int weaponIndex); extern BG_GetEquippedWeaponState_t BG_GetEquippedWeaponState; diff --git a/src/Game/Client.cpp b/src/Game/Client.cpp index de491e1d..b28388f9 100644 --- a/src/Game/Client.cpp +++ b/src/Game/Client.cpp @@ -39,6 +39,11 @@ namespace Game clientActive_t* clients = reinterpret_cast(0xB2C698); + cg_s* cgArray = reinterpret_cast(0x7F0F78); + cgs_t* cgsArray = reinterpret_cast(0x7ED3B8); + cgEntity_s* cg_entitiesArray = reinterpret_cast(0x8F3CA8); + dvar_t** cl_paused = reinterpret_cast(0x9FBE4C); + voiceCommunication_t* cl_voiceCommunication = reinterpret_cast(0x1079DA0); int CL_GetMaxXP() @@ -77,11 +82,19 @@ namespace Game return &clientUIActives[localClientNum]; } - clientActive_t* CL_GetLocalClientGlobals(const int localClientNum) + cg_s* CL_GetLocalClientGlobals(const int localClientNum) { AssertIn(localClientNum, MAX_LOCAL_CLIENTS); assert(clients[localClientNum].alwaysFalse == false); - return &clients[localClientNum]; + return &cgArray[localClientNum]; + } + + centity_s* CG_GetEntity([[maybe_unused]] int localClientNum, int entityIndex) + { + assert(entityIndex < MAX_GENTITIES); + assert(localClientNum == 0); + + return &cg_entitiesArray[entityIndex].entity; } void CL_AddDebugStar(const float* point, const float* color, int duration, int fromServer) diff --git a/src/Game/Client.hpp b/src/Game/Client.hpp index 1b31d72a..5bc8c4d6 100644 --- a/src/Game/Client.hpp +++ b/src/Game/Client.hpp @@ -94,11 +94,18 @@ namespace Game extern [[nodiscard]] connstate_t CL_GetLocalClientConnectionState(int localClientNum); extern [[nodiscard]] voiceCommunication_t* CL_GetLocalClientVoiceCommunication(int localClientNum); extern [[nodiscard]] clientUIActive_t* CL_GetLocalClientUIGlobals(int localClientNum); - extern [[nodiscard]] clientActive_t* CL_GetLocalClientGlobals(int localClientNum); + extern [[nodiscard]] cg_s* CL_GetLocalClientGlobals(int localClientNum); + extern [[nodiscard]] centity_s* CG_GetEntity(int localClientNum, int entityIndex); + + extern cg_s* cgArray; + extern cgs_t* cgsArray; + extern cgEntity_s* cg_entitiesArray; + extern dvar_t** cl_paused; extern void CL_AddDebugStar(const float* point, const float* color, int duration, int fromServer); extern void CL_MouseMove(int localClientNum, Game::usercmd_s* cmd, float frametime_base); extern void AdjustViewanglesForKeyboard(int localClientNum); + } diff --git a/src/Game/Common.cpp b/src/Game/Common.cpp index 4f92c9d6..209dcb3e 100644 --- a/src/Game/Common.cpp +++ b/src/Game/Common.cpp @@ -26,6 +26,7 @@ namespace Game Com_OpenLogFile_t Com_OpenLogFile = Com_OpenLogFile_t(0x60A8D0); Com_UpdateSlowMotion_t Com_UpdateSlowMotion = Com_UpdateSlowMotion_t(0x60B2D0); Com_Compress_t Com_Compress = Com_Compress_t(0x498220); + Com_LoadInfoString_t Com_LoadInfoString = Com_LoadInfoString_t(0x463500); int* com_frameTime = reinterpret_cast(0x1AD8F3C); diff --git a/src/Game/Common.hpp b/src/Game/Common.hpp index 22cc1449..482f53c1 100644 --- a/src/Game/Common.hpp +++ b/src/Game/Common.hpp @@ -71,6 +71,9 @@ namespace Game typedef int(*Com_Compress_t)(char* data_p); extern Com_Compress_t Com_Compress; + typedef char* (*Com_LoadInfoString_t)(const char* fileName, const char* fileDesc, const char* ident, char* loadBuffer); + extern Com_LoadInfoString_t Com_LoadInfoString; + extern int* com_frameTime; extern int* com_fixedConsolePosition; diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index d9260926..02c7e1c6 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -194,6 +194,7 @@ namespace Game UI_ReplaceConversions_t UI_ReplaceConversions = UI_ReplaceConversions_t(0x4E9740); UI_ParseInfos_t UI_ParseInfos = UI_ParseInfos_t(0x4027A0); UI_GetMapDisplayName_t UI_GetMapDisplayName = UI_GetMapDisplayName_t(0x420700); + ParseConfigStringToStruct_t ParseConfigStringToStruct = ParseConfigStringToStruct_t(0x403B60); Win_GetLanguage_t Win_GetLanguage = Win_GetLanguage_t(0x45CBA0); @@ -339,9 +340,6 @@ namespace Game ScreenPlacement* scrPlaceFullUnsafe = reinterpret_cast(0x1084460); ScreenPlacement* scrPlaceView = reinterpret_cast(0x1084378); - cg_s* cgArray = reinterpret_cast(0x7F0F78); - cgs_t* cgsArray = reinterpret_cast(0x7ED3B8); - PlayerKeyState* playerKeys = reinterpret_cast(0xA1B7D0); kbutton_t* playersKb = reinterpret_cast(0xA1A9A8); AimAssistGlobals* aaGlobArray = reinterpret_cast(0x7A2110); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index a9507e2c..084ff658 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -220,7 +220,10 @@ namespace Game typedef const char*(*UI_GetMapDisplayName_t)(const char* pszMap); extern UI_GetMapDisplayName_t UI_GetMapDisplayName; - + + typedef int(*ParseConfigStringToStruct_t)(void* pStruct, const Game::cspField_t* pFieldList, const int iNumFields, const char* pszBuffer, const int iMaxFieldTypes, int(__cdecl* parseSpecialFieldType)(char*, const char*, const int), void(__cdecl* parseStrcpy)(char*, char*)); + extern ParseConfigStringToStruct_t ParseConfigStringToStruct; + typedef void(*MSG_Init_t)(msg_t* buf, unsigned char* data, int length); extern MSG_Init_t MSG_Init; diff --git a/src/Game/Game.cpp b/src/Game/Game.cpp index 0b90782c..b55fd759 100644 --- a/src/Game/Game.cpp +++ b/src/Game/Game.cpp @@ -8,6 +8,8 @@ namespace Game G_Spawn_t G_Spawn = G_Spawn_t(0x4226F0); G_FreeEntity_t G_FreeEntity = G_FreeEntity_t(0x44C9D0); G_SpawnItem_t G_SpawnItem = G_SpawnItem_t(0x403770); + G_TempEntity_t G_TempEntity = G_TempEntity_t(0x4511F0); + G_AddEvent_t G_AddEvent = G_AddEvent_t(0x469A50); G_GetItemClassname_t G_GetItemClassname = G_GetItemClassname_t(0x492630); G_PrintEntities_t G_PrintEntities = G_PrintEntities_t(0x4E6A50); G_GetEntityTypeName_t G_GetEntityTypeName = G_GetEntityTypeName_t(0x4EB810); diff --git a/src/Game/Game.hpp b/src/Game/Game.hpp index 12b3fe9c..2da90278 100644 --- a/src/Game/Game.hpp +++ b/src/Game/Game.hpp @@ -27,6 +27,14 @@ namespace Game typedef gentity_s* (*G_Spawn_t)(); extern G_Spawn_t G_Spawn; + + typedef gentity_s* (*G_TempEntity_t)(float *origin, Game::entity_event_t entity); + extern G_TempEntity_t G_TempEntity; + + typedef void (*G_AddEvent_t)(gentity_s *ent, entity_event_t event, unsigned int eventParm); + extern G_AddEvent_t G_AddEvent; + + typedef void(*G_FreeEntity_t)(gentity_s* ed); extern G_FreeEntity_t G_FreeEntity; diff --git a/src/Game/Script.cpp b/src/Game/Script.cpp index 1f1a62d0..c7483ef9 100644 --- a/src/Game/Script.cpp +++ b/src/Game/Script.cpp @@ -31,6 +31,7 @@ namespace Game Scr_GetConstString_t Scr_GetConstString = Scr_GetConstString_t(0x494830); Scr_GetDebugString_t Scr_GetDebugString = Scr_GetDebugString_t(0x4EBF50); Scr_GetFloat_t Scr_GetFloat = Scr_GetFloat_t(0x443140); + Scr_GetVector_t Scr_GetVector = Scr_GetVector_t(0x411560); Scr_GetInt_t Scr_GetInt = Scr_GetInt_t(0x4F31D0); Scr_GetObject_t Scr_GetObject = Scr_GetObject_t(0x462100); Scr_GetTypeName_t Scr_GetTypeName = Scr_GetTypeName_t(0x4EFF10); diff --git a/src/Game/Script.hpp b/src/Game/Script.hpp index 64182e73..d9dff039 100644 --- a/src/Game/Script.hpp +++ b/src/Game/Script.hpp @@ -100,6 +100,9 @@ namespace Game typedef float(*Scr_GetFloat_t)(unsigned int index); extern Scr_GetFloat_t Scr_GetFloat; + + typedef float(*Scr_GetVector_t)(unsigned int index, float* destination); + extern Scr_GetVector_t Scr_GetVector; typedef int(*Scr_GetInt_t)(unsigned int index); extern Scr_GetInt_t Scr_GetInt; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index ffd8d362..ff29ad28 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -534,6 +534,8 @@ namespace Game CS_WEAPONFILES = 0xAF5, // 2805 Confirmed CS_WEAPONFILES_LAST = 0xFA3, // Confirmed too // 4003 CS_ITEMS = 4138, // Correct! CS_ITEMS is actually an item **COUNT** + + CS_LAST = CS_ITEMS, MAX_CONFIGSTRINGS = 4139 }; // Incomplete @@ -654,6 +656,13 @@ namespace Game SV_CMD_RELIABLE = 0x1, }; + enum RumbleSourceType + { + RUMBLESOURCE_INVALID = 0x0, + RUMBLESOURCE_ENTITY = 0x1, + RUMBLESOURCE_POS = 0x2, + }; + struct FxEffectDef; struct pathnode_t; struct pathnode_tree_t; @@ -662,6 +671,13 @@ namespace Game struct MenuEventHandlerSet; struct menuDef_t; + struct cspField_t + { + const char* szName; + int iOffset; + int iFieldType; + }; + struct CmdArgs { int nesting; @@ -2031,12 +2047,33 @@ namespace Game float trDelta[3]; }; + struct LerpEntityStateTurret + { + float gunAngles[3]; + int lastBarrelRotChangeTime; + int lastBarrelRotChangeRate; + int lastHeatChangeLevel; + int lastHeatChangeTime; + bool isBarrelRotating; + bool isOverheat; + bool isHeatingUp; + bool isBeingCarried; + }; + + union LerpEntityStateTypeUnion + { + LerpEntityStateTurret turret; + char pad0[0x24]; + }; + + static_assert(sizeof(LerpEntityStateTypeUnion) == 0x24); + struct LerpEntityState { int eFlags; trajectory_t pos; trajectory_t apos; - char pad0[0x24]; + LerpEntityStateTypeUnion u; }; static_assert(sizeof(LerpEntityState) == 0x70); @@ -6144,7 +6181,7 @@ namespace Game unsigned int hashSize; fileInIwd_s** hashTable; fileInIwd_s* buildBuffer; - }; +}; #ifdef IDA typedef void _iobuf; @@ -9150,7 +9187,10 @@ namespace Game cpose_t pose; LerpEntityState prevState; entityState_s nextState; - int flags; + char nextValid; + char bMuzzleFlash; + char bTrailMade; + char pad; unsigned __int8 tracerDrawRateCounter; unsigned __int8 weaponVisTestCounter; int previousEventSequence; @@ -9160,8 +9200,16 @@ namespace Game centity_s* updateDelayedNext; }; + // Unknown what it's real name is + struct cgEntity_s + { + centity_s entity; + int unkown; + }; + static_assert(sizeof(entityState_s) == 0x100); static_assert(sizeof(centity_s) == 0x200); + static_assert(sizeof(cgEntity_s) == 0x204); struct playerEntity_t { @@ -9784,6 +9832,7 @@ namespace Game unsigned char handler; }; + static_assert(sizeof(pmove_s) == 296); struct pml_t @@ -9814,6 +9863,130 @@ namespace Game PM_EFF_STANCE_COUNT = 4 }; + enum entity_event_t + { + EV_NONE = 0x0, + EV_FOLIAGE_SOUND = 0x1, + EV_STOP_WEAPON_SOUND = 0x2, + EV_SOUND_ALIAS = 0x3, + EV_SOUND_ALIAS_AS_MASTER = 0x4, + EV_STOPSOUNDS = 0x5, + EV_STANCE_FORCE_STAND = 0x6, + EV_STANCE_FORCE_CROUCH = 0x7, + EV_STANCE_FORCE_PRONE = 0x8, + EV_STANCE_INVALID = 0x9, + EV_ITEM_PICKUP = 0xA, + EV_AMMO_PICKUP = 0xB, + EV_NOAMMO = 0xC, + EV_EMPTYCLIP = 0xD, + EV_EMPTY_OFFHAND_PRIMARY = 0xE, + EV_EMPTY_OFFHAND_SECONDARY = 0xF, + EV_OFFHAND_END_NOTIFY = 0x10, + EV_RESET_ADS = 0x11, + EV_RELOAD = 0x12, + EV_RELOAD_FROM_EMPTY = 0x13, + EV_RELOAD_START = 0x14, + EV_RELOAD_END = 0x15, + EV_RELOAD_START_NOTIFY = 0x16, + EV_RELOAD_ADDAMMO = 0x17, + EV_RAISE_WEAPON = 0x18, + EV_FIRST_RAISE_WEAPON = 0x19, + EV_PUTAWAY_WEAPON = 0x1A, + EV_WEAPON_ALT = 0x1B, + EV_WEAPON_SWITCH_STARTED = 0x1C, + EV_PULLBACK_WEAPON = 0x1D, + EV_FIRE_WEAPON = 0x1E, + EV_FIRE_WEAPON_LASTSHOT = 0x1F, + EV_FIRE_RICOCHET = 0x20, + EV_RECHAMBER_WEAPON = 0x21, + EV_EJECT_BRASS = 0x22, + EV_FIRE_WEAPON_LEFT = 0x23, + EV_FIRE_WEAPON_LASTSHOT_LEFT = 0x24, + EV_EJECT_BRASS_LEFT = 0x25, + EV_HITCLIENT_FIRE_WEAPON = 0x26, + EV_HITCLIENT_FIRE_WEAPON_LASTSHOT = 0x27, + EV_HITCLIENT_FIRE_WEAPON_LEFT = 0x28, + EV_HITCLIENT_FIRE_WEAPON_LASTSHOT_LEFT = 0x29, + EV_SV_FIRE_WEAPON = 0x2A, + EV_SV_FIRE_WEAPON_LASTSHOT = 0x2B, + EV_SV_FIRE_WEAPON_LEFT = 0x2C, + EV_SV_FIRE_WEAPON_LASTSHOT_LEFT = 0x2D, + EV_MELEE_SWIPE = 0x2E, + EV_FIRE_MELEE = 0x2F, + EV_PREP_OFFHAND = 0x30, + EV_USE_OFFHAND = 0x31, + EV_SWITCH_OFFHAND = 0x32, + EV_MELEE_HIT = 0x33, + EV_MELEE_MISS = 0x34, + EV_MELEE_BLOOD = 0x35, + EV_FIRE_TURRET = 0x36, + EV_FIRE_SENTRY = 0x37, + EV_FIRE_QUADBARREL_1 = 0x38, + EV_FIRE_QUADBARREL_2 = 0x39, + EV_BULLET_HIT = 0x3A, + EV_BULLET_HIT_EXPLODE = 0x3B, + EV_BULLET_HIT_SHIELD = 0x3C, + EV_BULLET_HIT_CLIENT_SMALL = 0x3D, + EV_BULLET_HIT_CLIENT_LARGE = 0x3E, + EV_BULLET_HIT_CLIENT_EXPLODE = 0x3F, + EV_BULLET_HIT_CLIENT_SHIELD = 0x40, + EV_EXPLOSIVE_IMPACT_ON_SHIELD = 0x41, + EV_EXPLOSIVE_SPLASH_ON_SHIELD = 0x42, + EV_GRENADE_BOUNCE = 0x43, + EV_GRENADE_STICK = 0x44, + EV_GRENADE_REST = 0x45, + EV_GRENADE_EXPLODE = 0x46, + EV_GRENADE_PICKUP = 0x47, + EV_GRENADE_LETGO = 0x48, + EV_ROCKET_EXPLODE = 0x49, + EV_ROCKET_EXPLODE_NOMARKS = 0x4A, + EV_FLASHBANG_EXPLODE = 0x4B, + EV_CUSTOM_EXPLODE = 0x4C, + EV_CUSTOM_EXPLODE_NOMARKS = 0x4D, + EV_CHANGE_TO_DUD = 0x4E, + EV_DUD_EXPLODE = 0x4F, + EV_DUD_IMPACT = 0x50, + EV_TROPHY_EXPLODE = 0x51, + EV_BULLET = 0x52, + EV_PLAY_FX = 0x53, + EV_PLAY_FX_ON_TAG = 0x54, + EV_STOP_FX_ON_TAG = 0x55, + EV_PLAY_FX_ON_TAG_FOR_CLIENTS = 0x56, + EV_PHYS_EXPLOSION_SPHERE = 0x57, + EV_PHYS_EXPLOSION_CYLINDER = 0x58, + EV_PHYS_EXPLOSION_JOLT = 0x59, + EV_RADIUSDAMAGE = 0x5A, + EV_PHYS_JITTER = 0x5B, + EV_EARTHQUAKE = 0x5C, + EV_GRENADE_SUICIDE = 0x5D, + EV_DETONATE = 0x5E, + EV_NIGHTVISION_WEAR = 0x5F, + EV_NIGHTVISION_REMOVE = 0x60, + EV_MISSILE_REMOTE_BOOST = 0x61, + EV_OBITUARY = 0x62, + EV_NO_PRIMARY_GRENADE_HINT = 0x63, + EV_NO_SECONDARY_GRENADE_HINT = 0x64, + EV_TARGET_TOO_CLOSE_HINT = 0x65, + EV_TARGET_NOT_ENOUGH_CLEARANCE_HINT = 0x66, + EV_LOCKON_REQUIRED_HINT = 0x67, + EV_VEHICLE_COLLISION = 0x68, + EV_VEHICLE_SUSPENSION_SOFT = 0x69, + EV_VEHICLE_SUSPENSION_HARD = 0x6A, + EV_FOOTSTEP_SPRINT = 0x6B, + EV_FOOTSTEP_RUN = 0x6C, + EV_FOOTSTEP_WALK = 0x6D, + EV_FOOTSTEP_PRONE = 0x6E, + EV_JUMP = 0x6F, + EV_LANDING_FIRST = 0x70, + EV_LANDING_LAST = 0x8E, + EV_LANDING_PAIN_FIRST = 0x8F, + EV_LANDING_PAIN_LAST = 0xAD, + EV_MANTLE = 0xAE, + EV_DEBUG_SERVER_AIMING = 0xAF, + EV_MAX_EVENTS = 0xB0, + }; + + struct TempPriority { void* threadHandle; @@ -11047,6 +11220,87 @@ namespace Game GfxLight sceneLights[253]; }; + + struct RumbleDevguiGraphInfo + { + struct RumbleInfo* rumbleInfo; + struct RumbleGraph* rumbleGraph; + }; + + enum DevEventType + { + EVENT_ACTIVATE = 0x0, + EVENT_DEACTIVATE = 0x1, + EVENT_ACCEPT = 0x2, + EVENT_UPDATE = 0x3, + EVENT_DRAW = 0x4, + EVENT_SAVE = 0x5, + }; + + struct __declspec(align(4)) DevGraph + { + float(*knots)[2]; + unsigned __int16* knotCount; + unsigned __int16 knotCountMax; + int selectedKnot; + void(__cdecl* eventCallback)(DevGraph*, DevEventType, int); + void(__cdecl* textCallback)(DevGraph*, const float, const float, char*, const int); + void* data; + bool disableEditingEndPoints; + }; + + struct RumbleGraph + { + char graphName[64]; + float knots[16][2]; + unsigned __int16 knotCount; + DevGraph devguiGraph; + RumbleDevguiGraphInfo devguiGraphInfo; + }; + + static_assert(sizeof(RumbleGraph) == 236); + + struct RumbleInfo + { + int rumbleNameIndex; + float duration; + float range; + RumbleGraph* highRumbleGraph; + RumbleGraph* lowRumbleGraph; + int fadeWithDistance; + int broadcast; + dvar_t* durationDvar; + dvar_t* loopDvar; + }; + + static_assert(sizeof(RumbleInfo) == 36); + + + union RumbleSource + { + int entityNum; + float pos[3]; + }; + + struct ActiveRumble + { + RumbleInfo* rumbleInfo; + int startTime; + bool loop; + RumbleSourceType sourceType; + unsigned char scale; + RumbleSource source; + }; + + struct RumbleGlobals + { + RumbleGraph graphs[64]; + RumbleInfo infos[32]; + ActiveRumble activeRumbles[32]; + float receiverPos[3]; + int receiverEntNum; + }; + struct GfxSModelCachePageUsage { unsigned int pageCount; diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 2a80464b..bfbf7df6 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -59,6 +59,13 @@ #include #pragma comment (lib, "iphlpapi.lib") +#include +#pragma comment(lib, "Setupapi.lib") + +#include +#pragma comment(lib, "Hid.lib") + + // Ignore the warnings #pragma warning(push) #pragma warning(disable: 4100)