-
Автор темы
- #1
Вступление
Привет всем! Сегодня я хочу поделиться с вами информацией о том, почему снова появился no-spread и как он работает. Это действительно то, что изменит правила игры для всех
Изменения которые привели к его появлению
В ночь 9 ноября успешная компания Valve выпустила новое обновление для Counter-Strike 2. В этом обновлении они решили исправить проблемы с системой sub-tick. Одно из решений, к которому они пришли, было синхронизировать клиентское и серверный сид случайностей.
В этой части я применю реверс-инжиниринг Soufiw в знак уважения к исходному источнику.
После этого обновления, сид синхронизируется между клиентом и сервером с помощью хэширования углов обзора и текущего тика отрисовки.
В итоге, создание no-spread кажется действительно простым: всего лишь нужно получить текущий сид и компенсировать его, как в 2015 году! Но, к сожалению, это не работает так. При изменении угла обзора, сид также меняется, что логично. Чтобы обойти это ограничение, я пришел к более простому и оригинальному решению.
Использование хаоса как основу для порядка
Если вы сейчас стоите, то вам лучше присесть, потому что это самое простое решение, которое можно придумать.
Предположим, у нас есть угол обзора и текущий тик отрисовки. Для компенсации разброса мы можем перебирать случайные углы, вычислять для них сид и вычислять финальный угол с учетом разброса.
В итоге, наш принцип действия будет выглядеть так:
Таким образом, мы перебираем случайные варианты, пока не найдем сид, которое компенсирует разброс для текущего угла.
Давайте сразу перейдем к коду, который отвечает за моделирование разброса. Я не буду углубляться в подробные объяснения, чтобы быстрее показать реализацию.
Итог
Сложив всю информацию, вы могли прийти к тому же результату, что и я. Возврат компенсации отдачи повлечёт серьёзные изменения в культуре HvH и читеров в целом. Сейчас я хочу продемонстрировать, что это действительно работает.
Всем пока, еще встретимся!
Credits:
SapDragon - Theoretical research
Porches - Practical implementation
Привет всем! Сегодня я хочу поделиться с вами информацией о том, почему снова появился no-spread и как он работает. Это действительно то, что изменит правила игры для всех
Изменения которые привели к его появлению
В ночь 9 ноября успешная компания Valve выпустила новое обновление для Counter-Strike 2. В этом обновлении они решили исправить проблемы с системой sub-tick. Одно из решений, к которому они пришли, было синхронизировать клиентское и серверный сид случайностей.
Изменения на клиентеКак мы знаем, предыдущий no-spread появился из-за того, что сервер доверял клиентскому сиду. Решение этой проблемы было в генерации сида на сервере, что привело к десинхронизации до 9 ноября (в редких случаях она все еще существует)
В этой части я применю реверс-инжиниринг Soufiw в знак уважения к исходному источнику.
После этого обновления, сид синхронизируется между клиентом и сервером с помощью хэширования углов обзора и текущего тика отрисовки.
C++:
uint32_t GetRandomSeed( const QAngle& angles, int player_render_tick )
{
#ifdef SERVER_DLL
// sv_usercmd_custom_random_seed = false by default now.
// m_nPredictionRandomSeed = cmd->random_seed;
// m_nPredictionRandomSeedServer = sv_usercmd_custom_random_seed ? GetRngSeed() : cmd->random_seed;
if ( m_nPredictionRandomSeedServer != m_nPredictionRandomSeed )
return m_nPredictionRandomSeedServer;
#endif
struct HashBuffer
{
float pitch;
float yaw;
int tick;
};
HashBuffer buffer;
float pitch = AngleNormalize(angles.pitch);
pitch += pitch;
buffer.pitch = RoundFloat(pitch) * 0.5f;
float yaw = AngleNormalize(angles.yaw);
yaw += yaw;
buffer.yaw = RoundFloat(yaw) * 0.5f;
buffer.tick = player_render_tick;
CSHA1 sha1;
sha1.Reset( );
sha1.Update( &buffer, sizeof(buffer) );
sha1.Final();
return *reinterpret_cast<uint32_t*>(&sha1.m_digest[0]);
}
Использование хаоса как основу для порядка
Если вы сейчас стоите, то вам лучше присесть, потому что это самое простое решение, которое можно придумать.
Предположим, у нас есть угол обзора и текущий тик отрисовки. Для компенсации разброса мы можем перебирать случайные углы, вычислять для них сид и вычислять финальный угол с учетом разброса.
В итоге, наш принцип действия будет выглядеть так:
- Генерируем случайный угол.
- Вычисляем сид для этого случайного угла и текущего тика.
- Используем полученный сид для моделирования выстрела с разбросом из текущего угла.
- Получаем финальный угол выстрела с учетом разброса.
- Если финальный угол достаточно близок к исходному (в пределах заданного порога), сохраняем найденный сид.
- Если нет, генерируем новый случайный угол и повторяем шаги с 3 по 6.
- Как только находим подходящий угол, используем его вместо реального.
- Для нового угла обзора повторяем поиск подходящего угла сначала.
Таким образом, мы перебираем случайные варианты, пока не найдем сид, которое компенсирует разброс для текущего угла.
Как же посчитать разброс?Улучшения реализации: В нашем реализации мы брали угол обзора и моделировали разброс с сидом от 0 до 255. Это позволило оптимизировать подбор случайного угла.
Давайте сразу перейдем к коду, который отвечает за моделирование разброса. Я не буду углубляться в подробные объяснения, чтобы быстрее показать реализацию.
C++:
bool bForceMaxInaccuracy = weapon_debug_max_inaccuracy.GetBool();
bool bForceInaccuracyDirection = weapon_debug_inaccuracy_only_up.GetBool();
bool bWeaponAccuracyShotgunSpreadPatterns = weapon_accuracy_shotgun_spread_patterns.GetBool();
Math::RandomSeed(nRandomSeed + 1);
float flRadiusCurveDensity = Math::RandomFloat(0.0f, 1.0f);
if (nItemIndex == 64 && nMode == nSecondaryMode) {
// R8 Revolver secondary fire
flRadiusCurveDensity = 1.0f - flRadiusCurveDensity * flRadiusCurveDensity;
}
else if (nItemIndex == 28 && flRecoilIndex < 3) {
// Negev Wild Beast
for (int nIndex = 3; nIndex > flRecoilIndex; --nIndex) {
flRadiusCurveDensity *= flRadiusCurveDensity;
}
flRadiusCurveDensity = 1.0f - flRadiusCurveDensity;
}
if (bForceMaxInaccuracy) {
flRadiusCurveDensity = 1.0f;
}
float flTheta0 = Math::RandomFloat(0.0f, 2.0f * M_PI);
if (bForceInaccuracyDirection) {
flTheta0 = M_PI * 0.5f;
}
if (bWeaponAccuracyShotgunSpreadPatterns)
{
// Missing.
}
float flRadius0 = flRadiusCurveDensity * flInaccuracy;
float flX0 = flRadius0 * cosf(flTheta0);
float flY0 = flRadius0 * sinf(flTheta0);
float flSpreadCurveDensity = Math::RandomFloat(0.0f, 1.0f);
if (nItemIndex == 64 && nMode == nSecondaryMode) {
// R8 Revolver secondary fire
flSpreadCurveDensity = 1.0f - flSpreadCurveDensity * flSpreadCurveDensity;
}
else if (nItemIndex == 28 && flRecoilIndex < 3) {
// Negev Wild Beast
for (int nIndex = 3; nIndex > flRecoilIndex; --nIndex) {
flSpreadCurveDensity *= flSpreadCurveDensity;
}
flSpreadCurveDensity = 1.0f - flSpreadCurveDensity;
}
if (bForceMaxInaccuracy) {
flSpreadCurveDensity = 1.0f;
}
float flTheta1 = Math::RandomFloat(0.0f, 2.0f * M_PI);
if (bForceInaccuracyDirection) {
flTheta1 = M_PI * 0.5f;
}
float flRadius1 = flSpreadCurveDensity * flSpread;
float flX1 = flRadius1 * cosf(flTheta1);
float flY1 = flRadius1 * sinf(flTheta1);
double flYResult = (double)(flY1 * flRadius0) +
(double)(flY0 * flRadius1);
double flXResult = (double)(flX1 * flRadius0) +
(double)(flX0 * flRadius1);
Сложив всю информацию, вы могли прийти к тому же результату, что и я. Возврат компенсации отдачи повлечёт серьёзные изменения в культуре HvH и читеров в целом. Сейчас я хочу продемонстрировать, что это действительно работает.
Давайте посмотрим, как это работает на практике:В этом видео, записанном моим другом porches, мы тестируем работу компенсации отдачи. Для создания лабораторных условий отдачи установлено на 0.25 с помощью команды weapon_accuracy_forcespread.
Всем пока, еще встретимся!
Credits:
SapDragon - Theoretical research
Porches - Practical implementation