• Просьба не публиковать сообщения с предложениями продажи модов или сборок!

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

Система Секретных Пакетов для Rage MP

Lev Angel

Developer
Команда форума
Скриптер
Сообщения
924
В этом уроке напишем с нуля систему секретных пакетов. Подобная система есть во всех играх серии GTA. Идея простая: по карте разбросаны пикапы и когда игрок подбирает их все, то получает какую-то награду.

Видео часть с подробным описанием:
Untitled design.png

Условия:
- Нужно запоминать какие пакеты уже нашел игрок и при перезаходе их больше уже не показывать ему
- Когда игрок подбирает пакет, нужно показывать ему уведомление в котором будет указано сколько пакетов он уже нашел и сколько их всего
- Когда игрок подбирает все пакеты - выдать награду и соответствующее уведомление

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

JavaScript:
const packages = [
    { x: -422.77093505859375, y: 1166.211669921875, z: 325.973876953125},
    { x: -404.05560302734375, y: 1161.9130859375, z: 325.98529052734375},
    { x: -410.40142822265625, y: 1134.956298828125, z: 325.973297119140},
    { x: -428.8808288574219, y: 1138.2325439453125, z: 325.9739685058594}
];


При входе игрока на сервер будем отправлять ему этот список

JavaScript:
mp.events.add("playerReady", player => {
    player.call('playerInitSecretPackages', [packages]);
});

На клиентской стороне будем создавать пикапы по этому списку координат. Кроме пикапа создаем еще и колшейп.

JavaScript:
const pickupHash = 3732468094;


mp.events.add('playerInitSecretPackages', (packages)=> {
    packages.forEach((package, id) => {
        let colshape = mp.colshapes.newSphere(package.x, package.y, package.z, 1);
        colshape.secretPackageId= id;
        colshape.secretPickup = mp.game.object.createPickupRotate(pickupHash, package.x, package.y, package.z, 0, 0, 0, 512, 0, 0, true, pickupHash);
    });
});

20210108124516_1.jpg

Благодаря наличию колшейпов мы можем определять когда игрок подбирает пикап.
Примечание: Возможно для этого есть более простой способ и без колшейпа, но я честно его не нашел. По логике должен быть event когда игрок подбирает пикап, но на вики ничего нет.

JavaScript:
mp.events.add("playerEnterColshape", (colshape) => {
    if(colshape.secretPickup){
        mp.events.callRemote('playerCollectedPackage', colshape.secretPackageId ); // отправляем id пакета на сервер
        mp.game.object.removePickup(colshape.secretPickup);
        colshape.destroy();
    }
});

Когда игрок подбираем пикап мы будем отправлять на сервер событие playerCollectedPackage в котором будем передавать id этого пакета.
На сервере мы будем запоминать какие пакеты подобрал игрок, сохранять их в файл. Когда игрок заходит на сервер мы будем загружать список с id найденный пикапов и передавать на клиент не весь список packages, а только те, которые еще не были найдены.
Для этого модифицируем на серверный код

JavaScript:
var fs = require('fs'); // подключим модуль для работы с файлами

В папке packages/sevret-packages создадим папку data. В ней мы будем сохранять в файлах список id найденных пакетов для каждого игрока в отдельном файле. Название файла должно быть уникально и как-то связанно с игроком, чтобы при повторном заходе мы могли его найти и правильно определить. В этом уроке для простоты мы будем использовать ник игрока, но в реальном проекте советую использовать что-то другое, например, серийный номер клиента.
В самом файле мы будем хранить массив с id в виде JSON строки.

JavaScript:
mp.events.add("playerReady", player => {


    fs.readFile( __dirname + `/data/${player.name}.txt`, function(err,data){ // загружаем список найденный пакетов для игрока
        let collectedPackages = new Array();


        if(!err){
            collectedPackages = JSON.parse(data);
        }
        // если файла нет или возникла другая ошибка то collectedPackages будет просто пустым массивом


        player.packagesCollected = collectedPackages;


        let playerPackages = new Array(); // в этот массив запишем все координаты которые игрок еще не нашел


        packages.forEach((package, packageId) => {
            if (player.packagesCollected.indexOf(packageId) == -1){
                package.id = packageId // обязательно добавляем к координатам порядковый id пакета в массиве packages, т. к. в playerPackages порядок координат будет другой
                playerPackages.push(package);
            }
        });


        player.call('playerInitSecretPackages', [playerPackages]);


    });
});

Когда формируем массив playerPackages, то в него не попадут уже найденные ранее игроком координаты. Соответственно в самом playerPackages нумерация координат будет отличаться от исходной в packages. Когда мы сохраняем список id найденных игроком, то они соответствуют порядковым номерам в массиве packages. Поэтому чтобы с клиента нам приходили верные id - мы добавляем к координатам еще и id.
package.id = packageId

Клиентский код тоже немного модифицируем, чтобы учитывать наличие id в самом объекте с координатами.
JavaScript:
mp.events.add('playerInitSecretPackages', (packages)=> {
    packages.forEach((package) => {
        let colshape = mp.colshapes.newSphere(package.x, package.y, package.z, 1);
        colshape.secretPackageId= package.id;
        colshape.secretPickup = mp.game.object.createPickupRotate(pickupHash, package.x, package.y, package.z, 0, 0, 0, 512, 0, 0, true, pickupHash);
    });
});

Когда игрок подбирает пикам мы будем добавлять его в список найденных и сохранять его сразу в файл на сервере.
JavaScript:
mp.events.add("playerCollectedPackage", (player, packageId) => {
    player.packagesCollected.push(packageId);
    playerStorePackagesData(player);
    console.log(`${player.name} collected secret package id: ${packageId}`);
});




function playerStorePackagesData(player){
    fs.writeFile(__dirname + `/data/${player.name}.txt`, JSON.stringify(player.packagesCollected), () => {});
}

На клиент обратно будем слать событие с подтверждением. В нем мы отправим число - сколько всего пакетов и сколько из них нашел игрок

JavaScript:
mp.events.add("playerCollectedPackage", (player, packageId) => {
    ...
    
    player.call('playerSecretPackageInfo', [packages.length, player.packagesCollected.length]);
});

Для того чтобы показывать красивые уведомления мы будем использовать библиотеку scaleform_messages

JavaScript:
mp.events.add('playerSecretPackageInfo', (totalPackages, collectedPackages)=> {
    let title = "Секретный пакет найден";
    if(collectedPackages == totalPackages){
        title = "Вы нашли все Секретные пакеты";
    }


    mp.events.call("ShowShardMessage", title, `Найдено пакетов: ${collectedPackages}/${totalPackages} `);
});

20210108124529_1.jpg

Также на серверной стороне мы будем выдавать какую-то награду когда игрок найдет все секретные пакеты. В нашем случае это будет оружиме миниган :)

JavaScript:
mp.events.add("playerCollectedPackage", (player, packageId) => {
    ...


    if( player.packagesCollected.length == packages.length){
        playerCollectAllPackages(player);
    }
});




function playerCollectAllPackages(player){
    console.log(`${player.name} collected all secret packages`);
    player.giveWeapon(mp.joaat('weapon_minigun'), 10000);
}

20210108124615_1.jpg

Таким образом получаем полностью функциональный скрипт. Для работы нужно будет добавить больше координат пикапов и использовать более надежные идентификаторы в названиях файлов.
 

Вложения

  • secret-packages.zip
    5,3 КБ · Просмотры: 26
JavaScript:
mp.events.add("playerCollectedPackage", (player, packageId) => {
    ...


    if( player.packagesCollected.length == packages.length){
        playerCollectAllPackages(player);
    }
});




function playerCollectAllPackages(player){
    console.log(`${player.name} collected all secret packages`);
    player.giveWeapon(mp.joaat('weapon_minigun'), 10000);
}

Не совсем понятно, зачем выносить в отдельную функцию ;o
JavaScript:
mp.events.add("playerCollectedPackage", (player, packageId) => {
    ...

    if( player.packagesCollected.length == packages.length){
        console.log(`${player.name} collected all secret packages`);
        player.giveWeapon(mp.joaat('weapon_minigun'), 10000);
    }
});

JavaScript:
mp.events.add('playerSecretPackageInfo', (totalPackages, collectedPackages)=> {
    let title = "Секретный пакет найден";
    if(collectedPackages == totalPackages){
        title = "Вы нашли все Секретные пакеты";
    }


    mp.events.call("ShowShardMessage", title, `Найдено пакетов: ${collectedPackages}/${totalPackages} `);
});

Отвечал по поповоду этого на видео
JavaScript:
mp.events.add('playerSecretPackageInfo', (totalPackages, collectedPackages)=> {
    mp.events.call("ShowShardMessage", collectedPackages == totalPackages ? `Вы нашли все Секретные пакеты` : `Секретный пакет найден`, `Найдено пакетов: ${collectedPackages}/${totalPackages} `);
});

Ну и приятный на глаз:
JavaScript:
mp.events.add('playerSecretPackageInfo', (totalPackages, collectedPackages)=> {
    mp.events.call(
        "ShowShardMessage", 
        collectedPackages == totalPackages 
            ? `Вы нашли все Секретные пакеты` 
            : `Секретный пакет найден`, 
        `Найдено пакетов: ${collectedPackages}/${totalPackages} 
    `);
});

JavaScript:
mp.events.add("playerReady", player => {


    fs.readFile( __dirname + `/data/${player.name}.txt`, function(err,data){ // загружаем список найденный пакетов для игрока
        let collectedPackages = new Array();


        if(!err){
            collectedPackages = JSON.parse(data);
        }
        // если файла нет или возникла другая ошибка то collectedPackages будет просто пустым массивом


        player.packagesCollected = collectedPackages;


        let playerPackages = new Array(); // в этот массив запишем все координаты которые игрок еще не нашел


        packages.forEach((package, packageId) => {
            if (player.packagesCollected.indexOf(packageId) == -1){
                package.id = packageId // обязательно добавляем к координатам порядковый id пакета в массиве packages, т. к. в playerPackages порядок координат будет другой
                playerPackages.push(package);
            }
        });


        player.call('playerInitSecretPackages', [playerPackages]);


    });
});

Мелочь, а целая строчка
JavaScript:
packages.forEach((package, packageId) => {
    if (player.packagesCollected.indexOf(packageId) == -1) {
        playerPackages.push({ id: packageId, ...package });
    }
});

JavaScript:
var fs = require('fs'); // подключим модуль для работы с файлами

Будем просто модными и не будем выделяться
JavaScript:
const fs = require('fs'); // подключим модуль для работы с файлами
 
Спасибо за code review :)

Не совсем понятно, зачем выносить в отдельную функцию
Здесь наверное не очень удачное название для этой функции выбрал. Нужно было что-то вроде rewardPlayer.
Длинные функции которые много чего делают - это плохо. Да и человеку который будет ковыряться в коде на мой взгляд так будет более понятно.
Иногда я даже простую проверку выношу в отдельную функцию. Название функции показывает что делает этот код или что мы проверяем. Это делает код более читаемым и выразительным.
 
Не совсем понятно, зачем выносить в отдельную функцию ;o
По сути это аналогично комментарию
Название функции показывает что делает этот код или что мы проверяем. Это делает код более читаемым и выразительным.
Хотя 2 строчки в отдельную функцию, которая ни где больше не используется, действительно не стоит выносить
Ну и приятный на глаз:
JavaScript:
mp.events.add('playerSecretPackageInfo', (totalPackages, collectedPackages)=> {
mp.events.call(
"ShowShardMessage",
collectedPackages == totalPackages
? `Вы нашли все Секретные пакеты`
: `Секретный пакет найден`,
`Найдено пакетов: ${collectedPackages}/${totalPackages}
`);
});
Кому как, мне больше например такой вариант приятнее на глаз:
JavaScript:
mp.events.add('playerSecretPackageInfo', (totalPackages, collectedPackages) => {
    const title = collectedPackages == totalPackages ? `Вы нашли все Секретные пакеты` : `Секретный пакет найден`;
    const msg = `Найдено пакетов: ${collectedPackages}/${totalPackages} `;
    mp.events.call("ShowShardMessage", title, msg);
});
 
Мелочь, а целая строчка
JavaScript:
packages.forEach((package, packageId) => {
    if (player.packagesCollected.indexOf(packageId) == -1) {
        playerPackages.push({ id: packageId, ...package });
    }
});
Обьясните, пожалуйста, для вы тут используете спреад для package!?
 
Обьясните, пожалуйста, для вы тут используете спреад для package!?
чтобы каждый отправленный на клиент объект выглядел вот таким образом
JavaScript:
{ id: 0, x: -422.77093505859375, y: 1166.211669921875, z: 325.973876953125 }
из-за того что игрок мог собрать до этого какие-то пакеты то массив для инициализации может быть меньше и чтобы не сравнивать потом по координатам "собранные" пакеты проще указать id.
id будет соответствовать индексу в исходном массиве всех пакетов packages
в последствии клиент будет отправлять id "собранного" пакета на сервер и сервер сразу поймет какой именно пакет был собран из этого списка packages
 
Доброго времени, коллега)
Я думаю если сделать файл - допустим dataPackages.Json
JavaScript:
{
 
[
   {
      "name": "package1",
      "position": {
         "x": 00000,
         "y": 0000,
         "z": 000
      },
      "width": 10,
      "height": 10,
      "dimension": 0
   },
   {
      "name": "package2",
      "position": {
         "x": 00000,
         "y": 0000,
         "z": 000
      },
      "width": 10,
      "height": 10,
      "dimension": 0
   },
]
   ....
дальше на сервере подключить:
const dataPackages = require( '../dataPackages .json' );
сделать цикл перебора, что бы не плодить миллион строк кода и выполнить при подключении игрока
JavaScript:
dataPackages.forEach( element =>
        {
            createPackages( element );//вызываем функцию и передаем element перебора
        } )

ну и собственно сам код

JavaScript:
createPackages( element )
 {
let package;                          //вот тут он берет значения с .json файла
package = mp.colshapes.newRectangle( element.position, element.width, element.height, element.dimension );
package.playerEnter((player, package)=>
{
  console.log(`${player.name} entered the colshape`);
 mp.events.add("playerEnter-package", playerEnterColshapeHandler);
  });
}
ну а дальше само собой объявить евент на клиенте и тд...
работоспособность не проверял, писал прямо в теме) но +- должно
 
Как-то мысль оборвалась))
Я предпочитаю то что не критично утаскивать на клиент, чтобы лишний раз не напрягать сервак.
В данном случае width, height и dimension не меняются, поэтому можно их и не хранить в файле.
 
Как-то мысль оборвалась))
Я предпочитаю то что не критично утаскивать на клиент, чтобы лишний раз не напрягать сервак.
В данном случае width, height и dimension не меняются, поэтому можно их и не хранить в файле.
Чем больше значений ты хранишь на клиенте, тем больше вероятность, что их поменяет игрок(читер, хацкер и тд). Если какие-либо вычислительные процессы ставить на клиент, то нужно будет все равно проверять их сервером, так что проще все закинуть на сервер и результат отправлять на клиент.
 
Чем больше значений ты хранишь на клиенте, тем больше вероятность, что их поменяет игрок(читер, хацкер и тд). Если какие-либо вычислительные процессы ставить на клиент, то нужно будет все равно проверять их сервером, так что проще все закинуть на сервер и результат отправлять на клиент.
Хз, тут конечно спорный вопрос. В этом случае я решил, что это не супер критичные данные. Сервер все-таки не резиновый :)
 
Назад
Верх