diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index c6b86290..3b2ae383 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -102,6 +102,7 @@ namespace Components Loader::Register(new ConnectProtocol()); Loader::Register(new StartupMessages()); Loader::Register(new SoundMutexFix()); + Loader::Register(new Gamepad()); Loader::Register(new Client()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 998e71e8..92f95774 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -132,4 +132,5 @@ namespace Components #include "Modules/Stats.hpp" #include "Modules/SoundMutexFix.hpp" -#include "Modules/Client.hpp" \ No newline at end of file +#include "Modules/Gamepad.hpp" +#include "Modules/Client.hpp" diff --git a/src/Components/Modules/Colors.cpp b/src/Components/Modules/Colors.cpp index e13f914b..ef600954 100644 --- a/src/Components/Modules/Colors.cpp +++ b/src/Components/Modules/Colors.cpp @@ -2,6 +2,7 @@ namespace Components { + char Colors::LastColorIndex; Dvar::Var Colors::NewColors; Dvar::Var Colors::ColorBlind; Game::dvar_t* Colors::ColorAllyColorBlind; @@ -147,6 +148,8 @@ namespace Components Utils::Hook::Set(0x5A2E2E, limit); // No idea :P Utils::Hook::Set(0x5A2733, limit - '0'); // No idea :P + + LastColorIndex = limit; } char Colors::Add(uint8_t r, uint8_t g, uint8_t b) diff --git a/src/Components/Modules/Colors.hpp b/src/Components/Modules/Colors.hpp index be1f24de..bb783151 100644 --- a/src/Components/Modules/Colors.hpp +++ b/src/Components/Modules/Colors.hpp @@ -5,6 +5,8 @@ namespace Components class Colors : public Component { public: + static char LastColorIndex; + Colors(); ~Colors(); diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index f19cae05..35f36861 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -218,7 +218,7 @@ namespace Components { if (!Dedicated::IsEnabled() && Dvar::Var("sv_dontrotate").get()) { - Dvar::Var("sv_dontrotate").set(0); + Dvar::Var("sv_dontrotate").set(false); return; } diff --git a/src/Components/Modules/Dvar.cpp b/src/Components/Modules/Dvar.cpp index ad28175c..c83a1f7c 100644 --- a/src/Components/Modules/Dvar.cpp +++ b/src/Components/Modules/Dvar.cpp @@ -134,6 +134,36 @@ namespace Components } } + void Dvar::Var::setRaw(int integer) + { + assert(this->dvar->type == Game::DVAR_TYPE_INT); + if (this->dvar) + { + this->dvar->current.integer = integer; + this->dvar->latched.integer = integer; + } + } + + void Dvar::Var::setRaw(float value) + { + assert(this->dvar->type == Game::DVAR_TYPE_FLOAT); + if (this->dvar) + { + this->dvar->current.value = value; + this->dvar->latched.value = value; + } + } + + void Dvar::Var::setRaw(bool enabled) + { + assert(this->dvar->type == Game::DVAR_TYPE_BOOL); + if (this->dvar) + { + this->dvar->current.enabled = enabled; + this->dvar->latched.enabled = enabled; + } + } + template<> static Dvar::Var Dvar::Register(const char* name, bool value, Dvar::Flag flag, const char* description) { return Game::Dvar_RegisterBool(name, value, flag.val, description); diff --git a/src/Components/Modules/Dvar.hpp b/src/Components/Modules/Dvar.hpp index 47cf8102..33f291fb 100644 --- a/src/Components/Modules/Dvar.hpp +++ b/src/Components/Modules/Dvar.hpp @@ -33,6 +33,10 @@ namespace Components void set(float value); void set(bool enabled); + void setRaw(int integer); + void setRaw(float value); + void setRaw(bool enabled); + private: Game::dvar_t* dvar; }; diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp new file mode 100644 index 00000000..5ba7a66b --- /dev/null +++ b/src/Components/Modules/Gamepad.cpp @@ -0,0 +1,1911 @@ +#include "STDInclude.hpp" + +namespace Components +{ + Game::ButtonToCodeMap_t Gamepad::buttonList[] + { + {Game::GPAD_X, Game::K_BUTTON_X}, + {Game::GPAD_A, Game::K_BUTTON_A}, + {Game::GPAD_B, Game::K_BUTTON_B}, + {Game::GPAD_Y, Game::K_BUTTON_Y}, + {Game::GPAD_L_TRIG, Game::K_BUTTON_LTRIG}, + {Game::GPAD_R_TRIG, Game::K_BUTTON_RTRIG}, + {Game::GPAD_L_SHLDR, Game::K_BUTTON_LSHLDR}, + {Game::GPAD_R_SHLDR, Game::K_BUTTON_RSHLDR}, + {Game::GPAD_START, Game::K_BUTTON_START}, + {Game::GPAD_BACK, Game::K_BUTTON_BACK}, + {Game::GPAD_L3, Game::K_BUTTON_LSTICK}, + {Game::GPAD_R3, Game::K_BUTTON_RSTICK}, + {Game::GPAD_UP, Game::K_DPAD_UP}, + {Game::GPAD_DOWN, Game::K_DPAD_DOWN}, + {Game::GPAD_LEFT, Game::K_DPAD_LEFT}, + {Game::GPAD_RIGHT, Game::K_DPAD_RIGHT} + }; + + Game::StickToCodeMap_t Gamepad::analogStickList[4] + { + {Game::GPAD_LX, Game::K_APAD_RIGHT, Game::K_APAD_LEFT}, + {Game::GPAD_LY, Game::K_APAD_UP, Game::K_APAD_DOWN}, + {Game::GPAD_RX, Game::K_APAD_RIGHT, Game::K_APAD_LEFT}, + {Game::GPAD_RY, Game::K_APAD_UP, Game::K_APAD_DOWN}, + }; + + Game::GamePadStick Gamepad::stickForAxis[Game::GPAD_PHYSAXIS_COUNT] + { + Game::GPAD_RX, + Game::GPAD_RY, + Game::GPAD_LX, + Game::GPAD_LY, + Game::GPAD_INVALID, + Game::GPAD_INVALID + }; + + Game::GamepadPhysicalAxis Gamepad::axisSameStick[Game::GPAD_PHYSAXIS_COUNT] + { + Game::GPAD_PHYSAXIS_RSTICK_Y, + Game::GPAD_PHYSAXIS_RSTICK_X, + Game::GPAD_PHYSAXIS_LSTICK_Y, + Game::GPAD_PHYSAXIS_LSTICK_X, + Game::GPAD_PHYSAXIS_NONE, + Game::GPAD_PHYSAXIS_NONE + }; + + const char* Gamepad::physicalAxisNames[Game::GPAD_PHYSAXIS_COUNT] + { + "A_RSTICK_X", + "A_RSTICK_Y", + "A_LSTICK_X", + "A_LSTICK_Y", + "A_RTRIGGER", + "A_LTRIGGER" + }; + + const char* Gamepad::virtualAxisNames[Game::GPAD_VIRTAXIS_COUNT] + { + "VA_SIDE", + "VA_FORWARD", + "VA_UP", + "VA_YAW", + "VA_PITCH", + "VA_ATTACK" + }; + + const char* Gamepad::gamePadMappingTypeNames[Game::GPAD_MAP_COUNT] + { + "MAP_LINEAR", + "MAP_SQUARED" + }; + + Game::keyNum_t Gamepad::menuScrollButtonList[] + { + Game::K_APAD_UP, + Game::K_APAD_DOWN, + Game::K_APAD_LEFT, + Game::K_APAD_RIGHT, + Game::K_DPAD_UP, + Game::K_DPAD_DOWN, + Game::K_DPAD_LEFT, + Game::K_DPAD_RIGHT + }; + + Game::keyname_t Gamepad::extendedKeyNames[] + { + {"BUTTON_A", Game::K_BUTTON_A}, + {"BUTTON_B", Game::K_BUTTON_B}, + {"BUTTON_X", Game::K_BUTTON_X}, + {"BUTTON_Y", Game::K_BUTTON_Y}, + {"BUTTON_LSHLDR", Game::K_BUTTON_LSHLDR}, + {"BUTTON_RSHLDR", Game::K_BUTTON_RSHLDR}, + {"BUTTON_START", Game::K_BUTTON_START}, + {"BUTTON_BACK", Game::K_BUTTON_BACK}, + {"BUTTON_LSTICK", Game::K_BUTTON_LSTICK}, + {"BUTTON_RSTICK", Game::K_BUTTON_RSTICK}, + {"BUTTON_LTRIG", Game::K_BUTTON_LTRIG}, + {"BUTTON_RTRIG", Game::K_BUTTON_RTRIG}, + {"DPAD_UP", Game::K_DPAD_UP}, + {"DPAD_DOWN", Game::K_DPAD_DOWN}, + {"DPAD_LEFT", Game::K_DPAD_LEFT}, + {"DPAD_RIGHT", Game::K_DPAD_RIGHT}, + }; + + Game::keyname_t Gamepad::extendedLocalizedKeyNames[] + { + // Material text icons pattern: 0x01 width height material_name_len + {"^\x01\x32\x32\x08""button_a", Game::K_BUTTON_A}, + {"^\x01\x32\x32\x08""button_b", Game::K_BUTTON_B}, + {"^\x01\x32\x32\x08""button_x", Game::K_BUTTON_X}, + {"^\x01\x32\x32\x08""button_y", Game::K_BUTTON_Y}, + {"^\x01\x32\x32\x0D""button_lshldr", Game::K_BUTTON_LSHLDR}, + {"^\x01\x32\x32\x0D""button_rshldr", Game::K_BUTTON_RSHLDR}, + {"^\x01\x32\x32\x0C""button_start", Game::K_BUTTON_START}, + {"^\x01\x32\x32\x0B""button_back", Game::K_BUTTON_BACK}, + {"^\x01\x48\x32\x0D""button_lstick", Game::K_BUTTON_LSTICK}, + {"^\x01\x48\x32\x0D""button_rstick", Game::K_BUTTON_RSTICK}, + {"^\x01\x32\x32\x0C""button_ltrig", Game::K_BUTTON_LTRIG}, + {"^\x01\x32\x32\x0C""button_rtrig", Game::K_BUTTON_RTRIG}, + {"^\x01\x32\x32\x07""dpad_up", Game::K_DPAD_UP}, + {"^\x01\x32\x32\x09""dpad_down", Game::K_DPAD_DOWN}, + {"^\x01\x32\x32\x09""dpad_left", Game::K_DPAD_LEFT}, + {"^\x01\x32\x32\x0A""dpad_right", Game::K_DPAD_RIGHT}, + }; + Game::keyname_t Gamepad::combinedKeyNames[Game::KEY_NAME_COUNT + std::extent_v + 1]; + Game::keyname_t Gamepad::combinedLocalizedKeyNames[Game::KEY_NAME_COUNT + std::extent_v + 1]; + + Gamepad::ControllerMenuKeyMapping Gamepad::controllerMenuKeyMappings[] + { + {Game::K_BUTTON_A, Game::K_ENTER}, + {Game::K_BUTTON_START, Game::K_ENTER}, + {Game::K_BUTTON_B, Game::K_ESCAPE}, + {Game::K_BUTTON_BACK, Game::K_ESCAPE}, + {Game::K_DPAD_UP, Game::K_UPARROW}, + {Game::K_APAD_UP, Game::K_UPARROW}, + {Game::K_DPAD_DOWN, Game::K_DOWNARROW}, + {Game::K_APAD_DOWN, Game::K_DOWNARROW}, + {Game::K_DPAD_LEFT, Game::K_LEFTARROW}, + {Game::K_APAD_LEFT, Game::K_LEFTARROW}, + {Game::K_DPAD_RIGHT, Game::K_RIGHTARROW}, + {Game::K_APAD_RIGHT, Game::K_RIGHTARROW}, + }; + + Gamepad::GamePad Gamepad::gamePads[Game::MAX_GAMEPADS]{}; + Gamepad::GamePadGlobals Gamepad::gamePadGlobals[Game::MAX_GAMEPADS]{{}}; + 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_sticksConfig; + Dvar::Var Gamepad::gpad_buttonConfig; + 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; + Dvar::Var Gamepad::input_viewSensitivity; + Dvar::Var Gamepad::input_invertPitch; + Dvar::Var Gamepad::sv_allowAimAssist; + Dvar::Var Gamepad::aim_turnrate_pitch; + Dvar::Var Gamepad::aim_turnrate_pitch_ads; + Dvar::Var Gamepad::aim_turnrate_yaw; + Dvar::Var Gamepad::aim_turnrate_yaw_ads; + Dvar::Var Gamepad::aim_accel_turnrate_enabled; + Dvar::Var Gamepad::aim_accel_turnrate_lerp; + Dvar::Var Gamepad::aim_input_graph_enabled; + Dvar::Var Gamepad::aim_input_graph_index; + Dvar::Var Gamepad::aim_scale_view_axis; + Dvar::Var Gamepad::cl_bypassMouseInput; + Dvar::Var Gamepad::cg_mapLocationSelectionCursorSpeed; + Dvar::Var Gamepad::aim_aimAssistRangeScale; + Dvar::Var Gamepad::aim_slowdown_enabled; + Dvar::Var Gamepad::aim_slowdown_debug; + Dvar::Var Gamepad::aim_slowdown_pitch_scale; + Dvar::Var Gamepad::aim_slowdown_pitch_scale_ads; + Dvar::Var Gamepad::aim_slowdown_yaw_scale; + Dvar::Var Gamepad::aim_slowdown_yaw_scale_ads; + Dvar::Var Gamepad::aim_lockon_enabled; + Dvar::Var Gamepad::aim_lockon_deflection; + Dvar::Var Gamepad::aim_lockon_pitch_strength; + Dvar::Var Gamepad::aim_lockon_strength; + + Gamepad::GamePadGlobals::GamePadGlobals() + : axes{}, + nextScrollTime(0) + { + for (auto& virtualAxis : axes.virtualAxes) + { + virtualAxis.physicalAxis = Game::GPAD_PHYSAXIS_NONE; + virtualAxis.mapType = Game::GPAD_MAP_NONE; + } + } + + __declspec(naked) void Gamepad::MSG_WriteDeltaUsercmdKeyStub() + { + __asm + { + // fix stack pointer + add esp, 0Ch + + // put both forward move and rightmove values in the movement button + mov dl, byte ptr[edi + 1Ah] // to_forwardMove + mov dh, byte ptr[edi + 1Bh] // to_rightMove + + mov[esp + 30h], dx // to_buttons + + mov dl, byte ptr[ebp + 1Ah] // from_forwardMove + mov dh, byte ptr[ebp + 1Bh] // from_rightMove + + mov[esp + 2Ch], dx // from_buttons + + // return back + push 0x60E40E + retn + } + } + + void Gamepad::ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to) + { + char forward; + char right; + + if (Game::MSG_ReadBit(msg)) + { + short movementBits = static_cast(key ^ Game::MSG_ReadBits(msg, 16)); + + forward = static_cast(movementBits); + right = static_cast(movementBits >> 8); + } + else + { + forward = from->forwardmove; + right = from->rightmove; + } + + to->forwardmove = forward; + to->rightmove = right; + } + + __declspec(naked) void Gamepad::MSG_ReadDeltaUsercmdKeyStub() + { + __asm + { + push ebx // to + push ebp // from + push edi // key + push esi // msg + call ApplyMovement + add esp, 10h + + // return back + push 0x4921BF + ret + } + } + + __declspec(naked) void Gamepad::MSG_ReadDeltaUsercmdKeyStub2() + { + __asm + { + push ebx // to + push ebp // from + push edi // key + push esi // msg + call ApplyMovement + add esp, 10h + + // return back + push 3 + push esi + push 0x492085 + ret + } + } + + bool Gamepad::GPad_Check(const int gamePadIndex, const int portIndex) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + if (XInputGetCapabilities(portIndex, XINPUT_FLAG_GAMEPAD, &gamePad.caps) == ERROR_SUCCESS) + { + gamePad.enabled = true; + gamePad.portIndex = portIndex; + return true; + } + + gamePad.enabled = false; + return false; + } + + void Gamepad::GPad_RefreshAll() + { + auto currentGamePadNum = 0; + + for (auto currentPort = 0; currentPort < XUSER_MAX_COUNT && currentGamePadNum < Game::MAX_GAMEPADS; currentPort++) + { + if (GPad_Check(currentGamePadNum, currentPort)) + currentGamePadNum++; + } + } + + float Gamepad::LinearTrack(const float target, const float current, const float rate, const float deltaTime) + { + const auto err = target - current; + float step; + if (err <= 0.0f) + step = -rate * deltaTime; + else + step = rate * deltaTime; + + if (std::fabs(err) <= 0.001f) + return target; + + if (std::fabs(step) <= std::fabs(err)) + return current + step; + + return target; + } + + bool Gamepad::AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, const float clipHalfWidth, const float clipHalfHeight) + { + return clipHalfWidth >= clipMins[0] && clipMaxs[0] >= -clipHalfWidth + && clipHalfHeight >= clipMins[1] && clipMaxs[1] >= -clipHalfHeight; + } + + bool Gamepad::AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps) + { + // Check offhand flag + if ((ps->weapFlags & 2) == 0) + return false; + + // If offhand weapon has no id we are not using one + if (!ps->weapIndex) + return false; + + const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); + + return weaponDef->offhandClass != Game::OFFHAND_CLASS_NONE; + } + + const Game::AimScreenTarget* Gamepad::AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight) + { + const auto rangeSqr = range * range; + for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) + { + const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; + if (currentTarget->distSqr <= rangeSqr && AimAssist_DoBoundsIntersectCenterBox(currentTarget->clipMins, currentTarget->clipMaxs, regionWidth, regionHeight)) + { + return currentTarget; + } + } + + return nullptr; + } + + const Game::AimScreenTarget* Gamepad::AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, const int entIndex) + { + if (entIndex == Game::AIM_TARGET_INVALID) + return nullptr; + + for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) + { + const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; + if (currentTarget->entIndex == entIndex) + return currentTarget; + } + + return nullptr; + } + + const Game::AimScreenTarget* Gamepad::AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight, + const int prevTargetEnt) + { + const auto screenTarget = AimAssist_GetTargetFromEntity(aaGlob, prevTargetEnt); + + if (screenTarget && (range * range) > screenTarget->distSqr && AimAssist_DoBoundsIntersectCenterBox(screenTarget->clipMins, screenTarget->clipMaxs, regionWidth, regionHeight)) + return screenTarget; + + return AimAssist_GetBestTarget(aaGlob, range, regionWidth, regionHeight); + } + + bool Gamepad::AimAssist_IsLockonActive(const int gamePadIndex) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[gamePadIndex]; + + if (!aim_lockon_enabled.get() || !gpad_lockon_enabled.get()) + return false; + + if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) + return false; + + if (aaGlob.autoAimActive || aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) + return false; + + return true; + } + + void Gamepad::AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output) + { + assert(input); + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + + const auto prevTargetEnt = aaGlob.lockOnTargetEnt; + aaGlob.lockOnTargetEnt = Game::AIM_TARGET_INVALID; + + if (!AimAssist_IsLockonActive(input->localClientNum)) + return; + + const auto* weaponDef = Game::BG_GetWeaponDef(aaGlob.ps.weapIndex); + if (weaponDef->requireLockonToFire) + return; + + const auto deflection = aim_lockon_deflection.get(); + if (deflection > std::fabs(input->pitchAxis) && deflection > std::fabs(input->yawAxis) && deflection > std::fabs(input->rightAxis)) + return; + + if (!aaGlob.ps.weapIndex) + return; + + const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); + const auto screenTarget = AimAssist_GetPrevOrBestTarget(&aaGlob, aimAssistRange, aaGlob.tweakables.lockOnRegionWidth, aaGlob.tweakables.lockOnRegionHeight, prevTargetEnt); + + if (screenTarget && screenTarget->distSqr > 0.0f) + { + aaGlob.lockOnTargetEnt = screenTarget->entIndex; + const auto arcLength = std::sqrt(screenTarget->distSqr) * static_cast(M_PI); + + const auto pitchTurnRate = + (screenTarget->velocity[0] * aaGlob.viewAxis[2][0] + screenTarget->velocity[1] * aaGlob.viewAxis[2][1] + screenTarget->velocity[2] * aaGlob.viewAxis[2][2] + - (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 = + (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(); + + output->pitch -= pitchTurnRate * input->deltaTime; + output->yaw += yawTurnRate * input->deltaTime; + } + } + + void Gamepad::AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis) + { + assert(input); + assert(pitchAxis); + assert(yawAxis); + + const auto graphIndex = aim_input_graph_index.get(); + if (aim_input_graph_enabled.get() && graphIndex >= 0 && static_cast(graphIndex) < Game::AIM_ASSIST_GRAPH_COUNT) + { + const auto deflection = std::sqrt(input->pitchAxis * input->pitchAxis + input->yawAxis * input->yawAxis); + + float fraction; + if (deflection - 1.0f < 0.0f) + fraction = deflection; + else + fraction = 1.0f; + + if (0.0f - deflection >= 0.0f) + fraction = 0.0f; + + const auto graphScale = Game::GraphFloat_GetValue(&Game::aaInputGraph[graphIndex], fraction); + *pitchAxis = input->pitchAxis * graphScale; + *yawAxis = input->yawAxis * graphScale; + } + else + { + *pitchAxis = input->pitchAxis; + *yawAxis = input->yawAxis; + } + + if (aim_scale_view_axis.get()) + { + const auto absPitchAxis = std::fabs(*pitchAxis); + const auto absYawAxis = std::fabs(*yawAxis); + + if (absPitchAxis <= absYawAxis) + *pitchAxis = (1.0f - (absYawAxis - absPitchAxis)) * *pitchAxis; + else + *yawAxis = (1.0f - (absPitchAxis - absYawAxis)) * *yawAxis; + } + } + + bool Gamepad::AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps) + { + if (!aim_slowdown_enabled.get() || !gpad_slowdown_enabled.get()) + return false; + + if (!ps->weapIndex) + return false; + + const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); + if (weaponDef->requireLockonToFire) + return false; + + if (ps->linkFlags & 4) + return false; + + if (ps->weaponState >= Game::WEAPON_STUNNED_START && ps->weaponState <= Game::WEAPON_STUNNED_END) + return false; + + // The game checks for these flags. Their meaning is to be researched if necessary. + if (ps->eFlags & 0x100C00) + return false; + + if (!ps->hasAmmo) + return false; + + return true; + } + + void Gamepad::AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale) + { + assert(input); + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + assert(pitchScale); + assert(yawScale); + + *pitchScale = 1.0f; + *yawScale = 1.0f; + + if (!AimAssist_IsSlowdownActive(&aaGlob.ps)) + return; + + const auto* weaponDef = Game::BG_GetWeaponDef(aaGlob.ps.weapIndex); + const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); + const auto screenTarget = AimAssist_GetBestTarget(&aaGlob, aimAssistRange, aaGlob.tweakables.slowdownRegionWidth, aaGlob.tweakables.slowdownRegionHeight); + + if (screenTarget) + { + *pitchScale = AimAssist_Lerp(aim_slowdown_pitch_scale.get(), aim_slowdown_pitch_scale_ads.get(), aaGlob.adsLerp); + *yawScale = AimAssist_Lerp(aim_slowdown_yaw_scale.get(), aim_slowdown_yaw_scale_ads.get(), aaGlob.adsLerp); + } + + if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) + *pitchScale = 1.0f; + } + + float Gamepad::AimAssist_Lerp(const float from, const float to, const float fraction) + { + return (to - from) * fraction + from; + } + + void Gamepad::AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output) + { + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + + auto slowdownPitchScale = 0.0f; + auto slowdownYawScale = 0.0f; + float adjustedPitchAxis; + float adjustedYawAxis; + + if (aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) + { + adjustedPitchAxis = 0.0f; + adjustedYawAxis = 0.0f; + slowdownPitchScale = 1.0f; + slowdownYawScale = 1.0f; + } + else + { + AimAssist_CalcAdjustedAxis(input, &adjustedPitchAxis, &adjustedYawAxis); + AimAssist_CalcSlowdown(input, &slowdownPitchScale, &slowdownYawScale); + } + + const auto sensitivity = input_viewSensitivity.get(); + auto pitchTurnRate = AimAssist_Lerp(aim_turnrate_pitch.get(), aim_turnrate_pitch_ads.get(), aaGlob.adsLerp); + pitchTurnRate = slowdownPitchScale * aaGlob.fovTurnRateScale * sensitivity * pitchTurnRate; + auto yawTurnRate = AimAssist_Lerp(aim_turnrate_yaw.get(), aim_turnrate_yaw_ads.get(), aaGlob.adsLerp); + yawTurnRate = slowdownYawScale * aaGlob.fovTurnRateScale * sensitivity * yawTurnRate; + + if (input->pitchMax > 0 && input->pitchMax < pitchTurnRate) + pitchTurnRate = input->pitchMax; + if (input->yawMax > 0 && input->yawMax < yawTurnRate) + yawTurnRate = input->yawMax; + + const auto pitchSign = adjustedPitchAxis >= 0.0f ? 1.0f : -1.0f; + const auto yawSign = adjustedYawAxis >= 0.0f ? 1.0f : -1.0f; + + const auto pitchDelta = std::fabs(adjustedPitchAxis) * pitchTurnRate; + const auto yawDelta = std::fabs(adjustedYawAxis) * yawTurnRate; + + if (!aim_accel_turnrate_enabled.get()) + { + aaGlob.pitchDelta = pitchDelta; + aaGlob.yawDelta = yawDelta; + } + else + { + const auto accel = aim_accel_turnrate_lerp.get() * sensitivity; + if (pitchDelta <= aaGlob.pitchDelta) + aaGlob.pitchDelta = pitchDelta; + else + aaGlob.pitchDelta = LinearTrack(pitchDelta, aaGlob.pitchDelta, accel, input->deltaTime); + + if (yawDelta <= aaGlob.yawDelta) + aaGlob.yawDelta = yawDelta; + else + aaGlob.yawDelta = LinearTrack(yawDelta, aaGlob.yawDelta, accel, input->deltaTime); + } + + output->pitch += aaGlob.pitchDelta * input->deltaTime * pitchSign; + output->yaw += aaGlob.yawDelta * input->deltaTime * yawSign; + } + + void Gamepad::AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output) + { + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + + output->pitch = input->pitch; + output->yaw = input->yaw; + + if (aaGlob.initialized) + { + Game::AimAssist_UpdateTweakables(input->localClientNum); + Game::AimAssist_UpdateAdsLerp(input); + AimAssist_ApplyTurnRates(input, output); + + Game::AimAssist_ApplyAutoMelee(input, output); + AimAssist_ApplyLockOn(input, output); + } + + aaGlob.prevButtons = input->buttons; + } + + void Gamepad::CL_RemoteControlMove_GamePad(const int localClientNum, Game::usercmd_s* cmd) + { + // Buttons are already handled by keyboard input handler + + const auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); + const auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); + const auto yaw = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); + const auto pitch = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); + const auto sensitivity = input_viewSensitivity.get(); + + constexpr auto scale = static_cast(std::numeric_limits::max()); + cmd->remoteControlAngles[0] = ClampChar(cmd->remoteControlAngles[0] + static_cast(std::floor(-up * scale * sensitivity)) + + static_cast(std::floor(-pitch * scale * sensitivity))); + cmd->remoteControlAngles[1] = ClampChar(cmd->remoteControlAngles[1] + static_cast(std::floor(-right * scale * sensitivity)) + + static_cast(std::floor(-yaw * scale * sensitivity))); + } + + constexpr auto CL_RemoteControlMove = 0x5A6BA0; + __declspec(naked) void Gamepad::CL_RemoteControlMove_Stub() + { + __asm + { + // Prepare args for our function call + push edi // usercmd + push eax // localClientNum + + call CL_RemoteControlMove + + // Call our function, the args were already prepared earlier + call CL_RemoteControlMove_GamePad + add esp, 0x8 + + ret + } + } + + 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; + + auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); + auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); + auto magnitude = up * up + right * right; + + if (magnitude > 1.0f) + { + magnitude = std::sqrt(magnitude); + up /= magnitude; + right /= magnitude; + } + + Game::cgArray[0].selectedLocation[0] += right * cg_mapLocationSelectionCursorSpeed.get() * frameTime; + Game::cgArray[0].selectedLocation[1] -= up * mapAspectRatio * cg_mapLocationSelectionCursorSpeed.get() * frameTime; + + if (selectionRequiresAngle) + { + const auto yawUp = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); + const auto yawRight = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); + + if (std::fabs(yawUp) > 0.0f || std::fabs(yawRight) > 0.0f) + { + Game::vec2_t vec + { + yawUp, + -yawRight + }; + + Game::cgArray[0].selectedLocationAngle = Game::AngleNormalize360(Game::vectoyaw(&vec)); + Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; + Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; + } + } + else + { + Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; + Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; + } + + return true; + } + + constexpr auto CG_HandleLocationSelectionInput = 0x5A67A0; + __declspec(naked) void Gamepad::CG_HandleLocationSelectionInput_Stub() + { + __asm + { + // Prepare args for our function call + push esi // usercmd + push eax // localClientNum + + call CG_HandleLocationSelectionInput + + test al,al + jz exit_handling + + // Call our function, the args were already prepared earlier + call CG_HandleLocationSelectionInput_GamePad + + exit_handling: + add esp, 0x8 + ret + } + } + + bool Gamepad::CG_ShouldUpdateViewAngles(const int localClientNum) + { + return !Game::Key_IsKeyCatcherActive(localClientNum, Game::KEYCATCH_MASK_ANY) || Game::UI_GetActiveMenu(localClientNum) == Game::UIMENU_SCOREBOARD; + } + + float Gamepad::CL_GamepadAxisValue(const int gamePadIndex, const Game::GamepadVirtualAxis virtualAxis) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(virtualAxis > Game::GPAD_VIRTAXIS_NONE && virtualAxis < Game::GPAD_VIRTAXIS_COUNT); + const auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + const auto& [physicalAxis, mapType] = gamePadGlobal.axes.virtualAxes[virtualAxis]; + + if (physicalAxis <= Game::GPAD_PHYSAXIS_NONE || physicalAxis >= Game::GPAD_PHYSAXIS_COUNT) + return 0.0f; + + auto axisDeflection = gamePadGlobal.axes.axesValues[physicalAxis]; + + if (mapType == Game::GPAD_MAP_SQUARED) + { + const auto otherAxisSameStick = axisSameStick[physicalAxis]; + + float otherAxisDeflection; + if (otherAxisSameStick <= Game::GPAD_PHYSAXIS_NONE || otherAxisSameStick >= Game::GPAD_PHYSAXIS_COUNT) + otherAxisDeflection = 0.0f; + else + otherAxisDeflection = gamePadGlobal.axes.axesValues[otherAxisSameStick]; + + axisDeflection = std::sqrt(axisDeflection * axisDeflection + otherAxisDeflection * otherAxisDeflection) * axisDeflection; + } + + return axisDeflection; + } + + char Gamepad::ClampChar(const int value) + { + return static_cast(std::clamp(value, std::numeric_limits::min(), std::numeric_limits::max())); + } + + void Gamepad::CL_GamepadMove(const int gamePadIndex, Game::usercmd_s* cmd, const float frameTimeBase) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + auto& clientActive = Game::clients[gamePadIndex]; + + if (!gpad_enabled.get() || !gamePad.enabled) + return; + + auto pitch = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_PITCH); + if (!input_invertPitch.get()) + pitch *= -1; + + auto yaw = -CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_YAW); + auto forward = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_FORWARD); + auto side = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_SIDE); + + // The game implements an attack axis at this location. This axis is unused however so for this patch it was not implemented. + //auto attack = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_ATTACK); + + auto moveScale = static_cast(std::numeric_limits::max()); + + if (std::fabs(side) > 0.0f || std::fabs(forward) > 0.0f) + { + const auto length = std::fabs(side) <= std::fabs(forward) + ? side / forward + : forward / side; + moveScale = std::sqrt((length * length) + 1.0f) * moveScale; + } + + const auto forwardMove = static_cast(std::floor(forward * moveScale)); + const auto rightMove = static_cast(std::floor(side * moveScale)); + + cmd->rightmove = ClampChar(cmd->rightmove + rightMove); + cmd->forwardmove = ClampChar(cmd->forwardmove + forwardMove); + + // Swap attack and throw buttons when using controller and akimbo to match "left trigger"="left weapon" and "right trigger"="right weapon" + if(gamePad.inUse && clientActive.snap.ps.weapCommon.lastWeaponHand == Game::WEAPON_HAND_LEFT) + { + auto oldButtons = cmd->buttons; + if (oldButtons & Game::CMD_BUTTON_ATTACK) + cmd->buttons |= Game::CMD_BUTTON_THROW; + else + cmd->buttons &= ~Game::CMD_BUTTON_THROW; + + if (oldButtons & Game::CMD_BUTTON_THROW) + cmd->buttons |= Game::CMD_BUTTON_ATTACK; + else + cmd->buttons &= ~Game::CMD_BUTTON_ATTACK; + } + + // Check for frozen controls. Flag name should start with PMF_ + if (CG_ShouldUpdateViewAngles(gamePadIndex) && (clientActive.snap.ps.pm_flags & 0x800) == 0) + { + Game::AimInput aimInput{}; + Game::AimOutput aimOutput{}; + aimInput.deltaTime = frameTimeBase; + aimInput.buttons = cmd->buttons; + aimInput.localClientNum = gamePadIndex; + aimInput.deltaTimeScaled = static_cast(Game::cls->frametime) * 0.001f; + aimInput.pitch = clientActive.clViewangles[0]; + aimInput.pitchAxis = pitch; + aimInput.pitchMax = clientActive.cgameMaxPitchSpeed; + aimInput.yaw = clientActive.clViewangles[1]; + aimInput.yawAxis = yaw; + aimInput.yawMax = clientActive.cgameMaxYawSpeed; + aimInput.forwardAxis = forward; + aimInput.rightAxis = side; + AimAssist_UpdateGamePadInput(&aimInput, &aimOutput); + clientActive.clViewangles[0] = aimOutput.pitch; + clientActive.clViewangles[1] = aimOutput.yaw; + cmd->meleeChargeDist = aimOutput.meleeChargeDist; + cmd->meleeChargeYaw = aimOutput.meleeChargeYaw; + } + } + + constexpr auto CL_MouseMove = 0x5A6240; + __declspec(naked) void Gamepad::CL_MouseMove_Stub() + { + __asm + { + // Prepare args for our function call + push [esp+0x4] // frametime_base + push ebx // cmd + push eax // localClientNum + + push [esp+0x8] // restore frametime_base on the stack + call CL_MouseMove + add esp,4 + + // Call our function, the args were already prepared earlier + call CL_GamepadMove + add esp,0xC + + ret + } + } + + bool Gamepad::Gamepad_ShouldUse(const Game::gentity_s* playerEnt, const unsigned useTime) + { + // Only apply hold time to +usereload keybind + return !(playerEnt->client->buttons & Game::CMD_BUTTON_USE_RELOAD) || useTime >= static_cast(gpad_use_hold_time.get()); + } + + __declspec(naked) void Gamepad::Player_UseEntity_Stub() + { + __asm + { + // Execute overwritten instructions + cmp eax, [ecx + 0x10] + jl skipUse + + // Call our custom check + push eax + pushad + push eax + push edi + call Gamepad_ShouldUse + add esp, 8h + mov [esp + 0x20], eax + popad + pop eax + + // Skip use if custom check returns false + test al, al + jz skipUse + + // perform use + push 0x5FE39B + ret + + skipUse: + push 0x5FE3AF + ret + } + } + + bool Gamepad::Key_IsValidGamePadChar(const int key) + { + return key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_1 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_1 + || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_2 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_2 + || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_3 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_3; + } + + void Gamepad::CL_GamepadResetMenuScrollTime(const int gamePadIndex, const int key, const bool down, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + if (!down) + return; + + const auto scrollDelayFirst = gpad_menu_scroll_delay_first.get(); + for (const auto scrollButton : menuScrollButtonList) + { + if (key == scrollButton) + { + gamePadGlobal.nextScrollTime = scrollDelayFirst + time; + return; + } + } + } + + void Gamepad::CL_GamepadGenerateAPad(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); + + auto& gamePad = gamePads[gamePadIndex]; + + const auto stick = stickForAxis[physicalAxis]; + const auto stickIndex = stick & Game::GPAD_VALUE_MASK; + if (stick != Game::GPAD_INVALID) + { + assert(stickIndex < 4); + const auto& mapping = analogStickList[stickIndex]; + + if (gamePad.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; + CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, event, time); + } + else if (gamePad.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; + CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, event, time); + } + else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS]) + { + CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, Game::GPAD_BUTTON_RELEASED, time); + } + else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG]) + { + CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, Game::GPAD_BUTTON_RELEASED, time); + } + } + } + + void Gamepad::CL_GamepadEvent(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, const float value, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); + + auto& gamePad = gamePads[gamePadIndex]; + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + gamePadGlobal.axes.axesValues[physicalAxis] = value; + CL_GamepadGenerateAPad(gamePadIndex, physicalAxis, time); + + if (std::fabs(value) > 0.0f) + { + gamePad.inUse = true; + gpad_in_use.setRaw(true); + } + } + + void Gamepad::UI_GamepadKeyEvent(const int gamePadIndex, const int key, const bool down) + { + for (const auto& mapping : controllerMenuKeyMappings) + { + if (mapping.controllerKey == key) + { + Game::UI_KeyEvent(gamePadIndex, mapping.pcKey, down); + return; + } + } + + // No point in sending unmapped controller keystrokes to the key event handler since it doesn't know how to use it anyway + // Game::UI_KeyEvent(gamePadIndex, key, down); + } + + bool Gamepad::Scoreboard_HandleInput(int gamePadIndex, int key) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& keyState = Game::playerKeys[gamePadIndex]; + + if (keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "togglescores") == 0) + { + Game::Cbuf_AddText(gamePadIndex, "togglescores\n"); + return true; + } + + switch (key) + { + case Game::K_DPAD_UP: + Game::CG_ScrollScoreboardUp(Game::cgArray); + return true; + + case Game::K_DPAD_DOWN: + Game::CG_ScrollScoreboardDown(Game::cgArray); + return true; + + default: + return false; + } + } + + bool Gamepad::CL_CheckForIgnoreDueToRepeat(const int gamePadIndex, const int key, const int repeatCount, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + const int scrollDelayFirst = gpad_menu_scroll_delay_first.get(); + const int scrollDelayRest = gpad_menu_scroll_delay_rest.get(); + + for (const auto menuScrollButton : menuScrollButtonList) + { + if (key == menuScrollButton) + { + if (repeatCount == 1) + { + gamePadGlobal.nextScrollTime = time + scrollDelayFirst; + return false; + } + + if (time > gamePadGlobal.nextScrollTime) + { + gamePadGlobal.nextScrollTime = time + scrollDelayRest; + return false; + } + break; + } + } + } + + return repeatCount > 1; + } + + void Gamepad::CL_GamepadButtonEvent(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + + const auto pressed = buttonEvent == Game::GPAD_BUTTON_PRESSED; + const auto pressedOrUpdated = pressed || buttonEvent == Game::GPAD_BUTTON_UPDATE; + + auto& keyState = Game::playerKeys[gamePadIndex]; + keyState.keys[key].down = pressedOrUpdated; + + if (pressedOrUpdated) + { + if (++keyState.keys[key].repeats == 1) + keyState.anyKeyDown++; + } + else if (buttonEvent == Game::GPAD_BUTTON_RELEASED && keyState.keys[key].repeats > 0) + { + keyState.keys[key].repeats = 0; + if (--keyState.anyKeyDown < 0) + keyState.anyKeyDown = 0; + } + + if (pressedOrUpdated && CL_CheckForIgnoreDueToRepeat(gamePadIndex, key, keyState.keys[key].repeats, time)) + return; + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_LOCATION_SELECTION) && pressedOrUpdated) + { + if (key == Game::K_BUTTON_B || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+actionslot 4") == 0) + { + keyState.locSelInputState = Game::LOC_SEL_INPUT_CANCEL; + } + else if (key == Game::K_BUTTON_A || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+attack") == 0) + { + keyState.locSelInputState = Game::LOC_SEL_INPUT_CONFIRM; + } + return; + } + + const auto activeMenu = Game::UI_GetActiveMenu(gamePadIndex); + if(activeMenu == Game::UIMENU_SCOREBOARD) + { + if (buttonEvent == Game::GPAD_BUTTON_PRESSED && Scoreboard_HandleInput(gamePadIndex, key)) + return; + } + + keyState.locSelInputState = Game::LOC_SEL_INPUT_NONE; + + const auto* keyBinding = keyState.keys[key].binding; + + char cmd[1024]; + if (pressedOrUpdated) + { + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); + return; + } + + if (keyBinding) + { + if (keyBinding[0] == '+') + { + sprintf_s(cmd, "%s %i %i\n", keyBinding, key, time); + Game::Cbuf_AddText(gamePadIndex, cmd); + } + else + { + Game::Cbuf_AddText(gamePadIndex, keyBinding); + Game::Cbuf_AddText(gamePadIndex, "\n"); + } + } + } + else + { + if (keyBinding && keyBinding[0] == '+') + { + sprintf_s(cmd, "-%s %i %i\n", &keyBinding[1], key, time); + Game::Cbuf_AddText(gamePadIndex, cmd); + } + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); + } + } + } + + void Gamepad::CL_GamepadButtonEventForPort(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + gamePad.inUse = true; + gpad_in_use.setRaw(true); + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + CL_GamepadResetMenuScrollTime(gamePadIndex, key, buttonEvent == Game::GPAD_BUTTON_PRESSED, time); + + + CL_GamepadButtonEvent(gamePadIndex, key, buttonEvent, time); + } + + void Gamepad::GPad_ConvertStickToFloat(const short x, const short 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()); + + 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 (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; + + outX = stickVec[0] * len; + outY = stickVec[1] * len; + } + + float Gamepad::GPad_GetStick(const int gamePadIndex, const Game::GamePadStick stick) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + return gamePad.sticks[stick]; + } + + float Gamepad::GPad_GetButton(const int gamePadIndex, Game::GamePadButton button) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + 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; + } + 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 gamePadIndex, Game::GamePadButton button) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + 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); + + if (analogIndex < std::extent_v) + { + down = gamePad.analogs[analogIndex] > 0.0f; + lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; + } + } + + return down && !lastDown; + } + + bool Gamepad::GPad_ButtonRequiresUpdates(const int gamePadIndex, Game::GamePadButton button) + { + return (button & Game::GPAD_ANALOG_MASK || button & Game::GPAD_DPAD_MASK) && GPad_GetButton(gamePadIndex, button) > 0.0f; + } + + bool Gamepad::GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + bool down = false; + bool lastDown = false; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + + down = (gamePad.digitals & buttonValue) != 0; + lastDown = (gamePad.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 = gamePad.analogs[analogIndex] > 0.0f; + lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; + } + } + + return !down && lastDown; + } + + void Gamepad::GPad_UpdateSticksDown(const int gamePadIndex) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + for (auto stickIndex = 0u; stickIndex < std::extent_v; stickIndex++) + { + for (auto dir = 0; dir < Game::GPAD_STICK_DIR_COUNT; dir++) + { + 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; + } + } + } + } + + void Gamepad::GPad_UpdateSticks(const int gamePadIndex, const XINPUT_GAMEPAD& state) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + + auto& gamePad = gamePads[gamePadIndex]; + + 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(gamePadIndex); + +#ifdef DEBUG + if (gpad_debug.get()) + { + Logger::Print("Left: X: %f Y: %f\n", lVec[0], lVec[1]); + Logger::Print("Right: X: %f Y: %f\n", rVec[0], rVec[1]); + Logger::Print("Down: %i:%i %i:%i %i:%i %i:%i\n", 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]); + } +#endif + } + + void Gamepad::GPad_UpdateDigitals(const int gamePadIndex, const XINPUT_GAMEPAD& state) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + + auto& gamePad = gamePads[gamePadIndex]; + + 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); + +#ifdef DEBUG + if (gpad_debug.get()) + { + Logger::Print("Buttons: %x\n", gamePad.digitals); + } +#endif + } + + void Gamepad::GPad_UpdateAnalogs(const int gamePadIndex, const XINPUT_GAMEPAD& state) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + + auto& gamePad = gamePads[gamePadIndex]; + + 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; + +#ifdef DEBUG + if (gpad_debug.get()) + { + Logger::Print("Triggers: %f %f\n", gamePad.analogs[0], gamePad.analogs[1]); + } +#endif + } + + void Gamepad::GPad_UpdateAll() + { + GPad_RefreshAll(); + + for (auto currentGamePadIndex = 0; currentGamePadIndex < Game::MAX_GAMEPADS; currentGamePadIndex++) + { + const auto& gamePad = gamePads[currentGamePadIndex]; + if (!gamePad.enabled) + continue; + + XINPUT_STATE inputState; + if (XInputGetState(gamePad.portIndex, &inputState) != ERROR_SUCCESS) + continue; + + GPad_UpdateSticks(currentGamePadIndex, inputState.Gamepad); + GPad_UpdateDigitals(currentGamePadIndex, inputState.Gamepad); + GPad_UpdateAnalogs(currentGamePadIndex, inputState.Gamepad); + } + } + + void Gamepad::IN_GamePadsMove() + { + if (!gpad_enabled.get()) + return; + + GPad_UpdateAll(); + const auto time = Game::Sys_Milliseconds(); + + bool gpadPresent = false; + for (auto gamePadIndex = 0; gamePadIndex < Game::MAX_GAMEPADS; gamePadIndex++) + { + const auto& gamePad = gamePads[gamePadIndex]; + + if (gamePad.enabled) + { + gpadPresent = true; + const auto lx = GPad_GetStick(gamePadIndex, Game::GPAD_LX); + const auto ly = GPad_GetStick(gamePadIndex, Game::GPAD_LY); + const auto rx = GPad_GetStick(gamePadIndex, Game::GPAD_RX); + const auto ry = GPad_GetStick(gamePadIndex, Game::GPAD_RY); + const auto leftTrig = GPad_GetButton(gamePadIndex, Game::GPAD_L_TRIG); + const auto rightTrig = GPad_GetButton(gamePadIndex, Game::GPAD_R_TRIG); + + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_X, lx, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_Y, ly, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_X, rx, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_Y, ry, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LTRIGGER, leftTrig, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RTRIGGER, rightTrig, time); + + for (const auto& buttonMapping : buttonList) + { + if (GPad_IsButtonPressed(gamePadIndex, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort( + gamePadIndex, + buttonMapping.code, + Game::GPAD_BUTTON_PRESSED, + time); + } + else if (GPad_ButtonRequiresUpdates(gamePadIndex, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort( + gamePadIndex, + buttonMapping.code, + Game::GPAD_BUTTON_UPDATE, + time); + } + else if (GPad_IsButtonReleased(gamePadIndex, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort( + gamePadIndex, + buttonMapping.code, + Game::GPAD_BUTTON_RELEASED, + time); + } + } + } + } + + gpad_present.setRaw(gpadPresent); + } + + + void Gamepad::IN_Frame_Hk() + { + // Call original method + Utils::Hook::Call(0x64C490)(); + + IN_GamePadsMove(); + } + + void Gamepad::Gamepad_WriteBindings(const int gamePadIndex, const int handle) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + Game::FS_Printf(handle, "unbindallaxis\n"); + + for (auto virtualAxisIndex = 0u; virtualAxisIndex < Game::GPAD_VIRTAXIS_COUNT; virtualAxisIndex++) + { + const auto& axisMapping = gamePadGlobal.axes.virtualAxes[virtualAxisIndex]; + if (axisMapping.physicalAxis <= Game::GPAD_PHYSAXIS_NONE || axisMapping.physicalAxis >= Game::GPAD_PHYSAXIS_COUNT + || axisMapping.mapType <= Game::GPAD_MAP_NONE || axisMapping.mapType >= Game::GPAD_MAP_COUNT) + { + continue; + } + + const auto* physicalAxisName = physicalAxisNames[axisMapping.physicalAxis]; + const auto* virtualAxisName = virtualAxisNames[virtualAxisIndex]; + const auto* mappingName = gamePadMappingTypeNames[axisMapping.mapType]; + + Game::FS_Printf(handle, "bindaxis %s %s %s\n", physicalAxisName, virtualAxisName, mappingName); + } + } + + void Gamepad::Key_WriteBindings_Hk(const int localClientNum, const int handle) + { + // Call original function + Utils::Hook::Call(0x4A5A20)(localClientNum, handle); + + Gamepad_WriteBindings(0, handle); + } + + void __declspec(naked) Gamepad::Com_WriteConfiguration_Modified_Stub() + { + __asm + { + mov eax, [ecx + 0x18] + or eax, gamePadBindingsModifiedFlags // Also check for gamePadBindingsModifiedFlags + test al, 1 + jz endMethod + mov gamePadBindingsModifiedFlags, 0 // Reset gamePadBindingsModifiedFlags + mov eax, [ecx + 0x18] // Restore eax to dvar_modified_flags + + push 0x60B26E + retn + + endMethod: + push 0x60B298 + retn + } + } + + void Gamepad::Gamepad_BindAxis(const int gamePadIndex, const Game::GamepadPhysicalAxis realIndex, const Game::GamepadVirtualAxis axisIndex, const Game::GamepadMapping mapType) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(realIndex > Game::GPAD_PHYSAXIS_NONE && realIndex < Game::GPAD_PHYSAXIS_COUNT); + assert(axisIndex > Game::GPAD_VIRTAXIS_NONE && axisIndex < Game::GPAD_VIRTAXIS_COUNT); + assert(mapType > Game::GPAD_MAP_NONE && mapType < Game::GPAD_MAP_COUNT); + + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + gamePadGlobal.axes.virtualAxes[axisIndex].physicalAxis = realIndex; + gamePadGlobal.axes.virtualAxes[axisIndex].mapType = mapType; + + gamePadBindingsModifiedFlags |= 1; + } + + Game::GamepadPhysicalAxis Gamepad::StringToPhysicalAxis(const char* str) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (strcmp(str, physicalAxisNames[i]) == 0) + return static_cast(i); + } + + return Game::GPAD_PHYSAXIS_NONE; + } + + Game::GamepadVirtualAxis Gamepad::StringToVirtualAxis(const char* str) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (strcmp(str, virtualAxisNames[i]) == 0) + return static_cast(i); + } + + return Game::GPAD_VIRTAXIS_NONE; + } + + Game::GamepadMapping Gamepad::StringToGamePadMapping(const char* str) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (strcmp(str, gamePadMappingTypeNames[i]) == 0) + return static_cast(i); + } + + return Game::GPAD_MAP_NONE; + } + + void Gamepad::Axis_Bind_f(Command::Params* params) + { + if (params->length() < 4) + { + Logger::Print("bindaxis \n"); + return; + } + + const auto* physicalAxisText = params->get(1); + const auto* virtualAxisText = params->get(2); + const auto* mappingText = params->get(3); + + const Game::GamepadPhysicalAxis physicalAxis = StringToPhysicalAxis(physicalAxisText); + if (physicalAxis == Game::GPAD_PHYSAXIS_NONE) + { + Logger::Print("\"%s\" isn't a valid physical axis\n", physicalAxisText); + return; + } + + const Game::GamepadVirtualAxis virtualAxis = StringToVirtualAxis(virtualAxisText); + if (virtualAxis == Game::GPAD_VIRTAXIS_NONE) + { + Logger::Print("\"%s\" isn't a valid virtual axis\n", virtualAxisText); + return; + } + + const Game::GamepadMapping mapping = StringToGamePadMapping(mappingText); + if (mapping == Game::GPAD_MAP_NONE) + { + Logger::Print("\"%s\" isn't a valid input type\n", mappingText); + return; + } + + Gamepad_BindAxis(0, physicalAxis, virtualAxis, mapping); + } + + void Gamepad::Axis_Unbindall_f(Command::Params*) + { + auto& gamePadGlobal = gamePadGlobals[0]; + + for (auto& virtualAxis : gamePadGlobal.axes.virtualAxes) + { + virtualAxis.physicalAxis = Game::GPAD_PHYSAXIS_NONE; + virtualAxis.mapType = Game::GPAD_MAP_NONE; + } + } + + void Gamepad::Bind_GP_SticksConfigs_f(Command::Params*) + { + const auto* stickConfigName = gpad_sticksConfig.get(); + Game::Cbuf_AddText(0, Utils::String::VA("exec %s\n", stickConfigName)); + } + + void Gamepad::Bind_GP_ButtonsConfigs_f(Command::Params*) + { + const auto* buttonConfigName = gpad_buttonConfig.get(); + Game::Cbuf_AddText(0, Utils::String::VA("exec %s\n", buttonConfigName)); + } + + void Gamepad::Scores_Toggle_f(Command::Params*) + { + if(Game::cgArray[0].nextSnap) + { + if (Game::UI_GetActiveMenu(0) != Game::UIMENU_SCOREBOARD) + Game::CG_ScoresDown_f(); + else + Game::CG_ScoresUp_f(); + } + } + + void Gamepad::InitDvars() + { + gpad_enabled = Dvar::Register("gpad_enabled", false, Game::DVAR_FLAG_SAVED, "Game pad enabled"); + gpad_debug = Dvar::Register("gpad_debug", false, Game::DVAR_FLAG_NONE, "Game pad debugging"); + gpad_present = Dvar::Register("gpad_present", false, Game::DVAR_FLAG_NONE, "Game pad present"); + gpad_in_use = Dvar::Register("gpad_in_use", false, Game::DVAR_FLAG_NONE, "A game pad is in use"); + gpad_sticksConfig = Dvar::Register("gpad_sticksConfig", "", Game::DVAR_FLAG_SAVED, "Game pad stick configuration"); + gpad_buttonConfig = Dvar::Register("gpad_buttonConfig", "", Game::DVAR_FLAG_SAVED, "Game pad button configuration"); + gpad_menu_scroll_delay_first = Dvar::Register("gpad_menu_scroll_delay_first", 420, 0, 1000, Game::DVAR_FLAG_SAVED, "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_FLAG_SAVED, + "Menu scroll key-repeat delay, for repeats after the first, in milliseconds"); + gpad_rumble = Dvar::Register("gpad_rumble", true, Game::DVAR_FLAG_SAVED, "Enable game pad rumble"); + gpad_stick_pressed_hysteresis = Dvar::Register("gpad_stick_pressed_hysteresis", 0.1f, 0.0f, 1.0f, Game::DVAR_FLAG_NONE, + "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_FLAG_NONE, "Game pad stick pressed threshhold"); + gpad_stick_deadzone_max = Dvar::Register("gpad_stick_deadzone_max", 0.01f, 0.0f, 1.0f, Game::DVAR_FLAG_NONE, "Game pad maximum stick deadzone"); + gpad_stick_deadzone_min = Dvar::Register("gpad_stick_deadzone_min", 0.2f, 0.0f, 1.0f, Game::DVAR_FLAG_NONE, "Game pad minimum stick deadzone"); + gpad_button_deadzone = Dvar::Register("gpad_button_deadzone", 0.13f, 0.0f, 1.0f, Game::DVAR_FLAG_NONE, "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_FLAG_NONE, "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_FLAG_NONE, "Game pad maximum pad stick pressed value"); + gpad_use_hold_time = Dvar::Register("gpad_use_hold_time", 250, 0, INT32_MAX, Game::DVAR_FLAG_NONE, "Time to hold the 'use' button on gamepads to activate use"); + gpad_lockon_enabled = Dvar::Register("gpad_lockon_enabled", true, Game::DVAR_FLAG_SAVED, "Game pad lockon aim assist enabled"); + gpad_slowdown_enabled = Dvar::Register("gpad_slowdown_enabled", true, Game::DVAR_FLAG_SAVED, "Game pad slowdown aim assist enabled"); + + input_viewSensitivity = Dvar::Register("input_viewSensitivity", 1.0f, 0.0001f, 5.0f, Game::DVAR_FLAG_SAVED, "View Sensitivity"); + input_invertPitch = Dvar::Register("input_invertPitch", false, Game::DVAR_FLAG_SAVED, "Invert gamepad pitch"); + sv_allowAimAssist = Dvar::Register("sv_allowAimAssist", true, Game::DVAR_FLAG_NONE, "Controls whether aim assist features on clients are enabled"); + aim_turnrate_pitch = Dvar::Var("aim_turnrate_pitch"); + aim_turnrate_pitch_ads = Dvar::Var("aim_turnrate_pitch_ads"); + aim_turnrate_yaw = Dvar::Var("aim_turnrate_yaw"); + aim_turnrate_yaw_ads = Dvar::Var("aim_turnrate_yaw_ads"); + aim_accel_turnrate_enabled = Dvar::Var("aim_accel_turnrate_enabled"); + aim_accel_turnrate_lerp = Dvar::Var("aim_accel_turnrate_lerp"); + aim_input_graph_enabled = Dvar::Var("aim_input_graph_enabled"); + aim_input_graph_index = Dvar::Var("aim_input_graph_index"); + aim_scale_view_axis = Dvar::Var("aim_scale_view_axis"); + cl_bypassMouseInput = Dvar::Var("cl_bypassMouseInput"); + cg_mapLocationSelectionCursorSpeed = Dvar::Var("cg_mapLocationSelectionCursorSpeed"); + aim_aimAssistRangeScale = Dvar::Var("aim_aimAssistRangeScale"); + aim_slowdown_enabled = Dvar::Var("aim_slowdown_enabled"); + aim_slowdown_debug = Dvar::Var("aim_slowdown_debug"); + aim_slowdown_pitch_scale = Dvar::Var("aim_slowdown_pitch_scale"); + aim_slowdown_pitch_scale_ads = Dvar::Var("aim_slowdown_pitch_scale_ads"); + aim_slowdown_yaw_scale = Dvar::Var("aim_slowdown_yaw_scale"); + aim_slowdown_yaw_scale_ads = Dvar::Var("aim_slowdown_yaw_scale_ads"); + aim_lockon_enabled = Dvar::Var("aim_lockon_enabled"); + aim_lockon_deflection = Dvar::Var("aim_lockon_deflection"); + aim_lockon_pitch_strength = Dvar::Var("aim_lockon_pitch_strength"); + aim_lockon_strength = Dvar::Var("aim_lockon_strength"); + } + + void Gamepad::IN_Init_Hk() + { + // Call original method + Utils::Hook::Call(0x45D620)(); + + InitDvars(); + } + + const char* Gamepad::GetGamePadCommand(const char* command) + { + if (strcmp(command, "+activate") == 0 || strcmp(command, "+reload") == 0) + return "+usereload"; + if (strcmp(command, "+melee_breath") == 0) + return "+holdbreath"; + + return command; + } + + int Gamepad::Key_GetCommandAssignmentInternal_Hk(const char* cmd, int (*keys)[2]) + { + auto keyCount = 0; + + if (gamePads[0].inUse) + { + cmd = GetGamePadCommand(cmd); + for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) + { + if (!Key_IsValidGamePadChar(keyNum)) + continue; + + if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) + { + (*keys)[keyCount++] = keyNum; + + if (keyCount >= 2) + return keyCount; + } + } + } + else + { + for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) + { + if (Key_IsValidGamePadChar(keyNum)) + continue; + + if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) + { + (*keys)[keyCount++] = keyNum; + + if (keyCount >= 2) + return keyCount; + } + } + } + + return keyCount; + } + + 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; + gpad_in_use.setRaw(false); + + // Call original function + Utils::Hook::Call(0x4F6480)(localClientNum, key, down, time); + } + + bool Gamepad::IsGamePadInUse() + { + return gamePads[0].inUse; + } + + int Gamepad::CL_MouseEvent_Hk(const int x, const int y, const int dx, const int dy) + { + if (dx != 0 || dy != 0) + { + gamePads[0].inUse = false; + gpad_in_use.setRaw(false); + } + + // Call original function + return Utils::Hook::Call(0x4D7C50)(x, y, dx, dy); + } + + bool Gamepad::UI_RefreshViewport_Hk() + { + return cl_bypassMouseInput.get() || IsGamePadInUse(); + } + + void Gamepad::CreateKeyNameMap() + { + memcpy(combinedKeyNames, Game::keyNames, sizeof(Game::keyname_t) * Game::KEY_NAME_COUNT); + memcpy(&combinedKeyNames[Game::KEY_NAME_COUNT], extendedKeyNames, sizeof(Game::keyname_t) * std::extent_v); + combinedKeyNames[std::extent_v - 1] = {nullptr, 0}; + + memcpy(combinedLocalizedKeyNames, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); + memcpy(&combinedLocalizedKeyNames[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNames, + sizeof(Game::keyname_t) * std::extent_v); + combinedLocalizedKeyNames[std::extent_v - 1] = {nullptr, 0}; + + Utils::Hook::Set(0x4A780A, combinedKeyNames); + Utils::Hook::Set(0x4A7810, combinedKeyNames); + Utils::Hook::Set(0x435C9F, combinedKeyNames); + Utils::Hook::Set(0x435C98, combinedLocalizedKeyNames); + } + + Gamepad::Gamepad() + { + if (ZoneBuilder::IsEnabled()) + return; + + // Initialize gamepad environment + Utils::Hook(0x467C03, IN_Init_Hk, HOOK_CALL).install()->quick(); + + // package the forward and right move components in the move buttons + Utils::Hook(0x60E38D, MSG_WriteDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick(); + + // send two bytes for sending movement data + Utils::Hook::Set(0x60E501, 16); + Utils::Hook::Set(0x60E5CD, 16); + + // make sure to parse the movement data properly and apply it + Utils::Hook(0x492127, MSG_ReadDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x492009, MSG_ReadDeltaUsercmdKeyStub2, HOOK_JUMP).install()->quick(); + + // Also rewrite configuration when gamepad config is dirty + Utils::Hook(0x60B264, Com_WriteConfiguration_Modified_Stub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x60B223, Key_WriteBindings_Hk, HOOK_CALL).install()->quick(); + + CreateKeyNameMap(); + + Command::Add("bindaxis", Axis_Bind_f); + Command::Add("unbindallaxis", Axis_Unbindall_f); + Command::Add("bindgpsticksconfigs", Bind_GP_SticksConfigs_f); + Command::Add("bindgpbuttonsconfigs", Bind_GP_ButtonsConfigs_f); + Command::Add("togglescores", Scores_Toggle_f); + + if (Dedicated::IsEnabled()) + return; + + // Gamepad on frame hook + Utils::Hook(0x475E9E, IN_Frame_Hk, HOOK_CALL).install()->quick(); + + // Mark controller as unused when keyboard key is pressed + Utils::Hook(0x43D179, CL_KeyEvent_Hk, HOOK_CALL).install()->quick(); + + // Mark controller as unused when mouse is moved + Utils::Hook(0x64C507, CL_MouseEvent_Hk, HOOK_CALL).install()->quick(); + + // Hide cursor when controller is active + Utils::Hook(0x48E527, UI_RefreshViewport_Hk, HOOK_CALL).install()->quick(); + + // Only return gamepad keys when gamepad enabled and only non gamepad keys when not + Utils::Hook(0x5A7A23, Key_GetCommandAssignmentInternal_Hk, HOOK_CALL).install()->quick(); + + // Add hold time to gamepad usereload on hold prompts + Utils::Hook(0x5FE396, Player_UseEntity_Stub, HOOK_JUMP).install()->quick(); + + // Add gamepad inputs to remote control (eg predator) handling + Utils::Hook(0x5A6D4E, CL_RemoteControlMove_Stub, HOOK_CALL).install()->quick(); + + // Add gamepad inputs to location selection (eg airstrike location) handling + Utils::Hook(0x5A6D72, CG_HandleLocationSelectionInput_Stub, HOOK_CALL).install()->quick(); + + // Add gamepad inputs to usercmds + Utils::Hook(0x5A6DAE, CL_MouseMove_Stub, HOOK_CALL).install()->quick(); + } +} diff --git a/src/Components/Modules/Gamepad.hpp b/src/Components/Modules/Gamepad.hpp new file mode 100644 index 00000000..9808909f --- /dev/null +++ b/src/Components/Modules/Gamepad.hpp @@ -0,0 +1,197 @@ +#pragma once + +namespace Components +{ + class Gamepad : public Component + { + struct ControllerMenuKeyMapping + { + Game::keyNum_t controllerKey; + 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; + unsigned nextScrollTime; + + GamePadGlobals(); + }; + + public: + Gamepad(); + + private: + static Game::ButtonToCodeMap_t buttonList[]; + static Game::StickToCodeMap_t analogStickList[4]; + static Game::GamePadStick stickForAxis[]; + static Game::GamepadPhysicalAxis axisSameStick[]; + static const char* physicalAxisNames[]; + static const char* virtualAxisNames[]; + static const char* gamePadMappingTypeNames[]; + static Game::keyNum_t menuScrollButtonList[]; + static Game::keyname_t extendedKeyNames[]; + static Game::keyname_t extendedLocalizedKeyNames[]; + static Game::keyname_t combinedKeyNames[]; + static Game::keyname_t combinedLocalizedKeyNames[]; + static ControllerMenuKeyMapping controllerMenuKeyMappings[]; + + static GamePad gamePads[Game::MAX_GAMEPADS]; + static GamePadGlobals gamePadGlobals[Game::MAX_GAMEPADS]; + + 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_sticksConfig; + static Dvar::Var gpad_buttonConfig; + 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; + static Dvar::Var input_viewSensitivity; + static Dvar::Var input_invertPitch; + static Dvar::Var sv_allowAimAssist; + static Dvar::Var aim_turnrate_pitch; + static Dvar::Var aim_turnrate_pitch_ads; + static Dvar::Var aim_turnrate_yaw; + static Dvar::Var aim_turnrate_yaw_ads; + static Dvar::Var aim_accel_turnrate_enabled; + static Dvar::Var aim_accel_turnrate_lerp; + static Dvar::Var aim_input_graph_enabled; + static Dvar::Var aim_input_graph_index; + static Dvar::Var aim_scale_view_axis; + static Dvar::Var cl_bypassMouseInput; + static Dvar::Var cg_mapLocationSelectionCursorSpeed; + static Dvar::Var aim_aimAssistRangeScale; + static Dvar::Var aim_slowdown_enabled; + static Dvar::Var aim_slowdown_debug; + static Dvar::Var aim_slowdown_pitch_scale; + static Dvar::Var aim_slowdown_pitch_scale_ads; + static Dvar::Var aim_slowdown_yaw_scale; + static Dvar::Var aim_slowdown_yaw_scale_ads; + static Dvar::Var aim_lockon_enabled; + static Dvar::Var aim_lockon_deflection; + static Dvar::Var aim_lockon_pitch_strength; + static Dvar::Var aim_lockon_strength; + + static void MSG_WriteDeltaUsercmdKeyStub(); + + static void ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to); + + static void MSG_ReadDeltaUsercmdKeyStub(); + static void MSG_ReadDeltaUsercmdKeyStub2(); + + static float LinearTrack(float target, float current, float rate, float deltaTime); + static bool AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, float clipHalfWidth, float clipHalfHeight); + static bool AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps); + static const Game::AimScreenTarget* AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight); + static const Game::AimScreenTarget* AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, int entIndex); + static const Game::AimScreenTarget* AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight, int prevTargetEnt); + static bool AimAssist_IsLockonActive(int gamePadIndex); + static void AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output); + static void AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis); + static bool AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps); + static void AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale); + static float AimAssist_Lerp(float from, float to, float fraction); + static void AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output); + static void AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output); + + static void CL_RemoteControlMove_GamePad(int localClientNum, Game::usercmd_s* cmd); + static void CL_RemoteControlMove_Stub(); + static bool CG_HandleLocationSelectionInput_GamePad(int localClientNum, Game::usercmd_s* cmd); + static void CG_HandleLocationSelectionInput_Stub(); + static bool CG_ShouldUpdateViewAngles(int localClientNum); + static float CL_GamepadAxisValue(int gamePadIndex, Game::GamepadVirtualAxis virtualAxis); + static char ClampChar(int value); + static void CL_GamepadMove(int gamePadIndex, Game::usercmd_s* cmd, float frameTimeBase); + static void CL_MouseMove_Stub(); + + static bool Gamepad_ShouldUse(const Game::gentity_s* playerEnt, unsigned useTime); + static void Player_UseEntity_Stub(); + + static bool Key_IsValidGamePadChar(int key); + static void CL_GamepadResetMenuScrollTime(int gamePadIndex, int key, bool down, unsigned int time); + static bool Scoreboard_HandleInput(int gamePadIndex, int key); + static bool CL_CheckForIgnoreDueToRepeat(int gamePadIndex, int key, int repeatCount, unsigned int time); + static void UI_GamepadKeyEvent(int gamePadIndex, int key, bool down); + static void CL_GamepadGenerateAPad(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, unsigned time); + static void CL_GamepadEvent(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, float value, unsigned time); + static void CL_GamepadButtonEvent(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned time); + static void CL_GamepadButtonEventForPort(int gamePadIndex, 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 gamePadIndex, Game::GamePadStick stick); + static float GPad_GetButton(int gamePadIndex, Game::GamePadButton button); + static bool GPad_IsButtonPressed(int gamePadIndex, Game::GamePadButton button); + static bool GPad_ButtonRequiresUpdates(int gamePadIndex, Game::GamePadButton button); + static bool GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button); + + static void GPad_UpdateSticksDown(int gamePadIndex); + static void GPad_UpdateSticks(int gamePadIndex, const XINPUT_GAMEPAD& state); + static void GPad_UpdateDigitals(int gamePadIndex, const XINPUT_GAMEPAD& state); + static void GPad_UpdateAnalogs(int gamePadIndex, const XINPUT_GAMEPAD& state); + + static bool GPad_Check(int gamePadIndex, int portIndex); + static void GPad_RefreshAll(); + static void GPad_UpdateAll(); + static void IN_GamePadsMove(); + static void IN_Frame_Hk(); + + static void Gamepad_WriteBindings(int gamePadIndex, int handle); + static void Key_WriteBindings_Hk(int localClientNum, int handle); + static void Com_WriteConfiguration_Modified_Stub(); + + static void Gamepad_BindAxis(int gamePadIndex, Game::GamepadPhysicalAxis realIndex, Game::GamepadVirtualAxis axisIndex, Game::GamepadMapping mapType); + static Game::GamepadPhysicalAxis StringToPhysicalAxis(const char* str); + static Game::GamepadVirtualAxis StringToVirtualAxis(const char* str); + static Game::GamepadMapping StringToGamePadMapping(const char* str); + static void Axis_Bind_f(Command::Params* params); + static void Axis_Unbindall_f(Command::Params* params); + static void Bind_GP_SticksConfigs_f(Command::Params* params); + static void Bind_GP_ButtonsConfigs_f(Command::Params* params); + static void Scores_Toggle_f(Command::Params* params); + + static void InitDvars(); + static void IN_Init_Hk(); + + static const char* GetGamePadCommand(const char* command); + static int Key_GetCommandAssignmentInternal_Hk(const char* cmd, int(*keys)[2]); + 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 void CreateKeyNameMap(); + }; +} diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index ee36600a..24ee1d5f 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -560,7 +560,7 @@ namespace Components } hasDlc.push_back(hasAllMaps); - Dvar::Var(Utils::String::VA("isDlcInstalled_%d", pack.index)).set(hasAllMaps ? 1 : 0); + Dvar::Var(Utils::String::VA("isDlcInstalled_%d", pack.index)).set(hasAllMaps ? true : false); } // Must have all of dlc 3 to 5 or it causes issues @@ -571,7 +571,7 @@ namespace Components sentMessage = true; } - Dvar::Var("isDlcInstalled_All").set(hasAllDlcs ? 1 : 0); + Dvar::Var("isDlcInstalled_All").set(hasAllDlcs ? true : false); } bool Maps::IsCustomMap() diff --git a/src/Components/Modules/Materials.cpp b/src/Components/Modules/Materials.cpp index 5b53c434..173feefd 100644 --- a/src/Components/Modules/Materials.cpp +++ b/src/Components/Modules/Materials.cpp @@ -197,6 +197,12 @@ namespace Components add esp, 4h mov [esp + 20h], eax + + // Make all material text icons have white tint + mov eax,[esp + 0x50] + or eax,0x00FFFFFF + mov [esp + 0x50],eax + popad pop eax @@ -285,6 +291,63 @@ namespace Components #endif + int Materials::R_TextWidth_Hk(const char* text, int maxChars, Game::Font_s* font) + { + auto lineWidth = 0; + auto maxWidth = 0; + + if (maxChars <= 0) + maxChars = 0x7FFFFFFF; + + if (text == nullptr) + return 0; + + auto count = 0; + while (text && *text && count < maxChars) + { + const auto letter = Game::SEH_ReadCharFromString(&text, nullptr); + if (letter == '\r' || letter == '\n') + { + lineWidth = 0; + } + else + { + if (letter == '^' && text) + { + if (*text >= '0' && *text <= Colors::LastColorIndex) + { + text++; + continue; + } + + if (*text >= '\x01' && *text <= '\x02' && text[1] != '\0' && text[2] != '\0' && text[3] != '\0') + { + const auto width = text[1]; + const auto materialNameLength = text[3]; + + // This is how the game calculates width and height. Probably some 1 byte floating point number. + auto v9 = font->pixelHeight * (width - 16) + 16; + auto w = ((((v9 >> 24) & 0x1F) + v9) >> 5); + + lineWidth += w; + + text += 4; + for (auto currentLength = 0; currentLength < materialNameLength && *text; currentLength++) + text++; + continue; + } + } + + lineWidth += R_GetCharacterGlyph(font, letter)->dx; + if (lineWidth > maxWidth) + maxWidth = lineWidth; + count++; + } + } + + return maxWidth; + } + Materials::Materials() { Materials::ImageNameLength = 7; @@ -293,6 +356,7 @@ namespace Components Materials::ImageVersionCheckHook.initialize(0x53A456, Materials::ImageVersionCheck, HOOK_CALL)->install(); // Fix material pointer exploit + // Also make all material text icons have white tint Utils::Hook(0x534E0C, Materials::DrawMaterialStub, HOOK_CALL).install()->quick(); // Increment string pointer accordingly @@ -307,6 +371,9 @@ namespace Components // Debug material comparison Utils::Hook::Set(0x523894, Materials::MaterialComparePrint); + // Consider material text icons when calculating text width + Utils::Hook(0x5056C0, Materials::R_TextWidth_Hk, HOOK_JUMP).install()->quick(); + #ifdef DEBUG if (Flags::HasFlag("dump")) { diff --git a/src/Components/Modules/Materials.hpp b/src/Components/Modules/Materials.hpp index 2a2d55dd..a057cedb 100644 --- a/src/Components/Modules/Materials.hpp +++ b/src/Components/Modules/Materials.hpp @@ -33,6 +33,8 @@ namespace Components static int WriteDeathMessageIcon(char* string, int offset, Game::Material* material); static void DeathMessageStub(); + static int R_TextWidth_Hk(const char* text, int maxChars, Game::Font_s* font); + #ifdef DEBUG static void DumpImageCfg(int, const char*, const char* material); static void DumpImageCfgPath(int, const char*, const char* material); diff --git a/src/Components/Modules/Menus.cpp b/src/Components/Modules/Menus.cpp index d18c8b9c..6d4fa7c4 100644 --- a/src/Components/Modules/Menus.cpp +++ b/src/Components/Modules/Menus.cpp @@ -897,6 +897,7 @@ namespace Components Menus::Add("ui_mp/theater_menu.menu"); Menus::Add("ui_mp/pc_options_multi.menu"); Menus::Add("ui_mp/pc_options_game.menu"); + Menus::Add("ui_mp/pc_options_gamepad.menu"); Menus::Add("ui_mp/stats_reset.menu"); Menus::Add("ui_mp/stats_unlock.menu"); Menus::Add("ui_mp/security_increase_popmenu.menu"); diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 1dc256a3..72995ce6 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -352,11 +352,9 @@ namespace Components { // execute our hook pushad - pusha call Script::StoreScriptBaseProgramNum - popa popad // execute overwritten code caused by the jump hook diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 0fa394af..6692a23d 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -30,13 +30,19 @@ namespace Game BG_GetNumWeapons_t BG_GetNumWeapons = BG_GetNumWeapons_t(0x4F5CC0); BG_GetWeaponName_t BG_GetWeaponName = BG_GetWeaponName_t(0x4E6EC0); BG_LoadWeaponDef_LoadObj_t BG_LoadWeaponDef_LoadObj = BG_LoadWeaponDef_LoadObj_t(0x57B5F0); + BG_GetWeaponDef_t BG_GetWeaponDef = BG_GetWeaponDef_t(0x440EB0); Cbuf_AddServerText_t Cbuf_AddServerText = Cbuf_AddServerText_t(0x4BB9B0); Cbuf_AddText_t Cbuf_AddText = Cbuf_AddText_t(0x404B20); + CG_NextWeapon_f_t CG_NextWeapon_f = CG_NextWeapon_f_t(0x449DE0); CG_GetClientNum_t CG_GetClientNum = CG_GetClientNum_t(0x433700); CG_PlayBoltedEffect_t CG_PlayBoltedEffect = CG_PlayBoltedEffect_t(0x00430E10); CG_GetBoneIndex_t CG_GetBoneIndex = CG_GetBoneIndex_t(0x00504F20); + CG_ScoresDown_f_t CG_ScoresDown_f = CG_ScoresDown_f_t(0x580370); + CG_ScoresUp_f_t CG_ScoresUp_f = CG_ScoresUp_f_t(0x5802C0); + CG_ScrollScoreboardUp_t CG_ScrollScoreboardUp = CG_ScrollScoreboardUp_t(0x47A5C0); + CG_ScrollScoreboardDown_t CG_ScrollScoreboardDown = CG_ScrollScoreboardDown_t(0x493B50); CL_GetClientName_t CL_GetClientName = CL_GetClientName_t(0x4563D0); CL_IsCgameInitialized_t CL_IsCgameInitialized = CL_IsCgameInitialized_t(0x43EB20); @@ -129,6 +135,7 @@ namespace Game FS_FCloseFile_t FS_FCloseFile = FS_FCloseFile_t(0x462000); FS_WriteFile_t FS_WriteFile = FS_WriteFile_t(0x426450); FS_Write_t FS_Write = FS_Write_t(0x4C06E0); + FS_Printf_t FS_Printf = FS_Printf_t(0x459320); FS_Read_t FS_Read = FS_Read_t(0x4A04C0); FS_Seek_t FS_Seek = FS_Seek_t(0x4A63D0); FS_FTell_t FS_FTell = FS_FTell_t(0x4E6760); @@ -148,6 +155,7 @@ namespace Game Info_ValueForKey_t Info_ValueForKey = Info_ValueForKey_t(0x47C820); Key_SetCatcher_t Key_SetCatcher = Key_SetCatcher_t(0x43BD00); + Key_IsKeyCatcherActive_t Key_IsKeyCatcherActive = Key_IsKeyCatcherActive_t(0x4DA010); LargeLocalInit_t LargeLocalInit = LargeLocalInit_t(0x4A62A0); @@ -175,8 +183,12 @@ namespace Game Menus_FindByName_t Menus_FindByName = Menus_FindByName_t(0x487240); Menu_IsVisible_t Menu_IsVisible = Menu_IsVisible_t(0x4D77D0); Menus_MenuIsInStack_t Menus_MenuIsInStack = Menus_MenuIsInStack_t(0x47ACB0); + Menu_HandleKey_t Menu_HandleKey = Menu_HandleKey_t(0x4C4A00); + Menu_GetFocused_t Menu_GetFocused = Menu_GetFocused_t(0x4AFF10); MSG_Init_t MSG_Init = MSG_Init_t(0x45FCA0); + MSG_ReadBit_t MSG_ReadBit = MSG_ReadBit_t(0x476D20); + MSG_ReadBits_t MSG_ReadBits = MSG_ReadBits_t(0x4C3900); MSG_ReadData_t MSG_ReadData = MSG_ReadData_t(0x4527C0); MSG_ReadLong_t MSG_ReadLong = MSG_ReadLong_t(0x4C9550); MSG_ReadShort_t MSG_ReadShort = MSG_ReadShort_t(0x40BDD0); @@ -272,6 +284,7 @@ namespace Game SE_Load_t SE_Load = SE_Load_t(0x502A30); SEH_StringEd_GetString_t SEH_StringEd_GetString = SEH_StringEd_GetString_t(0x44BB30); + SEH_ReadCharFromString_t SEH_ReadCharFromString = SEH_ReadCharFromString_t(0x486560); Dvar_SetFromStringByName_t Dvar_SetFromStringByName = Dvar_SetFromStringByName_t(0x4F52E0); Dvar_SetFromStringByNameFromSource_t Dvar_SetFromStringByNameFromSource = Dvar_SetFromStringByNameFromSource_t(0x4FC770); @@ -321,6 +334,7 @@ namespace Game TeleportPlayer_t TeleportPlayer = TeleportPlayer_t(0x496850); UI_AddMenuList_t UI_AddMenuList = UI_AddMenuList_t(0x4533C0); + UI_GetActiveMenu_t UI_GetActiveMenu = UI_GetActiveMenu_t(0x4BE790); UI_CheckStringTranslation_t UI_CheckStringTranslation = UI_CheckStringTranslation_t(0x4FB010); UI_LoadMenus_t UI_LoadMenus = UI_LoadMenus_t(0x641460); UI_UpdateArenas_t UI_UpdateArenas = UI_UpdateArenas_t(0x4A95B0); @@ -329,13 +343,18 @@ namespace Game UI_GetContext_t UI_GetContext = UI_GetContext_t(0x4F8940); UI_TextWidth_t UI_TextWidth = UI_TextWidth_t(0x6315C0); UI_DrawText_t UI_DrawText = UI_DrawText_t(0x49C0D0); + UI_KeyEvent_t UI_KeyEvent = UI_KeyEvent_t(0x4970F0); Win_GetLanguage_t Win_GetLanguage = Win_GetLanguage_t(0x45CBA0); Vec3UnpackUnitVec_t Vec3UnpackUnitVec = Vec3UnpackUnitVec_t(0x45CA90); + vectoyaw_t vectoyaw = vectoyaw_t(0x45AD10); + AngleNormalize360_t AngleNormalize360 = AngleNormalize360_t(0x438DC0); unzClose_t unzClose = unzClose_t(0x41BF20); + AimAssist_ApplyAutoMelee_t AimAssist_ApplyAutoMelee = AimAssist_ApplyAutoMelee_t(0x56A360); + XAssetHeader* DB_XAssetPool = reinterpret_cast(0x7998A8); unsigned int* g_poolSize = reinterpret_cast(0x7995E8); @@ -352,6 +371,9 @@ namespace Game source_t **sourceFiles = reinterpret_cast(0x7C4A98); keywordHash_t **menuParseKeywordHash = reinterpret_cast(0x63AE928); + float* cl_angles = reinterpret_cast(0xB2F8D0); + float* cgameFOVSensitivityScale = reinterpret_cast(0xB2F884); + int* svs_time = reinterpret_cast(0x31D9384); int* svs_numclients = reinterpret_cast(0x31D938C); client_t* svs_clients = reinterpret_cast(0x31D9390); @@ -426,6 +448,21 @@ namespace Game GfxScene* scene = reinterpret_cast(0x6944914); + clientActive_t* clients = reinterpret_cast(0xB2C698); + + clientStatic_t* cls = reinterpret_cast(0xA7FE90); + + cg_s* cgArray = reinterpret_cast(0x7F0F78); + + PlayerKeyState* playerKeys = reinterpret_cast(0xA1B7D0); + kbutton_t* playersKb = reinterpret_cast(0xA1A9A8); + AimAssistGlobals* aaGlobArray = reinterpret_cast(0x7A2110); + + keyname_t* keyNames = reinterpret_cast(0x798580); + keyname_t* localizedKeyNames = reinterpret_cast(0x798880); + + GraphFloat* aaInputGraph = reinterpret_cast(0x7A2FC0); + XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize) { int elSize = DB_GetXAssetSizeHandlers[type](); @@ -698,12 +735,31 @@ namespace Game return atoi(StringTable_Lookup(rankTable, 0, maxrank, 7)); } - void Vec3Normalize(vec3_t& vec) + float Vec2Normalize(vec2_t& vec) { - const float length = static_cast(std::sqrt(std::pow(vec[0], 2) + std::pow(vec[1], 2) + std::pow(vec[2], 2))); - vec[0] /= length; - vec[1] /= length; - vec[2] /= length; + const float length = std::sqrt((vec[0] * vec[0]) + (vec[1] * vec[1])); + + if(length > 0.0f) + { + vec[0] /= length; + vec[1] /= length; + } + + return length; + } + + float Vec3Normalize(vec3_t& vec) + { + const float length = std::sqrt(std::pow(vec[0], 2.0f) + std::pow(vec[1], 2.0f) + std::pow(vec[2], 2.0f)); + + if(length > 0.0f) + { + vec[0] /= length; + vec[1] /= length; + vec[2] /= length; + } + + return length; } void Vec2UnpackTexCoords(const PackedTexCoords in, vec2_t* out) @@ -901,7 +957,25 @@ namespace Game Game::R_AddDebugLine(color, v[3], v[7]); } + float GraphGetValueFromFraction(const int knotCount, const float(*knots)[2], const float fraction) + { + for (auto knotIndex = 1; knotIndex < knotCount; ++knotIndex) + { + if (knots[knotIndex][0] >= fraction) + { + const auto adjustedFraction = (fraction - knots[knotIndex - 1][0]) / (knots[knotIndex][0] - knots[knotIndex - 1][0]); + return (knots[knotIndex][1] - knots[knotIndex - 1][1]) * adjustedFraction + knots[knotIndex - 1][1]; + } + } + + return -1.0f; + } + + float GraphFloat_GetValue(const GraphFloat* graph, const float fraction) + { + return GraphGetValueFromFraction(graph->knotCount, graph->knots, fraction) * graph->scale; + } #pragma optimize("", off) __declspec(naked) float UI_GetScoreboardLeft(void* /*a1*/) @@ -966,6 +1040,21 @@ namespace Game } } + bool PM_IsAdsAllowed(Game::playerState_s* playerState) + { + bool result; + + __asm + { + mov esi, playerState + mov ebx, 0x5755A0 + call ebx + mov result, al // AL + } + + return result; + } + __declspec(naked) void FS_AddLocalizedGameDirectory(const char* /*path*/, const char* /*dir*/) { __asm @@ -1140,6 +1229,32 @@ namespace Game } } + void Menu_SetNextCursorItem(Game::UiContext* a1, Game::menuDef_t* a2, int unk) + { + __asm + { + push unk + push a2 + mov eax, a1 + mov ebx, 0x639FE0 + call ebx + add esp, 0x8 // 2 args = 2x4 + } + } + + void Menu_SetPrevCursorItem(Game::UiContext* a1, Game::menuDef_t* a2, int unk) + { + __asm + { + push unk + push a2 + mov eax, a1 + mov ebx, 0x639F20 + call ebx + add esp, 0x8 // 2 args = 2x4 + } + } + __declspec(naked) void R_AddDebugLine(float* /*color*/, float* /*v1*/, float* /*v2*/) { __asm @@ -1199,5 +1314,48 @@ namespace Game retn } } + + __declspec(naked) Glyph* R_GetCharacterGlyph(Font_s* /*font */, unsigned int /*letter*/) + { + __asm + { + push eax + pushad + mov edi, [esp + 0x28 + 4] + push [esp + 0x24 + 4] + mov eax, 0x5055C0 + call eax + add esp,0x4 + mov [esp + 0x20],eax + + popad + pop eax + retn + } + } + + __declspec(naked) void AimAssist_UpdateTweakables(int /*localClientNum*/) + { + __asm + { + mov eax,[esp+0x4] + mov ebx,0x569950 + call ebx + retn + } + } + + __declspec(naked) void AimAssist_UpdateAdsLerp(const AimInput* /*aimInput*/) + { + __asm + { + mov eax, [esp + 0x4] + mov ebx, 0x569AA0 + call ebx + retn + } + } + + #pragma optimize("", on) } diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 3536a4b7..86a3334a 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -40,6 +40,9 @@ namespace Game typedef void*(__cdecl * BG_LoadWeaponDef_LoadObj_t)(const char* filename); extern BG_LoadWeaponDef_LoadObj_t BG_LoadWeaponDef_LoadObj; + typedef WeaponDef* (__cdecl * BG_GetWeaponDef_t)(int weaponIndex); + extern BG_GetWeaponDef_t BG_GetWeaponDef; + typedef void(__cdecl * Cbuf_AddServerText_t)(); extern Cbuf_AddServerText_t Cbuf_AddServerText; @@ -49,11 +52,26 @@ namespace Game typedef int(__cdecl * CG_GetClientNum_t)(); extern CG_GetClientNum_t CG_GetClientNum; - typedef std::int32_t(__cdecl* CG_PlayBoltedEffect_t) (std::int32_t, FxEffectDef*, std::int32_t, std::uint32_t); + typedef void(__cdecl * CG_NextWeapon_f_t)(); + extern CG_NextWeapon_f_t CG_NextWeapon_f; + + typedef std::int32_t(__cdecl * CG_PlayBoltedEffect_t) (std::int32_t, FxEffectDef*, std::int32_t, std::uint32_t); extern CG_PlayBoltedEffect_t CG_PlayBoltedEffect; - typedef std::int32_t(__cdecl* CG_GetBoneIndex_t)(std::int32_t, std::uint32_t name, char* index); + typedef std::int32_t(__cdecl * CG_GetBoneIndex_t)(std::int32_t, std::uint32_t name, char* index); extern CG_GetBoneIndex_t CG_GetBoneIndex; + + typedef void(__cdecl * CG_ScoresDown_f_t)(); + extern CG_ScoresDown_f_t CG_ScoresDown_f; + + typedef void(__cdecl * CG_ScoresUp_f_t)(); + extern CG_ScoresUp_f_t CG_ScoresUp_f; + + typedef void(__cdecl * CG_ScrollScoreboardUp_t)(cg_s* cgameGlob); + extern CG_ScrollScoreboardUp_t CG_ScrollScoreboardUp; + + typedef void(__cdecl * CG_ScrollScoreboardDown_t)(cg_s* cgameGlob); + extern CG_ScrollScoreboardDown_t CG_ScrollScoreboardDown; typedef char*(__cdecl * CL_GetClientName_t)(int localClientNum, int index, char *buf, size_t size); extern CL_GetClientName_t CL_GetClientName; @@ -311,6 +329,9 @@ namespace Game typedef int(__cdecl * FS_Write_t)(const void* buffer, size_t size, int file); extern FS_Write_t FS_Write; + typedef int(__cdecl * FS_Printf_t)(int file, const char* fmt, ...); + extern FS_Printf_t FS_Printf; + typedef int(__cdecl * FS_Read_t)(void* buffer, size_t size, int file); extern FS_Read_t FS_Read; @@ -354,6 +375,9 @@ namespace Game typedef void(__cdecl * Key_SetCatcher_t)(int localClientNum, int catcher); extern Key_SetCatcher_t Key_SetCatcher; + typedef bool(__cdecl * Key_IsKeyCatcherActive_t)(int localClientNum, int catcher); + extern Key_IsKeyCatcherActive_t Key_IsKeyCatcherActive; + typedef void(__cdecl * LargeLocalInit_t)(); extern LargeLocalInit_t LargeLocalInit; @@ -426,6 +450,15 @@ namespace Game typedef bool(__cdecl * Menus_MenuIsInStack_t)(UiContext *dc, menuDef_t *menu); extern Menus_MenuIsInStack_t Menus_MenuIsInStack; + typedef menuDef_t*(__cdecl * Menu_GetFocused_t)(UiContext* ctx); + extern Menu_GetFocused_t Menu_GetFocused; + + typedef void(__cdecl * Menu_HandleKey_t)(UiContext* ctx, menuDef_t* menu, Game::keyNum_t key, int down); + extern Menu_HandleKey_t Menu_HandleKey; + + typedef bool(__cdecl * UI_KeyEvent_t)(int clientNum, int key, int down); + extern UI_KeyEvent_t UI_KeyEvent; + typedef void(__cdecl * MSG_Init_t)(msg_t *buf, char *data, int length); extern MSG_Init_t MSG_Init; @@ -435,6 +468,12 @@ namespace Game typedef int(__cdecl * MSG_ReadLong_t)(msg_t* msg); extern MSG_ReadLong_t MSG_ReadLong; + typedef int(__cdecl * MSG_ReadBit_t)(msg_t* msg); + extern MSG_ReadBit_t MSG_ReadBit; + + typedef int(__cdecl * MSG_ReadBits_t)(msg_t* msg, int bits); + extern MSG_ReadBits_t MSG_ReadBits; + typedef short(__cdecl * MSG_ReadShort_t)(msg_t* msg); extern MSG_ReadShort_t MSG_ReadShort; @@ -462,10 +501,10 @@ namespace Game typedef void(__cdecl * MSG_WriteLong_t)(msg_t *msg, int c); extern MSG_WriteLong_t MSG_WriteLong; - typedef void(*MSG_WriteShort_t)(msg_t* msg, short s); + typedef void(__cdecl * MSG_WriteShort_t)(msg_t* msg, short s); extern MSG_WriteShort_t MSG_WriteShort; - typedef void(*MSG_WriteString_t)(msg_t* msg, const char *str); + typedef void(__cdecl * MSG_WriteString_t)(msg_t* msg, const char *str); extern MSG_WriteString_t MSG_WriteString; typedef int(__cdecl * MSG_WriteBitsCompress_t)(bool trainHuffman, const char *from, char *to, int size); @@ -657,6 +696,9 @@ namespace Game typedef char* (__cdecl * SEH_StringEd_GetString_t)(const char* string); extern SEH_StringEd_GetString_t SEH_StringEd_GetString; + typedef int (__cdecl * SEH_ReadCharFromString_t)(const char** text, int* isTrailingPunctuation); + extern SEH_ReadCharFromString_t SEH_ReadCharFromString; + typedef char* (__cdecl * SL_ConvertToString_t)(unsigned short stringValue); extern SL_ConvertToString_t SL_ConvertToString; @@ -752,6 +794,9 @@ namespace Game typedef void(__cdecl * UI_AddMenuList_t)(UiContext *dc, MenuList *menuList, int close); extern UI_AddMenuList_t UI_AddMenuList; + + typedef uiMenuCommand_t(__cdecl * UI_GetActiveMenu_t)(int localClientNum); + extern UI_GetActiveMenu_t UI_GetActiveMenu; typedef char* (__cdecl * UI_CheckStringTranslation_t)(char*, char*); extern UI_CheckStringTranslation_t UI_CheckStringTranslation; @@ -782,10 +827,19 @@ namespace Game typedef void (__cdecl * Vec3UnpackUnitVec_t)(PackedUnitVec, vec3_t *); extern Vec3UnpackUnitVec_t Vec3UnpackUnitVec; + + typedef float(__cdecl * vectoyaw_t)(vec2_t* vec); + extern vectoyaw_t vectoyaw; + + typedef float(__cdecl * AngleNormalize360_t)(float val); + extern AngleNormalize360_t AngleNormalize360; typedef void(__cdecl * unzClose_t)(void* handle); extern unzClose_t unzClose; + typedef void(__cdecl * AimAssist_ApplyAutoMelee_t)(const AimInput* input, AimOutput* output); + extern AimAssist_ApplyAutoMelee_t AimAssist_ApplyAutoMelee; + extern XAssetHeader* DB_XAssetPool; extern unsigned int* g_poolSize; @@ -799,6 +853,9 @@ namespace Game extern cmd_function_t** cmd_functions; + extern float* cl_angles; + extern float* cgameFOVSensitivityScale; + extern int* svs_time; extern int* svs_numclients; extern client_t* svs_clients; @@ -875,8 +932,28 @@ namespace Game extern GfxScene* scene; + extern clientActive_t* clients; + + extern clientStatic_t* cls; + + extern cg_s* cgArray; + + extern PlayerKeyState* playerKeys; + extern kbutton_t* playersKb; + extern AimAssistGlobals* aaGlobArray; + + constexpr auto KEY_NAME_COUNT = 95; + constexpr auto LOCALIZED_KEY_NAME_COUNT = 95; + extern keyname_t* keyNames; + extern keyname_t* localizedKeyNames; + + constexpr auto AIM_ASSIST_GRAPH_COUNT = 4u; + extern GraphFloat* aaInputGraph; + XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize); void Menu_FreeItemMemory(Game::itemDef_s* item); + void Menu_SetNextCursorItem(Game::UiContext* ctx, Game::menuDef_t* currentMenu, int unk = 1); + void Menu_SetPrevCursorItem(Game::UiContext* ctx, Game::menuDef_t* currentMenu, int unk = 1); const char* TableLookup(StringTable* stringtable, int row, int column); const char* UI_LocalizeMapName(const char* mapName); const char* UI_LocalizeGameType(const char* gameType); @@ -892,6 +969,8 @@ namespace Game void FS_AddLocalizedGameDirectory(const char *path, const char *dir); + bool PM_IsAdsAllowed(Game::playerState_s* playerState); + void ShowMessageBox(const std::string& message, const std::string& title); unsigned int R_HashString(const char* string); @@ -920,7 +999,8 @@ namespace Game void Image_Setup(GfxImage* image, unsigned int width, unsigned int height, unsigned int depth, unsigned int flags, _D3DFORMAT format); - void Vec3Normalize(vec3_t& vec); + float Vec2Normalize(vec2_t& vec); + float Vec3Normalize(vec3_t& vec); void Vec2UnpackTexCoords(const PackedTexCoords in, vec2_t* out); void MatrixVecMultiply(const float(&mulMat)[3][3], const vec3_t& mulVec, vec3_t& solution); void QuatRot(vec3_t* vec, const vec4_t* quat); @@ -931,4 +1011,12 @@ namespace Game void R_AddDebugString(float *color, float *pos, float scale, const char *str); void R_AddDebugBounds(float* color, Bounds* b); void R_AddDebugBounds(float* color, Bounds* b, const float(*quat)[4]); + + float GraphGetValueFromFraction(int knotCount, const float(*knots)[2], float fraction); + float GraphFloat_GetValue(const GraphFloat* graph, const float fraction); + + Glyph* R_GetCharacterGlyph(Font_s* font, unsigned int letter); + + void AimAssist_UpdateTweakables(int localClientNum); + void AimAssist_UpdateAdsLerp(const AimInput* input); } diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 51e71428..99064c9c 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -1,6 +1,6 @@ #pragma once -#define PROTOCOL 0x95 +#define PROTOCOL 0x96 #define NUM_CUSTOM_CLASSES 15 #define SEMANTIC_WATER_MAP 11 #define FX_ELEM_FIELD_COUNT 90 @@ -111,6 +111,39 @@ namespace Game IMG_CATEGORY_TEMP = 0x7, } ; + enum buttons_t + { + KB_LEFT = 0x0, + KB_RIGHT = 0x1, + KB_FORWARD = 0x2, + KB_BACK = 0x3, + KB_LOOKUP = 0x4, + KB_LOOKDOWN = 0x5, + KB_MOVELEFT = 0x6, + KB_MOVERIGHT = 0x7, + KB_STRAFE = 0x8, + KB_SPEED = 0x9, + KB_UP = 0xA, + KB_DOWN = 0xB, + KB_ANYUP = 0xC, + KB_MLOOK = 0xD, + KB_ATTACK = 0xE, + KB_BREATH = 0xF, + KB_FRAG = 0x10, + KB_OFFHANDSECONDARY = 0x11, + KB_MELEE = 0x12, + KB_ACTIVATE = 0x13, + KB_RELOAD = 0x14, + KB_USE_RELOAD = 0x15, + KB_PRONE = 0x16, + KB_CROUCH = 0x17, + KB_THROW = 0x18, + KB_SPRINT = 0x19, + KB_NIGHTVISION = 0x1A, + KB_TALK = 0x1B, + NUM_BUTTONS = 0x1C + }; + enum DvarSetSource { DVAR_SOURCE_INTERNAL = 0x0, @@ -173,6 +206,174 @@ namespace Game }; #pragma pack(pop) + enum KeyCatch_t + { + KEYCATCH_MASK_ANY = -1, + KEYCATCH_CONSOLE = 0x1, + KEYCATCH_UNKNOWN2 = 0x2, + KEYCATCH_UNKNOWN4 = 0x4, + KEYCATCH_LOCATION_SELECTION = 0x8, + KEYCATCH_UI = 0x10, + KEYCATCH_CHAT = 0x20, + KEYCATCH_UNKNOWN40 = 0x40, + KEYCATCH_UNKNOWN80 = 0x80, + KEYCATCH_UNKNOWN100 = 0x100, + }; + + enum keyNum_t + { + K_NONE = 0x0, + K_FIRSTGAMEPADBUTTON_RANGE_1 = 0x1, // First Gamepad 1 + K_BUTTON_A = 0x1, + K_BUTTON_B = 0x2, + K_BUTTON_X = 0x3, + K_BUTTON_Y = 0x4, + K_BUTTON_LSHLDR = 0x5, + K_BUTTON_RSHLDR = 0x6, + K_LASTGAMEPADBUTTON_RANGE_1 = 0x6, // Last Gamepad 1 + K_TAB = 0x9, + K_ENTER = 0xD, + K_FIRSTGAMEPADBUTTON_RANGE_2 = 0xE, // First Gamepad 2 + K_BUTTON_START = 0xE, + K_BUTTON_BACK = 0xF, + K_BUTTON_LSTICK = 0x10, + K_BUTTON_RSTICK = 0x11, + K_BUTTON_LTRIG = 0x12, + K_BUTTON_RTRIG = 0x13, + K_FIRSTDPAD = 0x14, // First Dpad + K_DPAD_UP = 0x14, + K_DPAD_DOWN = 0x15, + K_DPAD_LEFT = 0x16, + K_DPAD_RIGHT = 0x17, + K_LASTDPAD = 0x17, // Last Dpad + K_DPAD_LEFTRIGHT = 0x18, + K_DPAD_UPDOWN = 0x19, + K_LASTGAMEPADBUTTON_RANGE_2 = 0x19, // Last Gamepad 2 + K_ESCAPE = 0x1B, + K_FIRSTGAMEPADBUTTON_RANGE_3 = 0x1C, // First Gamepad 3 + K_FIRSTAPAD = 0x1C, // First APad + K_APAD_UP = 0x1C, + K_APAD_DOWN = 0x1D, + K_APAD_LEFT = 0x1E, + K_APAD_RIGHT = 0x1F, + K_LASTAPAD = 0x1F, // Last APad + K_LASTGAMEPADBUTTON_RANGE_3 = 0x1F, // Last Gamepad 3 + K_SPACE = 0x20, + K_BACKSPACE = 0x7F, + K_ASCII_FIRST = 0x80, + K_ASCII_181 = 0x80, + K_ASCII_191 = 0x81, + K_ASCII_223 = 0x82, + K_ASCII_224 = 0x83, + K_ASCII_225 = 0x84, + K_ASCII_228 = 0x85, + K_ASCII_229 = 0x86, + K_ASCII_230 = 0x87, + K_ASCII_231 = 0x88, + K_ASCII_232 = 0x89, + K_ASCII_233 = 0x8A, + K_ASCII_236 = 0x8B, + K_ASCII_241 = 0x8C, + K_ASCII_242 = 0x8D, + K_ASCII_243 = 0x8E, + K_ASCII_246 = 0x8F, + K_ASCII_248 = 0x90, + K_ASCII_249 = 0x91, + K_ASCII_250 = 0x92, + K_ASCII_252 = 0x93, + K_END_ASCII_CHARS = 0x94, + K_COMMAND = 0x96, + K_CAPSLOCK = 0x97, + K_POWER = 0x98, + K_PAUSE = 0x99, + K_UPARROW = 0x9A, + K_DOWNARROW = 0x9B, + K_LEFTARROW = 0x9C, + K_RIGHTARROW = 0x9D, + K_ALT = 0x9E, + K_CTRL = 0x9F, + K_SHIFT = 0xA0, + K_INS = 0xA1, + K_DEL = 0xA2, + K_PGDN = 0xA3, + K_PGUP = 0xA4, + K_HOME = 0xA5, + K_END = 0xA6, + K_F1 = 0xA7, + K_F2 = 0xA8, + K_F3 = 0xA9, + K_F4 = 0xAA, + K_F5 = 0xAB, + K_F6 = 0xAC, + K_F7 = 0xAD, + K_F8 = 0xAE, + K_F9 = 0xAF, + K_F10 = 0xB0, + K_F11 = 0xB1, + K_F12 = 0xB2, + K_F13 = 0xB3, + K_F14 = 0xB4, + K_F15 = 0xB5, + K_KP_HOME = 0xB6, + K_KP_UPARROW = 0xB7, + K_KP_PGUP = 0xB8, + K_KP_LEFTARROW = 0xB9, + K_KP_5 = 0xBA, + K_KP_RIGHTARROW = 0xBB, + K_KP_END = 0xBC, + K_KP_DOWNARROW = 0xBD, + K_KP_PGDN = 0xBE, + K_KP_ENTER = 0xBF, + K_KP_INS = 0xC0, + K_KP_DEL = 0xC1, + K_KP_SLASH = 0xC2, + K_KP_MINUS = 0xC3, + K_KP_PLUS = 0xC4, + K_KP_NUMLOCK = 0xC5, + K_KP_STAR = 0xC6, + K_KP_EQUALS = 0xC7, + K_MOUSE1 = 0xC8, + K_MOUSE2 = 0xC9, + K_MOUSE3 = 0xCA, + K_MOUSE4 = 0xCB, + K_MOUSE5 = 0xCC, + K_MWHEELDOWN = 0xCD, + K_MWHEELUP = 0xCE, + K_AUX1 = 0xCF, + K_AUX2 = 0xD0, + K_AUX3 = 0xD1, + K_AUX4 = 0xD2, + K_AUX5 = 0xD3, + K_AUX6 = 0xD4, + K_AUX7 = 0xD5, + K_AUX8 = 0xD6, + K_AUX9 = 0xD7, + K_AUX10 = 0xD8, + K_AUX11 = 0xD9, + K_AUX12 = 0xDA, + K_AUX13 = 0xDB, + K_AUX14 = 0xDC, + K_AUX15 = 0xDD, + K_AUX16 = 0xDE, + K_LAST_KEY = 0xDF, + }; + + enum uiMenuCommand_t + { + UIMENU_NONE = 0x0, + UIMENU_MAIN = 0x1, + UIMENU_INGAME = 0x2, + UIMENU_PREGAME = 0x3, + UIMENU_POSTGAME = 0x4, + UIMENU_SCRIPT_POPUP = 0x5, + UIMENU_SCOREBOARD = 0x6, + UIMENU_PARTY = 0x7, + UIMENU_GAMELOBBY = 0x8, + UIMENU_PRIVATELOBBY = 0x9, + UIMENU_ENDOFGAME = 0xA, + UIMENU_MIGRATION = 0xB, + }; + struct __declspec(align(4)) PhysPreset { const char *name; @@ -600,6 +801,636 @@ namespace Game MaterialShaderArgument *args; }; + enum OffhandClass + { + OFFHAND_CLASS_NONE = 0x0, + OFFHAND_CLASS_FRAG_GRENADE = 0x1, + OFFHAND_CLASS_SMOKE_GRENADE = 0x2, + OFFHAND_CLASS_FLASH_GRENADE = 0x3, + OFFHAND_CLASS_THROWINGKNIFE = 0x4, + OFFHAND_CLASS_OTHER = 0x5, + OFFHAND_CLASS_COUNT = 0x6, + }; + + enum ViewLockTypes + { + PLAYERVIEWLOCK_NONE = 0x0, + PLAYERVIEWLOCK_FULL = 0x1, + PLAYERVIEWLOCK_WEAPONJITTER = 0x2, + PLAYERVIEWLOCKCOUNT = 0x3, + }; + + struct SprintState + { + int sprintButtonUpRequired; + int sprintDelay; + int lastSprintStart; + int lastSprintEnd; + int sprintStartMaxLength; + }; + + + /* 1018 */ + struct MantleState + { + float yaw; + int timer; + int transIndex; + int flags; + }; + + /* 1019 */ + struct PlayerActiveWeaponState + { + int weapAnim; + int weaponTime; + int weaponDelay; + int weaponRestrictKickTime; + int weaponState; + int weapHandFlags; + unsigned int weaponShotCount; + }; + + /* 1020 */ + struct PlayerEquippedWeaponState + { + bool usedBefore; + bool dualWielding; + char weaponModel; + bool needsRechamber[2]; + }; + + /* 1021 */ + struct GlobalAmmo + { + int ammoType; + int ammoCount; + }; + + /* 1022 */ + struct ClipAmmo + { + int clipIndex; + int ammoCount[2]; + }; + + enum PlayerHandIndex + { + WEAPON_HAND_RIGHT = 0x0, + WEAPON_HAND_LEFT = 0x1, + NUM_WEAPON_HANDS = 0x2, + WEAPON_HAND_DEFAULT = 0x0, + }; + + /* 1023 */ + struct PlayerWeaponCommonState + { + int offHandIndex; + OffhandClass offhandPrimary; + OffhandClass offhandSecondary; + unsigned int weapon; + unsigned int primaryWeaponForAltMode; + int weapFlags; + float fWeaponPosFrac; + float aimSpreadScale; + int adsDelayTime; + int spreadOverride; + int spreadOverrideState; + PlayerHandIndex lastWeaponHand; + GlobalAmmo ammoNotInClip[15]; + ClipAmmo ammoInClip[15]; + int weapLockFlags; + int weapLockedEntnum; + float weapLockedPos[3]; + int weaponIdleTime; + }; + + enum ActionSlotType + { + ACTIONSLOTTYPE_DONOTHING = 0x0, + ACTIONSLOTTYPE_SPECIFYWEAPON = 0x1, + ACTIONSLOTTYPE_ALTWEAPONTOGGLE = 0x2, + ACTIONSLOTTYPE_NIGHTVISION = 0x3, + ACTIONSLOTTYPECOUNT = 0x4, + }; + + /* 1024 */ + struct ActionSlotParam_SpecifyWeapon + { + unsigned int index; + }; + + /* 1025 */ + struct ActionSlotParam + { + ActionSlotParam_SpecifyWeapon specifyWeapon; + }; + + enum objectiveState_t + { + OBJST_EMPTY = 0x0, + OBJST_ACTIVE = 0x1, + OBJST_INVISIBLE = 0x2, + OBJST_DONE = 0x3, + OBJST_CURRENT = 0x4, + OBJST_FAILED = 0x5, + OBJST_NUMSTATES = 0x6, + }; + + /* 1026 */ + struct objective_t + { + objectiveState_t state; + float origin[3]; + int entNum; + int teamNum; + int icon; + }; + + + /* 104 */ + enum he_type_t + { + HE_TYPE_FREE = 0x0, + HE_TYPE_TEXT = 0x1, + HE_TYPE_VALUE = 0x2, + HE_TYPE_PLAYERNAME = 0x3, + HE_TYPE_MAPNAME = 0x4, + HE_TYPE_GAMETYPE = 0x5, + HE_TYPE_MATERIAL = 0x6, + HE_TYPE_TIMER_DOWN = 0x7, + HE_TYPE_TIMER_UP = 0x8, + HE_TYPE_TIMER_STATIC = 0x9, + HE_TYPE_TENTHS_TIMER_DOWN = 0xA, + HE_TYPE_TENTHS_TIMER_UP = 0xB, + HE_TYPE_TENTHS_TIMER_STATIC = 0xC, + HE_TYPE_CLOCK_DOWN = 0xD, + HE_TYPE_CLOCK_UP = 0xE, + HE_TYPE_WAYPOINT = 0xF, + HE_TYPE_COUNT = 0x10, + }; + + struct hud_color + { + char r; + char g; + char b; + char a; + }; + + /* 1028 */ + union hudelem_color_t + { + hud_color __s0; + int rgba; + }; + + struct hudelem_s + { + he_type_t type; + float x; + float y; + float z; + int targetEntNum; + float fontScale; + float fromFontScale; + int fontScaleStartTime; + int fontScaleTime; + int font; + int alignOrg; + int alignScreen; + hudelem_color_t color; + hudelem_color_t fromColor; + int fadeStartTime; + int fadeTime; + int label; + int width; + int height; + int materialIndex; + int fromWidth; + int fromHeight; + int scaleStartTime; + int scaleTime; + float fromX; + float fromY; + int fromAlignOrg; + int fromAlignScreen; + int moveStartTime; + int moveTime; + int time; + int duration; + float value; + int text; + float sort; + hudelem_color_t glowColor; + int fxBirthTime; + int fxLetterTime; + int fxDecayStartTime; + int fxDecayDuration; + int soundID; + int flags; + }; + + struct $3EB5F037EADAEE8E2FA2A1F9FFF31312 + { + hudelem_s current[31]; + hudelem_s archival[31]; + }; + + enum pmtype_t + { + PM_NORMAL = 0x0, + PM_NORMAL_LINKED = 0x1, + PM_NOCLIP = 0x2, + PM_UFO = 0x3, + PM_MPVIEWER = 0x4, + PM_SPECTATOR = 0x5, + PM_INTERMISSION = 0x6, + PM_LASTSTAND = 0x7, + PM_DEAD = 0x8, + PM_DEAD_LINKED = 0x9, + }; + + struct playerState_s + { + int commandTime; + int pm_type; + int pm_time; + int pm_flags; + int otherFlags; + int linkFlags; + int bobCycle; + float origin[3]; + float velocity[3]; + int grenadeTimeLeft; + int throwbackGrenadeOwner; + int throwbackGrenadeTimeLeft; + unsigned int throwbackWeaponIndex; + int remoteEyesEnt; + int remoteEyesTagname; + int remoteControlEnt; + int foliageSoundTime; + int gravity; + float leanf; + int speed; + float delta_angles[3]; + int groundEntityNum; + float vLadderVec[3]; + int jumpTime; + float jumpOriginZ; + int legsTimer; + int legsAnim; + int torsoTimer; + int torsoAnim; + int legsAnimDuration; + int torsoAnimDuration; + int damageTimer; + int damageDuration; + int flinchYawAnim; + int corpseIndex; + int movementDir; + int eFlags; + int eventSequence; + int events[4]; + unsigned int eventParms[4]; + int oldEventSequence; + int unpredictableEventSequence; + int unpredictableEventSequenceOld; + int unpredictableEvents[4]; + unsigned int unpredictableEventParms[4]; + int clientNum; + int viewmodelIndex; + float viewangles[3]; + int viewHeightTarget; + float viewHeightCurrent; + int viewHeightLerpTime; + int viewHeightLerpTarget; + int viewHeightLerpDown; + float viewAngleClampBase[2]; + float viewAngleClampRange[2]; + int damageEvent; + int damageYaw; + int damagePitch; + int damageCount; + int damageFlags; + int stats[4]; + float proneDirection; + float proneDirectionPitch; + float proneTorsoPitch; + ViewLockTypes viewlocked; + int viewlocked_entNum; + float linkAngles[3]; + float linkWeaponAngles[3]; + int linkWeaponEnt; + int loopSound; + int cursorHint; + int cursorHintString; + int cursorHintEntIndex; + int cursorHintDualWield; + int iCompassPlayerInfo; + int radarEnabled; + int radarBlocked; + int radarMode; + int locationSelectionInfo; + SprintState sprintState; + float holdBreathScale; + int holdBreathTimer; + float moveSpeedScaleMultiplier; + MantleState mantleState; + PlayerActiveWeaponState weapState[2]; + unsigned int weaponsEquipped[15]; + PlayerEquippedWeaponState weapEquippedData[15]; + PlayerWeaponCommonState weapCommon; + float meleeChargeYaw; + int meleeChargeDist; + int meleeChargeTime; + unsigned int perks[2]; + unsigned int perkSlots[8]; + ActionSlotType actionSlotType[4]; + ActionSlotParam actionSlotParam[4]; + int weaponHudIconOverrides[6]; + int animScriptedType; + int shellshockIndex; + int shellshockTime; + int shellshockDuration; + float dofNearStart; + float dofNearEnd; + float dofFarStart; + float dofFarEnd; + float dofNearBlur; + float dofFarBlur; + float dofViewmodelStart; + float dofViewmodelEnd; + objective_t objective[32]; + int deltaTime; + int killCamEntity; + int killCamLookAtEntity; + int killCamClientNum; + $3EB5F037EADAEE8E2FA2A1F9FFF31312 hud; + unsigned int partBits[6]; + int recoilScale; + int diveDirection; + int stunTime; + }; + + enum LocSelInputState + { + LOC_SEL_INPUT_NONE = 0x0, + LOC_SEL_INPUT_CONFIRM = 0x1, + LOC_SEL_INPUT_CANCEL = 0x2, + }; + + struct field_t + { + int cursor; + int scroll; + int drawWidth; + int widthInPixels; + float charHeight; + int fixedSize; + char buffer[256]; + }; + + struct KeyState + { + int down; + int repeats; + const char* binding; + }; + + struct PlayerKeyState + { + field_t chatField; + int chat_team; + int overstrikeMode; + int anyKeyDown; + KeyState keys[256]; + LocSelInputState locSelInputState; + }; + + struct keyname_t + { + const char* name; + int keynum; + }; + + struct clSnapshot_t + { + playerState_s ps; + int valid; + int snapFlags; + int serverTime; + int messageNum; + int deltaNum; + int ping; + int cmdNum; + int numEntities; + int numClients; + int parseEntitiesIndex; + int parseClientsIndex; + int serverCommandNum; + }; + + enum StanceState + { + CL_STANCE_STAND = 0x0, + CL_STANCE_CROUCH = 0x1, + CL_STANCE_PRONE = 0x2, + }; + + struct ClientArchiveData + { + int serverTime; + float origin[3]; + float velocity[3]; + int bobCycle; + int movementDir; + float viewangles[3]; + int locationSelectionInfo; + float selectedLocation[2]; + float selectedLocationAngle; + }; + + struct outPacket_t + { + int p_cmdNumber; + int p_serverTime; + int p_realtime; + }; + + enum team_t + { + TEAM_FREE = 0x0, + TEAM_AXIS = 0x1, + TEAM_ALLIES = 0x2, + TEAM_SPECTATOR = 0x3, + TEAM_NUM_TEAMS = 0x4, + }; + + struct clientState_s + { + int clientIndex; + team_t team; + int modelindex; + int dualWielding; + int riotShieldNext; + int attachModelIndex[6]; + int attachTagIndex[6]; + char name[16]; + float maxSprintTimeMultiplier; + int rank; + int prestige; + unsigned int perks[2]; + int diveState; + int voiceConnectivityBits; + unsigned int playerCardIcon; + unsigned int playerCardTitle; + unsigned int playerCardNameplate; + }; + + enum usercmdButtonBits + { + CMD_BUTTON_ATTACK = 0x1, + CMD_BUTTON_SPRINT = 0x2, + CMD_BUTTON_MELEE = 0x4, + CMD_BUTTON_ACTIVATE = 0x8, + CMD_BUTTON_RELOAD = 0x10, + CMD_BUTTON_USE_RELOAD = 0x20, + CMD_BUTTON_PRONE = 0x100, + CMD_BUTTON_CROUCH = 0x200, + CMD_BUTTON_UP = 0x400, + CMD_BUTTON_ADS = 0x800, + CMD_BUTTON_DOWN = 0x1000, + CMD_BUTTON_BREATH = 0x2000, + CMD_BUTTON_FRAG = 0x4000, + CMD_BUTTON_OFFHAND_SECONDARY = 0x8000, + CMD_BUTTON_THROW = 0x80000, + }; + +#pragma pack(push, 4) + struct usercmd_s + { + int serverTime; + int buttons; + int angles[3]; + unsigned __int16 weapon; + unsigned __int16 primaryWeaponForAltMode; + unsigned __int16 offHandIndex; + char forwardmove; + char rightmove; + float meleeChargeYaw; + char meleeChargeDist; + char selectedLoc[2]; + char selectedLocAngle; + char remoteControlAngles[2]; + }; +#pragma pack(pop) + + struct LerpEntityState + { + char pad[0x70]; + }; + + struct clientLinkInfo_t + { + __int16 parentId; + char tagName; + char flags; + }; + + struct entityState_s + { + int number; + int eType; + LerpEntityState lerp; + int time2; + int otherEntityNum; + int attackerEntityNum; + int groundEntityNum; + int loopSound; + int surfType; + + union + { + int brushModel; + int triggerModel; + int item; + int xmodel; + int primaryLight; + } index; + + int clientNum; + int iHeadIcon; + int iHeadIconTeam; + int solid; + unsigned int eventParm; + int eventSequence; + int events[4]; + unsigned int eventParms[4]; + unsigned __int16 weapon; + int legsAnim; + int torsoAnim; + int un1; + int un2; + clientLinkInfo_t clientLinkInfo; + unsigned int partBits[6]; + int clientMask[1]; + }; + + struct clientActive_t + { + bool usingAds; + int timeoutcount; + clSnapshot_t snap; + bool alwaysFalse; + int serverTime; + int oldServerTime; + int oldFrameServerTime; + int serverTimeDelta; + int oldSnapServerTime; + int extrapolatedSnapshot; + int newSnapshots; + int serverId; + char mapname[64]; + int parseEntitiesIndex; + int parseClientsIndex; + int mouseDx[2]; + int mouseDy[2]; + int mouseIndex; + bool stanceHeld; + StanceState stance; + StanceState stancePosition; + int stanceTime; + int cgameUserCmdWeapon; + int cgameUserCmdOffHandIndex; + float cgameFOVSensitivityScale; + float cgameMaxPitchSpeed; + float cgameMaxYawSpeed; + float cgameKickAngles[3]; + float cgameOrigin[3]; + float cgameVelocity[3]; + float cgameViewangles[3]; + int cgameBobCycle; + int cgameMovementDir; + int cgameExtraButtons; + int cgamePredictedDataServerTime; + float clViewangles[3]; + usercmd_s cmds[128]; + int cmdNumber; + ClientArchiveData clientArchive[256]; + int clientArchiveIndex; + int packetBackupCount; + int packetBackupMask; + int parseEntitiesCount; + int parseClientsCount; + outPacket_t outPackets[32]; + clSnapshot_t snapshots[32]; + entityState_s parseEntities[19200]; + clientState_s parseClients[576]; + int corruptedTranslationFile; + char translationVersion[256]; + }; + struct MaterialTechnique { const char *name; @@ -2716,18 +3547,6 @@ namespace Game WEAPON_FIRETYPE_BURSTFIRE_FIRST = 0x2, WEAPON_FIRETYPE_BURSTFIRE_LAST = 0x4, }; - - enum OffhandClass - { - OFFHAND_CLASS_NONE = 0x0, - OFFHAND_CLASS_FRAG_GRENADE = 0x1, - OFFHAND_CLASS_SMOKE_GRENADE = 0x2, - OFFHAND_CLASS_FLASH_GRENADE = 0x3, - OFFHAND_CLASS_THROWINGKNIFE = 0x4, - OFFHAND_CLASS_OTHER = 0x5, - OFFHAND_CLASS_COUNT = 0x6, - }; - enum weapStance_t { WEAPSTANCE_STAND = 0x0, @@ -3976,25 +4795,6 @@ namespace Game }; #pragma pack(pop) -#pragma pack(push, 4) - struct usercmd_s - { - int serverTime; - int buttons; - int angles[3]; - unsigned __int16 weapon; - unsigned __int16 primaryWeaponForAltMode; - unsigned __int16 offHandIndex; - char forwardmove; - char rightmove; - float meleeChargeYaw; - char meleeChargeDist; - char selectedLoc[2]; - char selectedLocAngle; - char remoteControlAngles[2]; - }; -#pragma pack(pop) - typedef char mapname_t[40]; struct traceWork_t @@ -4500,60 +5300,15 @@ namespace Game unsigned int team; char pad2[436]; int flags; - char pad3[724]; + int spectatorClient; + int lastCmdTime; + int buttons; + int oldbuttons; + int latched_buttons; + int buttonsSinceLastFrame; + char pad3[700]; } gclient_t; - struct LerpEntityState - { - char pad[0x70]; - }; - - struct clientLinkInfo_t - { - __int16 parentId; - char tagName; - char flags; - }; - - struct entityState_s - { - int number; - int eType; - LerpEntityState lerp; - int time2; - int otherEntityNum; - int attackerEntityNum; - int groundEntityNum; - int loopSound; - int surfType; - - union - { - int brushModel; - int triggerModel; - int item; - int xmodel; - int primaryLight; - } index; - - int clientNum; - int iHeadIcon; - int iHeadIconTeam; - int solid; - unsigned int eventParm; - int eventSequence; - int events[4]; - unsigned int eventParms[4]; - unsigned __int16 weapon; - int legsAnim; - int torsoAnim; - int un1; - int un2; - clientLinkInfo_t clientLinkInfo; - unsigned int partBits[6]; - int clientMask[1]; - }; - struct EntHandle { unsigned __int16 number; @@ -5276,6 +6031,411 @@ namespace Game int allowAddDObj; }; + struct AimInput + { + float deltaTime; + float deltaTimeScaled; + float pitch; + float pitchAxis; + float pitchMax; + float yaw; + float yawAxis; + float yawMax; + float forwardAxis; + float rightAxis; + int buttons; + int localClientNum; + }; + + struct AimOutput + { + float pitch; + float yaw; + float meleeChargeYaw; + char meleeChargeDist; + }; + + struct clientLogo_t + { + int startTime; + int duration; + int fadein; + int fadeout; + Material* material[2]; + }; + + struct vidConfig_t + { + unsigned int sceneWidth; + unsigned int sceneHeight; + unsigned int displayWidth; + unsigned int displayHeight; + unsigned int displayFrequency; + int isFullscreen; + float aspectRatioWindow; + float aspectRatioScenePixel; + float aspectRatioDisplayPixel; + unsigned int maxTextureSize; + unsigned int maxTextureMaps; + bool deviceSupportsGamma; + }; + + struct trDebugLine_t + { + float start[3]; + float end[3]; + float color[4]; + int depthTest; + }; + + struct trDebugString_t + { + float xyz[3]; + float color[4]; + float scale; + char text[96]; + }; + + struct clientDebugStringInfo_t + { + int max; + int num; + trDebugString_t* strings; + int* durations; + }; + + struct clientDebugLineInfo_t + { + int max; + int num; + trDebugLine_t* lines; + int* durations; + }; + + struct clientDebug_t + { + int prevFromServer; + int fromServer; + clientDebugStringInfo_t clStrings; + clientDebugStringInfo_t svStringsBuffer; + clientDebugStringInfo_t svStrings; + clientDebugLineInfo_t clLines; + clientDebugLineInfo_t svLinesBuffer; + clientDebugLineInfo_t svLines; + }; + + struct ClientMatchData + { + char def[64]; + char data[1024]; + }; + + struct clientStatic_t + { + int quit; + int hunkUsersStarted; + char servername[256]; + int rendererStarted; + int soundStarted; + int uiStarted; + int frametime; + float frametime_base; + int realtime; + bool gpuSyncedPrevFrame; + bool inputUpdatedPrevFrame; + clientLogo_t logo; + float mapCenter[3]; + int lastServerPinged; + int pingedServerCount; + int totalServersParsed; + int pingUpdateSource; + Material* whiteMaterial; + Material* consoleMaterial; + Font_s* consoleFont; + vidConfig_t vidConfig; + clientDebug_t debug; + int doVidRestart; + ClientMatchData matchData; + XNADDR xnaddrs[18]; + float debugRenderPos[3]; + int skelValid; + int skelTimeStamp; + volatile int skelMemPos; + char skelMemory[262144]; + char* skelMemoryStart; + bool allowedAllocSkel; + int serverId; + gameState_t gameState; + clSnapshot_t noDeltaSnapshot; + int nextNoDeltaEntity; + entityState_s noDeltaEntities[1024]; + }; + + struct GraphFloat + { + char name[64]; + float knots[32][2]; + unsigned __int16 knotCount; + float scale; + }; + + struct __declspec(align(8)) cg_s + { + playerState_s predictedPlayerState; + char _pad0[0x254]; + void* snap; + void* nextSnap; + char _pad1[0x673DC]; + int frametime; // + 0x6A754 + char _pad2[0x960C]; // + 0x6A758 + float compassMapWorldSize[2]; // + 0x73D64 + char _pad3[0x74]; // + 0x73D6C + float selectedLocation[2]; // + 0x73DE0 + float selectedLocationAngle; + float selectedAngleLocation[2]; + float selectedLocationPrev[2]; + float selectedLocationAnglePrev; + char _pad4[0x89740]; + }; + + static constexpr auto MAX_GAMEPADS = 1; + + static constexpr auto GPAD_VALUE_MASK = 0xFFFFFFFu; + static constexpr auto GPAD_DPAD_MASK = XINPUT_GAMEPAD_DPAD_UP | XINPUT_GAMEPAD_DPAD_DOWN | XINPUT_GAMEPAD_DPAD_LEFT | XINPUT_GAMEPAD_DPAD_RIGHT; + static constexpr auto GPAD_DIGITAL_MASK = 1u << 28; + static constexpr auto GPAD_ANALOG_MASK = 1u << 29; + static constexpr auto GPAD_STICK_MASK = 1u << 30; + + enum GamePadButton + { + GPAD_NONE = 0, + GPAD_UP = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_DPAD_UP & GPAD_VALUE_MASK), + GPAD_DOWN = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_DPAD_DOWN & GPAD_VALUE_MASK), + GPAD_LEFT = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_DPAD_LEFT & GPAD_VALUE_MASK), + GPAD_RIGHT = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_DPAD_RIGHT & GPAD_VALUE_MASK), + GPAD_START = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_START & GPAD_VALUE_MASK), + GPAD_BACK = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_BACK & GPAD_VALUE_MASK), + GPAD_L3 = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_LEFT_THUMB & GPAD_VALUE_MASK), + GPAD_R3 = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_RIGHT_THUMB & GPAD_VALUE_MASK), + GPAD_L_SHLDR = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_LEFT_SHOULDER & GPAD_VALUE_MASK), + GPAD_R_SHLDR = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_RIGHT_SHOULDER & GPAD_VALUE_MASK), + GPAD_A = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_A & GPAD_VALUE_MASK), + GPAD_B = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_B & GPAD_VALUE_MASK), + GPAD_X = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_X & GPAD_VALUE_MASK), + GPAD_Y = GPAD_DIGITAL_MASK | (XINPUT_GAMEPAD_Y & GPAD_VALUE_MASK), + GPAD_L_TRIG = GPAD_ANALOG_MASK | (0 & GPAD_VALUE_MASK), + GPAD_R_TRIG = GPAD_ANALOG_MASK | (1 & GPAD_VALUE_MASK), + }; + + enum GamePadStick + { + GPAD_INVALID = 0x0, + GPAD_LX = GPAD_STICK_MASK | (0 & GPAD_VALUE_MASK), + GPAD_LY = GPAD_STICK_MASK | (1 & GPAD_VALUE_MASK), + GPAD_RX = GPAD_STICK_MASK | (2 & GPAD_VALUE_MASK), + GPAD_RY = GPAD_STICK_MASK | (3 & GPAD_VALUE_MASK), + }; + + enum GamePadButtonEvent + { + GPAD_BUTTON_RELEASED = 0x0, + GPAD_BUTTON_PRESSED = 0x1, + GPAD_BUTTON_UPDATE = 0x2, + }; + + enum GamepadPhysicalAxis + { + GPAD_PHYSAXIS_NONE = -1, + GPAD_PHYSAXIS_RSTICK_X = 0x0, + GPAD_PHYSAXIS_RSTICK_Y = 0x1, + GPAD_PHYSAXIS_LSTICK_X = 0x2, + GPAD_PHYSAXIS_LSTICK_Y = 0x3, + GPAD_PHYSAXIS_RTRIGGER = 0x4, + GPAD_PHYSAXIS_LTRIGGER = 0x5, + + GPAD_PHYSAXIS_COUNT, + }; + + enum GamepadVirtualAxis + { + GPAD_VIRTAXIS_NONE = -1, + GPAD_VIRTAXIS_SIDE = 0x0, + GPAD_VIRTAXIS_FORWARD = 0x1, + GPAD_VIRTAXIS_UP = 0x2, + GPAD_VIRTAXIS_YAW = 0x3, + GPAD_VIRTAXIS_PITCH = 0x4, + GPAD_VIRTAXIS_ATTACK = 0x5, + + GPAD_VIRTAXIS_COUNT + }; + + enum GamePadStickDir + { + GPAD_STICK_POS = 0x0, + GPAD_STICK_NEG = 0x1, + + GPAD_STICK_DIR_COUNT + }; + + enum GamepadMapping + { + GPAD_MAP_NONE = -1, + GPAD_MAP_LINEAR = 0x0, + GPAD_MAP_SQUARED = 0x1, + + GPAD_MAP_COUNT + }; + + struct ButtonToCodeMap_t + { + GamePadButton padButton; + int code; + }; + + struct StickToCodeMap_t + { + GamePadStick padStick; + int posCode; + int negCode; + }; + + struct GamepadVirtualAxisMapping + { + GamepadPhysicalAxis physicalAxis; + GamepadMapping mapType; + }; + + struct GpadAxesGlob + { + float axesValues[GPAD_PHYSAXIS_COUNT]; + GamepadVirtualAxisMapping virtualAxes[GPAD_VIRTAXIS_COUNT]; + }; + + enum weaponstate_t + { + WEAPON_READY = 0x0, + WEAPON_RAISING = 0x1, + WEAPON_RAISING_ALTSWITCH = 0x2, + WEAPON_DROPPING = 0x3, + WEAPON_DROPPING_QUICK = 0x4, + WEAPON_DROPPING_ALT = 0x5, + WEAPON_FIRING = 0x6, + WEAPON_RECHAMBERING = 0x7, + WEAPON_RELOADING = 0x8, + WEAPON_RELOADING_INTERUPT = 0x9, + WEAPON_RELOAD_START = 0xA, + WEAPON_RELOAD_START_INTERUPT = 0xB, + WEAPON_RELOAD_END = 0xC, + WEAPON_MELEE_INIT = 0xD, + WEAPON_MELEE_FIRE = 0xE, + WEAPON_MELEE_END = 0xF, + WEAPON_OFFHAND_INIT = 0x10, + WEAPON_OFFHAND_PREPARE = 0x11, + WEAPON_OFFHAND_HOLD = 0x12, + WEAPON_OFFHAND_FIRE = 0x13, + WEAPON_OFFHAND_DETONATE = 0x14, + WEAPON_OFFHAND_END = 0x15, + WEAPON_DETONATING = 0x16, + WEAPON_SPRINT_RAISE = 0x17, + WEAPON_SPRINT_LOOP = 0x18, + WEAPON_SPRINT_DROP = 0x19, + WEAPON_STUNNED_START = 0x1A, + WEAPON_STUNNED_LOOP = 0x1B, + WEAPON_STUNNED_END = 0x1C, + WEAPON_NIGHTVISION_WEAR = 0x1D, + WEAPON_NIGHTVISION_REMOVE = 0x1E, + + WEAPONSTATES_NUM + }; + + struct AimAssistPlayerState + { + float velocity[3]; + int eFlags; + int linkFlags; + int pm_flags; + int weapFlags; + int weaponState; + float fWeaponPosFrac; + int weapIndex; + bool hasAmmo; + bool isDualWielding; + bool isThirdPerson; + bool isExtendedMelee; + }; + + struct AimTweakables + { + float slowdownRegionWidth; + float slowdownRegionHeight; + float autoAimRegionWidth; + float autoAimRegionHeight; + float autoMeleeRegionWidth; + float autoMeleeRegionHeight; + float lockOnRegionWidth; + float lockOnRegionHeight; + }; + + constexpr auto AIM_TARGET_INVALID = 0x3FF; + struct AimScreenTarget + { + int entIndex; + float clipMins[2]; + float clipMaxs[2]; + float aimPos[3]; + float velocity[3]; + float distSqr; + float crosshairDistSqr; + }; + + enum AutoMeleeState + { + AIM_MELEE_STATE_OFF = 0x0, + AIM_MELEE_STATE_TARGETED = 0x1, + AIM_MELEE_STATE_UPDATING = 0x2, + }; + +#pragma warning(push) +#pragma warning(disable: 4324) + struct __declspec(align(16)) AimAssistGlobals + { + AimAssistPlayerState ps; + char _pad1[4]; + float screenMtx[4][4]; + float invScreenMtx[4][4]; + bool initialized; + int prevButtons; + AimTweakables tweakables; + float eyeOrigin[3]; + float viewOrigin[3]; + float viewAngles[3]; + float viewAxis[3][3]; + float fovTurnRateScale; + float fovScaleInv; + float adsLerp; + float pitchDelta; + float yawDelta; + float screenWidth; + float screenHeight; + AimScreenTarget screenTargets[64]; + int screenTargetCount; + int autoAimTargetEnt; + bool autoAimPressed; + bool autoAimActive; + float autoAimPitch; + float autoAimPitchTarget; + float autoAimYaw; + float autoAimYawTarget; + AutoMeleeState autoMeleeState; + int autoMeleeTargetEnt; + float autoMeleePitch; + float autoMeleePitchTarget; + float autoMeleeYaw; + float autoMeleeYawTarget; + int lockOnTargetEnt; + }; +#pragma warning(pop) + #pragma endregion #ifndef IDA diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index 3a7fc61a..208e0cab 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -10,6 +10,7 @@ #define VC_EXTRALEAN #define WIN32_LEAN_AND_MEAN #define _CRT_SECURE_NO_WARNINGS +#define _USE_MATH_DEFINES // Requires Visual Leak Detector plugin: http://vld.codeplex.com/ #define VLD_FORCE_ENABLE @@ -39,6 +40,8 @@ #include #include #include +#include +#include // Experimental C++17 features #include @@ -49,6 +52,9 @@ #include #pragma comment(lib, "D3dx9.lib") +#include +#pragma comment (lib, "xinput.lib") + // Usefull for debugging template class Sizer { }; #define BindNum(x, y) Sizer y;