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

Сообщения
207
Реакции
420
Помог
10 раз(а)
Тема для готовых функций на SourcePawn, которые можно просто вставить в свой код и использовать.

  1. Передача прав на указатель (Handle)
    Трюк основан на функциях CloneHandle() и CloseHandle(). Сначала клонируется нужный указатель, потом удаляется старый.
    C++:
    stock void ChangeHandleOwner(Handle &hPointer, Handle hNewOwner = null) {
      Handle hDummy = CloneHandle(hPointer, hNewOwner);
      CloseHandle(hPointer);
      hPointer = hDummy;
    }
  2. Вызов форвардов с клоном указателя
    Ещё один полезный, по-моему мнению, сток. Позволяет вызвать некий "форвард". Но в отличие от обычного форварда, передаётся на каждый плагин не оригинальный указатель, а его клон.
    C++:
    stock void CallEventWithClonedHandle(const char[] szFunctionName, Handle hPointer) {
      Handle    hClone;
      Function  ptrFunc;
      Handle    hPlugin;
      Handle    hPlugIter = GetPluginIterator();
      Handle    hCoreHandle = GetMyHandle();
    
      while (MorePlugins(hPlugIter)) {
        hPlugin = ReadPlugin(hPlugIter);
        if (hPlugin == hCoreHandle || GetPluginStatus(hPlugin) != Plugin_Running)
          continue;
    
        ptrFunc = GetFunctionByName(hPlugin, szFunctionName);
        if (ptrFunc == INVALID_FUNCTION)
          continue;
    
        hClone = CloneHandle(hPointer, hPlugin);
    
        Call_StartFunction(hPlugin, ptrFunc);
        Call_PushCell(hClone);
        Call_Finish();
      }
    
      CloseHandle(hPlugIter);
    }
  3. Перевод IP-адреса из строкового представления в бинарное (двоичное)
    Перегоняет айпишник из строкового представления - в двоичное. Позволяет сэкономить память, если он где-то впоследствии хранится, т.к. IP-адрес в бинарном представлении занимает фиксированно 4 байта.
    C++:
    stock int IP2Bin(const char[] szAddress) {
      char szAddr[16];
      strcopy(szAddr, sizeof(szAddr), szAddress);
    
      int iPos = 0;
      int iResult;
    
      for (int iDotId = 0; iDotId < 4; ++iDotId) {
        iPos = FindCharInString(szAddr, '.', true);
        iResult |= StringToInt(szAddr[iPos+1]) << (iDotId * 8);
    
        if (iPos != -1)
          szAddr[iPos] = 0;
      }
    
      return iResult;
    }

Делитесь своими стоками. Будем собирать.
 
Сообщения
1,661
Реакции
1,486
Помог
24 раз(а)
Функция с исходников амхмодх. Делит строку на левую и правую часть с помощью разделителя.
Переписал потому что не нашел аналогов.
Код:
bool strtok( char[ ] sText, char[ ] sLeft, char[ ] sRight, int iToken = ' ' )
{
    bool bDoneFlag;

    int iLeftPos, iRightPos;
    int iLen = strlen( sText );

    for( int i = 0; i < iLen; i++ )
    {
        if( !bDoneFlag && sText[ i ] == iToken )
        {
            bDoneFlag = true;
            i++;
        }

        if( bDoneFlag )
            sRight[ iRightPos++ ] = sText[ i ];
        else
            sLeft[ iLeftPos++ ] = sText[ i ];
    }

    sRight[ iRightPos ] = 0;
    sLeft[ iLeftPos ] = 0;

    return true;
}
 
Последнее редактирование:
Сообщения
1,661
Реакции
1,486
Помог
24 раз(а)
C++:
stock void SetEntityRender( int iEntity, RenderFx iFx = RENDERFX_NONE, RenderMode iMode = RENDER_NORMAL, int iRed = 255, int iGreen = 255, int iBlue = 255, int iAlpha = 255 )
{
    SetEntityRenderFx( iEntity, iFx );
    SetEntityRenderMode( iEntity, iMode );
    SetEntityRenderColor( iEntity, iRed, iGreen, iBlue, iAlpha );
}
10 Июн 2018
Хз почему таких элементарных вещей нет в этом вашем сурсе.
Код:
#define MIN(%0,%1)                ( ( ( %0 ) < ( %1 ) ) ? ( %0 ) : ( %1 ) )
#define MAX(%0,%1)                ( ( ( %0 ) > ( %1 ) ) ? ( %0 ) : ( %1 ) )
Код:
enum
{
    WEAPON_DEAGLE = 1, WEAPON_ELITE, WEAPON_FIVESEVEN, WEAPON_GLOCK, WEAPON_AK47 = 7, WEAPON_AUG, WEAPON_AWP,
    WEAPON_FAMAS, WEAPON_G3SG1, WEAPON_GALILAR = 13, WEAPON_M249, WEAPON_M4A1 = 16, WEAPON_MAC10, WEAPON_P90 = 19,
    WEAPON_UMP45 = 24, WEAPON_XM1014, WEAPON_BIZON, WEAPON_MAG7, WEAPON_NEGEV, WEAPON_SAWEDOFF,
    WEAPON_TEC9, WEAPON_TASER, WEAPON_HKP2000, WEAPON_MP7, WEAPON_MP9, WEAPON_NOVA, WEAPON_P250,
    WEAPON_SCAR20 = 38, WEAPON_SG556, WEAPON_SSG08, WEAPON_KNIFE = 42, WEAPON_FLASHBANG, WEAPON_HEGRENADE,
    WEAPON_SMOKEGRENADE, WEAPON_MOLOTOV, WEAPON_DECOY, WEAPON_INCGRENADE, WEAPON_C4,
    WEAPON_KNIFE_T = 59, WEAPON_M4A1_SILENCER, WEAPON_USP_SILENCER, WEAPON_CZ75A = 63, WEAPON_REVOLVER
};

char WEAPON_NAMES[ MAX_WEAPONS + 1 ][ ] =
{
    "", "weapon_deagle", "weapon_elite", "weapon_fiveseven", "weapon_glock", "", "", "weapon_ak47", "weapon_aug", "weapon_awp",
    "weapon_famas", "weapon_g3sg1", "", "weapon_galilar", "weapon_m249", "", "weapon_m4a1", "weapon_mac10", "", "weapon_p90",
    "", "", "", "", "weapon_ump45", "weapon_xm1014", "weapon_bizon", "weapon_mag7", "weapon_negev", "weapon_sawedoff",
    "weapon_tec9", "weapon_taser", "weapon_hkp2000", "weapon_mp7", "weapon_mp9", "weapon_nova", "weapon_p250",
    "", "weapon_scar20", "weapon_sg556", "weapon_ssg08", "", "weapon_knife", "weapon_flashbang", "weapon_hegrenade",
    "weapon_smokegrenade", "weapon_molotov", "weapon_decoy", "weapon_incgrenade", "weapon_c4", "", "", "", "", "",
    "", "", "", "", "weapon_knife_t", "weapon_m4a1_silencer", "weapon_usp_silencer", "", "weapon_cz75a", "weapon_revolver"
};
 
Сообщения
207
Реакции
420
Помог
10 раз(а)
Простейший define для обработки всех игроков на сервере.
C++:
#define LoopPlayers(%0) for (int %0 = MaxClients; %0 != 0; --%0) if (IsClientInGame(%0))
Пример использования из реального плагина (кое-что убрал):
C++:
public void OnGroupsInfoLoaded(Database hDB, DBResultSet hResults, const char[] szError, any data) {
  if (szError[0]) {
    LogError("[XF] Database failure when fetching groups information: %s", szError);
    return;
  }

  // ...

  if (g_bLate)
    LoopPlayers(iClient)
      if (!IsFakeClient(iClient) && IsClientAuthorized(iClient))
        LookupUserPermissions(iClient);
}
 
Сообщения
198
Реакции
273
Помог
5 раз(а)
CrazyHackGUT, я правильно понял, что там только однострочечная конструкция может использоваться?
 
Сообщения
207
Реакции
420
Помог
10 раз(а)
BoecSpecOPs, нет. Можно и несколько строк. Тогда в конце каждой строки (кроме последней) надо дописывать \. Экранизировать перевод каретки короче.
16 Июн 2018
На основе кода fantom отсюда сделал аналогичный конвертер строкового простейшего представления времени (1y3m, 1y2m4d5i6 и т.д.) - в секундное.
C++:
stock int UTIL_ParseTime(const char[] szStr) {
  static int  iTime[]   = {60,  3600, 86400, 2592000, 31104000};
  static char szTime[]  = "ihdmy";

  int iDummy, iResult;
  int iPos, iTPos;
  char cData;

  while (szStr[iPos] != EOS) {
    cData = szStr[iPos++];

    if (cData >= '0' && cData <= '9') {
      iDummy = (iDummy * 10) + (cData - '0');
      continue;
    }

    for (iTPos = 0; iTPos < sizeof(iTime); ++iTPos) {
      if (cData == szTime[iTPos]) {
        iResult += iDummy * iTime[iTPos];
        iDummy = 0;
        break;
      }
    }
  }

  iResult += iDummy;
  return iResult;
}
1529176815141.png
 
Сообщения
207
Реакции
420
Помог
10 раз(а)
Псевдо-генератор UUID v4
C++:
stock void GenerateUUID(char[] szBuffer, int iBufferLength) {
  FormatEx(szBuffer, iBufferLength, "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
    // 32 bits for "time_low"
    GetRandomInt(0, 0xffff), GetRandomInt(0, 0xffff),

    // 16 bits for "time_mid"
    GetRandomInt(0, 0xffff),

    // 16 bits for "time_hi_and_version"
    // four most significant bits holds version number 4
    (GetRandomInt(0, 0x0fff) | 0x4000),

    // 16 bits, 8 bits for "clk_seq_hi_res",
    // 8 bits for "clk_seq_low",
    // two most significant bits holds zero and one for variant DCE1.1
    (GetRandomInt(0, 0x3fff) | 0x8000),

    // 48 bits for node
    GetRandomInt(0, 0xffff), GetRandomInt(0, 0xffff), GetRandomInt(0, 0xffff)
  );
}
 
Сообщения
207
Реакции
420
Помог
10 раз(а)
Чистка ника от мультибайтовых символов размером 4 байта
Полезно, когда база в кодировке utf8, и перегонять в utf8mb4 нет возможности или просто отсутствие желания.
Оригинал написан Фениксом.
C++:
int GetFixedClientName(int iClient, char[] szBuffer, int iMaxLength) {
  char sName[MAX_NAME_LENGTH * 2 + 1];
  GetClientName(iClient, sName, sizeof(sName));

  for (int i = 0, len = strlen(sName), CharBytes; i < len;) {
    if ((CharBytes = GetCharBytes(sName[i])) == 4){
      len -= 4;
      for (int u = i; u <= len; u++) {
        sName[u] = sName[u+4];
      }
    } else {
      i += CharBytes;
    }
  }

  return strcopy(szBuffer, iMaxLength, sName);
}
 
Сообщения
10
Реакции
12
Проверка флагов из строки (на наличие одного из прописанных), например, char szFlag[] = { "abcz" }, т.к. ф-я ReadFlagString() занесет в переменную все флаги и, соотственно, затем проверка через GetUserFlagBits() проверит лишь на все флаги вместе. Моя же ф-я вернет true, если у игрока будет хотя бы один флаг, иначе - false.

C++:
stock bool CheckAdminFlagsByString(int iClient, const char[] szFlagString)
{
    AdminFlag aFlag;
    int iFlags;

    for (int i = 0; i < strlen(szFlagString); i++)
    {
        if(!FindFlagByChar(szFlagString[i], aFlag))     continue;
        iFlags |= FlagToBit(aFlag);
        if (GetUserFlagBits(iClient) & iFlags)
        {
            return true;
        }
    }
    return false;
}
 
Сообщения
141
Реакции
201
Помог
5 раз(а)
Rabb1t зачем мне конвертировать флаги из строки в int если я могу использовать например GetUserFlagBits(client) & (FLAG_1|FLAG_2)?
 
Сообщения
10
Реакции
12
juice, это пригодится для тех случаев, если требуется в конфиге или кваре указать необходимые флаги, которые откроют доступ к какому-то Вашему функционалу.
 
Сообщения
141
Реакции
201
Помог
5 раз(а)
juice, это пригодится для тех случаев, если требуется в конфиге или кваре указать необходимые флаги, которые откроют доступ к какому-то Вашему функционалу.
ок, почему бы мне не сделать так:
C-like:
int numchars;
ReadFlagString("abcz", numchars);
return (GetUserFlagBits(client) & numchars) != 0;
EDIT: потому что я не умею читать и то что написано выше не будет работать как было задумано, надо было сделать так:
return (GetUserFlagBits(client) & ReadFlagString("abcz")) != 0;
 
Последнее редактирование:
Сообщения
10
Реакции
12
juice, признаю, в таком случае работает корректно. 1552039987430.png

C++:
public void OnPluginStart()
{
    RegConsoleCmd("sm_test", Command_Test);
}

public Action Command_Test(int iClient, int args)
{
    int numchars;
    ReadFlagString("bz", numchars);
    if((GetUserFlagBits(iClient) & numchars) != 0)
        PrintToChat(iClient, "У тебя есть доступ.");
    else
        PrintToChat(iClient, "У тебя нет доступа.");
    return Plugin_Handled;
}
Видимо, у меня не работало должным образом, потому что использовал такой вариант: numchars = ReadFlagString("bz");
Учту, спасибо. Поставил бы лайк, да доступа нет. :D
 
Последнее редактирование:
Сообщения
141
Реакции
201
Помог
5 раз(а)
чёт я тоже ошибся :D

GetUserFlagBits(iClient) & ReadFlagString("bz") так будет корректно
 
Сообщения
207
Реакции
420
Помог
10 раз(а)
Может, пригодится кому. Писалось для себя, в свободное время.
В один прекрасный вечер надоело из плагина в плагин таскать код обновления базы данных. Так родилась идея вынести это в инклуду, да ещё и максимально абстрактно (настолько, насколько это возможно).
Идея заключается в последовательном приведении БД к нужной версии (все таблицы, колонки, и так далее). Так, как обычно запускаются миграции в том же Laravel.
Саму инклуду можно взять здесь. Писалось, повторюсь, в свободное время по вечерам (и пару раз ночью). Там же пример есть простой.
Сама инклуда будет обновляться, если будет необходимо.

Вкратце о том, как использовать. Тот же пример, что и по ссылке выше, но чуть подробный, и более откомментированный.
C++:
#include <sourcemod>
#include <dbi>
#include <dbupdater>

public void OnPluginStart()
{
    // Получаем указатель на соединение с базой данных любым удобным способом.
    //
    // Для примера, я буду получать его в один поток с игровым тиком.
    // В реальности, так делать не надо, ибо может вызвать фриз.
    char szError[256];
    Database hDb = SQL_Connect("my_database_configuration", true, szError, sizeof(szError));

    if (!hDB)
    {
        SetFailState("Database failure: %s", szError);
        return;
    }

    // Теперь инициализируем "мигратор".
    // Он на вход ничего не принимает при инициализации.
    // Для настроек, у него есть отдельные методы.
    DBUpdater_Start();

    // Настроим имя таблицы для хранения миграций (опционально).
    DBUpdater_SetTableName("mysuperplugin_migrations");

    // Настроим идентификатор плагина для хранения миграций (опционально).
    //
    // Желательно делать это: по-умолчанию, DBUpdater использует в качестве
    // идентификатора плагина - путь к нему относительно папки plugins.
    // Если плагин резко будет перемещён/переименован, DBUpdater попытается
    // ещё раз применить все миграции.
    //
    // Если плагин лежит по пути "/addons/sourcemod/plugins/test/teeest/core.smx",
    // то во внутренней таблице он получит идентификатор "test/teeest/core.smx".
    DBUpdater_SetPluginIdentifier("Core");

    // Для добавления миграций, используется метод "DBUpdater_Add()".
    //
    // На вход ему передаётся идентификатор версии (в виде числа)
    // и указатель на функцию, которая отвечает за генерацию запросов
    // для выполнения.
    //
    // Мы рекомендуем использовать следующий формат версий:
    // abbccde
    // Расшифровка: a.b.c d (alpha: 1, beta: 3, RC: 5, stable: 7, PL: 9) e
    // Например, версию 1001070 можно прочитать как "1.0.0".
    //
    // В DBUpdater входят вспомогательные макросы для добавления и
    // объявления самих функций-миграторов. Их можно выключить, объявив
    // перед подключением инклуды константу "_dbupdater_withoutmacros":
    // #define _dbupdater_withoutmacros
    // #include <dbupdater>
    //
    // В этом примере, мы не стали выключать, и покажем, как удобно их
    // использовать. Единственный макрос для добавления миграции в очередь
    // принимает только один аргумент: идентификатор версии.
    __DBUPDATER_ADD(1000070);
    __DBUPDATER_ADD(1001070);

    // Ещё можете включить "unsafe" режим. Он отличается от стандартного
    // тем, что Вы можете получать указатель на соединение с базой данных
    // посередине миграции, если не сохранили его. Делать так - НЕ
    // РЕКОМЕНДУЕТСЯ, но если очень хочется - всегда пожалуйста.
    // Но мы рекомендуем избегать использования соединения с БД в момент
    // работы "мигратора".
    // DBUpdater_MarkAsUnsafe();

    // Как только всё готово - можете запускать миграции.
    // "Раннер" на вход берёт три параметра:
    // - соединение с базой данных
    // - указатель на функцию, которая будет вызвана по окончанию работы
    // - любые кастомные данные. По-умолчанию, "0".
    // Обратите внимание: функция, которая будет вызвана по окончанию работы,
    // так же получит указатель на соединение с базой данных, но временный.
    // Если он Вам нужен - склонируйте его. Но лучше держите свой где-нибудь
    // в глобальных переменных.
    DBUpdater_Run(hDb, DBUpdater_OnFinished);

    // Всё. После этого, DBUpdater посмотрит на последнюю установленную миграцию,
    // и, если необходимо, запустит другие.
    // Если нужды нет - сразу вызовет переданную в раннер функцию.
}

// Объявляем сами миграции.
// Выше мы упоминали "удобные макросы". Один из таких - __DBUPDATER_MAKEMIGRATION().
// Он генерирует прототип функции и имя для неё, получая на вход только идентификатор
// версии.
// Например, "вызвав" его с аргументом 1000070, сгенерируется статическая функция с
// именем "_DBUpdater_1000070_Migration".
__DBUPDATER_MAKEMIGRATION(1000070)
{
    // В самой миграции нам доступны:
    // - int iInstalledVersion. Текущая установленная версия.
    // - int iProcessableVersion. Текущая обрабатываемая версия.
    // - Transaction hTxn. Транзакция, которая будет отправлена на выполнение по
    //                     завершению выполнения Вашей функции-"мигратора".
    // - DBDriver hDriver. Драйвер соединения с базой данных. Полезно, если Вы
    //                     поддерживаете несколько возможных драйверов (MySQL,
    //                     SQLite), и хотите отдавать запросы, которые точно
    //                     сработают на сервере.
    // - any data. Переданные данные при добавлении миграции. Для "макросов",
    //             это всегда "0".

    // типа создаём таблицы...
    hTxn.AddQuery(" ... ");
    hTxn.AddQuery(" ... ");
}

__DBUPDATER_MAKEMIGRATION(1001070)
{
    // В обновлении мы решили создать ещё пару таблиц, обновить старые.
    // Типа создаём...
    hTxn.AddQuery(" ... ");
    hTxn.AddQuery(" ... ");

    // Один из запросов зависит от драйвера.
    if (hDriver == view_as<DBDriver>(SQL_GetDriver("sqlite"))) // SQLite
    {
        // ...
    }
    else if (hDriver == view_as<DBDriver>(SQL_GetDriver("mysql"))) // MySQL
    {
        // ...
    }
    else if (hDriver == view_as<DBDriver>(SQL_GetDriver("pgsql"))) // PostgreSQL
    {
        // ...
    }

    // .. ещё какие-то сверхсложные запросы ..
}

// Функция, которой заканчивается исполнение всех миграций.
static void DBUpdater_OnFinished(int iInstalledVersion, Database hDB, const char[] szError, any data)
{
    // - int iInstalledVersion. Последняя успешно установленная версия.
    // - Database hDB. Соединение с базой, которое прилетело из последнего Database.Query()
    //                 ОБРАТИТЕ ВНИМАНИЕ: Этот указатель на соединение с базой умрёт по
    //                 завершению выполнения этой функции. Если он Вам нужен - склонируйте
    //                 его.
    // - char[] szError. Ошибка, если она произошла.
    // - any data. Кастомные данные, переданные в раннер.

    // ... суперсложная логика по обработке результатов, стопа плагина (если произошла ошибка) ...
}
 
Сообщения
207
Реакции
420
Помог
10 раз(а)
Абсолютно случайно вспомнил о SMLib. Это сборник (более 350+) стоков на разные случаи жизни. Сайт у них умер, но вот репозиторий на Гитхабе жив.
https://github.com/bcserv/smlib
 
Сообщения
10
Реакции
12
CrazyHackGUT, подогнал мне тут одну свою достаточно полезную сток функцию:
C++:
stock bool UTIL_IsInArray(any elem, any[] elements, int size)
{
    for (int i = size-1; i != -1; --i)
    {
        if (elements[i] == elem)
        {
            return true;
        }
    }

    return false;
}
Пример использования:
C++:
void func()
{
    int a = 5, b = 15, c = 25;
    if(UTIL_IsInArray(5, {a, b, c}, 3)) // 3 - потому что 3 элемента в массиве
    {
        // один из элементов прошел проверку (в данном случае a)
    }
    else
    {
        // ни один элемент не прошел проверку
    }
}
Объясняю зачем: так выглядит куда симпатичнее, нежели
C++:
void func()
{
    int a = 5, b = 15, c = 25;
    if(a == 5 || b == 5 || c == 5)
    {
        // истина если a, b или c равно(ы) 5 (в нашем случае a равно 5)
    }
    else
    {
        // ложь
    }
}
 

Пользователи, просматривающие эту тему

Сейчас на форуме нет ни одного пользователя.
Сверху Снизу