diff --git a/premake5.lua b/premake5.lua index 91bee2df..20744831 100644 --- a/premake5.lua +++ b/premake5.lua @@ -79,6 +79,11 @@ newoption { description = "Zonebuilder generates iw4x zones that cannot be loaded without IW4x specific patches." } +newoption { + trigger = "aimassist-enable", + description = "Enables code for controller aim assist." +} + newaction { trigger = "version", description = "Returns the version string for the current commit of the source code.", @@ -332,6 +337,9 @@ workspace "iw4x" if _OPTIONS["iw4x-zones"] then defines { "GENERATE_IW4X_SPECIFIC_ZONES" } end + if _OPTIONS["aimassist-enable"] then + defines { "AIM_ASSIST_ENABLED" } + end -- Pre-compiled header pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp index 9b72f5d9..9c4e9613 100644 --- a/src/Components/Modules/Gamepad.cpp +++ b/src/Components/Modules/Gamepad.cpp @@ -1,6 +1,7 @@ #include "STDInclude.hpp" #include +#include namespace Game { @@ -180,6 +181,17 @@ namespace Components 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; Dvar::Var Gamepad::xpadSensitivity; Dvar::Var Gamepad::xpadEarlyTime; @@ -348,6 +360,116 @@ namespace Components 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) + { + if ((ps->weapFlags & 2) == 0) + return false; + + 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); + } + + void Gamepad::AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output) + { +#ifdef AIM_ASSIST_ENABLED + + 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 (!aim_lockon_enabled.get() || AimAssist_IsPlayerUsingOffhand(&aaGlob.ps) || aaGlob.autoAimActive || aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) + 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; + } + +#endif + } + void Gamepad::AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis) { assert(input); @@ -390,14 +512,63 @@ namespace Components } } - void Gamepad::AimAssist_CalcSlowdown(const Game::AimInput* /*input*/, float* pitchScale, float* yawScale) + bool Gamepad::AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps) { - /*assert(input); */ + if (!aim_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; + + if (ps->eFlags & 0x300800) + 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; + +#ifdef AIM_ASSIST_ENABLED + + 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; + +#endif } float Gamepad::AimAssist_Lerp(const float from, const float to, const float fraction) @@ -483,6 +654,7 @@ namespace Components AimAssist_ApplyTurnRates(input, output); Game::AimAssist_ApplyAutoMelee(input, output); + AimAssist_ApplyLockOn(input, output); } aaGlob.prevButtons = input->buttons; @@ -1451,6 +1623,17 @@ namespace Components 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() diff --git a/src/Components/Modules/Gamepad.hpp b/src/Components/Modules/Gamepad.hpp index 6816f2d3..dbcdeb08 100644 --- a/src/Components/Modules/Gamepad.hpp +++ b/src/Components/Modules/Gamepad.hpp @@ -115,6 +115,43 @@ namespace Game 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]; @@ -142,7 +179,8 @@ namespace Game float lockOnRegionWidth; float lockOnRegionHeight; }; - + + constexpr auto AIM_TARGET_INVALID = 0x3FF; struct AimScreenTarget { int entIndex; @@ -273,6 +311,17 @@ namespace Components 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 Dvar::Var xpadSensitivity; static Dvar::Var xpadEarlyTime; @@ -289,7 +338,14 @@ namespace Components 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 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); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 6c2c35a5..c9edb167 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -30,6 +30,7 @@ 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); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 0ec533a3..1f762b72 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; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 80c293bd..7dc0736c 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -1288,6 +1288,16 @@ namespace Game 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, + }; + #pragma pack(push, 4) struct usercmd_s { diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index e48f1ae4..36f7d469 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