This repository has been archived on 2024-06-01. You can view files and clone it, but cannot push or open issues or pull requests.
cathook/src/prediction.cpp
nullifiedcat 579b4b6f58 stuff
2018-07-29 14:53:08 +03:00

458 lines
15 KiB
C++

/*
* prediction.cpp
*
* Created on: Dec 5, 2016
* Author: nullifiedcat
*/
#include <glez/draw.hpp>
#include <settings/Bool.hpp>
#include "common.hpp"
static settings::Bool debug_enginepred{ "debug.engine-pred-others", "false" };
static settings::Bool debug_pp_extrapolate{ "debug.pp-extrapolate", "false" };
static settings::Bool debug_pp_rockettimeping{ "debug.pp-rocket-time-ping", "false" };
// TODO there is a Vector() object created each call.
Vector SimpleLatencyPrediction(CachedEntity *ent, int hb)
{
if (!ent)
return Vector();
Vector result;
GetHitbox(ent, hb, result);
float latency = g_IEngine->GetNetChannelInfo()->GetLatency(FLOW_OUTGOING) +
g_IEngine->GetNetChannelInfo()->GetLatency(FLOW_INCOMING);
result += CE_VECTOR(ent, netvar.vVelocity) * latency;
return result;
}
float PlayerGravityMod(CachedEntity *player)
{
// int movetype = CE_INT(player, netvar.movetype);
// if (movetype == MOVETYPE_FLY || movetype == MOVETYPE_NOCLIP) return
// 0.0f;
if (HasCondition<TFCond_Parachute>(player))
return 0.448f;
return 1.0f;
}
bool PerformProjectilePrediction(CachedEntity *target, int hitbox)
{
Vector src, vel, hit;
;
// src = vfunc<Vector(*)(IClientEntity*)>(RAW_ENT(target),
// 299)(RAW_ENT(target));
return true;
}
std::vector<std::vector<Vector>> predicted_players{};
int predicted_player_count = 0;
void Prediction_CreateMove()
{
static bool setup = false;
if (!setup)
{
setup = true;
predicted_players.resize(32);
}
if (!debug_enginepred)
return;
for (int i = 1; i < g_GlobalVars->maxClients; i++)
{
CachedEntity *ent = ENTITY(i);
if (CE_GOOD(ent))
{
Vector o = ent->m_vecOrigin();
predicted_players[i].clear();
for (int j = 0; j < 20; j++)
{
Vector r = EnginePrediction(ent, 0.05f);
ent->m_vecOrigin() = r;
predicted_players[i].push_back(std::move(r));
}
ent->m_vecOrigin() = o;
CE_VECTOR(ent, 0x354) = o;
// logging::Info("Predicted %d to be at [%.2f, %.2f, %.2f] vs [%.2f,
// %.2f, %.2f]", i, r.x,r.y,r.z, o.x, o.y, o.z);
predicted_player_count = i;
}
}
}
#if ENABLE_VISUALS
void Prediction_PaintTraverse()
{
if (!debug_enginepred)
return;
for (int i = 1; i < predicted_player_count; i++)
{
CachedEntity *ent = ENTITY(i);
if (CE_GOOD(ent))
{
Vector previous_screen;
if (!draw::WorldToScreen(ent->m_vecOrigin(), previous_screen))
continue;
rgba_t color = colors::FromRGBA8(255, 0, 0, 255);
for (int j = 0; j < predicted_players[i].size(); j++)
{
Vector screen;
if (draw::WorldToScreen(predicted_players[i][j], screen))
{
glez::draw::line(screen.x, screen.y,
previous_screen.x - screen.x,
previous_screen.y - screen.y, color, 0.5f);
previous_screen = screen;
}
else
{
break;
}
color.r -= 1.0f / 20.0f;
}
}
}
}
#endif
Vector EnginePrediction(CachedEntity *entity, float time)
{
Vector result = entity->m_vecOrigin();
IClientEntity *ent = RAW_ENT(entity);
typedef void (*SetupMoveFn)(IPrediction *, IClientEntity *, CUserCmd *,
class IMoveHelper *, CMoveData *);
typedef void (*FinishMoveFn)(IPrediction *, IClientEntity *, CUserCmd *,
CMoveData *);
void **predictionVtable = *((void ***) g_IPrediction);
SetupMoveFn oSetupMove =
(SetupMoveFn)(*(unsigned *) (predictionVtable + 19));
FinishMoveFn oFinishMove =
(FinishMoveFn)(*(unsigned *) (predictionVtable + 20));
// CMoveData *pMoveData = (CMoveData*)(sharedobj::client->lmap->l_addr +
// 0x1F69C0C); CMoveData movedata {};
char *object = new char[165];
CMoveData *pMoveData = (CMoveData *) object;
float frameTime = g_GlobalVars->frametime;
float curTime = g_GlobalVars->curtime;
CUserCmd fakecmd{};
memset(&fakecmd, 0, sizeof(CUserCmd));
Vector vel;
if (velocity::EstimateAbsVelocity)
velocity::EstimateAbsVelocity(RAW_ENT(entity), vel);
else
vel = CE_VECTOR(entity, netvar.vVelocity);
fakecmd.command_number = last_cmd_number;
fakecmd.forwardmove = vel.x;
fakecmd.sidemove = -vel.y;
Vector oldangles = CE_VECTOR(entity, netvar.m_angEyeAngles);
static Vector zerov{ 0, 0, 0 };
CE_VECTOR(entity, netvar.m_angEyeAngles) = zerov;
CUserCmd *original_cmd = NET_VAR(ent, 4188, CUserCmd *);
NET_VAR(ent, 4188, CUserCmd *) = &fakecmd;
g_GlobalVars->curtime =
g_GlobalVars->interval_per_tick * NET_INT(ent, netvar.nTickBase);
g_GlobalVars->frametime = time;
Vector old_origin = entity->m_vecOrigin();
NET_VECTOR(ent, 0x354) = entity->m_vecOrigin();
//*g_PredictionRandomSeed = MD5_PseudoRandom(current_user_cmd->command_number) &
// 0x7FFFFFFF;
g_IGameMovement->StartTrackPredictionErrors(
reinterpret_cast<CBasePlayer *>(ent));
oSetupMove(g_IPrediction, ent, &fakecmd, NULL, pMoveData);
g_IGameMovement->ProcessMovement(reinterpret_cast<CBasePlayer *>(ent),
pMoveData);
oFinishMove(g_IPrediction, ent, &fakecmd, pMoveData);
g_IGameMovement->FinishTrackPredictionErrors(
reinterpret_cast<CBasePlayer *>(ent));
delete[] object;
NET_VAR(entity, 4188, CUserCmd *) = original_cmd;
g_GlobalVars->frametime = frameTime;
g_GlobalVars->curtime = curTime;
result = ent->GetAbsOrigin();
NET_VECTOR(ent, 0x354) = old_origin;
CE_VECTOR(entity, netvar.m_angEyeAngles) = oldangles;
return result;
}
Vector ProjectilePrediction_Engine(CachedEntity *ent, int hb, float speed,
float gravitymod,
float entgmod /* ignored */)
{
Vector origin = ent->m_vecOrigin();
Vector hitbox;
GetHitbox(ent, hb, hitbox);
Vector hitbox_offset = hitbox - origin;
if (speed == 0.0f)
return Vector();
Vector velocity;
if (velocity::EstimateAbsVelocity)
velocity::EstimateAbsVelocity(RAW_ENT(ent), velocity);
else
velocity = CE_VECTOR(ent, netvar.vVelocity);
// TODO ProjAim
float medianTime = g_pLocalPlayer->v_Eye.DistTo(hitbox) / speed;
float range = 1.5f;
float currenttime = medianTime - range;
if (currenttime <= 0.0f)
currenttime = 0.01f;
float besttime = currenttime;
float mindelta = 65536.0f;
Vector bestpos = origin;
Vector current = origin;
int maxsteps = 40;
bool onground = false;
if (ent->m_Type() == ENTITY_PLAYER)
{
if (CE_INT(ent, netvar.iFlags) & FL_ONGROUND)
onground = true;
}
float steplength = ((float) (2 * range) / (float) maxsteps);
for (int steps = 0; steps < maxsteps; steps++, currenttime += steplength)
{
ent->m_vecOrigin() = current;
current = EnginePrediction(ent, steplength);
if (onground)
{
float toground = DistanceToGround(current);
current.z -= toground;
}
float rockettime = g_pLocalPlayer->v_Eye.DistTo(current) / speed;
if (fabs(rockettime - currenttime) < mindelta)
{
besttime = currenttime;
bestpos = current;
mindelta = fabs(rockettime - currenttime);
}
}
ent->m_vecOrigin() = origin;
CE_VECTOR(ent, 0x354) = origin;
bestpos.z += (400 * besttime * besttime * gravitymod);
// S = at^2/2 ; t = sqrt(2S/a)*/
Vector result = bestpos + hitbox_offset;
/*logging::Info("[Pred][%d] delta: %.2f %.2f %.2f", result.x - origin.x,
result.y - origin.y, result.z - origin.z);*/
return result;
}
Vector ProjectilePrediction(CachedEntity *ent, int hb, float speed,
float gravitymod, float entgmod)
{
if (!ent)
return Vector();
Vector result;
// if (not debug_pp_extrapolate) {
GetHitbox(ent, hb, result);
//} else {
// result = SimpleLatencyPrediction(ent, hb);
//
//}
float latency = g_IEngine->GetNetChannelInfo()->GetLatency(FLOW_OUTGOING) +
g_IEngine->GetNetChannelInfo()->GetLatency(FLOW_INCOMING);
if (speed == 0.0f)
return Vector();
trace::filter_no_player.SetSelf(RAW_ENT(ent));
float dtg = DistanceToGround(ent);
Vector vel;
if (velocity::EstimateAbsVelocity)
velocity::EstimateAbsVelocity(RAW_ENT(ent), vel);
else
vel = CE_VECTOR(ent, netvar.vVelocity);
// TODO ProjAim
float medianTime = g_pLocalPlayer->v_Eye.DistTo(result) / speed;
float range = 1.5f;
float currenttime = medianTime - range;
if (currenttime <= 0.0f)
currenttime = 0.01f;
float besttime = currenttime;
float mindelta = 65536.0f;
Vector bestpos = result;
int maxsteps = 300;
for (int steps = 0; steps < maxsteps;
steps++, currenttime += ((float) (2 * range) / (float) maxsteps))
{
Vector curpos = result;
curpos += vel * currenttime;
if (debug_pp_extrapolate)
{
curpos += vel * currenttime * latency;
}
if (dtg > 0.0f)
{
curpos.z -= currenttime * currenttime * 400.0f * entgmod;
if (curpos.z < result.z - dtg)
curpos.z = result.z - dtg;
}
float rockettime = g_pLocalPlayer->v_Eye.DistTo(curpos) / speed;
if (debug_pp_rockettimeping)
rockettime +=
g_IEngine->GetNetChannelInfo()->GetLatency(FLOW_OUTGOING);
if (fabs(rockettime - currenttime) < mindelta)
{
besttime = currenttime;
bestpos = curpos;
mindelta = fabs(rockettime - currenttime);
}
}
if (debug_pp_rockettimeping)
besttime += g_IEngine->GetNetChannelInfo()->GetLatency(FLOW_OUTGOING);
bestpos.z += (400 * besttime * besttime * gravitymod);
// S = at^2/2 ; t = sqrt(2S/a)*/
return bestpos;
}
float DistanceToGround(CachedEntity *ent)
{
if (ent->m_Type() == ENTITY_PLAYER)
{
if (CE_INT(ent, netvar.iFlags) & FL_ONGROUND)
return 0;
}
Vector origin = ent->m_vecOrigin();
float v1 = DistanceToGround(origin + Vector(10.0f, 10.0f, 0.0f));
float v2 = DistanceToGround(origin + Vector(-10.0f, 10.0f, 0.0f));
float v3 = DistanceToGround(origin + Vector(10.0f, -10.0f, 0.0f));
float v4 = DistanceToGround(origin + Vector(-10.0f, -10.0f, 0.0f));
return MIN(v1, MIN(v2, MIN(v3, v4)));
}
float DistanceToGround(Vector origin)
{
trace_t ground_trace;
Ray_t ray;
Vector endpos = origin;
endpos.z -= 8192;
ray.Init(origin, endpos);
g_ITrace->TraceRay(ray, MASK_PLAYERSOLID, &trace::filter_no_player,
&ground_trace);
return 8192.0f * ground_trace.fraction;
}
/*
// Set of to be fuctions for preciting players, similear to ncc prediction.
// The way air prediction works is that we use getabsvel to get a baseline
position of where the player could be
// next tick. Then we save that into the array for our math next tick.
// After the first tick passed, we check to see how the GetAbsVel function
actually performed and we can correct for its
// mistakes by comparing the result from GetAbsVel last tick and where the
player currently is this tick and applying an
// offset for predictions.
// With the new offset, you can use GetAbsVel and apply the offset to get 1 tick
but for every other time you would need
// to apply the offset due to the way airstrafing works.
// GetAbsVel only works in a strait fassion of what the players current velocity
and doesnt factor in what the players
// next velocity could be due to the player having the ability to airstrafe and
change that by quite a bit.
// Ground prediction works in the way of it using GetAbsVel to get a baseline
direction of where the player is going and
// attempting to predict the players movement from that. The prediction should
check its surroundings for corners, walls,
// and the like to determine a path of where the player could potentially go. We
would also want to check if players
// collision box would intercept a wall or floor and interpolate even more with
that in mind.
// If a player is moving too steeply onto a wall, the prediction should stop
there and count that as a place for where the
// player would be for any time after it.
// we can change between the two prediction types based on the ground flag
netvar.
// For using the predictions to work as projectile aimbot, we can determine the
distance from the player kind of like how
// the current projectile aimbot currently does but we will follow the predicted
path instead of just placing a vector in
// a really simple fassion.
// This is based on the estimation that GetAbsVelocity predicts players next
position for the next createmove tick
// A set of vectors for every potential player
static Vector last_predicted_vector[32];
// An array to store predictions
static Vector last_predictions[32];
// Vectors to determine whether the player was in the air last tick
static bool last_predicted_inair[32];
// Should be run every createmove to predict playermovement
void RunPredictPlayers() {
// Create a cached ent for use in the for loop
CachedEntity* ent;
// Loop through players
for (int i = 0; i < 32; i++) {
// Get an ent from current loop and check for dormancy/null
ent = ENTITY(i);
if (CE_BAD(ent)) continue;
// Grab netvar for ground to control type of prediction
int flags = CE_INT(g_pLocalPlayer->entity, netvar.iFlags);
bool ground = (flags & (1 << 0));
// For ground prediction, we would just use the old method for
now if (ground) {
// Set our last "in air" state to false
last_predicted_vector_inair[i] = false;
// For air prediction, attempts to exerpolate strafing speed
} else {
// If we were not in the air last tick, we need to
create our first prediction if (!last_predicted_inair[i]) {
// Set "in air" to true to allow air prediction
to work next tick last_predicted_inair[i] = true;
// Get our abs velocity and set it into the
array velocity::EstimateAbsVelocity(RAW_ENT(ent), last_predicted_vector[i]);
// Since we have been in the air last tick, we can
create an offset off of prediction errors } else {
// Create a storage vector and get abs velocity
of Vector current_prediction; velocity::EstimateAbsVelocity(RAW_ENT(ent),
current_prediction); last_predictions[32];
}
}
}
}
// Draws our predicted player pathing for debug or visual use
void DrawPredictPlayers() {
// TODO
}
*/