Форум Pawn.Wiki - Воплоти мечту в реальность!: Инвентарь без смс и регистрации. - Форум Pawn.Wiki - Воплоти мечту в реальность!

Перейти к содержимому

Страница 1 из 1
  • Вы не можете создать новую тему
  • Вы не можете ответить в тему

Инвентарь без смс и регистрации. Оценка: -----

#1
Пользователь офлайн   Perdolinka 

  • Профессионал
  • Вставить ник
  • Раскрыть информацию
Привет.

Я не умею красиво говорить, скорее, люблю делать, чем писать сотню слов. Данный урок посвящается тем, кто до сих пор не может создать систему инвентаря, ввиду то ли не желания, то ли отсутствия навыков программирования на пешке. Раньше, когда учился программировать, таких статей на просторах пувн форумов, я не встречал. Поэтому было принято решение написать таков самому, расписав работоспособность в деталях. Данный материал предназначен для лиц, достигших петнадсати лет имеющих базовые познания в области Pawn.

Библиотеки, необходимые для работы:
a_samp и к.о - собственно, мы же разрабатываем под SA-MP и его продукты;
foreach(0.4.3) - в связи с использованием итератора, а не массива для хранения данных;
TDW Dialog - дистрибутив для динамической работы с диалогами. Можно пользоваться любым другим, но изначально система будет заточена под него, да и мне лично он по нраву;
Pawn.CMD - командный обработчик. Пользуюсь им, поэтому решил не отходить от традиции и в этот раз;
MYSQL R39-6 - Нужно для работы с базой данных. Можно выше версию взять, ибо в тех версиях исправлены баги, которые есть в этой, но подойдёт и эта.

От слов к делу:
/* Подключение библиотек, необходимых для работы. 
Если путь к ним отличен от стандартного: pawno/include, тогда уж стоит указывать его по шаблону: #include <../название папки, где находится библиотека/имя библиотеки */
#include <a_samp>
#include <foreach>
#include <tdw_dialog>
#include <Pawn.CMD>
#include <a_mysql> 

/* определим директиву, которая будет хранить количество наших слотов в инвентаре. Но, при этом, не забываем проверить, а не определена ли она где-то уже, скажем, в каком-нибудь стороннем файле. Если определена, тогда слегка обойдём правила, изменив найденное значение на своё */
#if !defined MAX_PLAYER_INV_SLOTS
    #define MAX_PLAYER_INV_SLOTS (10)
#else 
    #undef MAX_PLAYER_INV_SLOTS 
    #define MAX_PLAYER_INV_SLOTS (10)
#endif 

/* та же самая перспектива, что и с MAX_PLAYER_INV_SLOTS */
#if !defined MAX_INVENTORY_ITEMS
    #define MAX_INVENTORY_ITEMS (4)
#else 
    #undef MAX_INVENTORY_ITEMS 
    #define MAX_INVENTORY_ITEMS (4)
#endif 

/* объявим макросы, устанавливающие и получающие  значения наших основных данных: ид игрока в базе; ника */
#if !defined GetPlayerUserID
    #define GetPlayerUserID(%0) g_player_main[%0][PM_ID]
#endif 

#if !defined SetPlayerUserID
    #define SetPlayerUserID(%0,%1) g_player_main[%0][PM_ID] = %1
#endif 

#if !defined GetPlayerNameEx
    #define GetPlayerNameEx(%0) g_player_main[%0][PM_NAME]
#endif 

/* создадим двухмерный итератор, для каждого игрока(MAX_PLAYERS) на MAX_PLAYER_INV_SLOTS слотов. Обычно не люблю давать название переменным/массивам в стиле PascalCase, но итераторы тот редкостный случай, где я делаю это, ввиду того, что, при итерировании, лично мне, смотриться лучше, нежели, скажем, тот же camelCase. Хотя, опять таки, это лично мои нравы, которые вполне могут отличаться от Ваших.
P.s не забываем об ограничениях, имеющихся в итератора: числа, больше размера итератора и меньше нуля - не способны войти в него. Потому, увеличивая количество предметов, стоит увеличивать и вторую меру итератора, дабы значения смогли записаться туда. Но, тем не менее, итерироваться стоит до числа, хранящего количество наших слотов. Тем самым, помнится мне что в библиотеке, которую я предоставил, не представлена возможность создавать трёхмерные итераторы, потому приходится делать костылём, создавая два отдельных итератора под наши нужды, сохраняя в первом из них номер предмета и количество во втором. Есть ещё вариант обойтись умножением MAX_PLAYERS на sizeof g_inventory_items, но по мне, так это слишком затратный вариант. В будущем, обновляя вариант, возможно переделаю пол реалии трёхмерного, если проверю до конца работоспобность его, используя библиотеку foreach от karim'a. Хотя, крайне сомневаюсь в наличии такого дистрибутива там */ 

new Iterator: PlayerItemsID[MAX_PLAYERS]<sizeof g_inventory_items>; 

new Iterator: PlayerItemsCount[MAX_PLAYERS]<sizeof g_inventory_items>; 

/* структура, хранящая основные данные о игроке: ид в базе; ник */
enum E_PLAYER_MAIN_STRUCT
{
    PM_ID,
    PM_NAME[MAX_PLAYER_NAME]
} 

new g_player_main[MAX_PLAYERS][E_PLAYER_MAIN_STRUCT]; 

/* структура предметов: имя(string); описание(string); преимущества(string). В дальнейшем можно добавлять элементы: например цену за предмет. Но, тогда уж, стоит не забывать о том, что Pawn не динамичен и, приходится заполнять даже те значения, у которых нет цены, дабы компилятор не сгенерировал ошибку. Не стал определять размерность массивов в качестве директив, но, при желании, это можно сделать. Также стоит подметить, что память выделялась с учётом EOS('\0') */
enum E_INVENTORY_ITEMS_STRUCT
{
    II_NAME[11],
    II_CONTENT[92],
    II_ADVANTAGES[109]
} 

/* создадим двумерный массив на MAX_INVENTORY_ITEMS, обращающийся к перечислению, хранящему список элементов, необходимых для соответствующего описания наших предметов */
new g_inventory_items[MAX_INVENTORY_ITEMS][E_INVENTORY_ITEMS_STRUCT] = 
{
    {
        "Аптечка", 
        "Лечит весь организм от увечий", 
        "Положительное влияние на весь организм\n"\
        "Низкая рыночная стоимость"
    },
    {
        "Подорожник",
        "Восстанавливает клетки, даёт долгосрочный эффект регенерации",
        "Легко найти в лесу\n"\
        "Низкая рыночная стоимость"
    },
    {
        "Удка",
        "Позволяет ловить рыбу",
        "Доступный инструмент, который всегда под рукой\n"\
        "Имеет несколько уровней улучшений\n"\
        "Низкая рыночная стоимость"
    },
    {
        "Шокер",
        "Позволяет оглушить врага на 10 сек, сделав его неподвижным и устойчивым к физическим атакам",
        "Эффективно работает, даже имея низкий заряд\n"\
        "Лёгок в освоении\n"\
        "Низкая рыночная стоимость"
    }
}; 

new g_mysql_handle = -1; 

stock IsConnectionToMysqlNormal()
    return g_mysql_handle != -1; 

@__OnPlayerLoadInventory(playerid);
@__OnPlayerLoadInventory(playerid)
{
    new rows = cache_num_rows(); 

    if(rows > MAX_PLAYER_INV_SLOTS)
        rows = MAX_PLAYER_INV_SLOTS; 

    for(new idx; idx < rows; idx ++)
    {
        new item_id = cache_get_field_content_int(idx, "item_id", g_mysql_handle),
                count = cache_get_field_content_int(idx, "count", g_mysql_handle); 

        Iter_Add(PlayerItemsID[playerid], item_id);
        Iter_Add(PlayerItemsCount[playerid], count);
    }
    return printf("Загружено предметов: %d, для игрока: %d", Iter_Count(PlayerItemsID[playerid]), playerid);
}
/* стоит создать таблицу inventory со столбцами:
id(int 11, AUTO_INCREMENT) - ид записи;
user_id(int 11) - ид игрока в базе;
slot(int 2) - слот;
item_id(int, допустим 8) - ид предмета;
count(int, допустим 6) - количество
Также отправляет многопоточный запрос, получающий данные о инвентаре игрока, которые, в последующем, будут записаны в итератор. Хотя, по сути дела, можно было бы замутить LEFT JOIN и не делать такого, сделав связку между users и inventory, добавив в конце условие(WHERE), равен ли столбец u.name нику игрока. Должно работать */
stock LoadPlayerInventoryItems(playerid)
{
    new fmt_str[] = 
    {
        "SELECT * FROM inventory i WHERE i.user_id=%d ORDER BY i.slot ASC"
    },
    result_str[sizeof fmt_str + (- 2 + 11)]; 

    format(result_str, sizeof result_str, GetPlayerUserID(playerid));
    mysql_tquery
    (
        g_mysql_handle, 
        result_str, "@__OnPlayerLoadInventory",
        "d", playerid
    );
    
    return (!mysql_errno() ? 1 : 0);
} 

@__OnPlayerLoadUserID(playerid);
@__OnPlayerLoadUserID(playerid)
{
    new user_id = cache_get_field_content_int(0, "user_id", g_mysql_handle); 

    SetPlayerUserID(playerid, user_id); 

    if(IsPlayerUserIDValid(playerid))
        LoadPlayerInventoryItems(playerid); 

    return 1;
} 

/* стоит создать таблицу users со столбцами:
id(int 11, AUTO_INCREMENT) - ид записи;
user_id(int 11) - ид игрока в базе;
name(varchar 24) - имя игрока. 
Данная функция отправляет многопоточный запрос, не мешающий работе основного - серверу, получая ид игрока в базе. В случае, если таков найден, то она загрузит его инвентарь. */ 

stock LoadPlayerUserID(playerid)
{
    new fmt_str[] = 
    {
        "SELECT u.user_id FROM users u"\
        " WHERE u.name=%s"
    },
    result_str[sizeof fmt_str + (- 2 + MAX_PLAYER_NAME)]; 

    format(result_str, sizeof result_str, fmt_str, GetPlayerNameEx(playerid));
    mysql_tquery(g_mysql_handle, result_str, "@__OnPlayerLoadUserID", "d", playerid); 

    return (!mysql_errno() ? 1 : 0);
} 

stock ClearPlayerUserID(playerid)
    return SetPlayerUserID(playerid, -1); 

stock ClearPlayerNickName(playerid)
    return g_player_main[playerid][PM_NAME][0] = EOS; 

public OnGameModeInit()
{
    /* подключение к базе данных, тут введите свои данные */
    g_mysql_handle = mysql_connect(/* адрес */, /* пользователь */, /* имя таблицы */, /* пароль */); 

    /* Тут, думаю, особо объяснять нет надобности.
    Функция IsConnectionToMysqlNormal вернёт единицу в случае, если соединение с базой данных установлено. */
    print
    (
        "Подключение к базе данных %s", 
        IsConnectionToMysqlNormal() ? ("удалось") : ("не удалось")
    );
    /* инициализация итератора. Если речь о двумерном и выше, то это принципиально */
    Iter_Init(PlayerItemsID);
    Iter_Init(PlayerItemsCount); 

    return 1;
} 

public OnGameModeExit()
{
    /* прерываем соединение с БД */
    mysql_close(g_mysql_handle);
    
    return 1;
} 

public OnPlayerConnect(playerid)
{
    /* предварительно очищаем айди игрока в базе, чтобы в дальнейшем глянуть, получён ли он с помощью запроса */
    ClearPlayerUserID(playerid);
    /* получаем ник игрока, записывая в элемент перечисления. Нужно, чтобы не дёргают вызов функции GetPlayerName 100-500 раз, да и айдишник в базе получить тоже нужно, следовательно нужны данные для выборки */
    GetPlayerName(playerid, g_player_name[playerid], MAX_PLAYER_NAME);
    LoadPlayerUserID(playerid); 

    return 1;
} 

stock OnPlayerDisconnect(playerid, reason)
{
    /* очищаем итераторы игрока, дабы туда, в дальнейшем, записались данные нового и айдишник в базе, вместе с ником, само собой. */
    ClearPlayerUserID(playerid);
    ClearPlayerNickName(playerid); 

    Iter_Clear(PlayerItemsID[playerid]);
    Iter_Clear(PlayerItemsCount[playerid]); 

    return 1;
} 

/* получение данных, имеющихся в слоте: ид предмета; кол-во. С последующим возвращением результата по ссылке, в переменную. Принцип работы кода:
Итерируемся по обоим из итераторов до тех пор, пока переменная index не будет равна аргументу slot. Отсчёт в переменной начинается с -1 и идёт до slot включительно,  ибо отсчёт слотов начинается с нуля, потому, если, скажем в аргумент slot попадёт седьмой слот(шестой, т.к отсчёт от нуля), то будет пройдено семь итераций: 0, 1, 2, 3, 4, 5, 6. И тогда уж, в случае достижения цели, цикл остановится, вернув результат. Кроме того, возвращает единицу, если index достиг цели. */
stock GetPlayerSlotData(playerid, slot, &item_id , &count)
{
    new index = -1; 

    foreach(new idx : PlayerItemsID[playerid])
    {
        foreach(new j : PlayerItemsCount[playerid])
        {
            index ++; 

            if(index == slot)
            {
                item_id = idx;
                count = j;
                
                break;
            }
        }
    }
    return (index == slot);
} 

/* функция, показывающая информацию о предметах игрока в диалоге с типом DIALOG_STYLE_TABLIST_HEADERS.
Шаблон рассчёта памяти: 
result_str:
(- 2 + 2) - минус два - отнимаем длину спецификатора(%d), плюс  - прибавляем длину макс. кол-ва слотов(10 - 2 символа);
(- 2 + 10) - минус два - отнимаем длину спецификатора(%s), плюс  - прибавляем длину макс. кол-ва имени(10 символов, без учёта EOS);
(- 2 + 5) - минус два - отнимаем длину спецификатора(%d), плюс  - прибавляем длину макс. кол-ва предмета(допустим это пятизначное число, тогда уж прибавим 5 символов) 

dest:
19 - длина подзаголовка(Номер\t\tИмя). Считалось с учётом того, что \t и \n занимают один символ;
result_str * MAX_PLAYER_INV_SLOTS - так, как полей в сумме десять, умножаем итоговую строку с данными на нужное число.
*/ 

stock ShowPlayerInventoryItemsDialog(playerid)
{
    new fmt_str[] = "[%d]\t\t%s\t\t%d\n",
            result_str[sizeof fmt_str + (- 2 + 2) + (- 2 + 10) + (- 2 + 5)],
            dest[19 + (sizeof result_str * MAX_PLAYER_INV_SLOTS)]  = "Номер\t\tИмя\t\tКол-во\n",
            name[11]; 

    for(new idx, item_id, count; idx < MAX_PLAYER_INV_SLOTS; idx ++)
    {
        GetPlayerSlotData(playerid, idx, item_id, count); 

        GetInventoryItemName(item_id, name); 

        format
        (
            result_str, sizeof result_str, 
            fmt_str,
            idx + 1,
            name,
            count
        );
        strcat(dest, result_str);
    }
    return OpenDialog
    (
        playerid, "DIALOG_INVENTORY", DIALOG_STYLE_TABLIST_HEADERS, 
        "Ваши предметы",
        dest, 
        "Информация", "Закрыть"
    );
} 

/* функции, получающие данные о предмете: имя; описание; преимущества, записывая их в строку dest, дабы не возвращать напрямую. Не стал делать уж макросы ради упрощения жизни, оставив это на плечах тех, кому это и впрямь нужно будет */
stock GetInventoryItemName(item_id, dest[11])
    format(dest, sizeof dest, "%s", g_inventory_items[item_id - 1][II_NAME]); 

stock GetInventoryItemContent(item_id, dest[92])
    format(dest, sizeof dest, "%s", g_inventory_items[item_id - 1][II_CONTENT]); 

stock GetInventoryItemAdvantages(item_id, dest[109])
    format(dest, sizeof dest, "%s", g_inventory_items[item_id - 1][II_ADVANTAGES]); 

stock ShowPlayerInfoAboutItemDialog(playerid, slot)
{
    new name[11],
            content[92],
            advantages[109],
            item_id,
            count; 

    GetPlayerSlotData(playerid, slot, item_id, count); 

    GetInventoryItemName(item_id, name);
    GetInventoryItemContent(item_id, content);
    GetInventoryItemAdvantages(item_id, advantages); 

    new fmt_str[] = 
    {
        "Слот предмета: %d\n"\
        "Количество: %d\n"\
        "Имя предмета: %s\n"\
        "Описание предмета: %s\n"\
        "Преимущества: %s"
    },
    result_str[sizeof fmt_str + (- 2 + 2) + 
                      (- 2 + 5) + (- 2 + (sizeof name - 1)) + 
                      (- 2 + (sizeof content - 1)) + 
                      (- 2 + (sizeof advantages - 1))]; 

    format
    (
        result_str, sizeof result_str, 
        fmt_str, 
        slot + 1,
        count,
        name,
        content,
        advantages
    );
    return OpenDialog
    (
        playerid, "DIALOG_INFO", DIALOG_STYLE_MSGBOX,
        "Информация",
        result_str, 
        "Закрыть", ""
    );
}
/* функция, обрабатывающая диалог, заместо OnPlayerDialogResponse. Если нажата левая кнопка, то покажем диалог с информацией о текущем предмете */
dialog DIALOG_INVENTORY(playerid, response, listitem, const inputtext[])
{
    if(response)
        return ShowPlayerInfoAboutItemDialog(playerid, listitem); 

    return 1;
} 

/* функция, проверяющая наличие предмета в инвентаре игрока. Входящие параметры:
item_id - ид предмета;
count - количество. 
Возвращает единицу в случае, если есть предмет с определённым количеством.
*/
stock IsPlayerInvItemValid(item_id, count)
{
    if(item_id && count)
        return 1; 

    return 0;
} 

/* функция, убирающая предмет из указанного слота. Входящие параметры:
playerid - ид игрока, у которого убираем;
slot - слот. 
Возвращает единицу, если удаление прошло успешно.
*/
stock RemovePlayerInvItem(playerid, slot)
{
    new item_id,
            count;
    
    GetPlayerSlotData(playerid, slot - 1, item_id, count); 

    if(!IsPlayerInvItemValid(item_id, count))
        return 0; 

    new fmt_str[] = 
    {
        "DELETE FROM inventory"\
        " WHERE user_id=%d AND slot=%d"
    },
    result_str[sizeof fmt_str + (- 2 + 11) + (- 2 + 2)];

    format(result_str, sizeof result_str, fmt_str, GetPlayerUserID(playerid), slot); 
    mysql_tquery(g_mysql_handle, result_str, "", ""); 

    new result = (!mysql_errno() ? 1 : 0); 

    if(result)
    {
        Iter_SafeRemove(PlayerItemsID[playerid], item_id, slot);
        Iter_SafeRemove(PlayerItemsCount[playerid], count, slot);
    }
return result;
} 

/* функция, дающая игроку предмет с указанным кол-вом. Входящие параметры:
playerid - ид игрока;
item_id - ид предмета;
count - количество. 
Возвращает единицу в случае, если удалось выдать предмет. */
stock GivePlayerItem(playerid, item_id, count)
{
    new slot = Iter_Free(PlayerItemsID),
             slot_ex = slot + 1,
             result; 

    if(slot_ex)
    {
        new fmt_str[] =
        {
            "INSERT INTO inventory(user_id,slot,item_id,count)"\
            " VALUES(%d,%d,%d,%d)"
        },
        result_str[sizeof fmt_str + (- 2 + 11) + (- 2 + 2) + (- 4 + (5 * 2))]; 

        format
        (
            result_str, sizeof result_str, 
            fmt_str, 
            GetPlayerUserID(playerid), 
            slot_ex,
            item_id,
            count
        );
        mysql_tquery(g_mysql_handle, result_str, "", ""); 

        result = (!mysql_errno() ? 1 : 0); 

        if(result)
        {
            Iter_Add(PlayerItemsID[playerid], item_id);
            Iter_Add(PlayerItemsCount[playerid], count);
        }
    }
    return result;
} 

/* функция, оперирующая количеством предмета в инвентаре игрока. Входящие параметры: 
playerid - ид игрока;
oper - оперируемый символ: + или -;
slot - слот;
count - количество. Возвращает единицу, если операция выполнена успешно */
stock OperatePlayerItem(playerid, const oper[2], slot, count)
{
    new item_id,
            current_count;
    
    GetPlayerSlotData(playerid, slot, item_id, current_count) 

    if(!IsPlayerInvItemValid(item_id, current_count))
        return 0; 

    if(oper[0] == '+')
    {
        if((current_count + count) > 99_000)
            return 0;
    }
    else
    {
        if((current_count - count) <= 0)
            return  RemovePlayerInvItem(playerid, slot);
    } 

    new fmt_str[] = 
    {
        "UPDATE inventory i SET i.count=i.count%c%d"\
        " WHERE i.user_id=%d AND i.slot=%d"
    },
    result_str[sizeof fmt_str + (- 2 + 1) + (- 2 + 5) + (- 2 + 11) + (- 2 + 2)]; 

    format
    (
        result_str, sizeof result_str, 
        fmt_str, 
        oper[0], 
        count,
        GetPlayerUserID(playerid),
        slot
    );
    mysql_tquery(g_mysql_handle, result_str, "", ""); 

    new result = (!mysql_errno() ? 1 : 0); 

    if(result)
    {
        Iter_SafeRemove(PlayerItemsCount[playerid], current_count, slot); 

        new bool: is_pointer_valid = false; 

        for(new idx = Iter_Begin(PlayerItemsCount[playerid]); idx = Iter_Next(PlayerItemsCount[playerid] != (slot - 1); idx ++)
{
    if(idx == (slot - 1))
    {
        is_pointer_valid = true;
        break;
    }
} 

    if(is_pointer_valid)
    {
        new sum = oper[0] == '+' ? current_count + count : current_count - count; 

        Iter_Add(PlayerItemsCount[playerid], sum);
    }
}
return result;
}
    
/* команда, введя которую, можно посмотреть данные о предметах */
cmd:test123(playerid)
    return ShowPlayerInventoryItemsDialog(playerid); 

/* команда для выдачи предмета.
Шаблон использования: /test228 [ид предмета] [кол-во] */
cmd:test228(playerid, const params[])
{
    extract params -> new item_id, count; 

    /* не помешает проверить, не является ли предмет невалидным. В случае если да, тогда возвращать, дабы не ставить выход за пределы в дальнейшем. Такую же вещь провернём и с количество, проверив не является ли входящее число большим за пятизначное */
    if((item_id > sizeof g_inventory_items) ||
    (count > 99_999))
        return 0; 

    return GivePlayerItem(playerid, item_id, count);
} 

/* команда для удаления предмета. Шаблон использования: /test1337 слот */
cmd:test1337(playerid, const params[])
{
    extract params -> new slot; 

    return RemovePlayerInvItem(playerid, slot);
} 

/* команда для прибавления/убавления предмета Шаблон использования: /test4308 + или - слот сколько прибавить/отнять */
cmd:test4308(playerid, const params[])
{
    extract params -> new string: oper[2], slot, count;
    
    return OperatePlayerItem(playerid, oper, slot, count);
}
    

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

Сообщение отредактировал Perdolinka: 06 февраля 2023 - 01:15

5

#2
Пользователь офлайн   krolik-gta 

  • Новичок
  • Вставить ник
  • Раскрыть информацию
"ибо код писался в текстовом редакторе на телефоне." ебнуться на телефоне такое написать :yes:
0

Поделиться темой:


Страница 1 из 1
  • Вы не можете создать новую тему
  • Вы не можете ответить в тему

1 человек читают эту тему
0 пользователей, 1 гостей, 0 скрытых пользователей


Яндекс.Метрика