Add Rapidfire

This commit is contained in:
BenCat07 2020-10-16 20:19:14 +02:00 committed by LightCat
parent be4baf0f18
commit a6c1a16aa6
13 changed files with 435 additions and 126 deletions

View File

@ -1,4 +1,4 @@
<Tab name="Movement" padding="6 6 6 6">
<Tab name="Movement" padding="0 6 6 6">
<Box padding="12 6 6 6" width="content" height="content" name="Bunny hop">
<List width="150">
<AutoVariable width="fill" target="bunnyhop.enable" label="Enable bunny hop"/>
@ -47,7 +47,7 @@
<!-- <!AutoVariable width="fill" target="navbot.take-teleporters" label="take teleporters"/> -->
</List>
</Box>
<Box padding="12 6 6 6" width="content" height="content" name="Anti-backstab" x="370">
<Box padding="12 6 6 6" width="content" height="content" name="Anti-backstab" x="170" y="300">
<List width="180">
<AutoVariable width="fill" target="antibackstab.enable" label="Enable anti-backstab"/>
<AutoVariable width="fill" target="antibackstab.angle" label="Detection angle"/>
@ -56,11 +56,12 @@
<AutoVariable width="fill" target="antibackstab.nope" label="Nope!" tooltip="Have the charachter say 'no'."/>
</List>
</Box>
<Box padding="12 6 6 6" width="content" height="content" name="Warp" x="370" y="65">
<List width="180">
<Box padding="12 6 6 6" width="content" height="content" name="Warp" x="370">
<List width="195">
<AutoVariable width="fill" target="warp.enabled" label="Enable warp" tooltip="Allows you to charge a burst of speed."/>
<AutoVariable width="fill" target="warp.speed" label="Warp speed" tooltip="The discharge speed (0.5 = +50%, 2 = +200%. +2300% is the maximum possible)."/>
<AutoVariable width="fill" target="warp.key" label="Warp key" tooltip="Pressing this key will use all stored ticks to accelerate you."/>
<AutoVariable width="fill" target="warp.charge-key" label="Charge key" tooltip="Hold down this key to charge warp."/>
<AutoVariable width="fill" target="warp.draw" label="Draw warp" tooltip="Draws a bar indicating your warp readiness."/>
<AutoVariable width="fill" target="warp.bar-size" label="Bar size"/>
<AutoVariable width="fill" target="warp.bar-x" label="X position"/>
@ -76,6 +77,13 @@
<AutoVariable width="fill" target="warp.on-hit.backwards" label="Warp backwards" tooltip="Warp when hit option"/>
<AutoVariable width="fill" target="warp.on-hit.left" label="Warp left" tooltip="Warp when hit option"/>
<AutoVariable width="fill" target="warp.on-hit.right" label="Warp right" tooltip="Warp when hit option"/>
<Box padding="12 6 6 6" width="content" height="content" name="Rapidfire">
<List width="185">
<AutoVariable width="fill" target="warp.rapidfire" label="Enable Rapidfire" tooltip="Allows you to shoot multiple shots at once or reduce time between shots."/>
<AutoVariable width="fill" target="warp.rapidfire.no-movement" label="Prevent movement in rapidfire" tooltip="Attempt to not move when Rapidfiring."/>
<AutoVariable width="fill" target="warp.rapidfire.key" label="Rapidfire key" tooltip="Optional. If set you can use this key to control when to rapidfire."/>
</List>
</Box>
</List>
</Box>
</Tab>

View File

@ -80,7 +80,7 @@ struct offsets
{
return PlatformOffset(7, undefined, undefined);
}
static constexpr uint32_t CreateMoveEarly()
static constexpr uint32_t CreateMoveInput()
{
return PlatformOffset(3, undefined, undefined);
}

View File

@ -1,4 +1,5 @@
#pragma once
#include "settings/Bool.hpp"
class INetMessage;
namespace hacks::tf2::warp
{

View File

@ -171,6 +171,7 @@ inline Vector GetAimAtAngles(Vector origin, Vector target, CachedEntity *punch_c
}
void AimAt(Vector origin, Vector target, CUserCmd *cmd, bool compensate_punch = true);
void FastStop();
void AimAtHitbox(CachedEntity *ent, int hitbox, CUserCmd *cmd, bool compensate_punch = true);
bool IsProjectileCrit(CachedEntity *ent);

View File

@ -11,12 +11,14 @@ enum ec_types
{
/* Note: engine prediction is run on this kind of CreateMove */
CreateMove = 0,
/* Note: this is the CreatMove one layer higher, and should only be used for things that mess with command number*/
CreateMoveEarly,
/* This kind of CreateMove will run earlier than all CreateMove events
/* Note: this is the CreateMove one layer higher, and should only be used for things that mess with command number*/
CreateMoveLate,
/* This kind of CreateMove will run earlier than "CreateMove" events
* and guranteed to run before EnginePrediction
*/
CreateMove_NoEnginePred,
/* Note: this is still CreateMove, just ran before original is called, needed in some cases like changing tickcount before original gets called*/
CreateMoveEarly,
#if ENABLE_VISUALS
Draw,
#endif

View File

@ -44,7 +44,7 @@ DECLARE_HOOKED_METHOD(DispatchUserMessage, bool, void *, int, bf_read &);
DECLARE_HOOKED_METHOD(IN_KeyEvent, int, void *, int, ButtonCode_t, const char *);
DECLARE_HOOKED_METHOD(FrameStageNotify, void, void *, ClientFrameStage_t);
// IInput
DECLARE_HOOKED_METHOD(CreateMoveEarly, void, IInput *, int, float, bool)
DECLARE_HOOKED_METHOD(CreateMoveInput, void, IInput *, int, float, bool)
DECLARE_HOOKED_METHOD(GetUserCmd, CUserCmd *, IInput *, int);
// INetChannel
DECLARE_HOOKED_METHOD(SendNetMsg, bool, INetChannel *, INetMessage &, bool, bool);

View File

@ -10,6 +10,11 @@
#include <enums.hpp>
#include "config.h"
#include "vector"
#include <optional>
#include "interfaces.hpp"
#include "sdk.hpp"
#pragma once
class CachedEntity;
class Vector;
@ -23,6 +28,7 @@ Vector ProjectilePrediction(CachedEntity *ent, int hb, float speed, float gravit
Vector ProjectilePrediction_Engine(CachedEntity *ent, int hb, float speed, float gravitymod, float entgmod /* ignored */, float proj_startvelocity = 0.0f);
std::vector<Vector> Predict(Vector pos, float offset, Vector vel, Vector acceleration, std::pair<Vector, Vector> minmax, float time, int count, bool vischeck = true);
Vector PredictStep(Vector pos, Vector &vel, Vector acceleration, std::pair<Vector, Vector> &minmax, float time, float steplength = g_GlobalVars->interval_per_tick, bool vischeck = true, std::optional<float> grounddistance = std::nullopt);
float PlayerGravityMod(CachedEntity *player);
Vector EnginePrediction(CachedEntity *player, float time);

View File

@ -1065,7 +1065,7 @@ static CatCommand debug_print_crit_info("debug_print_crit_info", "Print a bunch
});
static InitRoutine init([]() {
EC::Register(EC::CreateMoveEarly, CreateMove, "crit_cm");
EC::Register(EC::CreateMoveLate, CreateMove, "crit_cm");
#if ENABLE_VISUALS
EC::Register(EC::Draw, Draw, "crit_draw");
#endif

View File

@ -218,7 +218,7 @@ void hack::Hook()
hooks::input.Set(g_IInput);
hooks::input.HookMethod(HOOK_ARGS(GetUserCmd));
hooks::input.HookMethod(HOOK_ARGS(CreateMoveEarly));
hooks::input.HookMethod(HOOK_ARGS(CreateMoveInput));
hooks::input.Apply();
#if ENABLE_VISUALS || ENABLE_TEXTMODE

View File

@ -12,13 +12,20 @@
#include "MiscAimbot.hpp"
#include "PlayerTools.hpp"
#include "DetourHook.hpp"
#include "WeaponData.hpp"
#include "MiscTemporary.hpp"
namespace hacks::tf2::warp
{
static settings::Boolean enabled{ "warp.enabled", "false" };
static settings::Boolean no_movement{ "warp.rapidfire.no-movement", "true" };
static settings::Boolean rapidfire{ "warp.rapidfire", "false" };
static settings::Boolean wait_full{ "warp.rapidfire.wait-full", "true" };
static settings::Button rapidfire_key{ "warp.rapidfire.key", "<null>" };
static settings::Float speed{ "warp.speed", "23" };
static settings::Boolean draw{ "warp.draw", "false" };
static settings::Button warp_key{ "warp.key", "<null>" };
static settings::Button charge_key{ "warp.charge-key", "<null>" };
static settings::Boolean charge_passively{ "warp.charge-passively", "true" };
static settings::Boolean charge_in_jump{ "warp.charge-passively.jump", "true" };
static settings::Boolean charge_no_input{ "warp.charge-passively.no-inputs", "false" };
@ -34,6 +41,20 @@ static settings::Boolean warp_right{ "warp.on-hit.right", "true" };
// Hidden control rvars for communtiy servers
static settings::Int maxusrcmdprocessticks("warp.maxusrcmdprocessticks", "24");
bool in_warp = false;
bool in_rapidfire = false;
// Should we choke the packet or not? (in rapidfire)
bool choke_packet = false;
// Were we warping last tick?
// why is this needed at all, why do i have to write this janky bs
bool was_in_warp = false;
// How many ticks we have to add to our CreateMove packet
int ticks_to_add = 0;
int GetMaxWarpTicks();
void warpLogic();
// Draw control
static settings::Int size{ "warp.bar-size", "100" };
static settings::Int bar_x{ "warp.bar-x", "50" };
@ -48,10 +69,58 @@ static bool charged = false;
static bool should_warp = true;
static bool was_hurt = false;
bool shouldRapidfire()
{
if (!rapidfire)
return false;
// Already in rapidfire
if (in_rapidfire)
return false;
// No key set? Always run. Else check if key is held
if (rapidfire_key && !rapidfire_key.isKeyDown())
return false;
// Dead player
if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer() || CE_BAD(LOCAL_W))
return false;
// Weapon specific ignores, knives, Huntsman, Mediguns, and the grappling hook
if (re::C_TFWeaponBase::GetWeaponID(RAW_ENT(LOCAL_W)) == 7 || LOCAL_W->m_iClassID() == CL_CLASS(CTFCompoundBow) || LOCAL_W->m_iClassID() == CL_CLASS(CWeaponMedigun) || LOCAL_W->m_iClassID() == CL_CLASS(CTFGrapplingHook))
return false;
// Ignore throwables/consumables/etc
if (g_pLocalPlayer->weapon_mode == weapon_throwable || g_pLocalPlayer->weapon_mode == weapon_consumable || g_pLocalPlayer->weapon_mode == weapon_pda)
return false;
// Unrevved minigun cannot rapidfire
if (LOCAL_W->m_iClassID() == CL_CLASS(CTFMinigun) && CE_INT(LOCAL_W, netvar.iWeaponState) != 3 && CE_INT(LOCAL_W, netvar.iWeaponState) != 2)
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())))
return false;
// Mouse 1 is held, do it.
return current_user_cmd && current_user_cmd->buttons & IN_ATTACK;
}
// Should we warp?
bool shouldWarp(bool check_amount)
{
return ((warp_key && warp_key.isKeyDown()) || was_hurt) && (!check_amount || warp_amount);
return
// Ingame?
g_IEngine->IsInGame() &&
// Warp key held?
(((warp_key && warp_key.isKeyDown())
// Hurt warp?
|| was_hurt
// Rapidfire and trying to attack?
|| shouldRapidfire()) &&
// Do we have enough to warp?
(!check_amount || warp_amount));
}
// How many ticks of excess we have (for decimal speeds)
@ -59,6 +128,10 @@ float excess_ticks = 0.0f;
int GetWarpAmount(bool finalTick)
{
int max_extra_ticks = *maxusrcmdprocessticks - 1;
// Rapidfire ignores speed
if (in_rapidfire)
return max_extra_ticks;
// No limit set
if (!*maxusrcmdprocessticks)
max_extra_ticks = INT_MAX;
@ -106,8 +179,21 @@ void Warp(float accumulated_extra_samples, bool finalTick)
if (warp_amnt)
{
int calls = std::min(warp_ticks, warp_amnt);
// Starts at 1 for the previous packet we already stored
int packets_sent = 1;
for (int i = 0; i < calls; i++)
{
// Choke unless we sent too many already
choke_packet = true;
// We are sending the last one that fits in this clc_move, stop choking and send them all off
if (packets_sent == 21 || i == calls - 1)
{
choke_packet = false;
packets_sent = -1;
}
original(accumulated_extra_samples, finalTick);
// Only decrease ticks for the final CL_Move tick
if (finalTick)
@ -115,7 +201,9 @@ void Warp(float accumulated_extra_samples, bool finalTick)
warp_amount--;
warp_ticks--;
}
packets_sent++;
}
ticks_to_add = 0;
}
cl_move_detour.RestorePatch();
@ -140,39 +228,6 @@ int GetMaxWarpTicks()
return ticks;
}
void SendNetMessage(INetMessage &msg)
{
if (!enabled)
return;
// Credits to MrSteyk for this btw
if (msg.GetGroup() == 0xA)
{
// Charge
if (should_charge && !charged)
{
int ticks = GetMaxWarpTicks();
auto movemsg = (CLC_Move *) &msg;
// Just null it :shrug:
movemsg->m_nBackupCommands = 0;
movemsg->m_nNewCommands = 0;
movemsg->m_DataOut.Reset();
movemsg->m_DataOut.m_nDataBits = 0;
movemsg->m_DataOut.m_nDataBytes = 0;
movemsg->m_DataOut.m_iCurBit = 0;
warp_amount++;
if (warp_amount >= ticks)
{
warp_amount = ticks;
charged = true;
}
}
}
should_charge = false;
}
// Approximate demoknight shield speed at a given tick
float approximateSpeedAtTick(int ticks_since_start, float initial_speed, float max_speed)
{
@ -188,8 +243,10 @@ int approximateTicksForDist(float distance, float initial_speed, int max_ticks)
bool has_booties = false;
if (CE_GOOD(LOCAL_E) && LOCAL_E->m_bAlivePlayer() && CE_GOOD(LOCAL_W))
{
// We have a skullcutter, mark as such
if (CE_INT(LOCAL_W, netvar.iItemDefinitionIndex) == 172)
is_skullcutter = true;
// We have the Booties, mark as such
if (HasWeapon(LOCAL_E, 405) || HasWeapon(LOCAL_E, 608))
has_booties = true;
}
@ -236,20 +293,187 @@ peek_state current_peek_state = IDLE;
// Used to determine when we should start moving back with peek
static int charge_at_start = 0;
// Used to determine when demoknight warp should be over
// Used to determine when demoknight warp should be running
static bool was_overridden = false;
// How long since we are in one of the revved states?
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()
{
ticks_in_revved = 0;
replaced_last_tick = false;
}
void handleMinigun()
{
// Minigun rapidfire will not work if you hold m1, so we Revv it up with m2 instead.
if (rapidfire && warp_amount && CE_GOOD(LOCAL_W) && LOCAL_W->m_iClassID() == CL_CLASS(CTFMinigun))
{
// Revving
if (CE_INT(LOCAL_W, netvar.iWeaponState) == 1)
{
// Replace m1 with m2 to make rapidfire work
if (current_user_cmd->buttons & IN_ATTACK)
{
current_user_cmd->buttons |= IN_ATTACK2;
current_user_cmd->buttons &= ~IN_ATTACK;
replaced_last_tick = true;
}
else
replaced_last_tick = false;
}
// Revved
else if (CE_INT(LOCAL_W, netvar.iWeaponState) == 3)
{
// Once we are revved (via m1) we need to wait 23 ticks before we use rapidfire, else it will fail. Valve jank.
if (replaced_last_tick && current_user_cmd->buttons & IN_ATTACK)
{
if (ticks_in_revved < 23)
{
current_user_cmd->buttons |= IN_ATTACK2;
current_user_cmd->buttons &= ~IN_ATTACK;
ticks_in_revved++;
}
// We waited the 23 ticks, skip over this code now
else
resetRevvstate();
}
// No m1 revv/not attacking
else
resetRevvstate();
}
// Other state was entered, e.g. we unrevved. Reset aswell
else
resetRevvstate();
}
}
// This is called first, it subsequently calls all the CreateMove functions.
void CL_Move_hook(float accumulated_extra_samples, bool bFinalTick)
{
CL_Move_t original = (CL_Move_t) cl_move_detour.GetOriginalFunc();
original(accumulated_extra_samples, bFinalTick);
cl_move_detour.RestorePatch();
// Should we warp?
if (shouldWarp(true))
{
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())
charged = false;
in_warp = false;
in_rapidfire = false;
was_in_warp = true;
}
}
// Run before we call the original, we need to adjust the tickcount on the command
// and the global variable so our time based functions are synced properly.
void CreateMoveEarly()
{
if (hacks::tf2::warp::in_rapidfire && current_user_cmd)
{
if (current_user_cmd)
{
g_GlobalVars->tickcount++;
current_user_cmd->tick_count++;
}
}
}
// 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()
{
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, 0.0f);
// Restore from the engine prediction
const_cast<Vector &>(RAW_ENT(LOCAL_E)->GetAbsOrigin()) = original_origin;
}
}
}
// This calls the warp logic and applies some rapidfire specific logic afterwards
// if it applies.
void CreateMove()
{
warpLogic();
// Either in rapidfire, or the tick just after. Either way we need to force bSendPackets in some way.
bool should_rapidfire = shouldRapidfire();
if (in_rapidfire || should_rapidfire || was_in_warp)
{
// If choke packet is set or we are about to rapidfire, choke the packet (Latter is to ensure it is in the same batch as our rapidfire ones)
if (choke_packet || should_rapidfire)
*bSendPackets = false;
// We either just stopped warping, or are on the last tick of rapidfire and want to forcefully release all the usercmds in one packet.
else
*bSendPackets = true;
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
// and demoknight mode.
void warpLogic()
{
if (!enabled)
return;
if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer())
return;
// Handle minigun in rapidfire
handleMinigun();
// Charge logic
if (!shouldWarp(false))
{
warp_last_tick = false;
current_state = ATTACK;
current_peek_state = IDLE;
// Charge warp
if (charge_key && charge_key.isKeyDown())
{
should_charge = true;
return;
}
Vector velocity{};
velocity::EstimateAbsVelocity(RAW_ENT(LOCAL_E), velocity);
@ -261,8 +485,13 @@ void CreateMove()
ground_ticks = 0;
}
bool button_block = (current_user_cmd->buttons & (IN_ATTACK | IN_ATTACK2));
// Charge on minigun even with m2 held
if (LOCAL_E->m_bAlivePlayer() && CE_GOOD(LOCAL_W) && LOCAL_W->m_iClassID() == CL_CLASS(CTFMinigun))
button_block = current_user_cmd->buttons & IN_ATTACK;
// Bunch of checks, if they all pass we are standing still
if ((ground_ticks > 1 || charge_in_jump) && (charge_no_input || velocity.IsZero()) && !HasCondition<TFCond_Charging>(LOCAL_E) && !current_user_cmd->forwardmove && !current_user_cmd->sidemove && !current_user_cmd->upmove && !(current_user_cmd->buttons & IN_JUMP) && !(current_user_cmd->buttons & (IN_ATTACK | IN_ATTACK2)))
if ((ground_ticks > 1 || charge_in_jump) && (charge_no_input || velocity.IsZero()) && !HasCondition<TFCond_Charging>(LOCAL_E) && !current_user_cmd->forwardmove && !current_user_cmd->sidemove && !current_user_cmd->upmove && !(current_user_cmd->buttons & IN_JUMP) && !button_block)
{
if (!move_last_tick)
should_charge = true;
@ -270,14 +499,25 @@ void CreateMove()
return;
}
else if (charge_passively && (charge_in_jump || ground_ticks > 1) && !(current_user_cmd->buttons & (IN_ATTACK | IN_ATTACK2)))
else if (charge_passively && (charge_in_jump || ground_ticks > 1))
{
// Use everxy xth tick for charging
bool button_block = (current_user_cmd->buttons & (IN_ATTACK | IN_ATTACK2));
// Charge on minigun even with m2 held
if (LOCAL_W->m_iClassID() == CL_CLASS(CTFMinigun))
button_block = current_user_cmd->buttons & IN_ATTACK;
if (!button_block)
{
// Use every xth tick for charging
if (*warp_movement_ratio > 0 && !(tickcount % *warp_movement_ratio))
should_charge = true;
move_last_tick = true;
}
}
}
// Ignore the rest if rapidfire is/should be running
else if (shouldRapidfire() || in_rapidfire)
return;
// Warp when hurt
else if (was_hurt)
{
@ -476,6 +716,98 @@ void CreateMove()
was_hurt_last_tick = was_hurt;
}
// The second to last thing that gets called, its only job is to write the commands locally and then queue for sending.
// We simply make the "backup" command buffer accessible which allows us to send more at once.
//
// Only called if *bSendPackets is true.
void CL_SendMove_hook()
{
byte data[4000];
// the +4 one is choked commands
int nextcommandnr = NET_INT(g_IBaseClientState, offsets::lastoutgoingcommand()) + NET_INT(g_IBaseClientState, offsets::lastoutgoingcommand() + 4) + 1;
// send the client update packet
CLC_Move moveMsg;
moveMsg.m_DataOut.StartWriting(data, sizeof(data));
// Determine number of backup commands to send along
int cl_cmdbackup = 2;
// How many real new commands have queued up
moveMsg.m_nNewCommands = 1 + NET_INT(g_IBaseClientState, offsets::lastoutgoingcommand() + 4);
moveMsg.m_nNewCommands = std::clamp(moveMsg.m_nNewCommands, 0, 15);
// Excessive commands (Used for longer fakelag, credits to https://www.unknowncheats.me/forum/source-engine/370916-23-tick-guwop-fakelag-break-lag-compensation-running.html)
int extra_commands = NET_INT(g_IBaseClientState, offsets::lastoutgoingcommand() + 4) + 1 - moveMsg.m_nNewCommands;
cl_cmdbackup = std::max(2, extra_commands);
moveMsg.m_nBackupCommands = std::clamp(cl_cmdbackup, 0, 7);
int numcmds = moveMsg.m_nNewCommands + moveMsg.m_nBackupCommands;
int from = -1; // first command is deltaed against zeros
bool bOK = true;
for (int to = nextcommandnr - numcmds + 1; to <= nextcommandnr; to++)
{
bool isnewcmd = to >= (nextcommandnr - moveMsg.m_nNewCommands + 1);
// Call the write to buffer
typedef bool (*WriteUsercmdDeltaToBuffer_t)(IBaseClientDLL *, bf_write *, int, int, bool);
// first valid command number is 1
bOK = bOK && vfunc<WriteUsercmdDeltaToBuffer_t>(g_IBaseClient, offsets::PlatformOffset(23, offsets::undefined, offsets::undefined), 0)(g_IBaseClient, &moveMsg.m_DataOut, from, to, isnewcmd);
from = to;
}
if (bOK)
{
// Make fakelag work as we want it to
if (extra_commands > 0)
((INetChannel *) g_IEngine->GetNetChannelInfo())->m_nChokedPackets -= extra_commands;
// only write message if all usercmds were written correctly, otherwise parsing would fail
((INetChannel *) g_IEngine->GetNetChannelInfo())->SendNetMsg(moveMsg);
}
}
// Called after CL_SendMove to transmit the clc_move message. We choke it if we want to charge warp.
void SendNetMessage(INetMessage &msg)
{
if (!enabled)
return;
// Credits to MrSteyk for this btw
if (msg.GetGroup() == 0xA)
{
// Charge
if (should_charge && !charged)
{
int ticks = GetMaxWarpTicks();
auto movemsg = (CLC_Move *) &msg;
// Just null it :shrug:
movemsg->m_nBackupCommands = 0;
movemsg->m_nNewCommands = 0;
movemsg->m_DataOut.Reset();
movemsg->m_DataOut.m_nDataBits = 0;
movemsg->m_DataOut.m_nDataBytes = 0;
movemsg->m_DataOut.m_iCurBit = 0;
warp_amount++;
if (warp_amount >= ticks)
{
warp_amount = ticks;
charged = true;
}
}
}
should_charge = false;
}
#if ENABLE_VISUALS
void Draw()
{
@ -563,81 +895,14 @@ void rvarCallback(settings::VariableBase<bool> &, bool)
yaw_selections.push_back(90.0f);
}
void CL_SendMove_hook()
{
byte data[4000];
// the +4 one is choked commands
int nextcommandnr = NET_INT(g_IBaseClientState, offsets::lastoutgoingcommand()) + NET_INT(g_IBaseClientState, offsets::lastoutgoingcommand() + 4) + 1;
// send the client update packet
CLC_Move moveMsg;
moveMsg.m_DataOut.StartWriting(data, sizeof(data));
// Determine number of backup commands to send along
int cl_cmdbackup = 2;
// How many real new commands have queued up
moveMsg.m_nNewCommands = 1 + NET_INT(g_IBaseClientState, offsets::lastoutgoingcommand() + 4);
moveMsg.m_nNewCommands = std::clamp(moveMsg.m_nNewCommands, 0, 15);
// Excessive commands (Used for longer fakelag, credits to https://www.unknowncheats.me/forum/source-engine/370916-23-tick-guwop-fakelag-break-lag-compensation-running.html)
int extra_commands = NET_INT(g_IBaseClientState, offsets::lastoutgoingcommand() + 4) + 1 - moveMsg.m_nNewCommands;
cl_cmdbackup = std::max(2, extra_commands);
moveMsg.m_nBackupCommands = std::clamp(cl_cmdbackup, 0, 7);
int numcmds = moveMsg.m_nNewCommands + moveMsg.m_nBackupCommands;
int from = -1; // first command is deltaed against zeros
bool bOK = true;
for (int to = nextcommandnr - numcmds + 1; to <= nextcommandnr; to++)
{
bool isnewcmd = to >= (nextcommandnr - moveMsg.m_nNewCommands + 1);
// Call the write to buffer
typedef bool (*WriteUsercmdDeltaToBuffer_t)(IBaseClientDLL *, bf_write *, int, int, bool);
// first valid command number is 1
bOK = bOK && vfunc<WriteUsercmdDeltaToBuffer_t>(g_IBaseClient, offsets::PlatformOffset(23, offsets::undefined, offsets::undefined), 0)(g_IBaseClient, &moveMsg.m_DataOut, from, to, isnewcmd);
from = to;
}
if (bOK)
{
// Make fakelag work as we want it to
if (extra_commands > 0)
((INetChannel *) g_IEngine->GetNetChannelInfo())->m_nChokedPackets -= extra_commands;
// only write message if all usercmds were written correctly, otherwise parsing would fail
((INetChannel *) g_IEngine->GetNetChannelInfo())->SendNetMsg(moveMsg);
}
}
void CL_Move_hook(float accumulated_extra_samples, bool bFinalTick)
{
CL_Move_t original = (CL_Move_t) cl_move_detour.GetOriginalFunc();
original(accumulated_extra_samples, bFinalTick);
cl_move_detour.RestorePatch();
// Should we warp?
if (shouldWarp(true))
{
Warp(accumulated_extra_samples, bFinalTick);
if (warp_amount < GetMaxWarpTicks())
charged = false;
}
}
static InitRoutine init([]() {
static auto cl_move_addr = gSignatures.GetEngineSignature("55 89 E5 57 56 53 81 EC 9C 00 00 00 83 3D ? ? ? ? 01");
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::CreateMoveEarly, CreateMoveEarly, "warp_createmove_early", EC::very_early);
g_IEventManager2->AddListener(&listener, "player_hurt", false);
EC::Register(
EC::Shutdown,

View File

@ -1621,6 +1621,30 @@ 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);
Vector direction;
VectorAngles(vel, direction);
float speed = vel.Length();
// Prevent overshooting
speed *= 0.5f;
direction.y = current_user_cmd->viewangles.y - direction.y;
Vector negated_direction;
AngleVectors2(VectorToQAngle(direction), &negated_direction);
negated_direction *= -speed;
current_user_cmd->forwardmove = negated_direction.x;
current_user_cmd->sidemove = negated_direction.y;
}
bool IsEntityVisiblePenetration(CachedEntity *entity, int hb)
{
trace_t trace_visible;

View File

@ -18,6 +18,7 @@
#include "HookedMethods.hpp"
#include "nospread.hpp"
#include "Warp.hpp"
static settings::Boolean minigun_jump{ "misc.minigun-jump-tf2c", "false" };
static settings::Boolean roll_speedhack{ "misc.roll-speedhack", "false" };
@ -127,6 +128,7 @@ DEFINE_HOOKED_METHOD(CreateMove, bool, void *this_, float input_sample_time, CUs
Vector vsilent, ang;
current_user_cmd = cmd;
EC::run(EC::CreateMoveEarly);
IF_GAME(IsTF2C())
{
if (CE_GOOD(LOCAL_W) && minigun_jump && LOCAL_W->m_iClassID() == CL_CLASS(CTFMinigun))
@ -430,11 +432,11 @@ void WriteCmd(IInput *input, CUserCmd *cmd, int sequence_nr)
}
// This gets called before the other CreateMove, but since we run original first in here all the stuff gets called after normal CreateMove is done
DEFINE_HOOKED_METHOD(CreateMoveEarly, void, IInput *this_, int sequence_nr, float input_sample_time, bool arg3)
DEFINE_HOOKED_METHOD(CreateMoveInput, void, IInput *this_, int sequence_nr, float input_sample_time, bool arg3)
{
bSendPackets = reinterpret_cast<bool *>((uintptr_t) __builtin_frame_address(1) - 8);
// Call original function, includes Normal CreateMove
original::CreateMoveEarly(this_, sequence_nr, input_sample_time, arg3);
original::CreateMoveInput(this_, sequence_nr, input_sample_time, arg3);
CUserCmd *cmd = nullptr;
if (this_ && GetCmds(this_) && sequence_nr > 0)
@ -457,10 +459,10 @@ DEFINE_HOOKED_METHOD(CreateMoveEarly, void, IInput *this_, int sequence_nr, floa
return;
}
PROF_SECTION(CreateMoveEarly);
PROF_SECTION(CreateMoveInput);
// Run EC
EC::run(EC::CreateMoveEarly);
EC::run(EC::CreateMoveLate);
// Write the usercmd
WriteCmd(this_, current_late_user_cmd, sequence_nr);

View File

@ -56,7 +56,7 @@ Vector Predict(Vector &pos, Vector &vel, Vector acceleration, std::optional<floa
return result;
}
Vector PredictStep(Vector pos, Vector &vel, Vector acceleration, std::pair<Vector, Vector> &minmax, float time, float steplength = g_GlobalVars->interval_per_tick, bool vischeck = true, std::optional<float> grounddistance = std::nullopt)
Vector PredictStep(Vector pos, Vector &vel, Vector acceleration, std::pair<Vector, Vector> &minmax, float time, float steplength, bool vischeck, std::optional<float> grounddistance)
{
PROF_SECTION(PredictNew)
Vector result = pos;