void c_movement::simulate_movement_for_jumpbug(c_user_cmd* cmd)
{
auto pawn = g_ctx->m_local_pawn;
if (!pawn)
return;
auto movement_services = pawn->m_movement_services();
if (!movement_services)
return;
c_user_cmd temp_cmd = *cmd;
CMoveData move_data{};
movement_services->Setup(&temp_cmd, &move_data);
pawn->PhysicsRunThink(0);
movement_services->ProcessMovement(&temp_cmd);
movement_services->Finish(&temp_cmd, &move_data);
}
void c_movement::jump_bug()
{
if (!(g_ctx->m_user_cmd->m_button_state.m_button_state & IN_JUMP))
return;
if (!g_configs->misc.m_jumpbug)
return;
if (!g_interfaces->m_var->get_by_name("sv_autobunnyhopping")->get_bool())
return;
c_user_cmd* cmd = g_ctx->m_user_cmd;
auto* base_cmd = cmd->pb.mutable_base();
auto* pawn = g_ctx->m_local_pawn;
if (!pawn)
return;
auto* movement = pawn->m_movement_services();
if (!movement)
return;
const int original_flags = pawn->m_flags();
const vec3_t original_origin = pawn->m_scene_node()->m_abs_origin();
const vec3_t original_velocity = pawn->m_velocity();
simulate_movement_for_jumpbug(cmd);
const int predicted_flags = pawn->m_flags();
pawn->m_flags() = original_flags;
pawn->m_scene_node()->m_abs_origin() = original_origin;
pawn->m_velocity() = original_velocity;
const bool was_on_ground = (original_flags & FL_ONGROUND);
const bool will_be_on_ground = (predicted_flags & FL_ONGROUND);
bool edge_detected = false;
if (was_on_ground && !will_be_on_ground)
{
auto* collision = pawn->m_collision();
if (collision)
{
const vec3_t mins = collision->m_mins();
const vec3_t maxs = collision->m_maxs();
trace_filter_t filter;
g_interfaces->m_trace->init_player_movement_trace_filter(&filter, pawn, 0x1C3003, COLLISION_GROUP_PLAYER_MOVEMENT);
const float speed = original_velocity.length_2d();
if (speed > 50.0f)
{
vec3_t forward_pos = original_origin + original_velocity.normalized() * 24.0f;
forward_pos.z -= 32.0f;
game_trace_t forward_trace;
bbox_t bounds{ mins, maxs };
if (g_interfaces->m_trace->trace_player_bbox(&original_origin, &forward_pos, &bounds, &filter, &forward_trace))
{
edge_detected = forward_trace.m_fraction >= 0.95f;
}
}
if (!edge_detected)
{
vec3_t down_pos = original_origin - vec3_t(0, 0, 32.0f);
game_trace_t down_trace;
bbox_t bounds{ mins, maxs };
if (g_interfaces->m_trace->trace_player_bbox(&original_origin, &down_pos, &bounds, &filter, &down_trace))
{
edge_detected = down_trace.m_fraction >= 0.9f;
}
}
}
}
if ((was_on_ground && !will_be_on_ground) || edge_detected)
{
cmd->m_button_state.set_button_state(IN_DUCK, IN_BUTTON_DOWN);
base_cmd->clear_subtick_moves();
const float interval = g_interfaces->m_globals->m_interval_per_tick;
const float tick_progress = fmodf(g_interfaces->m_globals->m_curtime, interval) / interval;
float duck_timing = 0.0f;
float jump_timing = 0.2f - (tick_progress * 0.1f);
float unduck_timing = 0.85f + (tick_progress * 0.05f);
float unjump_timing = unduck_timing + 0.03f;
const float speed = original_velocity.length_2d();
if (speed > 320.0f)
{
jump_timing *= 0.95f;
unduck_timing *= 1.02f;
}
else if (speed < 200.0f)
{
jump_timing *= 1.05f;
unduck_timing *= 0.98f;
}
auto add_subtick = [&](float time, int button, bool pressed)
{
if (auto move = g_interfaces->m_csgo_input->create_new_subtick_move_step(base_cmd->mutable_subtick_moves()))
{
move->set_when(time);
move->set_button(button);
move->set_pressed(pressed);
base_cmd->mutable_subtick_moves()->AddAllocated(move);
}
};
add_subtick(duck_timing, IN_DUCK, true);
add_subtick(jump_timing, IN_JUMP, true);
add_subtick(unduck_timing, IN_DUCK, false);
add_subtick(unjump_timing, IN_JUMP, false);
}
else if (!(original_flags & FL_ONGROUND))
{
cmd->m_button_state.set_button_state(IN_DUCK, IN_BUTTON_UP);
}
}