Склад полезного кода [GoldSrc]

C-подобный:
#define MAX_STUDIO_BONES  128 // из goldsrc

/**
 * Получает хитбоксы у субмодели.
 *
 * @param szModel  Путь до модели (Пр. "models/bag.mdl")
 * @param iSub     Номер субмодели [0;iNum)
 * @param flMaxs   Local BB max.
 * @param flMins   Local BB min.
 * @param flSize   max - min
 *
 * @note Поддержка составных моделей (игроков)  нет (?). Получает хитбоксы субмодели
 *       предметов, оружия и т.д., объеденённых в 1 файл.
 *
 * @return         true/false;
 */

stock bool:UTIL_GetHitBoxSubModel(const szModel[], const iSub, Float: flMaxs[3], Float: flMins[3], Float: flSize[3])
{
    new hFile = fopen(szModel, "rb");

    if (!hFile)
    {
        server_print("Can`t open %s", szModel);
        return false;
    }

    new iBlock, iCount, index = -1;

    fseek(hFile, 204, SEEK_SET);
    fread(hFile, iCount, BLOCK_INT);
    fread(hFile, iBlock, BLOCK_INT);
    fseek(hFile, iBlock, SEEK_SET);

    for (new i = 0, iSubs, iTotal; i < iCount; i++)
    {
        fseek(hFile, 64, SEEK_CUR);
        fread(hFile, iSubs, BLOCK_INT);
        fseek(hFile, 4, SEEK_CUR);
        fread(hFile, iBlock, BLOCK_INT);

        if (iSub < iSubs + iTotal)
        {
            index = iSub - iTotal;
            break;
        }
       
        iTotal += iSubs;
    }

    if (index < 0)
    {
        server_print("Submodel index %i not found", iSub);
        fclose(hFile);
        return false;
    }

    fseek(hFile, iBlock, SEEK_SET);
    fseek(hFile, 112 * index, SEEK_CUR);
    fseek(hFile, 80, SEEK_CUR);
   
    fread(hFile, iCount, BLOCK_INT);
    fread(hFile, iBlock, BLOCK_INT);

    if (iCount < 1)
    {
        server_print("No vertices in submodel %i", iSub);
        fclose(hFile);
        return false;
    }

    fseek(hFile, iBlock, SEEK_SET);
    new bool:bBones[MAX_STUDIO_BONES];

    for (new i = 0, bone; i < iCount; i++)
    {
        fread(hFile, bone, BLOCK_BYTE);
        if (bone < MAX_STUDIO_BONES)
        {
            bBones[bone] = true;
        }
    }

   
    fseek(hFile, 156, SEEK_SET);
    fread(hFile, iCount, BLOCK_INT);
    fread(hFile, iBlock, BLOCK_INT);
    fseek(hFile, iBlock, SEEK_SET);

    for (new i = 0, bone; i < iCount; i++)
    {
        fread(hFile, bone, BLOCK_INT);

        if (bone >= 0 && bone < MAX_STUDIO_BONES && bBones[bone])
        {
            fseek(hFile, 4, BLOCK_INT);
            fread_blocks(hFile, _:flMins, 3, BLOCK_INT);
            fread_blocks(hFile, _:flMaxs, 3, BLOCK_INT);
            flSize[0] = flMaxs[0] - flMins[0];
            flSize[1] = flMaxs[1] - flMins[1];
            flSize[2] = flMaxs[2] - flMins[2];

            fclose(hFile);
            return true;
        }

        fseek(hFile, 28, SEEK_CUR);
    }

    fclose(hFile);
    return false;
}
Вывод парсинга модели из https://hlds.run/resources/100/
C-подобный:
public plugin_init()
{
    new szModel[] = "models/weapon/noob/wmg_default_v2.mdl";
    new iNum = UTIL_GetCountSubModels(szModel);
    new szName[64];

    new Float: flMaxs[3], Float: flMins[3], Float: flSize[3];
    for (new i; i < iNum; i++)
    {
        UTIL_GetHitBoxSubModel(szModel, i, flMaxs, flMins, flSize);
        UTIL_GetNameSubModel(szModel, i, szName, 64);
        server_print("#%i | %s | Maxs: {%.2f, %.2f, %.2f} | Mins: {%.2f, %.2f, %.2f} | Size: {%.2f, %.2f, %.2f}", i, szName, flMaxs[0], flMaxs[1], flMaxs[2],
            flMins[0], flMins[1], flMins[2], flSize[0], flSize[1], flSize[2]);
    }
  
    return PLUGIN_HANDLED;
}
C-подобный:
#0 | blank | Maxs: {0.00, 0.00, 0.00} | Mins: {0.00, 0.00, 0.00} | Size: {0.00, 0.00, 0.00}
#1 | w_glock18 | Maxs: {-3.39, 1.65, 3.67} | Mins: {0.00, -1.65, -3.67} | Size: {-3.39, 3.31, 7.34}
#2 | w_usp | Maxs: {-6.04, 2.68, 5.15} | Mins: {0.00, -2.68, -5.15} | Size: {-6.04, 5.36, 10.31}
#3 | w_deagle | Maxs: {-4.28, 1.78, 3.75} | Mins: {0.00, -1.78, -3.75} | Size: {-4.28, 3.57, 7.51}
#4 | w_elite | Maxs: {0.00, 9.02, 4.55} | Mins: {0.00, -3.59, -7.05} | Size: {0.00, 12.62, 11.60}
#5 | w_p228 | Maxs: {-3.56, 1.67, 3.72} | Mins: {0.00, -1.67, -3.72} | Size: {-3.56, 3.35, 7.45}
#6 | w_fiveseven | Maxs: {-3.56, 1.67, 3.72} | Mins: {0.00, -1.67, -3.72} | Size: {-3.56, 3.35, 7.45}
#7 | w_m3 | Maxs: {-5.21, 0.60, 22.61} | Mins: {0.00, -0.61, -12.97} | Size: {-5.21, 1.21, 35.59}
#8 | w_xm1014 | Maxs: {-6.28, 1.42, 19.79} | Mins: {0.00, -1.34, -24.26} | Size: {-6.28, 2.76, 44.05}
#9 | w_mac10 | Maxs: {-15.96, 1.54, 3.88} | Mins: {0.00, -1.01, -17.55} | Size: {-15.96, 2.56, 21.44}
#10 | w_tmp | Maxs: {-3.51, 0.67, 4.54} | Mins: {0.00, -0.67, -5.18} | Size: {-3.51, 1.34, 9.73}
#11 | w_mp5 | Maxs: {-0.16, 27.29, 3.90} | Mins: {0.00, -1.54, -10.43} | Size: {-0.16, 28.84, 14.34}
#12 | w_ump45 | Maxs: {-9.51, 1.13, 7.19} | Mins: {0.00, -1.13, -7.31} | Size: {-9.51, 2.27, 14.51}
#13 | w_p90 | Maxs: {-11.19, 1.47, 5.40} | Mins: {0.00, -1.47, -5.40} | Size: {-11.19, 2.94, 10.81}
#14 | w_famas | Maxs: {-5.67, 19.18, 1.01} | Mins: {0.00, -25.83, -1.02} | Size: {-5.67, 45.02, 2.04}
#15 | w_galil | Maxs: {0.00, 33.88, 3.67} | Mins: {0.00, -19.28, -9.94} | Size: {0.00, 53.16, 13.61}
#16 | w_m4a1 | Maxs: {-15.20, 1.04, 3.30} | Mins: {0.00, -1.17, -7.44} | Size: {-15.20, 2.22, 10.75}
#17 | w_ak47 | Maxs: {-7.84, 1.26, 11.43} | Mins: {0.00, -1.16, -0.87} | Size: {-7.84, 2.43, 12.31}
#18 | w_aug | Maxs: {-0.25, 20.35, 6.80} | Mins: {0.00, -14.06, -5.09} | Size: {-0.25, 34.41, 11.90}
#19 | w_sg552 | Maxs: {-9.56, 1.52, 8.06} | Mins: {0.00, -1.52, -2.57} | Size: {-9.56, 3.05, 10.64}
#20 | w_awp | Maxs: {-19.93, 1.35, 4.59} | Mins: {0.00, -1.29, -3.97} | Size: {-19.93, 2.65, 8.56}
#21 | w_sg550 | Maxs: {-10.52, 1.00, 7.67} | Mins: {0.00, -1.00, -2.00} | Size: {-10.52, 2.00, 9.67}
#22 | w_g3sg1 | Maxs: {-20.85, 1.33, 5.40} | Mins: {0.00, -1.32, -5.40} | Size: {-20.85, 2.65, 10.81}
#23 | w_scout | Maxs: {-20.53, 1.30, 3.99} | Mins: {0.00, -1.30, -4.00} | Size: {-20.53, 2.61, 7.99}
#24 | w_m249 | Maxs: {-1.00, 24.25, 5.01} | Mins: {0.00, -10.26, -6.13} | Size: {-1.00, 34.51, 11.14}
#25 | w_hegrenade | Maxs: {-4.61, 4.67, 4.05} | Mins: {0.00, -4.67, -4.03} | Size: {-4.61, 9.34, 8.08}
#26 | w_flashbang | Maxs: {-4.61, 4.67, 4.05} | Mins: {0.00, -4.67, -4.03} | Size: {-4.61, 9.34, 8.08}
#27 | w_smokegrenade | Maxs: {-4.61, 4.67, 4.05} | Mins: {0.00, -4.67, -4.03} | Size: {-4.61, 9.34, 8.08}
#28 | w_shield | Maxs: {-31.13, 15.51, 2.96} | Mins: {0.00, -15.52, -2.97} | Size: {-31.13, 31.03, 5.93}
#29 | w_kevlar | Maxs: {0.00, 14.19, 17.23} | Mins: {0.00, -13.52, -15.97} | Size: {0.00, 27.71, 33.20}
#30 | w_c4_backpack | Maxs: {-6.84, 5.21, 2.94} | Mins: {0.00, -5.21, -2.94} | Size: {-6.84, 10.43, 5.88}
#31 | w_c4 | Maxs: {-2.56, 2.79, 6.30} | Mins: {0.00, -3.60, -7.28} | Size: {-2.56, 6.40, 13.59}
#32 | w_thighpack | Maxs: {-3.65, 10.43, 6.80} | Mins: {0.00, -9.25, -4.63} | Size: {-3.65, 19.68, 11.43}
 
Последнее редактирование:
Время разговора в микрофон (именно разговора, а не использования микрофона)

Способ с использованием Voice Utils + Revoice Plus

C-подобный:
#include <amxmodx>
#include <voiceutils>

new Float:g_PlayerTalkTime[MAX_PLAYERS + 1], Float:g_TimePressedTalkKey[MAX_PLAYERS + 1];

public plugin_init()
{
    register_plugin("Test Speak", "0.0.1", "Albertio");
}

public VU_OnStartSpeak(const index)
{
    g_TimePressedTalkKey[index] = get_gametime();
}

public VU_OnStopSpeak(const index)
{
    if(g_TimePressedTalkKey[index])
    {
        g_PlayerTalkTime[index] += (get_gametime() - g_TimePressedTalkKey[index]);
        g_TimePressedTalkKey[index] = 0.0;
        //client_print(index, print_chat, "%.2f", g_PlayerTalkTime[index]);
    }
}

Способ с использованием VTC + REAPI

C-подобный:
#include <amxmodx>
#include <reapi>

new Float:g_PlayerTalkTime[MAX_PLAYERS + 1], Float:g_TimePressedTalkKey[MAX_PLAYERS + 1];

public plugin_init()
{
    register_plugin("Test Speak", "0.0.1", "Albertio");
}

public VTC_OnClientStartSpeak(const index)
{
    g_TimePressedTalkKey[index] = get_gametime();
}

public VTC_OnClientStopSpeak(const index)
{
    if(g_TimePressedTalkKey[index])
    {
        g_PlayerTalkTime[index] += (get_gametime() - g_TimePressedTalkKey[index]);
        g_TimePressedTalkKey[index] = 0.0;
        //client_print(index, print_chat, "%.2f", g_PlayerTalkTime[index]);
    }
}
Сообщение автоматически объединено:

Возвращает склоненное слово "секунда" в зависимости от цифры. Может потребоваться для таймеров или подобных вещей.

C-подобный:
stock time_case(time)
{
    new szString[16];

    if(time % 10 == 1 && time % 10 != 11)
    {
        formatex(szString, charsmax(szString), "секунда");
        return szString;
    }
    else if(time % 10 >= 2 && time % 10 <= 4)
    {
        formatex(szString, charsmax(szString), "секунды");
        return szString;
    }

    formatex(szString, charsmax(szString), "секунд");
    return szString;
}
Сообщение автоматически объединено:

1. Удаление комментариев из строки (парсинг конфигов)
Автор: malniata
Описание: Этот код удаляет комментарии после символов # или ;. Идеально подходит для чтения конфигурационных файлов (например, whitelist.ini), где строки могут содержать пояснения. Также он корректно обрабатывает кавычки ("), игнорируя символы комментариев внутри строк.

C++:
// Символы, которые считаются началом комментария
new const COMMENTS[] =   {'#', ';'};
new const QUOTE = '^"';

stock delete_comment(szString[])    {
    new len = strlen(szString);
    new bool: bInQuotes;

    for (new i; i < len; i++)   {
        // Отслеживаем, внутри ли мы кавычек
        if (szString[i] == QUOTE)    {
            bInQuotes = !bInQuotes;
        }

        // Если внутри кавычек — пропускаем проверку на комментарии
        if (bInQuotes)  {
            continue;
        }

        // Ищем символ комментария
        for (new c; c < sizeof COMMENTS; c++)   {
            if (szString[i] == COMMENTS[c]) {
                szString[i] = EOS; // Обрезаем строку здесь
                break;
            }
        }
    }
}

2. Мигание денег (BlinkAcct)
Автор: Albertio
Описание: Вызывает мигание счетчика денег у игрока (эффект, как при попытке купить дорогую вещь). Полезно для кастомных магазинов или экономики.

C++:
// id - индекс игрока
// blinkQty - количество миганий (по умолчанию 1)
stock blink_money(const id, const blinkQty = 1)
{
    // 104 = get_user_msgid("BlinkAcct")
    message_begin(MSG_ONE, 104, .player = id);
    write_byte(blinkQty);
    message_end();
}

### 3. Универсальные кулдауны (Cooldown System)
Автор: AnonymousAmx
Описание: Очень удобная система "отката" действий. Позволяет легко задать задержку для любой функции (например, сохранение данных, использование способности, отправка сообщений). Просто вызываете isUserCooldown — и он сам решает, прошел ли срок.

C++:
// Список типов кулдаунов
enum eData_CoolDown {
    Cooldown_SaveLink = 0,
    Cooldown_Storage,
    Cooldown_UseSpell // Можно добавлять свои
};
new Float:g_fReloadCooldown[MAX_PLAYERS + 1][eData_CoolDown];

// Функция проверки/установки кулдауна
stock isUserCooldown(pId, eData_CoolDown:iTask, Float:fTime = 0.1) {
    // Если время еще не вышло — возвращаем false
    if(g_fReloadCooldown[pId][iTask] > get_gametime())
        return false;
   
    // Иначе устанавливаем новое время и возвращаем true
    g_fReloadCooldown[pId][iTask] = get_gametime() + fTime;
    return true;
}

// Пример использования
public some_action(id) {
    if(isUserCooldown(id, Cooldown_UseSpell, 5.0)) {
        // Код активации способности (сработает только раз в 5 секунд)
        client_print(id, print_chat, "Вы использовали способность!");
    } else {
        client_print(id, print_chat, "Подождите еще 5 секунд!");
    }
}

4. Количество разрядов числа и работа с разрядами
Автор: Albertio
Описание: Три полезные математические функции:
  • math_numbs — возвращает длину числа (сколько цифр).
  • math_first — возвращает первые N цифр числа.
  • math_last — возвращает последние N цифр числа.

C++:
// 1. Узнать длину числа (345 -> 3)
stock math_numbs(num) {
    return floatround(floatlog(float(num)), floatround_floor) + 1;
}

// 2. Взять первые numeral цифр (345, 2 -> 34)
stock math_first(num, numeral = 1) {
    new numbs = floatround(floatlog(float(num)), floatround_floor) + 1;
    return num / floatround(floatpower(10.0, float(numbs) - float(numeral)));
}

3. Взять последние numeral цифр (345, 2 -> 45)
stock math_last(num, numeral = 1) {
    return num % floatround(floatpower(10.0, float(numeral)));
}


6. Извлечение всех цифр из строки
Автор: ArKaNeMaN / Albertio
Описание: Позволяет "вытянуть" только цифры из любой строки. Например, из строки "Ваш ID: 100500" получится строка "100500".

C++:
#define CharIsNumeric(%1) (%1 >= 0x30 && %1 <= 0x39)

stock ExtractDigitsFromString(const Str[], Out[], const OutLen) {
    new iCharNum = 0, i = 0;
   
    while(iCharNum <= OutLen && Str[i]) {
        if(CharIsNumeric(Str[i])) {
            Out[iCharNum] = Str[i];
            iCharNum++;
        }
        i++;
    }
    Out[iCharNum] = 0;
    return iCharNum;
}

### 7. Шаблон для создания мотивирующих таблиц (MOTD)
Автор: AnonymousAmx
Описание: Упрощает создание красивых HTML-таблиц в MOTD. Автоматически чередует цвета строк (полосатая таблица), что улучшает читаемость списков (топ игроков, список правил).

C++:
// Стиль (CSS) для таблицы
#define STATSX_SHELL_DESIGN8_STYLE_STAT "<meta charset=UTF-8><style>body{background:#242424;margin:20px;font-family:Tahoma}th{background:#2F3034;color:#BDB670;text-align:left} table{padding:4px;background:#4A4945;font-size:18px;color:#FFF}#c{background:#3B3C37}</style>"

new bool:g_bJumpPosition;

// Функция добавления строки с автопереключением цвета (#c)
stock addMotdLine(szBuffer[2048], &iLen, szTitleName[], szTitleInfo[], any:...) {
    new szInfo[1024];
    vformat(szInfo, charsmax(szInfo), szTitleInfo, 5);
 
    if(!g_bJumpPosition) {
        iLen += formatex(szBuffer[iLen], charsmax(szBuffer) - iLen, "<tr><th>%s:<td>%s</tr>", szTitleName, szInfo);
    } else {
        iLen += formatex(szBuffer[iLen], charsmax(szBuffer) - iLen, "<tr id=c><th>%s:<td>%s<tr>", szTitleName, szInfo);
    }
    g_bJumpPosition = !g_bJumpPosition;
}

### 8. Псевдонимы для unix timestamp
Автор: AnonymousAmx
Описание: Преобразует время в секундах в читаемый формат (дни, часы, минуты). Позволяет гибко настраивать, какие единицы показывать (например, только "часы:минуты").

C++:
// Флаги для конфигурации
enum {
    ULD_Second = (1<<0),
    ULD_Minute = (1<<1),
    ULD_Hour   = (1<<2),
    ULD_Day    = (1<<3)
};

stock unixToLastData(bitType, iUnixTime) {
    new szTime[128], iLen;
    if(bitType & ULD_Day)   iLen += formatex(szTime[iLen], charsmax(szTime)-iLen, "%d д. ", (iUnixTime / 86400));
    if(bitType & ULD_Hour)  iLen += formatex(szTime[iLen], charsmax(szTime)-iLen, "%d ч. ", ((iUnixTime / 3600) % 24));
    if(bitType & ULD_Minute) iLen += formatex(szTime[iLen], charsmax(szTime)-iLen, "%d мин. ", ((iUnixTime / 60) % 60));
    if(bitType & ULD_Second) iLen += formatex(szTime[iLen], charsmax(szTime)-iLen, "%d сек. ", (iUnixTime % 60));
   
    szTime[iLen - 2] = '^0'; // Убираем последнюю запятую
    return szTime;
}

// Пример: unixToLastData(ULD_Hour|ULD_Minute, 3661) -> "1 ч. 1 мин."
Сообщение автоматически объединено:

1. Спавн оружейных ящиков (Armoury Entity)
Автор: d3m37r4
Описание: Позволяет динамически создать на карте ящик с оружием (как в базовой игре). Вы указываете тип оружия, количество патронов/гранат и координаты. Полезно для кастомных режимов (Gungame, Deathmatch).
Особенность: Код также определяет точку спавна — перед игроком (туда, куда он смотрит).

C++:
// Пример вызова: спавн АК-47 перед игроком
public cmdSpawnArmoury(id) {
    new Float:originEnd[3];
    // Функция вычисляет точку в 200 юнитах перед игроком
    getCoordPointInDirView(id, 200.0, originEnd);
   
    // Спавним ящик с АК-47 (ARMOURY_AK47), 1 запасом
    spawnArmouryEntity(ARMOURY_AK47, 1, originEnd);
}

// Основная функция спавна
spawnArmouryEntity(const ArmouryItemPack:armouryItem, const armouryCount, const Float:origin[3], bool:blockPickup = false) {
    new ent = rg_create_entity("armoury_entity"); // Создаем энтити
   
    if(is_nullent(ent)) {
        return NULLENT;
    }
  
    // Устанавливаем тип оружия и количество
    set_member(ent, m_Armoury_iItem, ArmouryItemPack:armouryItem);
    set_member(ent, m_Armoury_iCount, armouryCount);
   
    // Устанавливаем твердость (SOLID_TRIGGER — можно подобрать)
    set_entvar(ent, var_solid, blockPickup ? SOLID_NOT : SOLID_TRIGGER);

    engfunc(EngFunc_SetOrigin, ent, origin); // Ставим на нужное место
    dllfunc(DLLFunc_Spawn ,ent ); // Активируем

    return ent;
}

2. Расстояние до потолка (для прыжков или проверок)
Автор: Albertio
Описание: Возвращает расстояние от ног игрока до ближайшей твердой поверхности над ним (потолка, балки). Может использоваться для ограничения высоких прыжков или проверки, не уперся ли игрок головой в потолок.

C++:
stock Float:fm_distance_to_ceiling(index, ignoremonsters = 1) {
    new Float:start[3], Float:dest[3], Float:end[3];
    pev(index, pev_origin, start); // Берем позицию игрока
   
    // Пускаем луч строго вверх (z = 8191 — максимальная высота карты)
    dest[0] = start[0];
    dest[1] = start[1];
    dest[2] = 8191.0;

    engfunc(EngFunc_TraceLine, start, dest, ignoremonsters, index, 0);
    get_tr2(0, TR_vecEndPos, end); // Где луч уперся в потолок

    pev(index, pev_absmin, start); // Нижняя точка модели игрока (ноги)
    new Float:ret = start[2] - end[2]; // Разница между ногами и потолком

    return ret < 0 ? floatabs(ret) : 0.0;
}

3. Полное удаление заложников (Hostages)
Автор: MayroN
Описание: Два варианта кода для удаления заложников с карты.
  • Первый (через FM_Spawn): Удаляет ВСЕХ заложников на ВСЕХ картах (если активировать).
  • Второй (через Ham_Spawn): Удаляет заложников только на конкретной карте (например, cs_discounter).

Вариант 1 (Глобальное удаление):
C++:
public fwd_spawn( iEnt ) {
    // Список классов для удаления
    new szRemovingEntities[][] = {
        "hostage_entity",       // Сам заложник
        "func_hostage_rescue",  // Зона спасения
        "info_hostage_rescue"   // Точка спавна заложников
    }
   
    // Проверяем и удаляем
    for( new i = 0; i < sizeof szRemovingEntities; ++i ) {
        if( equal( szClassName, szRemovingEntities[ i ] ) ) {
            engfunc( EngFunc_RemoveEntity, iEnt )
            return FMRES_SUPERCEDE
        }
    }
    return FMRES_IGNORED
}

Вариант 2 (Только на нужной карте):
C++:
#define MAP "cs_discounter" // Название карты

public plugin_precache() {  
    new mapname[31];
    get_mapname(mapname, 31);
   
    if(!equali(mapname, MAP)) return; // Если карта не та — выходим
 
    // Регистрируем Ham_Spawn для заложников
    RegisterHam(Ham_Spawn, "hostage_entity", "Remove_Hostage", true);
}
 
public Remove_Hostage(Ent) {
    remove_entity(Ent); // Удаляем при спавне
}

4. Удаление брошенных щипцов (Cutters / Thighpack)
Автор: MayroN
Описание: Автоматически удаляет щипцы для резки решеток (из доп. оборудования) через заданное время после того, как игрок их выбросил. Полезно для оптимизации сервера (чтобы не копить мусор).

C++:
#define THIGHTPACK_TIME 10.0 // Удалять через 10 секунд

public plugin_init() {
    // Ловим момент, когда у модели устанавливается модель (предмет появился в мире)
    register_forward(FM_SetModel, "Thighpack_ItemModel");
    // Ловим "мышление" энтити, чтобы удалить
    RegisterHam(Ham_Think, "item_thighpack", "Remove_Thighpack");
}

public Thighpack_ItemModel(const iEntity) {
    // Проверяем, что это именно щипцы
    static szClassName[32];
    pev(iEntity, pev_classname, szClassName, charsmax(szClassName));

    if(equal(szClassName, "item_thighpack")) { 
        // Задаем время, через которое энтити "подумает"
        set_pev(iEntity, pev_nextthink, get_gametime() + THIGHTPACK_TIME); 
    }
    return FMRES_IGNORED;
}

public Remove_Thighpack(iEntity) {
    if (!pev_valid(iEntity)) return HAM_IGNORED;
   
    set_pev(iEntity, pev_flags, FL_KILLME); // Помечаем на удаление
    return HAM_SUPERCEDE;
}
Сообщение автоматически объединено:

1. Надежная идентификация объектов (Нумерация энтитей)
Автор: BalbuR
Описание: Это, пожалуй, самый ценный код в этой части темы. Прямое сохранение индекса энтити (iEnt) в массив — опасно, потому что после удаления объекта его индекс может быть передан новой энтити. Этот код решает проблему: он вешает уникальную метку (iuser4) на каждый объект и проверяет её при доступе.

Как это работает:
1. При создании объекта: Через RequestFrame (на следующем кадре) объекту присваивается уникальный номер g_iKeyCounter.
2. При сохранении в массив: Сохраняется не только индекс (iEnt), но и владелец (owner) и уникальная метка (iKey).
3. При проверке: Код сверяет, совпадает ли метка у объекта в массиве с реальной меткой объекта в игре. Если нет — объект изменился или удален, запись в массиве стирается.

C++:
#include <amxmodx>
#include <reapi>

new g_iKeyCounter = 1; // Глобальный счетчик

public plugin_init() {
    RegisterHookChain(RH_ED_Alloc, "fw_Edict_Alloc", true);
    RegisterHookChain(RH_ED_Free, "fw_Edict_Free", false);
}

// Выделение памяти под объект
public fw_Edict_Alloc() {
    new iEnt = GetHookChainReturn(ATYPE_INTEGER);
    // Нумеруем на следующем кадре, когда объект станет валидным
    RequestFrame("@fw_NextFrame_CreateEntity", iEnt);
}

// На следующем кадре сервера
@fw_NextFrame_CreateEntity(iEnt) {
    if (!is_nullent(iEnt)) {
        // Ставим уникальную метку в iuser4
        set_entvar(iEnt, var_iuser4, g_iKeyCounter);
        g_iKeyCounter++;
    }
}

// ------------------------------------------------------------------
// Пример использования: динамический массив для "шапок" игроков
// ------------------------------------------------------------------
enum aArrData {
    aArrData_pIndex, // Индекс энтити
    aArrData_pOwner, // Владелец
    aArrData_iKey    // Уникальная метка
}

new Array:g_aArrayData;

// Функция поиска шапки игрока с ПРОВЕРКОЙ валидности
stock FindUserHat(id) {
    for (new i, szData[aArrData], pIndex; i < ArraySize(g_aArrayData); i++) {
        ArrayGetArray(g_aArrayData, i, szData);
        pIndex = szData[aArrData_pIndex];

        // ***** КЛЮЧЕВАЯ ПРОВЕРКА *****
        // Если объект не валиден ИЛИ метка в игре не совпадает с сохраненной
        if (is_nullent(pIndex) ||
            get_entvar(pIndex, var_owner) != szData[aArrData_pOwner] ||
            szData[aArrData_iKey] != get_entvar(pIndex, var_iuser4)) {
           
            ArrayDeleteItem(g_aArrayData, i--); // Чистим мусор
            continue;
        }

        if (szData[aArrData_pOwner] == id) {
            return pIndex;
        }
    }
    return FM_NULLENT;
}


3. Математика: Поворот вектора с помощью матрицы
Автор: t3rkecorejz
Описание: Код нашел в исходниках Half-Life (Valve). Позволяет математически повернуть направление вектора (например, "вперед") в соответствии с углами обзора (Pitch, Yaw, Roll). Нужен для сложной физики (правильный выброс осколков, струй воды, прикрепленных объектов).

C++:
#define PITCH 0 // up/down
#define YAW   1 // left/right
#define ROLL  2 // fall over

// Преобразуем углы в матрицу 3x4
stock UTIL_AngleMatrix(const Float: angles[3], Float: matrix[3][4]) {
    new Float: angle;
    new Float: sr, sp, sy, cr, cp, cy;

    // Расчет синусов и косинусов
    angle = angles[YAW] * (M_PI * 2.0 / 360.0);
    sy = floatsin(angle);
    cy = floatcos(angle);
   
    angle = angles[PITCH] * (M_PI * 2.0 / 360.0);
    sp = floatsin(angle);
    cp = floatcos(angle);
   
    angle = angles[ROLL] * (M_PI * 2.0 / 360.0);
    sr = floatsin(angle);
    cr = floatcos(angle);

    // Заполняем матрицу
    matrix[0][0] = cp * cy;
    matrix[1][0] = cp * sy;
    matrix[2][0] = -sp;
    matrix[0][1] = sr * sp * cy + cr * -sy;
    matrix[1][1] = sr * sp * sy + cr * cy;
    matrix[2][1] = sr * cp;
    matrix[0][2] = (cr * sp * cy + -sr * -sy);
    matrix[1][2] = (cr * sp * sy + -sr * cy);
    matrix[2][2] = cr * cp;
}

// Поворачиваем вектор с помощью матрицы
stock UTIL_VectorRotate(const Float: in1[3], const Float: in2[3][4], Float: out[3]) {
    out[0] = xs_vec_dot(in1, in2[0]); // Скалярное произведение
    out[1] = xs_vec_dot(in1, in2[1]);
    out[2] = xs_vec_dot(in1, in2[2]);
}

4. Восстановление здоровья (и критика)
Автор: Albertio
Описание: Способ восстановить здоровье через перехват урона (RG_CBasePlayer_TakeDamage) без использования таймеров. Здоровье восстанавливается мгновенно после получения урона, "откатывая" его.

Важно: Другие участники (Xelson) раскритиковали этот метод, назвав его "костылем".
  • Минусы: Нет контроля над скоростью восстановления, потенциальные баги с разным типом урона (утопление, паралич).
  • Альтернатива (от SergeyShorokhov): Создание фейковой энтити trigger_hurt, которая наносит "отрицательный" урон.

C++:
// Пример "костыльного" восстановления (не рекомендуется для сложных систем)
public CBasePlayer_TakeDamage_Post(const iVictim, iInflictor, iAttacker, Float:flDamage, bitsDamageType) {
    if(bitsDamageType & DMG_DROWN) return HC_CONTINUE; // Не для утопления

    if(is_user_alive(iVictim)) {
        // "Возвращаем" здоровье
        set_member(iVictim, m_idrowndmg, get_member(iVictim, m_idrowndmg) - get_member(iVictim, m_idrownrestored) + floatround(flDamage));
        set_member(iVictim, m_idrownrestored, 0);
    }
    return HC_CONTINUE;
}
Сообщение автоматически объединено:

Автор кода: Vaqtincha
Код убирает всю отдачу (Recoil + Spray) для всех оружий.


C-подобный:
#include <amxmodx>
#include <fakemeta>
#include <hamsandwich>


const SECONDARY_WPN_BS = ((1<<CSW_P228)|(1<<CSW_ELITE)|(1<<CSW_FIVESEVEN)|(1<<CSW_USP)|(1<<CSW_GLOCK18)|(1<<CSW_DEAGLE))
const EXP_WPN_BS = ((1<<CSW_HEGRENADE)|(1<<CSW_SMOKEGRENADE)|(1<<CSW_FLASHBANG)|(1<<CSW_KNIFE)|(1<<CSW_C4))
const SNIPER_WPN_BS = ((1<<CSW_AWP)|(1<<CSW_SCOUT)|(1<<CSW_SG550)|(1<<CSW_G3SG1))

const m_pPlayer = 41
const m_iFOV = 363
const random_seed = 96
const m_flAccuracy = 62
const m_flLastFire = 63
const m_iShotsFired = 64
const m_iId = 43

const XO_WEAPON = 4

#define MAX_CLIENTS     32
new Float:g_vecPunchAngle[MAX_CLIENTS + 1][3], Float:g_vecVelocity[MAX_CLIENTS + 1][3]


public plugin_init()
{
    register_plugin("Patch Accuracy", "0.2", "Vaqtincha")

    for(new szWeaponName[17], iId = CSW_P228; iId <= CSW_P90; iId++)
    {
        if(!(EXP_WPN_BS & (1 << iId)) && get_weaponname(iId, szWeaponName, charsmax(szWeaponName)))
        {
            RegisterHam(Ham_Weapon_PrimaryAttack, szWeaponName, "CBasePlayerWeapon_PrimAttack", false)
            RegisterHam(Ham_Weapon_PrimaryAttack, szWeaponName, "CBasePlayerWeapon_PrimAttackP", true)
        }
    }
}

public CBasePlayerWeapon_PrimAttackP(const pWeapon)
{
    if(pWeapon <= 0)
        return

    new pPlayer = get_pdata_cbase(pWeapon, m_pPlayer, XO_WEAPON)
    if(pPlayer > 0)
    {
        set_pev(pPlayer, pev_punchangle, g_vecPunchAngle[pPlayer])

        if(g_vecVelocity[pPlayer][0] && g_vecVelocity[pPlayer][0] && g_vecVelocity[pPlayer][0])
        {
            set_pev(pPlayer, pev_velocity, g_vecVelocity[pPlayer])
            set_pev(pPlayer, pev_fov, float(get_pdata_int(pPlayer, m_iFOV)))
            set_pev(pPlayer, pev_flags, pev(pPlayer, pev_flags) & ~(FL_ONGROUND|FL_DUCKING))

            g_vecVelocity[pPlayer][0] = g_vecVelocity[pPlayer][1] = g_vecVelocity[pPlayer][2] = 0.0
        }
    }
}

public CBasePlayerWeapon_PrimAttack(const pWeapon)
{
    if(pWeapon <= 0)
        return

    new pPlayer = get_pdata_cbase(pWeapon, m_pPlayer, XO_WEAPON)
    if(pPlayer > 0)
    {
        pev(pPlayer, pev_punchangle, g_vecPunchAngle[pPlayer])
        set_pdata_int(pPlayer, random_seed, 0)

        new iId = get_pdata_int(pWeapon, m_iId, XO_WEAPON)
        if(SECONDARY_WPN_BS & (1 << iId))
        {
            set_pdata_float(pWeapon, m_flLastFire, 0.0, XO_WEAPON)
            set_pdata_float(pWeapon, m_flAccuracy, 1.0, XO_WEAPON)
        }
        else if(SNIPER_WPN_BS & (1 << iId))
        {
            pev(pPlayer, pev_velocity, g_vecVelocity[pPlayer])

            set_pev(pPlayer, pev_flags, pev(pPlayer, pev_flags) | (FL_ONGROUND|FL_DUCKING))
            set_pev(pPlayer, pev_velocity, Float:{0.0, 0.0, 0.0})
            set_pev(pPlayer, pev_fov, 40.0)

            if(iId == CSW_SG550 || iId == CSW_G3SG1)
            {
                set_pdata_float(pWeapon, m_flLastFire, 0.0, XO_WEAPON)
                set_pdata_float(pWeapon, m_flAccuracy, 1.0, XO_WEAPON)
            }
        }
        else
        {
            set_pdata_int(pWeapon, m_iShotsFired, 0, XO_WEAPON)
            set_pdata_float(pWeapon, m_flAccuracy, 0.0, XO_WEAPON)
        }
    }
}
Сообщение автоматически объединено:

Автор кода: wopox1337
Регулировка прозрачности игрока в зависимости от скорости.
C-подобный:
#include <amxmodx>
#include <engine>

new MIN_TRANS;
new Float: SPEED_SENSETIVE;
new Float: INVISIBLE_FACTOR;
new Float: TRANSPARENCY;

public plugin_init()
{
    register_plugin("Transparency by Speed", "0.0.1", "wopox1337");

    new pCvar;
    pCvar = create_cvar("ToS_SpeedSens", "250.0");
    bind_pcvar_float(pCvar, SPEED_SENSETIVE);

    pCvar = create_cvar("ToS_InvisFactor", "2.0");
    bind_pcvar_float(pCvar, INVISIBLE_FACTOR);

    pCvar = create_cvar("ToS_Trans", "255.0");
    bind_pcvar_float(pCvar, TRANSPARENCY);

    pCvar = create_cvar("ToS_Min","25");
    bind_pcvar_num(pCvar, MIN_TRANS);
}

public client_PreThink(pPlayer)
{
    if(!is_user_alive(pPlayer))
        return;

    static Float: fVecVelocity[3];
    entity_get_vector(pPlayer, EV_VEC_velocity, fVecVelocity);
 
    static Float: fSpeed;
    fSpeed = vector_length(fVecVelocity);

    set_rendering(
        .index      = pPlayer,
        .fx         = kRenderFxNone,
        .render     = kRenderTransAlpha,
        .amount     = get_transparency_by_speed(fSpeed)
    );
}

stock get_transparency_by_speed(Float: fSpeed)
{
    return max(MIN_TRANS, floatround( (fSpeed < SPEED_SENSETIVE) ? fSpeed / INVISIBLE_FACTOR : TRANSPARENCY ));
}
Сообщение автоматически объединено:

Автор кода: wopox1337
Выставление свойств видимости объектов индивидуально для игрока (другие не видят).

C-подобный:
#include <amxmodx>
#include <fakemeta>

stock const CLASSES[][] = {
    "func_door_rotating",
    "func_door",
    "func_breakable",
    "func_button"
}

new Trie: g_tClasses;

public plugin_init() {
    register_forward(FM_AddToFullPack, "AddToFullPack", ._post = true);

    g_tClasses = TrieCreate();

    for(new i; i < sizeof CLASSES; i++)
        TrieSetCell(g_tClasses, CLASSES[i], 0);

// for tests
    register_clcmd("radio2", "toggle");
}

public plugin_end()
    TrieDestroy(g_tClasses);

new bool: gBool[33];
public toggle(pPlayer) {
    gBool[pPlayer] = !gBool[pPlayer];

    return PLUGIN_HANDLED;
}

public AddToFullPack(es, e, ent, host, hostflags, player, pSet) {
    if(!is_user_alive(host) || !gBool[host]) return;

    static szString[19];
    pev(e, pev_classname, szString, charsmax(szString));

    if(TrieKeyExists(g_tClasses, szString))
    {
        set_es_rendering(
            .es = es,
            .fx = kRenderFxGlowShell,
            .color = {255, 0, 0},
            .render = kRenderTransColor,
            .amount = 100
        );
    }
}

stock set_es_rendering(es = 0, fx = kRenderFxNone, color[3] = {255, 255, 255}, render = kRenderNormal, amount = 16) {
    set_es(es, ES_RenderFx, fx);
    set_es(es, ES_RenderColor, color);
    set_es(es, ES_RenderMode, render);
    set_es(es, ES_RenderAmt, amount);
}
Сообщение автоматически объединено:

Автор кода: steelzzz
Код для определения времени суток.

C-подобный:
GetTime()
{
    new szText[24]
    new szTime[32]; get_time("%H", szTime, charsmax(szTime))
    new iTime = str_to_num(szTime)

    switch(iTime)
    {
        case 0..4: szText = "Доброй ночи"
        case 5..11: szText = "Доброе утро"
        case 12..16: szText = "Добрый день"
        case 17..23: szText = "Добрый вечер"
    }
    return szText
}


C-подобный:
#include <amxmodx>

public plugin_init()
{
    register_clcmd("say 2", "radio1test")
}

public radio1test(iPlayer)
{
    new szTime[32]; get_time("%H", szTime, charsmax(szTime))
    new szText[24]; formatex(szText, charsmax(szText), "%s", GetTime())
    client_print(iPlayer, print_chat, "%s | %d", szText, str_to_num(szTime))

}
   
GetTime()
{
    new szText[24]
    new szTime[32]; get_time("%H", szTime, charsmax(szTime))
    new iTime = str_to_num(szTime)

    switch(iTime)
    {
        case 0..4: szText = "Доброй ночи"
        case 5..11: szText = "Доброе утро"
        case 12..16: szText = "Добрый день"
        case 17..23: szText = "Добрый вечер"
    }
    return szText
}
Сообщение автоматически объединено:

Нумерация объектов для их дальнейшей идентификации
Автор:
BalbuR

PHP:
#include <amxmodx>
#include <reapi>

new g_iKeyCounter = 1; // счетчик для нумерации объектов

public plugin_init() {
    RegisterHookChain(RH_ED_Alloc, "fw_Edict_Alloc", true);
    RegisterHookChain(RH_ED_Free, "fw_Edict_Free", false);
}

// удаление объекта
public fw_Edict_Free(iEnt)
{
    server_print("Remove entity(free) %d (%s) | iKey = %d", iEnt, !is_nullent(iEnt) ? "valid" : "invalid", get_entvar(iEnt, var_iuser4));
}

// Выделение памяти под объект, все еще невалидный
public fw_Edict_Alloc()
{
    new iEnt =  GetHookChainReturn(ATYPE_INTEGER);
    server_print("Created entity %d (%s)", iEnt, !is_nullent(iEnt) ? "valid" : "invalid");

    RequestFrame("@fw_NextFrame_CreateEntity", iEnt);
}
// на следующем кадре сервера проверяем что объект валидный и ставим ему метку
@fw_NextFrame_CreateEntity(iEnt)
{
    // Нумеруем объект, чтобы использовать номер как идентификатор
    if (!is_nullent(iEnt)) {
        new szClassName[32];
        get_entvar(iEnt, var_classname, szClassName, 31);
        server_print("Check entity %d [%s] | iKey = %d", iEnt, szClassName, g_iKeyCounter);

        set_entvar(iEnt, var_iuser4, g_iKeyCounter);
        g_iKeyCounter++;
    }
}

В следующем плагине используем нашу метку для идентификации

C++:
#include <amxmodx>
#include <reapi>
#include <fakemeta>

enum aArrData {
    aArrData_pIndex,
    aArrData_pOwner,
    aArrData_iKey
}

new Array:g_aArrayData;

public plugin_init()
{
    g_aArrayData = ArrayCreate(aArrData);
}

public RemoveHat(id) {
    new pHat;
    if (is_user_alive(id)) {
        pHat = FindUserHat(id);

        if (!is_nullent(pHat)) {
            set_entvar(pHat, var_flags, FL_KILLME);
        }
    }
}

public CreateHat(id)
{
    new pHat;

    if (is_user_alive(id)) {

        pHat = rg_create_entity("info_target");

        if (!is_nullent(pHat)) {
            set_entvar(pHat, var_classname, "hats");

            engfunc(EngFunc_SetModel, pHat, "models/p_ak47.mdl");
            set_entvar(pHat, var_owner, id);
            set_entvar(pHat, var_aiment, id);
            set_entvar(pHat, var_movetype, MOVETYPE_FOLLOW);

            new szData[aArrData];

            szData[aArrData_pIndex] = pHat;
            szData[aArrData_iKey] = get_entvar(pHat, var_iuser4);
            szData[aArrData_pOwner] = get_entvar(pHat, var_owner);

            ArrayPushArray(g_aArrayData, szData);
        }
    }
}

stock FindUserHat(id)
{
    for (new i, szData[aArrData], pIndex; i<ArraySize(g_aArrayData); i++) {
        ArrayGetArray(g_aArrayData, i, szData);

        pIndex = szData[aArrData_pIndex];

        // Если данные объекта из массива не совпадают с самим объектом, тогда очищаем этот элемент [это говорит о том, что объект подчищен или изменен]
        if (is_nullent(pIndex) || get_entvar(pIndex, var_owner) != szData[aArrData_pOwner] || szData[aArrData_iKey] != get_entvar(pIndex, var_iuser4)) {
            ArrayDeleteItem(g_aArrayData, i--);
            continue;
        }

        if (szData[aArrData_pOwner] == id) {
            return pIndex;
        }
    }

    return FM_NULLENT;
}

---

В боевом варианте с этим же примером, этот пул шапок можно копировать на фейковые модели игрока (плагины DanceMenu и т.д.), дабы отображать шапочки и на фейках.


Поворот вектора с помощью матрицы
Автор:
t3rkecorejz

C:
#define PITCH        0 // up / down
#define YAW            1 // left / right
#define ROLL        2 // fall over

stock UTIL_AngleMatrix(const Float: angles[3], Float: matrix[3][4])
{
    new Float: angle;
    new Float: sr, Float: sp, Float: sy, Float: cr, Float: cp, Float: cy;

    angle = angles[YAW] * (M_PI * 2.0 / 360.0);
    sy = floatsin(angle);
    cy = floatcos(angle);
    angle = angles[PITCH] * (M_PI * 2.0 / 360.0);
    sp = floatsin(angle);
    cp = floatcos(angle);
    angle = angles[ROLL] * (M_PI * 2.0 / 360.0);
    sr = floatsin(angle);
    cr = floatcos(angle);

    // matrix = (YAW * PITCH) * ROLL
    matrix[0][0] = cp * cy;
    matrix[1][0] = cp * sy;
    matrix[2][0] = -sp;
    matrix[0][1] = sr * sp * cy + cr * -sy;
    matrix[1][1] = sr * sp * sy + cr * cy;
    matrix[2][1] = sr * cp;
    matrix[0][2] = (cr * sp * cy + -sr * -sy);
    matrix[1][2] = (cr * sp * sy + -sr * cy);
    matrix[2][2] = cr * cp;
    matrix[0][3] = 0.0;
    matrix[1][3] = 0.0;
    matrix[2][3] = 0.0;
}

C:
stock UTIL_VectorRotate(const Float: in1[3], const Float: in2[3][4], Float: out[3])
{
    out[0] = xs_vec_dot(in1, in2[0]);
    out[1] = xs_vec_dot(in1, in2[1]);
    out[2] = xs_vec_dot(in1, in2[2]);
}

---

Восстановление здоровья (пример)
Автор:
Albertio

C++:
#include <amxmodx>
#include <reapi>

public plugin_init()
{
    register_plugin("Restoring Health", "0.0.1", "Albertio");

    RegisterHookChain(RG_CBasePlayer_TakeDamage, "CBasePlayer_TakeDamage_Post", true);
}

public CBasePlayer_TakeDamage_Post(const iVictim, iInflictor, iAttacker, Float:flDamage, bitsDamageType)
{
    if(bitsDamageType & DMG_DROWN)
        return HC_CONTINUE;

    if(is_user_alive(iVictim))
    {
        set_member(iVictim, m_idrowndmg, get_member(iVictim, m_idrowndmg) - get_member(iVictim, m_idrownrestored) + floatround(flDamage));
        set_member(iVictim, m_idrownrestored, 0);
    }

    return HC_CONTINUE;
}

Константы типов урона:
C++:
#define DMG_GENERIC                     0           // Generic damage was done
#define DMG_CRUSH                       (1<<0)      // Crushed by falling or moving object
#define DMG_BULLET                      (1<<1)      // Shot
#define DMG_SLASH                       (1<<2)      // Cut, clawed, stabbed
#define DMG_BURN                        (1<<3)      // Heat burned
#define DMG_FREEZE                      (1<<4)      // Frozen
#define DMG_FALL                        (1<<5)      // Fell too far
#define DMG_BLAST                       (1<<6)      // Explosive blast damage
#define DMG_CLUB                        (1<<7)      // Crowbar, punch, headbutt
#define DMG_SHOCK                       (1<<8)      // Electric shock
#define DMG_SONIC                       (1<<9)      // Sound pulse shockwave
#define DMG_ENERGYBEAM                  (1<<10)     // Laser or other high energy beam
#define DMG_NEVERGIB                    (1<<12)     // With this bit OR'd in, no damage type will be able to gib victims upon death
#define DMG_ALWAYSGIB                   (1<<13)     // With this bit OR'd in, any damage type can be made to gib victims upon death.
#define DMG_DROWN                       (1<<14)     // Drowning
#define DMG_PARALYZE                    (1<<15)     // Slows affected creature down
#define DMG_NERVEGAS                    (1<<16)     // Nerve toxins, very bad
#define DMG_POISON                      (1<<17)     // Blood poisioning
#define DMG_RADIATION                   (1<<18)     // Radiation exposure
#define DMG_DROWNRECOVER                (1<<19)     // Drowning recovery
#define DMG_ACID                        (1<<20)     // Toxic chemicals or acid burns
#define DMG_SLOWBURN                    (1<<21)     // In an oven
#define DMG_SLOWFREEZE                  (1<<22)     // In a subzero freezer
#define DMG_MORTAR                      (1<<23)     // Hit by air raid (done to distinguish grenade from mortar)
#define DMG_GRENADE                     (1<<24)     // Counter-Strike only - Hit by HE grenade
#define DMG_TIMEBASED                   (~(0x3fff)) // Mask for time-based damage

---

Альтернативный метод (fm_fakedamage)
Автор:
SergeyShorokhov (ссылка на fakemeta_util.inc)

C++:
stock fm_fakedamage(victim, const classname[], Float:takedmgdamage, damagetype) {
    new class[] = "trigger_hurt";
    new entity = fm_create_entity(class);
    if (!entity)
        return 0;

    new value[16];
    float_to_str(takedmgdamage * 2, value, sizeof value - 1);
    fm_set_kvd(entity, "dmg", value, class);

    num_to_str(damagetype, value, sizeof value - 1);
    fm_set_kvd(entity, "damagetype", value, class);

    fm_set_kvd(entity, "origin", "8192 8192 8192", class);
    fm_DispatchSpawn(entity);

    set_pev(entity, pev_classname, classname);
    fm_fake_touch(entity, victim);
    fm_remove_entity(entity);

    return 1;
}
Сообщение автоматически объединено:

Быстрая конвертация времени в читаемый формат с падежами

Поддерживает русские падежи для слов "секунда/секунды/секунд" и т.д.


C-подобный:
stock FormatTimeRu(iSeconds, szOutput[], iLen, bool:bShort = false) {
    new iYears, iDays, iHours, iMins;
    
    iYears = iSeconds / 31536000;
    iSeconds %= 31536000;
    iDays = iSeconds / 86400;
    iSeconds %= 86400;
    iHours = iSeconds / 3600;
    iSeconds %= 3600;
    iMins = iSeconds / 60;
    iSeconds %= 60;
    
    iLen = 0;
    
    if(bShort) {
        if(iYears) iLen += formatex(szOutput[iLen], iLen, "%dг ", iYears);
        if(iDays)  iLen += formatex(szOutput[iLen], iLen, "%dд ", iDays);
        if(iHours) iLen += formatex(szOutput[iLen], iLen, "%dч ", iHours);
        if(iMins)  iLen += formatex(szOutput[iLen], iLen, "%dм ", iMins);
        if(iSeconds || !iLen) iLen += formatex(szOutput[iLen], iLen, "%dс", iSeconds);
    } else {
        if(iYears) iLen += formatex(szOutput[iLen], iLen, "%d %s ", iYears, Declension(iYears, "год", "года", "лет"));
        if(iDays)  iLen += formatex(szOutput[iLen], iLen, "%d %s ", iDays, Declension(iDays, "день", "дня", "дней"));
        if(iHours) iLen += formatex(szOutput[iLen], iLen, "%d %s ", iHours, Declension(iHours, "час", "часа", "часов"));
        if(iMins)  iLen += formatex(szOutput[iLen], iLen, "%d %s ", iMins, Declension(iMins, "минута", "минуты", "минут"));
        if(iSeconds || !iLen) iLen += formatex(szOutput[iLen], iLen, "%d %s", iSeconds, Declension(iSeconds, "секунда", "секунды", "секунд"));
    }
    
    return iLen;
}

stock Declension(iNumber, const szForm1[], const szForm2[], const szForm5[]) {
    iNumber = iNumber % 100;
    if(iNumber > 10 && iNumber < 20) return szForm5;
    iNumber %= 10;
    if(iNumber == 1) return szForm1;
    if(iNumber >= 2 && iNumber <= 4) return szForm2;
    return szForm5;
}

// Использование:
new szTime[64];
FormatTimeRu(3665, szTime, charsmax(szTime), false);
// Результат: "1 час 1 минута 5 секунд"
 
Последнее редактирование:
C-подобный:
/**
 * Converts a floating-point value to a FixedUnsigned16 format.
 *
 * This function converts a floating-point value to the FixedUnsigned16 format,
 * clamping it within the valid range [0, 0xFFFF].
 *
 * @param value     The floating-point value to convert.
 * @param scale     The scaling factor (default is 1 << 12).
 * @return          Returns the FixedUnsigned16 representation of the value.
 */
stock FixedUnsigned16(Float: value, scale = (1 << 12))
{
    return clamp(floatround(value * scale), 0, 0xFFFF)
}

C-подобный:
/**
 * Проверяет, ослеплён ли игрок
 *
 * @param pPlayer   Указатель на игрока
 *
 * @return          true/false
 */
stock bool: IsBlind(pPlayer)
{
    return bool:(Float: get_member(pPlayer, m_blindStartTime) + Float: get_member(pPlayer, m_blindFadeTime) >= get_gametime());
}

C-подобный:
/**
 * Возвращает бит значение SignalState (cssdk_const.inc) зоны, в которой находится игрок
 *
 * @param player    Указатель на игрока
 *
 * @return          SignalState
 */
stock SignalState:rg_get_user_mapzones(const player)
{
    new iSignals[UnifiedSignals];
    get_member(player, m_signals, iSignals);
    return SignalState:iSignals[US_State];
}

C-подобный:
/**
 * Находится ли игрок в зоне покупки
 *
 * @param player    Указатель на игрока
 *
 * @return          true/false
 */
stock bool:rg_user_in_buyzone(const player)
{
    return bool:(rg_get_user_mapzones(player) & SIGNAL_BUY);
}

C-подобный:
/**
 * Выдаёт оружие игроку с возможность указать кол-во боезапас и типом выдачи
 *
 * @param pPlayer       Указатель на игрока
 * @param weapon        Название оружия (weapon_*)
 * @param type          Тип выдачи (см. GiveType в reapi_gamedll_const.inc)
 * @param ammount       Количество боезопаса
 *
 * @noreturn
 */
stock rg_give_item_ex(pPlayer, weapon[], GiveType:type = GT_APPEND, ammount = 0)
{
    rg_give_item(pPlayer, weapon, type);
    if (ammount) rg_set_user_bpammo(pPlayer, rg_get_weapon_info(weapon, WI_ID), ammount);
}

C-подобный:
/**
 * Получает кол-во игроков в командах
 *
 * @param terrorist     Переменная для получения кол-ва террористов
 * @param ct            Переменная для получения кол-ва контр-террористов
 * @param spec          Переменная для получения кол-ва наблюдателей
 * @param unassigned    Переменная для получения кол-ва неопределившихся
 *
 * @noreturn
 */
stock rg_get_players_num(&terrorist = 0, &ct = 0, &spec = 0, &unassigned = 0)
{
    for (new id = 1; id <= MaxClients; id++)
    {
        if (!is_user_connected(id)) continue;
  
        switch (get_member(id, m_iTeam))
        {
            case TEAM_TERRORIST: terrorist++;
            case TEAM_CT: ct++;
            case TEAM_SPECTATOR: spec++;
            case TEAM_UNASSIGNED: unassigned++;
        }
    }
}

C-подобный:
/**
 * Установим счёт игрокам.
 *
 * @note    rh_set_score(pUser, 3, 4) - установим счёт.
 *          rh_set_score(pUser)       - обнулим.
 *
 * @param frags   - убийства.
 * @param deaths  - смерти.
 *
 * @noreturn
 */
stock rh_set_score(const pUser, const frags = 0, const deaths = 0)
{
    set_entvar(pUser, var_frags, float(frags));
    set_member(pUser, m_iDeaths, deaths);

    message_begin(MSG_BROADCAST, 85);
    write_byte(pUser);
    write_short(frags);
    write_short(deaths);
    write_short(0);
    write_short(0);
    message_end();
}

C-подобный:
/**
 * Возвращает время до конца раунда
 *
 * @return  float seconds
 */
stock Float:rg_get_remaining_time()
{
    return (float(get_member_game(m_iRoundTimeSecs)) - get_gametime() + Float:get_member_game(m_fRoundStartTimeReal));
}

C-подобный:
/**
 * Возвращает индекс оружия (в т.ч. кастомного) у игрока при наличии
 *
 * @param id        Указатель на игрока
 * @param wid       WeaponIdType
 * @param slot      InventorySlotType
 *
 * @return          pEdict или 0
 */
stock rg_get_player_item(const id, const WeaponIdType:wid, const InventorySlotType:slot = NONE_SLOT) {
    new item = get_member(id, m_rgpPlayerItems, slot);
    while (!is_nullent(item)) {
        if (get_member(item, m_iId) == wid) {
            return item;
        }
        item = get_member(item, m_pNext);
    }

    return 0;
}

C-подобный:
/**
 * Отправляет сообщение в консоле сервера, учитывая лимит символов
 *
 * @param buffer        Текст для отправки
 *
 * @noreturn
 */
stock SERVER_PRINT(const buffer[])
{
  for(new i, len = strlen(buffer); i < len; i+=255) {
    engfunc(EngFunc_ServerPrint, fmt("%-255s", buffer[i]));
  }
  engfunc(EngFunc_ServerPrint, "\n");
}
 
Последнее редактирование:
#define MSG_SCORE 85 stock rh_set_score(const pUser, const frags = 0, const deaths = 0) { set_entvar(pUser, var_frags, float(frags)); set_member(pUser, m_iDeaths, deaths); message_begin(MSG_BROADCAST, 85);
->
message_begin(MSG_BROADCAST, MSG_SCORE)
Сообщение автоматически объединено:

Если уж используется #define
Сообщение автоматически объединено:

после правки выше, удалите моё сообщение =)
 
У каждой модели есть своя глобальная таблица скинов, откуда и берутся различные вариации скинов. Но есть нюанс - для каждой субмодели будет отображаться максимальное кол-во скинов в модели. Например, eсли у субмодели #1 существует 3 набора скина, а у субмодели #2 их только 2, то у субмодели #2 будет показываться что возможно 3 вариации скинов. 3-го скина нет, и будет показываться просто текущий. Поэтому для чёткого понимания кол-ва скинов у субмоделей и был создан данный сток. Пример вывода:
C-подобный:
#0 | FNAF_bonny | 2 skins
#1 | FNAF_chika | 2 skins
#2 | FNAF_freddy | 3 skins
#3 | FNAF_foxy | 1 skins
#4 | FNAF_foxy_hook | 1 skins
#5 | FNAF_puppet | 1 skins
#6 | FNAF_nightmarionne | 1 skins

C-подобный:
#define MAX_STUDIO_MESHES   32
#define MAX_SKIN_FAMILIES   16
#define MAX_SKIN_REFS       32

/**
 * Возвращает список уникальных скинов для заданной субмодели
 *
 * @param szModel       Путь до модели
 * @param iSub          Номер субмодели [0;iNum)
 * @param pSkins        Массив для записи индексов скинов
 * @param iLen          Максимальное количество индексов, которое можно записать
 *
 * @return              Кол-во уникальных скинов
 */
stock UTIL_GetSubModelSkins(const szModel[], iSub, pSkins[], iLen)
{
    new hFile = fopen(szModel, "rb");

    if (!hFile)
    {
        server_print("Can`t open %s", szModel);
        return false;
    }

    new iBlock, iCount, index = -1;

    fseek(hFile, 204, SEEK_SET);
    fread(hFile, iCount, BLOCK_INT);
    fread(hFile, iBlock, BLOCK_INT);
    fseek(hFile, iBlock, SEEK_SET);

    for (new i = 0, iSubs, iTotal; i < iCount; i++)
    {
        fseek(hFile, 64, SEEK_CUR);
        fread(hFile, iSubs, BLOCK_INT);
        fseek(hFile, 4, SEEK_CUR);
        fread(hFile, iBlock, BLOCK_INT);

        if (iSub < iSubs + iTotal)
        {
            index = iSub - iTotal;
            break;
        }
      
        iTotal += iSubs;
    }

    if (index < 0)
    {
        server_print("Index not found %i", iSub);
        fclose(hFile);
        return false;
    }

    fseek(hFile, iBlock, SEEK_SET);
    fseek(hFile, 112 * index, SEEK_CUR);
    fseek(hFile, 72, SEEK_CUR);

    fread(hFile, iCount, BLOCK_INT);
    fread(hFile, iBlock, BLOCK_INT);

    if (iCount < 1)
    {
        fclose(hFile);
        if (iLen > 0) pSkins[0] = 0;
        return 1;
    }

    new iSkins, bool:bRefs[MAX_SKIN_REFS];

    fseek(hFile, 192, SEEK_SET);
    fread(hFile, iSkins, BLOCK_INT);
    if (iSkins > MAX_SKIN_REFS) iSkins = MAX_SKIN_REFS;

    fseek(hFile, iBlock, SEEK_SET);

    iBlock = 0;

    for (new i = 0, skin; i < iCount; i++)
    {
        fseek(hFile, 8, SEEK_CUR);
        fread(hFile, skin, BLOCK_INT);

        if (0 <= skin && skin < iSkins)
        {
            if (!bRefs[skin])
            {
                bRefs[skin] = true;
                iBlock++;
            }
        }

        fseek(hFile, 8, SEEK_CUR);
    }

    if (!iBlock)
    {
        fclose(hFile);
        if (iLen > 0) pSkins[0] = 0;
        return 1;
    }

    new skinTable[MAX_SKIN_FAMILIES][MAX_SKIN_REFS];
    new iFamilies;

    fseek(hFile, 196, SEEK_SET);
    fread(hFile, iFamilies, BLOCK_INT);
  
    if (iFamilies < 1) iFamilies = 1;
    if (iFamilies > MAX_SKIN_FAMILIES) iFamilies = MAX_SKIN_FAMILIES;
  
    fread(hFile, iBlock, BLOCK_INT);
    fseek(hFile, iBlock, SEEK_SET);

    for (new i = 0, byte1, byte2; i < iFamilies; i++)
    {
        for (new j = 0; j < iSkins; j++)
        {
            fread(hFile, byte1, BLOCK_BYTE);
            fread(hFile, byte2, BLOCK_BYTE);
            skinTable[i][j] = byte1 | (byte2 << 8);
        }
    }

    fclose(hFile);

    new texture[MAX_SKIN_FAMILIES][MAX_SKIN_REFS], iRef[MAX_SKIN_FAMILIES], indexf[MAX_SKIN_FAMILIES];
    iCount = 0;

    for (new i = 0; i < iFamilies; i++)
    {
        new textureList[MAX_SKIN_REFS], iTexture = 0;
      
        for (new j = 0; j < iSkins; j++)
        {
            if (bRefs[j])
            {
                textureList[iTexture] = skinTable[i][j];
                iTexture++;
            }
        }
      
        new bool:found = false;
        for (new j = 0; j < iCount; j++)
        {
            if (iTexture != iRef[j])
            {
                continue;
            }

            new bool:match = true;
          
            for (new k = 0; k < iTexture; k++)
            {
                if (textureList[k] != texture[j][k])
                {
                    match = false;
                    break;
                }
            }

            if (match)
            {
                found = true;
                break;
            }
        }

        if (!found && iCount < MAX_SKIN_FAMILIES)
        {
            for (new j = 0; j < iTexture; j++)
            {
                texture[iCount][j] = textureList[j];
            }

            iRef[iCount] = iTexture;
            indexf[iCount] = i;
            iCount++;
        }
    }

    if (iCount > iLen)
    {
        iCount = iLen;
    }

    for (new i = 0; i < iCount; i++)
    {
        pSkins[i] = indexf[i];
    }

    return iCount;
}
 
Последнее редактирование:
Bodypart - это и все bodygroup, и все body. Поэтому стоит учесть, что много полигонные модели, имеющие несколько body, будут выводить все части, но для всех них var_body одинаковый и будет равен 0 << 1 т.е. всегда 0.
Для управления отображения bodypart используется схема (index << base), где index - порядковый номер внутри bodypart; base - сдвиг.
Например для много полигональной модели, мы имеем такие выводы:
9 Bodypart:
C-подобный:
Bodypart 0: name='studio', models=1, base=1
Bodypart 1: name='studio', models=1, base=1
Bodypart 2: name='studio', models=1, base=1
Bodypart 3: name='studio', models=1, base=1
Bodypart 4: name='studio', models=1, base=1
Bodypart 5: name='studio', models=1, base=1
Bodypart 6: name='studio', models=1, base=1
Bodypart 7: name='studio', models=1, base=1
Bodypart 8: name='backpack', models=2, base=1
С 0 по 7 - это наша модель игрока.
8 - группа рюкзака, состоящая из 2 субмоделей.
Делая цикл по UTIL_GetCountSubModels и проверяя по UTIL_GetBodyInfoSubModel, находим субмодели для Bodypart 8:
C-подобный:
Submodel #8 'blank' -> bodypart 8, local submodel 0
Submodel #9 'Bomb' -> bodypart 8, local submodel 1
Blank - пустая модель, которая нужна для отключения рюкзака, имеет порядковый номер в группе (локальную субмодель) - 0.
Bomb - модель бомбы, имеет номер 1.
Получается, чтоб показать на модели игрока бомбу, достаточно сделать var_vody = (1 << 1). Для отмены достаточно сделать var_body = 0.

Допустим, у модели была бы ещё шапка, тогда:
C-подобный:
Bodypart 9: name='hat', models=2, base=2
...
Submodel #10 'blank' -> bodypart 9, local submodel 0
Submodel #11 'hat' -> bodypart 9, local submodel 1
Показать бомбу и шапку: var_body = (1 << 1) | (1 << 2)
Убрать шапку, оставить бомбу: var_body &= ~(1 << 2)

C-подобный:
/**
 * Получает кол-во bodypart в модели
 *
 * @param szModel       Путь до модели
 *
 * @return              iCount
 */
stock UTIL_GetCountBodypart(const szModel[])
{
    new hFile = fopen(szModel, "rb");

    if (!hFile)
    {
        server_print("Can`t open %s", szModel);
        return 0;
    }

    new iCount;
    fseek(hFile, 204, SEEK_SET);
    fread(hFile, iCount, BLOCK_INT);
    fclose(hFile);
    return iCount;
}

C-подобный:
/**
 * Получает информацию о контректном bodypart
 *
 * @param szModel       Путь до модели
 * @param iBody         Номер bodypart
 * @param szName        Буффер для записи названия bodypart
 * @param iLen          Длина буффера
 * @param iNum          Кол-во субмоделей в bodypart
 * @param iBase         Сдвиг для body
 *
 * @return              true/false
 */
stock bool:UTIL_GetBodypartInfo(const szModel[], const iBody, szName[], iLen, &iNum, &iBase)
{
    new hFile = fopen(szModel, "rb");

    if (!hFile)
    {
        server_print("Can`t open %s", szModel);
        return false;
    }

    new iBlock, szTemp[64];
    fseek(hFile, 208, SEEK_SET);
    fread(hFile, iBlock, BLOCK_INT);
    fseek(hFile, iBlock + iBody * 76, SEEK_SET);
  
    fread_blocks(hFile, _:szTemp, 64, BLOCK_CHAR);
    fread(hFile, iNum, BLOCK_INT);
    fread(hFile, iBase, BLOCK_INT);
    copy(szName, iLen, szTemp);

    fclose(hFile);
    return true;
}

C-подобный:
/**
 * По номеру субмодели определяет его bodypart, и номер субмодели внутри
 *
 * @param szModel       Путь до модели
 * @param iSub          Номер субмодели (глобально)
 * @param iBody         Номер bodypart
 * @param iNum          Номер субмодели в bodypart
 *
 * @return              true/false
 */
stock bool:UTIL_GetBodyInfoSubModel(const szModel[], iSub, &iBody, &iNum)
{
    new hFile = fopen(szModel, "rb");

    if (!hFile)
    {
        server_print("Can`t open %s", szModel);
        return false;
    }

    new iBlock, iCount;
    fseek(hFile, 204, SEEK_SET);
    fread(hFile, iCount, BLOCK_INT);
    fread(hFile, iBlock, BLOCK_INT);
    fseek(hFile, iBlock, SEEK_SET);

    for (new i = 0, iModel, iTotal; i < iCount; i++)
    {
        fseek(hFile, 64, SEEK_CUR);
        fread(hFile, iModel, BLOCK_INT);
        fseek(hFile, 8, SEEK_CUR);

        if (iSub < iTotal + iModel)
        {
            iBody = i;
            iNum = iSub - iTotal;
            fclose(hFile);
            return true;
        }

        iTotal += iModel;
    }

    fclose(hFile);
    return false;
}
 
Последнее редактирование:

Кто просматривает тему

Назад
Верх