void CGrenadePrediction::Setup(Vector& vecSrc, Vector& vecThrow, Vector viewangles)
{
if (!vars.visuals.nadepred)
return;
Vector angThrow = viewangles;
float pitch = angThrow.x;
if (pitch <= 90.0f)
{
if (pitch < -90.0f)
{
pitch += 360.0f;
}
}
else
{
pitch -= 360.0f;
}
float a = pitch - (90.0f - fabs(pitch)) * 10.0f / 90.0f;
angThrow.x = a;
// Gets ThrowVelocity from weapon files
// Clamped to [15,750]
float flVel = 750.0f * 0.9f;
// Do magic on member of grenade object [esi+9E4h]
// m1=1 m1+m2=0.5 m2=0
static const float power[] = { 1.0f, 1.0f, 0.5f, 0.0f };
float b = power[act];
// Clamped to [0,1]
b = b * 0.7f;
b = b + 0.3f;
flVel *= b;
Vector vForward, vRight, vUp;
angle_vectors2(angThrow, &vForward, &vRight, &vUp); //angThrow.ToVector(vForward, vRight, vUp);
vecSrc = csgo->unpred_eyepos;
float off = (power[act] * 12.0f) - 12.0f;
vecSrc.z += off;
// Game calls UTIL_TraceHull here with hull and assigns vecSrc tr.endpos
trace_t tr;
Vector vecDest = vecSrc;
vecDest += vForward * 22.0f; //vecDest.MultAdd(vForward, 22.0f);
TraceHull(vecSrc, vecDest, tr);
// After the hull trace it moves 6 units back along vForward
// vecSrc = tr.endpos - vForward * 6
Vector vecBack = vForward; vecBack *= 6.0f;
vecSrc = tr.endpos;
vecSrc -= vecBack;
// Finally calculate velocity
vecThrow = csgo->local->GetVelocity(); vecThrow *= 1.25f;
vecThrow += vForward * flVel; // vecThrow.MultAdd(vForward, flVel);
}
void CGrenadePrediction::Simulate(CViewSetup* setup)
{
if (!vars.visuals.nadepred)
return;
Vector vecSrc, vecThrow;
Vector angles; interfaces.engine->GetViewAngles(angles);
Setup(vecSrc, vecThrow, angles);
float interval = interfaces.global_vars->interval_per_tick;
// Log positions 20 times per sec
int logstep = static_cast<int>(0.05f / interval);
int logtimer = 0;
path.clear();
for (unsigned int i = 0; i < path.max_size() - 1; ++i)
{
if (!logtimer)
path.push_back(vecSrc);
int s = Step(vecSrc, vecThrow, i, interval);
if ((s & 1)) break;
// Reset the log timer every logstep OR we bounced
if ((s & 2) || logtimer >= logstep) logtimer = 0;
else ++logtimer;
}
path.push_back(vecSrc);
}
int CGrenadePrediction::Step(Vector& vecSrc, Vector& vecThrow, int tick, float interval)
{
// Apply gravity
Vector move;
AddGravityMove(move, vecThrow, interval, false);
// Push entity
trace_t tr;
PushEntity(vecSrc, move, tr);
int result = 0;
// Check ending conditions
if (CheckDetonate(vecThrow, tr, tick, interval))
{
result |= 1;
}
// Resolve collisions
if (tr.fraction != 1.0f)
{
result |= 2; // Collision!
ResolveFlyCollisionCustom(tr, vecThrow, interval);
}
// Set new position
vecSrc = tr.endpos;
return result;
}
bool CGrenadePrediction::CheckDetonate(const Vector& vecThrow, const trace_t& tr, int tick, float interval)
{
switch (type)
{
case WEAPON_SMOKEGRENADE:
case WEAPON_DECOY:
// Velocity must be <0.1, this is only checked every 0.2s
if (vecThrow.Length2D() < 0.1f)
{
int det_tick_mod = static_cast<int>(0.2f / interval);
return !(tick % det_tick_mod);
}
return false;
case WEAPON_MOLOTOV:
case WEAPON_INC:
// Detonate when hitting the floor
if (tr.fraction != 1.0f && tr.plane.normal.z > 0.7f)
return true;
// OR we've been flying for too long
case WEAPON_FLASHBANG:
case WEAPON_HEGRENADE:
// Pure timer based, detonate at 1.5s, checked every 0.2s
return static_cast<float>(tick) * interval > 1.5f && !(tick % static_cast<int>(0.2f / interval));
default:
assert(false);
return false;
}
}
void CGrenadePrediction::TraceHull(Vector& src, Vector& end, trace_t& tr)
{
if (!vars.visuals.nadepred)
return;
Ray_t ray;
ray.Init(src, end, Vector(-2.0f, -2.0f, -2.0f), Vector(2.0f, 2.0f, 2.0f));
CTraceFilterWorldAndPropsOnly filter;
//filter.SetIgnoreClass("BaseCSGrenadeProjectile");
//filter.bShouldHitPlayers = false;
interfaces.trace->TraceRay(ray, 0x200400B, &filter, &tr);
}
void CGrenadePrediction::AddGravityMove(Vector& move, Vector& vel, float frametime, bool onground)
{
if (!vars.visuals.nadepred)
return;
Vector basevel(0.0f, 0.0f, 0.0f);
move.x = (vel.x + basevel.x) * frametime;
move.y = (vel.y + basevel.y) * frametime;
if (onground)
{
move.z = (vel.z + basevel.z) * frametime;
}
else
{
// Game calls GetActualGravity( this );
float gravity = 800.0f * 0.4f;
float newZ = vel.z - (gravity * frametime);
move.z = ((vel.z + newZ) / 2.0f + basevel.z) * frametime;
vel.z = newZ;
}
}
void CGrenadePrediction::PushEntity(Vector& src, const Vector& move, trace_t& tr)
{
if (!vars.visuals.nadepred)
return;
Vector vecAbsEnd = src;
vecAbsEnd += move;
// Trace through world
TraceHull(src, vecAbsEnd, tr);
}
void CGrenadePrediction::ResolveFlyCollisionCustom(trace_t& tr, Vector& vecVelocity, float interval)
{
if (!vars.visuals.nadepred)
return;
// Calculate elasticity
float flSurfaceElasticity = 1.0; // Assume all surfaces have the same elasticity
float flGrenadeElasticity = 0.45f; // GetGrenadeElasticity()
float flTotalElasticity = flGrenadeElasticity * flSurfaceElasticity;
if (flTotalElasticity > 0.9f) flTotalElasticity = 0.9f;
if (flTotalElasticity < 0.0f) flTotalElasticity = 0.0f;
// Calculate bounce
Vector vecAbsVelocity;
PhysicsClipVelocity(vecVelocity, tr.plane.normal, vecAbsVelocity, 2.0f);
vecAbsVelocity *= flTotalElasticity;
// Stop completely once we move too slow
float flSpeedSqr = vecAbsVelocity.LengthSqr();
static const float flMinSpeedSqr = 20.0f * 20.0f; // 30.0f * 30.0f in CSS
if (flSpeedSqr < flMinSpeedSqr)
{
//vecAbsVelocity.Zero();
vecAbsVelocity.x = 0.0f;
vecAbsVelocity.y = 0.0f;
vecAbsVelocity.z = 0.0f;
}
// Stop if on ground
if (tr.plane.normal.z > 0.7f)
{
vecVelocity = vecAbsVelocity;
vecAbsVelocity *= ((1.0f - tr.fraction) * interval); //vecAbsVelocity.Mult((1.0f - tr.fraction) * interval);
PushEntity(tr.endpos, vecAbsVelocity, tr);
}
else
{
vecVelocity = vecAbsVelocity;
}
}
int CGrenadePrediction::PhysicsClipVelocity(const Vector& in, const Vector& normal, Vector& out, float overbounce)
{
static const float STOP_EPSILON = 0.1f;
float backoff;
float change;
float angle;
int i, blocked;
blocked = 0;
angle = normal[2];
if (angle > 0)
{
blocked |= 1; // floor
}
if (!angle)
{
blocked |= 2; // step
}
backoff = in.Dot(normal) * overbounce;
for (i = 0; i < 3; i++)
{
change = normal[i] * backoff;
out[i] = in[i] - change;
if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
{
out[i] = 0;
}
}
return blocked;
}
void CGrenadePrediction::Tick(int buttons)
{
if (!vars.visuals.nadepred)
return;
bool in_attack = buttons & IN_ATTACK;
bool in_attack2 = buttons & IN_ATTACK2;
//g_pICvar->Consolecolor_tPrintf(SDK::color_t(255, 255, 255, 255), "OView");
act = (in_attack && in_attack2) ? ACT_LOB :
(in_attack2) ? ACT_DROP :
(in_attack) ? ACT_THROW :
ACT_NONE;
}
void CGrenadePrediction::View(CViewSetup* setup)
{
if (!vars.visuals.nadepred)
return;
if (csgo->local && csgo->local->isAlive())
{
IBaseCombatWeapon* weapon = csgo->local->GetWeapon();
if (weapon && weapon->IsNade() && act != ACT_NONE)
{
type = weapon->GetItemDefinitionIndex();
Simulate(setup);
}
else
{
type = 0;
}
}
}
void CGrenadePrediction::Paint()
{
if (!vars.visuals.nadepred)
return;
if ((type) && !(path.empty()))
{
Vector nadeStart, nadeEnd;
Vector nadeStart1, nadeEnd1;
Vector prev = path[0];
for (auto it = path.begin(), end = path.end(); it != end; ++it)
{
if (Math::WorldToScreen2(prev, nadeStart) && Math::WorldToScreen2(*it, nadeEnd))
{
Drawing::DrawLine((int)nadeStart.x, (int)nadeStart.y, (int)nadeEnd.x, (int)nadeEnd.y, vars.visuals.nadepred_color);
}
prev = *it;
}
Drawing::DrawFilledCircle(nadeEnd.x, nadeEnd.y, 5, 100, color_t(0, 0, 0, 200));
Drawing::DrawFilledCircle(nadeEnd.x, nadeEnd.y, 4, 100, color_t(255, 255, 255, 120));
}
}