Трассировка пуль

Shkrest

Участник
Сообщения
7
Рейтинг
0
Помогите с трассерами для WSS — код шлёт всё правильно, а трасс всё равно ноль

Всем привет, сообщество!

Есть у меня система кастомных скинов оружия (аналог WSS — под каждым скином своя v/p/w модель, урон, магазин и т.д.), крутится на ReHLDS + AMXX + ReAPI + Hamsandwich. Решил добавить визуальные трассеры пуль — чисто косметика, чтобы заскиненное оружие стреляло красиво, пистолеты потоньше, всё остальное потолще. Никакого влияния на хитрег не планировалось, просто beauty overlay.

Формально всё готово, компилируется без единой ошибки, хук стреляет, данные корректные... а трассера как не было, так и нет. Третий день воюю с векторной алгеброй вместо того, чтобы фиксить нормальные баги. Пришло время звать на помощь зал.

Стек: ReHLDS + AMXX 1.10 + ReAPI + Hamsandwich, cstrike.

Что делаю (упрощённо, без внутренностей системы скинов):

Вешаю хук на выстрел только для оружия, у которого активен кастомный скин:

Код:
RegisterHam(Ham_Weapon_PrimaryAttack, "weapon_ak47", "Tracer_OnFire_Post", true)
// и так по всем классам оружия из списка

public Tracer_OnFire_Post(pWeapon) {
    // тут проверка "а точно ли на этом оружии активен скин" - опущено, это внутренности системы
    if (!SkinIsActive(pWeapon))
        return

    new pPlayer = get_member(pWeapon, m_pPlayer)
    if (!is_user_connected(pPlayer) || !is_user_alive(pPlayer))
        return

    DrawTracer(pPlayer, iWidth) // iWidth - 15 для пистолетов, 30 для остального
}

А дальше собственно рисование луча через TE_BEAMPOINTS:

Код:
DrawTracer(pPlayer, iWidth) {
    new Float:vecOrigin[3], Float:vecViewOfs[3], Float:vecAngles[3]
    pev(pPlayer, pev_origin, vecOrigin)
    pev(pPlayer, pev_view_ofs, vecViewOfs)
    pev(pPlayer, pev_v_angle, vecAngles)

    new Float:vecStart[3]
    vecStart[0] = vecOrigin[0] + vecViewOfs[0]
    vecStart[1] = vecOrigin[1] + vecViewOfs[1]
    vecStart[2] = vecOrigin[2] + vecViewOfs[2]

    new Float:vecDir[3]
    angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecDir)

    new Float:vecEnd[3]
    vecEnd[0] = vecStart[0] + vecDir[0] * 8192.0
    vecEnd[1] = vecStart[1] + vecDir[1] * 8192.0
    vecEnd[2] = vecStart[2] + vecDir[2] * 8192.0

    new tr = create_tr2()
    engfunc(EngFunc_TraceLine, vecStart, vecEnd, IGNORE_MONSTERS, pPlayer, tr)
    get_tr2(tr, TR_vecEndPos, vecEnd)
    free_tr2(tr)

    message_begin(MSG_PVS, SVC_TEMPENTITY, vecStart)
    write_byte(TE_BEAMPOINTS)
    write_coord(vecStart[0])
    write_coord(vecStart[1])
    write_coord(vecStart[2])
    write_coord(vecEnd[0])
    write_coord(vecEnd[1])
    write_coord(vecEnd[2])
    write_short(g_iTracerSprite) // precache_model("sprites/laserbeam.spr"), индекс валидный
    write_byte(0)   // startframe
    write_byte(0)   // framerate
    write_byte(2)   // life, 0.1s
    write_byte(iWidth) // width, 0.1 юнита
    write_byte(0)   // noise
    write_byte(255) // r
    write_byte(180) // g
    write_byte(60)  // b
    write_byte(200) // brightness
    write_byte(0)   // speed
    message_end()
}

Что уже проверено и отбраковано:

  • Спрайт прекешится нормально, precache_model() возвращает валидный ненулевой индекс, никаких warning в логах.
  • Ширина луча — это десятые доли юнита, а не пиксели. Спалился на этом сам: сначала стояло write_byte(3) (то есть 0.3 юнита — практически невидимо), поднял до 15/30 — ничего не изменилось.
  • Включил дебаг-логирование прямо в хуке: хук стабильно срабатывает, скин определяется правильно, координаты start/end вменяемые (не (0,0,0), не NaN, трасса реально ложится вдоль линии выстрела), сообщение реально уходит через message_end() — то есть по всем признакам сервер делает всё, что должен.
  • Заметил, что стрелок не видит собственный трассер, если тот идёт строго вдоль линии взгляда (глаза → направление взгляда). Логика простая: движок считает видимую ширину луча как векторное произведение направления луча и направления "точка → камера", а у самого стрелка эти два вектора коллинеарны — произведение ноль, луч визуально схлопывается в точку. Сдвинул старт луча в сторону (эмуляция позиции ствола, что-то вроде vecStart += vecDir*10 + vecRight*5 - vecUp*4) — по идее должно было решить проблему для самого стрелка.

И вот тут я упёрся в стену: по всем логам всё отправляется правильно, а на экране — пустота. Причём не проверено потом с чужого ракурса (другой игрок/бот/спектатор), может там уже и видно, но руки пока не дошли, а переделывать всё вслепую больше не хочется.

Вопросы к знающим людям:

  1. Может, TE_BEAMPOINTS вообще не лучший выбор для таких вещей, и все нормальные люди используют что-то другое (TE_TRACER, кастомный TE_BEAMENTPOINT, что угодно ещё)?
  2. Может дело в MSG_PVS — вдруг для какого-то из зрителей точка старта луча не входит в его PVS, и сообщение до него физически не долетает, хотя логика с координатами верна?
  3. Спрайт sprites/laserbeam.spr — стандартный ресурс CS 1.6, но может у части клиентов он не докачан/не резолвится (fastdl молчит об этом, естественно)? Как это вообще дебажить со стороны клиента?
  4. Кто делал рабочие трассеры под ReHLDS/AMXX — поделитесь, пожалуйста, каким методом рисовали луч, не через TE_BEAMPOINTS ли какие подводные камни словили.

Сам код системы скинов не выкладываю (не моё, чужая интеллектуальная собственность плюс работает система за пределами вот этого конкретного вопроса), но кусок логики отрисовки самого трассера — вот он выше целиком, дело явно не в скинах, а в самом рисовании луча.

Заранее спасибо всем, кто дочитал до конца и тем более кто ответит по делу.
 
  1. Кто делал рабочие трассеры под ReHLDS/AMXX — поделитесь, пожалуйста, каким методом рисовали луч, не через TE_BEAMPOINTS ли какие подводные камни словили.
В weapons pro я делал трассера, если тебе надо, то скину, ибо нет смысла голову ломать, мои плагин с wss работает.
 
Shkrest, Со стандартным оружием всё работает.
Дебаж логику условий на "а точно ли на этом оружии активен скин".

Можно ещё использовать другую мессагу, она позволит выпускать луч прямо из ствола оружия.
Но в таком случае луч будет привязан к нему. Может быть визуальный "шум". См. видео.

[
Код:
/**
    История изменений:
        1.0.0 (10.09.2026) by b0t.
            - Test;
*/
new const VERSION[] = "1.0.0"

#include <amxmodx>
#include <hamsandwich>
#include <reapi>
#include <fakemeta>
#include <xs>

new const iColor[] = {
    0,255,0
};

new
    g_iSpritePrecacheId,
    bool:g_bTraceMethod;

public plugin_precache() {
    g_iSpritePrecacheId = precache_model("sprites/shockwave.spr");
}

public plugin_init() {
    register_plugin("Trace Bullet",VERSION,"b0t.");

    g_bTraceMethod = false;
    register_clcmd("tr_method","ClCmd_ToggleTraceMethod");

    RegisterHam(Ham_Weapon_PrimaryAttack,"weapon_ak47","Weapon_PrimaryAttack_Post",false);
}

public ClCmd_ToggleTraceMethod(const id) {
    g_bTraceMethod ^= true;

    client_print(id,print_chat,"Trace Method: %i",g_bTraceMethod);

    return PLUGIN_HANDLED;
}

public Weapon_PrimaryAttack_Post(const eWeapon) {
    if(is_nullent(eWeapon)) {
        return HAM_IGNORED;
    }

    new id = get_member(eWeapon,m_pPlayer);

    if(!is_user_alive(id)) {
        return HAM_IGNORED;
    }

    switch(g_bTraceMethod) {
        case false: {
            CreateMsg_BEAMPOINTS(id);
        }
        case true: {
            CreateMsg_BEAMENTPOINT(id,iColor);
        }
    }

    return HAM_IGNORED;
}

stock CreateMsg_BEAMENTPOINT(const id,const iColor[]) {
    #define WEAPON_BIT  0x1000

    new Float:fVecStart[3],Float:fVecEnd[3];
    GetVec(id,fVecStart,fVecEnd);

    message_begin_f(MSG_BROADCAST,SVC_TEMPENTITY); {
        write_byte(TE_BEAMENTPOINT);
        write_short(id | WEAPON_BIT);
        write_coord_f(fVecEnd[0]);
        write_coord_f(fVecEnd[1]);
        write_coord_f(fVecEnd[2]);
        write_short(g_iSpritePrecacheId);
        write_byte(1);
        write_byte(5);
        write_byte(1);      //life
        write_byte(2);
        write_byte(0);
        write_byte(iColor[0]);
        write_byte(iColor[1]);
        write_byte(iColor[2]);
        write_byte(255);
        write_byte(0);
    }
    message_end();

    #undef WEAPON_BIT
}

stock CreateMsg_BEAMPOINTS(const pPlayer) {
    new Float:vecStart[3],Float:vecEnd[3];
    GetVec(pPlayer,vecStart,vecEnd);

    message_begin_f(MSG_PVS,SVC_TEMPENTITY,vecStart); {
        write_byte(TE_BEAMPOINTS);
        write_coord_f(vecStart[0]);
        write_coord_f(vecStart[1]);
        write_coord_f(vecStart[2]);
        write_coord_f(vecEnd[0]);
        write_coord_f(vecEnd[1]);
        write_coord_f(vecEnd[2]);
        write_short(g_iSpritePrecacheId);
        write_byte(0);   // startframe
        write_byte(0);   // framerate
        write_byte(2);   // life, 0.1s
        write_byte(2); // width, 0.1 юнита
        write_byte(0);   // noise
        write_byte(255); // r
        write_byte(180); // g
        write_byte(60);  // b
        write_byte(200); // brightness
        write_byte(0);   // speed
    }
    message_end();
}

stock GetVec(const id,Float:fVecStart[3],Float:fVecEnd[3]) {
    new Float:vecOrigin[3], Float:vecViewOfs[3], Float:vecAngles[3]
    get_entvar(id,var_origin,vecOrigin)
    get_entvar(id,var_view_ofs,vecViewOfs)
    get_entvar(id,var_v_angle,vecAngles)

    new Float:vecStart[3];
    xs_vec_add(vecOrigin,vecViewOfs,vecStart);
    xs_vec_copy(vecStart,fVecStart);
    
    new Float:vecDir[3]
    angle_vector(vecAngles, ANGLEVECTOR_FORWARD, vecDir)

    new Float:vecEnd[3]
    vecEnd[0] = vecStart[0] + vecDir[0] * 8192.0
    vecEnd[1] = vecStart[1] + vecDir[1] * 8192.0
    vecEnd[2] = vecStart[2] + vecDir[2] * 8192.0

    new tr = create_tr2()
    engfunc(EngFunc_TraceLine, vecStart, vecEnd, IGNORE_MONSTERS, id, tr)
    get_tr2(tr, TR_vecEndPos, vecEnd)
    free_tr2(tr)

    xs_vec_copy(vecEnd,fVecEnd);
}


 
Видео рутуб, это отдельный вид мазохизма. Большое спасибо. Выглядит круто. Попробую. Придется Клаудить.
 
  • Смешно
Реакции: b0t.
Назад
Верх