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"> <Box padding="12 6 6 6" width="content" height="content" name="Bunny hop">
<List width="150"> <List width="150">
<AutoVariable width="fill" target="bunnyhop.enable" label="Enable bunny hop"/> <AutoVariable width="fill" target="bunnyhop.enable" label="Enable bunny hop"/>
@ -47,7 +47,7 @@
<!-- <!AutoVariable width="fill" target="navbot.take-teleporters" label="take teleporters"/> --> <!-- <!AutoVariable width="fill" target="navbot.take-teleporters" label="take teleporters"/> -->
</List> </List>
</Box> </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"> <List width="180">
<AutoVariable width="fill" target="antibackstab.enable" label="Enable anti-backstab"/> <AutoVariable width="fill" target="antibackstab.enable" label="Enable anti-backstab"/>
<AutoVariable width="fill" target="antibackstab.angle" label="Detection angle"/> <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'."/> <AutoVariable width="fill" target="antibackstab.nope" label="Nope!" tooltip="Have the charachter say 'no'."/>
</List> </List>
</Box> </Box>
<Box padding="12 6 6 6" width="content" height="content" name="Warp" x="370" y="65"> <Box padding="12 6 6 6" width="content" height="content" name="Warp" x="370">
<List width="180"> <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.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.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.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.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-size" label="Bar size"/>
<AutoVariable width="fill" target="warp.bar-x" label="X position"/> <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.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.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"/> <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> </List>
</Box> </Box>
</Tab> </Tab>

View File

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

View File

@ -1,4 +1,5 @@
#pragma once #pragma once
#include "settings/Bool.hpp"
class INetMessage; class INetMessage;
namespace hacks::tf2::warp 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 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); void AimAtHitbox(CachedEntity *ent, int hitbox, CUserCmd *cmd, bool compensate_punch = true);
bool IsProjectileCrit(CachedEntity *ent); bool IsProjectileCrit(CachedEntity *ent);

View File

@ -11,12 +11,14 @@ enum ec_types
{ {
/* Note: engine prediction is run on this kind of CreateMove */ /* Note: engine prediction is run on this kind of CreateMove */
CreateMove = 0, CreateMove = 0,
/* Note: this is the CreatMove one layer higher, and should only be used for things that mess with command number*/ /* Note: this is the CreateMove one layer higher, and should only be used for things that mess with command number*/
CreateMoveEarly, CreateMoveLate,
/* This kind of CreateMove will run earlier than all CreateMove events /* This kind of CreateMove will run earlier than "CreateMove" events
* and guranteed to run before EnginePrediction * and guranteed to run before EnginePrediction
*/ */
CreateMove_NoEnginePred, 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 #if ENABLE_VISUALS
Draw, Draw,
#endif #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(IN_KeyEvent, int, void *, int, ButtonCode_t, const char *);
DECLARE_HOOKED_METHOD(FrameStageNotify, void, void *, ClientFrameStage_t); DECLARE_HOOKED_METHOD(FrameStageNotify, void, void *, ClientFrameStage_t);
// IInput // 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); DECLARE_HOOKED_METHOD(GetUserCmd, CUserCmd *, IInput *, int);
// INetChannel // INetChannel
DECLARE_HOOKED_METHOD(SendNetMsg, bool, INetChannel *, INetMessage &, bool, bool); DECLARE_HOOKED_METHOD(SendNetMsg, bool, INetChannel *, INetMessage &, bool, bool);

View File

@ -10,6 +10,11 @@
#include <enums.hpp> #include <enums.hpp>
#include "config.h" #include "config.h"
#include "vector" #include "vector"
#include <optional>
#include "interfaces.hpp"
#include "sdk.hpp"
#pragma once
class CachedEntity; class CachedEntity;
class Vector; 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); 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); 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); float PlayerGravityMod(CachedEntity *player);
Vector EnginePrediction(CachedEntity *player, float time); 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([]() { static InitRoutine init([]() {
EC::Register(EC::CreateMoveEarly, CreateMove, "crit_cm"); EC::Register(EC::CreateMoveLate, CreateMove, "crit_cm");
#if ENABLE_VISUALS #if ENABLE_VISUALS
EC::Register(EC::Draw, Draw, "crit_draw"); EC::Register(EC::Draw, Draw, "crit_draw");
#endif #endif

View File

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

View File

@ -12,13 +12,20 @@
#include "MiscAimbot.hpp" #include "MiscAimbot.hpp"
#include "PlayerTools.hpp" #include "PlayerTools.hpp"
#include "DetourHook.hpp" #include "DetourHook.hpp"
#include "WeaponData.hpp"
#include "MiscTemporary.hpp"
namespace hacks::tf2::warp namespace hacks::tf2::warp
{ {
static settings::Boolean enabled{ "warp.enabled", "false" }; 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::Float speed{ "warp.speed", "23" };
static settings::Boolean draw{ "warp.draw", "false" }; static settings::Boolean draw{ "warp.draw", "false" };
static settings::Button warp_key{ "warp.key", "<null>" }; 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_passively{ "warp.charge-passively", "true" };
static settings::Boolean charge_in_jump{ "warp.charge-passively.jump", "true" }; static settings::Boolean charge_in_jump{ "warp.charge-passively.jump", "true" };
static settings::Boolean charge_no_input{ "warp.charge-passively.no-inputs", "false" }; 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 // Hidden control rvars for communtiy servers
static settings::Int maxusrcmdprocessticks("warp.maxusrcmdprocessticks", "24"); 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 // Draw control
static settings::Int size{ "warp.bar-size", "100" }; static settings::Int size{ "warp.bar-size", "100" };
static settings::Int bar_x{ "warp.bar-x", "50" }; static settings::Int bar_x{ "warp.bar-x", "50" };
@ -48,10 +69,58 @@ static bool charged = false;
static bool should_warp = true; static bool should_warp = true;
static bool was_hurt = false; 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? // Should we warp?
bool shouldWarp(bool check_amount) 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) // How many ticks of excess we have (for decimal speeds)
@ -59,6 +128,10 @@ float excess_ticks = 0.0f;
int GetWarpAmount(bool finalTick) int GetWarpAmount(bool finalTick)
{ {
int max_extra_ticks = *maxusrcmdprocessticks - 1; int max_extra_ticks = *maxusrcmdprocessticks - 1;
// Rapidfire ignores speed
if (in_rapidfire)
return max_extra_ticks;
// No limit set // No limit set
if (!*maxusrcmdprocessticks) if (!*maxusrcmdprocessticks)
max_extra_ticks = INT_MAX; max_extra_ticks = INT_MAX;
@ -106,8 +179,21 @@ void Warp(float accumulated_extra_samples, bool finalTick)
if (warp_amnt) if (warp_amnt)
{ {
int calls = std::min(warp_ticks, 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++) 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); original(accumulated_extra_samples, finalTick);
// Only decrease ticks for the final CL_Move tick // Only decrease ticks for the final CL_Move tick
if (finalTick) if (finalTick)
@ -115,7 +201,9 @@ void Warp(float accumulated_extra_samples, bool finalTick)
warp_amount--; warp_amount--;
warp_ticks--; warp_ticks--;
} }
packets_sent++;
} }
ticks_to_add = 0;
} }
cl_move_detour.RestorePatch(); cl_move_detour.RestorePatch();
@ -140,39 +228,6 @@ int GetMaxWarpTicks()
return ticks; 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 // Approximate demoknight shield speed at a given tick
float approximateSpeedAtTick(int ticks_since_start, float initial_speed, float max_speed) 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; bool has_booties = false;
if (CE_GOOD(LOCAL_E) && LOCAL_E->m_bAlivePlayer() && CE_GOOD(LOCAL_W)) 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) if (CE_INT(LOCAL_W, netvar.iItemDefinitionIndex) == 172)
is_skullcutter = true; is_skullcutter = true;
// We have the Booties, mark as such
if (HasWeapon(LOCAL_E, 405) || HasWeapon(LOCAL_E, 608)) if (HasWeapon(LOCAL_E, 405) || HasWeapon(LOCAL_E, 608))
has_booties = true; has_booties = true;
} }
@ -236,20 +293,187 @@ peek_state current_peek_state = IDLE;
// Used to determine when we should start moving back with peek // Used to determine when we should start moving back with peek
static int charge_at_start = 0; 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; 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() 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) if (!enabled)
return; return;
if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer()) if (CE_BAD(LOCAL_E) || !LOCAL_E->m_bAlivePlayer())
return; return;
// Handle minigun in rapidfire
handleMinigun();
// Charge logic
if (!shouldWarp(false)) if (!shouldWarp(false))
{ {
warp_last_tick = false; warp_last_tick = false;
current_state = ATTACK; current_state = ATTACK;
current_peek_state = IDLE; current_peek_state = IDLE;
// Charge warp
if (charge_key && charge_key.isKeyDown())
{
should_charge = true;
return;
}
Vector velocity{}; Vector velocity{};
velocity::EstimateAbsVelocity(RAW_ENT(LOCAL_E), velocity); velocity::EstimateAbsVelocity(RAW_ENT(LOCAL_E), velocity);
@ -261,8 +485,13 @@ void CreateMove()
ground_ticks = 0; 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 // 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) if (!move_last_tick)
should_charge = true; should_charge = true;
@ -270,14 +499,25 @@ void CreateMove()
return; 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));
if (*warp_movement_ratio > 0 && !(tickcount % *warp_movement_ratio)) // Charge on minigun even with m2 held
should_charge = true; if (LOCAL_W->m_iClassID() == CL_CLASS(CTFMinigun))
move_last_tick = true; 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 // Warp when hurt
else if (was_hurt) else if (was_hurt)
{ {
@ -476,6 +716,98 @@ void CreateMove()
was_hurt_last_tick = was_hurt; 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 #if ENABLE_VISUALS
void Draw() void Draw()
{ {
@ -563,81 +895,14 @@ void rvarCallback(settings::VariableBase<bool> &, bool)
yaw_selections.push_back(90.0f); 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 InitRoutine init([]() {
static auto cl_move_addr = gSignatures.GetEngineSignature("55 89 E5 57 56 53 81 EC 9C 00 00 00 83 3D ? ? ? ? 01"); 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); cl_move_detour.Init(cl_move_addr, (void *) CL_Move_hook);
EC::Register(EC::LevelShutdown, LevelShutdown, "warp_levelshutdown"); 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, CreateMove, "warp_createmove", EC::very_late);
EC::Register(EC::CreateMoveEarly, CreateMoveEarly, "warp_createmove_early", EC::very_early);
g_IEventManager2->AddListener(&listener, "player_hurt", false); g_IEventManager2->AddListener(&listener, "player_hurt", false);
EC::Register( EC::Register(
EC::Shutdown, 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); 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) bool IsEntityVisiblePenetration(CachedEntity *entity, int hb)
{ {
trace_t trace_visible; trace_t trace_visible;

View File

@ -18,6 +18,7 @@
#include "HookedMethods.hpp" #include "HookedMethods.hpp"
#include "nospread.hpp" #include "nospread.hpp"
#include "Warp.hpp"
static settings::Boolean minigun_jump{ "misc.minigun-jump-tf2c", "false" }; static settings::Boolean minigun_jump{ "misc.minigun-jump-tf2c", "false" };
static settings::Boolean roll_speedhack{ "misc.roll-speedhack", "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; Vector vsilent, ang;
current_user_cmd = cmd; current_user_cmd = cmd;
EC::run(EC::CreateMoveEarly);
IF_GAME(IsTF2C()) IF_GAME(IsTF2C())
{ {
if (CE_GOOD(LOCAL_W) && minigun_jump && LOCAL_W->m_iClassID() == CL_CLASS(CTFMinigun)) 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 // 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); bSendPackets = reinterpret_cast<bool *>((uintptr_t) __builtin_frame_address(1) - 8);
// Call original function, includes Normal CreateMove // 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; CUserCmd *cmd = nullptr;
if (this_ && GetCmds(this_) && sequence_nr > 0) if (this_ && GetCmds(this_) && sequence_nr > 0)
@ -457,10 +459,10 @@ DEFINE_HOOKED_METHOD(CreateMoveEarly, void, IInput *this_, int sequence_nr, floa
return; return;
} }
PROF_SECTION(CreateMoveEarly); PROF_SECTION(CreateMoveInput);
// Run EC // Run EC
EC::run(EC::CreateMoveEarly); EC::run(EC::CreateMoveLate);
// Write the usercmd // Write the usercmd
WriteCmd(this_, current_late_user_cmd, sequence_nr); 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; 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) PROF_SECTION(PredictNew)
Vector result = pos; Vector result = pos;