Иконка ресурса

Player preferences 1.0.7

Нет прав для скачивания

cpCTRL

bruh
Скриптер
Постоялец
Регистрация
20 Фев 2018
Сообщения
263
Симпатии
65
Пол
Мужской
Я не стал бы это делать, пока не увижу, что из БД получены корректные данные, для которых это нужно
они перезаписываются в любом случае.
Если были некорректно получены, в массив занесены не будут, но ведь сторонние плагины пишут и при дисконнекте перезапись сделает своё дело.
Можно ещё для пущего сделать создание на конкретном участке (при добавлении значений с других плагинов), дабы не создавать если соединение с бд не было установлено, либо чэндж значения был заблокирован сторонним плагином
 

georgeml

Скриптер
Постоялец
Регистрация
12 Сен 2017
Сообщения
595
Симпатии
314
они перезаписываются в любом случае.
Это тоже перезаписывается в любом случае, однако, оно здесь есть зачем-то.

C++:
if (TrieKeyExists(g_tPlayerPreferences[id], key))  {
        TrieGetString(g_tPlayerPreferences[id], key, value, MAX_VALUE_STRING_LENGTH - 1);
    }
TrieKeyExists зачем здесь. TrieGetString вернет true, если возьмет строку

C++:
ExecuteForward(g_eForwards[Fwd_KeyChanged], fwReturn, id, key);

    if (g_hTuple == Empty_Handle || fwReturn == PLUGIN_HANDLED) {
        return -1;
    }
fwReturn == PLUGIN_HANDLED не сработает. Форвард отправится и пойдет дальше, потому что ET_IGNORE
Так и не увидел пример... Не понятен смысл хранения разных данных в разных массивах, если читается все строкой и записывается строкой.
 

angrybot

Администратор
Модератор
Разработчик
Постоялец
Пользователь
Дизайнер
Регистрация
15 Дек 2019
Сообщения
166
Симпатии
60
От этого толку нет. Если боитесь, что на место игрока зайдет другой, успокойтесь. Для него тоже будет вызван запрос и эти, "типо не его" данные, все равно перезапишутся.
как раз толк от этого есть, раз нет, зачем нам нужны проверки на -1 userid в самом engine? давай просто удалим оттуда userid. эти "типа не его" данные прийдут в каллбек который он в будущем реализует и получиться что один игрок может получить доступ к чужим возможностям, вот как раз отсутствие подобных проверок в твоем плагине, game cms api и является тем странным.

cpCTRL покожи актуальный код.
 

angrybot

Администратор
Модератор
Разработчик
Постоялец
Пользователь
Дизайнер
Регистрация
15 Дек 2019
Сообщения
166
Симпатии
60
//вылетает с сервера без вызова client_disconnected(id1), довольно часто встречается
это возможно лишь в случаях когда пользователь даже client_connect не прошел, либо не прошел putinserver, есть форвард client_remove, можно ловить там, но он не нужен в случае с сохранением данных, те кто прошел putinserver всегда получаут disconnected, в отличии от disconnect.

Либо заводить отдельный хендлер.
либо смотреть внимательно код

Ну еще в plugin_end если возможно нужно тоже добавить проверку всех игроков, если вдруг disconnected и putinserver не были вызваны.
зачем? эти игроки даже не появятся в этих списках, для всех валидных игроков вызовится disconnected при смене карты.

О чем и говорю. Не логично связывать несвязные вещи.
Логику не видите. После захода другого игрока будет новый запрос с новым ответом.
Ответ выше. В нем есть логика и она подтверждена в работе. А писать кучу ненужных проверок- на любителя.
это был сарказм, как раз в движке сделали тоже самое, везде делают так, в любом нормальном проекте где нужна уникальность, давай тогда все uuid выбросим тоже, а чего, ведь у нас есть индекс слота.


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

fantom

Разработчик
Регистрация
11 Июн 2017
Сообщения
426
Симпатии
293
Пол
Мужской
georgeml, что будет, если настройка дает возможность кикать, игрок тут же вышел, на его слот зашел игрок и его загрузка зафейдилась? Верно, он будет иметь настройки другого игрока. Проверка на юзерид критически важна, как и большинство других проверок. Раз в год и палка стреляет и то, что вроде как должно оаботать увы уже не работает. Глупо говорить не делать проверку основываясь на собственных убеждениях
 

fl0wer

Пользователь
Регистрация
9 Июл 2017
Сообщения
10
Симпатии
21
Пол
Мужской
либо смотреть внимательно код
Фикс я предоставил и с реального сервера где установлен плагин, который вызывал ошибки.
Если не шаришь, то лучше ничего не пиши, а то так и будешь фулл-стэком вместо глубокого изучения (переход на личности = бан).
Удачи, продолжайте говнокодить.
Вам надо-то нормально загрузить игрока через треды и уже с его айдишником работать. При диссконекте просто обновлять данные, если игрок загружен. Обосрались как всегда.
 
Последнее редактирование:

angrybot

Администратор
Модератор
Разработчик
Постоялец
Пользователь
Дизайнер
Регистрация
15 Дек 2019
Сообщения
166
Симпатии
60
не учи ученных. кому ты нужен? нарушаешь? получаешь баллы. никто не обращает внимание даже уже на тебя, много о тебе рассказали, много посмеялася.

продолжишь оффтопить или заниматься дезинформацией, конкретно в эта тема мы временно защищаем cpCTRL от неправильных советов - продолжишь получать баллы, да, тебе на них без разницы, но это не dev-cs.ru хороший пример - [Цена снижена] Продам Ложную гранату (Декой) из CS:GO для CS 1.6 где всем плевать на твое отношение и твои глупые советы потому что больше никого найти не могут и еще сделать разработчик

C++:
int *memory = new int[0];
printf("ptr is %p - %d\n", memory, *memory);

// output
// ptr is 0x183deb0 - 0
// dereferencing is available


C++:
void * operator new[](size_t size){
    return (calloc(1, size));
}
int *memory = new int[0];
printf("ptr is %p - %d\n", memory, *memory);

// output
// ptr is 0x183deb0 - 0
// dereferencing is available
C++:
//native SQL_ThreadQuery(Handle:cn_tuple, const handler[], const query[], const data[]="", dataSize=0);
C++:
static cell tmpdata[1] = {0};
data_addr = MF_PrepareCellArray(tmpdata, 1);
C++:
#define CHECK_NATIVE_PLAYER(%1,%2) \
    if (!g_bConnected[%1]) { \
        DEBUG && log_error(AMX_ERR_NATIVE, "Invalid player %d", %1); \
        return %2; \
    }

new id = data[0];

CHECK_NATIVE_PLAYER(id, PLUGIN_HANDLED)

The calloc function allocates space for an array of nmemb objects, each of whose size is size. The space is initialized to all bits zero. Returns: The calloc function returns either a null pointer or a pointer to the allocated space.

georgeml как fantom и писал это один из случаев, есть случаи с задержкой загрузки данных, они частые, то что это вам не встречается не значит что этого нет, советы не связанные с деградацией проверок на валидность приветствуються.
 

georgeml

Скриптер
Постоялец
Регистрация
12 Сен 2017
Сообщения
595
Симпатии
314
Верно, он будет иметь настройки другого игрока.
Чтобы такого не было, нужно правильно и вовремя делать очистку данных.

Глупо говорить не делать проверку основываясь на собственных убеждениях
Этот "типа глупый" совет оставили, а остальные найденные жуткие косяки удалили. Ну ок, ваше право.
Не слушайте совет, если не согласны с ним. Не вижу поводов, чтобы цепляться к нему и усираться в доказательстве обратного.
Хотите что-то доказать?- напишите грамотно реально работающий плагин. На данный момент, я не увидел ни одного!
И да- я не даю советы. Я делюсь опытом.
 

cpCTRL

bruh
Скриптер
Постоялец
Регистрация
20 Фев 2018
Сообщения
263
Симпатии
65
Пол
Мужской

angrybot

Администратор
Модератор
Разработчик
Постоялец
Пользователь
Дизайнер
Регистрация
15 Дек 2019
Сообщения
166
Симпатии
60
register_native("pp_is_user_loaded", "native_is_loaded"); не вижу реализации

и еще по хорошему сделал бы как писал, два плагина, один из которых просто вызывает подгрузку и так далее как описывали, ты сделал каллбек модель.
тоже соидет, но я не понимаю логику, если хочется так то оставляй так.
насчет плагина не подскажу так как не вижу пока что никаких примеров использования и так далее, лично сам не вижу какие нибудь полезные случаи где можно это использовать, шаг влево вправо и придется что-нибудь переделывать чтобы совместить с чем-нибудь что требует другой реализации,
если у тебя Trie и ты не беспокоишься о типах - можешь использовать его дальше, то есть если тебе вдруг нужно будет проверять что за тип данных приходит в Trie ты не сможешь этого сделать, то есть объединить все эти нативы в один общий не получится, техническая возможность при этом присутствует, тебе надо тогда просто удалить глобальный json для игрока и в Trie все как строки помещать, либо в Array как строки, ключ значение делать, ты все равно в конце сможешь один раз сформировать json объект из строк, в цикле с ArrayGetString, так как json поддерживает лишь строки, json 5 уже можешь обойтись без стрингификации примитивных типов.

либо можешь совместить как в этом плагине, там связка Array и Trie.
 

karaulov

Скриптер
Постоялец
Пользователь
Регистрация
5 Май 2019
Сообщения
1.041
Симпатии
357
Пол
Мужской
Скажите лучше версия где данные сохраняются "никнейм + стим" уже есть ?) Что бы если компьютером пользуется несколько человек, для них разная инфа сохранялась. (Ну там клуб компьютерный, если такие еще существуют, или банально компьютером пользуются родственники/друзья)

А то я просто свой вариант не использую жду когда появится "официальная" версия :)
 

cpCTRL

bruh
Скриптер
Постоялец
Регистрация
20 Фев 2018
Сообщения
263
Симпатии
65
Пол
Мужской
angrybot, пока до такого додумал
Код:
//Based on plugin: https://dev-cs.ru/resources/984/ by GM-X Team

#include <amxmodx>
#include <json>
#include <sqlx>
#include <player_preferences>

new bool: DEBUG;

new const PluginName[] = "Player preferences";
new const PluginVersion[] = "1.0.8";
new const PluginAuthor[] = "GM-X Team, cpctrl";
new const PluginURL[] = "https://goldsrc.ru/members/3085/";

#define CHECK_NATIVE_ARGS_NUM(%1,%2,%3) \
    if (%1 < %2) { \
        DEBUG && log_error(AMX_ERR_NATIVE, "Invalid num of arguments %d. Expected %d", %1, %2); \
        return %3; \
    }

#define CHECK_NATIVE_PLAYER(%1,%2) \
    if (!is_user_connected(%1)) { \
        DEBUG && log_error(AMX_ERR_NATIVE, "Invalid player %d", %1); \
        return %2; \
    }

const MAX_KEY_LENGTH = 32;
const MAX_VALUE_STRING_LENGTH = 32;

enum fwdStruct  {
    Fwd_Initiated,
    Fwd_KeyChanged
};

enum sqlx_e {
    SQL_TABLE[32],
    SQL_HOST[32],
    SQL_USER[64],
    SQL_PASS[64],
    SQL_DB[32]
};

enum temp_e {
    TEMP_ID,
    TEMP_USERID,
    TEMP_PLUGIN,
    TEMP_CALLBACK[64]
};

new g_eForwards[fwdStruct];
new Handle: g_hTuple;
new g_eDbData[sqlx_e];

new Trie: g_tPlayerPreferences[MAX_PLAYERS + 1];
new JSON: g_jPlayerPreferences[MAX_PLAYERS + 1];

new LoadState: g_eLoadState[MAX_PLAYERS + 1] = { STATE_FAIL, ... };

public plugin_init()    {
#if AMXX_VERSION_NUM < 200
    register_plugin(PluginName, PluginVersion, PluginAuthor);
#else
    register_plugin(PluginName, PluginVersion, PluginAuthor, PluginURL);
#endif

    g_eForwards[Fwd_Initiated] = CreateMultiForward("pp_player_initiate", ET_IGNORE, FP_CELL, FP_CELL);
    g_eForwards[Fwd_KeyChanged] = CreateMultiForward("pp_player_key_changed", ET_CONTINUE, FP_CELL, FP_STRING);

    DEBUG = !!(plugin_flags() & AMX_FLAG_DEBUG);

    sql_initiate();
}

public client_putinserver(id)   {
    if (is_user_hltv(id) || is_user_bot(id))    {
        return;
    }

    g_tPlayerPreferences[id] = TrieCreate();
    g_jPlayerPreferences[id] = json_init_object();
}

public client_disconnected(id)  {
    if (g_tPlayerPreferences[id] != Invalid_Trie)   {
        SavePreferences(id);
    }
}

public plugin_end() {
    DestroyForward(g_eForwards[Fwd_Initiated]);
    DestroyForward(g_eForwards[Fwd_KeyChanged]);
}

public plugin_natives() {
    register_native("pp_has_key", "native_has_key");

    register_native("pp_get_load_state", "native_get_state");
    register_native("pp_load_user", "native_load_user");

    register_native("pp_get_number", "native_get_number");
    register_native("pp_get_float", "native_get_float");
    register_native("pp_get_bool", "native_get_bool");
    register_native("pp_get_string", "native_get_string");

    register_native("pp_set_number", "native_set_number");
    register_native("pp_set_float", "native_set_float");
    register_native("pp_set_bool", "native_set_bool");
    register_native("pp_set_string", "native_set_string");
}

public bool: native_has_key(plugin, argc) {
    enum    {
        arg_player = 1,
        arg_key
    };

    CHECK_NATIVE_ARGS_NUM(argc, 2, false)

    new id = get_param(arg_player);
    CHECK_NATIVE_PLAYER(id, false)

    new key[MAX_KEY_LENGTH];
    get_string(arg_key, key, MAX_KEY_LENGTH - 1);

    return TrieKeyExists(g_tPlayerPreferences[id], key);
}

public LoadState: native_get_state(plugin, argc) {
    enum    {
        arg_player = 1
    };

    CHECK_NATIVE_ARGS_NUM(argc, 1, STATE_FAIL)

    new id = get_param(arg_player);
    CHECK_NATIVE_PLAYER(id, STATE_FAIL)

    return g_eLoadState[id];
}

public bool: native_load_user(plugin, argc)   {
    if (g_hTuple == Empty_Handle)   {
        return false;
    }

    enum    {
        arg_player = 1,
        arg_callback,
        arg_authtype
    };

    CHECK_NATIVE_ARGS_NUM(argc, 2, false)

    new id = get_param(arg_player);
    CHECK_NATIVE_PLAYER(id, false)

    new buffer[512], len;
    len = formatex(buffer, charsmax(buffer), "SELECT `data` FROM `%s` WHERE ", g_eDbData[SQL_TABLE]);

    new name[MAX_NAME_LENGTH * 2], auth[MAX_AUTHID_LENGTH];

    get_user_name(id, name, MAX_NAME_LENGTH - 1);
    get_user_authid(id, auth, MAX_AUTHID_LENGTH - 1);

    mysql_escape_string(name, charsmax(name));

    switch (get_param(arg_authtype))   {
        case AUTH_BY_NAME:   {
            len += formatex(buffer[len], charsmax(buffer) - len, "`name` = '%s';", name);
        }
        case AUTH_BY_STEAM:  {
            len += formatex(buffer[len], charsmax(buffer) - len, "`auth` = '%s';", auth);
        }
        case AUTH_COMBINE:   {
            len += formatex(buffer[len], charsmax(buffer) - len, "(`auth` = '%s' AND `name` = '%s');", auth, name);
        }
    }

    g_eLoadState[id] = STATE_LOADING;

    new temp[temp_e];

    temp[TEMP_ID] = id;
    temp[TEMP_USERID] = get_user_userid(id);
    temp[TEMP_PLUGIN] = plugin;

    new callback[64];
    get_string(arg_callback, callback, charsmax(callback));

    temp[TEMP_CALLBACK] = get_func_id(callback, plugin);

    SQL_ThreadQuery(g_hTuple, "ThreadHandler", buffer, temp, sizeof temp);

    return true;
}

public native_get_number(plugin, argc)  {
    enum    {
        arg_player = 1,
        arg_key,
        arg_default
    };

    CHECK_NATIVE_ARGS_NUM(argc, 2, 0)

    new id = get_param(arg_player);
    CHECK_NATIVE_PLAYER(id, 0)

    new key[MAX_KEY_LENGTH];
    get_string(arg_key, key, MAX_KEY_LENGTH - 1);

    if (!TrieKeyExists(g_tPlayerPreferences[id], key))  {
        return argc >= arg_default ? get_param(arg_default) : 0;
    }

    new value;
    TrieGetCell(g_tPlayerPreferences[id], key, value);

    return value;
}

public Float: native_get_float(plugin, argc)  {
    enum    {
        arg_player = 1,
        arg_key,
        arg_default
    };

    CHECK_NATIVE_ARGS_NUM(argc, 2, 0.0)

    new id = get_param(arg_player);
    CHECK_NATIVE_PLAYER(id, 0.0)

    new key[MAX_KEY_LENGTH];
    get_string(arg_key, key, MAX_KEY_LENGTH - 1);

    if (!TrieKeyExists(g_tPlayerPreferences[id], key))  {
        return argc >= arg_default ? get_param_f(arg_default) : 0.0;
    }

    new value;
    TrieGetCell(g_tPlayerPreferences[id], key, value);

    return float(value);
}

public bool: native_get_bool(plugin, argc)  {
    enum    {
        arg_player = 1,
        arg_key,
        arg_default
    };

    CHECK_NATIVE_ARGS_NUM(argc, 2, false)

    new id = get_param(arg_player);
    CHECK_NATIVE_PLAYER(id, false)

    new key[MAX_KEY_LENGTH];
    get_string(arg_key, key, MAX_KEY_LENGTH - 1);

    if (!TrieKeyExists(g_tPlayerPreferences[id], key))  {
        return bool: (argc >= arg_default ? get_param(arg_default) : 0);
    }

    new value;
    TrieGetCell(g_tPlayerPreferences[id], key, value);

    return bool: value;
}

public native_get_string(plugin, argc)  {
    enum    {
        arg_player = 1,
        arg_key,
        arg_dest,
        arg_length,
        arg_default
    };

    CHECK_NATIVE_ARGS_NUM(argc, 2, 0)

    new id = get_param(arg_player);
    CHECK_NATIVE_PLAYER(id, 0)

    new key[MAX_KEY_LENGTH], value[MAX_VALUE_STRING_LENGTH];

    get_string(arg_key, key, MAX_KEY_LENGTH - 1);

    if (!TrieGetString(g_tPlayerPreferences[id], key, value, MAX_VALUE_STRING_LENGTH - 1) && argc >= arg_default)  {
        get_string(arg_default, value, MAX_VALUE_STRING_LENGTH - 1);
    }

    return set_string(arg_dest, value, get_param(arg_length));
}

public native_set_number(plugin, argc)  {
    enum    {
        arg_player = 1,
        arg_key,
        arg_value
    };

    CHECK_NATIVE_ARGS_NUM(argc, 3, 0)

    new id = get_param(arg_player);
    CHECK_NATIVE_PLAYER(id, 0)

    new key[MAX_KEY_LENGTH], value;
    get_string(arg_key, key, charsmax(key));
    value = get_param(arg_value);

    TrieSetCell(g_tPlayerPreferences[id], key, value);

    return setValue(id, key, json_init_number(value));
}

public native_set_bool(plugin, argc)  {
    enum    {
        arg_player = 1,
        arg_key,
        arg_value
    };

    CHECK_NATIVE_ARGS_NUM(argc, 3, 0)

    new id = get_param(arg_player);
    CHECK_NATIVE_PLAYER(id, 0)

    new key[MAX_KEY_LENGTH], bool: value;
    get_string(arg_key, key, charsmax(key));
    value = bool: get_param(arg_value);

    TrieSetCell(g_tPlayerPreferences[id], key, cell: value);

    return setValue(id, key, json_init_bool(value));
}

public native_set_float(plugin, argc)  {
    enum    {
        arg_player = 1,
        arg_key,
        arg_value
    };

    CHECK_NATIVE_ARGS_NUM(argc, 3, 0)

    new id = get_param(arg_player);
    CHECK_NATIVE_PLAYER(id, 0)

    new key[MAX_KEY_LENGTH], Float: value;
    get_string(arg_key, key, charsmax(key));
    value = get_param_f(arg_value);

    TrieSetCell(g_tPlayerPreferences[id], key, value);

    return setValue(id, key, json_init_number(cell: value));
}

public native_set_string(plugin, argc)  {
    enum    {
        arg_player = 1,
        arg_key,
        arg_value
    };

    CHECK_NATIVE_ARGS_NUM(argc, 3, 0)

    new id = get_param(arg_player);
    CHECK_NATIVE_PLAYER(id, 0)

    new key[MAX_KEY_LENGTH], value[MAX_VALUE_STRING_LENGTH];
    get_string(arg_key, key, charsmax(key));
    get_string(arg_value, value, charsmax(value));

    TrieSetString(g_tPlayerPreferences[id], key, value);

    return setValue(id, key, json_init_string(value));
}

public ThreadHandler(failstate, Handle: query, error[], errnum, data[], size, Float: queuetime)   {
    if (failstate)  {
        log_error(AMX_ERR_NATIVE, "[PP] [%d]: %s", errnum, error);
        return PLUGIN_HANDLED;
    }

    new id = data[TEMP_ID];

    CHECK_NATIVE_PLAYER(id, PLUGIN_HANDLED)

    if (get_user_userid(id) != data[TEMP_USERID]) {
        DEBUG && log_amx("[PP] Userid %d != Pushed userid %d", get_user_userid(id), data[TEMP_USERID]);

        return PLUGIN_HANDLED;
    }

    if (SQL_NumResults(query))  {
        new preferences[612];
        SQL_ReadResult(query, SQL_FieldNameToNum(query, "data"), preferences, charsmax(preferences));

        new JSON: jsonValue = json_parse(preferences);

        if (jsonValue == Invalid_JSON || preferences[0] != '{' || preferences[strlen(preferences) - 1] != '}')   {
            json_free(jsonValue);

            DEBUG && log_error(AMX_ERR_NATIVE, "[PP] Skipped load from bad format json on %d <%N>", id, id);

            g_eLoadState[id] = STATE_FAIL;

            return PLUGIN_HANDLED;
        }

        for (new i = 0, n = json_object_get_count(jsonValue), JSON: element, key[MAX_KEY_LENGTH], value[MAX_VALUE_STRING_LENGTH], num, bool: boolean; i < n; i++)  {
            json_object_get_name(jsonValue, i, key, charsmax(key));
            element = json_object_get_value_at(jsonValue, i);

            switch  (json_get_type(element)) {
                case JSONString:    {
                    json_get_string(element, value, MAX_VALUE_STRING_LENGTH - 1);

                    TrieSetString(g_tPlayerPreferences[id], key, value);
                    json_object_set_string(g_jPlayerPreferences[id], key, value);
                }
                case JSONNumber:    {
                    num = json_get_number(element);

                    TrieSetCell(g_tPlayerPreferences[id], key, num);
                    json_object_set_number(g_jPlayerPreferences[id], key, num);
                }
                case JSONBoolean:   {
                    boolean = json_get_bool(element);

                    TrieSetCell(g_tPlayerPreferences[id], key, boolean ? 1 : 0);
                    json_object_set_bool(g_jPlayerPreferences[id], key, boolean);
                }
            }
            json_free(element);
        }
        json_free(jsonValue);

        g_eLoadState[id] = STATE_LOADED;
    }
    else    {
        g_eLoadState[id] = STATE_NODATA;
    }

    if (data[TEMP_PLUGIN] != INVALID_PLUGIN_ID && data[TEMP_CALLBACK] != INVALID_PLUGIN_ID && callfunc_begin_i(data[TEMP_CALLBACK], data[TEMP_PLUGIN]) == 1)   {
        callfunc_push_int(id);
        callfunc_push_int(_: g_eLoadState[id]);
        callfunc_end();
    }
    else    {
        ExecuteForward(g_eForwards[Fwd_Initiated], _, id, _: g_eLoadState[id]);
    }

    return PLUGIN_HANDLED;
}

stock setValue(const id, const key[], JSON: value)    {
    new fwReturn;
    ExecuteForward(g_eForwards[Fwd_KeyChanged], fwReturn, id, key);

    if (g_hTuple == Empty_Handle || fwReturn == PLUGIN_HANDLED) {
        return -1;
    }

    json_object_set_value(g_jPlayerPreferences[id], key, value);
    json_free(value);

    return 1;
}

stock SavePreferences(const id)  {
    if (g_hTuple == Empty_Handle)   {
        ChallengeClear(id);
        return;
    }

    if (json_serial_size(g_jPlayerPreferences[id]) < 5)  {
        ChallengeClear(id);
        return;
    }

    static buffer[2042], serial[1024];
    new auth[MAX_AUTHID_LENGTH], name[MAX_NAME_LENGTH];

    get_user_name(id, name, MAX_NAME_LENGTH - 1);
    mysql_escape_string(name, charsmax(name));

    get_user_authid(id, auth, MAX_AUTHID_LENGTH - 1);

    json_serial_to_string(g_jPlayerPreferences[id], serial, charsmax(serial));
    mysql_escape_string(serial, charsmax(serial));

    formatex(buffer, charsmax(buffer), "INSERT INTO `%s` (`name`, `auth`, `data`) VALUES ('%s', '%s', \
        '%s') ON DUPLICATE KEY UPDATE `data` = '%s';", g_eDbData[SQL_TABLE], name, auth, serial, serial);

    DEBUG && log_amx(buffer);

    SQL_ThreadQuery(g_hTuple, "ThreadEmpty", buffer);

    ChallengeClear(id);
}

public ThreadEmpty(failstate, Handle: query, error[], errnum, data[], size, Float: queuetime)   {
    if (failstate)  {
        log_error(AMX_ERR_NATIVE, "[PP] [%d]: %s", errnum, error);
    }

    return PLUGIN_HANDLED;
}

stock ChallengeClear(id)   {
    json_free(g_jPlayerPreferences[id]);
    TrieDestroy(g_tPlayerPreferences[id]);
    g_eLoadState[id] = STATE_FAIL;
}

stock sql_initiate()    {
    new filePath[PLATFORM_MAX_PATH];
    get_localinfo("amxx_configsdir", filePath, PLATFORM_MAX_PATH - 1);

    add(filePath, PLATFORM_MAX_PATH - 1, "/preferences.json");

    if (!file_exists(filePath)) {
        set_fail_state("Configuration file '%s' not found", filePath);
        return;
    }

    new JSON: config = json_parse(filePath, true);

    if (config == Invalid_JSON)    {
        set_fail_state("Configuration file '%s' read error", filePath);
        return;
    }

    new temp[64];

    json_object_get_string(config, "sql_table", temp, charsmax(temp));
    copy(g_eDbData[SQL_TABLE], charsmax(g_eDbData[SQL_TABLE]), temp);

    json_object_get_string(config, "sql_host", temp, charsmax(temp));
    copy(g_eDbData[SQL_HOST], charsmax(g_eDbData[SQL_HOST]), temp);

    json_object_get_string(config, "sql_user", temp, charsmax(temp));
    copy(g_eDbData[SQL_USER], charsmax(g_eDbData[SQL_USER]), temp);

    json_object_get_string(config, "sql_password", temp, charsmax(temp));
    copy(g_eDbData[SQL_PASS], charsmax(g_eDbData[SQL_PASS]), temp);

    json_object_get_string(config, "sql_db", temp, charsmax(temp));
    copy(g_eDbData[SQL_DB], charsmax(g_eDbData[SQL_DB]), temp);

    json_free(config);

    new Handle: sConnection;

    g_hTuple = SQL_MakeDbTuple(
        g_eDbData[SQL_HOST],
        g_eDbData[SQL_USER],
        g_eDbData[SQL_PASS],
        g_eDbData[SQL_DB]
    );

    new errCode, error[512];
    sConnection = SQL_Connect(g_hTuple, errCode, error, charsmax(error));

    if (sConnection == Empty_Handle)    {
        SQL_FreeHandle(g_hTuple);
        g_hTuple = Empty_Handle;

        DEBUG && log_amx("[PP] Error connecting to db '%s': #%d: %s", g_eDbData[SQL_DB], errCode, error);

        return;
    }

    server_print("[PP] Connection to '%s' database success", g_eDbData[SQL_DB]);

    SQL_FreeHandle(sConnection);
}

stock mysql_escape_string(dest[], len)    {
    replace_all(dest, len, "\\", "\\\\");
    replace_all(dest, len, "\0", "\\0");
    replace_all(dest, len, "\n", "\\n");
    replace_all(dest, len, "\\r", "\\\r");
    replace_all(dest, len, "\x1a", "\\Z");
    replace_all(dest, len, "'", "\\'");
    replace_all(dest, len, "`", "\\`");
}
Код:
#include <amxmodx>
#include <player_preferences>

new const PluginName[] = "Player preferences: Addon";
new const PluginVersion[] = "1.0.8";
new const PluginAuthor[] = "GM-X Team, cpctrl";
new const PluginURL[] = "https://goldsrc.ru/members/3085/";

public plugin_init()    {
#if AMXX_VERSION_NUM < 200
    register_plugin(PluginName, PluginVersion, PluginAuthor);
#else
    register_plugin(PluginName, PluginVersion, PluginAuthor, PluginURL);
#endif
}

public client_putinserver(id)   {
    if (pp_load_user(id, "loaded_player", AUTH_COMBINE))   {
        log_amx("[PP] User %d<%n> goto loading", id, id);
    }
}

new const stateNames[LoadState][] =    {
    "failed load",
    "is loading",
    "has been loaded",
    "no have data"
};

public loaded_player(const id, LoadState: eLoadState)  {
    log_amx("[PP] User %d<%n> %s", id, id, stateNames[eLoadState]);
}
 

karaulov

Скриптер
Постоялец
Пользователь
Регистрация
5 Май 2019
Сообщения
1.041
Симпатии
357
Пол
Мужской
cpCTRL, а функция escape из sqlx работает хуже чем mysql_escape_string из кода плагина?
 

cpCTRL

bruh
Скриптер
Постоялец
Регистрация
20 Фев 2018
Сообщения
263
Симпатии
65
Пол
Мужской
karaulov, подробнее. О функции исключения(?) LIKE говоришь?
 

karaulov

Скриптер
Постоялец
Пользователь
Регистрация
5 Май 2019
Сообщения
1.041
Симпатии
357
Пол
Мужской
cpCTRL, я имею ввиду замена символов SQL_QuoteString (sqlx) и mysql_escape_string которая прописана в коде, разница есть ?
 

cpCTRL

bruh
Скриптер
Постоялец
Регистрация
20 Фев 2018
Сообщения
263
Симпатии
65
Пол
Мужской
karaulov, экранирует только одинарные ковычки
Чекну сурсы позже
 

cpCTRL

bruh
Скриптер
Постоялец
Регистрация
20 Фев 2018
Сообщения
263
Симпатии
65
Пол
Мужской

karaulov

Скриптер
Постоялец
Пользователь
Регистрация
5 Май 2019
Сообщения
1.041
Симпатии
357
Пол
Мужской
А, оказывается вызывает функцию mysql_real_escape_string, что делает почти то же самое.
 

cpCTRL

bruh
Скриптер
Постоялец
Регистрация
20 Фев 2018
Сообщения
263
Симпатии
65
Пол
Мужской

karaulov

Скриптер
Постоялец
Пользователь
Регистрация
5 Май 2019
Сообщения
1.041
Симпатии
357
Пол
Мужской
Я бы например хотел написать свою статистику сервера(, без каких-либо rcon паролей и отправки логов), и игроков в таком виде это куда проще и потом в веб скриптах легко парсить и работать с данными, но плагин ограничивает возможностью работы только с игроками, конечно было бы куда лучше разбить плагин на две части, одну для работы с бд+json, а вторую уже как player preferences, то есть как обёртку. Я же так понимаю что это асинхронные запросы и можно обновлять данные в любое время?
 
Последнее редактирование:
Сверху Снизу