Склад функций SourcePawn

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #41
Цикл по игрокам
Простейший 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("[XenForo] Database failure when fetching groups information: %s", szError);
    return;
  }

  // ...

  if (g_bLate)
    LoopPlayers(iClient)
      if (!IsFakeClient(iClient) && IsClientAuthorized(iClient))
        LookupUserPermissions(iClient);
}
 

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #42
Преобразование строкового сокращённого времени в секунды
На основе кода F@nt0M с одного форума по CS 1.6 сделал аналогичный конвертер строкового простейшего представления времени (1y3m, 1y2m4d5i6 и т.д.) - в секундное.
C-подобный:
stock int 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;
}

1593621107187.png
 
  • Мне нравится
Реакции: NaN

Grey83

не пишу плагины с весны 2022
Сообщения
8,521
Реакции
4,980
Появилась у меня проблема с выводом в KeyHintText текста моего плагина в CSS, так чтобы другой плагин не перебивал сообщение от моего, но при этом и мой не перебивал сообщения других плагинов.
В общем решил объединить тексты от двух плагинов (точнее дописать мой текст в KeyHintText другого).

Вот такое решение получилось (это не кусок кода плагина, так тестовый код):
C-подобный:
#include <usermessages>

public OnPluginStart()
{
    // Ловим сообщения в KeyHintText от других плагинов
    HookUserMessage(GetUserMessageId("KeyHintText"), HookKeyHintText, true);
}

public Action HookKeyHintText(UserMsg msg_id, BfRead msg, const int[] players, int playersNum, bool reliable, bool init)
{
    static char txt[1024];
    BfReadByte(msg);    // Этот байт нам попросту не нужен
    BfReadString(msg, txt, sizeof(txt)); // Получаем текст сообщения

    // Дописываем свой текст, в данном случае ник игрока, которому предназначалось сообщение
    Format(txt, sizeof(txt), "%N\n%s", players[0], txt);
    // Записываем в датапак необходимые нам данные и отправляем их в каллбэк RequestFrame()
    // Это необходимор потому, что в одно и тоже время можно отсылать только одно сообщение
    DataPack data = new DataPack();
    RequestFrame(RequestFrame_Callback, data);
    data.WriteString(txt);
    data.WriteCell(players[0]);

    // Блочим вывод сообщения плагина
    return Plugin_Handled;
}

public void RequestFrame_Callback(DataPack data)
{
    // Получаем сохранённую инфу в DataPack
    ResetPack(data);
    static char txt[1024];
    data.ReadString(txt, sizeof(txt));
    int client = data.ReadCell();
    delete data;

    // И снова посылаем сообщение адресату, но уже дополненное нами
    // Флаг USERMSG_BLOCKHOOKS необходим чтобы наш плагин не хукался самим собой (правда и другие это не смогут сделать тоже)
    Handle msg = StartMessageOne("KeyHintText", client, USERMSG_BLOCKHOOKS);
    BfWriteByte(msg, 1);
    BfWriteString(msg, txt);
    EndMessage();
}
 
Последнее редактирование:

rejchev

менеджер клоунов
Сообщения
1,669
Реакции
1,291
Надстройка над strlen, подсчет количества символов по факту, игнорируя байтовую принадлежность:
C-подобный:
stock int numlts_stock(const char[] szSomeString)
{
    int a;
    
    for(int i, b; i < strlen(szSomeString); i++)
    {
        if((b = IsCharMB(szSomeString[i])))
            i += b-1;

        a++;
    }

    return a;
}
 

_wS_

Участник
Сообщения
383
Реакции
760
Я только сегодня осознал что SM всегда сравнивает int числа как signed int. Максимальное значение 2147483647 (19.01.2038). Если вы, например, вытащили из sql таблицы timestamp время или другое unsigned int, и оно превысило int лимит, и вы сравниваете это число с каким-то числом, то наступает полнейший фейл, например 2147483649 > 0 возвращает false.. Поэтому можете использовать эту функцию (или если вы знаете более оптимальный вариант, поделитесь).

C++:
// -1 (a < b), 0 (a == b), 1 (a > b)
int uintcmp(int a, int b)
{
    if (a == b) {
        return 0;
    }
    char a_[12], b_[12];
    FormatEx(a_, sizeof(a_), "%u", a);
    FormatEx(b_, sizeof(b_), "%u", b);
    int a_size = strlen(a_), b_size = strlen(b_);
    if (a_size > b_size) {
        return 1;
    }
    if (a_size < b_size) {
        return -1;
    }
    int i = -1;
    while (++i < a_size) {
        if (a_[i] > b_[i]) {
            return 1;
        }
        if (a_[i] < b_[i]) {
            return -1;
        }
    }
    return 0; // wtf
}

public void OnPluginStart()
{
    // max int 2147483647
    int a = 2147483649;
    int b = 1000;
    PrintToServer("%u > %u = %s", a, b, a > b ? "yes" : "no");
    PrintToServer("%u > %u = %s", a, b, uintcmp(a, b) == 1 ? "yes" : "no");
    
    // Результат:
    // 2147483649 > 1000 = no
    // 2147483649 > 1000 = yes
}
Сообщения автоматически склеены:

Есть еще такая проблема: SQL_FetchInt не знает что такое unsigned int.
У меня в таблице значение 4294967294 и вот что происходит при его извлечении:

C++:
char s1[12], s2[12];

int i = results.FetchInt(1);
FormatEx(s1, sizeof(s1), "%u", i);

results.FetchString(1, s2, sizeof(s2));
i = StringToInt(s2);
FormatEx(s2, sizeof(s2), "%u", i);

PrintToServer("%s\n%s", s1, s2);

// Результат:

// 2147483647 (omg)
// 4294967294

Спешат на помощь другие функции:

C++:
#define UINT_SIZE 12

// Преобразовать unsigned int в строку (IntToString не может) (return: Number of cells written)
stock int UIntToString(int value, char[] s, int size) {
    return FormatEx(s, size, "%u", value);
}

// Извлечь из поля unsigned int (SQL_FetchInt не может)
stock int SQL_FetchUInt(Handle results, int field, DBResult &result=DBVal_Error, int error=0)
{
    char s[UINT_SIZE];
    return SQL_FetchString(results, field, s, sizeof(s), result) && result == DBVal_Data ? StringToInt(s) : error;
}
 
Последнее редактирование:

Grey83

не пишу плагины с весны 2022
Сообщения
8,521
Реакции
4,980
Мимоходом запилил убирание незначащих нулей у float при форматировании текста.
Дефолтом у нас при форматировании без ограничения количества символов после десятичной запятой выводится 6 знаков.
PrintToServer("%f", 0.1); выведет 0.100000
Данная функция уберёт лишние нули (кроме самой первой, если другие цифры после запятой отсутствуют):
C-подобный:
stock char FloatRemoveZeros(float val)
{
    static char buffer[12];
    int i = FloatToString(val, buffer, sizeof(buffer))-1;
    while(i > 0 && buffer[i] == '0') i--;
    if(buffer[i] == '.') buffer[++i] = '0';
    buffer[++i] = 0;

    return buffer;
}
Правда Крузя говорит, что возвращение строкового массива функцией может спровоцировать утечки памяти в SM 1.8 (про более свежие версии информации у него нет).
Использовать в коде можно вот так:
C-подобный:
public void OnPluginStart()
{
    float f = 0.1;
    PrintToServer("%f = %s", f, FloatRemoveZeros(f));
}
В результате выведется следующая строка: 0.100000 = 0.1
 
Последнее редактирование:

xtance

Участник
Сообщения
513
Реакции
743
Как известно, в меню влезает очень мало текста (по слухам где-то 512 байт), и бывает что под конец он обрезается (вот так: �)
А ещё меньше влезает кириллицы (логично, хоть и не справедливо)
Впрочем, в кириллице и латинице существуют одинаково выглядящие буквы (аоеурсхАОЕСХРНВТКМЗ) 🤔
Этот инклюд (cmenu.inc) добавляет функции для автоматической замены этих букв на латиницу...
  • CString(char[] строка, int длина_строки)
  • CTitle(Menu меню, char[] заголовок, ...) = аналог SetMenuTitle
  • CItem(Menu меню, const char[] ключ, const char[] названия, int стиль, ...) = аналог AddMenuItem с поддержкой форматирования
"..." означает аргументы для подстановки в строку, если они есть
Чтобы использовать, закиньте cmenu.inc в папку scripting/include и в начало своего плагина добавьте #include <cmenu>
Пример плагина в файле cyrillicmenu.sp

Вот как было
20210811083904_1.jpg
И вот как стало
20210811083847_1.jpg

Конечно, чем больше используются вышеуказанные буквы, тем больше польза от инклюда
Код для перебирания строки отсюда !COMMAND eater (пожиратель команд)
 

Вложения

  • cyrillicmenu.sp
    3.4 КБ · Просмотры: 33
  • cmenu.inc
    2.2 КБ · Просмотры: 51
Последнее редактирование:

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #48
Как известно, в меню влезает очень мало текста (по слухам где-то 512 байт), и бывает что под конец он обрезается (вот так: �)
А ещё меньше влезает кириллицы (логично, хоть и не справедливо)
Впрочем, у нас есть одинаково выглядящие буквы (аоеурсхАОЕСХРНВТКМЗ) 🤔
Этот инклюд (cmenu.inc) добавляет функции для автоматической замены этих букв на латиницу...
  • CString(char[] строка, int длина_строки)
  • CTitle(Menu меню, char[] заголовок, ...) = аналог SetMenuTitle
  • CItem(Menu меню, const char[] ключ, const char[] названия, int стиль, ...) = аналог AddMenuItem с поддержкой форматирования
"..." означает аргументы для подстановки в строку, если они есть
Чтобы использовать, закиньте cmenu.inc в папку scripting/include и в начало своего плагина добавьте #include <cmenu>
Пример плагина в файле cyrillicmenu.sp

Вот как было
Посмотреть вложение 82537
И вот как стало
Посмотреть вложение 82538

Конечно, чем больше используются вышеуказанные буквы, тем больше польза от инклюда
Код для перебирания строки отсюда !COMMAND eater (пожиратель команд)
на ксс кириллица насколько я помню в меню жирнее чем латинница, если это до сих пор так - может выглядеть довольно странно
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,521
Реакции
4,980
@R1KO, так и есть (в v91 точно было).
Иногда приходилось вручную кириллицу на латиницу менять автозаменой в текстовом редакторе. Да и дальше пожалуй буду так делать.
 

Boeing 767

заскамили мамонта ни за что, ни про что
Сообщения
524
Реакции
913
Функция созданная для этой темы : [CS:S v34] Узнать количество сыгранных раундов

Для компиляции функции: Другое - [INC] Memory Extended
Сломаться функция может скорее всего только в csgo, css никто особо не трогает.
На основе этой функции можно получить любую переменную из CCSGameRules, просто поменяв смещение.

Где-нибудь вверху плагина #include MemoryEx и функцию в любом месте.
C-подобный:
int getTotalRoundsPlayed()
{
    static Address m_iTotalRoundsPlayed;
    BaseMemory mem;

    if( !m_iTotalRoundsPlayed )
    {
        switch(GetEngineVersion())
        {
            case Engine_SourceSDK2006:
            {
                int mp_dump_timers_callback[] = { 0x83,0xEC,0x0C,0xA1,0x2A,0x2A,0x2A,0x2A,0x85,0xC0,0x74,0x2A,0x89,0x04,0x24 };
                Pointer pFunc = g_hMem.FindPattern("server_i486.so", mp_dump_timers_callback, sizeof mp_dump_timers_callback );
           
                if( !pFunc )
                    SetFailState("outdated signature cssv34");

                mem.SetAddr( pFunc + PTR(0x4) );
                mem.SetAddr( mem.ReadInt() );
                m_iTotalRoundsPlayed = PTR(mem.ReadInt()) + PTR(560);
            }
            case Engine_CSS:
            {
                int mp_dump_timers_callback[] = { 0x55,0x89,0xE5,0x83,0xEC,0x08,0xE8,0x2A,0x2A,0x2A,0x2A,0x84,0xC0,0x74,0x2A,0xA1,0x2A,0x2A,0x2A,0x2A,0x85,0xC0 };
                Pointer pFunc = g_hMem.FindPattern("server_srv.so", mp_dump_timers_callback, sizeof mp_dump_timers_callback );
           
                if( !pFunc )
                    SetFailState("outdated signature cssob");

                mem.SetAddr( pFunc + PTR(0x10) );
                mem.SetAddr( mem.ReadInt() );
                m_iTotalRoundsPlayed = PTR(mem.ReadInt()) + PTR(612);
            }
            case Engine_CSGO:
            {
                int mp_functions_callback[] = { 0x55,0x89,0xE5,0x83,0xEC,0x08,0xE8,0x2A,0x2A,0x2A,0x2A,0x84,0xC0,0x74,0x2A,0xA1,0x2A,0x2A,0x2A,0x2A,0x85,0xC0,0x74,0x2A,0x89,0x45,0x08,0xC9,0xE9,0x2A,0x2A,0x2A,0x2A,0x8D,0xB4,0x26,0x00,0x00,0x00,0x00,0xC9,0xC3 };
                Pointer pFunc = g_hMem.FindPattern("server.so", mp_functions_callback, sizeof mp_functions_callback );
           
                if( !pFunc )
                    SetFailState("outdated signature csgo");

                mem.SetAddr( pFunc + PTR(0x10) );
                mem.SetAddr( mem.ReadInt() );
                m_iTotalRoundsPlayed = PTR(mem.ReadInt()) + PTR(3760);
            }
            default: SetFailState("unsupported game");
        }
    }

    mem.SetAddr( m_iTotalRoundsPlayed );
    return mem.ReadInt();
}
 
Последнее редактирование:

Kruzya

Участник
Сообщения
12,970
Реакции
10,914
  • Команда форума
  • #51
Подготавливая апдейт для своего ядра демок, и в очередной раз "задрючившись" не забывать закрывать хендлы жсонов, придумал небольшой сток. Чистоты кода ради - он тут же возвращает переданный на него хендл.
lazy_handle.inc:
/**
 * Requests the closing handle in next frame and returns passed handle.
 *
 * @param     hHandle   Handle for lazy closing.
 * @return              Passed handle.
 */
stock Handle LazyCloseHandle(Handle hHandle)
{
  if (hHandle)
  {
    RequestFrame(OnHandleShouldBeClosed, hHandle);
  }

  return hHandle;
}

static void OnHandleShouldBeClosed(Handle hHndl)
{
  hHndl.Close();
}

Идея заключается в том, чтобы "запланировать закрытие хендла" в следующем кадре (т.е. когда-то после завершения выполнения функции). Это не подходит для ситуаций, когда одна функция создаёт единомоментно - много хендлов (больше 5000, например), но там и сам Сурсмод, на самом деле, "разозлится" на частое создание и удаление хендлов, и выгрузит плагин. Оффтоп
Ну и коротенький пример, как это можно использовать: есть натив, который записывает какие-то данные в JSON-массив, который достаётся из JSON-объекта. Кто работал с рипом - знает, что любая попытка достать JSON-объект или массив, создаёт хендл для него, что заставляет постоянно следить за этим.
AutoDemo.sp:
/**
 * Params for this native:
 * -> szEventName (string const)
 * -> hMetaData (StringMap)
 */
public int API_TriggerEvent(Handle hPlugin, int iNumParams) {
  if (!g_bRecording)
    return; //ignore this event.

  char szEventName[64];
  GetNativeString(1, szEventName, sizeof(szEventName));

  StringMap hEventData = GetNativeCell(2);
  if (hEventData) {
    hEventData = view_as<StringMap>(UTIL_LazyCloseHandle(CloneHandle(hEventData, g_hCorePlugin)));
  }

  any data = (iNumParams < 3 ? 0 : GetNativeCell(3));
  if (!UTIL_TriggerEventListeners(szEventName, sizeof(szEventName), hEventData, data))
  {
    return;
  }

  JSONArray hEvents = view_as<JSONArray>(UTIL_LazyCloseHandle(g_hMetaInfo.Get("events")));
  JSONObject hEvent = view_as<JSONObject>(UTIL_LazyCloseHandle(new JSONObject()));
  hEvent.SetInt("time", GetTime());
  hEvent.SetInt("tick", SourceTV_GetRecordingTick());
  hEvent.SetString("event_name", szEventName);
  hEvent.Set("data", view_as<JSON>(UTIL_LazyCloseHandle(UTIL_StringMapToJSON(hEventData))));
  hEvents.Push(hEvent);
}
Ну и для сравнения, этот же код, но без этого стока:
AutoDemo.sp:
/**
 * Params for this native:
 * -> szEventName (string const)
 * -> hMetaData (StringMap)
 */
public int API_TriggerEvent(Handle hPlugin, int iNumParams) {
  if (!g_bRecording)
    return; //ignore this event.

  char szEventName[64];
  GetNativeString(1, szEventName, sizeof(szEventName));

  StringMap hEventData = GetNativeCell(2);
  if (hEventData) {
    hEventData = view_as<StringMap>(CloneHandle(hEventData, g_hCorePlugin));
  }

  any data = (iNumParams < 3 ? 0 : GetNativeCell(3));
  if (!UTIL_TriggerEventListeners(szEventName, sizeof(szEventName), hEventData, data))
  {
    hEventData.Close();
    return;
  }

  JSONArray hEvents = view_as<JSONArray>(g_hMetaInfo.Get("events"));
  JSONObject hEvent = new JSONObject();
  JSONObject hEventDataJSON = UTIL_StringMapToJSON(hEventData);

  hEvent.SetInt("time", GetTime());
  hEvent.SetInt("tick", SourceTV_GetRecordingTick());
  hEvent.SetString("event_name", szEventName);
  hEvent.Set("data", hEventDataJSON);
  hEvents.Push(hEvent);

  hEventDataJSON.Close();
  hEvents.Close();
  hEvent.Close();
}
 
Последнее редактирование:

HolyHender

Прокуратура на связи
Сообщения
271
Реакции
394
Прыгает к самой последней позиции DataPack.
Удобно, когда не знаешь количество записанных данных в DataPack, и тебе нужно получить последнее значение.

C-подобный:
/**
 * Прыгает к самой последней позиции DataPack.
 *
 * @param data          DataPack Handle.
 * @return              False - Invalid DataPack.
 */
stock bool JumpToLastPackItem(DataPack data)
{
    if(!data)
    {
        return false;
    }

    data.Reset();
    DataPackPos pos;
   
    while(data.IsReadable())
    {
        data.Position = ++pos;
    }

    data.Position = --pos;
    return true;
}
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,521
Реакции
4,980
@HolyHender, а почему возвращает bool, а не int?
Кмк, int возвращать имело бы больше смысла.
Ну и выдавать 0 при пустом датапаке, а при несуществующем -1 (хотя это нужно проверять задолго до вызова этой функции).
 

Vit_ amin

Добрая душа
Сообщения
1,504
Реакции
660
Конвертация числа типа int в формат чч:мм:сс (если часов 0, то соотвественно они не пишутся), для форматирования и расчетов используется функция из engine.so (в моем случае игра CS: Source), то я думаю что и для других игр на движке Source она будет действительна (ТОЛЬКО UNIX, Win мне лень делать, если надо, найду сигнатуру)
GameData:
C-подобный:
            COM_FormatSeconds
            {
                library    "engine"
                linux    "@_Z17COM_FormatSecondsi"
            }
Code:
C-подобный:
    StartPrepSDKCall(SDKCall_Static);

    PrepSDKCall_SetFromConf(g_hGameData, SDKConf_Signature, "COM_FormatSeconds");
    PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_ByValue);
    PrepSDKCall_SetReturnInfo(SDKType_String, SDKPass_Pointer);

    Handle hFormatSeconds = EndPrepSDKCall();

    char     szText[16];
    int        iValue = "VALUE"

    SDKCall(hFormatSeconds, szText, sizeof(szText), iValue);

    PrintToServer("szText: %s", szText);
 
Последнее редактирование:

Nekro

Терра инкогнита
Сообщения
4,025
Реакции
2,260
@Kailo, вроде можно проще разбить секунды на большие промежутки времени
Вот так, например (собственно разбиение занимает всего 1 строку):
PHP:
static const char sName[][][] =
{
    {"день",    "дня",        "дней"},
    {"час",        "часа",        "часов"},
    {"минута",    "минуты",    "минут"},
    {"секунда",    "секунды",    "секунд"}
};

public void OnPluginStart()
{
    int time = GetTime() - 1471777116;
    // вот эту строку, где форматируется текст
    PrintToServer("Время, которое прошло после написания поста:%s%s%s%s", NumToName(time/3600/24, 0), NumToName(time/3600%24, 1), NumToName(time/60%60, 2), NumToName(time%60, 3));
}

stock char NumToName(int num, const int type)
{
    static char buffer[PLATFORM_MAX_PATH];
    buffer[0] = 0;

    int form;
    switch(num)
    {
        case 0:        return buffer;
        case 1:        form = 0;
        case 2,3,4:    form = 1;
        default:
        {
            if(num < 21)    form = 2;
            else switch(num%10)
            {
                case 1:        form = 0;
                case 2,3,4:    form = 1;
                default:    form = 2;
            }
        }
    }
    FormatEx(buffer, sizeof(buffer), " %i %s", num, sName[type][form]);
    return buffer;
}
[ANY] Определение окончания слова день (дня, дней)
Ежели у кого из добрых людей будет минутка, то переделайте на php пожалуйста. Если нет, как смогу сам выложу
Сообщения автоматически склеены:

Лень матушка, но позже надо будет перевести, а пока вот так:
PHP:
function time_diff_conv($start, $s) {
    $t = array( //suffixes
        'д' => 86400,
        'ч' => 3600,
        'м' => 60,
    );
    $s = abs($s - $start);
    foreach($t as $key => &$val) {
        $$key = floor($s/$val);
        $s -= ($$key*$val);
        $string .= ($$key==0) ? '' : $$key . "$key ";
    }
    return $string . $s. 'с';
}
Источник PHP: time - Manual
 
Последнее редактирование:

Vit_ amin

Добрая душа
Сообщения
1,504
Реакции
660
Парсирует строчку в переменной до появления пробела (используется как native функция BreakString из SourceMod, но как по мне для моей конкретной задачи она мне больше понравилась), только Linux, игра CS: Source OrangeBox (по требованию могу найти для Win32 или другой игры)
GameData
C-подобный:
     CVEngineServer::IVEngineServer
     {
          library    "engine"
          linux    "@_ZL46__CreateCVEngineServerIVEngineServer_interfacev"
     }

Plugin:
C-подобный:
// ===================================
// VTABLE INDEX START
// ===================================
#define VTABLE_PARSE_FILE        56        // const char*    IVEngineServer::ParseFile(const char*, char*, int)
// ===================================
// VTABLE INDEX END
// ===================================

#define DATA_MAXLEN                32        // Check methodmap ParseFile (TODO: maybe need more that 32, but now it's enough)

static Address    g_pEngine;                // IVEngineServer pointer
static Handle     m_hParseFile;

// ===================================
methodmap IVEngineServer
{
   // ===================================
   // START PROPERTIES
   // ===================================
   property IVEngineServer Pointer
   {
       public get()
       {
           return view_as<IVEngineServer>(g_pEngine);
       }
   }
   // ===================================
   // END PROPERTIES
   // ===================================

   /**
     * Parse a token from a file
     *
     * @return              Token string
     */
   public const char ParseFile(const char[] szReturn, char[] szToken, const int iTokenLen)
   {
       return SDKCall(m_hParseFile, this.Pointer, szReturn, DATA_MAXLEN, szReturn, szToken, iTokenLen);
   }
}

public void ConnectEngineInterface()
{
   // ===================================
   {
       StartPrepSDKCall(SDKCall_Static);

       PrepSDKCall_SetFromConf(g_hGameData, SDKConf_Signature, "CVEngineServer::IVEngineServer");
       PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);

       Handle hEngineServer = EndPrepSDKCall();
       g_pEngine = SDKCall(hEngineServer);

       delete hEngineServer;
   }
   // ===================================
   {
       StartPrepSDKCall(SDKCall_Raw);
 
       PrepSDKCall_SetVirtual(VTABLE_PARSE_FILE);
       PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer);        // const char*     data
       PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer);        // char*         token
       PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);    // int             maxlen
       PrepSDKCall_SetReturnInfo(SDKType_String, SDKPass_Pointer);
 
       m_hParseFile = EndPrepSDKCall();
   }
   // ===================================
}

Использование в плагине:
C-подобный:
    char szText[] = "Test Message";
    char szToken[16];
    IVEngineServer pEngine;
    do
    {
        pEngine.ParseFile(szText, szToken, sizeof(szToken));
        PrintToServer("Message: %s", szToken);
    }
    while (szText[0]);

Вывод:
C-подобный:
Message: Test
Message: Message
 
Последнее редактирование:

Vit_ amin

Добрая душа
Сообщения
1,504
Реакции
660
Правильный подсчет UNICODE символов с использованием кодировки UTF-8 (native поддержка из SourceMod) поддержка до 6 байтных символов (игра CS: Source Linux), но возможно сигнатура подойдет и для других Source игр:
GameData:
C-подобный:
            UnicodeLength
            {
                linux    "@_Z15Q_UnicodeLengthPKc"
            }
Plugin:
C-подобный:
   // U+1F600 (Grinning Face)
   // UTF-8 - 0xF0 0x9F 0x98 0x80 (4 байта)
   char[] szText = "😀";
   // int __cdecl Q_UnicodeLength(char *)
   StartPrepSDKCall(SDKCall_Static);

   PrepSDKCall_SetFromConf(g_hGameData, SDKConf_Signature, "UnicodeLength");

   PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer);
   PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);

   Handle hTest = EndPrepSDKCall();

   int iSize = SDKCall(hTest, szText);

   PrintToServer("Size: %i", iSize);
   PrintToServer("strlen: %i", strlen(szText));

Вывод:
C-подобный:
Size: 1
strlen: 4
 
Последнее редактирование:

Grey83

не пишу плагины с весны 2022
Сообщения
8,521
Реакции
4,980
Запилил аналог функции ExplodeString(), но с сохранением результата не в массив строк, а в StringMap (полезно, если нужно сделать список без повторений, кроме того можно дописывать новые строки):
ExplodeString2StringMap:
/**
 * Breaks a string into pieces and stores each piece into a StringMap.
 *
 * @param text              The string to split.
 * @param split             The string to use as a split delimiter.
 * @param str_map           StringMap to store chunks of text.
 * @param add               False (default) to keep only new strings, true add strings to existing strings.
 * @param copyRemainder     False (default) discard excess pieces, true to ignore
 *                          delimiters after last piece.
 * @return                  Number of strings retrieved.
 */

stock int ExplodeString2StringMap(const char[] text, const char[] split, StringMap &str_map, const bool add = false, const bool copyRemainder = false)
{
    // если StringMap не существует или разделитель - пустая строка, то завершаем выполнение разбивки
    if(!str_map || !split[0])
        return 0;

    int num;
    if(!add) str_map.Clear();
    else num = str_map.Size

    int len = strlen(text), bytes = GetCharBytes(split);
    char[] source = new char[len+1];

    int i, size, start;
    while(i < len && text[i])
    {
        if(text[i] == split[0] && (bytes == 1 || !strncmp(split, text[i], bytes)))    // разделитель обнаружен
        {
            // вычисляем размер копируемого куска и сохраняем его, если он не нулевой
            if((size = i - start) > 0)
            {
                strcopy(source, size+1, text[start]);
                if(TrimString(source))    // удаляем пробелы в начале и конце куска
                    str_map.SetValue(source, start, true);    // сохраняем кусок строки в StringMap
            }

            start = i+bytes;    // запоминаем положение первого символа после разделителя
            if(bytes > 1) i = start - 1;    // смещаем положение проверки на размер разделителя, если он больше одного байта
        }
        i++;
    }

    if(copyRemainder && (size = len - start) > 0)
    {
        strcopy(source, size+1, text[start]);
        if(TrimString(source))    // удаляем пробелы в начале и конце куска
            str_map.SetValue(source, start, true);    // сохраняем кусок строки в StringMap
    }

    return str_map.Size - num;
}
В прикреплённом тестовый плагин для проверки работы кода (содержит результат запуска в качестве комментария).
Сообщения автоматически склеены:

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

Вложения

  • ExplodeString2StringMap.sp
    4.3 КБ · Просмотры: 6

Vit_ amin

Добрая душа
Сообщения
1,504
Реакции
660
Получение индекса бомбы (weapon_c4) без расчетов в скрипте SourcePawn (используется структура CUtlVector под C4 бомбу из движка Source), (UNIX only) (CSS:OB only)
Также если на сервере имеется количество бомб больше чем 1, то изменив чуть код можно узнать индекс каждой бомбы, а количество получается через g_C4s.Count
GameData:
C-подобный:
Games
{
    cstrike
    {
        Addresses
        {
            g_C4s.Index
            {
                read        "66"    // 0x42
                signature    "CC4::CC4"
            }
            g_C4s.Count
            {
                read        "56"    // 0x38
                signature    "CC4::CC4"
            }
        }
        Signatures
        {
            CC4::CC4
            {
                linux    "@_ZN3CC4C2Ev"
            }
        }
    }
}

Plugin:
C-подобный:
// Code ...

Address g_pC4Count = GameData.GetAddress("g_C4s.Count");
Address g_pC4Index = GameData.GetAddress("g_C4s.Index");


#define GET_C4_COUNT Dereference(g_pC4Count, .iType = NumberType_Int8)
#define GET_C4_INDEX Dereference(Dereference(Dereference(Dereference(g_pC4Index)), 0x20), 0x06, NumberType_Int16)

stock any Dereference(any pBase, any iOffset = 0, NumberType iType = NumberType_Int32)
{
   return LoadFromAddress(pBase + OFFSET(iOffset), iType);
}

static Action ConCmd(int iClient, int iArgC)
{
   if (!GET_C4_COUNT)
   {
       return Plugin_Handled;
   }

   PrintToServer("C4 Index: %i", GET_C4_INDEX);

   return Plugin_Handled;
}

// Code ...

Benchmark:
C-подобный:
// code ...
static Action ConCmd_Test(int iClient, int iArgC)
{
   g_hTest.Start();
   int iEntity = FindEntityByClassname(MaxClients + 1, "weapon_c4");

   g_hTest.Stop();

   PrintToServer("Time: %f", g_hTest.Time);

   return Plugin_Handled;
}

static Action ConCmd_Test2(int iClient, int iArgC)
{
   g_hTest.Start();
   int iEntity = GET_C4_INDEX;

   g_hTest.Stop();

   PrintToServer("Time: %f", g_hTest.Time);
   return Plugin_Handled;
}
// code ...
Result:
C-подобный:
sm_test
Time: 0.000018
sm_test2
Time: 0.000001
 
Последнее редактирование:

Dragokas

Добрая душа
Сообщения
229
Реакции
213
Нормализация регистра символов слов в предложении.

"ПРИВЕТ из БУНКЕРА." => "Привет из Бункера."

Работает по правилу:
1-я буква 1-го слова - Большая.
1-я буква остальных слов - не трогаем.
Остальные буквы - маленькие.

Сделано на основе слегка модифицированных стоков от komashchenko.
Следовательно, поддерживает любые языки (не только RU/EN).

C-подобный:
public void OnPluginStart()
{
    char[] test = "ПРИВЕТ из БУНКЕРА.";
    NormalizeName(test);
    PrintToChatAll(test); // выведет "Привет из Бункера."
}

void NormalizeName(char[] str)
{
    if( !str[0] )
        return;
  
    UTF8CharToUpper(str, 0, GetCharBytes(str[0]));
  
    int bytesToProcess, startByte = GetCharBytes(str[0]), max_len = strlen(str);
  
    // start from 2nd letter
    for( int i = startByte; i < max_len; i += GetCharBytes(str[i]) )
    {
        if( IsCharSpace(str[i]) )
        {
            if( bytesToProcess )
            {
                UTF8CharToLower(str, startByte, startByte + bytesToProcess);
            }
            // skip first letter of next words
            startByte = i + 1 + (i+1 < max_len ? GetCharBytes(str[i+1]) : 0 );
            bytesToProcess = 0;
            ++i;
        }
        else {
            bytesToProcess += GetCharBytes(str[i]);
        }
    }
    if( bytesToProcess ) // process leftovers
    {
        UTF8CharToLower(str, startByte, startByte + bytesToProcess);
    }
}

// Made by komashchenko: https://hlmod.ru/resources/inc-utf8-string.898/
// Forked.
/**
 * Делает с нижнего регистра верхний.
 *
 * @param string            строка для конвертации.
 * @param int                начальный байт обработки.
 * @param end                конечный байт обработки.
 * @return        количество символов которые были переведены в верхний регистр 
 */
stock int UTF8CharToUpper(char[] string, int start = 0, int end = -1)
{
    int I = start, O = end == -1 ? strlen(string) : end, Byte, K;
    while(I < O)
    {
        Byte = string[I];
        if(Byte >= 128)
        {
            if(Byte >= 240)
            {
                if(strlen(string[I]) < 4) break;
                else I += 4;
            }
            else if(Byte >= 224)
            {
                if(strlen(string[I]) < 3) break;
                else I += 3;
            }
            else if(Byte >= 192)
            {
                if(strlen(string[I]) < 2) break;
                Byte = (Byte % 32) * 64;
                Byte += (string[I+1] % 64);
                if(1072 <= Byte <= 1103 || Byte == 1105 || 1110 <= Byte <= 1111 || Byte == 1169)
                {
                    if(1105 <= Byte <= 1111) Byte = Byte-80;
                    else if(Byte != 1169) Byte = Byte-32;
                    else Byte--;
                    string[I]   = 192 + RoundToFloor(float(Byte / 64));
                    string[I+1] = 128 + (Byte % 64);
                    K++;
                }
                I += 2;
            }
            else break;
        }
        else
        {
            if(97 <= Byte <= 122)
            {
                string[I] = Byte-32;
                K++;
            }
            I += 1;
        }
    }
    return K;
}

// Made by komashchenko: https://hlmod.ru/resources/inc-utf8-string.898/
// Forked.
/**
 * Делает с верхнего регистра нижний.
 *
 * @param string            строка для конвертации.
 * @param int                начальный байт обработки.
 * @param end                конечный байт обработки.
 * @return        количество символов которые были переведены в нижний регистр 
 */
stock int UTF8CharToLower(char[] string, int start = 0, int end = -1)
{
    int I = start, O = end == -1 ? strlen(string) : end, Byte, K;
    while(I < O)
    {
        Byte = string[I];
        if(Byte >= 128)
        {
            if(Byte >= 240)
            {
                if(strlen(string[I]) < 4) break;
                else I += 4;
            }
            else if(Byte >= 224)
            {
                if(strlen(string[I]) < 3) break;
                else I += 3;
            }
            else if(Byte >= 192)
            {
                if(strlen(string[I]) < 2) break;
                Byte = (Byte % 32) * 64;
                Byte += (string[I+1] % 64);
                if(1040 <= Byte <= 1071 || Byte == 1025 || 1030 <= Byte <= 1031 || Byte == 1168)
                {
                    if(1025 <= Byte <= 1031) Byte = Byte+80;
                    else if(Byte != 1168) Byte = Byte+32;
                    else Byte++;
                    string[I]   = 192 + RoundToFloor(float(Byte / 64));
                    string[I+1] = 128 + (Byte % 64);
                    K++;
                }
                I += 2;
            }
            else break;
        }
        else
        {
            if(65 <= Byte <= 90)
            {
                string[I] = Byte+32;
                K++;
            }
            I++;
        }
    }
    return K;
}
 
Последнее редактирование:
Сверху Снизу