Improve Hitrate on rapidfire

This commit is contained in:
BenCat07 2020-12-07 15:40:54 +01:00
parent 13a0c43b4e
commit 2e79fcc0d3
24 changed files with 183 additions and 96 deletions

View File

@ -17,6 +17,7 @@ class ISurface;
class IPanel;
} // namespace vgui
class IToolFrameworkInternal;
class ISteamClient;
class ISteamFriends;
class IVEngineClient013;
@ -95,6 +96,7 @@ extern IUniformRandomStream *g_pUniformStream;
extern int *g_PredictionRandomSeed;
extern IFileSystem *g_IFileSystem;
extern IMDLCache *g_IMDLCache;
extern IToolFrameworkInternal *g_IToolFramework;
void CreateInterfaces();
void CreateEarlyInterfaces();

View File

@ -207,6 +207,7 @@ public:
offset_t m_iPlayerIndex;
offset_t m_hTargetPlayer;
offset_t m_flResetTime;
offset_t m_flMaxspeed;
};
extern NetVars netvar;

View File

@ -228,4 +228,8 @@ struct offsets
{
return PlatformOffset(0x2fb8, undefined, undefined);
}
static constexpr uint32_t Think()
{
return PlatformOffset(27, undefined, undefined);
}
};

View File

@ -18,6 +18,7 @@
#include <gametrace.h>
#include <engine/IEngineTrace.h>
#include <materialsystem/imaterialvar.h>
#include <toolframework/itoolframework.h>
#include <globalvars_base.h>
#include <materialsystem/itexture.h>
#include <engine/ivmodelinfo.h>

View File

@ -75,4 +75,5 @@ extern VMTHook materialsystem;
extern VMTHook enginevgui;
extern VMTHook vstd;
extern VMTHook eventmanager2;
extern VMTHook toolbox;
} // namespace hooks

View File

@ -15,6 +15,7 @@ enum ec_types
CreateMoveLate,
/* This kind of CreateMove will run earlier than "CreateMove" events
* and guranteed to run before EnginePrediction
* This is NEEDED for any kind of movement
*/
CreateMove_NoEnginePred,
/* Note: this is still CreateMove, just ran before original is called, needed in some cases like changing tickcount before original gets called*/

View File

@ -94,6 +94,8 @@ DECLARE_HOOKED_METHOD(EmitSound2, void, void *, IRecipientFilter &, int, int, co
DECLARE_HOOKED_METHOD(EmitSound3, void, void *, IRecipientFilter &, int, int, int, float, soundlevel_t, int, int, int, const Vector *, const Vector *, CUtlVector<Vector> *, bool, float, int);
// g_IPrediction
DECLARE_HOOKED_METHOD(RunCommand, void, IPrediction *, IClientEntity *, CUserCmd *, IMoveHelper *);
// g_IToolFramework
DECLARE_HOOKED_METHOD(Think, void, IToolFrameworkInternal *, bool);
} // namespace hooked_methods
// TODO

6
include/hooks/Think.hpp Normal file
View File

@ -0,0 +1,6 @@
#pragma once
namespace hooked_methods
{
// Update Prediction, Used by warp/Doubletap aswell
void UpdatePred();
} // namespace hooked_methods

View File

@ -26,6 +26,8 @@ public:
// Start of CM
void Update();
// After prediction
void UpdateEye();
// End of CM
void UpdateEnd();
int team;

View File

@ -82,6 +82,13 @@ public:
typedef bool (*fn_t)(IClientEntity *, bool, IClientEntity *);
return vfunc<fn_t>(self, offsets::PlatformOffset(490, offsets::undefined, 490), 0)(self, unknown1, unknown2);
}
inline static float ApplyFireDelay(IClientEntity *self, float delay)
{
typedef float (*ApplyFireDelay_t)(IClientEntity *, float);
static auto signature = gSignatures.GetClientSignature("55 89 E5 57 56 53 83 EC 6C C7 45 ? 00 00 00 00 A1 ? ? ? ? C7 45 ? 00 00 00 00 8B 5D ? 85 C0 0F 84 ? ? ? ? 8D 55 ? 89 04 24 31 F6 89 54 24 ? C7 44 24 ? ? ? ? ? C7 44 24 ? ? ? ? ? C7 44 24 ? ? ? ? ? C7 44 24 ? ? ? ? ? C7 44 24 ? 6B 00 00 00 C7 44 24 ? ? ? ? ? C7 44 24 ? 00 00 00 00 C7 44 24 ? 00 00 00 00 C7 44 24 ? 00 00 00 00 C7 44 24 ? 00 00 00 00 FF 50 ? A1 ? ? ? ? 8B 0D ? ? ? ? 8B 55 ? 89 45 ? 8B 45 ? 85 C9 89 55 ? 89 45 ? 0F 85 ? ? ? ? 85 DB 0F 84 ? ? ? ? 8B 7B ? 85 FF 0F 84 ? ? ? ? C7 04 24 ? ? ? ? E8 ? ? ? ? 89 45 ? 8B 07 89 3C 24 FF 10 8B 7D ? 8B 10 C7 44 24 ? 00 00 00 00 89 5C 24 ? C7 44 24 ? ? ? ? ? 89 7C 24 ? 89 04 24 FF 52 ? D9 5D ? F3 0F 10 45 ? F3 0F 11 04 24 E8 ? ? ? ? D9 5D");
static ApplyFireDelay_t ApplyFireDelay_fn = (ApplyFireDelay_t) signature;
return ApplyFireDelay_fn(self, delay);
}
inline static void AddToCritBucket(IClientEntity *self, float value)
{
constexpr float max_bucket_capacity = 1000.0f;

View File

@ -56,6 +56,7 @@ IUniformRandomStream *g_pUniformStream = nullptr;
int *g_PredictionRandomSeed = nullptr;
IFileSystem *g_IFileSystem = nullptr;
IMDLCache *g_IMDLCache = nullptr;
IToolFrameworkInternal *g_IToolFramework = nullptr;
template <typename T> T *BruteforceInterface(std::string name, sharedobj::SharedObject &object, int start = 0)
{
@ -169,6 +170,7 @@ void CreateInterfaces()
g_IStudioRender = BruteforceInterface<IStudioRender>("VStudioRender", sharedobj::studiorender());
g_IVRenderView = BruteforceInterface<IVRenderView>("VEngineRenderView", sharedobj::engine());
g_IMaterialSystemHL = (IMaterialSystem *) g_IMaterialSystem;
g_IToolFramework = BruteforceInterface<IToolFrameworkInternal>("VTOOLFRAMEWORKVERSION", sharedobj::engine());
IF_GAME(IsTF2())
{
g_pScreenSpaceEffects = **(IScreenSpaceEffectManager ***) (gSignatures.GetClientSignature("8D 74 26 00 55 89 E5 57 56 53 83 EC 1C 8B 5D 08 8B 7D 0C 8B 75 10 ") + 0x1c3);

View File

@ -33,6 +33,7 @@ void NetVars::Init()
IF_GAME(IsTF2())
{
this->m_flMaxspeed = gNetvars.get_offset("DT_BasePlayer", "m_flMaxspeed");
res_iTeam = gNetvars.get_offset("DT_TFPlayerResource", "baseclass", "m_iTeam");
res_bAlive = gNetvars.get_offset("DT_TFPlayerResource", "baseclass", "m_bAlive");
this->res_iMaxBuffedHealth = gNetvars.get_offset("DT_TFPlayerResource", "m_iMaxBuffedHealth");

View File

@ -257,6 +257,10 @@ void hack::Hook()
hooks::prediction.HookMethod(HOOK_ARGS(RunCommand));
hooks::prediction.Apply();
hooks::toolbox.Set(g_IToolFramework);
hooks::toolbox.HookMethod(HOOK_ARGS(Think));
hooks::toolbox.Apply();
#if ENABLE_VISUALS
sdl_hooks::applySdlHooks();
#endif

View File

@ -56,5 +56,5 @@ static void CreateMove()
if (!jump)
ticks_last_jump = 0;
}
static InitRoutine EC([]() { EC::Register(EC::CreateMove_NoEnginePred, CreateMove, "Bunnyhop", EC::average); });
static InitRoutine EC([]() { EC::Register(EC::CreateMove_NoEnginePred, CreateMove, "Bunnyhop", EC::early); });
} // namespace hacks::shared::bunnyhop

View File

@ -774,7 +774,7 @@ void rvarCallback(settings::VariableBase<int> &var, int after)
}
static InitRoutine runinit([]() {
EC::Register(EC::CreateMove, cm, "cm_followbot", EC::average);
EC::Register(EC::CreateMove_NoEnginePred, cm, "cm_followbot", EC::average);
#if ENABLE_VISUALS
EC::Register(EC::Draw, draw, "draw_followbot", EC::average);
#endif

View File

@ -1219,7 +1219,7 @@ static void cm()
}
static InitRoutine init([]() {
EC::Register(EC::CreateMove, cm, "cm_walkbot", EC::average);
EC::Register(EC::CreateMove_NoEnginePred, cm, "cm_walkbot", EC::average);
EC::Register(EC::LevelInit, OnLevelInit, "init_walkbot", EC::average);
#if ENABLE_VISUALS
EC::Register(EC::Draw, Draw, "draw_walkbot", EC::average);

View File

@ -14,6 +14,7 @@
#include "DetourHook.hpp"
#include "WeaponData.hpp"
#include "MiscTemporary.hpp"
#include "Think.hpp"
namespace hacks::tf2::warp
{
@ -109,6 +110,12 @@ bool UpdateRFKey()
return allow_key;
}
float getFireDelay()
{
auto weapon_data = GetWeaponData(RAW_ENT(LOCAL_W));
return re::C_TFWeaponBase::ApplyFireDelay(RAW_ENT(LOCAL_W), weapon_data->m_flTimeFireDelay);
}
bool shouldRapidfire()
{
if (!rapidfire)
@ -139,8 +146,7 @@ bool shouldRapidfire()
return false;
// We do not have the amount of ticks needed, don't try it
auto weapon_data = GetWeaponData(RAW_ENT(LOCAL_W));
if (warp_amount < TIME_TO_TICKS(weapon_data->m_flTimeFireDelay) && (TIME_TO_TICKS(weapon_data->m_flTimeFireDelay) < *maxusrcmdprocessticks - 1 || (wait_full && warp_amount != GetMaxWarpTicks())))
if (warp_amount < TIME_TO_TICKS(getFireDelay()) && (TIME_TO_TICKS(getFireDelay()) < *maxusrcmdprocessticks - 1 || (wait_full && warp_amount != GetMaxWarpTicks())))
return false;
// Mouse 1 is held, do it.
@ -175,9 +181,17 @@ int GetWarpAmount(bool finalTick)
{
int max_extra_ticks = *maxusrcmdprocessticks - 1;
// Rapidfire ignores speed
// Rapidfire ignores speed, and we send 15 + 7, aka maximum new + backup commands
if (in_rapidfire)
return max_extra_ticks;
{
// Warp right before the next shot
float shot_time = getFireDelay();
// This is to prevent Minigun/Pistol from only shooting once
if (TICKS_TO_TIME(22) / shot_time >= 2)
shot_time = TICKS_TO_TIME(23);
return std::min(22, TIME_TO_TICKS(shot_time) - 2);
// return 22;
}
// No limit set
if (!*maxusrcmdprocessticks)
max_extra_ticks = INT_MAX;
@ -239,6 +253,8 @@ void Warp(float accumulated_extra_samples, bool finalTick)
choke_packet = false;
packets_sent = -1;
}
else
hooked_methods::UpdatePred();
original(accumulated_extra_samples, finalTick);
// Only decrease ticks for the final CL_Move tick
@ -346,11 +362,6 @@ static bool was_overridden = false;
static int ticks_in_revved = 0;
static bool replaced_last_tick = false;
// Original Player origin and velocity, needed to not break because our engine pred.
// We adjust it as the ticks go.
static Vector original_origin;
static Vector original_velocity;
// Reset all the revv data
void resetRevvstate()
{
@ -414,15 +425,7 @@ void CL_Move_hook(float accumulated_extra_samples, bool bFinalTick)
{
in_warp = true;
if (shouldRapidfire())
{
in_rapidfire = true;
// Store original info
original_origin = LOCAL_E->m_vecOrigin();
velocity::EstimateAbsVelocity(RAW_ENT(LOCAL_E), original_velocity);
// Zero out non z movement, it will just get messy else
original_velocity.x = 0.0f;
original_velocity.y = 0.0f;
}
Warp(accumulated_extra_samples, bFinalTick);
if (warp_amount < GetMaxWarpTicks())
@ -449,26 +452,12 @@ void CreateMoveEarly()
}
}
// Fix the player origin after engine prediction, as it tries to predict the local
// player linearly and just breaks doubletap when falling/moving
void CreateMoveFixPrediction()
// Run before prediction so we can do Faststop logic
void CreateMovePrePredict()
{
if (hacks::tf2::warp::in_rapidfire && current_user_cmd)
{
// Run very simple gravity calculations to ensure we do not miss
if (no_movement)
{
static ConVar *sv_gravity = g_ICvar->FindVar("sv_gravity");
Vector gravity{ 0.0f, 0.0f, -sv_gravity->GetFloat() };
auto mins = RAW_ENT(LOCAL_E)->GetCollideable()->OBBMins();
auto maxs = RAW_ENT(LOCAL_E)->GetCollideable()->OBBMaxs();
std::pair<Vector, Vector> minmax{ mins, maxs };
PredictStep(original_origin, original_velocity, gravity, &minmax);
// Restore from the engine prediction
const_cast<Vector &>(RAW_ENT(LOCAL_E)->GetAbsOrigin()) = original_origin;
}
}
// Attempt to stop fast in place to make movement smoother
if (in_rapidfire && no_movement)
FastStop();
}
// This calls the warp logic and applies some rapidfire specific logic afterwards
@ -490,10 +479,6 @@ void CreateMove()
was_in_warp = false;
}
// Attempt to stop fast in place to make movement smoother
if (in_rapidfire && no_movement)
FastStop();
}
// Does all the logic related to charging and mode/settings specific actions like peek warp
@ -948,8 +933,8 @@ static InitRoutine init([]() {
cl_move_detour.Init(cl_move_addr, (void *) CL_Move_hook);
EC::Register(EC::LevelShutdown, LevelShutdown, "warp_levelshutdown");
EC::Register(EC::CreateMove, CreateMoveFixPrediction, "warp_createmove_fixpred", EC::very_early);
EC::Register(EC::CreateMove, CreateMove, "warp_createmove", EC::very_late);
EC::Register(EC::CreateMove_NoEnginePred, CreateMovePrePredict, "warp_prepredict");
EC::Register(EC::CreateMoveEarly, CreateMoveEarly, "warp_createmove_early", EC::very_early);
g_IEventManager2->AddListener(&listener, "player_hurt", false);
EC::Register(

View File

@ -1621,29 +1621,41 @@ void AimAtHitbox(CachedEntity *ent, int hitbox, CUserCmd *cmd, bool compensate_p
AimAt(g_pLocalPlayer->v_Eye, r, cmd, compensate_punch);
}
// Thanks to "copypaste" on UnknownCheats, this really helped
void FastStop()
{
// Get velocity
Vector vel;
velocity::EstimateAbsVelocity(RAW_ENT(LOCAL_E), vel);
static auto sv_friction = g_ICvar->FindVar("sv_friction");
static auto sv_stopspeed = g_ICvar->FindVar("sv_stopspeed");
auto speed = vel.Length2D();
auto friction = sv_friction->GetFloat() * CE_FLOAT(LOCAL_E, 0x12b8);
auto control = (speed < sv_stopspeed->GetFloat()) ? sv_stopspeed->GetFloat() : speed;
auto drop = control * friction * g_GlobalVars->interval_per_tick;
if (speed > drop - 1.0f)
{
Vector velocity = vel;
Vector direction;
VectorAngles(vel, direction);
float speed = vel.Length();
// Prevent overshooting
speed *= 0.5f;
float speed = velocity.Length();
direction.y = current_user_cmd->viewangles.y - direction.y;
Vector negated_direction;
AngleVectors2(VectorToQAngle(direction), &negated_direction);
negated_direction *= -speed;
Vector forward;
AngleVectors2(VectorToQAngle(direction), &forward);
Vector negated_direction = forward * -speed;
current_user_cmd->forwardmove = negated_direction.x;
current_user_cmd->sidemove = negated_direction.y;
}
else
{
current_user_cmd->forwardmove = current_user_cmd->sidemove = 0.0f;
}
}
bool IsEntityVisiblePenetration(CachedEntity *entity, int hb)
{

View File

@ -118,4 +118,5 @@ VMTHook soundclient{};
VMTHook enginevgui{};
VMTHook vstd{};
VMTHook eventmanager2{};
VMTHook toolbox{};
} // namespace hooks

View File

@ -15,6 +15,7 @@ set(files "${CMAKE_CURRENT_LIST_DIR}/CanPacket.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RunCommand.cpp"
"${CMAKE_CURRENT_LIST_DIR}/SendNetMsg.cpp"
"${CMAKE_CURRENT_LIST_DIR}/Shutdown.cpp"
"${CMAKE_CURRENT_LIST_DIR}/Think.cpp"
"${CMAKE_CURRENT_LIST_DIR}/FireEvent.cpp"
"${CMAKE_CURRENT_LIST_DIR}/FireEventClientSide.cpp"
"${CMAKE_CURRENT_LIST_DIR}/IsPlayingTimeDemo.cpp"

View File

@ -23,7 +23,7 @@
static settings::Boolean minigun_jump{ "misc.minigun-jump-tf2c", "false" };
static settings::Boolean roll_speedhack{ "misc.roll-speedhack", "false" };
static settings::Boolean forward_speedhack{ "misc.roll-speedhack.forward", "false" };
static settings::Boolean engine_pred{ "misc.engine-prediction", "true" };
settings::Boolean engine_pred{ "misc.engine-prediction", "true" };
static settings::Boolean debug_projectiles{ "debug.projectiles", "false" };
static settings::Int fullauto{ "misc.full-auto", "0" };
static settings::Boolean fuckmode{ "misc.fuckmode", "false" };
@ -31,6 +31,7 @@ static settings::Boolean fuckmode{ "misc.fuckmode", "false" };
class CMoveData;
namespace engine_prediction
{
static Vector original_origin;
void RunEnginePrediction(IClientEntity *ent, CUserCmd *ucmd)
{
@ -51,6 +52,8 @@ void RunEnginePrediction(IClientEntity *ent, CUserCmd *ucmd)
// Backup
float frameTime = g_GlobalVars->frametime;
float curTime = g_GlobalVars->curtime;
int tickcount = g_GlobalVars->tickcount;
original_origin = ent->GetAbsOrigin();
CUserCmd defaultCmd{};
if (ucmd == nullptr)
@ -79,11 +82,19 @@ void RunEnginePrediction(IClientEntity *ent, CUserCmd *ucmd)
g_GlobalVars->frametime = frameTime;
g_GlobalVars->curtime = curTime;
g_GlobalVars->tickcount = tickcount;
// Adjust tickbase
NET_INT(ent, netvar.nTickBase)++;
return;
}
// Restore Origin
void FinishEnginePrediction(IClientEntity *ent, CUserCmd *ucmd)
{
const_cast<Vector &>(ent->GetAbsOrigin()) = original_origin;
original_origin.Invalidate();
}
} // namespace engine_prediction
void PrecalculateCanShoot()
@ -317,8 +328,12 @@ DEFINE_HOOKED_METHOD(CreateMove, bool, void *this_, float input_sample_time, CUs
{
PROF_SECTION(CM_WRAPPER);
EC::run(EC::CreateMove_NoEnginePred);
if (engine_pred)
{
engine_prediction::RunEnginePrediction(RAW_ENT(LOCAL_E), current_user_cmd);
g_pLocalPlayer->UpdateEye();
}
EC::run(EC::CreateMove);
}
@ -464,6 +479,12 @@ DEFINE_HOOKED_METHOD(CreateMoveInput, void, IInput *this_, int sequence_nr, floa
// Run EC
EC::run(EC::CreateMoveLate);
if (CE_GOOD(LOCAL_E))
{
// Restore prediction
if (engine_prediction::original_origin.IsValid())
engine_prediction::FinishEnginePrediction(RAW_ENT(LOCAL_E), current_late_user_cmd);
}
// Write the usercmd
WriteCmd(this_, current_late_user_cmd, sequence_nr);
}

28
src/hooks/Think.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "HookedMethods.hpp"
extern settings::Boolean engine_pred;
namespace hooked_methods
{
// Update Prediction, Used by warp/Doubletap aswell
void UpdatePred()
{
if (isHackActive() && g_IEngine->IsInGame() && CE_GOOD(LOCAL_E) && engine_pred)
{
int signon_state = *(int *) (*(unsigned *) &g_IBaseClientState + 304);
int m_nDeltaTick = *(int *) (*(unsigned *) &g_IBaseClientState + 408);
int lastoutgoingcommand = *(int *) (*(unsigned *) &g_IBaseClientState + offsets::lastoutgoingcommand());
int chokedcommands = *(int *) (*(unsigned *) &g_IBaseClientState + offsets::lastoutgoingcommand() + 4);
int last_command_ack = *(int *) (*(unsigned *) &g_IBaseClientState + offsets::lastoutgoingcommand() + 8);
// Only run if fully connected
if (signon_state == 6 && m_nDeltaTick > 0)
g_IPrediction->Update(m_nDeltaTick ? m_nDeltaTick + 1 : 0, m_nDeltaTick > 0, last_command_ack, lastoutgoingcommand + chokedcommands);
}
}
DEFINE_HOOKED_METHOD(Think, void, IToolFrameworkInternal *_this, bool finaltick)
{
UpdatePred();
return original::Think(_this, finaltick);
}
} // namespace hooked_methods

View File

@ -156,6 +156,11 @@ void LocalPlayer::Update()
}
}
void LocalPlayer::UpdateEye()
{
v_Eye = entity->m_vecOrigin() + CE_VECTOR(entity, netvar.vViewOffset);
}
void LocalPlayer::UpdateEnd()
{
if (!isFakeAngleCM)

View File

@ -807,7 +807,7 @@ static void drawcrumbs()
#endif
static InitRoutine runinit([]() {
EC::Register(EC::CreateMove, cm, "cm_navparser", EC::average);
EC::Register(EC::CreateMove_NoEnginePred, cm, "cm_navparser", EC::average);
#if ENABLE_VISUALS
EC::Register(EC::Draw, drawcrumbs, "draw_navparser", EC::average);
#endif