• Из-за обновления GTA 5 (был добавлен новый патч) может временно не работать вход в RAGE Multiplayer.

    ERROR: Your game version is not supported by RAGE Multiplayer.

    Данная ошибка говорит о том, что GTA V обновилась до новой версии (GTA Online тоже). Вам необходимо обновить саму игру в главном меню вашего приложения (Steam / Epic Games / Rockstar Games).
    Если после этого RAGE:MP все равно не работает - вам нужно дождаться выхода патча для самого мультиплеера (обычно это занимает от нескольких часов до нескольких дней).

    Новости и апдейты Rockstar Games - https://www.rockstargames.com/ru/newswire/
    Статус всех служб для Rockstar Games Launcher и поддерживаемых игр: https://support.rockstargames.com/ru/servicestatus


    Grand Theft Auto 5 (+ GTA Online) последний раз были обновлены:

Урок Создаем систему регистрации и авторизации на MongoDB для сервера RAGE:MP

Harland David Sanders

Куратор портала
Автор темы
Команда форума
Куратор портала
VIP
high coder
media
10 Сен 2020
3,060
2,451
219
Создание формы регистрации и авторизации пользователя с CEF, используя базу данных MongoDB.

Решил немножко продвинуть тему создания каких-то проектов для Rage:MP на MongoDB.
Эта статья является текстовой документацией к видео: (обрабатывается YouTube).
Напомню, что код является шаблоном для ваших фантазий. Вы можете дорабатывать как его хотите и делиться этим.
Я в свою очередь, при хорошем активе по теме MongoDB, буду поддерживать эту тему добавляя новые функции и знакомя вас ближе с этой базой данных.

index.html: https://sourceb.in/hakBVeoBGV
style.css: https://sourceb.in/FGCSnL6rvD


1) Вызов окна браузера при входе в игру:
(Создаем папку и в ней создаем файл index.js)

В первую очередь, когда игрок только подключается к нашему серверу, ему должно высветиться окошко с регистрацией, либо авторизацией. Вызов этого окна должен происходить с серверной части. Для этого используем встроенное событие "playerJoin", срабатывающее в момент появление на сервере нового игрока. Через него вызываем на клиентскую часть, используя player.call, пользовательское событие "showBrowser". Оно будет отображать нам то самое окно.

JavaScript:
mp.events.add('playerJoin', async(player) => {
    player.call('showBrowser')
})

2) Инициализация окна браузера на клиенте:
(Для клиентской стороны, создаем папку login, в нее заносим файл index.js)
(В главном файле клиента, не забудьте подключить эту папку!)

Вот мы передали ивент на клиент. Теперь давайте рассмотрим, что нам необходимо для того чтобы установить камеру в определенном месте. Для начала мы объявим некоторые переменные. Комментарий с их пояснением прикреплен.

JavaScript:
let vector = { x: 212.9087371826172, y: -1397.1019287109375, z: 2700.027587890625} //Вектор камеры
let loginCamera = mp.cameras.new('start', vector, new mp.Vector3(-20,0,0), 40); //Cама камера и начало ее работы.
let loginScreen //Переменная окна браузера
let clientPlayer = mp.players.local //Переменная игрока(может не пригодится, но мало ли :D )

Ну вот, основные переменные для работы с клиентом мы создали, можно приступать к нашим ивентам! Первое событие, которое нам пригодится - showBrowser, и сразу разберем событие, которое будет отвечать за то, чтобы этот браузер скрыть hideBrowser. Второе будет вызываться в случае, если игрок удачно прошел авторизацию/регистрацию на нашем проекте.

JavaScript:
mp.events.add({
    'showBrowser': () => {
        loginCamera.setActive(true);//Устанавливаем камеру
        mp.game.cam.renderScriptCams(true, false, 0, true, false);//Указываем, что камера находится в режиме рендера

        mp.game.ui.displayHud(false)// Убираем худ
        mp.game.ui.displayRadar(false)// Убираем радар

        loginScreen = mp.browsers.new('package://web/login/index.html') //Подключаем CEF часть к переменной loginScreen
        loginScreen.execute("mp.invoke('focus', true)")//Даем возможность использовать курсор
        mp.gui.chat.show(false);//Убираем возможность использовать чат
    },
    'hideBrowser': () => {
        mp.game.cam.renderScriptCams(false, false, 0, true, false);//Убираем рендер, чтобы вернуть камеру к игроку
        loginCamera.setActive(false)//Отключаем камеру

        mp.game.ui.displayHud(true)//Включаем худ
        mp.game.ui.displayRadar(true)//Включаем радар

        loginScreen.execute("mp.invoke('focus', false)")//Убираем курсор у игрока
        mp.gui.chat.show(true)//Даем возможность писать что-либо в чат
        loginScreen.active = false//Отключаем нашу CEF часть.
    }
})

3) Работа с CEF частью. Вёрстка
(Cоздаем отдельную папку в клиентской части "web", а в ней папку "login". Здесь заносим наши файлы верстки и скрипты для формы логина.)

Теперь нам нужно сверстать то, что будем показывать пользователю, html и css файл вы могли скопировать в самом начале. Сейчас мы подробно разберем что и за что отвечает в JS скрипте. Вернитесь в HTML файл, и вы во-первых можете увидеть, что я повесил на кнопку в форме авторизации/регистрации событие onclick, в него занёс будущую функцию либо же checkLogin, либо же checkRegister. То есть в этих функциях содержится валидация, и если она будет пройдена успешно, то тогда на клиент, а потом на сервер будет отправляться JSON объект с данными введенными пользователем и уже тогда, если данные пройдут валидацию уже на БД, скроется окно браузера. Вот такая на первый взгляд замысловатая схема работы этой системы. Ах, еще! После каждого input создан span, он будет отвечать за вывод ошибки. Так вот, давайте смотреть, что у нас под капотом нашего JS файлика.

JavaScript:
let textLoginUsername = document.getElementById('textLoginUsername');//По ID указываем путь к спану логинНикнейма
let textLoginPassword = document.getElementById('textLoginPassword');//По ID указываем путь к спану логинПароля
function checkLogin() {//Функция валидации данных
    let userLogin = document.getElementById("userLogin").value//По ID указываем путь к инпуту логинНикнейма и его значению
    let passLogin = document.getElementById("passLogin").value//По ID указываем путь к инпуту логинПароля и его значению
    setErrorFor(textLoginUsername, '', '#000')//В случае чего сбрасываем ошибку(разбор этой функции ниже)
    setErrorFor(textLoginPassword, '', '#000')//В случае чего сбрасываем ошибку(разбор этой функции ниже)
    if(userLogin.length <= 4) {//Валидация никнейма и вывод ошибки в спан
        return setErrorFor(textLoginUsername, 'Никнейм слишком мал', '#ff0000')
    }
    if(passLogin.length <= 4) {//Валидация пароля и вывод ошибки в спан
        return setErrorFor(textLoginPassword, 'Пароль слишком мал', '#ff0000')
    }
    mp.trigger('loginClient', JSON.stringify({userLogin, passLogin}))//Если всё гуд, то вызов на клиент ивента с внесёнными данными
}

let textRegisterUsername = document.getElementById("textRegisterUsername")//По ID указываем путь к спану регистрНикнейма
let textRegisterPassword = document.getElementById("textRegisterPassword")//По ID указываем путь к спану регистрПароля
function checkRegister() {//Функция валидации данных
    let userRegister = document.getElementById("userRegister").value.trim()//По ID указываем путь к инпуту регистрНикнейма и его значению.
    let passRegister = document.getElementById("passRegister").value.trim()//По ID указываем путь к инпуту регистрПароля и его значению.
    setErrorFor(textRegisterUsername, '', '#000')//В случае чего сбрасываем ошибку(разбор этой функции ниже)
    setErrorFor(textRegisterPassword, '', '#000')//В случае чего сбрасываем ошибку(разбор этой функции ниже)
    if(userRegister.length <= 4) {//Валидация никнейма и вывод ошибки в спан
        return setErrorFor(textRegisterUsername, 'Никнейм слишком мал', '#ff0000')
    }
    if(passRegister.length <= 4) {//Валидация пароля и вывод ошибки в спан
        return setErrorFor(textRegisterPassword, 'Пароль слишком мал', '#ff0000')
    }
    mp.trigger('registerClient', JSON.stringify({userRegister, passRegister}))//Если всё гуд, то вызов на клиент ивента с внесёнными данными
}
//Функция которая будет заносить ошибку в спан.
//Первым аргументом указывается в какой именно, вторым аргументом - сообщение. Третьим - цвет
function setErrorFor(textPart, message, color) {
    textPart.innerHTML = message
    textPart.style.color = color
}
//Эта функция так же заносит в определенный спан ошибку.
//Но она срабатывает, когда вызывается с клиента на сервер, для валидации данных с БД.
function showError(textPart, message, color) {
    textPart.innerHTML = message
    textPart.style.color = color
}
//Не буду на этом заострять внимание. Этот кусок кода является как бы слайдером для перемещения
//Между формой регистрации и формой авторизации.
let loginForm = document.getElementById("login")
let regForm = document.getElementById("register")
let btn = document.getElementById("btn")
function loginScroll() {
    loginForm.style.left = "-400px"
    regForm.style.left = "50px"
    btn.style.left = "110px"
}
function registeScroll() {
    loginForm.style.left = "50px"
    regForm.style.left = "450px"
    btn.style.left = "0"
}

4) Отправляем форму с CEF на клиент и на сервер.
Мы при помощи mp.trigger вызвали с CEF на клиент события авторизации пользователя, либо регистрации. Данные которые внес пользователь, мы поместили в JSON объект и будет тащить их до самого сервера :D

JavaScript:
mp.events.add({
    //Вызываем с клиента на сервер событие о том, что пользователь хочет залогиниться и JSON объект с введенными данными.
    'loginClient': (logData) => {
        mp.events.callRemote('loginServer', logData)
    },
    //Вызываем с клиента на сервер событие о том, что пользователь хочет зарегистрироваться и JSON объект с введенными данными.
    'registerClient': (regData) => {
        mp.events.callRemote('registerServer', regData)
    }
})

Так же не забываем про то, что мы должны показывать ошибки от БД, к примеру мол при регистрации "Такой пользователь существует". Для этого добавляем еще на клиентскую сторону событие showError, которое будет вызывать функцию в браузер с текстом ошибки и заносить её в спан.

JavaScript:
//Это событие будет отвечать за вывод в инпут сообщения об ошибки от БД
mp.events.add('showError', (textPart, errorMessage) => {
    loginScreen.execute(`showError(${textPart}, '${errorMessage}', '#ff0000')`);
});

5) Подключение базы данных к серверу.
(На серверной стороне создаём отдельную папку mongoDB, в ней создаем папку models.)
(В папке mongoDB создаём файл index.js и mongo.js.)
(В папке models создаём файл player-schema.js.)
(Необходимо установить в server-files библиотеки bcrypt(Для хэширования данных) и mongoose(Для работы с MongoDB)

А теперь самое сладкое. Зачем вы здесь все собрались. Во первых краткий инструктаж почему я решил использовать базу данных, о которых может вы даже не слышали, если работали с SAMP. Это лично мое мнение. У меня нет большого опыта работы с MySQL, поэтому мнение можно считать субъективным. Пользуйтесь той, с которой работать вам удобнее всего.

MongoDB — документо-ориентированная система управления базами данных, не требующая описания схемы таблиц. Считается одним из классических примеров NoSQL-систем, использует JSON-подобные документы и схему базы данных. То есть - нереляционная БД.

Mongoose - это библиотека JavaScript, позволяющая вам определять схемы со строго-типизированными данными. Сразу после определения схемы Mongoose дает вам возможность создать Model (модель), основанную на определенной схеме. Затем модель синхронизируется с документом MongoDB с помощью определения схемы модели.


🙌 Подведём небольшой итог:

1)
У нас есть MongoDB. Чтобы связаться с ней, нам необходим npm пакет mongoose. Так же существует несколько типов баз данных MongoDB. Это не только JSON объекты, это еще могут быть и графики (MongoDB Charts), а также MongoDB Realm. То есть выбор огромный! Но мы разберем и будем пользоваться другими двумя (на выбор) типами. Это MongoDB Compass и MongoDB Atlas.
Первый тип - это приложение, которое позволяет УДОБНО пользоваться базой на локальном уровне.
Второй тип - это страница в браузере. Рекомендую использовать второй, ибо там хранится все в облаке, и не возникнет проблем, с тем, что может перегрузиться и так далее.

2) Если вы планируете огромный проект, и вам нужен шустрый отклик, то MongoDB будет шустрее.

3) MongoDB имеет динамические запросы документов (document-based query)

4) Отсутствие сложных JOIN'ов.

5) Для того чтобы подключиться к ней. Вам не нужно запускать denwer, openServer. Достаточно зайти в приложение, если используете локальную, либо в браузер, если используете веб-версию.

6) Для хранения используемых в данный момент данных используется внутренняя память, что позволяет получать более быстрый доступ.

7) Работа с большими данными? Вам очень подойдет MongoDB.

Так вот. Ссылка на видео как подключить эту БД к своему компьютеру есть в описании к видео. А если к веб-версии, то вам достаточно лишь зарегистрироваться и войти в аккаунт!

Возвращаемся к разработке! Сейчас работаем с файлом mongo.js, в нём мы подключаем нашу базу данных к проекту и в дальнейшем мы сможем с ней работать из любой точки проекта не подключая нигде повторно! Для этого используется keepAlive: true. Смотрим и изучаем код!

JavaScript:
const mongoose = require('mongoose')//Подключаем библиотеку mongoose для связи с MongoDB

module.exports = async() => {//Экспортируем асинхронную функцию.
    await mongoose.connect('mongodb://localhost:27017/ragemp', {//С помощью метода "connect", подключаемся к MongoDB
        keepAlive: true,//Своего рода постоянное HTTP соединение.
        useNewUrlParser: true,//Отключает предупреждение. MongoDB меняет анализатор URL строки
        useUnifiedTopology: true,//Так же относится к обязательной настройке. Иначе будут предупреждения(soon)
        useFindAndModify: false//Так же относится к обязательной настройке. Иначе будут предупреждения(soon)
    })
    return mongoose//Возвращаем по сути наше подключение в конце функции.
}

Переносимся в код index.js(папка mongoDB) и просто сначала экспортируем функцию а потом её подключаем для корректной работы базы данных.

JavaScript:
const mongo = require('./mongo')//Экспортируем функцию

//Используя встроенный ивент загрузки пакетов, подключаем нашу БД ко всему проекту!
mp.events.add('packagesLoaded', async() => {
    await mongo()
})

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

Переходим в файл player-schema. Прежде чем начнете читать комментарии к коду, объясню. В начале мы создали объект Schema, который в конце экспортируем в нашу БД, но чтобы пароль не отправлялся в том виде, какой он есть, то мы его хэшируем сразу же методом .pre('save'). А так же создаем свой метод для проверки уже хэшированного пароля и пароля введенного пользователем. На самом деле MongoDB на столько надежна, что хэш паролей может иметь место для крупных проектов, но в любом случае лучше обезопасить себя и своих игроков!

JavaScript:
const mongoose = require('mongoose')//Подключаем модуль mongoose(подключение к mongoDB)
const bcrypt = require('bcrypt')//Подключаем модуль bcrypt(хэширование)
//Создаём объект схемы и передаём в него необходимые значения, указывая типы этих данных.
const Schema = mongoose.Schema({
    username: String,
    password: String,
})
//Если будет создаваться новая БД, то пароль в ней будет сразу хэширован автоматически.
Schema.pre('save', function(next) {
    if(!this.isModified('password'))
        return next();
    bcrypt.hash(this.password, 10,(err,passwordHash) => {
        if(err)
            return next(err)
        this.password = passwordHash
        next()
    })
})
//Создание собственного метода для своего рода валидации
//Хэшированнного пароля и пароля введенного пользователем
Schema.methods.comparePassword = function(candidatePassword, cb) {
    bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
        if (err) return cb(err);
        cb(null, isMatch);
    });
}
//Экспортирование нашей схемы в БД
module.exports = mongoose.model('player-schema', Schema)

6) Проверка данных на регистрацию и авторизацию пользователя.
(Работаем с index.js файлом в серверной части.)

Выходим на финишную прямую ребят! Сейчас будем обрабатывать пользовательские ивенты, которые выполняют функцию валидации пароля, но уже на стороне базы данных. Своего рода погружение в мир бэкенда :D
Опять же. В двух словах. Мы создаём константу в которую по сути заносим все нашу БД с игроками и уже в дальнейшем оперируем ей для всякой валидации. Находя определенное значение в ней и сравнивая выдавая результаты. Я постарался сделать в таком формате, который схож на работу с MySQL, чтобы вам было понятно, но при большой отдаче покажу БОЛЕЕ КОРОТКИЙ способ записи.

JavaScript:
//Подключаемся к нашей БД со всеми игроками проекта
const playerSchema = require('../mongoDB/models/player-schema')
//Обрабатываем ивент, когда пользователь отправил форму с данными входа на сервер
mp.events.add('loginServer', async(player, logData) => {
    //Парсим объект данных пользователя
    logData = JSON.parse(logData)
    //Ищем в нашей БД игрока с таким же ником, что ввёл пользователь в форму
    await playerSchema.findOne({username: logData.userLogin}, function(err, cb) {
        //Если такого игрока нет, то в спан выводится ошибка об этом
            if(!cb) player.call('showError', ['textLoginUsername', 'Такого игрока не существует!'])
            //В противном случае мы сравниваем хэш пароля в БД и указаного пользователем.
            //Если что-то пошло не так, выводим в спан ошибку.
            //Заметьте, что мы не подключали здесь библиотеку bcrypt. Все автоматически!
            cb.comparePassword(logData.passLogin, function(err, isMatch) {
                if(isMatch === true){
                    player.call('hideBrowser')
                } else {
                    player.call('showError', ['textLoginPassword', 'Неверный пароль'])
                }     
            })
        })
});
//Обрабатываем ивент, когда пользователь отправил форму с данными регистрации на сервер
mp.events.add('registerServer', async(player, regData) => {
    //Парсим объект данных пользователя
    regData = JSON.parse(regData)
    //Ищем в нашей БД игрока с таким же ником, что ввёл пользователь в форму
        await playerSchema.findOne({username: regData.userRegister}, function(err,data) {
            //Если такой игрок есть, то в спан выводится ошибка об этом
            if(data) {
               player.call('showError', ['textRegisterUsername', 'Такой игрок существует!'])
            }  else {
            //В противном случае мы создаём в БД нового пользователя с данными которые он внес.
            //Так же вызываем ивент, который скроет CEF часть и ТПнет игрока.
            //Заметьте, что мы не подключали здесь библиотеку bcrypt.
            //Пароль будет хэширован автоматически.
                player.call('hideBrowser')
                new playerSchema({
                    username: regData.userRegister,
                    password: regData.passRegister
                }).save()
            }
        
        })
});

Скриншот:

Снимок экрана (1163).png



Автор: shevdev


Скачать архив: https://disk.yandex.ru/d/WUFBRVMwhN8Ntg
 

Gerych

Начинающий специалист
24 Ноя 2020
79
9
56
Mongo DB - в перспективе очень плохая база данных. Поскольку она не оптимизирована ей будет сложно не лагать на 550-700 онлайна.
 

UchihaMadara

Гуру
high coder
27 Окт 2020
512
225
101
для средних проектов думаю подойдёт, для крупных на подобии gta5rp нет. она в разы медленнее mysql или тому же форку mariadb
Во-первых, MySQL не подходит для больших проектов.
Во-вторых, я уверен, что ты просто не умеешь в монго, поэтому делаешь такие выводы.
В-третьих, покажи свой большой проект, где ты перешёл с монго на MySQL, чтобы увеличить скорость работы.
 

челавек паук

Специалист
7 Ноя 2020
158
43
95
Во-первых, MySQL не подходит для больших проектов.
Во-вторых, я уверен, что ты просто не умеешь в монго, поэтому делаешь такие выводы.
В-третьих, покажи свой большой проект, где ты перешёл с монго на MySQL, чтобы увеличить скорость работы.
1. Это ты сам придумал?
2. Я пользовался монго в своё время.
3. Тесты в инете.
 

X-Clusiv

Модератор
Команда форума
high coder
4 Окт 2020
582
274
161
29
Во-первых, MySQL не подходит для больших проектов.
Во-вторых, я уверен, что ты просто не умеешь в монго, поэтому делаешь такие выводы.
В-третьих, покажи свой большой проект, где ты перешёл с монго на MySQL, чтобы увеличить скорость работы.
Чушь...
 

X-Clusiv

Модератор
Команда форума
high coder
4 Окт 2020
582
274
161
29
  1. На запись MongoDB быстрее, если использовать как key-value storage;
  2. Чтение примерно одинаково происходит;
  3. Обе системы — вполне приличные, никто не устарел, никто никого не убил, явного проигрывающего нет.
habr
 

Frenk Buller

Начинающий специалист
20 Мар 2021
31
12
48
подскажите как сделать статичный id игроку( типо id:2322) и вывести его в игре под ником
 

X-Clusiv

Модератор
Команда форума
high coder
4 Окт 2020
582
274
161
29
Вытягивай из бд id персонажа, вставляй в render и рисуй текст над персом с этим номером.
подскажите как сделать статичный id игроку( типо id:2322) и вывести его в игре под ником