- Статус
- Оффлайн
- Регистрация
- 13 Фев 2026
- Сообщения
- 706
- Реакции
- 18
Надоело смотреть на стагнацию в разделах по олдскульным шутерам, так что выкатываю немного технического мяса, которое валялось в закромах. Разберем фикс «резинок» (rubberbanding) при использовании спидхака в CS 1.6.
Суть проблемы проста: когда на сервере срабатывает SV_CheckCmdTimes и ваши команды начинают игнорироваться, движок перестает симулировать игрока на стороне сервера. При этом клиент по инерции продолжает предсказывать движение, что и вызывает дикие откаты назад.
Для начала нам нужно определить состояние «затыка». Это делается в CL_Move перед вызовом оригинала:
Почему 3 байта?
В GoldSrc это размер дескрипторов дельты (фактически только заголовок). Если размер пакета равен 3 байтам, значит никаких изменений в состоянии игрока не произошло — сервер нас не симулирует.
Теперь сам фикс. Мы будем хукать CL_PredictMove, так как именно эта функция в GoldSrc отвечает за фактическую симуляцию игрока на клиенте.
Технические нюансы логики:
Метод старый как мир, но в большинстве паблик сурсов до сих пор реализован криво или не реализован вовсе. Юзайте, если пилите свой рейдж-мод для классики.
Интересно будет глянуть, у кого какие оффсеты на старых non-steam билдах, закидывайте в тред если есть наработки.
Суть проблемы проста: когда на сервере срабатывает SV_CheckCmdTimes и ваши команды начинают игнорироваться, движок перестает симулировать игрока на стороне сервера. При этом клиент по инерции продолжает предсказывать движение, что и вызывает дикие откаты назад.
Для начала нам нужно определить состояние «затыка». Это делается в CL_Move перед вызовом оригинала:
Код:
game_i.is_stuck = local && c_game::is_alive(local) && game_i.client_state->frames[game_i.client_state->parsecountmod].clientbytes == 3;
Почему 3 байта?
В GoldSrc это размер дескрипторов дельты (фактически только заголовок). Если размер пакета равен 3 байтам, значит никаких изменений в состоянии игрока не произошло — сервер нас не симулирует.
Теперь сам фикс. Мы будем хукать CL_PredictMove, так как именно эта функция в GoldSrc отвечает за фактическую симуляцию игрока на клиенте.
Код:
// Оффсет под актуальный Steam билд hw.dll
make_hook(void, __cdecl, get_module_base("hw.dll") + 0x1AB760, CL_PredictMove, bool predicting)
{
// Если мы в стаке — скипаем симуляцию
if (game_i.is_stuck)
{
return;
}
original.call(predicting);
}
Технические нюансы логики:
- Пайплайн вызовов выглядит так: CL_Move -> CL_PredictMove -> CL_ReadPackets.
- Мы определяем флаг is_stuck на основе данных последнего вызова CL_ReadPackets. Если сервер прислал пустую дельту, мы блокируем текущую симуляцию.
- Важно стопать именно CL_PredictMove, а не CL_RunUsercmd. Если заблокировать RunUsercmd, движок все равно будет копировать фреймы в список состояний клиента, что породит кучу неприятных визуальных багов.
- В итоге клиент всегда остается на 1 фрейм позади сервера, что полностью убирает эффект rubberbanding при агрессивном спидхаке. Привет из Source Engine, там похожая архитектурная особенность.
Метод старый как мир, но в большинстве паблик сурсов до сих пор реализован криво или не реализован вовсе. Юзайте, если пилите свой рейдж-мод для классики.
Интересно будет глянуть, у кого какие оффсеты на старых non-steam билдах, закидывайте в тред если есть наработки.