Перевод: DJ_WEST
Данная статья больше всего посвящена скриптерам, которые умеют работать с векторами. Но я постараюсь описать все более подробно, чтобы данную статью смог понять каждый человек. Это очень эффективный метод, не считая того, что есть ситуации, когда вы не видите игрока. Но это не будет проблемой. Вы можете использовать данный метод для своего мода, все что вам требуется немного изменить код.
1. Константы
Эти константы используются, как константы умножения.Они применяются при создании некоторых массивов, которые используются для вычисления, если игрок будет видимым.
- Код: Выделить всё
#define GENERAL_X_Y_SORROUNDING 18.5 // 16.0
#define CONSTANT_Z_CROUCH_UP 31.25 // 32.0
#define CONSTANT_Z_CROUCH_DOWN 17.5 // 16.0
#define CONSTANT_Z_STANDUP_UP 34.0 // 36.0
#define CONSTANT_Z_STANDUP_DOWN 35.25 // 36.0
#define GENERAL_X_Y_SORROUNDING_HALF 9.25 // 8.0
#define GENERAL_X_Y_SORROUNDING_HALF2 12.0 // 8.0
#define CONSTANT_Z_CROUCH_UP_HALF 15.5 // 16.0
#define CONSTANT_Z_CROUCH_DOWN_HALF 8.75 // 8.0
#define CONSTANT_Z_STANDUP_UP_HALF 17.0 // 18.0
#define CONSTANT_Z_STANDUP_DOWN_HALF 17.5 // 18.0
#define ANGLE_COS_HEIGHT_CHECK 0.7071 // cos(45 градусов)
2. Массивы
Теперь нам нужны некоторые массивы для вычислений.
- Код: Выделить всё
// Используется для определения головной точки оружия
new const Float:weapon_edge_point[CSW_P90+1] =
{
0.00, 35.5, 0.00, 42.0, 0.00, 35.5, 0.00, 37.0, 37.0, 0.00, 35.5, 35.5, 32.0, 41.0, 32.0, 36.0, 41.0, 35.5, 41.0, 32.0, 37.0, 35.5, 42.0, 41.0, 44.0, 0.00, 35.5, 37.0, 32.0, 0.00, 32.0
}
// Используется в качестве массива для умножения
new const Float:vec_multi_lateral[] =
{
GENERAL_X_Y_SORROUNDING,
-GENERAL_X_Y_SORROUNDING,
GENERAL_X_Y_SORROUNDING_HALF2,
-GENERAL_X_Y_SORROUNDING_HALF
}
// Используется в качестве высоты, если игрок приседает
new const Float:vec_add_height_crouch[] =
{
CONSTANT_Z_CROUCH_UP,
-CONSTANT_Z_CROUCH_DOWN,
CONSTANT_Z_CROUCH_UP_HALF,
-CONSTANT_Z_CROUCH_DOWN_HALF
}
// Используется в качестве высоты, если игрок встает
new const Float:vec_add_height_standup[] =
{
CONSTANT_Z_STANDUP_UP,
-CONSTANT_Z_STANDUP_DOWN,
CONSTANT_Z_STANDUP_UP_HALF,
-CONSTANT_Z_STANDUP_DOWN_HALF
}
3. Необходимые переменные
Нам нужно прослеживать путь (точки видимости) для сохранения результатов:
- Код: Выделить всё
new thld
public plugin_init()
{
// Мы создаем его в plugin_init
thdl = create_tr2()
}
public plugin_end()
{
// Мы удаляем его в plugin_end
free_tr2(thdl)
}
Нам также необходим список для хранения типов объектов, если наш объект прозрачен или нет. Это необходимо при частой проверки видимости игрока.
- Код: Выделить всё
// Данная бит-сумма позволяет хранить до 2048 объектов.
new bs_array_transp[64] // Бит-сумма, которая равняется 64*32
new bs_array_solid[64] // Бит-сумма, которая равняется 64*32
Для удобства работы с данными бит-суммами были сделаны следующие макросы:
- Код: Выделить всё
#define add_transparent_ent(%1) bs_array_transp[((%1 - 1) / 32)] |= (1<<((%1 - 1) % 32))
#define del_transparent_ent(%1) bs_array_transp[((%1 - 1) / 32)] &= ~(1<<((%1 - 1) % 32))
#define is_transparent_ent(%1) (bs_array_transp[((%1 - 1) / 32)] & (1<<((%1 - 1) % 32)))
#define add_solid_ent(%1) bs_array_solid[((%1 - 1) / 32)] |= (1<<((%1 - 1) % 32))
#define del_solid_ent(%1) bs_array_solid[((%1 - 1) / 32)] &= ~(1<<((%1 - 1) % 32))
#define is_solid_ent(%1) (bs_array_solid[((%1 - 1) / 32)] & (1<<((%1 - 1) % 32)))
4. Необходимые функции
Трассировочные функции:
- Код: Выделить всё
// Проверяет является ли точка видимой от начало до нее. Игнорируются стекло, игроки и ignore_ent.
bool:is_point_visible(const Float:start[3], const Float:point[3], ignore_ent)
{
engfunc(EngFunc_TraceLine, start, point, IGNORE_GLASS | IGNORE_MONSTERS, ignore_ent, thdl)
static Float:fraction
get_tr2(thdl, TR_flFraction, fraction)
// Возвращение результата
// Если 1, значит мы ничего не коснулись
return (fraction == 1.0)
}
// Тоже самое, что и верхняя функция, только проверяем объект, который мы коснулись и через который можно видеть
bool:is_point_visible_texture(const Float:start[3], const Float:point[3], ignore_ent)
{
engfunc(EngFunc_TraceLine, start, point, IGNORE_GLASS | IGNORE_MONSTERS, ignore_ent, thdl)
// Сохраняем объект
static ent
ent = get_tr2(thdl, TR_pHit)
static Float:fraction
get_tr2(thdl, TR_flFraction, fraction)
// Если мы ударили что-то, то начать проверку
if (fraction != 1.0 && ent > 0)
{
// Это означает, что мы не знаем, чего мы коснулись
if (!is_transparent_ent(ent) && !is_solid_ent(ent))
{
static texture_name[2]
static Float:vec[3]
// Эти вычисления сделаны в целях безопасности
// функция TraceTexture будет убивать сервер на маленький дистанциях
xs_vec_sub(point, start, vec)
xs_vec_mul_scalar(vec, (5000.0 / xs_vec_len(vec)), vec)
xs_vec_add(start, vec, vec)
engfunc(EngFunc_TraceTexture, ent, start, vec, texture_name, charsmax(texture_name))
// Если texture_name начинается с { - это означает, что у нас прозрачная текстура,
// если да, то мы делаем перетрассировку и добавляем данный объект, как прозрачный.
// Если нет, то мы добавляем объект, как непрозрачный и следовательно нам не нужно его снова проверять.
if (equal(texture_name, "{"))
{
add_transparent_ent(ent)
ignore_ent = ent
engfunc(EngFunc_TraceLine, start, point, IGNORE_GLASS | IGNORE_MONSTERS, ignore_ent, thdl)
get_tr2(thdl, TR_flFraction, fraction)
return (fraction == 1.0)
}
else
{
add_solid_ent(ent)
return (fraction == 1.0)
}
}
// Это означает, что объект или прозрачный, или нет
else
{
if (is_solid_ent(ent))
{
return (fraction == 1.0)
}
else
{
ignore_ent = ent
engfunc(EngFunc_TraceLine, start, point, IGNORE_GLASS | IGNORE_MONSTERS, ignore_ent, thdl)
get_tr2(thdl, TR_flFraction, fraction)
return (fraction == 1.0)
}
}
}
return (fraction == 1.0)
}
Векторные функции [взято из xs.inc]:
- Код: Выделить всё
// Сложение двух векторов
stock xs_vec_add(const Float:in1[], const Float:in2[], Float:out[])
{
out[0] = in1[0] + in2[0];
out[1] = in1[1] + in2[1];
out[2] = in1[2] + in2[2];
}
// Вычитание двух векторов
stock xs_vec_sub(const Float:in1[], const Float:in2[], Float:out[])
{
out[0] = in1[0] - in2[0];
out[1] = in1[1] - in2[1];
out[2] = in1[2] - in2[2];
}
// Умножение вектора на скалярную величину
stock xs_vec_mul_scalar(const Float:vec[], Float:scalar, Float:out[])
{
out[0] = vec[0] * scalar;
out[1] = vec[1] * scalar;
out[2] = vec[2] * scalar;
}
// Возвращает длину вектора
stock Float:xs_vec_len(const Float:vec[3])
{
return floatsqroot(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]);
}
// Умножение двух векторов
stock Float:xs_vec_dot(const Float:vec[], const Float:vec2[])
{
return (vec[0]*vec2[0] + vec[1]*vec2[1] + vec[2]*vec2[2])
}
// Проверка векторов на равность
bool:xs_vec_equal(const Float:vec1[], const Float:vec2[])
{
return (vec1[0] == vec2[0]) && (vec1[1] == vec2[1]) && (vec1[2] == vec2[2]);
}
5. Известные прозрачные объекты (через которые можно видеть)
- Код: Выделить всё
public plugin_precache()
{
register_forward(FM_Spawn, "fw_spawn", 1)
}
public fw_spawn(ent)
{
if (!pev_valid(ent))
return FMRES_IGNORED
static rendermode, Float:renderamt
rendermode = pev(ent, pev_rendermode)
pev(ent, pev_renderamt, renderamt)
// Если объект прозрачный, добавить в список
if (((rendermode == kRenderTransColor || rendermode == kRenderGlow || rendermode == kRenderTransTexture) && renderamt < 255.0) || (rendermode == kRenderTransAdd))
{
add_transparent_ent(ent)
return FMRES_IGNORED
}
return FMRES_IGNORED
}
6. Создание функции видимости игрока
Теперь имея константы, массивы, переменные и функции, мы можем создать функцию, которая будет определять виден ли нам игрок.
- Код: Выделить всё
stock is_player_visible(visor, target)
{
// Объявления
static Float:origin[3], Float:start[3], Float:end[3], Float:addict[3], Float:plane_vec[3], Float:normal[3], ignore_ent
// Игнорирумый объект - visor. В данном случае используем его в качестве теста.
ignore_ent = visor
// Проверяем находится ли игрок позади объекта visor
// Получаем координаты visor
pev(visor, pev_origin, origin)
// Получаем угол наклона visor
pev(visor, pev_v_angle, normal)
// Конвертируем вектор
angle_vector(normal, ANGLEVECTOR_FORWARD, normal)
// Получаем коодиранты цели
pev(target, pev_origin, end)
// Делаем вычитание векторов дял получения одного вектора
xs_vec_sub(end, origin, plane_vec)
// Получаем реальные данные между двумя векторами
xs_vec_mul_scalar(plane_vec, (1.0/xs_vec_len(plane_vec)), plane_vec)
// После умножение двух векторов их длина равняется 1
// Вычисляем косинус угла между ними, если он будет отрицательным - значит игрок находится позади, возвращаем false
if (xs_vec_dot(plane_vec, normal) < 0)
{
return false
}
// Получаем смещение обзора visor и добавляем их к координатам для получения координат глаз
pev(visor, pev_view_ofs, start)
xs_vec_add(start, origin, start)
// Начало становится концом
origin = end
// Теперь у нас есть 2 важных вектора:
// start - координаты глаз объекта visor
// origin - коодинаты цели
// Это используется один раз для обновления ignore_ent
// Если точка видна, то возвращаем true
if (is_point_visible_texture(start, origin, ignore_ent))
return true
// Получаем смещение обзора цели и добавляем их к координатам для получения координат глаз
pev(target, pev_view_ofs, end)
xs_vec_add(end, origin, end)
// Если точка видна, возвращаем true
if (is_point_visible(start, end, ignore_ent))
return true
// Проверяем точку оружия. Сначала проверяем, чтобы она не была равна нулю
if (weapon_edge_point[get_user_weapon(target)] != 0.00)
{
// Получаем угол обзора и конвектируем в вектор
pev(target, pev_v_angle, addict)
angle_vector(addict, ANGLEVECTOR_FORWARD, addict)
// Перемножаем их для получения головной точки оружия
xs_vec_mul_scalar(addict, weapon_edge_point[get_user_weapon(target)], addict)
// Добавляем их в конец координат для получения головной точки
xs_vec_add(end, addict, end)
// Если точка оружия видна, то возвращаем true
if (is_point_visible(start, end, ignore_ent))
return true
}
// Делаем вычитание
xs_vec_sub(start, origin, normal)
// У нас есть вектор:
// normal - вектор между начальным вектором и координатами цели
// Делаем проверку плоскости
// Данные функции создадут плоскость, которая будет крутиться на основании позиции игрока
// Проверять будем точки, которые сущестуют на плоскости
// Нормализуем normal вектор
// Это важно при проблемах с высотой
xs_vec_mul_scalar(normal, 1.0/(xs_vec_len(normal)), normal)
// Конвертируем в угловой вектор
vector_to_angle(normal, plane_vec)
// Создаем вектор, который перпендикулярен normal вектору
angle_vector(plane_vec, ANGLEVECTOR_RIGHT, plane_vec)
// Проверяем, находимся ли мы на том же уровне с целью
if (floatabs(normal[2]) <= ANGLE_COS_HEIGHT_CHECK)
{
// Проверяем, присели ли цель
// Создаем плоскость
if (pev(target, pev_flags) & FL_DUCKING)
{
for (new i=0;i<4;i++)
{
if (i<2)
{
for (new j=0;j<2;j++)
{
// Перемножаем вектора и добавляем к координатам для проверки видимости
// Тоже самое будет происходить и дальше
xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
addict[2] = vec_add_height_crouch[j]
xs_vec_add(origin, addict, end)
if (is_point_visible(start, end, ignore_ent))
return true
}
}
else
{
for (new j=2;j<4;j++)
{
xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
addict[2] = vec_add_height_crouch[j]
xs_vec_add(origin, addict, end)
if (is_point_visible(start, end, ignore_ent))
return true
}
}
}
}
else
{
for (new i=0;i<4;i++)
{
if (i<2)
{
for (new j=0;j<2;j++)
{
xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
addict[2] = vec_add_height_standup[j]
xs_vec_add(origin, addict, end)
if (is_point_visible(start, end, ignore_ent))
return true
}
}
else
{
for (new j=2;j<4;j++)
{
xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
addict[2] = vec_add_height_standup[j]
xs_vec_add(origin, addict, end)
if (is_point_visible(start, end, ignore_ent))
return true
}
}
}
}
}
else
{
// Единственное различие с предыдущим кодом - это использование normal вектора для перемещения точек вперед и назад
// Проверяем выше ли мы игрока
if (normal[2] > 0.0)
{
normal[2] = 0.0
xs_vec_mul_scalar(normal, 1/(xs_vec_len(normal)), normal)
if (pev(target, pev_flags) & FL_DUCKING)
{
for (new i=0;i<4;i++)
{
if (i<2)
{
for (new j=0;j<2;j++)
{
xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
addict[2] = vec_add_height_crouch[j]
xs_vec_add(origin, addict, end)
xs_vec_mul_scalar(normal, (j == 0) ? (-GENERAL_X_Y_SORROUNDING) : (GENERAL_X_Y_SORROUNDING), addict)
xs_vec_add(end, addict, end)
if (is_point_visible(start, end, ignore_ent))
return true
}
}
else
{
for (new j=2;j<4;j++)
{
xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
addict[2] = vec_add_height_crouch[j]
xs_vec_add(origin, addict, end)
if (is_point_visible(start, end, ignore_ent))
return true
}
}
}
}
else
{
for (new i=0;i<4;i++)
{
if (i<2)
{
for (new j=0;j<2;j++)
{
xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
addict[2] = vec_add_height_standup[j]
xs_vec_add(origin, addict, end)
xs_vec_mul_scalar(normal, (j == 0) ? (-GENERAL_X_Y_SORROUNDING) : (GENERAL_X_Y_SORROUNDING), addict)
xs_vec_add(end, addict, end)
if (is_point_visible(start, end, ignore_ent))
return true
}
}
else
{
for (new j=2;j<4;j++)
{
xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
addict[2] = vec_add_height_standup[j]
xs_vec_add(origin, addict, end)
if (is_point_visible(start, end, ignore_ent))
return true
}
}
}
}
}
else
{
normal[2] = 0.0
xs_vec_mul_scalar(normal, 1/(xs_vec_len(normal)), normal)
if (pev(target, pev_flags) & FL_DUCKING)
{
for (new i=0;i<4;i++)
{
if (i<2)
{
for (new j=0;j<2;j++)
{
xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
addict[2] = vec_add_height_crouch[j]
xs_vec_add(origin, addict, end)
xs_vec_mul_scalar(normal, (j == 0) ? GENERAL_X_Y_SORROUNDING : (-GENERAL_X_Y_SORROUNDING), addict)
xs_vec_add(end, addict, end)
if (is_point_visible(start, end, ignore_ent))
return true
}
}
else
{
for (new j=2;j<4;j++)
{
xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
addict[2] = vec_add_height_crouch[j]
xs_vec_add(origin, addict, end)
if (is_point_visible(start, end, ignore_ent))
return true
}
}
}
}
else
{
for (new i=0;i<4;i++)
{
if (i<2)
{
for (new j=0;j<2;j++)
{
xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
addict[2] = vec_add_height_standup[j]
xs_vec_add(origin, addict, end)
xs_vec_mul_scalar(normal, (j == 0) ? GENERAL_X_Y_SORROUNDING : (-GENERAL_X_Y_SORROUNDING), addict)
xs_vec_add(end, addict, end)
if (is_point_visible(start, end, ignore_ent))
return true
}
}
else
{
for (new j=2;j<4;j++)
{
xs_vec_mul_scalar(plane_vec, vec_multi_lateral[i], addict)
addict[2] = vec_add_height_standup[j]
xs_vec_add(origin, addict, end)
if (is_point_visible(start, end, ignore_ent))
return true
}
}
}
}
}
}
// Цель не видна
return false
}
7. Как увидеть точки, которые мы тестируем
нам нужно добавить данный код в плагин:
- Код: Выделить всё
stock spr_bomb
public plugin_precache()
{
spr_bomb = precache_model("sprites/ledglow.spr")
}
stock bomb_led(const Float:point[3])
{
message_begin(MSG_BROADCAST, SVC_TEMPENTITY)
write_byte(TE_GLOWSPRITE)
engfunc(EngFunc_WriteCoord, point[0])
engfunc(EngFunc_WriteCoord, point[1])
engfunc(EngFunc_WriteCoord, point[2])
write_short(spr_bomb)
write_byte(1)
write_byte(3)
write_byte(255)
message_end()
}
И изменить функции "is_point_visible", "is_point_visible_texture" на:
- Код: Выделить всё
bool:is_point_visible(const Float:start[3], const Float:point[3], ignore_ent)
{
bomb_led(point)
return false
}
bool:is_point_visible_texture(const Float:start[3], const Float:point[3], ignore_ent)
{
bomb_led(point)
return false
}