Rewrite crithack
This commit is contained in:
parent
5a88fb7842
commit
fddbac7d9d
@ -1,13 +1,11 @@
|
||||
<Tab name="Crits" padding="4 4 4 4">
|
||||
<Box padding="12 6 6 6" width="content" height="content" name="Crits">
|
||||
<List width="200">
|
||||
<AutoVariable width="fill" target="crit.key" label="Crit Key"/>
|
||||
<AutoVariable width="fill" target="crit.auto" label="Always Crit (Ignore Crit Key)"/>
|
||||
<AutoVariable width="fill" target="crit.info" label="Crit Info"/>
|
||||
<AutoVariable width="fill" target="crit.experimental" label="Instant Crits"/>
|
||||
<AutoVariable width="fill" target="crit.melee" label="Melee Crits"/>
|
||||
<AutoVariable width="fill" target="crit.experimental.melee" label="Experimental Melee Crits"/>
|
||||
<AutoVariable width="fill" target="crit.force-gameplay" label="Do Not Hinder Gameplay"/>
|
||||
<AutoVariable width="fill" target="crit.enabled" label="Enable Crithack" tooltip="Note that melee crits will not be enabled"/>
|
||||
<AutoVariable width="fill" target="crit.key" label="Crit Key" tooltip="Key used for non-melee crits"/>
|
||||
<AutoVariable width="fill" target="crit.draw-info" label="Crit Info" tooltip="Draw Useful information on screen"/>
|
||||
<AutoVariable width="fill" target="crit.melee" label="Melee Crits" tooltip="Enable Melee crits (Not bound to key)"/>
|
||||
<AutoVariable width="fill" target="crit.old-mode" label="Old mode" tooltip="Use the old method for crits"/>
|
||||
</List>
|
||||
</Box>
|
||||
</Tab>
|
||||
|
@ -121,6 +121,7 @@ public:
|
||||
|
||||
offset_t flChargeBeginTime;
|
||||
offset_t flLastFireTime;
|
||||
offset_t flObservedCritChance;
|
||||
offset_t hThrower;
|
||||
offset_t hMyWeapons;
|
||||
|
||||
|
@ -80,6 +80,10 @@ struct offsets
|
||||
{
|
||||
return PlatformOffset(7, undefined, undefined);
|
||||
}
|
||||
static constexpr uint32_t CreateMoveLate()
|
||||
{
|
||||
return PlatformOffset(21, undefined, undefined);
|
||||
}
|
||||
static constexpr uint32_t CreateMove()
|
||||
{
|
||||
return PlatformOffset(22, undefined, undefined);
|
||||
|
@ -1,37 +1,7 @@
|
||||
/*
|
||||
* crits.h
|
||||
*
|
||||
* Created on: Feb 25, 2017
|
||||
* Author: nullifiedcat
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
class CUserCmd;
|
||||
class IClientEntity;
|
||||
|
||||
// BUGBUG TODO this struct is outdated
|
||||
struct crithack_saved_state
|
||||
{
|
||||
float unknown2868;
|
||||
float unknown2864;
|
||||
int unknown2620;
|
||||
float unknown2880;
|
||||
char unknown2839;
|
||||
float bucket2616;
|
||||
int seed2876;
|
||||
|
||||
void Save(IClientEntity *entity);
|
||||
void Load(IClientEntity *entity);
|
||||
};
|
||||
|
||||
#include "settings/Bool.hpp"
|
||||
namespace criticals
|
||||
{
|
||||
|
||||
bool random_crits_enabled();
|
||||
extern settings::Boolean enabled;
|
||||
extern settings::Boolean melee;
|
||||
} // namespace criticals
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
extern int *g_PredictionRandomSeed;
|
||||
extern std::unordered_map<int, int> command_number_mod;
|
||||
|
@ -40,5 +40,7 @@ public:
|
||||
bool isHackActive();
|
||||
|
||||
extern CUserCmd *current_user_cmd;
|
||||
// Seperate user cmd used in the late createmove
|
||||
extern CUserCmd *current_late_user_cmd;
|
||||
|
||||
extern GlobalSettings g_Settings;
|
||||
|
@ -119,6 +119,9 @@ std::unique_ptr<char[]> strfmt(const char *fmt, ...);
|
||||
bool HasWeapon(CachedEntity *ent, int wantedId);
|
||||
bool IsAmbassador(CachedEntity *ent);
|
||||
bool AmbassadorCanHeadshot();
|
||||
// Validate a UserCmd
|
||||
#define VERIFIED_CMD_SIZE 90
|
||||
void ValidateUserCmd(CUserCmd *cmd, int sequence_nr);
|
||||
|
||||
// Convert a TF2 handle into an IDX -> ENTITY(IDX)
|
||||
int HandleToIDX(int handle);
|
||||
|
@ -11,6 +11,8 @@ 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*/
|
||||
CreateMoveLate,
|
||||
/* This kind of CreateMove will run earlier than all CreateMove events
|
||||
* and guranteed to run before EnginePrediction
|
||||
*/
|
||||
|
@ -41,6 +41,7 @@ DECLARE_HOOKED_METHOD(LevelShutdown, void, void *);
|
||||
// ClientMode + 4
|
||||
DECLARE_HOOKED_METHOD(FireGameEvent, void, void *, IGameEvent *);
|
||||
// IBaseClient
|
||||
DECLARE_HOOKED_METHOD(CreateMoveLate, void, void *this_, int sequence_nr, float input_sample_time, bool arg3)
|
||||
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);
|
||||
|
@ -129,7 +129,7 @@ public:
|
||||
float mult2 = *(float *) (unk3);
|
||||
|
||||
float multiplier = 0.5f;
|
||||
int seed = C_BaseEntity::m_nPredictionRandomSeed() ^ (owner->entindex() | self->entindex());
|
||||
int seed = C_BaseEntity::m_nPredictionRandomSeed() ^ (owner->entindex() | (self->entindex() << 8));
|
||||
RandomSeed(seed);
|
||||
|
||||
bool result = true;
|
||||
|
@ -10,23 +10,168 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <mathlib/vector.h>
|
||||
#include "checksum_crc.h"
|
||||
|
||||
class CUserCmd
|
||||
{
|
||||
public:
|
||||
virtual ~CUserCmd(){};
|
||||
CUserCmd()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
command_number = 0;
|
||||
tick_count = 0;
|
||||
viewangles.Init();
|
||||
forwardmove = 0.0f;
|
||||
sidemove = 0.0f;
|
||||
upmove = 0.0f;
|
||||
buttons = 0;
|
||||
impulse = 0;
|
||||
weaponselect = 0;
|
||||
weaponsubtype = 0;
|
||||
random_seed = 0;
|
||||
mousedx = 0;
|
||||
mousedy = 0;
|
||||
|
||||
hasbeenpredicted = false;
|
||||
}
|
||||
|
||||
CUserCmd &operator=(const CUserCmd &src)
|
||||
{
|
||||
if (this == &src)
|
||||
return *this;
|
||||
|
||||
command_number = src.command_number;
|
||||
tick_count = src.tick_count;
|
||||
viewangles = src.viewangles;
|
||||
forwardmove = src.forwardmove;
|
||||
sidemove = src.sidemove;
|
||||
upmove = src.upmove;
|
||||
buttons = src.buttons;
|
||||
impulse = src.impulse;
|
||||
weaponselect = src.weaponselect;
|
||||
weaponsubtype = src.weaponsubtype;
|
||||
random_seed = src.random_seed;
|
||||
mousedx = src.mousedx;
|
||||
mousedy = src.mousedy;
|
||||
|
||||
hasbeenpredicted = src.hasbeenpredicted;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CUserCmd(const CUserCmd &src)
|
||||
{
|
||||
*this = src;
|
||||
}
|
||||
|
||||
CRC32_t GetChecksum(void) const
|
||||
{
|
||||
CRC32_t crc;
|
||||
|
||||
CRC32_Init(&crc);
|
||||
CRC32_ProcessBuffer(&crc, &command_number, sizeof(command_number));
|
||||
CRC32_ProcessBuffer(&crc, &tick_count, sizeof(tick_count));
|
||||
CRC32_ProcessBuffer(&crc, &viewangles, sizeof(viewangles));
|
||||
CRC32_ProcessBuffer(&crc, &forwardmove, sizeof(forwardmove));
|
||||
CRC32_ProcessBuffer(&crc, &sidemove, sizeof(sidemove));
|
||||
CRC32_ProcessBuffer(&crc, &upmove, sizeof(upmove));
|
||||
CRC32_ProcessBuffer(&crc, &buttons, sizeof(buttons));
|
||||
CRC32_ProcessBuffer(&crc, &impulse, sizeof(impulse));
|
||||
CRC32_ProcessBuffer(&crc, &weaponselect, sizeof(weaponselect));
|
||||
CRC32_ProcessBuffer(&crc, &weaponsubtype, sizeof(weaponsubtype));
|
||||
CRC32_ProcessBuffer(&crc, &random_seed, sizeof(random_seed));
|
||||
CRC32_ProcessBuffer(&crc, &mousedx, sizeof(mousedx));
|
||||
CRC32_ProcessBuffer(&crc, &mousedy, sizeof(mousedy));
|
||||
CRC32_Final(&crc);
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
// Allow command, but negate gameplay-affecting values
|
||||
void MakeInert(void)
|
||||
{
|
||||
viewangles.Init();
|
||||
forwardmove = 0.f;
|
||||
sidemove = 0.f;
|
||||
upmove = 0.f;
|
||||
buttons = 0;
|
||||
impulse = 0;
|
||||
}
|
||||
|
||||
char pad0[4];
|
||||
// For matching server and client commands for debugging
|
||||
int command_number;
|
||||
|
||||
// the tick the client created this command
|
||||
int tick_count;
|
||||
|
||||
// Player instantaneous view angles.
|
||||
Vector viewangles;
|
||||
// Intended velocities
|
||||
// forward velocity.
|
||||
float forwardmove;
|
||||
// sideways velocity.
|
||||
float sidemove;
|
||||
// upward velocity.
|
||||
float upmove;
|
||||
// Attack button states
|
||||
int buttons;
|
||||
uint8_t impulse;
|
||||
// Impulse command issued.
|
||||
byte impulse;
|
||||
char pad1[3];
|
||||
// Current weapon id
|
||||
int weaponselect;
|
||||
int weaponsubtype;
|
||||
int random_seed;
|
||||
short mousedx;
|
||||
short mousedy;
|
||||
|
||||
int random_seed; // For shared random functions
|
||||
short mousedx; // mouse accum in x from create move
|
||||
short mousedy; // mouse accum in y from create move
|
||||
|
||||
// Client only, tracks whether we've predicted this command at least once
|
||||
bool hasbeenpredicted;
|
||||
};
|
||||
char pad2[3];
|
||||
} __attribute__((packed));
|
||||
|
||||
class CVerifiedUserCmd
|
||||
{
|
||||
public:
|
||||
CUserCmd m_cmd;
|
||||
CRC32_t m_crc;
|
||||
} __attribute__((packed));
|
||||
|
||||
inline CRC32_t GetChecksum(CUserCmd *cmd)
|
||||
{
|
||||
CRC32_t crc;
|
||||
|
||||
CRC32_Init(&crc);
|
||||
CRC32_ProcessBuffer(&crc, &cmd->command_number, sizeof(cmd->command_number));
|
||||
CRC32_ProcessBuffer(&crc, &cmd->tick_count, sizeof(cmd->tick_count));
|
||||
CRC32_ProcessBuffer(&crc, &cmd->viewangles, sizeof(cmd->viewangles));
|
||||
CRC32_ProcessBuffer(&crc, &cmd->forwardmove, sizeof(cmd->forwardmove));
|
||||
CRC32_ProcessBuffer(&crc, &cmd->sidemove, sizeof(cmd->sidemove));
|
||||
CRC32_ProcessBuffer(&crc, &cmd->upmove, sizeof(cmd->upmove));
|
||||
CRC32_ProcessBuffer(&crc, &cmd->buttons, sizeof(cmd->buttons));
|
||||
CRC32_ProcessBuffer(&crc, &cmd->impulse, sizeof(cmd->impulse));
|
||||
CRC32_ProcessBuffer(&crc, &cmd->weaponselect, sizeof(cmd->weaponselect));
|
||||
CRC32_ProcessBuffer(&crc, &cmd->weaponsubtype, sizeof(cmd->weaponsubtype));
|
||||
CRC32_ProcessBuffer(&crc, &cmd->random_seed, sizeof(cmd->random_seed));
|
||||
CRC32_ProcessBuffer(&crc, &cmd->mousedx, sizeof(cmd->mousedx));
|
||||
CRC32_ProcessBuffer(&crc, &cmd->mousedy, sizeof(cmd->mousedy));
|
||||
CRC32_Final(&crc);
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
inline CUserCmd *GetCmds(void *iinput)
|
||||
{
|
||||
return *(CUserCmd **) ((uintptr_t) iinput + 0xfc);
|
||||
}
|
||||
|
||||
inline CVerifiedUserCmd *GetVerifiedCmds(void *iinput)
|
||||
{
|
||||
return *(CVerifiedUserCmd **) ((uintptr_t) iinput + 0x100);
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ void NetVars::Init()
|
||||
"m_AttributeList"); // hmmm
|
||||
this->flChargeBeginTime = gNetvars.get_offset("DT_WeaponPipebombLauncher", "PipebombLauncherLocalData", "m_flChargeBeginTime");
|
||||
this->flLastFireTime = gNetvars.get_offset("DT_TFWeaponBase", "LocalActiveTFWeaponData", "m_flLastFireTime");
|
||||
this->flObservedCritChance = gNetvars.get_offset("DT_TFWeaponBase", "LocalActiveTFWeaponData", "m_flObservedCritChance");
|
||||
this->bDistributed = gNetvars.get_offset("DT_CurrencyPack", "m_bDistributed");
|
||||
this->_condition_bits = gNetvars.get_offset("DT_TFPlayer", "m_Shared", "m_ConditionList", "_condition_bits");
|
||||
this->m_flStealthNoAttackExpire = gNetvars.get_offset("DT_TFPlayer", "m_Shared", "tfsharedlocaldata", "m_flStealthNoAttackExpire");
|
||||
|
802
src/crits.cpp
802
src/crits.cpp
@ -1,357 +1,571 @@
|
||||
/*
|
||||
* crits.cpp
|
||||
*
|
||||
* Created on: Feb 25, 2017
|
||||
* Author: nullifiedcat
|
||||
*/
|
||||
|
||||
#include <settings/Bool.hpp>
|
||||
#include "common.hpp"
|
||||
#include "Backtrack.hpp"
|
||||
#include "PlayerTools.hpp"
|
||||
#include "crits.hpp"
|
||||
#include "netadr.h"
|
||||
|
||||
std::unordered_map<int, int> command_number_mod{};
|
||||
|
||||
namespace criticals
|
||||
{
|
||||
|
||||
static settings::Boolean crit_info{ "crit.info", "false" };
|
||||
settings::Boolean enabled{ "crit.enabled", "false" };
|
||||
static settings::Boolean draw{ "crit.draw-info", "false" };
|
||||
settings::Boolean melee{ "crit.melee", "false" };
|
||||
static settings::Button crit_key{ "crit.key", "<null>" };
|
||||
static settings::Boolean auto_crit{ "crit.auto", "false" };
|
||||
static settings::Boolean crit_melee{ "crit.melee", "false" };
|
||||
static settings::Boolean crit_legiter{ "crit.force-gameplay", "false" };
|
||||
static settings::Boolean crit_experimental{ "crit.experimental", "false" };
|
||||
static settings::Boolean old_mode{ "crit.old-mode", "false" };
|
||||
|
||||
int find_next_random_crit_for_weapon(IClientEntity *weapon)
|
||||
// How much is added to bucket per shot?
|
||||
float added_per_shot = 0.0f;
|
||||
class weapon_info
|
||||
{
|
||||
int tries = 0, number = current_user_cmd->command_number, found = 0, seed_backup;
|
||||
|
||||
crithack_saved_state state{};
|
||||
state.Save(weapon);
|
||||
|
||||
seed_backup = *g_PredictionRandomSeed;
|
||||
while (!found && tries < 4096)
|
||||
public:
|
||||
float crit_bucket{};
|
||||
unsigned int weapon_seed{};
|
||||
unsigned unknown1{};
|
||||
unsigned unknown2{};
|
||||
bool unknown3{};
|
||||
float unknown4{};
|
||||
int crit_attempts{};
|
||||
int crit_count{};
|
||||
float observed_crit_chance{};
|
||||
bool unknown7{};
|
||||
weapon_info()
|
||||
{
|
||||
*g_PredictionRandomSeed = MD5_PseudoRandom(number) & 0x7FFFFFFF;
|
||||
found = re::C_TFWeaponBase::CalcIsAttackCritical(weapon);
|
||||
if (found)
|
||||
break;
|
||||
++tries;
|
||||
++number;
|
||||
}
|
||||
void Load(IClientEntity *weapon)
|
||||
{
|
||||
crit_bucket = *(float *) ((uintptr_t) weapon + 0xa38);
|
||||
weapon_seed = *(unsigned int *) ((uintptr_t) weapon + 0xb3c);
|
||||
unknown1 = *(unsigned int *) ((uintptr_t) weapon + 0xb30);
|
||||
unknown2 = *(unsigned int *) ((uintptr_t) weapon + 0xb34);
|
||||
unknown3 = *(bool *) ((uintptr_t) weapon + 0xb17);
|
||||
unknown4 = *(float *) ((uintptr_t) weapon + 0xb40);
|
||||
crit_attempts = *(int *) ((uintptr_t) weapon + 0xa3c);
|
||||
crit_count = *(int *) ((uintptr_t) weapon + 0xa40);
|
||||
observed_crit_chance = *(float *) ((uintptr_t) weapon + 0xbfc);
|
||||
unknown7 = *(bool *) ((uintptr_t) weapon + 0xb18);
|
||||
}
|
||||
weapon_info(IClientEntity *weapon)
|
||||
{
|
||||
Load(weapon);
|
||||
}
|
||||
void restore_data(IClientEntity *weapon)
|
||||
{
|
||||
*(float *) ((uintptr_t) weapon + 0xa38) = crit_bucket;
|
||||
*(unsigned int *) ((uintptr_t) weapon + 0xb3c) = weapon_seed;
|
||||
*(unsigned int *) ((uintptr_t) weapon + 0xb30) = unknown1;
|
||||
*(unsigned int *) ((uintptr_t) weapon + 0xb34) = unknown2;
|
||||
*(bool *) ((uintptr_t) weapon + 0xb17) = unknown3;
|
||||
*(float *) ((uintptr_t) weapon + 0xb40) = unknown4;
|
||||
*(int *) ((uintptr_t) weapon + 0xa3c) = crit_attempts;
|
||||
*(int *) ((uintptr_t) weapon + 0xa40) = crit_count;
|
||||
*(float *) ((uintptr_t) weapon + 0xbfc) = observed_crit_chance;
|
||||
*(bool *) ((uintptr_t) weapon + 0xb18) = unknown7;
|
||||
}
|
||||
};
|
||||
|
||||
float GetWithdrawMult(IClientEntity *wep)
|
||||
{
|
||||
weapon_info info(wep);
|
||||
// Increment call count
|
||||
int call_count = info.crit_count + 1;
|
||||
// How many times there was a check for crits
|
||||
int crit_checks = info.crit_attempts + 1;
|
||||
float flMultiply = 0.5;
|
||||
|
||||
if (g_pLocalPlayer->weapon_mode != weapon_melee)
|
||||
{
|
||||
float flTmp = min(max((((float) call_count / (float) crit_checks) - 0.1) * 1.111, 0.0), 1.0);
|
||||
flMultiply = (flTmp * 2.0) + 1.0;
|
||||
}
|
||||
|
||||
*g_PredictionRandomSeed = seed_backup;
|
||||
if (!crit_experimental || g_pLocalPlayer->weapon_mode == weaponmode::weapon_melee)
|
||||
state.Load(weapon);
|
||||
if (found)
|
||||
return number;
|
||||
return 0;
|
||||
float flToRemove = flMultiply * 3.0;
|
||||
return flToRemove;
|
||||
}
|
||||
|
||||
void unfuck_bucket(IClientEntity *weapon)
|
||||
static CatCommand debug_print_mult("debug_print_mult", "print Withdraw multiplier", []() {
|
||||
if (CE_BAD(LOCAL_E) || CE_BAD(LOCAL_W))
|
||||
return;
|
||||
logging::Info("Multiplier: %f", GetWithdrawMult(RAW_ENT(LOCAL_W)));
|
||||
});
|
||||
|
||||
static Timer bucket_cap_refresh{};
|
||||
float GetBucketCap()
|
||||
{
|
||||
static bool changed;
|
||||
static float return_value;
|
||||
static ConVar *tf_weapon_criticals = g_ICvar->FindVar("tf_weapon_criticals_bucket_cap");
|
||||
if (bucket_cap_refresh.test_and_set(5000))
|
||||
return_value = tf_weapon_criticals->GetFloat();
|
||||
return return_value;
|
||||
}
|
||||
|
||||
// Add damage parameter adds the same value that AddToCritbucket would add
|
||||
bool IsAllowedToWithdrawFromCritBucket(IClientEntity *wep, float flDamage, bool add_damage = true)
|
||||
{
|
||||
weapon_info info(wep);
|
||||
if (add_damage)
|
||||
{
|
||||
info.crit_bucket += flDamage;
|
||||
if (info.crit_bucket > GetBucketCap())
|
||||
info.crit_bucket = GetBucketCap();
|
||||
}
|
||||
float flToRemove = GetWithdrawMult(wep) * flDamage;
|
||||
// Can remove
|
||||
if (flToRemove <= info.crit_bucket)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// As opposed to the function above this does actual writing
|
||||
void SimulateNormalShot(IClientEntity *wep, float flDamage)
|
||||
{
|
||||
weapon_info info(wep);
|
||||
info.crit_bucket += flDamage;
|
||||
if (info.crit_bucket > GetBucketCap())
|
||||
info.crit_bucket = GetBucketCap();
|
||||
// Write other values important for iteration
|
||||
info.crit_attempts++;
|
||||
info.restore_data(wep);
|
||||
}
|
||||
|
||||
bool CanWeaponCrit(IClientEntity *wep)
|
||||
{
|
||||
// Are we allowed to even use that much?
|
||||
if (g_pLocalPlayer->weapon_mode != weapon_melee && !IsAllowedToWithdrawFromCritBucket(wep, added_per_shot))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Calculate shots until crit
|
||||
int ShotsUntilCrit(IClientEntity *wep)
|
||||
{
|
||||
weapon_info info(wep);
|
||||
// How many shots until we can crit
|
||||
int shots = 0;
|
||||
// Predicting 100 shots should be fine
|
||||
for (shots = 0; shots < 100; shots++)
|
||||
{
|
||||
if (IsAllowedToWithdrawFromCritBucket(wep, added_per_shot, true))
|
||||
break;
|
||||
// Do calculations
|
||||
SimulateNormalShot(wep, added_per_shot);
|
||||
}
|
||||
// Restore variables
|
||||
info.restore_data(wep);
|
||||
return shots;
|
||||
}
|
||||
|
||||
typedef float (*AttribHookFloat_t)(float, const char *, IClientEntity *, void *, bool);
|
||||
|
||||
// Calculate Crit penalty
|
||||
float CalcCritPenalty(IClientEntity *wep)
|
||||
{
|
||||
// Need this to get crit chance from weapon
|
||||
static uintptr_t AttribHookFloat = 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 75 ? 85 C0 0F 84 ? ? ? ? 8D 55 ? 89 04 24 31 DB 89 54 24");
|
||||
static AttribHookFloat_t AttribHookFloat_fn = AttribHookFloat_t(AttribHookFloat);
|
||||
|
||||
// Player specific Multiplier
|
||||
float crit_mult = re::CTFPlayerShared::GetCritMult((re::CTFPlayerShared *) (((uintptr_t) RAW_ENT(LOCAL_E)) + 0x5f3));
|
||||
|
||||
// Weapon specific Multiplier
|
||||
static std::string query = "mult_crit_chance";
|
||||
float flMultCritChance = AttribHookFloat_fn(crit_mult * 0.02, query.c_str(), wep, 0, 1);
|
||||
|
||||
return flMultCritChance;
|
||||
}
|
||||
|
||||
std::pair<float, float> CritMultInfo(IClientEntity *wep)
|
||||
{
|
||||
float cur_crit = CalcCritPenalty(wep);
|
||||
float observed_chance = CE_FLOAT(LOCAL_W, netvar.flObservedCritChance);
|
||||
float needed_chance = cur_crit + 0.1f;
|
||||
return std::pair<float, float>(observed_chance, needed_chance);
|
||||
}
|
||||
|
||||
CatCommand debug_observed("debug_observed_crit", "debug", []() {
|
||||
if (CE_GOOD(LOCAL_E) && CE_GOOD(LOCAL_W))
|
||||
logging::Info("%f", CE_FLOAT(LOCAL_W, netvar.flObservedCritChance));
|
||||
});
|
||||
|
||||
CatCommand debug_damage("debug_print_damage_until_crit", "debug", []() {
|
||||
if (CE_GOOD(LOCAL_E) && CE_GOOD(LOCAL_W))
|
||||
{
|
||||
float penalty = CalcCritPenalty(RAW_ENT(LOCAL_W));
|
||||
|
||||
float observed_chance = CE_FLOAT(LOCAL_W, netvar.flObservedCritChance);
|
||||
|
||||
rgba_t color = colors::green;
|
||||
if (penalty + 0.1f < observed_chance)
|
||||
color = colors::red;
|
||||
|
||||
Color valve_color(color.r * 255.0f, color.g * 255.0f, color.b * 255.0f, color.a * 255.0f);
|
||||
g_ICvar->ConsoleColorPrintf(valve_color, "%f\n", observed_chance);
|
||||
}
|
||||
});
|
||||
|
||||
CatCommand debug_shots("debug_print_shots_until_crit", "debug", []() {
|
||||
if (CE_GOOD(LOCAL_E) && CE_GOOD(LOCAL_W))
|
||||
logging::Info("%d", ShotsUntilCrit(RAW_ENT(LOCAL_W)));
|
||||
});
|
||||
|
||||
int next_crit_tick()
|
||||
{
|
||||
auto wep = RAW_ENT(LOCAL_W);
|
||||
|
||||
int old_seed = MD5_PseudoRandom(current_late_user_cmd->command_number) & 0x7FFFFFFF;
|
||||
// Try 4096 times
|
||||
for (int i = 0; i < 4096; i++)
|
||||
{
|
||||
int cmd_number = current_late_user_cmd->command_number + i;
|
||||
// Set random seed
|
||||
*g_PredictionRandomSeed = MD5_PseudoRandom(cmd_number) & 0x7FFFFFFF;
|
||||
// Save weapon state to not break anything
|
||||
weapon_info info(wep);
|
||||
bool is_crit = re::C_TFWeaponBase::CalcIsAttackCritical(wep);
|
||||
// Restore state
|
||||
info.restore_data(wep);
|
||||
// Is a crit
|
||||
if (is_crit)
|
||||
{
|
||||
*g_PredictionRandomSeed = old_seed;
|
||||
return cmd_number;
|
||||
}
|
||||
}
|
||||
*g_PredictionRandomSeed = old_seed;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static Timer random_crit_refresh{};
|
||||
bool random_crits_enabled()
|
||||
{
|
||||
static bool return_value;
|
||||
static ConVar *tf_weapon_criticals = g_ICvar->FindVar("tf_weapon_criticals");
|
||||
if (random_crit_refresh.test_and_set(3000))
|
||||
return_value = tf_weapon_criticals->GetBool();
|
||||
return return_value;
|
||||
}
|
||||
|
||||
static int force_ticks = 0;
|
||||
|
||||
bool IsEnabled()
|
||||
{
|
||||
// Random crits on
|
||||
if (!random_crits_enabled())
|
||||
return false;
|
||||
// Weapon specific checks
|
||||
if (force_ticks || ((melee && g_pLocalPlayer->weapon_mode == weapon_melee) || (enabled && g_pLocalPlayer->weapon_mode != weapon_melee && (!crit_key || crit_key.isKeyDown()))))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
static int idx = 0;
|
||||
static std::vector<int> crit_cmds;
|
||||
|
||||
void force_crit()
|
||||
{
|
||||
if (!IsEnabled())
|
||||
return;
|
||||
if (!old_mode && g_pLocalPlayer->weapon_mode != weapon_melee && LOCAL_W->m_iClassID() != CL_CLASS(CTFPipebombLauncher))
|
||||
{
|
||||
if (crit_cmds.size())
|
||||
{
|
||||
if (idx > crit_cmds.size())
|
||||
idx = 0;
|
||||
// Magic crit cmds
|
||||
current_late_user_cmd->command_number = crit_cmds[idx];
|
||||
current_late_user_cmd->random_seed = MD5_PseudoRandom(current_late_user_cmd->command_number) & 0x7FFFFFFF;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int next_crit = next_crit_tick();
|
||||
if (next_crit != -1)
|
||||
{
|
||||
// We can just force to nearest crit for melee, and sticky launchers apparently
|
||||
if (g_pLocalPlayer->weapon_mode == weapon_melee || LOCAL_W->m_iClassID() == CL_CLASS(CTFPipebombLauncher))
|
||||
{
|
||||
if (LOCAL_W->m_iClassID() == CL_CLASS(CTFPipebombLauncher))
|
||||
{
|
||||
if (!force_ticks)
|
||||
force_ticks = 3;
|
||||
force_ticks--;
|
||||
}
|
||||
current_late_user_cmd->command_number = next_crit;
|
||||
current_late_user_cmd->random_seed = MD5_PseudoRandom(next_crit) & 0x7FFFFFFF;
|
||||
}
|
||||
// No crit, don't attack
|
||||
else if (current_late_user_cmd->command_number != next_crit)
|
||||
current_user_cmd->buttons &= ~IN_ATTACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_cmds()
|
||||
{
|
||||
// Melee weapons do not matter
|
||||
if (g_pLocalPlayer->weapon_mode == weapon_melee)
|
||||
return;
|
||||
auto weapon = RAW_ENT(LOCAL_W);
|
||||
static float last_bucket;
|
||||
static int last_weapon;
|
||||
|
||||
if (current_user_cmd->command_number)
|
||||
changed = false;
|
||||
|
||||
float &bucket = re::C_TFWeaponBase::crit_bucket_(weapon);
|
||||
|
||||
if (bucket != last_bucket)
|
||||
int lowest_value = INT_MAX;
|
||||
int cur_cmdnum = current_late_user_cmd->command_number;
|
||||
bool old_cmds = false;
|
||||
for (auto &cmd : crit_cmds)
|
||||
if (cmd < cur_cmdnum)
|
||||
old_cmds = true;
|
||||
if (weapon->entindex() != last_weapon || old_cmds)
|
||||
{
|
||||
if (changed && weapon->entindex() == last_weapon)
|
||||
// Used later
|
||||
int xor_dat = (weapon->entindex() << 8 | LOCAL_E->m_IDX);
|
||||
// Clear old data
|
||||
crit_cmds.clear();
|
||||
added_per_shot = 0.0f;
|
||||
|
||||
// 30.000.000 seems like a good cap
|
||||
for (int i = cur_cmdnum, j = 5; i <= cur_cmdnum + 30000000 + 200 && j > 0; i++)
|
||||
{
|
||||
bucket = last_bucket;
|
||||
// Reverse seed
|
||||
int iSeed = MD5_PseudoRandom(i) & 0x7fffffff;
|
||||
int tempSeed = iSeed ^ (xor_dat);
|
||||
RandomSeed(tempSeed);
|
||||
int iResult = RandomInt(0, 9999);
|
||||
// Returns something low? Store it.
|
||||
if (iResult <= lowest_value)
|
||||
{
|
||||
if (i > cur_cmdnum + 200)
|
||||
{
|
||||
// Found lower value
|
||||
if (iResult < lowest_value)
|
||||
{
|
||||
crit_cmds.clear();
|
||||
lowest_value = iResult;
|
||||
j = 5;
|
||||
}
|
||||
crit_cmds.push_back(i);
|
||||
if (added_per_shot == 0.0f)
|
||||
{
|
||||
int backup_seed = *g_PredictionRandomSeed;
|
||||
// Set random seed
|
||||
*g_PredictionRandomSeed = MD5_PseudoRandom(i) & 0x7FFFFFFF;
|
||||
// Save weapon state to not break anything
|
||||
weapon_info info(weapon);
|
||||
// We modify and write using this
|
||||
weapon_info write(weapon);
|
||||
|
||||
// Set Values so they don't get in the way
|
||||
write.crit_bucket = GetBucketCap();
|
||||
write.crit_attempts = 100000000;
|
||||
write.crit_count = 0;
|
||||
write.observed_crit_chance = 0.0f;
|
||||
|
||||
// Write onto weapon
|
||||
write.restore_data(weapon);
|
||||
|
||||
bool is_crit = re::C_TFWeaponBase::CalcIsAttackCritical(weapon);
|
||||
|
||||
if (is_crit)
|
||||
{
|
||||
weapon_info new_info(weapon);
|
||||
// How much is Subtracted per shot
|
||||
added_per_shot = (GetBucketCap() - new_info.crit_bucket) / 3.0f;
|
||||
}
|
||||
|
||||
// Restore original state
|
||||
info.restore_data(weapon);
|
||||
// Adjust with multiplier
|
||||
|
||||
*g_PredictionRandomSeed = backup_seed;
|
||||
}
|
||||
j--;
|
||||
}
|
||||
}
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
last_weapon = weapon->entindex();
|
||||
last_bucket = bucket;
|
||||
}
|
||||
|
||||
struct cached_calculation_s
|
||||
// Fix bucket on non-local servers
|
||||
void unfuck_bucket(IClientEntity *weapon)
|
||||
{
|
||||
int command_number;
|
||||
int init_command;
|
||||
int weapon_entity;
|
||||
};
|
||||
INetChannel *ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
|
||||
if (!ch)
|
||||
return;
|
||||
auto addr = ch->GetRemoteAddress();
|
||||
// Local server needs no fixing
|
||||
if (addr.type == NA_LOOPBACK)
|
||||
return;
|
||||
// Melee doesn't either
|
||||
if (g_pLocalPlayer->weapon_mode == weapon_melee)
|
||||
return;
|
||||
|
||||
static cached_calculation_s cached_calculation{};
|
||||
static float last_bucket;
|
||||
static int last_weapon;
|
||||
static unsigned last_tick;
|
||||
static float last_fire_time;
|
||||
|
||||
static int number = 0;
|
||||
static int lastnumber = 0;
|
||||
static int lastusercmd = 0;
|
||||
static const model_t *lastweapon = nullptr;
|
||||
|
||||
bool force_crit(IClientEntity *weapon)
|
||||
{
|
||||
auto command_number = current_user_cmd->command_number;
|
||||
|
||||
if (lastnumber < command_number || lastweapon != weapon->GetModel() || lastnumber - command_number > 1000)
|
||||
weapon_info info(weapon);
|
||||
float bucket = info.crit_bucket;
|
||||
float fire_time = NET_FLOAT(weapon, netvar.flLastFireTime);
|
||||
if (weapon->entindex() == last_weapon)
|
||||
{
|
||||
if (!*crit_experimental || g_pLocalPlayer->weapon_mode == weapon_melee)
|
||||
if (bucket != last_bucket && last_bucket != 0)
|
||||
{
|
||||
if (cached_calculation.init_command > command_number || command_number - cached_calculation.init_command > 4096 || (command_number && (cached_calculation.command_number < command_number)))
|
||||
cached_calculation.weapon_entity = 0;
|
||||
if (cached_calculation.weapon_entity == weapon->entindex())
|
||||
return bool(cached_calculation.command_number);
|
||||
}
|
||||
number = find_next_random_crit_for_weapon(weapon);
|
||||
}
|
||||
else
|
||||
number = lastnumber;
|
||||
// logging::Info("Found critical: %d -> %d", command_number,
|
||||
// number);
|
||||
if (crit_experimental && GetWeaponMode() != weapon_melee)
|
||||
{
|
||||
cached_calculation.command_number = number;
|
||||
cached_calculation.weapon_entity = LOCAL_W->m_IDX;
|
||||
if (!crit_legiter)
|
||||
{
|
||||
if (number && number != command_number)
|
||||
if (last_tick == tickcount)
|
||||
{
|
||||
command_number_mod[command_number] = number;
|
||||
auto ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
|
||||
*(int *) ((uint64_t) ch + offsets::m_nOutSequenceNr()) = number - 1;
|
||||
bucket = last_bucket;
|
||||
info.crit_bucket = bucket;
|
||||
info.restore_data(weapon);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (number && number != command_number && number - 30 < command_number)
|
||||
{
|
||||
|
||||
command_number_mod[command_number] = number;
|
||||
auto ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
|
||||
*(int *) ((uint64_t) ch + offsets::m_nOutSequenceNr()) = number - 1;
|
||||
// We want to adjust it ourselves
|
||||
if (bucket > last_bucket)
|
||||
bucket = last_bucket;
|
||||
}
|
||||
|
||||
// We shot, add to bucket
|
||||
if (last_fire_time != fire_time)
|
||||
{
|
||||
bucket += added_per_shot;
|
||||
// Don't overshoot
|
||||
if (bucket > GetBucketCap())
|
||||
bucket = GetBucketCap();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!crit_legiter)
|
||||
{
|
||||
if (command_number != number && number && number != command_number)
|
||||
current_user_cmd->buttons &= ~IN_ATTACK;
|
||||
else
|
||||
current_user_cmd->buttons |= IN_ATTACK;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (command_number + 30 > number && number && number != command_number)
|
||||
current_user_cmd->buttons &= ~IN_ATTACK;
|
||||
else
|
||||
current_user_cmd->buttons |= IN_ATTACK;
|
||||
}
|
||||
}
|
||||
lastweapon = weapon->GetModel();
|
||||
lastnumber = number;
|
||||
return number != 0;
|
||||
|
||||
last_fire_time = fire_time;
|
||||
last_tick = tickcount;
|
||||
last_weapon = weapon->entindex();
|
||||
last_bucket = bucket;
|
||||
|
||||
info.crit_bucket = bucket;
|
||||
info.restore_data(weapon);
|
||||
}
|
||||
|
||||
void handle_melee_crit(IClientEntity *weapon)
|
||||
void CreateMove()
|
||||
{
|
||||
// Should i crit or save my crits?
|
||||
bool in_range = false;
|
||||
for (int i = 1; i <= g_IEngine->GetMaxClients(); i++)
|
||||
{
|
||||
// We already found out we should crit
|
||||
if (in_range)
|
||||
break;
|
||||
CachedEntity *target = ENTITY(i);
|
||||
|
||||
// Various ignores
|
||||
if (CE_BAD(target) || !target->m_bAlivePlayer() || !player_tools::shouldTarget(target) || !target->m_bEnemy())
|
||||
continue;
|
||||
|
||||
// Shorten code
|
||||
namespace bt = hacks::shared::backtrack;
|
||||
|
||||
// If backtracking, use the backtrack ticks
|
||||
if (bt::isBacktrackEnabled)
|
||||
{
|
||||
// Loop all ticks
|
||||
for (int j = 0; j < 66; j++)
|
||||
{
|
||||
auto bt_data = bt::headPositions[i][j];
|
||||
// Invalid tick
|
||||
if (!bt::ValidTick(bt_data, target))
|
||||
continue;
|
||||
// If within 400 HU range then crit, else just farm crits
|
||||
if (bt_data.collidable.center.DistTo(g_pLocalPlayer->v_Eye) < 400.0f)
|
||||
{
|
||||
in_range = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector ent_orig = target->m_vecOrigin();
|
||||
// Center ent origin
|
||||
ent_orig += (RAW_ENT(target)->GetCollideable()->OBBMaxs() + RAW_ENT(target)->GetCollideable()->OBBMins()) / 2;
|
||||
// In range, crit!
|
||||
if (ent_orig.DistTo(g_pLocalPlayer->v_Eye) < 400.0f)
|
||||
{
|
||||
in_range = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (in_range)
|
||||
force_crit(weapon);
|
||||
else
|
||||
{
|
||||
// Please don't crit thanks
|
||||
if (find_next_random_crit_for_weapon(weapon) == current_user_cmd->command_number)
|
||||
{
|
||||
// Backup
|
||||
int original_cmd = current_user_cmd->command_number;
|
||||
// How many crits in a row?
|
||||
int found = 0;
|
||||
// Ensure not to cockblock when critboosted, 5 should be plently
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
current_user_cmd->command_number++;
|
||||
if (find_next_random_crit_for_weapon(weapon) == current_user_cmd->command_number)
|
||||
found++;
|
||||
}
|
||||
// If we found less than 10 crits in a row, please don't crit thanks
|
||||
if (found < 10)
|
||||
current_user_cmd->buttons &= ~IN_ATTACK;
|
||||
current_user_cmd->command_number = original_cmd;
|
||||
}
|
||||
}
|
||||
}
|
||||
void create_move()
|
||||
{
|
||||
if (!crit_key && !crit_melee && !auto_crit)
|
||||
if (!enabled && !melee && !draw)
|
||||
return;
|
||||
if (!random_crits_enabled())
|
||||
if (CE_BAD(LOCAL_E) || CE_BAD(LOCAL_W))
|
||||
return;
|
||||
if (CE_BAD(LOCAL_W))
|
||||
|
||||
unfuck_bucket(RAW_ENT(LOCAL_W));
|
||||
// Update cmds
|
||||
update_cmds();
|
||||
if (!enabled && !melee)
|
||||
return;
|
||||
if (current_user_cmd->command_number)
|
||||
lastusercmd = current_user_cmd->command_number;
|
||||
if (!force_ticks && (!(melee && g_pLocalPlayer->weapon_mode == weapon_melee) && crit_key && !crit_key.isKeyDown()))
|
||||
return;
|
||||
if (!current_late_user_cmd->command_number)
|
||||
return;
|
||||
|
||||
IClientEntity *weapon = RAW_ENT(LOCAL_W);
|
||||
if (!re::C_TFWeaponBase::IsBaseCombatWeapon(weapon))
|
||||
return;
|
||||
if (!re::C_TFWeaponBase::AreRandomCritsEnabled(weapon))
|
||||
return;
|
||||
if (!CanShoot())
|
||||
if (!re::C_TFWeaponBase::CanFireCriticalShot(weapon, false, nullptr))
|
||||
return;
|
||||
unfuck_bucket(weapon);
|
||||
if ((current_user_cmd->buttons & IN_ATTACK) && ((crit_key && crit_key.isKeyDown()) || auto_crit) && current_user_cmd->command_number)
|
||||
|
||||
if (force_ticks || (CanShoot() && current_late_user_cmd->buttons & IN_ATTACK))
|
||||
{
|
||||
if (GetWeaponMode() == weapon_melee)
|
||||
handle_melee_crit(weapon);
|
||||
// Are we even allowed to crit?
|
||||
if (CanWeaponCrit(RAW_ENT(LOCAL_W)))
|
||||
force_crit();
|
||||
}
|
||||
}
|
||||
|
||||
// Storage
|
||||
static int last_crit_tick = -1;
|
||||
static int last_bucket = 0;
|
||||
static int shots_until_crit = 0;
|
||||
static int last_wep = 0;
|
||||
static std::pair<float, float> crit_mult_info;
|
||||
void Draw()
|
||||
{
|
||||
if (!draw)
|
||||
return;
|
||||
if (!g_IEngine->GetNetChannelInfo())
|
||||
last_crit_tick = -1;
|
||||
if (CE_GOOD(LOCAL_E) && CE_GOOD(LOCAL_W))
|
||||
{
|
||||
auto wep = RAW_ENT(LOCAL_W);
|
||||
float bucket = weapon_info(wep).crit_bucket;
|
||||
|
||||
// Reset because too old
|
||||
if (wep->entindex() != last_wep || last_crit_tick - current_late_user_cmd->command_number < 0 || (last_crit_tick - current_late_user_cmd->command_number) * g_GlobalVars->interval_per_tick > 30)
|
||||
last_crit_tick = next_crit_tick();
|
||||
|
||||
// Used by multiple things
|
||||
bool can_crit = CanWeaponCrit(wep);
|
||||
|
||||
if (bucket != last_bucket || wep->entindex() != last_wep)
|
||||
{
|
||||
// Recalculate shots until crit
|
||||
if (!can_crit)
|
||||
shots_until_crit = ShotsUntilCrit(wep);
|
||||
// Recalc crit multiplier info
|
||||
crit_mult_info = CritMultInfo(wep);
|
||||
}
|
||||
|
||||
// Display for when crithack is active/can't be active
|
||||
if (IsEnabled() && last_crit_tick != -1)
|
||||
AddCenterString("Forcing Crits!", colors::red);
|
||||
|
||||
// Mark if the weapon can randomly crit or not
|
||||
if (!re::C_TFWeaponBase::CanFireCriticalShot(RAW_ENT(LOCAL_W), false, nullptr))
|
||||
{
|
||||
AddCenterString("Weapon cannot randomly crit.", colors::red);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!can_crit)
|
||||
AddCenterString("Shots until crit: " + std::to_string(shots_until_crit), colors::orange);
|
||||
|
||||
// Mark bucket as ready/not ready
|
||||
auto color = colors::red;
|
||||
if (can_crit)
|
||||
color = colors::green;
|
||||
AddCenterString("Crit Bucket: " + std::to_string(bucket), color);
|
||||
|
||||
// Print crit Multiplier information
|
||||
|
||||
// Crit chance is low enough
|
||||
if (crit_mult_info.first < crit_mult_info.second)
|
||||
color = colors::green;
|
||||
// Cannot crit
|
||||
else
|
||||
force_crit(weapon);
|
||||
}
|
||||
else if ((current_user_cmd->buttons & IN_ATTACK) && current_user_cmd->command_number && GetWeaponMode() == weapon_melee && (crit_melee || auto_crit) && g_pLocalPlayer->weapon()->m_iClassID() != CL_CLASS(CTFKnife))
|
||||
{
|
||||
handle_melee_crit(weapon);
|
||||
}
|
||||
else if ((current_user_cmd->buttons & IN_ATTACK) && current_user_cmd->command_number && GetWeaponMode() != weapon_melee && crit_key)
|
||||
{
|
||||
// Save crits!
|
||||
if (find_next_random_crit_for_weapon(weapon) == current_user_cmd->command_number)
|
||||
color = colors::red;
|
||||
AddCenterString("Crit Penalty: " + std::to_string(crit_mult_info.first), color);
|
||||
|
||||
// Time until crit
|
||||
if (old_mode && can_crit && last_crit_tick != -1)
|
||||
{
|
||||
// Backup
|
||||
int original_cmd = current_user_cmd->command_number;
|
||||
// How many crits in a row?
|
||||
int found = 0;
|
||||
// Ensure not to cockblock when critboosted, 5 should be plently
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
current_user_cmd->command_number++;
|
||||
if (find_next_random_crit_for_weapon(weapon) == current_user_cmd->command_number)
|
||||
found++;
|
||||
}
|
||||
// If we found less than 5 crits in a row, please don't crit thanks
|
||||
if (found < 5)
|
||||
current_user_cmd->buttons &= ~IN_ATTACK;
|
||||
current_user_cmd->command_number = original_cmd;
|
||||
// Ticks / Ticks per second
|
||||
float time = (last_crit_tick - current_late_user_cmd->command_number) * g_GlobalVars->interval_per_tick;
|
||||
AddCenterString("Crit in " + std::to_string(time) + "s", colors::orange);
|
||||
}
|
||||
|
||||
// Update
|
||||
last_bucket = bucket;
|
||||
last_wep = wep->entindex();
|
||||
}
|
||||
}
|
||||
|
||||
bool random_crits_enabled()
|
||||
void LevelShutdown()
|
||||
{
|
||||
static ConVar *tf_weapon_criticals = g_ICvar->FindVar("tf_weapon_criticals");
|
||||
return tf_weapon_criticals->GetBool();
|
||||
last_crit_tick = -1;
|
||||
}
|
||||
|
||||
#if ENABLE_VISUALS
|
||||
void draw()
|
||||
{
|
||||
if (CE_BAD(LOCAL_W))
|
||||
return;
|
||||
IClientEntity *weapon = RAW_ENT(LOCAL_W);
|
||||
if (!weapon)
|
||||
return;
|
||||
if (!re::C_TFWeaponBase::IsBaseCombatWeapon(weapon))
|
||||
return;
|
||||
if (!re::C_TFWeaponBase::AreRandomCritsEnabled(weapon))
|
||||
return;
|
||||
if (crit_info && CE_GOOD(LOCAL_W))
|
||||
{
|
||||
if (crit_key.isKeyDown() || auto_crit)
|
||||
{
|
||||
AddCenterString("FORCED CRITS!", colors::red);
|
||||
}
|
||||
IF_GAME(IsTF2())
|
||||
{
|
||||
if (!random_crits_enabled())
|
||||
AddCenterString("Random crits are disabled", colors::yellow);
|
||||
else
|
||||
{
|
||||
if (!re::C_TFWeaponBase::CanFireCriticalShot(RAW_ENT(LOCAL_W), false, nullptr))
|
||||
AddCenterString("Weapon can't randomly crit", colors::yellow);
|
||||
else if (lastusercmd)
|
||||
{
|
||||
if (number > lastusercmd)
|
||||
{
|
||||
float nextcrit = ((float) number - (float) lastusercmd) / (float) 90;
|
||||
if (nextcrit > 0.0f)
|
||||
{
|
||||
AddCenterString(format("Time to next crit: ", nextcrit, "s"), colors::orange);
|
||||
}
|
||||
}
|
||||
AddCenterString("Weapon can randomly crit");
|
||||
}
|
||||
}
|
||||
AddCenterString(format("Bucket: ", re::C_TFWeaponBase::crit_bucket_(RAW_ENT(LOCAL_W))));
|
||||
}
|
||||
// AddCenterString(format("Time: ",
|
||||
// *(float*)((uintptr_t)RAW_ENT(LOCAL_W) + 2872u)));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
static InitRoutine init([]() {
|
||||
EC::Register(EC::CreateMove, criticals::create_move, "cm_crits", EC::very_late);
|
||||
#if ENABLE_VISUALS
|
||||
EC::Register(EC::Draw, criticals::draw, "draw_crits");
|
||||
#endif
|
||||
EC::Register(EC::CreateMoveLate, CreateMove, "crit_cm");
|
||||
EC::Register(EC::Draw, Draw, "crit_draw");
|
||||
EC::Register(EC::LevelShutdown, LevelShutdown, "crit_lvlshutdown");
|
||||
});
|
||||
} // namespace criticals
|
||||
|
||||
void crithack_saved_state::Load(IClientEntity *entity)
|
||||
{
|
||||
*(float *) (uintptr_t(entity) + 2868) = unknown2868;
|
||||
*(float *) (uintptr_t(entity) + 2864) = unknown2864;
|
||||
*(float *) (uintptr_t(entity) + 2880) = unknown2880;
|
||||
*(float *) (uintptr_t(entity) + 2616) = bucket2616;
|
||||
*(int *) (uintptr_t(entity) + 2620) = unknown2620;
|
||||
*(int *) (uintptr_t(entity) + 2876) = seed2876;
|
||||
*(char *) (uintptr_t(entity) + 2839) = unknown2839;
|
||||
}
|
||||
|
||||
void crithack_saved_state::Save(IClientEntity *entity)
|
||||
{
|
||||
unknown2868 = *(float *) (uintptr_t(entity) + 2868);
|
||||
unknown2864 = *(float *) (uintptr_t(entity) + 2864);
|
||||
unknown2880 = *(float *) (uintptr_t(entity) + 2880);
|
||||
bucket2616 = *(float *) (uintptr_t(entity) + 2616);
|
||||
unknown2620 = *(int *) (uintptr_t(entity) + 2620);
|
||||
seed2876 = *(int *) (uintptr_t(entity) + 2876);
|
||||
unknown2839 = *(char *) (uintptr_t(entity) + 2839);
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ void GlobalSettings::Init()
|
||||
}
|
||||
|
||||
CUserCmd *current_user_cmd{ nullptr };
|
||||
CUserCmd *current_late_user_cmd{ nullptr };
|
||||
|
||||
bool isHackActive()
|
||||
{
|
||||
|
@ -185,6 +185,7 @@ void hack::Hook()
|
||||
|
||||
hooks::client.Set(g_IBaseClient);
|
||||
hooks::client.HookMethod(HOOK_ARGS(DispatchUserMessage));
|
||||
hooks::client.HookMethod(HOOK_ARGS(CreateMoveLate));
|
||||
#if ENABLE_VISUALS
|
||||
hooks::client.HookMethod(HOOK_ARGS(FrameStageNotify));
|
||||
hooks::client.HookMethod(HOOK_ARGS(IN_KeyEvent));
|
||||
|
@ -1575,6 +1575,15 @@ bool IsEntityVisiblePenetration(CachedEntity *entity, int hb)
|
||||
return false;
|
||||
}
|
||||
|
||||
void ValidateUserCmd(CUserCmd *cmd, int sequence_nr)
|
||||
{
|
||||
CRC32_t crc = GetChecksum(cmd);
|
||||
if (crc != GetVerifiedCmds(g_IInput)[sequence_nr % VERIFIED_CMD_SIZE].m_crc)
|
||||
{
|
||||
*cmd = GetVerifiedCmds(g_IInput)[sequence_nr % VERIFIED_CMD_SIZE].m_cmd;
|
||||
}
|
||||
}
|
||||
|
||||
// Used for getting class names
|
||||
CatCommand print_classnames("debug_print_classnames", "Lists classnames currently available in console", []() {
|
||||
// Create a tmp ent for the loop
|
||||
|
@ -411,4 +411,44 @@ DEFINE_HOOKED_METHOD(CreateMove, bool, void *this_, float input_sample_time, CUs
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void WriteCmd(IInput *input, CUserCmd *cmd, int sequence_nr)
|
||||
{
|
||||
// Write the usercmd
|
||||
GetVerifiedCmds(input)[sequence_nr % VERIFIED_CMD_SIZE].m_cmd = *cmd;
|
||||
GetVerifiedCmds(input)[sequence_nr % VERIFIED_CMD_SIZE].m_crc = GetChecksum(cmd);
|
||||
GetCmds(input)[sequence_nr % VERIFIED_CMD_SIZE] = *cmd;
|
||||
}
|
||||
|
||||
DEFINE_HOOKED_METHOD(CreateMoveLate, void, void *this_, int sequence_nr, float input_sample_time, bool arg3)
|
||||
{
|
||||
// Call original function, includes Early CreateMove
|
||||
original::CreateMoveLate(this_, sequence_nr, input_sample_time, arg3);
|
||||
CUserCmd *cmd = g_IInput->GetUserCmd(sequence_nr);
|
||||
|
||||
if (!cmd)
|
||||
return;
|
||||
|
||||
current_late_user_cmd = cmd;
|
||||
|
||||
if (!isHackActive())
|
||||
{
|
||||
WriteCmd(g_IInput, current_late_user_cmd, sequence_nr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_IEngine->IsInGame())
|
||||
{
|
||||
WriteCmd(g_IInput, current_late_user_cmd, sequence_nr);
|
||||
return;
|
||||
}
|
||||
|
||||
PROF_SECTION(CreateMoveLate);
|
||||
|
||||
// Run EC
|
||||
EC::run(EC::CreateMoveLate);
|
||||
|
||||
// Write the usercmd
|
||||
WriteCmd(g_IInput, current_late_user_cmd, sequence_nr);
|
||||
}
|
||||
} // namespace hooked_methods
|
||||
|
@ -10,26 +10,10 @@ namespace hooked_methods
|
||||
|
||||
DEFINE_HOOKED_METHOD(GetUserCmd, CUserCmd *, IInput *this_, int sequence_number)
|
||||
{
|
||||
CUserCmd *def = original::GetUserCmd(this_, sequence_number);
|
||||
int oldcmd;
|
||||
INetChannel *ch;
|
||||
|
||||
if (def == nullptr)
|
||||
return def;
|
||||
|
||||
if (command_number_mod.find(def->command_number) != command_number_mod.end())
|
||||
{
|
||||
// logging::Info("Replacing command %i with %i", def->command_number,
|
||||
// command_number_mod[def->command_number]);
|
||||
oldcmd = def->command_number;
|
||||
def->command_number = command_number_mod[def->command_number];
|
||||
def->random_seed = MD5_PseudoRandom(unsigned(def->command_number)) & 0x7fffffff;
|
||||
command_number_mod.erase(command_number_mod.find(oldcmd));
|
||||
*(int *) ((unsigned) g_IBaseClientState + offsets::lastoutgoingcommand()) = def->command_number - 1;
|
||||
ch = (INetChannel *) g_IEngine->GetNetChannelInfo(); //*(INetChannel**)((unsigned)g_IBaseClientState
|
||||
//+ offsets::m_NetChannel());
|
||||
*(int *) ((unsigned) ch + offsets::m_nOutSequenceNr()) = def->command_number - 1;
|
||||
}
|
||||
return def;
|
||||
// We need to overwrite this if crithack is on
|
||||
if (criticals::enabled || criticals::melee)
|
||||
return &GetCmds(this_)[sequence_number % VERIFIED_CMD_SIZE];
|
||||
else
|
||||
return original::GetUserCmd(this_, sequence_number);
|
||||
}
|
||||
} // namespace hooked_methods
|
||||
} // namespace hooked_methods
|
||||
|
Reference in New Issue
Block a user