Гайд CS:GO Penetration(Trace) System

Модератор раздела «Создание читов CS2»
Забаненный
Статус
Оффлайн
Регистрация
21 Июн 2022
Сообщения
148
Реакции[?]
366
Поинты[?]
155K
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
В этой теме вы узнаете:
- Как работает движок трассировки.
- Что такое фильтры трассировки.
- Как работает система пенетрации.
- Как сделать правильный autowall.
- И многое другое.

Структура игровой трассировки (GameTrace) #1
Давайте взглянем на исходник игры и найдем соответствующий класс нужной нами игровой трассировки. (
Пожалуйста, авторизуйтесь для просмотра ссылки.
)
Как мы видим, класс состоит из переменных: fractionleftsolid, surface, hitgroup, physicsbone, worldSurfaceIndex, hitbox, m_pEnt и др., а так же из функций:
Пожалуйста, авторизуйтесь для просмотра ссылки.
,
Пожалуйста, авторизуйтесь для просмотра ссылки.
,
Пожалуйста, авторизуйтесь для просмотра ссылки.
,
Пожалуйста, авторизуйтесь для просмотра ссылки.
и
Пожалуйста, авторизуйтесь для просмотра ссылки.
Ниже мы разберем каждую переменную и функцию.

Данные, которые хранит игровая трассировка #1.1
surface​
поверхность удара​
hitgroup​
определенная часть тела​
physicsbone​
физическая кость​
worldSurfaceIndex​
индекс структуры msurface2_t​
m_pEnt​
объект столкновения​
hitbox​
если мы(трассировка) наткнулись на что-то и это не является мировым объектом, то возвращаемым значением будет индекс хитбокса, иначе статическим индексом пропа.​
startpos​
начальная позиция​
endpos​
конечная позиция​
plane​
поверхности при ударе​
fraction​
время завершения (равен 1.0, если мы не во что не попали)​
contents​
содержимое на другой стороне поверхности удара​
dispFlags​
флаги смещения для маркировки поверхностей​
allsolid​
возвращает true, если плоскость невалидна​
startsolid​
возвращает true, если начальная точка находилась в сплошной области​


Принцип работы функции
Пожалуйста, авторизуйтесь для просмотра ссылки.
#1.2
Функция отслеживает только мировые столкновения и возвращает true, если объект это мировая сущность.
C++:
bool CGameTrace::DidHitWorld() const
{
    return m_pEnt == GetWorldEntity();
}
Примечание от Valve: Если функция возвращает true, то вы не сможете использовать функцию GetHitBoxIndex.

Принцип работы функции
Пожалуйста, авторизуйтесь для просмотра ссылки.
#1.3

Функция отслеживает все столкновения, кроме мировых. Возвращает true, если мы(трассировка) наткнулись на что-то и это не является мировым объектом.
Для проверки столкновения с миром, мы используем уже известную нами функцию
Пожалуйста, авторизуйтесь для просмотра ссылки.
(#1.2)
C++:
bool CGameTrace::DidHitNonWorldEntity() const
{
    return m_pEnt != NULL && !DidHitWorld();
}
Принцип работы функции
Пожалуйста, авторизуйтесь для просмотра ссылки.
#1.4

Функция возвращает индекс объекта, на который мы(трассировка) наткнулись. В случае неудачи возвращается -1.
C++:
int CGameTrace::GetEntityIndex() const
{
    if ( m_pEnt )
        return m_pEnt->entindex();
    else
        return -1;
}
Принцип работы функции
Пожалуйста, авторизуйтесь для просмотра ссылки.
#1.5

Функция возвращает true, если мы(трассировка) наткнулись на какой-либо объект при условии: если мы в него попали && недействительная плоскость && начальная точка находилась в сплошной области (см. #1.1).
C++:
bool CGameTrace::DidHit() const
{
    return fraction < 1 || allsolid || startsolid;
}
Фильтр трассировки #2
Если кратно, то это создано для того, чтобы фильтровать/игнорировать определенные выбранные нами объекты.
Мы можем выбрать что хотим трассировать: всё на карте, только мир, только не мировые объекты и т.д.
C++:
enum TraceType_t
{
    TRACE_EVERYTHING = 0,
    TRACE_WORLD_ONLY,    
    TRACE_ENTITIES_ONLY,
    TRACE_EVERYTHING_FILTER_PROPS,    // будет передавать IHandleEntity для реквизита через фильтр, в отличие от всех других фильтров.
};
Соответствующее классы для определенного фильтра (
Пожалуйста, авторизуйтесь для просмотра ссылки.
):
C++:
class CTraceFilterEntitiesOnly : public ITraceFilter
{
public:
    virtual TraceType_t    GetTraceType() const
    {
        return TRACE_ENTITIES_ONLY;
    }
};


class CTraceFilterWorldOnly : public ITraceFilter
{
public:
    bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
    {
        return false;
    }
    virtual TraceType_t    GetTraceType() const
    {
        return TRACE_WORLD_ONLY;
    }
};

class CTraceFilterWorldAndPropsOnly : public ITraceFilter
{
public:
    bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
    {
        return false;
    }
    virtual TraceType_t    GetTraceType() const
    {
        return TRACE_EVERYTHING;
    }
};

class CTraceFilterHitAll : public CTraceFilter
{
public:
    virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
    {
        return true;
    }
};
Структура лучей (Ray_t) #3
Данная структура помогает в создании собственного луча.
Ниже приведена таблица с данными и их объяснениями, которые хранит и использует структура Ray_t (
Пожалуйста, авторизуйтесь для просмотра ссылки.
).
m_Start​
начальная точка​
m_Delta​
направление и длина луча​
m_StartOffset​
фактическое начало луча (чтобы получить это, добавьте m_Start)​
m_Extents​
описывает выровненный по оси прямоугольник, выдавленный вдоль луча​
m_IsRay​
возвращает true, если m_Extents равен нулю​
m_IsSwept​
возвращает true, если направление и длина луча не равны нулю​

Функции, которые инициализируют нужные данные (
Пожалуйста, авторизуйтесь для просмотра ссылки.
):
C++:
void Init( Vector const& start, Vector const& end )
{
    Assert( &end );
    VectorSubtract( end, start, m_Delta );

    m_IsSwept = (m_Delta.LengthSqr() != 0);

    VectorClear( m_Extents );
    m_pWorldAxisTransform = NULL;
    m_IsRay = true;

    // Offset m_Start to be in the center of the box...
    VectorClear( m_StartOffset );
    VectorCopy( start, m_Start );
}

void Init( Vector const& start, Vector const& end, Vector const& mins, Vector const& maxs )
{
    Assert( &end );
    VectorSubtract( end, start, m_Delta );

    m_pWorldAxisTransform = NULL;
    m_IsSwept = (m_Delta.LengthSqr() != 0);

    VectorSubtract( maxs, mins, m_Extents );
    m_Extents *= 0.5f;
    m_IsRay = (m_Extents.LengthSqr() < 1e-6);

    Assert(m_Extents.x >=0 && m_Extents.y >= 0 && m_Extents.z >= 0);
    // Offset m_Start to be in the center of the box...
    VectorAdd( mins, maxs, m_StartOffset );
    m_StartOffset *= 0.5f;
    VectorAdd( start, m_StartOffset, m_Start );
    m_StartOffset *= -1.0f;
}
Структура движка трассировки (EngineTrace) #4
Это очень интересная вещь, но в основном всех кодеров читов интересует только эти функции:
- GetPointContents: Возвращает маску содержимого и объект в определенной позиции карты (принимает 3 параметра: позицию, маску содержимого и объект).
- ClipRayToEntity: Пускает луч до определенного объекта на карте и возвращает данные трассировки (принимает 4 аргумента: данные о луче(см. #3), маску содержимого, объект и данные о трассировке).
- TraceRay: Просто принимает луч трассировки (принимает 4 аргумента: данные о луче, маску содержимого, объект и данные о трассировке).
Я не буду заострять внимание на всех функциях, так как их много, но если вам интересно более глубже изучить движок трассировки, то вот ссылка:
Пожалуйста, авторизуйтесь для просмотра ссылки.


Система пенетрации (или как сделать autowall) #5
Изучив всю нужную информацию о движке трассировки и сопутствующих к нему структур и классов, мы можем сделать функцию, которая в простонародии называется autowall или же система пенетрации.
Приступим к самому главному — изучению работы простелов в CS:GO. Существует интересная функция
Пожалуйста, авторизуйтесь для просмотра ссылки.
, она проводит различные вычисления и работу с выстреленной пулей, в пример возьмем импакты: внутри функции сохраняются данные x,y,z из конечной позиции в структуре трассировкии передаются в событие(эвент) "bullet_impact".
1672499810518.png

Но нас не интересуют импакты, нам нужно сделать хорошую систему прострелов. Поэтому, если более внимательней изучить код, то можно найти интересные переменные:
Пожалуйста, авторизуйтесь для просмотра ссылки.
и другие, о которых я напишу позже.
Ниже приведена таблица, которая объясняет значение этих данных:
fCurrentDamage​
урон пули на текущей траектории​
flCurrentDistance​
расстояние, которое прошла пуля​
flPenetrationPower​
толщина стены, которую может пробить эта пуля​
flPenetrationDistance​
расстояние, на котором пуля способна пробить стену​
flDamageModifier​
модификация мощности пуль по умолчанию после того, как они пробивают стену​
flPenetrationModifier​
модификатор мощности пробития​

Прочитав, я думаю понятно, что это весьма интересные и нужные данные для системы прострела. Посмотрим, какие они имеют значения, где еще рассчитываются и где применяются, исключив ненужный код импактов и другого дерьма.
Как мы можем видеть, новое расстояние рассчитывается на базе разницы пройденого и максимального, а урон исходя из нового пройденного пулей расстояния.
А так же видим проверку на достижения дистанции пробития пулей, после этого пробитий не должно быть. И если наш модификатор мощности пробития очень низкий, просто останавливаем пулю. Установка nPenetrationCount в ноль предотвращает попадание пули в объект на максимальном расстоянии.

Видим, что переменные передаются в качестве аргументов функции HandleBulletPenetration.

Данная функция рассчитывает при каком условии пуля больше не может пробивать определенные объекты(материалы), т.е справляется с проникновением пуль в объекты. Возвращает значение true, если пуля больше не может простреливать какой-либо объект.
Вот один из примеров, когда пуля не сможет прострелить объект (при условии, если мы на максимальном расстоянии и материал это решетка или стекло):

Далее, если немного опуститься вглубь функции, можно увидеть расчет модификации мощности пуль, после того, как они пробивают стену, модификатор мощности пробития, урон пули на текущей дистанции, толщину стены, которую может пробить пуля и центр дистанции.

Хорошо, начинку мы изучили, но потеряли одну проверку связанную с функцией
Пожалуйста, авторизуйтесь для просмотра ссылки.
.

Что же она такого интересного делает? Данная функция находит точную конечную позицию проникновения пули.
Если кратко, то она работает пока дистанция меньше или равна максимальной, после вычисляет конечную позицию на основе дистанции, далее получает содержимое точки и работает с системой трассировки и его движком, пускает луч к объекту, проверяет разные данные по типу startsolid и surface флаги для проверки попадания через стену в хитбокс игрока.
Но самое интересное во всех этих вычислениях — функция
Пожалуйста, авторизуйтесь для просмотра ссылки.
. Она возвращает true, если интересующий нас объект является ломаемым. Нам нужно сделать ребилд данной функции: сделать бекап take_damage`a, установить значение damage_yes(2) для take_damage`a, вызвать оригинал и ресторить take_damage.
Вроде бы все, не забываем о расчете урона для каждой хитгрупы противника. Для расчета урона в CS:GO сделана функция
Пожалуйста, авторизуйтесь для просмотра ссылки.
. Украдем оттуда немного расчетов и переделаем их под себя.

Имея на руках всю нужную информацию, можем сделать полноценный, готовый autowall.
C++:
    if ( !weapon )
        return false;

    const auto weapon_data = weapon->get_weapon_info( );

    if ( !weapon_data )
        return false;

    c_game_trace enter_trace;
    const uint32_t filter_[ 4 ] = { *patterns[ trace_filter ].add( 0x3D ).as<uint32_t*>( ),
                            reinterpret_cast< uint32_t >( ctx::local( ) ), 0, 0 };

    current_damage = weapon_data->m_damage; // damage of the bullet at it's current trajectory
    auto eye_position = pos;
    constexpr auto penetration_distance = 3000.0f; // distance at which the bullet is capable of penetrating a wall
    auto penetration_power = 35.0f; // thickness of a wall that this bullet can penetrate
    hits = 4;

    static const auto var = i::convar->find_convar( "sv_penetration_type" );
    if ( const int possible_hits = var->get_int( ); possible_hits == 1 ) {
        penetration_power = weapon_data->m_penetration;
    }

    const float max_range = std::min( weapon_data->m_range, ( eye_position - point ).length( false ) );
    const auto end = eye_position + direction * max_range;

    while ( current_damage >= 1.0f ) {
        i::engine_trace->trace_ray( ray_t( eye_position, end ), mask_shot_hull | contents_hitbox,
                                    ( c_trace_filter* )filter_, &enter_trace );

        if ( e )
            clip_trace_to_player( eye_position, end + direction * 40.0f, mask_shot_hull | contents_hitbox, &enter_trace, static_cast< c_csplayer* >( e ) );

        const auto enter_surface_data = i::phys_surface_props->get_surface_data( enter_trace.surface.surface_props );
        const auto enter_surf_penetration_modifier = enter_surface_data->game.penetration_modifier;

        // calculate the damage based on the distance the bullet travelled.
        const float distance_traced = ( enter_trace.end - eye_position ).length( false );
        current_damage *= std::pow( weapon_data->m_range_modifier, distance_traced / 500.f );

        // we didn't hit anything, stop tracing shoot
        if ( enter_trace.fraction == 1.0f ) {
            break;
        }

        // check if we reach penetration distance, no more penetrations after that
        // or if our modifyer is super low, just stop the bullet
        if ( distance_traced > penetration_distance && weapon_data->m_penetration > 0.0f || enter_surf_penetration_modifier < 0.1f ) {
            break;
        }

        //if ( *( v123 + 13196 ) == v83 ) {
        //    v115 = 4352;
        //    if ( *( hit_ent + 39649 ) )
        //        current_damage = 1.0;
        //}

        const auto can_do_damage = enter_trace.hitgroup != hitgroup_gear && enter_trace.hitgroup != hitgroup_generic;
        const auto is_player = enter_trace.entity->is_player( );
        const auto is_enemy = static_cast< c_csplayer* >( enter_trace.entity )->team( ) != ctx::local( )->team( );

        if ( can_do_damage && is_player && is_enemy ) {
            scale_damage( static_cast< c_csplayer* >( enter_trace.entity ), enter_trace.hitgroup, weapon_data, current_damage );
            hitbox = enter_trace.hitbox;
            return true;
        }

        if ( !hits )
            break;

        static const auto damage_reduction_bullets = i::convar->find_convar( "ff_damage_reduction_bullets" );
        static const auto damage_bullet_penetration = i::convar->find_convar( "ff_damage_bullet_penetration" );

        if ( !handle_bullet_penetration( weapon_data, enter_trace, eye_position, direction, hits, current_damage, penetration_power, damage_reduction_bullets->get_float( ), damage_bullet_penetration->get_float( ) ) )
            break;
    }

    return false;
C++:
    if ( !e->is_player( ) )
        return;

    auto is_armored = [&]( )->bool {
        switch ( group ) {
        case hitgroup_head:
            return e->has_helmet( );
        case hitgroup_generic:
        case hitgroup_chest:
        case hitgroup_stomach:
        case hitgroup_leftarm:
        case hitgroup_rightarm:
            return true;
        default:
            return false;
        }
    };

    // @xref: https://github.com/perilouswithadollarsign/cstrike15_src/blob/master/game/server/player.cpp#L1194
    static auto mp_damage_scale_ct_head = i::convar->find_convar( "mp_damage_scale_ct_head" );
    static auto mp_damage_scale_t_head = i::convar->find_convar( "mp_damage_scale_t_head" );

    static auto mp_damage_scale_ct_body = i::convar->find_convar( "mp_damage_scale_ct_body" );
    static auto mp_damage_scale_t_body = i::convar->find_convar( "mp_damage_scale_t_body" );

    float head_damage_scale = e->team( ) == 3 ? mp_damage_scale_ct_head->get_float( ) : e->team( ) == 2 ? mp_damage_scale_t_head->get_float( ) : 1.0f;
    const float body_damage_scale = e->team( ) == 3 ? mp_damage_scale_ct_body->get_float( ) : e->team( ) == 2 ? mp_damage_scale_t_body->get_float( ) : 1.0f;

    if ( e->has_heavy_armor( ) )
        head_damage_scale *= 0.5f;

    switch ( group ) {
    case hitgroup_head:
        {
            current_damage *= weapon_data->m_headshot_multiplier * head_damage_scale;
        } break;
    case hitgroup_chest:
    case hitgroup_leftarm:
    case hitgroup_rightarm:
    case 8: // neck
        {
            current_damage *= body_damage_scale;
        } break;
    case hitgroup_stomach:
        {
            current_damage *= 1.25f * body_damage_scale;
        } break;
    case hitgroup_leftleg:
    case hitgroup_rightleg:
        {
            current_damage *= 0.75f * body_damage_scale;
        } break;
    default: break;
    }

    const auto armor_value = static_cast< float >( e->armor( ) );

    if ( armor_value <= 0.0f || !is_armored( ) )
        return;

    // @ida: module: server.dll; sig: 80 BF ? ? ? ? ? F3 0F 10 5C 24 ? F3 0F 10 35
    float heavy_armor_bonus = 1.0f,
        armor_bonus = 0.5f,
        armor_ratio = weapon_data->m_armor_ratio * 0.5f;

    if ( e->has_heavy_armor( ) ) {
        heavy_armor_bonus = 0.25f;
        armor_bonus = 0.33f;
        armor_ratio *= 0.20f;
    }

    float damage_to_health = current_damage * armor_ratio;
    const auto damage_to_armor = ( current_damage - damage_to_health ) * ( heavy_armor_bonus * armor_bonus );

    // does this use more armor than we have?
    if ( damage_to_armor > armor_value )
        damage_to_health = current_damage - armor_value / armor_bonus;

    current_damage = damage_to_health;
C++:
    // @ida: https://imgur.com/x3Qe12r
    // module: server.dll; sig: F3 0F 5C CE F3 0F 11 5D ?
    auto end = vector3d( );
    auto v22 = vector3d( );

    auto current_distance = 0.0f;
    auto first_contents = 0;

    while ( current_distance <= 90.0f ) {
        current_distance += 4.0f;
        end = start_position + direction * current_distance;

        if ( !first_contents )
            first_contents = i::engine_trace->get_point_contents( start, mask_shot_hull | contents_hitbox, nullptr );

        const auto point_contents = i::engine_trace->get_point_contents( start, mask_shot_hull | contents_hitbox, nullptr );

        if ( point_contents & mask_shot_hull && ( !( point_contents & contents_hitbox ) || point_contents == first_contents ) )
            continue;

        v22 = end - direction * 4.0f;
        i::engine_trace->trace_ray( ray_t( end, v22 ), mask_shot_hull | contents_hitbox, nullptr, &exit_trace );

        if ( static const auto var = i::convar->find_convar( "sv_clip_penetration_traces_to_players" );
             var->get_int( ) ) {
            clip_trace_to_player( end, v22, 0x4600400B, &exit_trace, ctx::local( ) );
        }

        if ( exit_trace.start_solid && exit_trace.surface.flags & surf_hitbox ) {
            const uint32_t filter[ 4 ] = { *patterns[ trace_filter ].add( 0x3D ).as<uint32_t*>( ),
                reinterpret_cast< uint32_t >( exit_trace.entity ), 0, 0 };

            i::engine_trace->trace_ray( ray_t( end, start_position ), mask_shot_hull | contents_hitbox,
                                        ( c_trace_filter* )filter, &exit_trace );

            if ( exit_trace.hit( ) && !exit_trace.start_solid ) {
                return true;
            }

            continue;
        }

        // client.dll; 0F 85 D3 00 00 00 8A 5B 42
        if ( !exit_trace.hit( ) && exit_trace.start_solid ) {
            const auto ent = static_cast< c_csplayer* >( enter_trace.entity );
            if ( ent && ent != ctx::local( ) && ent->is_enemy( ) ) {
                if ( is_breakable_entity( ent ) ) {
                    exit_trace = enter_trace;
                    exit_trace.end = start_position + direction;
                    return true;
                }
            }
            continue;
        }

        // server.dll;
        if ( exit_trace.hit( ) && !exit_trace.start_solid ) {
            if ( is_breakable_entity( static_cast< c_csplayer* >( enter_trace.entity ) ) && is_breakable_entity( static_cast< c_csplayer* >( exit_trace.entity ) ) ) {
                end = exit_trace.end;
                return true;
            }

            if ( enter_trace.surface.flags & surf_nodraw || !( exit_trace.surface.flags & surf_nodraw ) && exit_trace.plane.normal.dot( direction ) <= 1.0f ) {
                end -= direction * exit_trace.fraction * 4.0f;
                return true;
            }
        }
    }

    //if ( !enter_trace.did_hit_non_world_entity() || !is_breakable_entity( enter_trace.entity ) ) {
    //    direction = a6;
    //    direction = a7;
    //    direction = a8;
    //    return true;
    //}

    return false;
C++:
    c_game_trace exit_trace;

    const auto enter_surface_data = i::phys_surface_props->get_surface_data( enter_trace.surface.surface_props );
    const auto enter_material = enter_surface_data->game.material;

    const bool is_solid_surf = enter_trace.contents >> 3 & contents_solid;
    const bool is_light_surf = enter_trace.surface.flags >> 7 & surf_light;

    if ( !possible_hits_remaining && enter_material != 89 && enter_material != 71 )
        return false;

    if ( !weapon_data->m_penetration
         || !trace_to_exit( enter_trace, exit_trace, enter_trace.end, direction )
         && !( i::engine_trace->get_point_contents( enter_trace.end, 0x600400B, nullptr ) & 0x600400B ) )
        return false;

    static const auto var = i::convar->find_convar( "sv_penetration_type" );
    const int possible_hits = var->get_int( );

    const auto exit_surface_data = i::phys_surface_props->get_surface_data( exit_trace.surface.surface_props );
    const auto exit_material = exit_surface_data->game.material;

    float combined_penetration_modifier{};
    float final_damage_modifier;

    if ( possible_hits != 1 ) {
        if ( is_solid_surf || is_light_surf ) {
            combined_penetration_modifier = 1.0f;
            final_damage_modifier = 0.99f;
        }
        else {
            final_damage_modifier = enter_surface_data->game.damage_modifier;
            combined_penetration_modifier = fminf( enter_surface_data->game.penetration_modifier,
                                                   exit_surface_data->game.penetration_modifier );
            if ( enter_surface_data->game.damage_modifier > enter_surface_data->game.jump_factor )
                final_damage_modifier = enter_surface_data->game.jump_factor;
        }
        if ( enter_material == exit_material && ( exit_material == 87 || exit_material == 77 ) )
            combined_penetration_modifier *= 2.0f;

        if ( ( exit_trace.end - enter_trace.end ).length_sqr( ) > combined_penetration_modifier * penetration_power )
            return false;

        current_damage *= final_damage_modifier;
        eye_position = exit_trace.end;
        --possible_hits_remaining;

        return true;
    }

    final_damage_modifier = 0.16f;
    if ( !is_solid_surf && !is_light_surf ) {
        if ( enter_material == 89 ) {
            final_damage_modifier = 0.05f;
            combined_penetration_modifier = 3.0f;
        }

        if ( enter_material != 71 ) {
            // CLIENT.DLL
            const auto hit_ent = static_cast< c_csplayer* >( enter_trace.entity );
            if ( hit_ent ) {
                const auto func = memory::vfunc<bool( __thiscall* )( c_csplayer* )>( hit_ent, 158 )( hit_ent );
                if ( func && hit_ent->unk_awol_thing( ) ) // 0x9AE1 {
                    combined_penetration_modifier = exit_surface_data->game.penetration_modifier;
                    final_damage_modifier = 0.16f;
                }
            }

            if ( enter_material == 70 && ( ff_damage_reduction_bullets == 0.0f && hit_ent ) ) {
                const auto func = memory::vfunc<bool( __thiscall* )( c_csplayer* )>( hit_ent, 158 )( hit_ent );
                if ( func && !hit_ent->is_enemy( ) ) {
                    // If friendly fire is off, this will scale the penetration power and damage a bullet does when penetrating another friendly player
                    if ( ff_damage_bullet_penetration == 0.0f )
                        return false;

                    combined_penetration_modifier = ff_damage_bullet_penetration;
                    final_damage_modifier = 0.16f;
                }
            }

            combined_penetration_modifier = ( enter_surface_data->game.penetration_modifier + exit_surface_data->game.penetration_modifier ) / 2.0f;
            final_damage_modifier = 0.16f;

            // SERVER.DLL
            /*if ( !hit_ent || !hit_ent->unk_awol_thing( ) ) {
                    if ( enter_material != 70
                         || ff_damage_reduction_bullets
                         || hit_ent->team( ) == ctx::local( )->team( ) ) {
                        combined_penetration_modifier = ( enter_surface_data->game.penetration_modifier + exit_surface_data->game.penetration_modifier ) / 2.0f;
                        final_damage_modifier = 0.16f;
                    }
                }

                // i think we must remove this check
                if ( ff_damage_bullet_penetration == 0.0f )
                    return false;

                combined_penetration_modifier = ff_damage_bullet_penetration;
                final_damage_modifier = 0.16f;*/
        }
    }


    if ( enter_material != 89 && enter_material != 71 ) {
        combined_penetration_modifier = 1.0f;
        if ( enter_material == exit_material ) {
            if ( exit_material == 87 || exit_material == 85 ) {
                combined_penetration_modifier = 3.0f;
            }
            else if ( exit_material == 76 ) {
                combined_penetration_modifier = 2.0f;
            }
        }
    }

    const auto thickness = ( exit_trace.end - enter_trace.end ).length_sqr( );
    const auto modifier = fmax( 1.0f / combined_penetration_modifier, 0.0f );

    const auto lost_damage = fmax(
        modifier * thickness / 24.0f + current_damage * final_damage_modifier + fmax(
        3.75f / penetration_power, 0.0f ) * 3.0f * modifier, 0.0f );

    if ( lost_damage > current_damage )
        return false;

    if ( lost_damage > 0.0f )
        current_damage -= lost_damage;

    if ( current_damage < 1.0f )
        return false;

    eye_position = exit_trace.end;
    --possible_hits_remaining;

    return true;
 
t.me/shepard
Пользователь
Статус
Оффлайн
Регистрация
8 Апр 2019
Сообщения
296
Реакции[?]
93
Поинты[?]
21K
Крутой гайд, для понимающих людей будет однозначно полезно
 
Keep Ev0lving, Stay Fatal
Эксперт
Статус
Оффлайн
Регистрация
6 Фев 2018
Сообщения
1,543
Реакции[?]
583
Поинты[?]
99K
Крутой гайд, для понимающих людей будет однозначно полезно
Нет.
Тема была сделана для новичков, которые не шарят за «большие функции» и их смысл.
Виллиаму респект, ибо расписано все максимально просто и понятно.
Именно такими должны быть темы для новичков. Никакой приват инфы, но при этом максимально правильная и нужная инфа из паблика.
Рад, что есть человек, которые радует новичков такими темами, в наше время таких не было…
 
t.me/shepard
Пользователь
Статус
Оффлайн
Регистрация
8 Апр 2019
Сообщения
296
Реакции[?]
93
Поинты[?]
21K
Нет.
Тема была сделана для новичков, которые не шарят за «большие функции» и их смысл.
Виллиаму респект, ибо расписано все максимально просто и понятно.
Именно такими должны быть темы для новичков. Никакой приват инфы, но при этом максимально правильная и нужная инфа из паблика.
Рад, что есть человек, которые радует новичков такими темами, в наше время таких не было…
Согласен, не правильно сформулировал, именно это я и хотел написать!
 
Начинающий
Статус
Оффлайн
Регистрация
25 Окт 2022
Сообщения
35
Реакции[?]
6
Поинты[?]
0
Нет.
Тема была сделана для новичков, которые не шарят за «большие функции» и их смысл.
Виллиаму респект, ибо расписано все максимально просто и понятно.
Именно такими должны быть темы для новичков. Никакой приват инфы, но при этом максимально правильная и нужная инфа из паблика.
Рад, что есть человек, которые радует новичков такими темами, в наше время таких не было…
Полностью вас поддерживаю, сегодня день лучших гайдов для меня
 
Пользователь
Статус
Оффлайн
Регистрация
13 Сен 2021
Сообщения
598
Реакции[?]
116
Поинты[?]
43K
Хороша тема, получше чем "Best autowall tutorial by kolyanprohvh228"

Попкорн на теме от самых ожидаемых людей
1672680228196.png
1672684339336.png
1673625517916.png
 
Последнее редактирование:
Сверху Снизу