Rewrite crithack
This commit is contained in:
parent
5a88fb7842
commit
fddbac7d9d
@ -1,13 +1,11 @@
|
|||||||
<Tab name="Crits" padding="4 4 4 4">
|
<Tab name="Crits" padding="4 4 4 4">
|
||||||
<Box padding="12 6 6 6" width="content" height="content" name="Crits">
|
<Box padding="12 6 6 6" width="content" height="content" name="Crits">
|
||||||
<List width="200">
|
<List width="200">
|
||||||
<AutoVariable width="fill" target="crit.key" label="Crit Key"/>
|
<AutoVariable width="fill" target="crit.enabled" label="Enable Crithack" tooltip="Note that melee crits will not be enabled"/>
|
||||||
<AutoVariable width="fill" target="crit.auto" label="Always Crit (Ignore Crit Key)"/>
|
<AutoVariable width="fill" target="crit.key" label="Crit Key" tooltip="Key used for non-melee crits"/>
|
||||||
<AutoVariable width="fill" target="crit.info" label="Crit Info"/>
|
<AutoVariable width="fill" target="crit.draw-info" label="Crit Info" tooltip="Draw Useful information on screen"/>
|
||||||
<AutoVariable width="fill" target="crit.experimental" label="Instant Crits"/>
|
<AutoVariable width="fill" target="crit.melee" label="Melee Crits" tooltip="Enable Melee crits (Not bound to key)"/>
|
||||||
<AutoVariable width="fill" target="crit.melee" label="Melee Crits"/>
|
<AutoVariable width="fill" target="crit.old-mode" label="Old mode" tooltip="Use the old method for crits"/>
|
||||||
<AutoVariable width="fill" target="crit.experimental.melee" label="Experimental Melee Crits"/>
|
|
||||||
<AutoVariable width="fill" target="crit.force-gameplay" label="Do Not Hinder Gameplay"/>
|
|
||||||
</List>
|
</List>
|
||||||
</Box>
|
</Box>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
@ -121,6 +121,7 @@ public:
|
|||||||
|
|
||||||
offset_t flChargeBeginTime;
|
offset_t flChargeBeginTime;
|
||||||
offset_t flLastFireTime;
|
offset_t flLastFireTime;
|
||||||
|
offset_t flObservedCritChance;
|
||||||
offset_t hThrower;
|
offset_t hThrower;
|
||||||
offset_t hMyWeapons;
|
offset_t hMyWeapons;
|
||||||
|
|
||||||
|
@ -80,6 +80,10 @@ struct offsets
|
|||||||
{
|
{
|
||||||
return PlatformOffset(7, undefined, undefined);
|
return PlatformOffset(7, undefined, undefined);
|
||||||
}
|
}
|
||||||
|
static constexpr uint32_t CreateMoveLate()
|
||||||
|
{
|
||||||
|
return PlatformOffset(21, undefined, undefined);
|
||||||
|
}
|
||||||
static constexpr uint32_t CreateMove()
|
static constexpr uint32_t CreateMove()
|
||||||
{
|
{
|
||||||
return PlatformOffset(22, undefined, undefined);
|
return PlatformOffset(22, undefined, undefined);
|
||||||
|
@ -1,37 +1,7 @@
|
|||||||
/*
|
|
||||||
* crits.h
|
|
||||||
*
|
|
||||||
* Created on: Feb 25, 2017
|
|
||||||
* Author: nullifiedcat
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "settings/Bool.hpp"
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace criticals
|
namespace criticals
|
||||||
{
|
{
|
||||||
|
extern settings::Boolean enabled;
|
||||||
bool random_crits_enabled();
|
extern settings::Boolean melee;
|
||||||
} // namespace criticals
|
} // 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();
|
bool isHackActive();
|
||||||
|
|
||||||
extern CUserCmd *current_user_cmd;
|
extern CUserCmd *current_user_cmd;
|
||||||
|
// Seperate user cmd used in the late createmove
|
||||||
|
extern CUserCmd *current_late_user_cmd;
|
||||||
|
|
||||||
extern GlobalSettings g_Settings;
|
extern GlobalSettings g_Settings;
|
||||||
|
@ -119,6 +119,9 @@ std::unique_ptr<char[]> strfmt(const char *fmt, ...);
|
|||||||
bool HasWeapon(CachedEntity *ent, int wantedId);
|
bool HasWeapon(CachedEntity *ent, int wantedId);
|
||||||
bool IsAmbassador(CachedEntity *ent);
|
bool IsAmbassador(CachedEntity *ent);
|
||||||
bool AmbassadorCanHeadshot();
|
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)
|
// Convert a TF2 handle into an IDX -> ENTITY(IDX)
|
||||||
int HandleToIDX(int handle);
|
int HandleToIDX(int handle);
|
||||||
|
@ -11,6 +11,8 @@ 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*/
|
||||||
|
CreateMoveLate,
|
||||||
/* This kind of CreateMove will run earlier than all CreateMove events
|
/* This kind of CreateMove will run earlier than all CreateMove events
|
||||||
* and guranteed to run before EnginePrediction
|
* and guranteed to run before EnginePrediction
|
||||||
*/
|
*/
|
||||||
|
@ -41,6 +41,7 @@ DECLARE_HOOKED_METHOD(LevelShutdown, void, void *);
|
|||||||
// ClientMode + 4
|
// ClientMode + 4
|
||||||
DECLARE_HOOKED_METHOD(FireGameEvent, void, void *, IGameEvent *);
|
DECLARE_HOOKED_METHOD(FireGameEvent, void, void *, IGameEvent *);
|
||||||
// IBaseClient
|
// 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(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);
|
||||||
|
@ -129,7 +129,7 @@ public:
|
|||||||
float mult2 = *(float *) (unk3);
|
float mult2 = *(float *) (unk3);
|
||||||
|
|
||||||
float multiplier = 0.5f;
|
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);
|
RandomSeed(seed);
|
||||||
|
|
||||||
bool result = true;
|
bool result = true;
|
||||||
|
@ -10,23 +10,168 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <mathlib/vector.h>
|
#include <mathlib/vector.h>
|
||||||
|
#include "checksum_crc.h"
|
||||||
|
|
||||||
class CUserCmd
|
class CUserCmd
|
||||||
{
|
{
|
||||||
public:
|
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;
|
int command_number;
|
||||||
|
|
||||||
|
// the tick the client created this command
|
||||||
int tick_count;
|
int tick_count;
|
||||||
|
|
||||||
|
// Player instantaneous view angles.
|
||||||
Vector viewangles;
|
Vector viewangles;
|
||||||
|
// Intended velocities
|
||||||
|
// forward velocity.
|
||||||
float forwardmove;
|
float forwardmove;
|
||||||
|
// sideways velocity.
|
||||||
float sidemove;
|
float sidemove;
|
||||||
|
// upward velocity.
|
||||||
float upmove;
|
float upmove;
|
||||||
|
// Attack button states
|
||||||
int buttons;
|
int buttons;
|
||||||
uint8_t impulse;
|
// Impulse command issued.
|
||||||
|
byte impulse;
|
||||||
|
char pad1[3];
|
||||||
|
// Current weapon id
|
||||||
int weaponselect;
|
int weaponselect;
|
||||||
int weaponsubtype;
|
int weaponsubtype;
|
||||||
int random_seed;
|
|
||||||
short mousedx;
|
int random_seed; // For shared random functions
|
||||||
short mousedy;
|
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;
|
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
|
"m_AttributeList"); // hmmm
|
||||||
this->flChargeBeginTime = gNetvars.get_offset("DT_WeaponPipebombLauncher", "PipebombLauncherLocalData", "m_flChargeBeginTime");
|
this->flChargeBeginTime = gNetvars.get_offset("DT_WeaponPipebombLauncher", "PipebombLauncherLocalData", "m_flChargeBeginTime");
|
||||||
this->flLastFireTime = gNetvars.get_offset("DT_TFWeaponBase", "LocalActiveTFWeaponData", "m_flLastFireTime");
|
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->bDistributed = gNetvars.get_offset("DT_CurrencyPack", "m_bDistributed");
|
||||||
this->_condition_bits = gNetvars.get_offset("DT_TFPlayer", "m_Shared", "m_ConditionList", "_condition_bits");
|
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");
|
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 "common.hpp"
|
||||||
#include "Backtrack.hpp"
|
#include "crits.hpp"
|
||||||
#include "PlayerTools.hpp"
|
#include "netadr.h"
|
||||||
|
|
||||||
std::unordered_map<int, int> command_number_mod{};
|
std::unordered_map<int, int> command_number_mod{};
|
||||||
|
|
||||||
namespace criticals
|
namespace criticals
|
||||||
{
|
{
|
||||||
|
settings::Boolean enabled{ "crit.enabled", "false" };
|
||||||
static settings::Boolean crit_info{ "crit.info", "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::Button crit_key{ "crit.key", "<null>" };
|
||||||
static settings::Boolean auto_crit{ "crit.auto", "false" };
|
static settings::Boolean old_mode{ "crit.old-mode", "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" };
|
|
||||||
|
|
||||||
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;
|
public:
|
||||||
|
float crit_bucket{};
|
||||||
crithack_saved_state state{};
|
unsigned int weapon_seed{};
|
||||||
state.Save(weapon);
|
unsigned unknown1{};
|
||||||
|
unsigned unknown2{};
|
||||||
seed_backup = *g_PredictionRandomSeed;
|
bool unknown3{};
|
||||||
while (!found && tries < 4096)
|
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);
|
void Load(IClientEntity *weapon)
|
||||||
if (found)
|
{
|
||||||
break;
|
crit_bucket = *(float *) ((uintptr_t) weapon + 0xa38);
|
||||||
++tries;
|
weapon_seed = *(unsigned int *) ((uintptr_t) weapon + 0xb3c);
|
||||||
++number;
|
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;
|
float flToRemove = flMultiply * 3.0;
|
||||||
if (!crit_experimental || g_pLocalPlayer->weapon_mode == weaponmode::weapon_melee)
|
return flToRemove;
|
||||||
state.Load(weapon);
|
|
||||||
if (found)
|
|
||||||
return number;
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 float last_bucket;
|
||||||
static int last_weapon;
|
static int last_weapon;
|
||||||
|
|
||||||
if (current_user_cmd->command_number)
|
|
||||||
changed = false;
|
|
||||||
|
|
||||||
float &bucket = re::C_TFWeaponBase::crit_bucket_(weapon);
|
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_weapon = weapon->entindex();
|
||||||
last_bucket = bucket;
|
last_bucket = bucket;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct cached_calculation_s
|
// Fix bucket on non-local servers
|
||||||
|
void unfuck_bucket(IClientEntity *weapon)
|
||||||
{
|
{
|
||||||
int command_number;
|
INetChannel *ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
|
||||||
int init_command;
|
if (!ch)
|
||||||
int weapon_entity;
|
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;
|
weapon_info info(weapon);
|
||||||
static int lastnumber = 0;
|
float bucket = info.crit_bucket;
|
||||||
static int lastusercmd = 0;
|
float fire_time = NET_FLOAT(weapon, netvar.flLastFireTime);
|
||||||
static const model_t *lastweapon = nullptr;
|
if (weapon->entindex() == last_weapon)
|
||||||
|
|
||||||
bool force_crit(IClientEntity *weapon)
|
|
||||||
{
|
|
||||||
auto command_number = current_user_cmd->command_number;
|
|
||||||
|
|
||||||
if (lastnumber < command_number || lastweapon != weapon->GetModel() || lastnumber - command_number > 1000)
|
|
||||||
{
|
{
|
||||||
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)))
|
if (last_tick == tickcount)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
command_number_mod[command_number] = number;
|
bucket = last_bucket;
|
||||||
auto ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
|
info.crit_bucket = bucket;
|
||||||
*(int *) ((uint64_t) ch + offsets::m_nOutSequenceNr()) = number - 1;
|
info.restore_data(weapon);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (number && number != command_number && number - 30 < command_number)
|
|
||||||
{
|
|
||||||
|
|
||||||
command_number_mod[command_number] = number;
|
// We want to adjust it ourselves
|
||||||
auto ch = (INetChannel *) g_IEngine->GetNetChannelInfo();
|
if (bucket > last_bucket)
|
||||||
*(int *) ((uint64_t) ch + offsets::m_nOutSequenceNr()) = number - 1;
|
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
|
|
||||||
{
|
last_fire_time = fire_time;
|
||||||
if (!crit_legiter)
|
last_tick = tickcount;
|
||||||
{
|
last_weapon = weapon->entindex();
|
||||||
if (command_number != number && number && number != command_number)
|
last_bucket = bucket;
|
||||||
current_user_cmd->buttons &= ~IN_ATTACK;
|
|
||||||
else
|
info.crit_bucket = bucket;
|
||||||
current_user_cmd->buttons |= IN_ATTACK;
|
info.restore_data(weapon);
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_melee_crit(IClientEntity *weapon)
|
void CreateMove()
|
||||||
{
|
{
|
||||||
// Should i crit or save my crits?
|
if (!enabled && !melee && !draw)
|
||||||
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)
|
|
||||||
return;
|
return;
|
||||||
if (!random_crits_enabled())
|
if (CE_BAD(LOCAL_E) || CE_BAD(LOCAL_W))
|
||||||
return;
|
return;
|
||||||
if (CE_BAD(LOCAL_W))
|
|
||||||
|
unfuck_bucket(RAW_ENT(LOCAL_W));
|
||||||
|
// Update cmds
|
||||||
|
update_cmds();
|
||||||
|
if (!enabled && !melee)
|
||||||
return;
|
return;
|
||||||
if (current_user_cmd->command_number)
|
if (!force_ticks && (!(melee && g_pLocalPlayer->weapon_mode == weapon_melee) && crit_key && !crit_key.isKeyDown()))
|
||||||
lastusercmd = current_user_cmd->command_number;
|
return;
|
||||||
|
if (!current_late_user_cmd->command_number)
|
||||||
|
return;
|
||||||
|
|
||||||
IClientEntity *weapon = RAW_ENT(LOCAL_W);
|
IClientEntity *weapon = RAW_ENT(LOCAL_W);
|
||||||
if (!re::C_TFWeaponBase::IsBaseCombatWeapon(weapon))
|
if (!re::C_TFWeaponBase::IsBaseCombatWeapon(weapon))
|
||||||
return;
|
return;
|
||||||
if (!re::C_TFWeaponBase::AreRandomCritsEnabled(weapon))
|
if (!re::C_TFWeaponBase::AreRandomCritsEnabled(weapon))
|
||||||
return;
|
return;
|
||||||
if (!CanShoot())
|
if (!re::C_TFWeaponBase::CanFireCriticalShot(weapon, false, nullptr))
|
||||||
return;
|
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)
|
// Are we even allowed to crit?
|
||||||
handle_melee_crit(weapon);
|
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
|
else
|
||||||
force_crit(weapon);
|
color = colors::red;
|
||||||
}
|
AddCenterString("Crit Penalty: " + std::to_string(crit_mult_info.first), color);
|
||||||
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))
|
|
||||||
{
|
// Time until crit
|
||||||
handle_melee_crit(weapon);
|
if (old_mode && can_crit && last_crit_tick != -1)
|
||||||
}
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
// Backup
|
// Ticks / Ticks per second
|
||||||
int original_cmd = current_user_cmd->command_number;
|
float time = (last_crit_tick - current_late_user_cmd->command_number) * g_GlobalVars->interval_per_tick;
|
||||||
// How many crits in a row?
|
AddCenterString("Crit in " + std::to_string(time) + "s", colors::orange);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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");
|
last_crit_tick = -1;
|
||||||
return tf_weapon_criticals->GetBool();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#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([]() {
|
static InitRoutine init([]() {
|
||||||
EC::Register(EC::CreateMove, criticals::create_move, "cm_crits", EC::very_late);
|
EC::Register(EC::CreateMoveLate, CreateMove, "crit_cm");
|
||||||
#if ENABLE_VISUALS
|
EC::Register(EC::Draw, Draw, "crit_draw");
|
||||||
EC::Register(EC::Draw, criticals::draw, "draw_crits");
|
EC::Register(EC::LevelShutdown, LevelShutdown, "crit_lvlshutdown");
|
||||||
#endif
|
|
||||||
});
|
});
|
||||||
} // namespace criticals
|
} // 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_user_cmd{ nullptr };
|
||||||
|
CUserCmd *current_late_user_cmd{ nullptr };
|
||||||
|
|
||||||
bool isHackActive()
|
bool isHackActive()
|
||||||
{
|
{
|
||||||
|
@ -185,6 +185,7 @@ void hack::Hook()
|
|||||||
|
|
||||||
hooks::client.Set(g_IBaseClient);
|
hooks::client.Set(g_IBaseClient);
|
||||||
hooks::client.HookMethod(HOOK_ARGS(DispatchUserMessage));
|
hooks::client.HookMethod(HOOK_ARGS(DispatchUserMessage));
|
||||||
|
hooks::client.HookMethod(HOOK_ARGS(CreateMoveLate));
|
||||||
#if ENABLE_VISUALS
|
#if ENABLE_VISUALS
|
||||||
hooks::client.HookMethod(HOOK_ARGS(FrameStageNotify));
|
hooks::client.HookMethod(HOOK_ARGS(FrameStageNotify));
|
||||||
hooks::client.HookMethod(HOOK_ARGS(IN_KeyEvent));
|
hooks::client.HookMethod(HOOK_ARGS(IN_KeyEvent));
|
||||||
|
@ -1575,6 +1575,15 @@ bool IsEntityVisiblePenetration(CachedEntity *entity, int hb)
|
|||||||
return false;
|
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
|
// Used for getting class names
|
||||||
CatCommand print_classnames("debug_print_classnames", "Lists classnames currently available in console", []() {
|
CatCommand print_classnames("debug_print_classnames", "Lists classnames currently available in console", []() {
|
||||||
// Create a tmp ent for the loop
|
// 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;
|
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
|
} // namespace hooked_methods
|
||||||
|
@ -10,26 +10,10 @@ namespace hooked_methods
|
|||||||
|
|
||||||
DEFINE_HOOKED_METHOD(GetUserCmd, CUserCmd *, IInput *this_, int sequence_number)
|
DEFINE_HOOKED_METHOD(GetUserCmd, CUserCmd *, IInput *this_, int sequence_number)
|
||||||
{
|
{
|
||||||
CUserCmd *def = original::GetUserCmd(this_, sequence_number);
|
// We need to overwrite this if crithack is on
|
||||||
int oldcmd;
|
if (criticals::enabled || criticals::melee)
|
||||||
INetChannel *ch;
|
return &GetCmds(this_)[sequence_number % VERIFIED_CMD_SIZE];
|
||||||
|
else
|
||||||
if (def == nullptr)
|
return original::GetUserCmd(this_, sequence_number);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
} // namespace hooked_methods
|
} // namespace hooked_methods
|
||||||
|
Reference in New Issue
Block a user