Добро пожаловать на Pro Pawn - Портал о PAWN-скриптинге.
Страница 1 из 31 1 2 3 11 ... ПоследняяПоследняя
Показано с 1 по 10 из 308
  1. #1
    Аватар для DeimoS
    Модератор?

    Статус
    Оффлайн
    Регистрация
    27.01.2014
    Адрес
    Восточный Мордор
    Сообщений
    5,588
    Репутация:
    1984 ±

    Создание системы регистрации на основе плагина MySQL [R39/R40]

    Доброго времени суток.
    В этом уроке я попытаюсь объяснить Вам, как создаётся система записи/загрузки аккаунтов на основе плагина MySQL от BlueG.
    Код, предоставленный в статье, актуален для веток "R39" и "R40".
    Начнём.




    Плагин MySQL от BlueG:



     F.A.Q. <Как скачать с GitHub>

    1. Переходим по ссылке выше и скачиваем нужную версию плагина

     Скриншот






    2. Разархивируем скачанный архив и его содержимое перемещаем в папку с модом

     Скриншот






    3. Открываем файл "server.cfg" и ищем строчку "plugins".
    - Если нашли, то приписываем ко всем прочим плагинам новый - "mysql"
    - Если не нашли, то добавляем в самый конец файла новую строку - "plugins mysql"

    * Если сервер запускается в Ubuntu Linux / CentOS, к названию плагина приписываем расширение ".so" ("mysql.so"). Если же Windows, то приписывать к названию плагина ничего не нужно.

     Скриншот






    4. Открываем скрипт, в который будем прописывать регистрацию и подключаем инклюд MySQL (я буду использовать стандартный пустой мод, ссылку на который помещу в конце урока).

     Скриншот









    База данных:


    • Софт для работы с базой данных:
      Для работы с базой данных (чтение/редактирование) предпочтительнее использовать Open Server.

      Преимущество данного программного комплекса в том, что он является полностью портативным. Иными словами, Вам достаточно будет скачать его всего раз, после чего Вы сможете спокойно его переносить на флешку, другой жёсткий диск и так далее - он не потеряет своей работоспособности.
      Так же очевидным преимуществом Open Server над тем же Denwer является то, что проект активно дорабатывается (упомянутый Denwer не обновляется с 2013 года)

      Официальный сайт программы ospanel.io

       F.A.Q. <Как скачать с официального сайта Open Server>


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


      * При бесплатной загрузке программы скорость скачивания будет сильно ограничена, из-за чего загрузка может длиться несколько часов. При желании, Вы можете попытаться найти инсталлятор Open Server на торрент-трекерах, но учитывайте, что уже бывали случаи, когда torrent-версии данной программы модифицировались. Так что промышляйте подобным только на свой страх и риск, тщательно изучая комментарии, если такие присутствуют.


       F.A.Q. <Установка Open Server>
      Установка Open Server проста до безобразия:

      1. Запускаем скачанный ранее файл и указываем место распаковки программы.
      2. Готово! Вы настоящий хакер!


       F.A.Q. <Как открыть веб-приложение для работы с СУБД MySQL (Open Server)>

      • Заходим в указанную при распаковке папку и находим там два файла: "Open Server x32.exe" и "Open Server x64.exe". Запускаем файл, в зависимости от разрядности Вашей системы.
      • В открывшемся окне выбираем желаемый язык.
      • Далее программа может предложить Вам установить патчи для Microsoft Visual C++. Для пущей уверенности, соглашаемся на установку.
      • В системном трее (области уведомления справа снизу) находим значок красного флажка, нажимаем на него правой кнопкой и выбираем в самом верху кнопку "Запустить".


      При желании, в настройках Вы можете включить автозапуск Open Server при запуске Windows и/или автозапуск самого MySQL-сервера (то действие, которое описано в последнем пункте).


       F.A.Q. <Краткий экскурс по phpMyAdmin>
      Вкладка "Обзор"


      Во вкладке "Обзор" находится содержимое выбранной базы данных. Если данная вкладка неактивна - таблица пуста.


      Типы столбцов


      MySQL поддерживает все основные виды столбцов и кучу их подвидов (которые, как я понимаю, нужны для оптимизации работы таблицы), но я расскажу Вам лишь о самых основных (те, которые потребуются нам для простой работы с данными)

      INT - это, как Вы могли уже догадаться, Integer (от англ. "Целое число"). В нём мы можем хранить все данные, являющиеся целым числом (деньги, уровень, кол-во смертей/убийств и прочее).
      VARCHAR - это тип данных, в котором хранятся строки (буквы любого регистра/числа/спец.символы). Максимальная длина строки, которую может хранить такое поле - 255 символов. VARCHAR - это усовершенствованная версия типа столбца CHAR, главной отличительной особенностью которого является то, что если при создании столбца с типом CHAR прописать 100 символов, а записать, например, 50 - остальные 50 ячеек будут заполнены пробелами. В VARCHAR же сохранится именно 50 символов + 1 символ для нуль-символа (конца строки).
      FLOAT - это тип данных, в котором можно хранить вещественные числа (дробные/числа с плавающей точкой)

      Всё остальное лично мной не использовалось и о всех тонкостях поведать Вам я не могу. Но Вы всегда можете прочитать об этом на официальной странице MySQL =)


      Создание нового столбца (поля)


      Новый столбец (поле) создать очень просто. Для этого находим нужную нам таблицу, открываем её, жмём на вкладку "Структура" и ищем последний столбец. Под ним есть надпись "Добавить" и далее идут настройки. Именно эта строка нам и нужна.
      В поле после "Добавить" пишем то число столбцов, которое нам нужно добавит в таблицу (если они должны располагаться друг за другом). Далее выбираем позицию, в которую нам нужно добавить эти поля (это может быть как начало/конец таблицы, так и определённое поле, после которого новые поля и будут добавлены). Выбираем нужную позицию и жмём "Ок". Далее нужно настроить новые столбцы и всё :)

       Скриншот






      Идентификатор


      Идентификаторы нужны в таблице для того, чтоб можно было с лёгкостью связать данные нескольких таблиц, опираясь на данные какой-то одной. MySQL самостоятельно устанавливает идентификатор для новой строки в момент создания этой строки. Ни в коем случае не меняйте данный параметр вручную. Изменять его может исключительно MySQL!

       Сброс auto_increment до определённого значения
      ДЕЛАЙТЕ ЭТО С ОСТОРОЖНОСТЬЮ! ПРИ НЕПРАВИЛЬНОМ ОБРАЩЕНИИ С AUTO_INCREMENT ВОЗМОЖНА ПОРЧА ДАННЫХ.

      1. Открываем таблицу, в которой будем менять значение поля с auto_increment
      2. Находим вкладку "Операции" и открываем её
      3. Находим поле AUTO_INCREMENT и меняем значение напротив поля на нужное
      Готово. Теперь при создании нового поля значение AUTO_INCREMENT будет равно установленному и начнётся отсчёт именно с него



    • Работа с базой данных:

      В данной инструкции я расскажу Вам о том, как создать базу данных в phpMyAdmin

      1. Открываем phpMyAdmin, если не сделали этого ранее

      2. В панели сверху выбираем пункт "Базы данных"

         Скриншот



      3. В поле под надписью "Создать базу данных" вводим имя нашей базы данных (я введу "sa-mp". Вы можете написать своё) и в поле "Сравнение" выбираем "utf8_general_ci". После того, как совершили эти действия, нажимаем на кнопку "Создать".

         Скриншот



      4. Если всё сделали правильно, phpMyAdmin уведомит Вас о успешном создании нашей базы данных и в списке слева появится новая запись.
         Скриншот



        Если нет - исправляем ошибки, о которых сообщит phpMyAdmin, и создаём.
    Последний раз редактировалось DeimoS; 13.02.2021 в 10:43.
    Связаться со мной в VK можно через личные сообщения этой группы
    Заказы не принимаю

    Широко известно, что идеи стоят 0.8333 цента каждая (исходя из рыночной цены 10 центов за дюжину).
    Великих идей полно, на них нет спроса.
    Воплощение идеи в законченную игру требует долгой работы,
    таланта, терпения и креативности, не говоря уж о затратах денег, времени и ресурсов.
    Предложить идею просто, воплотить – вот в чём проблема

    Steve Pavlina

  2. #2
    Аватар для DeimoS
    Модератор?

    Статус
    Оффлайн
    Регистрация
    27.01.2014
    Адрес
    Восточный Мордор
    Сообщений
    5,588
    Репутация:
    1984 ±

    *Продолжение* [Создание системы аккаунтов:]

    • Создание таблицы для хранения аккаунтов:

      • Выбираем базу данных, в которой будем создавать таблицу (если Вы создавали базу данных вместе со мной, то выбираем "sa-mp")

      • На открывшейся странице находим окошко "Создать таблицу" и вводим данные о таблице:
        • В поле "Имя таблицы" вводим "account"
        • В поле "Количество столбцов" вводим "3"

        и нажимаем "ОК"
         Скриншот



      • Структуру новой таблицы заполняем следующим образом:

          Открыть/закрыть
        • Первый столбец
          - Имя столбца: id
          - Тип столбца: INT
          - Длина/значения: 11
          Все остальные значения, кроме A_I, не трогаем. Находим A_I (Auto Increment) и ставим галочку.
          Идентификаторов не может быть больше одного в таблице. Да оно и не нужно =)


        • Второй столбец
          - Имя столбца: player_name
          - Тип столбца: VARCHAR
          - Длина/значения: 24 (так как длина ника в SA-MP не может превышать 24 символа)
          Все остальные значения не трогаем.

        • Третий столбец
          - Имя столбца: password
          - Тип столбца: VARCHAR
          - Длина/значения: 30 (Я ограничу длину пароля игрока 30-ю символами. Вы можете сделать меньше/больше)
          Все остальные значения не трогаем.


        Внимание:
        MySQL чувствительна к регистру, поэтому если Вы решили записать имена столбцов иначе, учитывайте это в дальнейшем.

         Скриншот


    • Создание самой системы:

      • Данные для подключения
        В начало нашего скрипта (под "#include <a_samp>") добавим:
        1. #include <a_mysql>
        2.  
        3. #define MYSQL_HOST "localhost"//Адрес, по которому расположен MySQL-Сервер
        4. #define MYSQL_USER "root"//Имя пользователя, на которого была создана база данных
        5. #define MYSQL_DATABASE "sa-mp"// Имя базы данных
        6. #define MYSQL_PASSWORD ""//Пароль для доступа к серверу MySQL


        Так же нужно добавить переменную, в которой будет хранится ID подключения к базе данных
          Открыть/закрыть
         Для R39
        1. new mysql_connect_ID;

         Для R40
        1. new MySQL:mysql_connect_ID;


         Разбор кода
        1. #include <a_mysql>

        Собственно, подключаем инклюд, в котором хранится объявление всех функций для работы с плагином

        1. #define MYSQL_HOST "localhost"

        Этот макрос хранит в себе адрес хостинга, где хранится база данных (мы будем запускать на ПК, поэтому указываем адрес локальной сети).

        1. #define MYSQL_USER "root"

        Этот макрос хранит в себе имя пользователя, у которого имеется доступ к базе данных (так как мы запускаем мод на ПК, у нас это администратор. Когда будете запускать мод на хостинге, Вам выдадут особое имя)

        1. #define MYSQL_DATABASE "first_database"

        Этот макрос содержит в себе имя базы данных. Если при создании БД Вы прописывали имя отличное от моего, измените его на то, что указали Вы.

        1. #define MYSQL_PASSWORD ""

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

        1. new mysql_connect_ID;
        2. //new MySQL:mysql_connect_ID;

        Это целочисленная переменная, в которой мы будем хранить ID подключения, которое потребуется для работы большинства функций.


         Скриншот







      • Подключение к базе данных
        Теперь нам нужно связать сервер с нашей базой данных. Для этого в OnGameModeInIt пропишем следующий код:

          Открыть/закрыть
         Для R39
        1. mysql_connect_ID = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_DATABASE, MYSQL_PASSWORD);


         Для R40
        1. mysql_connect_ID = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE);


        Так же, если Вы хотите сохранять в базу данных текст на русском, следует добавить следующие запросы прямо после подключения
          Открыть/закрыть

        1. mysql_set_charset("cp1251");

         Получится так

         Для R39
        1. mysql_connect_ID = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_DATABASE, MYSQL_PASSWORD);
        2. mysql_set_charset("cp1251");

         Для R40
        1. mysql_connect_ID = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE);
        2. mysql_set_charset("cp1251");


         Разбор кода
          Открыть/закрыть
         Для R39
        Функция mysql_connect нужна для подключения плагина к базе данных. Она имеет 7 параметров, из которых только 4 первых обязательны к заполнению.
        1. native mysql_connect(const host[], const user[], const database[], const password[], port = 3306, bool:autoreconnect = true, pool_size = 2);
        2. /*------------------------------------------------------------------------------------------------------------------------------------------
        3. const host[] - Адрес хостинга, на котором расположена база данных. Этот адрес у нас хранится в макросе "MYSQL_HOST"
        4. const user[] - Имя пользователя, который имеет доступ к базе данных. Это имя записано в макросе "MYSQL_USER"
        5. const database[] - Имя базы данных, к которой нужно подключиться. Это имя записано в макросе "MYSQL_DATABASE"
        6. const password[] - Пароль от хостинга, на котором расположена база данных. Этот пароль записан в макросе "MYSQL_PASSWORD"
        7. port = 3306 - Порт, по которому производится подключение. Его нужно прописывать только в случае, когда идёт работа с несколькими базами данных (НЕ ТАБЛИЦАМИ). В нашем случае мы значение не трогали.
        8. bool:autoreconnect = true - Настройки автоматического переподключения к базе данных, если связь с БД была потеряна
        9. pool_size = 2 - Число потоков, по которым будет работать плагин. */

         Для R40
        Функция mysql_connect нужна для подключения плагина к базе данных. Она имеет 5 параметров, из которых только 4 первых обязательны к заполнению.
        1. native MySQL:mysql_connect(const host[], const user[], const password[], const database[], MySQLOpt:option_id = MySQLOpt:0);
        2. /*------------------------------------------------------------------------------------------------------------------------------------------
        3. const host[] - Адрес хостинга, на котором расположена база данных. Этот адрес у нас хранится в макросе "MYSQL_HOST"
        4. const user[] - Имя пользователя, который имеет доступ к базе данных. Это имя записано в макросе "MYSQL_USER"
        5. const password[] - Пароль от хостинга, на котором расположена база данных. Этот пароль записан в макросе "MYSQL_PASSWORD"
        6. const database[] - Имя базы данных, к которой нужно подключиться. Это имя записано в макросе "MYSQL_DATABASE"
        7. MySQLOpt:option_id - Опция, с которой создаётся подключение. Более подробнее я расскажу об этом в отдельной статье*/


        Данная функция возвращает ID подключения, который мы и записали в переменную.

         Скриншот




         Дополнение: Отслеживание качества подключения
        Так же Вы можете добавить простую проверку, которая будет отображать в логах то, насколько удачно произошло подключение к базе данных и выведет причину (либо код ошибки) в случае, если подключение не удалось. Для этого сразу после кода о подключении добавим такой код:
        1. switch(mysql_errno())
        2. {
        3. case 0: print("Подключение к базе данных удалось");
        4. case 1044: print("Подключение к базе данных не удалось [Указано неизвестное имя пользователя]");
        5. case 1045: print("Подключение к базе данных не удалось [Указан неизвестный пароль]");
        6. case 1049: print("Подключение к базе данных не удалось [Указана неизвестная база данных]");
        7. case 2003: print("Подключение к базе данных не удалось [Хостинг с базой данных недоступен]");
        8. case 2005: print("Подключение к базе данных не удалось [Указан неизвестный адрес хостинга]");
        9. default: printf("Подключение к базе данных не удалось [Неизвестная ошибка. Код ошибки: %d]", mysql_errno());
        10. }

        Где mysql_errno - функция, которая возвращает ID ошибки при отправке запросов к базе данных.

         Скриншот








      • "Перечисление" (enum) для хранения данных

         Немного лирики
        Для хранения данных мы можем использовать либо связку "enum + двумерный массив", либо просто массивы, либо pVar. Удобнее всего использовать связку "enum + двумерный массив", но Вы можете использовать другой способ, если достаточно осведомлены о нём и считаете его удобным.
        Как я уже писал ранее, это вводный урок и поэтому мы реализуем хранение лишь трёх параметров: ID аккаунта, имя игрока и пароль от аккаунта. А раз с способом хранения данных мы определились, а так же определились с данными, которые будем хранить - можно приступать к написанию кода.

        Под наши данные для подключения добавим следующий код:
        1. enum e_PLAYER_INFO
        2. {
        3. pID,
        4. pName[MAX_PLAYER_NAME],
        5. pPassword[31]
        6. };
        7. new pInfo[MAX_PLAYERS][e_PLAYER_INFO];


         Разбор кода

         enum
        1. enum e_PLAYER_INFO//Объявление перечисления
        2. {
        3. pID,//Первый член перечисления. Так как значение не указано, по-умолчанию имеет ID 0
        4. pName[MAX_PLAYER_NAME],//Второй член перечисления. Имеет ID 1
        5. pPassword[31]//Третий член перечисления. Имеет ID 2
        6. };
        7. new pInfo[MAX_PLAYERS][e_PLAYER_INFO];//Двумерный массив, где первая группа ячеек подразумевает разделение на 1000 ячеек (под каждого игрока), а вторая - обращение к членам перечисления


        1. enum e_PLAYER_INFO
        2. /*---------------------
        3. enum - оператор
        4. e_PLAYER_INFO - название перечисления*/

        enum - англ. Enumeration (Перечисление) - это тип, состоящий из набора целочисленных констант, называемых перечислителями. Перечисление позволяет объединить данные разных типов в одну "группу". Enum состоит из самого оператора и названия перечисления/

        1. pID,

        Первый член перечисления, а по совместительству и ячейка, в которой будет хранится ID аккаунта игрока. Имеет целочисленный тип и может хранить только целые числа (КЭП)

        1. pName[MAX_PLAYER_NAME],

        Второй член перечисления, а по совместительству и ячейка, в которой будет хранится имя аккаунта. Для хранения строки требуется массив, который мы и создали. В базе данных мы выделили 24 ячейки под хранение ника, поэтому и тут нам глупо выделять меньше/больше.

        1. pPassword[31]

        Третий член массива, а по совместительству и ячейка, в которой будет хранится пароль от аккаунта. Тут мы, опять же, создали массив и выделили 31ячеек (так как решили, что пароль игрока не должен будет превышать эти самые 30 ячеек + 1 символ на конец строки. Если в базе данных Вы выделяли другие число ячеек для столбца - прописывайте своё значение)

        1. new pInfo[MAX_PLAYERS][e_PLAYER_INFO];

        Массив, через который мы будем обращаться у нужному нам члену перечисления за данными. В первой группе ячеек мы будем указывать ID игрока (ID Игрока будет равен номеру ячейки, в которой будут записываться его данные. Так мы с лёгкостью сможем в дальнейшем с этими данными работать). Во второй группе ячеек мы будем указывать имя члена перечисления, которое так же вызовет нужную нам ячейку с данными


         Скриншот








      • Поиск игрока в базе данных и запись ника в массив

        Теперь нам нужно сделать запрос для поиска игрока в базе данных, а так же записать ник игрока (ибо игрок не сможет сменить свой ник без нашего ведома, поэтому логичнее всего просто 1 раз записать его при входе и уже использовать массив, чем каждый раз вызывать GetPlayerName).
        В OnPlayerConnect вставим:
          Открыть/закрыть
         Для R39
        1. GetPlayerName(playerid, pInfo[playerid][pName], MAX_PLAYER_NAME);
        2. new query_string[49+MAX_PLAYER_NAME-4+1];
        3. format(query_string, sizeof(query_string), "SELECT * FROM `account` WHERE `player_name` = '%s'", pInfo[playerid][pName]);
        4. mysql_function_query(mysql_connect_ID, query_string, true, "FindPlayerInTable","i", playerid);

         Разбор кода
        1. GetPlayerName(playerid, pInfo[playerid][pName], MAX_PLAYER_NAME);

        С помощью функции GetPlayerName мы записали имя игрока в наш массив, дабы в будущем получить возможность брать имя игрока из этого массива, а не вызывать повторно функцию GetPlayerName.

        1. format(query_string, sizeof(query_string), "SELECT * FROM `account` WHERE `player_name` = '%s'", pInfo[playerid][pName]);

        С помощью функции format мы вставили имя в наш запрос, который заставит плагин просмотреть таблицу на предмет строк, в которых данные столбца "player_name" совпадают с именем игрока.

        1. mysql_function_query(mysql_connect_ID, query_string, true, "FindPlayerInTable", "i", playerid);
        2. /* А это и есть та самая функция, которая отправляет команду таблице через плагин.
        3. ----------------------------------------------------------------------------------------------
        4.  
        5. mysql_function_query - название функции
        6.  
        7. mysql_connect_ID - Первый параметр функции, в котором указывается ID базы данных, с которой идёт работа
        8.  
        9. query_string - Второй параметр функции, в котором пишется сам запрос с которым плагин обратится к базе данных
        10.  
        11. true - Третий параметр, который отвечает за кэширование запроса. Кэширование нужно только для тех запросов, в результате которых из таблицы должно вернуть какие-либо данные (например, найдена ли нужная строка или же какие данные записаны в той или иной строке. Если мы обновляем данные в таблице - кэширование не требуется)
        12.  
        13. "FindPlayerInTable" - Четвёртый параметр, в котором указывается имя паблика, в который будут помещены данные, возвращённые из базы данных (требуется только в случае запросов с кэшированием)
        14.  
        15. "i" - Пятый параметр, который позволяет передать в паблик, указанный в четвёртом параметре, какие-либо данные из того участка кода, в котором был отправлен запрос в базу данных. Работает точно так же, как SetTimerEx (в этом параметре указываются типы данных, которые будут переданы. Сами данные указываются в следующем параметре)
        16.  
        17. playerid - Шестой параметр, в котором и указываются все данные для передачи в указанный паблик. Это может быть любой тип данных из тех, кто доступны в Pawn.



         Для R40
        1. GetPlayerName(playerid, pInfo[playerid][pName], MAX_PLAYER_NAME);
        2. new query_string[49+MAX_PLAYER_NAME-4+1];
        3. format(query_string, sizeof(query_string), "SELECT * FROM `account` WHERE `player_name` = '%s'", pInfo[playerid][pName]);
        4. mysql_tquery(mysql_connect_ID, query_string, "FindPlayerInTable","i", playerid);

         Разбор кода
        1. GetPlayerName(playerid, pInfo[playerid][pName], MAX_PLAYER_NAME);

        С помощью функции GetPlayerName мы записали имя игрока в наш массив, дабы в будущем получить возможность брать имя игрока из этого массива, а не вызывать повторно функцию GetPlayerName.

        1. format(query_string, sizeof(query_string), "SELECT * FROM `account` WHERE `player_name` = '%s'", pInfo[playerid][pName]);

        С помощью функции format мы вставили имя в наш запрос, который заставит плагин просмотреть таблицу на предмет строк, в которых данные столбца "player_name" совпадают с именем игрока.

        1. mysql_tquery(mysql_connect_ID, query_string, "FindPlayerInTable", "i", playerid);
        2. /* А это и есть та самая функция, которая отправляет команду таблице через плагин.
        3. ----------------------------------------------------------------------------------------------
        4.  
        5. mysql_tquery - название функции
        6.  
        7. mysql_connect_ID - Первый параметр функции, в котором указывается ID базы данных, с которой идёт работа
        8.  
        9. query_string - Второй параметр функции, в котором пишется сам запрос с которым плагин обратится к базе данных
        10.  
        11. "FindPlayerInTable" - Третий параметр, в котором указывается имя паблика, в который будут помещены данные, возвращённые из базы данных (требуется только в случае запросов с кэшированием)
        12.  
        13. "i" - Четвёртый параметр, который позволяет передать в паблик, указанный в четвёртом параметре, какие-либо данные из того участка кода, в котором был отправлен запрос в базу данных. Работает точно так же, как SetTimerEx (в этом параметре указываются типы данных, которые будут переданы. Сами данные указываются в следующем параметре)
        14.  
        15. playerid - Пятый параметр, в котором и указываются все данные для передачи в указанный паблик. Это может быть любой тип данных из тех, кто доступны в Pawn.





         Скриншот




        Так же нам нужно эти данные записать в наш массив. Для этого мы идём в самый конец скрипта (для удобства) и вставляем туда следующий код:
          Открыть/закрыть
         Для R39
        1. forward FindPlayerInTable(playerid);
        2. public FindPlayerInTable(playerid)
        3. {
        4. new rows, fields;
        5. cache_get_data(rows, fields);
        6. if(!rows)
        7. {
        8. ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "Введите пароль для регистрации нового аккаунта:", "Регистрация", "Выход");
        9. }
        10. else
        11. {
        12. ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "Введите пароль от аккаунта для того, чтоб продолжить игру:", "Вход", "Выход");
        13. cache_get_field_content(0, "password", pInfo[playerid][pPassword], mysql_connect_ID, 31);
        14. }
        15. return 1;
        16. }

         Разбор кода
        1. forward FindPlayerInTable(playerid);
        2. public FindPlayerInTable(playerid)

        Так мы объявляем новый колбэк, указывая его имя и параметры, которые должны передаваться при вызове этого колбэка.

        1. new rows, fields;

        Создаём две переменные, в которые запишем число строк и столбцов, которые будут возвращены в результате обработки нашего запроса

        1. cache_get_data(rows, fields);
        2. /* Это и есть та самая запись.
        3. ------------------------------
        4. cache_get_data - имя функции, которая возвращает число строк и столбцов, возвращённых в результате обработки запроса
        5. rows - первый параметр функции, который хранит в себе число строк, возвращённых MySQL
        6. fields - второй параметр функции, который хранит в себе число столбцов, возвращённых MySQL
        7. Оба параметра обязательны*/


        1. if(!rows)

        Проверка, которая звучит так: "Если число строк, возвращённых MySQL, равно нулю...". Иначе говоря, если MySQL вернуло 0 строк после обработки запроса, значит аккаунт игрока не создан и нам можно предложить создать этот самый аккаунт игроку.

        1. ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "Введите пароль для регистрации нового аккаунта:", "Регистрация", "Выход");

        Собственно, сам диалог регистрации
         Подробнее о диалоге
        1. ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "Введите пароль для регистрации нового аккаунта:", "Регистрация", "Выход");
        2. /*------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        3. ShowPlayerDialog - Имя функции, которая отвечает за показ диалога
        4. playerid - Первый параметр, который отвечает за то, какому игроку показывать диалог (указывается ID игрока)
        5. dRegister - Второй параметр, в котором указывается ID диалога, по которому мы потом будем добавлять различные действия для этого самого диалога (dRegister - макрос, который мы создадим чуть позже)
        6. DIALOG_STYLE_INPUT - Третий параметр, в котором указывается стиль диалога (в данном случае это диалог с полем для ввода пользовательских данных)
        7. "Регистрация нового пользователя" - Четвёртый параметр, в котором указывается текст, который будет отображаться в шапке диалога
        8. "Введите пароль для регистрации нового аккаунта:" - Пятый параметр, в котором указывается текст самого диалогового окна
        9. "Регистрация" - Седьмой параметр, в котором мы указываем текст, содержащийся в левой кнопке диалогового окна
        10. "Выход" - Восьмой параметр, в котором указываем текст, содержащийся в правой кнопке диалогового окна
        11. */


        Или же прочтите статьи о диалоговых окнах на wiki.sa-mp.com:



        1. else

        Если проверка "if(!rows)" не сработала, значит MySQL нашёл в таблице аккаунт с нужным именем. А раз нашёл, значит показываем диалог с авторизацией и запишем пароль от аккаунта для дальнейших проверок

        1. cache_get_field_content(0, "password", pInfo[playerid][pPassword], mysql_connect_ID, 31);
        2. /*-------------------------------------------------------------------------------------------------------------------------
        3. cache_get_field_content - название функции, которая возвращает значение указанного столбца
        4. 0 - первый параметр функции, в котором указывается порядковый номер строки, возвращённой запросом (НЕ В ТАБЛИЦЕ). Более подробно расскажу чуть ниже
        5. "password" - второй параметр, в котором указывается имя столбца, значение которого нам нужно получить
        6. pInfo[playerid][pPassword] - третий параметр, в котором указывается массив для записи данных, которые вернёт функция
        7. mysql_connect_ID - четвёртый параметр, в котором указывается ID соединения с базой данных
        8. 31- пятый параметр, в котором указываться максимальный размер строки, которую может вернуть функция (ставить столько, сколько выделено в таблице для столбца и в скрипте для массива)
        9. */




         Для R40
        1. forward FindPlayerInTable(playerid);
        2. public FindPlayerInTable(playerid)
        3. {
        4. new rows;
        5. cache_get_row_count(rows);
        6.  
        7. if(!rows)
        8. {
        9. ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "Введите пароль для регистрации нового аккаунта:", "Регистрация", "Выход");
        10. }
        11. else
        12. {
        13. ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "Введите пароль от аккаунта для того, чтоб продолжить игру:", "Вход", "Выход");
        14. cache_get_value_name(0, "password", pInfo[playerid][pPassword], 31);
        15. }
        16. return 1;
        17. }

         Разбор кода
        1. forward FindPlayerInTable(playerid);
        2. public FindPlayerInTable(playerid)

        Так мы объявляем новый колбэк, указывая его имя и параметры, которые должны передаваться при вызове этого колбэка.

        1. new rows;

        Создаём переменную, в которую запишем число строк, которые будут возвращены в результате обработки нашего запроса


        1. cache_get_row_count(rows);
        2. /* Это и есть та самая запись.
        3. ------------------------------
        4. cache_get_row_count - имя функции, которая возвращает число строк, возвращённых в результате обработки запроса
        5. rows - параметр функции, который хранит в себе число строк, возвращённых MySQL */


        1. if(!rows)

        Проверка, которая звучит так: "Если число строк, возвращённых MySQL, равно нулю...". Иначе говоря, если MySQL вернуло 0 строк после обработки запроса, значит аккаунт игрока не создан и нам можно предложить создать этот самый аккаунт игроку.

        1. ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "Введите пароль для регистрации нового аккаунта:", "Регистрация", "Выход");

        Собственно, сам диалог регистрации
         Подробнее о диалоге
        1. ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "Введите пароль для регистрации нового аккаунта:", "Регистрация", "Выход");
        2. /*------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        3. ShowPlayerDialog - Имя функции, которая отвечает за показ диалога
        4. playerid - Первый параметр, который отвечает за то, какому игроку показывать диалог (указывается ID игрока)
        5. dRegister - Второй параметр, в котором указывается ID диалога, по которому мы потом будем добавлять различные действия для этого самого диалога (dRegister - макрос, который мы создадим чуть позже)
        6. DIALOG_STYLE_INPUT - Третий параметр, в котором указывается стиль диалога (в данном случае это диалог с полем для ввода пользовательских данных)
        7. "Регистрация нового пользователя" - Четвёртый параметр, в котором указывается текст, который будет отображаться в шапке диалога
        8. "Введите пароль для регистрации нового аккаунта:" - Пятый параметр, в котором указывается текст самого диалогового окна
        9. "Регистрация" - Седьмой параметр, в котором мы указываем текст, содержащийся в левой кнопке диалогового окна
        10. "Выход" - Восьмой параметр, в котором указываем текст, содержащийся в правой кнопке диалогового окна
        11. */


        Или же прочтите статьи о диалоговых окнах на wiki.sa-mp.com:



        1. else

        Если проверка "if(!rows)" не сработала, значит MySQL нашёл в таблице аккаунт с нужным именем. А раз нашёл, значит показываем диалог с авторизацией и запишем пароль от аккаунта для дальнейших проверок

        1. cache_get_value_name(0, "password", pInfo[playerid][pPassword], mysql_connect_ID, 31);
        2. /*-------------------------------------------------------------------------------------------------------------------------
        3. cache_get_value_name - название функции, которая возвращает значение указанного столбца
        4. 0 - первый параметр функции, в котором указывается порядковый номер строки, возвращённой запросом (НЕ В ТАБЛИЦЕ). Более подробно расскажу чуть ниже
        5. "password" - второй параметр, в котором указывается имя столбца, значение которого нам нужно получить
        6. pInfo[playerid][pPassword] - третий параметр, в котором указывается массив для записи данных, которые вернёт функция
        7. mysql_connect_ID - четвёртый параметр, в котором указывается ID соединения с базой данных
        8. 31- пятый параметр, в котором указываться максимальный размер строки, которую может вернуть функция (ставить столько, сколько выделено в таблице для столбца и в скрипте для массива)
        9. */







      • Создание действий для диалогов регистрации/авторизации

        Сначала нам нужно вспомнить о себе любимых и упростить работу с диалогами, создав ещё одно перечисление, которое будет позволять писать нам на месте ID диалога какие-то слова, намекающие нам на предназначение этого диалога, а не обычные числа. Ведь согласитесь, "dRegister" гораздо сильнее намекает нам на то, что этот диалог является диалогом регистрации, нежели просто "0", "1" или какое-либо ещё число. Но перечисление не только даст нам возможность более лучше понимать предназначение диалога по его ID, но и избавит нас от страха того, что ID диалогов могут перепутаться, ведь перечисление само определит свободный ID и установит его.

        Чтоб создать такое перечисление, находим наше перечисление с данными игроков и выше него создадим ещё одно:
        1. enum e_DIALOG_IDs
        2. {
        3. dKickMessage,//Автоматически займёт ID 0
        4. dRegister,//ID 1
        5. dLogin//ID 2
        6. };


         Если Вы желаете изменить порядковые ID в перечислении
        Если Вы желаете, чтоб первый диалог занимал не ID 0, а, например, ID 10, достаточно присвоить первому члену перечисления "10" и далее перечисление само рассчитает ID для последующих членов:
        1. enum e_DIALOG_IDs
        2. {
        3. dKickMessage = 10,//Установим ID 10
        4. dRegister,//ID 11
        5. dLogin//ID 12
        6. };

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

        Всё =) Теперь достаточно придумывать имя каждому новому диалогу, дописывать его в этот список и тогда Вы точно никогда не запутаетесь при создании новых диалогов.

        Теперь напишем сами действия. В OnDialogResponse добавим:
          Открыть/закрыть
         Для R39

        1. switch(dialogid)
        2. {
        3. case dRegister:
        4. {
        5. if(!response)
        6. {
        7. ShowPlayerDialog(playerid, dKickMessage, DIALOG_STYLE_MSGBOX, "Оповещение", "{FFFFFF}Вы были кикнуты с сервера.\n{FF0000}Причина: Отказ от регистрации.\n{FFFFFF}Для выхода с сервера введите \"/q\" в чат", "Выход", "");
        8. return Kick(playerid);
        9. }
        10. if(!strlen(inputtext)) return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Вы не можете продолжить регистрацию не введя пароль!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        11. else if(strlen(inputtext) < 4) return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Пароль слишком короткий!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        12. else if(strlen(inputtext) > 30) return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Пароль слишком длинный!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        13. for(new i = strlen(inputtext)-1; i != -1; i--)
        14. {
        15. switch(inputtext[i])
        16. {
        17. case '0'..'9', 'а'..'я', 'a'..'z', 'А'..'Я', 'A'..'Z': continue;
        18. default: return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Пароль содержит запрещённые символы!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        19. }
        20. }
        21. pInfo[playerid][pPassword][0] = EOS;
        22. strins(pInfo[playerid][pPassword], inputtext, 0);
        23. CreateNewAccount(playerid, pInfo[playerid][pPassword]);
        24. return 1;
        25. }
        26. case dLogin:
        27. {
        28. if(!response)
        29. {
        30. ShowPlayerDialog(playerid, dKickMessage, DIALOG_STYLE_MSGBOX, "Оповещение", "{FFFFFF}Вы были кикнуты с сервера.\n{FF0000}Причина: Отказ от авторизации.\n{FFFFFF}Для выхода с сервера введите \"/q\" в чат", "Выход", "");
        31. return Kick(playerid);
        32. }
        33. if(!strlen(inputtext)) return ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы не можете продолжить авторизацию не введя пароль!\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        34. for(new i = strlen(inputtext)-1; i != -1; i--)
        35. {
        36. switch(inputtext[i])
        37. {
        38. case '0'..'9', 'а'..'я', 'a'..'z', 'А'..'Я', 'A'..'Z': continue;
        39. default: return ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Введённый пароль содержит запрещённые символы!\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        40. }
        41. }
        42. if(!strcmp(pInfo[playerid][pPassword], inputtext))
        43. {
        44. new query_string[49+MAX_PLAYER_NAME];
        45. format(query_string, sizeof(query_string), "SELECT * FROM `account` WHERE `player_name` = '%s'", pInfo[playerid][pName]);
        46. mysql_function_query(mysql_connect_ID, query_string, true, "UploadPlayerAccount","i", playerid);
        47. }
        48. else
        49. {
        50. switch(GetPVarInt(playerid, "WrongPassword"))
        51. {
        52. case 0: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталось 3 попытки.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        53. case 1: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталось 2 попытки.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        54. case 2: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталось 1 попытка.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        55. case 3: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталась последняя попытка, после чего Вас кикнет.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        56. default:
        57. {
        58. ShowPlayerDialog(playerid, dKickMessage, DIALOG_STYLE_MSGBOX, "Оповещение", "{FFFFFF}Вы были кикнуты с сервера.\n{FF0000}Причина: Превышен лимит попыток на ввод пароля.\n{FFFFFF}Для выхода с сервера введите \"/q\" в чат", "Выход", "");
        59. return Kick(playerid);
        60. }
        61. }
        62. SetPVarInt(playerid, "WrongPassword", GetPVarInt(playerid, "WrongPassword")+1);
        63. }
        64. return 1;
        65. }
        66. }



         Для R40

        1. switch(dialogid)
        2. {
        3. case dRegister:
        4. {
        5. if(!response)
        6. {
        7. ShowPlayerDialog(playerid, dKickMessage, DIALOG_STYLE_MSGBOX, "Оповещение", "{FFFFFF}Вы были кикнуты с сервера.\n{FF0000}Причина: Отказ от регистрации.\n{FFFFFF}Для выхода с сервера введите \"/q\" в чат", "Выход", "");
        8. return Kick(playerid);
        9. }
        10. if(!strlen(inputtext)) return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Вы не можете продолжить регистрацию не введя пароль!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        11. else if(strlen(inputtext) < 4) return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Пароль слишком короткий!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        12. else if(strlen(inputtext) > 30) return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Пароль слишком длинный!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        13. for(new i = strlen(inputtext)-1; i != -1; i--)
        14. {
        15. switch(inputtext[i])
        16. {
        17. case '0'..'9', 'а'..'я', 'a'..'z', 'А'..'Я', 'A'..'Z': continue;
        18. default: return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Пароль содержит запрещённые символы!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        19. }
        20. }
        21. pInfo[playerid][pPassword][0] = EOS;
        22. strins(pInfo[playerid][pPassword], inputtext, 0);
        23. CreateNewAccount(playerid, pInfo[playerid][pPassword]);
        24. return 1;
        25. }
        26. case dLogin:
        27. {
        28. if(!response)
        29. {
        30. ShowPlayerDialog(playerid, dKickMessage, DIALOG_STYLE_MSGBOX, "Оповещение", "{FFFFFF}Вы были кикнуты с сервера.\n{FF0000}Причина: Отказ от авторизации.\n{FFFFFF}Для выхода с сервера введите \"/q\" в чат", "Выход", "");
        31. return Kick(playerid);
        32. }
        33. if(!strlen(inputtext)) return ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы не можете продолжить авторизацию не введя пароль!\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        34. for(new i = strlen(inputtext)-1; i != -1; i--)
        35. {
        36. switch(inputtext[i])
        37. {
        38. case '0'..'9', 'а'..'я', 'a'..'z', 'А'..'Я', 'A'..'Z': continue;
        39. default: return ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Введённый пароль содержит запрещённые символы!\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        40. }
        41. }
        42. if(!strcmp(pInfo[playerid][pPassword], inputtext))
        43. {
        44. new query_string[49+MAX_PLAYER_NAME];
        45. format(query_string, sizeof(query_string), "SELECT * FROM `account` WHERE `player_name` = '%s'", pInfo[playerid][pName]);
        46. mysql_tquery(mysql_connect_ID, query_string, "UploadPlayerAccount","i", playerid);
        47. }
        48. else
        49. {
        50. switch(GetPVarInt(playerid, "WrongPassword"))
        51. {
        52. case 0: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталось 3 попытки.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        53. case 1: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталось 2 попытки.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        54. case 2: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталось 1 попытка.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        55. case 3: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталась последняя попытка, после чего Вас кикнет.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        56. default:
        57. {
        58. ShowPlayerDialog(playerid, dKickMessage, DIALOG_STYLE_MSGBOX, "Оповещение", "{FFFFFF}Вы были кикнуты с сервера.\n{FF0000}Причина: Превышен лимит попыток на ввод пароля.\n{FFFFFF}Для выхода с сервера введите \"/q\" в чат", "Выход", "");
        59. return Kick(playerid);
        60. }
        61. }
        62. SetPVarInt(playerid, "WrongPassword", GetPVarInt(playerid, "WrongPassword")+1);
        63. }
        64. return 1;
        65. }
        66. }


         Разбор кода
        1. switch(dialogid)

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


        1. case dRegister:

        Эта строка всё так же относится к switch. Именно тут мы указываем значения, которые будем искать в "dialogid". В данном случае мы указали не число, а член перечисления, который подразумевает под собой ID "1"


        1. if(!response)
        2. {
        3. ShowPlayerDialog(playerid, dKickMessage, DIALOG_STYLE_MSGBOX, "Оповещение", "{FFFFFF}Вы были кикнуты с сервера.\n{FF0000}Причина: Отказ от регистрации.\n{FFFFFF}Для выхода с сервера введите \"/q\" в чат", "Вход", "Выход");
        4. return Kick(playerid);
        5. }

        Тут мы проверяем значение параметра "response". Если оно равно нулю, значит игрок нажал на вторую (правую) кнопку или Esc (то бишь, попытался закрыть диалоговое окно) и поэтому мы его отсоединяем от сервера, показывая диалог-оповещение.
         Что за восклицательный знак перед response?
        Восклицательный знак перед параметром означает то, что мы ожидаем увидеть в "response" значение "false". Следовательно, эта проверка может выглядеть и так "if(response == 0)".

        Для тех, кто не в курсе:
        1. false = 0
        2. true = "любое значение, не равное нулю"



        1. if(!strlen(inputtext)) return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Вы не можете продолжить регистрацию не введя пароль!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        2. else if(strlen(inputtext) < 4) return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Пароль слишком короткий!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        3. else if(strlen(inputtext) > 30) return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Пароль слишком длинный!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        4. for(new i = strlen(inputtext)-1; i != -1; i--)
        5. {
        6. switch(inputtext[i])
        7. {
        8. case '0'..'9', 'а'..'я', 'a'..'z', 'А'..'Я', 'A'..'Z': continue;
        9. default: return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Пароль содержит запрещённые символы!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        10. }
        11. }

        Это серия различных проверок того, что ввёл игрок в поле для ввода (в данном случае - это пароль). Вот разбор первых трёх условий
        1. if(!strlen(inputtext)) // Если игрок ничего не ввёл в поле для ввода...
        2. else if(strlen(inputtext) < 4) // Если длинна введённого игроком текста меньше четырёх символов...
        3. else if(strlen(inputtext) > 30) // Если длинна введённого игроком текста больше 30 символов...

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

        Теперь разберёмся с циклом.
        1. for(new i = strlen(inputtext)-1; i != -1; i--)
        2. {
        3. switch(inputtext[i])
        4. {
        5. case '0'..'9', 'а'..'я', 'a'..'z', 'А'..'Я', 'A'..'Z': continue;
        6. default: return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Пароль содержит запрещённые символы!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");
        7. }
        8. }

        О циклах можно прочесть, опять же, в статье, написанной Ziggi об операторах. Я же расскажу подробнее о структуре.
        1. for(new i = strlen(inputtext)-1; i != -1; i--)
        2. /*--------------------------------------------
        3. new i = strlen(inputtext)-1
        4. // strlen вернёт число символов, введённых игроком в поле для ввода (текст, введённый игроком, хранится в inputtext), но strlen учитывает и нуль-символ, который нам совсем не нужен. Поэтому от возвращённого функцией strlen числа отнимем единицу и запишем это число в переменную "i".
        5. --------------------------------------------
        6. i != -1
        7. // Тут мы задаём условие, которое звучит так: "Если "i" не равно "-1" - совершаем новый такт в цикле. Иначе останавливаем выполнение цикла". Стоит заметить, что проверка "!=" (не равно) подразумевает под собой то, что число "-1" не входит в поле видимости цикла (то есть, при "i" равной "-1", код в теле цикла не сработает. На последней итерации (такте) цикла переменная "i" будет равна "0").
        8. --------------------------------------------
        9. i--
        10. // Ну а тут мы просто отнимаем единицу от значения, хранящегося в "i". Эта запись равносильна следующим:
        11. 1) "i -= 1"
        12. 2) "i = i - 1"
        13. */


        Ну а это
        1. switch(inputtext[i])

        уже знакомый нам switch, где мы проверяем значение определённой ячейки в массиве (ID этой ячейки хранится в "i")

         Немного о кодировках
        Теперь окунёмся в теоретическую часть, слабо относящуюся к уроку, которая поможет понять код, разбираемый дальше.
        Любая машина, которая в перспективе может поднять восстание и уничтожить жизнь на Земле, не понимает ничего, кроме нулей и единиц. Но мы, человеки создавшие этих киборгов-убийц, не согласились плясать под их дудку и придумали различные способы конвертировать привычные нам символы, отличные от нуля и единицы, в привычные киборгам-убийцам нули и единицы. Эти способы мы обозвали кодировками. Кодировок на сегодняшний день есть достаточно много и все они представляют из себя что-то типа таблиц, где за каждым символом, понятным нам, закреплён набор других символов (если кодировка направлена на двоичную систему счисления, с которой и работают машины, то это будет набор нулей и единиц. Ну а если нет, то, в любом случае, данные из отличной от двоичной системы счисления, конвертируются в двоичную систему счисления и только потом подаются машине). И какой бы символ Вы не попытались всунуть машине, она конвертирует его в набор нулей и единиц, опираясь на заданную в неё кодировку, и только потом будет с ним работать (кстати, именно по причине того, что в каждой кодировке за символами может быть закреплено любое кол-во нулей и единиц, которое заблагорассудится создателю этой кодировки, случается искажение текста, если напечатать текст в редакторе с одной кодировкой, а после переключить на другую. То есть, машина не может сама определить кодировку и если в одной кодировке за символом "@" закреплён такой набор нулей и единиц - "01110", а в другой, например, "01010", машина выдаст Вам не "@", а что-то совсем другое).
        В общем, наматываем на ус следующее:
        1) Однажды Ваш тостер поджарит не хлебушек, а Вас.
        2) Машины работают с набором нулей и единиц. Вся информация, какой бы она не была, конвертируется в нули и единицы. А для того, чтоб мы могли как-то контролировать этот процесс, придумали кодировки.

        Когда намотка на ус завершена, приступаем к дальнейшему разбору кода.


        switch работает исключительно с числами. В case нельзя указать ни переменную, ни слово в привычном для всех виде. Поэтому есть два варианта:
        1) Открывать таблицу символов для ASCII кодировки и искать тот набор нулей и единиц, что соответствует нашему символу. Ну и вписывать его.
        2) Использовать апостроф и операцию, что я описал в первом пункте, машина сделает за Вас.
        В коде выше мы используем именно второй способ, ибо он гораздо нагляднее.
        1. case '0'..'9', 'а'..'я', 'a'..'z', 'А'..'Я', 'A'..'Z': continue;

        Как я уже описывал выше, в switch мы проверяем каждый символ в ведённой игроком строке. А тут мы указываем то, какие из символов мы ищем. В данном случае если мы находим одну из цифр или же букв кириллического/латинского алфавита в любом из регистров, то мы пропускаем итерацию и смотрим содержимое следующей ячейки в массиве с строкой. То есть, тут мы указываем все символы, что разрешены в пароле.

        1. default: return ShowPlayerDialog(playerid, dRegister, DIALOG_STYLE_INPUT, "Регистрация нового пользователя", "{FF0000}Ошибка: {FFFFFF}Пароль содержит запрещённые символы!\nВведите пароль для регистрации нового аккаунта:\n{C0C0C0}Примечание:\n{666666}- Пароль чувствителен к регистру.\n- Пароль должен содержать от 4 до 30 символов.\n- Пароль может содержать латинские/кириллические символы и цифры (aA-zZ, аА-яЯ, 0-9).", "Регистрация", "Выход");

        default в "switch" - это как else в "if". То есть, если ни одно из условий в case не сработало, значит в дело вступает код из default. И в данном случае мы показываем игроку диалог в котором сказано, что тот ввёл запрещённый символ, отличный от перечисленных в case.


        Если ни одно из условий выше не сработало, значит игрок удовлетворил все наши правила по поводу введённого пароля и можно приступить к дальнейшей регистрации. Этот код:
        1. pInfo[playerid][pPassword][0] = EOS;

        Обнулит значение массива, хранящего пароль, дабы в нём по какой-либо причине не остался пароль предыдущего пользователя при регистрации нового. А этот код:
        1. strins(pInfo[playerid][pPassword], inputtext, 0);

        запишет введённый игроком пароль в массив pInfo, в ячейку с номером, хранящимся в playerid, которая сама разделяется на то число ячеек, сколько членов перечисления указано в enum (в данном случае в ячейку, которая равна ячейке "pPassword", а это ячейка с ID 2).
        О функции strins можно прочесть тут.


        А тут
        1. CreateNewAccount(playerid, pInfo[playerid][pPassword]);

        Мы вызываем самописанную функцию, в которой будет хранится код создания аккаунта (запросы и прочее).




        В диалоге с авторизацией условия практически идентичны, поэтому описывать их не буду, а перейду сразу к новому условию:
          Открыть/закрыть
         Для R39
        1. if(!strcmp(pInfo[playerid][pPassword], inputtext))
        2. {
        3. new query_string[49+MAX_PLAYER_NAME];
        4. format(query_string, sizeof(query_string), "SELECT * FROM `account` WHERE `player_name` = '%s'", pInfo[playerid][pName]);
        5. mysql_function_query(mysql_connect_ID, query_string, true, "UploadPlayerAccount","i", playerid);
        6. }
        7. else
        8. {
        9. switch(GetPVarInt(playerid, "WrongPassword"))
        10. {
        11. case 0: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталось 3 попытки.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        12. case 1: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталось 2 попытки.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        13. case 2: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталось 1 попытка.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        14. case 3: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталась последняя попытка, после чего Вас кикнет.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        15. default:
        16. {
        17. ShowPlayerDialog(playerid, dKickMessage, DIALOG_STYLE_MSGBOX, "Оповещение", "{FFFFFF}Вы были кикнуты с сервера.\n{FF0000}Причина: Превышен лимит попыток на ввод пароля.\n{FFFFFF}Для выхода с сервера введите \"/q\" в чат", "Вход", "Выход");
        18. return Kick(playerid);
        19. }
        20. }
        21. SetPVarInt(playerid, "WrongPassword", GetPVarInt(playerid, "WrongPassword")+1);
        22. }



        1. if(!strcmp(pInfo[playerid][pPassword], inputtext))

        Тут мы сравниваем введённый игроком пароль с тем, что загрузили в массив pInfo ранее (при показе диалогов в "FindPlayerInTable")
        Если пароли совпали, то отправляем запрос в базу данных, который загрузит из таблицы все данные об игроке и удалим pVar, в котором мы подсчитываем число попыток ввести пароль
        1. new query_string[49+MAX_PLAYER_NAME];
        2. /* Массив, 49 ячеек которого нужны для хранения текста запроса и 24 ячейки (MAX_PLAYER_NAME равно 24)
        3. для хранения ника игрока. Точнее, тут мы указали максимально возможную длину запроса*/
        4. format(query_string, sizeof(query_string), "SELECT * FROM `account` WHERE `player_name` = '%s'", pInfo[playerid][pName]);
        5. /*SELECT * FROM - Указываем, что нам нужны данные всех столбцов...
        6. `account`- Указываем имя таблицы, в которой нужно искать
        7. WHERE `player_name` = '%s' - Указываем то, на основе каких данных нужно делать выборку (в данном случае грузим все найденные данные, исходя из имени игрока)*/
        8. mysql_function_query(mysql_connect_ID, query_string, true, "UploadPlayerAccount","i", playerid);
        9. /*Напоминаю, что название функции, в которую будут возвращены данные из таблицы, указываются в четвёртом параметре и сейчас эта функция называется "UploadPlayerAccount"*/

        Ну а если не совпали, показываем игроку сообщение о том, что он неверно ввёл пароль и прибавляем к pVar для подсчёта попыток единицу. Когда значение pVar будет больше трёх, кикаем игрока.


         Для R40
        1. if(!strcmp(pInfo[playerid][pPassword], inputtext))
        2. {
        3. new query_string[49+MAX_PLAYER_NAME];
        4. format(query_string, sizeof(query_string), "SELECT * FROM `account` WHERE `player_name` = '%s'", pInfo[playerid][pName]);
        5. mysql_tquery(mysql_connect_ID, query_string, "UploadPlayerAccount","i", playerid);
        6. }
        7. else
        8. {
        9. switch(GetPVarInt(playerid, "WrongPassword"))
        10. {
        11. case 0: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталось 3 попытки.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        12. case 1: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталось 2 попытки.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        13. case 2: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталось 1 попытка.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        14. case 3: ShowPlayerDialog(playerid, dLogin, DIALOG_STYLE_INPUT, "Авторизация", "{FF0000}Ошибка: {FFFFFF}Вы ввели неверный пароль! У Вас осталась последняя попытка, после чего Вас кикнет.\nВведите пароль от аккаунта для входа на сервер:", "Вход", "Выход");
        15. default:
        16. {
        17. ShowPlayerDialog(playerid, dKickMessage, DIALOG_STYLE_MSGBOX, "Оповещение", "{FFFFFF}Вы были кикнуты с сервера.\n{FF0000}Причина: Превышен лимит попыток на ввод пароля.\n{FFFFFF}Для выхода с сервера введите \"/q\" в чат", "Вход", "Выход");
        18. return Kick(playerid);
        19. }
        20. }
        21. SetPVarInt(playerid, "WrongPassword", GetPVarInt(playerid, "WrongPassword")+1);
        22. }



        1. if(!strcmp(pInfo[playerid][pPassword], inputtext))

        Тут мы сравниваем введённый игроком пароль с тем, что загрузили в массив pInfo ранее (при показе диалогов в "FindPlayerInTable")
        Если пароли совпали, то отправляем запрос в базу данных, который загрузит из таблицы все данные об игроке и удалим pVar, в котором мы подсчитываем число попыток ввести пароль
        1. new query_string[49+MAX_PLAYER_NAME];
        2. /* Массив, 49 ячеек которого нужны для хранения текста запроса и 24 ячейки (MAX_PLAYER_NAME равно 24)
        3. для хранения ника игрока. Точнее, тут мы указали максимально возможную длину запроса*/
        4. format(query_string, sizeof(query_string), "SELECT * FROM `account` WHERE `player_name` = '%s'", pInfo[playerid][pName]);
        5. /*SELECT * FROM - Указываем, что нам нужны данные всех столбцов...
        6. `account`- Указываем имя таблицы, в которой нужно искать
        7. WHERE `player_name` = '%s' - Указываем то, на основе каких данных нужно делать выборку (в данном случае грузим все найденные данные, исходя из имени игрока)*/
        8. mysql_tquery(mysql_connect_ID, query_string, "UploadPlayerAccount","i", playerid);
        9. /*Напоминаю, что название функции, в которую будут возвращены данные из таблицы, указываются в четвёртом параметре и сейчас эта функция называется "UploadPlayerAccount"*/

        Ну а если не совпали, показываем игроку сообщение о том, что он неверно ввёл пароль и прибавляем к pVar для подсчёта попыток единицу. Когда значение pVar будет больше трёх, кикаем игрока.



         Скриншот






    Последний раз редактировалось DeimoS; 06.03.2023 в 16:58.
    Связаться со мной в VK можно через личные сообщения этой группы
    Заказы не принимаю

    Широко известно, что идеи стоят 0.8333 цента каждая (исходя из рыночной цены 10 центов за дюжину).
    Великих идей полно, на них нет спроса.
    Воплощение идеи в законченную игру требует долгой работы,
    таланта, терпения и креативности, не говоря уж о затратах денег, времени и ресурсов.
    Предложить идею просто, воплотить – вот в чём проблема

    Steve Pavlina

  3. #3
    Аватар для DeimoS
    Модератор?

    Статус
    Оффлайн
    Регистрация
    27.01.2014
    Адрес
    Восточный Мордор
    Сообщений
    5,588
    Репутация:
    1984 ±

    *Продолжение*

    • Создание/загрузка аккаунта игрока

      Создание аккаунта:
      В самый конец мода вставим этот stock:
        Открыть/закрыть
       Для R39
      1. stock CreateNewAccount(playerid, password[])
      2. {
      3. new query_string[66+MAX_PLAYER_NAME-4+30+1];
      4. format(query_string, sizeof(query_string), "INSERT INTO `account` (`player_name`, `password`) VALUES ('%s', '%s')", pInfo[playerid][pName], password);
      5. mysql_function_query(mysql_connect_ID, query_string, true, "UploadPlayerAccountNumber", "i", playerid);
      6.  
      7.  
      8. format(query_string, sizeof(query_string), "Аккаунт %s успешно зарегистрирован. Администрация желает Вам приятной игры!", pInfo[playerid][pName]);
      9. SendClientMessage(playerid, 0xFFFFFF00, query_string);
      10. SpawnPlayer(playerid);
      11. return 1;
      12. }

       Разбор кода
      1. CreateNewAccount(playerid, password[])

      Собственно, сама функция с двумя параметрами: числовой, который хранит ID игрока и в виде массива, который хранит пароль.
      1. format(query_string, sizeof(query_string), "INSERT INTO `account` (`player_name`, `password`) VALUES ('%s', '%s')", pInfo[playerid][pName], password);

      Данным запросом мы обновляем данные в таблице на те, что игрок ввёл при регистрации. В данный запрос нужно добавлять обновление только тех данных, что можно заполучить только в момент регистрации (например, IP игрока). Имя и пароль мы сохраняем для того, чтоб при выходе с сервера мы могли сохранить остальные данные, опираясь на имя игрока (то есть, возраст, пол и прочее можно сохранить и при выходе, а не забивать этот запрос. Тут только важная информация, которую либо не достать в дальнейшем, либо без которой дальнейшая работа каких-либо систем невозможна).

      1. mysql_function_query(mysql_connect_ID, query_string, true, "UploadPlayerAccountNumber", "i", playerid);

      Эта функция, в целом, Вам известна по предыдущим моим объяснениям в данном уроке, но есть одно "но". Вообще, при отправке запроса "INSERT INTO", не нужно указывать ни кэширование запроса (третий параметр (true)), ни коллбэк, который будет являться кэшем. Но после создания аккаунта нам надо узнать номер строки (значение столбца ID), который занял этот аккаунт для того, чтоб потом мы могли сохранить все изменения при выходе игрока. Для этого можно использовать функцию "cache_insert_id", которая как раз и вернёт нужное нам значение (о ней я расскажу позже). Но чтоб она сработала, как раз и нужно, чтоб MySQL вернуло данные о запросе, поэтому я и дописал тут коллбэк.

      1. format(query_string, sizeof(query_string), "Аккаунт %s успешно зарегистрирован. Администрация желает Вам приятной игры!", pInfo[playerid][pName]);
      2. SendClientMessage(playerid, 0xFFFFFF00, query_string);

      Тут мы оповещаем игрока о успешной регистрации :)
      1. SpawnPlayer(playerid);

      Ну а этой функцией мы отправляем игрока на спавн. Она практически идентична (а, возможно, и полностью идентична) вызову коллбэка OnPlayerSpawn напрямую, но лучше использовать именно функцию.


      А вот и сам коллбэк, который вернёт ID аккаунта:
      1. forward UploadPlayerAccountNumber(playerid);
      2. public UploadPlayerAccountNumber(playerid) pInfo[playerid][pID] = cache_insert_id(mysql_connect_ID);


       Разбор кода
      Собственно, тут Вам должно быть известно всё, кроме самого главного - функции cache_insert_id.
      Эта функция имеет всего один необязательный параметр, в котором указывается ID подключения к БД.
      1. cache_insert_id(connectionHandle = 1);

      Эта функция возвращает значение столбца с параметром AUTO_INCREMENT, которое появилось после "INSERT" запроса.


       Для R40
      1. stock CreateNewAccount(playerid, password[])
      2. {
      3. new query_string[66+MAX_PLAYER_NAME-4+30+1];
      4. format(query_string, sizeof(query_string), "INSERT INTO `account` (`player_name`, `password`) VALUES ('%s', '%s')", pInfo[playerid][pName], password);
      5. mysql_tquery(mysql_connect_ID, query_string, "UploadPlayerAccountNumber", "i", playerid);
      6.  
      7.  
      8. format(query_string, sizeof(query_string), "Аккаунт %s успешно зарегистрирован. Администрация желает Вам приятной игры!", pInfo[playerid][pName]);
      9. SendClientMessage(playerid, 0xFFFFFF00, query_string);
      10. SpawnPlayer(playerid);
      11. return 1;
      12. }

       Разбор кода
      1. CreateNewAccount(playerid, password[])

      Собственно, сама функция с двумя параметрами: числовой, который хранит ID игрока и в виде массива, который хранит пароль.
      1. format(query_string, sizeof(query_string), "INSERT INTO `account` (`player_name`, `password`) VALUES ('%s', '%s')", pInfo[playerid][pName], password);

      Данным запросом мы обновляем данные в таблице на те, что игрок ввёл при регистрации. В данный запрос нужно добавлять обновление только тех данных, что можно заполучить только в момент регистрации (например, IP игрока). Имя и пароль мы сохраняем для того, чтоб при выходе с сервера мы могли сохранить остальные данные, опираясь на имя игрока (то есть, возраст, пол и прочее можно сохранить и при выходе, а не забивать этот запрос. Тут только важная информация, которую либо не достать в дальнейшем, либо без которой дальнейшая работа каких-либо систем невозможна).

      1. mysql_tquery(mysql_connect_ID, query_string, "UploadPlayerAccountNumber", "i", playerid);

      Эта функция, в целом, Вам известна по предыдущим моим объяснениям в данном уроке.

      1. format(query_string, sizeof(query_string), "Аккаунт %s успешно зарегистрирован. Администрация желает Вам приятной игры!", pInfo[playerid][pName]);
      2. SendClientMessage(playerid, 0xFFFFFF00, query_string);

      Тут мы оповещаем игрока о успешной регистрации :)
      1. SpawnPlayer(playerid);

      Ну а этой функцией мы отправляем игрока на спавн. Она практически идентична (а, возможно, и полностью идентична) вызову коллбэка OnPlayerSpawn напрямую, но лучше использовать именно функцию.


      А вот и сам коллбэк, который вернёт ID аккаунта:
      1. forward UploadPlayerAccountNumber(playerid);
      2. public UploadPlayerAccountNumber(playerid) pInfo[playerid][pID] = cache_insert_id();


       Разбор кода
      Собственно, тут Вам должно быть известно всё, кроме самого главного - функции cache_insert_id.
      Эта функция не имеет параметров.
      1. cache_insert_id();

      Эта функция возвращает значение столбца с параметром AUTO_INCREMENT, которое появилось после "INSERT" запроса.


      Загрузка аккаунта:
        Открыть/закрыть
       Для R39
      1. forward UploadPlayerAccount(playerid);
      2. public UploadPlayerAccount(playerid)
      3. {
      4. pInfo[playerid][pID] = cache_get_field_content_int(0, "id", mysql_connect_ID);
      5. SendClientMessage(playerid, 0xFFFFFF00, "Вы успешно авторизировались!");
      6. SpawnPlayer(playerid);
      7. return 1;
      8. }

       Разбор кода
      Пока что в нашей таблице всего 3 столбца: ID аккаунта, имя и пароль. Имя мы узнали в момент входа игрока на сервер, а пароль в момент авторизации, поэтому грузить повторно их бессмысленно. Осталось загрузить только ID аккаунта, который потом можно использовать в различных системах (например, в системе личного транспорта этот самый транспорт привязать к ID аккаунта игрока).
      1. pInfo[playerid][pID] = cache_get_field_content_int(0, "id", mysql_connect_ID);

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


      cache_get_field_content_int - Функция, которая работает с целочисленными данными.

      Параметры:
      1. cache_get_field_content_int(row, const field_name[], connectionHandle = 1);
      2. /*------------------------------------------------
      3. row - Номер строки в ВОЗВРАЩЁННЫХ данных, с которой будем работать (первая строка всегда равна нулю и так как в результате запроса строк не может быть больше одной, мы ставим "0")
      4. const field_name[] - Имя столбца, из которого и будем брать значение
      5. connectionHandle - ID базы данных*/


      Пример использования:
      1. new variable = cache_get_field_content_int(0, "level", dbHandle);
      2. printf("Уровень игрока равен %d", variable);



      cache_get_field_content - Функция, которая работает с строками.

      Параметры:
      1. cache_get_field_content(row, const field_name[], destination[], connectionHandle = 1, max_len=sizeof(destination));
      2. /*-----------------------------------------------------------------------------------------------------------------
      3. row - Номер строки в ВОЗВРАЩЁННЫХ данных, с которой будем работать (первая строка всегда равна нулю)
      4. const field_name[] - Имя столбца, из которого и будем брать значение
      5. destination[] - Массив, в который будет записан текст из столбца
      6. connectionHandle - ID базы данных
      7. max_len - Необязательный параметр. Изначально равен числу ячеек в массиве, который указали в "destination"*/


      Пример использования:
      1. new player_name[MAX_PLAYER_NAME];
      2. cache_get_field_content(0, "player_name", player_name, dbHandle);
      3. printf("Имя игрока - %s", player_name);

      Примечание: Если в данной функции указать двумерный массив, параметр "max_len" обязателен к заполнению, так как оператор "sizeof" не сможет вернуть размер двумерного массива.
      Пример:
      1. new player_name[MAX_PLAYERS][MAX_PLAYER_NAME];
      2. cache_get_field_content(0, "player_name", player_name[playerid], dbHandle, MAX_PLAYER_NAME);// В последнем параметре мы указали размер второго индекса массива "player_name"
      3. printf("Имя игрока - %s", player_name);



      cache_get_field_content_float - Функция, которая работает с вещественным типом данных.

      Параметры:
      1. cache_get_field_content_float(row, const field_name[], connectionHandle = 1);
      2. /*-----------------------------------------------------------------------------------------------------------------
      3. row - Номер строки в ВОЗВРАЩЁННЫХ данных, с которой будем работать (первая строка всегда равна нулю)
      4. const field_name[] - Имя столбца, из которого и будем брать значение
      5. connectionHandle - ID базы данных*/


      Пример использования:
      1. new Float: health = cache_get_field_content_float(0, "player_health", dbHandle);
      2. printf("У игрока %f жизней", health);



      Ну а предназначение этих строк
      1. SendClientMessage(playerid, 0xFFFFFF00, "Вы успешно авторизировались!");
      2. SpawnPlayer(playerid);

      Вам и так известно :)


       Для R40

      1. forward UploadPlayerAccount(playerid);
      2. public UploadPlayerAccount(playerid)
      3. {
      4. cache_get_value_name_int(0, "id", pInfo[playerid][pID]);
      5. SendClientMessage(playerid, 0xFFFFFF00, "Вы успешно авторизировались!");
      6. SpawnPlayer(playerid);
      7. return 1;
      8. }

       Разбор кода
      Пока что в нашей таблице всего 3 столбца: ID аккаунта, имя и пароль. Имя мы узнали в момент входа игрока на сервер, а пароль в момент авторизации, поэтому грузить повторно их бессмысленно. Осталось загрузить только ID аккаунта, который потом можно использовать в различных системах (например, в системе личного транспорта этот самый транспорт привязать к ID аккаунта игрока).
      1. cache_get_value_name_int(0, "id", pInfo[playerid][pID]);

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


      cache_get_value_name_int - Функция, которая работает с целочисленными данными.

      Параметры:
      1. cache_get_value_name_int(row_idx, const column_name[], &destination);
      2. /*------------------------------------------------
      3. row_idx - Номер строки в ВОЗВРАЩЁННЫХ данных, с которой будем работать (первая строка всегда равна нулю и так как в результате запроса строк не может быть больше одной, мы ставим "0")
      4. const column_name[] - Имя столбца, из которого и будем брать значение
      5. &destination - Переменная, в которую запишется возвращённый результат*/


      Пример использования:
      1. new variable;
      2. cache_get_value_name_int(0, "level", variable);
      3. printf("Уровень игрока равен %d", variable);



      cache_get_value_name - Функция, которая работает с строками.

      Параметры:
      1. cache_get_value_name(row_idx, const column_name[], destination[], max_len = sizeof(destination));
      2. /*-----------------------------------------------------------------------------------------------------------------
      3. row_idx - Номер строки в ВОЗВРАЩЁННЫХ данных, с которой будем работать (первая строка всегда равна нулю)
      4. const column_name[] - Имя столбца, из которого и будем брать значение
      5. destination[] - Массив, в который будет записан текст из столбца
      6. max_len - Необязательный параметр. Изначально равен числу ячеек в массиве, который указали в "destination"*/


      Пример использования:
      1. new player_name[MAX_PLAYER_NAME];
      2. cache_get_value_name(0, "player_name", player_name);
      3. printf("Имя игрока - %s", player_name);

      Примечание: Если в данной функции указать двумерный массив, параметр "max_len" обязателен к заполнению, так как оператор "sizeof" не сможет вернуть размер двумерного массива.
      Пример:
      1. new player_name[MAX_PLAYERS][MAX_PLAYER_NAME];
      2. cache_get_value_name(0, "player_name", player_name[playerid], MAX_PLAYER_NAME);// В последнем параметре мы указали размер второго индекса массива "player_name"
      3. printf("Имя игрока - %s", player_name);



      cache_get_field_content_float - Функция, которая работает с вещественным типом данных.

      Параметры:
      1. cache_get_value_name_float(row_idx, const column_name[], &Float:destination);
      2. /*-----------------------------------------------------------------------------------------------------------------
      3. row_idx - Номер строки в ВОЗВРАЩЁННЫХ данных, с которой будем работать (первая строка всегда равна нулю)
      4. const column_name[] - Имя столбца, из которого и будем брать значение
      5. &Float:destination - Переменная, в которую запишется возвращённый результат*/


      Пример использования:
      1. new Float: health;
      2. cache_get_value_name_float(0, "player_health", health);
      3. printf("У игрока %f жизней", health);



      Ну а предназначение этих строк
      1. SendClientMessage(playerid, 0xFFFFFF00, "Вы успешно авторизировались!");
      2. SpawnPlayer(playerid);

      Вам и так известно :)


       Скриншот







    • Сохранение аккаунта

      Так же, в самом конце, добавим новый stock
        Открыть/закрыть
       Для R39
      1. stock SaveAccount(playerid)
      2. {
      3. new query_string[(21)+(16+11)+(20+MAX_PLAYER_NAME)+(16+30)] = "UPDATE `account` SET";
      4.  
      5. format(query_string, sizeof(query_string), "%s `player_name` = '%s',", query_string, pInfo[playerid][pName]);
      6. format(query_string, sizeof(query_string), "%s `password` = '%s'", query_string, pInfo[playerid][pPassword]);
      7.  
      8. format(query_string, sizeof(query_string), "%s WHERE `id` = '%d'", query_string, pInfo[playerid][pID]);
      9. mysql_function_query(mysql_connect_ID, query_string, false, "", "");
      10. return 1;
      11. }


       Для R40
      1. stock SaveAccount(playerid)
      2. {
      3. new query_string[(21)+(16+11)+(20+MAX_PLAYER_NAME)+(16+30)] = "UPDATE `account` SET";
      4.  
      5. format(query_string, sizeof(query_string), "%s `player_name` = '%s',", query_string, pInfo[playerid][pName]);
      6. format(query_string, sizeof(query_string), "%s `password` = '%s'", query_string, pInfo[playerid][pPassword]);
      7.  
      8. format(query_string, sizeof(query_string), "%s WHERE `id` = '%d'", query_string, pInfo[playerid][pID]);
      9. mysql_tquery(mysql_connect_ID, query_string, "", "");
      10. return 1;
      11. }


       Немного моего мнения
      Так как это всего лишь урок и тут мы делаем сохранение всего 3-х столбцов, сохранять, кроме имени и пароля, больше нечего. Но вообще, как я считаю, сохранять имя и пароль при выходе бессмысленно, ибо что первое, что второе не может быть никак изменено игроком без нашего ведома (только мы можем позволить игроку сменить ник, написав для этого функцию). Поэтому и сохранять эти данные следует прямо во время изменения, а не при каждом выходе. При выходе можно сохранять такую вещь, как координаты или здоровье. Ну, на крайний случай, ещё деньги, если Вам не по силам написать античит, при этом сделав сохранение денег при каждом их изменении так, чтоб сервер при этом не закидал MySQL запросами. Практически всё остальноё можно и нужно сохранять прямиком при внесении изменений в информацию (уровень/ID дома, которым игрок владеет/ID фракции и прочая информация, которая изменяется не так часто и изменение которой можно 100% отследить). Но это лишь моё мнение и Вы можете к нему не прислушиваться. От того, что Вы будете сохранять всё при выходе, хуже от этого не станет.


        Открыть/закрыть
       Для R39
       Разбор кода
      1. new query_string[(21)+(16+11)+(20+MAX_PLAYER_NAME)+(16+30)] = "UPDATE `account` SET";

      Объявление массива, в котором будет хранится запрос. Для удобства каждая часть запроса, помещённая в отдельный format, выделена скобками. Первое число в скобках означает число ячеек, которое нужно для хранения текста до форматирования (то есть, до вставки в него ника/пароля и прочих данных), а второе - число ячеек, которое требуется для хранения вставляемых данных. "+1" в конце - это ячейка для нуль-символа. Таким образом, работа с массивом делается максимально удобной для редактирования.
       Текущее сопоставление данных в ячейке с строками
      1. stock SaveAccount(playerid)
      2. {
      3. new query_string[(21)+(16+11)+(20+MAX_PLAYER_NAME)+(16+30)+1] = "UPDATE `account` SET";// "UPDATE `account` SET" - (21)
      4.  
      5. format(query_string, sizeof(query_string), "%s `player_name` = '%s',", query_string, pInfo[playerid][pName]);// (20+MAX_PLAYER_NAME)
      6. format(query_string, sizeof(query_string), "%s `password` = '%s'", query_string, pInfo[playerid][pPassword]);// (16+30)
      7.  
      8. format(query_string, sizeof(query_string), "%s WHERE `id` = '%d'", query_string, pInfo[playerid][pID]);// (16+11)
      9. mysql_function_query(mysql_connect_ID, query_string, false, "", "");
      10. return 1;
      11. }

       Как правильно высчитывать длину строки
      Один символ в строке равен одной ячейке. Заполнители: "%s, %d, %f и т.д." - не занимают ячеек, так как на их места появляются вставляемые данные => при счёте их не учитывать. При хранении строки стоит учитывать, что существует нуль-символ, который сигнализирует машине об окончании строки, поэтому к реальному размеру строки всегда прибавляем единицу. (НО: при вставке одной строки в другую, нуль-символ строки, которая идёт позади/внутри другой строки, исчезает, поэтому для вставляемой строки учитывается только реальный размер).

      Примеры:
       Нуль-символ
      Имеется строка - "Приветствую, игрок". Если посчитать число символов в ней (включая пробелы!), то всего их насчитаем ровно 18. Значит массив, в который мы будем записывать строку должен выглядеть так?
      1. new string[18];

      А вот и нет. Ведь есть ещё нуль-символ, для которого тоже нужно место. Если это место ему не предоставить, машина отбросит все символы, что не помещаются в 18 ячеек (если строка больше 18 ячеек) и ещё один, на место которого и поставит нуль-символ. Поэтому для хранения такой строки нам понадобится 19 ячеек и массив будет выглядеть так:
      1. new string[19];


       Заполнители
      Имеется строка - "Приветствую, %s", в которую мы хотим поместить имя игрока. Если посчитать число символов в ней (включая пробелы!), то всего их насчитаем ровно 15. Но не забываем, что заполнители веса не имеют, так как на их месте появляются вставляемые данные. Поэтому число ячеек равно не 15, а 13. Дабы ещё и удовлетворить потребности нуль-символа, выделим для него ещё одну ячейку и получим 14. Но и это ещё не всё. Раз мы решили вставлять ник игрока, то пора нам обратиться к официальной документации для SA-MP. Информацию можно поискать на официальном форуме или wiki-страничке, а можно открыть инклюды, что идут вместе с сервером и посмотреть там. В данном случае нам нужно узнать то, сколько ячеек в текущей версии мультиплеера, может занимать ник игрока. Что искать на форуме Вам, надеюсь, объяснять не нужно, а вот в инклюдах нужно искать всё то, что хоть как-то связано с именем игрока. Логично предположить, что макрос, содержащий информацию о максимальной длине ника, будет содержать слова "MAX" и "NAME", так что ищем среди макросов всё, что нам хоть как-то подходит. В данном случае нужный нам макрос хранится в инклюде "a_samp" и выглядит он так
      1. #define MAX_PLAYER_NAME (24)

      Скобки макросу нужны для максимальной совместимости с кодом, поэтому просто не обращаем на них внимания. Из макроса мы узнаём, что максимально возможная длина имени равна 24 символам. Так что к нашему числу ячеек (14) нужно прибавить 24 и именно это будет финальным размером нашего массива. В итоге получим следующее:
      1. new string[38];

      Таким образом мы выделили ровно столько ячеек, сколько может использоваться в случае, когда у игрока длина ника равна 24 символам и такой массив можно назвать оптимизированным.
       Другие варианты записи размера ячеек:

      Запись
      1. new string[38];

      Равносильна следующим записям:
        1. new string[14+MAX_PLAYER_NAME];
        1. new string[13+MAX_PLAYER_NAME+1];
        1. new string[14+24];
        1. new string[13+24+1];
        1. new string[(14)+(24)];
        1. new string[1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1];
      • И т.д.

      Все эти записи в итоге будут выглядеть как первая (new string[38];). Преобразование произойдёт в момент компиляции, так что о быстродействии заботиться не стоит :) Я лишь советую Вам использовать стандартные макросы, если они были даны разработчиками. Ибо тогда, если вдруг разработчики SA-MP, например, поменяют длину ника игрока, Вам будет достаточно перекомпилировать мод и жить дальше, а не уменьшать/увеличивать каждое значение вручную.


       Склейка строк
      Имеется три строки и три массива, которые хранят эти строки
      1. new string_1[7] = "Привет";
      2. new string_2[3] = ", ";//запятая + пробел + нуль-символ
      3. new string_3[6] = "игрок";

      Нам нужно все эти три строки склеить в одну. Воспользуемся функцией format. Выглядеть код будет так
      1. format(string_all, sizeof(string_all), "%s%s%s", string_1, string_2, string_3);

      Как правильно подсчитать размер для массива "string_all"? Просто сложить размер трёх ячеек и всё? Нет, не верно. Ведь при создании массивов для хранения строк по отдельности мы учитывали наличие нуль-символа, который сигнализирует о конце строки. А это значит, что при склейке эти значения нам совсем не нужны, ведь какой нуль-символ может быть в середине строки? Для верного подсчёта строки нам потребуется просто высчитать настоящий размер строк (или просто отнять от текущего кол-ва ячеек массивов единицу) и уже после этого прибавить к ним единицу. В итоге у нас выйдет такой массив:
      1. new string_all[14];



      После объявления массива мы сразу же запишем в него начало запроса, которое не требует вставки в себя каких-либо данных.


      1. format(query_string, sizeof(query_string), "%s `player_name` = '%s'", query_string, pInfo[playerid][pName]);
      2. format(query_string, sizeof(query_string), "%s `password` = '%s'", query_string, pInfo[playerid][pPassword]);

      Это сохранение имени игрока и пароля. "%s" в начале строки нужна для того, чтоб те данные, что уже имеются в query_string, поместились в начало строки и новые данные "приклеились" к ним. Вставка новых данных будет выглядеть так:
      1. format(query_string, sizeof(query_string), "%s `имя_столбца` = 'заполнитель'", query_string, /*переменная для вставки*/);

      Только не забываем подсчитать длину этой строки и добавить нужное число ячеек к массиву :) А так же запомните, что новая запись должна иметь в самом конце запятую, если она не стоит последней.
       Пример
      1. stock SaveAccount(playerid)
      2. {
      3. new query_string[(21)+(15+11)+(20+MAX_PLAYER_NAME)+(16+30)+(22+11)+(19+11)] = "UPDATE `account` SET";
      4.  
      5. format(query_string, sizeof(query_string), "%s `player_name` = '%s',", query_string, pInfo[playerid][pName]);//Запятая есть
      6. format(query_string, sizeof(query_string), "%s `password` = '%s',", query_string, pInfo[playerid][pPassword]);//Запятая есть
      7. format(query_string, sizeof(query_string), "%s `account_level` = '%d',", query_string, pInfo[playerid][pLevel]);//Запятая есть
      8. format(query_string, sizeof(query_string), "%s `admin_level` = '%d'", query_string, pInfo[playerid][pAdmin]);//Запятой нет
      9.  
      10. format(query_string, sizeof(query_string), "%s WHERE `id` = '%d'", query_string, pInfo[playerid][pID]);//И тут нет
      11. mysql_function_query(mysql_connect_ID, query_string, false, "", "");
      12. return 1;
      13. }


      В итоге выйдет следующий запрос:
      1. UPDATE `account` SET `player_name` = 'Имя_игрока', `password` = 'Пароль', `account_level` = 'Уровень игрока', `admin_level` = 'Уровень админки' WHERE `player_name` = 'Имя_игрока'

      То есть, запятые отделяют друг от друга данные для сохранения и MySQL таким образом определяет где заканчивается информация для одного столбца и начинается для следующего. Именно поэтому к предпоследней строке (в данном случае это сохранение админки) запятая не ставится (кто не понял: если поставить запятую, то MySQL будет искать после неё следующий блок данных, но найдёт лишь оператор "WHERE". Из-за этого случится ошибка и запрос не обработается)

      И да, это не самый оптимизированный вариант (постоянный вызов функции format, вместо одного), но использование подобного метода не навредит Вашему моду. Я использовал именно его потому, что он максимально удачен в плане "оптимизация/понятность/удобство" и для новичков это самый лучший вариант.


      1. format(query_string, sizeof(query_string), "%s WHERE `id` = '%d'", query_string, pInfo[playerid][pID]);

      Окончание нашего запроса, где мы указываем, что нужный аккаунт нужно искать по ID. Новые данные на сохранение нужно помещать между этой строкой и объявлением массива. Желательно в том порядке, в котором расположены столбцы в таблице (для Вашего же удобства).


      1. mysql_function_query(mysql_connect_ID, query_string, false, "", "");

      Отправляем полученный запрос. Мы обновляем данные в базе данных и никаких данных в ответ не ждём, поэтому и кэширование нам не нужно => и функцию мы никакую не вызываем.


       Для R40
       Разбор кода
      1. new query_string[(21)+(16+11)+(20+MAX_PLAYER_NAME)+(16+30)] = "UPDATE `account` SET";

      Объявление массива, в котором будет хранится запрос. Для удобства каждая часть запроса, помещённая в отдельный format, выделена скобками. Первое число в скобках означает число ячеек, которое нужно для хранения текста до форматирования (то есть, до вставки в него ника/пароля и прочих данных), а второе - число ячеек, которое требуется для хранения вставляемых данных. "+1" в конце - это ячейка для нуль-символа. Таким образом, работа с массивом делается максимально удобной для редактирования.
       Текущее сопоставление данных в ячейке с строками
      1. stock SaveAccount(playerid)
      2. {
      3. new query_string[(21)+(16+11)+(20+MAX_PLAYER_NAME)+(16+30)+1] = "UPDATE `account` SET";// "UPDATE `account` SET" - (21)
      4.  
      5. format(query_string, sizeof(query_string), "%s `player_name` = '%s',", query_string, pInfo[playerid][pName]);// (20+MAX_PLAYER_NAME)
      6. format(query_string, sizeof(query_string), "%s `password` = '%s'", query_string, pInfo[playerid][pPassword]);// (16+30)
      7.  
      8. format(query_string, sizeof(query_string), "%s WHERE `id` = '%d'", query_string, pInfo[playerid][pID]);// (16+11)
      9. mysql_tquery(mysql_connect_ID, query_string, "", "");
      10. return 1;
      11. }

       Как правильно высчитывать длину строки
      Один символ в строке равен одной ячейке. Заполнители: "%s, %d, %f и т.д." - не занимают ячеек, так как на их места появляются вставляемые данные => при счёте их не учитывать. При хранении строки стоит учитывать, что существует нуль-символ, который сигнализирует машине об окончании строки, поэтому к реальному размеру строки всегда прибавляем единицу. (НО: при вставке одной строки в другую, нуль-символ строки, которая идёт позади/внутри другой строки, исчезает, поэтому для вставляемой строки учитывается только реальный размер).

      Примеры:
       Нуль-символ
      Имеется строка - "Приветствую, игрок". Если посчитать число символов в ней (включая пробелы!), то всего их насчитаем ровно 18. Значит массив, в который мы будем записывать строку должен выглядеть так?
      1. new string[18];

      А вот и нет. Ведь есть ещё нуль-символ, для которого тоже нужно место. Если это место ему не предоставить, машина отбросит все символы, что не помещаются в 18 ячеек (если строка больше 18 ячеек) и ещё один, на место которого и поставит нуль-символ. Поэтому для хранения такой строки нам понадобится 19 ячеек и массив будет выглядеть так:
      1. new string[19];


       Заполнители
      Имеется строка - "Приветствую, %s", в которую мы хотим поместить имя игрока. Если посчитать число символов в ней (включая пробелы!), то всего их насчитаем ровно 15. Но не забываем, что заполнители веса не имеют, так как на их месте появляются вставляемые данные. Поэтому число ячеек равно не 15, а 13. Дабы ещё и удовлетворить потребности нуль-символа, выделим для него ещё одну ячейку и получим 14. Но и это ещё не всё. Раз мы решили вставлять ник игрока, то пора нам обратиться к официальной документации для SA-MP. Информацию можно поискать на официальном форуме или wiki-страничке, а можно открыть инклюды, что идут вместе с сервером и посмотреть там. В данном случае нам нужно узнать то, сколько ячеек в текущей версии мультиплеера, может занимать ник игрока. Что искать на форуме Вам, надеюсь, объяснять не нужно, а вот в инклюдах нужно искать всё то, что хоть как-то связано с именем игрока. Логично предположить, что макрос, содержащий информацию о максимальной длине ника, будет содержать слова "MAX" и "NAME", так что ищем среди макросов всё, что нам хоть как-то подходит. В данном случае нужный нам макрос хранится в инклюде "a_samp" и выглядит он так
      1. #define MAX_PLAYER_NAME (24)

      Скобки макросу нужны для максимальной совместимости с кодом, поэтому просто не обращаем на них внимания. Из макроса мы узнаём, что максимально возможная длина имени равна 24 символам. Так что к нашему числу ячеек (14) нужно прибавить 24 и именно это будет финальным размером нашего массива. В итоге получим следующее:
      1. new string[38];

      Таким образом мы выделили ровно столько ячеек, сколько может использоваться в случае, когда у игрока длина ника равна 24 символам и такой массив можно назвать оптимизированным.
       Другие варианты записи размера ячеек:

      Запись
      1. new string[38];

      Равносильна следующим записям:
        1. new string[14+MAX_PLAYER_NAME];
        1. new string[13+MAX_PLAYER_NAME+1];
        1. new string[14+24];
        1. new string[13+24+1];
        1. new string[(14)+(24)];
        1. new string[1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1];
      • И т.д.

      Все эти записи в итоге будут выглядеть как первая (new string[38];). Преобразование произойдёт в момент компиляции, так что о быстродействии заботиться не стоит :) Я лишь советую Вам использовать стандартные макросы, если они были даны разработчиками. Ибо тогда, если вдруг разработчики SA-MP, например, поменяют длину ника игрока, Вам будет достаточно перекомпилировать мод и жить дальше, а не уменьшать/увеличивать каждое значение вручную.


       Склейка строк
      Имеется три строки и три массива, которые хранят эти строки
      1. new string_1[7] = "Привет";
      2. new string_2[3] = ", ";//запятая + пробел + нуль-символ
      3. new string_3[6] = "игрок";

      Нам нужно все эти три строки склеить в одну. Воспользуемся функцией format. Выглядеть код будет так
      1. format(string_all, sizeof(string_all), "%s%s%s", string_1, string_2, string_3);

      Как правильно подсчитать размер для массива "string_all"? Просто сложить размер трёх ячеек и всё? Нет, не верно. Ведь при создании массивов для хранения строк по отдельности мы учитывали наличие нуль-символа, который сигнализирует о конце строки. А это значит, что при склейке эти значения нам совсем не нужны, ведь какой нуль-символ может быть в середине строки? Для верного подсчёта строки нам потребуется просто высчитать настоящий размер строк (или просто отнять от текущего кол-ва ячеек массивов единицу) и уже после этого прибавить к ним единицу. В итоге у нас выйдет такой массив:
      1. new string_all[14];



      После объявления массива мы сразу же запишем в него начало запроса, которое не требует вставки в себя каких-либо данных.


      1. format(query_string, sizeof(query_string), "%s `player_name` = '%s'", query_string, pInfo[playerid][pName]);
      2. format(query_string, sizeof(query_string), "%s `password` = '%s'", query_string, pInfo[playerid][pPassword]);

      Это сохранение имени игрока и пароля. "%s" в начале строки нужна для того, чтоб те данные, что уже имеются в query_string, поместились в начало строки и новые данные "приклеились" к ним. Вставка новых данных будет выглядеть так:
      1. format(query_string, sizeof(query_string), "%s `имя_столбца` = 'заполнитель'", query_string, /*переменная для вставки*/);

      Только не забываем подсчитать длину этой строки и добавить нужное число ячеек к массиву :) А так же запомните, что новая запись должна иметь в самом конце запятую, если она не стоит последней.
       Пример
      1. stock SaveAccount(playerid)
      2. {
      3. new query_string[(21)+(15+11)+(20+MAX_PLAYER_NAME)+(16+30)+(22+11)+(19+11)] = "UPDATE `account` SET";
      4.  
      5. format(query_string, sizeof(query_string), "%s `player_name` = '%s',", query_string, pInfo[playerid][pName]);//Запятая есть
      6. format(query_string, sizeof(query_string), "%s `password` = '%s',", query_string, pInfo[playerid][pPassword]);//Запятая есть
      7. format(query_string, sizeof(query_string), "%s `account_level` = '%d',", query_string, pInfo[playerid][pLevel]);//Запятая есть
      8. format(query_string, sizeof(query_string), "%s `admin_level` = '%d'", query_string, pInfo[playerid][pAdmin]);//Запятой нет
      9.  
      10. format(query_string, sizeof(query_string), "%s WHERE `id` = '%d'", query_string, pInfo[playerid][pID]);//И тут нет
      11. mysql_tquery(mysql_connect_ID, query_string, "", "");
      12. return 1;
      13. }


      В итоге выйдет следующий запрос:
      1. UPDATE `account` SET `player_name` = 'Имя_игрока', `password` = 'Пароль', `account_level` = 'Уровень игрока', `admin_level` = 'Уровень админки' WHERE `player_name` = 'Имя_игрока'

      То есть, запятые отделяют друг от друга данные для сохранения и MySQL таким образом определяет где заканчивается информация для одного столбца и начинается для следующего. Именно поэтому к предпоследней строке (в данном случае это сохранение админки) запятая не ставится (кто не понял: если поставить запятую, то MySQL будет искать после неё следующий блок данных, но найдёт лишь оператор "WHERE". Из-за этого случится ошибка и запрос не обработается)

      И да, это не самый оптимизированный вариант (постоянный вызов функции format, вместо одного), но использование подобного метода не навредит Вашему моду. Я использовал именно его потому, что он максимально удачен в плане "оптимизация/понятность/удобство" и для новичков это самый лучший вариант.


      1. format(query_string, sizeof(query_string), "%s WHERE `id` = '%d'", query_string, pInfo[playerid][pID]);

      Окончание нашего запроса, где мы указываем, что нужный аккаунт нужно искать по ID. Новые данные на сохранение нужно помещать между этой строкой и объявлением массива. Желательно в том порядке, в котором расположены столбцы в таблице (для Вашего же удобства).


      1. mysql_tquery(mysql_connect_ID, query_string, "", "");

      Отправляем полученный запрос. Мы обновляем данные в базе данных и никаких данных в ответ не ждём, поэтому и кэширование нам не нужно => и функцию мы никакую не вызываем.

      И в OnPlayerDisconnect
      1. SaveAccount(playerid);





    • Отключение от базы данных

      Так же нужно не забыть добавить запрос к плагину MySQL на отключение от нашей базы данных. Делается это просто.
      В OnGameModeExit
      1. mysql_close(mysql_connect_ID);





    • Обнуление массива с данными
      Это действие требуется для того, чтоб данные одного игрока не перемешались с данными другого в случае, если один игрок вышел и второй зашёл на тот же слот.

      Делается это просто. Сначала в OnPlayerDisconnect, после "SaveAccount(playerid);" добавим:
      1. RemovePlayerInfo(playerid);


      А после в конец скрипта:
      1. stock RemovePlayerInfo(playerid)
      2. {
      3. pInfo[playerid][pID] = 0;
      4. pInfo[playerid][pName][0] = EOS;
      5. pInfo[playerid][pPassword][0] = EOS;
      6. return 1;
      7. }


       Разбор кода
      1. pInfo[playerid][pName][0] = EOS;

      Член pName в перечислении e_PLAYER_INFO является массивом (в нём мы храним текст). А так, как сервер обрабатывает строки до первого нуль-символа (о них я рассказывал выше), для обнуления такого массива достаточно самую первую ячейку приравнять к этому самому нуль-символу и тогда сервер посчитает, что строка пустая.
      Но что же такое "EOS"? "EOS", по-сути, в Pawn равнозначна нулю. То есть, мы спокойно могли записать
      1. pInfo[playerid][pName][0] = 0;
      2. /*Не путать с
      3. pInfo[playerid][pName][0] = '0';
      4. или
      5. pInfo[playerid][pName][0] = "0";
      6. В этих случаях мы приравниваем первую ячейку не к нулю, а к тому набору нулей и единиц (или же числу, если брать не двоичную систему счисления), которому нуль равен в кодировке "utf8_general_ci". А нам нужен именно нуль как целое число. Только тогда сервер поймёт, что массив пуст.*/

      и всё бы работало как надо. Так же эта запись идентична записи
      1. pInfo[playerid][pName][0] = '\0';

      где "\0" обозначает нуль-символ.

      Поэтому если Вам нужно обновить строки, обновляйте их правильно. Переменные с вещественным типом данных следует обнулять как "0.0", ибо "девятый пункт рекомендаций"





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

      В начало мода, к переменным, добавим следующий массив:
      1. new player_is_authorized[MAX_PLAYERS char];

       Почему именно массив, а не pVar
      Причина одна - данные будут хранится и использоваться на протяжении всего времени, что игрок находится на сервере.
      Те же, кто использует pVar в подобных случаях, не очень понимает их предназначения. А логичнее всего их использовать для хранения временной информации, и вот почему:
      1) Многие считают, что при удалении pVar, память на сервере освобождается. Отнюдь, это не так. Скажу даже больше, если Вы не используете pVar, на сервере всё равно выделяется память под них. Ведь принцип работы pVar схож с принципами работы оперативной памяти. То есть, при запуске сервера резервируется определённое количество памяти. Теперь, когда вы создаёте pVar, часть этой памяти отдаётся под хранение записанных туда данных. Вот Вы попользовались этими данными и удалили pVar. Память, что требовалась для хранения Ваших данных, не стала вдруг свободной для всего. Она освободилась исключительно для нового pVar. Вы не сможете отдать участок памяти, где хранилась информация о pVar, под массив или что-то ещё. Нет. Она предназначена исключительно для pVar. Поэтому надеяться на какую-то оптимизацию в этом случае не стоит.

      2) Исходя из первого пункта следует, что глупо в pVar хранить информацию, которая будет востребована на протяжении всего нахождения игрока на сервере. Вот для временной информации pVar прекрасно подойдёт! Допустим, Вам нужно сохранить число, которое игрок ввёл как параметр для команды и вы сохранили его в pVar. Этот pVar занял свою ячейку в памяти (пусть это будет ячейка под номером 1). Далее Вы сохранили в pVar деньги игрока, дабы игрок смог через пол часа посмотреть то, насколько он смог разбогатеть (это уже будет pVar, который поместится в ячейку 2). Теперь Вы совершили все манипуляции с числом, что записывали в первый pVar и удалили его с помощью специальной функции DeletePVar. Пол часа ещё не прошло и второй pVar не задействован, поэтому удалять его мы не будем. Теперь нам понадобилось сохранить текущий уровень здоровья игрока. Мы создаём новый pVar и, по-идее, он должен занять ячейку под номером 3, ведь это совершенно новый pVar, который мы ещё не использовали ранее. Но нет, он займёт ячейку под номером 1, ведь до этого Вы удалили из неё pVar с числом.
      "Так всё равно же выделяется память, как и в случае с массивами!" - скажете Вы. Но вот Вам простой пример
       Пример

      1. /* Ситуация:
      2. Нам нужно временно хранить данные для дальнейшего использования. Всего пусть будет 2 разных случая.*/
      3. //Вариант решения №1
      4. new array_1[MAX_PLAYERS];
      5. new array_2[MAX_PLAYERS];
      6. //...
      7. array_2[playerid] = 1;
      8. if(array_2[playerid] == 1)...
      9. //...
      10. array_1[playerid] = 98;
      11. if(array_1[playerid] == 65)...
      12.  
      13. //Вариант решения №2
      14. new array_1[MAX_PLAYERS];
      15. //...
      16. array_1[playerid] = 1;
      17. if(array_1[playerid] == 1)...
      18. //...
      19. array_1[playerid] = 98;
      20. if(array_1[playerid] == 65)...
      21.  
      22. //Вариант решения №3
      23. SetPVarInt(playerid, "Array_1", 1);
      24. if(GetPVarInt(playerid, "Array_1") == 1) DeletePVar(playerid, "Array_1");
      25. //...
      26. SetPVarInt(playerid, "Array_2", 98);
      27. if(GetPVarInt(playerid, "Array_2") == 65) DeletePVar(playerid, "Array_2");

      В первом случае мы создаём 2 массива по 500 ячеек и они хранятся в памяти сервера всегда.
      Во втором случае мы создаём один массив на 500 ячеек и уже с ним работаем всё время. Это уже более похоже на pVar, но есть одно НО. Мы не сможем хранить в массиве одновременно несколько данных.
      В третьем случае мы используем pVar, где каждый pVar имеет своё уникальное имя и использует ту память, что уже и так была зарезервирована сервером. Если бы мы имели возможность сохранять в одном массиве несколько данных одновременно, то это было бы похоже именно на второй вариант.
      В общем, pVar хороши там, где данные не будут использоваться при каждом написании в чат или использовании команды (как это будет в случае с проверкой на авторизацию). Поэтому использовать их надо с умом.
       Постскриптум
      pVar можно представить следующим образом:
      1. new player_variables_int[MAX_PLAYERS][30];
      2. new player_variables_string[MAX_PLAYERS][30][500];
      3. new Float: player_variables_float[MAX_PLAYERS][30];

      И уже имена pVar давать в виде макросов. То есть:
      1. #define player_health 0
      2. player_variables_float[playerid][player_health] = 100.0;

      Только вся разница в том, что в случае с pVar машина сама подсчитает для Вас то число ячеек, которое потребуется для хранения данных и выделить нужное количество данных из зарезервированного участка памяти, а не Вы это будете делать. Ну и зачем выделять столько памяти, когда это уже давно сделали за Вас? :)
      Надеюсь теперь люди осознают всю прелесть pVar и перестанут использовать их где ни попадя.


      А далее есть два варианта: либо использовать массив напрямую, либо сделать макрос-функцию, которую уже и использовать дальше. Я опишу оба варианта, а дальше выбирать только Вам :)
       Вариант первый: Использование макроса
      В начало мода, после перечислений, добавляем следующее:
      1. //Макросы-функции
      2. #define IsPlayerAuthorized(%0) player_is_authorized{%0}
      3. #define SetPlayerAuthorized(%0,%1) player_is_authorized{%0} = %1
      4.  
      5. //Макросы для удобства
      6. #define PLAYER_OFFLINE 0
      7. #define PLAYER_ONLINE 1


      Теперь в UploadPlayerAccount/CreateNewAccount, перед SpawnPlayer, добавить
      1. SetPlayerAuthorized(playerid, PLAYER_ONLINE);


      Всё. Теперь осталось добавить проверки.
      В OnPlayerText добавляем следующее:
      1. if(!IsPlayerAuthorized(playerid))
      2. {
      3. SendClientMessage(playerid, -1, "Вы не авторизировались и не можете писать в чат!");
      4. return 0;
      5. }

       или же...
      1. if(IsPlayerAuthorized(playerid) == PLAYER_OFFLINE)
      2. {
      3. SendClientMessage(playerid, -1, "Вы не авторизировались и не можете писать в чат!");
      4. return 0;
      5. }


      В начало OnPlayerCommandText
      1. if(!IsPlayerAuthorized(playerid)) return SendClientMessage(playerid, -1, "Вы не авторизировались и не можете использовать команды!");

       или же...
      1. if(IsPlayerAuthorized(playerid) == PLAYER_OFFLINE) return SendClientMessage(playerid, -1, "Вы не авторизировались и не можете использовать команды!");


      Всё :) Главное помните, что данные проверки должны находится всегда в самом начале коллбэков/функций. Иначе код, что стоит выше них, успеет выполнится для неавторизированного игрока

       Вариант второй: Прямое использование

      Теперь в UploadPlayerAccount/CreateNewAccount, перед SpawnPlayer, добавить
      1. player_is_authorized{playerid} = 1;


      Всё. Теперь осталось добавить проверки.
      В OnPlayerText добавляем следующее:
      1. if(!player_is_authorized{playerid})
      2. {
      3. SendClientMessage(playerid, -1, "Вы не авторизировались и не можете писать в чат!");
      4. return 0;
      5. }

       или же...
      1. if(player_is_authorized{playerid} == 0)
      2. {
      3. SendClientMessage(playerid, -1, "Вы не авторизировались и не можете писать в чат!");
      4. return 0;
      5. }


      В начало OnPlayerCommandText
      1. if(!player_is_authorized{playerid}) return SendClientMessage(playerid, -1, "Вы не авторизировались и не можете использовать команды!");

       или же...
      1. if(player_is_authorized{playerid} == 0) return SendClientMessage(playerid, -1, "Вы не авторизировались и не можете использовать команды!");


      Всё :) Главное помните, что данные проверки должны находится всегда в самом начале коллбэков/функций. Иначе код, что стоит выше них, успеет выполнится для неавторизированного игрока. И ID переменная, хранящая ID Игрока, должна находится не в квадратных скобках, а в фигурных.


    Если есть какие-либо вопросы, если что-то непонятно объяснено или есть какие-либо дополнения/исправления для данного урока, прошу написать об этом ниже. Всем постараюсь помочь, все мнения приму к сведению.
    С вами был DeimoS. Спасибо за внимание
    Автор урока - DeimoS
    Копирование данного материала запрещено без разрешения автора




    UPD: Исправление ошибок "Запуск программы невозможен, так как на компьютере отсутствует MSVCP***.dll. Попробуйте переустановить программу"

    Цитата Сообщение от Nurick Посмотреть сообщение
    Сообщение об ошибке Требуемый компонент
    MSVCP120.dll Microsoft Visual C++ 2013
    MSVCP110.dll Microsoft Visual C++ 2012
    MSVCP100.dll Microsoft Visual C++ 2010
    MSVCP90.dll Microsoft Visual C++ 2008
    MSVCP80.dll Microsoft Visual C++ 2005

    Microsoft Visual C++ 2013 Redistributable Package
    32 - bit
    64 - bit
    Microsoft Visual C++ 2012 Redistributable Package
    32 - bit
    64 - bit
    Microsoft Visual C++ 2010 Redistributable Package
    32 - bit
    64 - bit
    Microsoft Visual C++ 2008 Redistributable Package
    32 - bit
    64 - bit
    Microsoft Visual C++ 2005 Redistributable Package
    32 - bit
    64 - bit
    Цитата Сообщение от Osetin Посмотреть сообщение
    Готовый код регистрации (R39) на Pastebin: http://pastebin.com/QiymnrPK
    Готовый код регистрации (R40) на Pastebin: http://pastebin.com/BFDpSDtx



    Интересные статьи на подобную тему:
    * [Урок] О плагине MySQL R40 от pBlueG (описание нововведений/отличия от R39 и т.п.)
    Последний раз редактировалось DeimoS; 08.08.2022 в 16:04.

  4. 43 пользователя(ей) сказали cпасибо:
    #ball (15.06.2015) 1136_man (27.07.2015) ArtZet (16.08.2016) Astrakhan30 (05.06.2016) Aurelius (10.01.2020) BadPawn (29.02.2016) Blood (23.02.2016) Camelot (29.06.2015) Daniel_Cortez (11.02.2016) Danny_Marcelo (03.11.2016) Desulaid (26.05.2015) Dr_Watson (14.02.2016) Gabriel (12.05.2015) Geebrox (12.10.2016) Guldan (25.04.2017) HarrWe (26.07.2016) Ildars (13.10.2016) karevkhvdrift (09.09.2016) Kovshevoy (08.12.2017) KrutoyKrosch (23.12.2016) kushichka (09.07.2016) L0ndl3m (27.10.2016) Long- (30.12.2016) m1n1vv (13.10.2015) McLuhan (04.02.2016) MrJu[N]ior (09.03.2016) Mr_LemoneZ (30.12.2016) Nurick (05.08.2015) Osetin (24.10.2016) Outsider (21.12.2017) paik (24.07.2016) Processing (24.01.2016) Profyan (09.09.2016) Quman (08.08.2015) Redsan (08.06.2016) seriu (12.02.2016) TimoXa32 (21.12.2017) vovandolg (23.10.2016) wAx (12.02.2016) Web (08.04.2017) [ForD] (24.01.2016) Иван Бубнов (26.12.2015) Тузик (08.09.2016)
  5. #4
    Аватар для Stats
    Пользователь

    Статус
    Оффлайн
    Регистрация
    01.09.2014
    Сообщений
    16
    Репутация:
    0 ±
    Прочитал чуть меньше половины, просто нет слов. Никогда не видел чтоб так подробно все описывали. Большое спасибо. Пошел читать далее и разбираться.

  6. #5
    Аватар для Nurick
    Пользователь

    Статус
    Оффлайн
    Регистрация
    19.04.2014
    Адрес
    Уфа, Россия
    Сообщений
    319
    Репутация:
    52 ±
    Правда устал читать, но это этого стоило! Много чего узнал, спасибо вам.

  7. #6
    Аватар для =SAN=
    Пользователь

    Статус
    Оффлайн
    Регистрация
    20.01.2014
    Адрес
    Saint-Petersburg
    Сообщений
    88
    Репутация:
    33 ±
    Отличный мануал,все описано подробно :)

  8. #7
    Аватар для Skyline
    Пользователь

    Статус
    Оффлайн
    Регистрация
    31.01.2014
    Адрес
    Россия
    Сообщений
    304
    Репутация:
    2 ±
    При запуске мода, появляется вот такая ошибка. Все попробовал сделать, ничего не выходит. Ни установка, ни прописка в команде пуска.
    Подскажите что делать. Ошибка - Скриншот

  9. #8
    Аватар для DeimoS
    Модератор?

    Статус
    Оффлайн
    Регистрация
    27.01.2014
    Адрес
    Восточный Мордор
    Сообщений
    5,588
    Репутация:
    1984 ±
    Цитата Сообщение от Skyline Посмотреть сообщение
    При запуске мода, появляется вот такая ошибка. Все попробовал сделать, ничего не выходит. Ни установка, ни прописка в команде пуска.
    Подскажите что делать. Ошибка - Скриншот
    У Вас не установлен распространяемый пакет Microsoft Visual C++. Ставить советую не самую последнюю, а начиная с 2005, ибо последующая версия не содержит компоненты предыдущей, а данный пакет может пригодится не только для работы MySQL.
    P.S. Если сейчас у Вас на компьютере установлена какая-то из версий сего пакета, удалите её и установите вручную последовательно каждую версию.
    И да, не выделяйте текст сообщения чёрным вручную. На форуме существует не только светный стиль, но и тёмный. И в нём текст Ваших сообщений читается очень плохо
    Связаться со мной в VK можно через личные сообщения этой группы
    Заказы не принимаю

    Широко известно, что идеи стоят 0.8333 цента каждая (исходя из рыночной цены 10 центов за дюжину).
    Великих идей полно, на них нет спроса.
    Воплощение идеи в законченную игру требует долгой работы,
    таланта, терпения и креативности, не говоря уж о затратах денег, времени и ресурсов.
    Предложить идею просто, воплотить – вот в чём проблема

    Steve Pavlina

  10. #9
    Аватар для Nurick
    Пользователь

    Статус
    Оффлайн
    Регистрация
    19.04.2014
    Адрес
    Уфа, Россия
    Сообщений
    319
    Репутация:
    52 ±
    Сообщение об ошибке Требуемый компонент
    MSVCP120.dll Microsoft Visual C++ 2013
    MSVCP110.dll Microsoft Visual C++ 2012
    MSVCP100.dll Microsoft Visual C++ 2010
    MSVCP90.dll Microsoft Visual C++ 2008
    MSVCP80.dll Microsoft Visual C++ 2005

    Microsoft Visual C++ 2013 Redistributable Package
    32 - bit
    64 - bit
    Microsoft Visual C++ 2012 Redistributable Package
    32 - bit
    64 - bit
    Microsoft Visual C++ 2010 Redistributable Package
    32 - bit
    64 - bit
    Microsoft Visual C++ 2008 Redistributable Package
    32 - bit
    64 - bit
    Microsoft Visual C++ 2005 Redistributable Package
    32 - bit
    64 - bit
    Последний раз редактировалось Nurick; 12.05.2016 в 10:17.

  11. #10
    Аватар для $continue$
    Пользователь

    Статус
    Оффлайн
    Регистрация
    02.08.2014
    Адрес
    г. Киров (aka Вятка)
    Сообщений
    1,487
    Репутация:
    276 ±
    Все офигенно расписано! Спасибо, много нового узнал.

 

 
Страница 1 из 31 1 2 3 11 ... ПоследняяПоследняя

Информация о теме

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

Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •