Работа Дальнобойщика

В этом уроке мы с нуля напишем работу дальнобойщика для сервера rage mp. Как обычно используем только javascript.

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

e124be-GTA5 2015-05-10 22-21-18-63.png

Ниже приведу финальную версию кода скрипта с комментариями. Полный архив с исходным кодом скрипта можно также скачать в приложении в конце поста. Для установки скопируйте в папку сервера и подключите клиентскую часть в client_packages/index.js.

Серверная часть (packages/tw/index.js)
JavaScript:
// Спавны грузовиков, трейлеров и точек загрузки/разгрузки
const truckSpawns = [
    { x: -422.77093505859375, y: 1166.211669921875, z: 325.973876953125, heading: -18.22724151611328},
    { x: -404.05560302734375, y: 1161.9130859375, z: 325.98529052734375, heading: -12.60693359375}
];

const trailerSpawns = [
    { x: -410.40142822265625, y: 1134.956298828125, z: 325.9732971191406, heading: -13.243707656860352},
    { x: -428.8808288574219, y: 1138.2325439453125, z: 325.9739685058594, heading: -16.17988395690918}
];

const pickPoints = [
    { x: -412.3080749511719, y: 1218.32763671875, z: 324.7181396484375},
    { x: -457.32415771484375, y: 1152.080810546875, z: 324.9734802246094},
    { x: -511.28912353515625, y: 1184.92919921875, z: 323.9432373046875},
    { x: -368.6314392089844, y: 1255.5517578125, z: 327.6026916503906},
    { x: -347.2783203125, y: 1152.3865966796875, z: 324.71673583984375}
];


mp.events.add('packagesLoaded', () => { // при старте сервера спавним транспорт для миссии
    spawnWorkVehicles("phantom3", truckSpawns);
    spawnWorkVehicles("trailers", trailerSpawns);
});


function spawnWorkVehicles(modelName, spawnPoints){ // функция позволяет заспавнить транспорт по координатам переданным в массиве spawnPoints
    spawnPoints.forEach( spawn => {
        mp.vehicles.new(mp.joaat(modelName), spawn, { heading: spawn.heading});
    });
}


mp.events.addCommand('tw', (player) => { // команда старта работы
    let loadPoint = getRandomPoint();
    let destPoint;

    do{
        destPoint = getRandomPoint();
    } while(loadPoint.x == destPoint.x && loadPoint.y == destPoint.y); // проверяем чтобы точки старта и финиша не совпадали

    player.call('playerStartTruckWork', [loadPoint, destPoint]);
});


function getRandomPoint(){ // получаем случайную точку из массива pickPoints
    return pickPoints[ Math.floor( Math.random() * pickPoints.length ) ];
}


Клиентская часть (client_packages/tw/index.js)
JavaScript:
const markerType = 1; // тип маркера
const markerSize = 5; // размер маркера
const markerColor = [255, 0, 0, 100]; // цвет маркера
const blipType = 67; // тип иконки на радаре

const freezeTime = 3; // время на сколько замораживать игрока при загрузке/разгрузке, в секундах

const localPlayer = mp.players.local; // локальный игрок

let loadPoint = false; // точка загрузки
let destPoint = false; // точка выгрузки

let workMarker = false; // маркер
let workMarkerColshape = false; // колшейп
let workBlip = false; // иконка на радаре

let missionStatus = 0; // Статус миссии: 0 - не начато, 1 - идем к точке загрзки, 2 - едем к точке разгрузки

mp.events.add('playerStartTruckWork', (startPoint, finishPoint)=> { // запуск миссии

    if( missionStatus !== 0){
        return mp.gui.chat.push("ОШИБКА: Вы уже начали работу Дальнобойщика!");
    }

    if ( !checkPlayerInVehicleWithTrailer() ) return false;

    // запонимаем точки старта и назначения
    loadPoint = startPoint;
    destPoint = finishPoint;

    setMarker(startPoint);
    missionStatus = 1;

    mp.gui.chat.push("Вы начали работу Дальнобойщика!");
});

mp.events.add('playerEnterColshape', (colshape) => { // попадание игрока в колшейп
    if( colshape == workMarkerColshape){ // проверяем что это наш колшейп
        pickLocation();
    }
});


function pickLocation(){ // игрок наехал на маркер
    
    if ( !checkPlayerInVehicleWithTrailer() ) return false;

    clearMarker();
    freezePlayer();

    if( missionStatus == 1){
        playerReachLoadingPoint(); // загружаем груз
    } else if ( missionStatus == 2){
        playerReachDestPoint(); // выгружаем груз
    }
 
}

function playerReachLoadingPoint(){ // игрок доехал до точки загрузки
    mp.gui.chat.push("Вы прибыли на место загрузки. Ожидайте...");

    setTimeout( () => {
        unfreezePlayer();
        mp.gui.chat.push("Отправляйтесь к месту разгрузки");
        missionStatus = 2;
        setMarker(destPoint);
    }, freezeTime * 1000);
}

function playerReachDestPoint(){ // игрок доехал до точки разгрузки
    mp.gui.chat.push("Вы прибыли на место разгрузки. Ожидайте...");
    
    setTimeout( () => {
        unfreezePlayer();
        mp.gui.chat.push("Груз доставлен. Спасибо за работу!");
        missionStatus = 0;
    }, freezeTime * 1000);
}


function setMarker(point){ // ставим маркер в точку point
    workMarker = mp.markers.new(markerType, point, markerSize, { color: markerColor});
    workMarkerColshape = mp.colshapes.newSphere(point.x, point.y, point.z, markerSize);
    workBlip = mp.blips.new(blipType, point, {shorRange: false});
    workBlip.setRoute(true); // включаем отображение маршрута на карте
}


function clearMarker(){ // убираем маркер
    workMarker.destroy();
    workMarkerColshape.destroy();
    workBlip.setRoute(false);
    workBlip.destroy();
}

function freezePlayer(){
    localPlayer.vehicle.freezePosition(true);
}

function unfreezePlayer(){
    localPlayer.vehicle.freezePosition(false);
}


function checkPlayerInVehicleWithTrailer(){ // проверяем нахождение игрока в грузовике с трейлером
    if( !localPlayer.vehicle){
        mp.gui.chat.push("ОШИБКА: Вы должны быть в транспорте!");
        return false;
    }

    if( !localPlayer.vehicle.isAttachedToTrailer() ){
        mp.gui.chat.push("ОШИБКА: У вас должен быть прицеплен трейлер!");
        return false;
    }

    return true;
}

В итоге получился полностью функциональный скрипт. Но если вы захотите использовать его на реальном сервере, то нужно будет доделать некоторые вещи самостоятельно.
  • Вознаграждение за работу. Самое простое - это давать деньги после завершения работы в playerReachDestPoint(). Пригодится Player::setMoney. Размер вознаграждения можно делать фиксированным, но интереснее будет если он будет привязан к расстоянию между точкой старта и назначения.​
  • Расставить транспорт и трейлеры в нужных местах.​
  • Подумать о респавне грузовиков и трейлеров когда они давно не используются.​
  • Возможно поменять логику начала работы, чтобы не приходилось каждый раз прописывать команду. Например, после завершения одной работы сразу стартовать другую. Но в таком случае нужна будет команда остановить работу, чтобы игрок смог как-то закончить ее.​
  • Останавливать работу при смерти игрока.​
 

Вложения

  • trucker-work.zip
    2,2 КБ · Просмотры: 82

wilsondev

Junior Developer
Сообщения
38
Классный разбор полностью всего кода! Спасибо огромное :)
 

Lev Angel

Developer
Команда форума
Скриптер
Сообщения
795
Спасибо ;)
Там в коде есть небольшая опечатка. Когда создаем blip неправильно указано название опции shortRange. Оно как бы не критично, поэтому уже не буду перезаливать ничего.
 

Jane

Junior Developer
Сообщения
31
Круто (y) Побольше бы таких уроков!!!
 

shturman

New member
Сообщения
4
Вот бы урок с созданием регистрации/авторизации и подключение к БД

За дальнобоев отдельное спасибо!)
 

Lev Angel

Developer
Команда форума
Скриптер
Сообщения
795
Пока в планах есть урок по работе с бд. Регистрация/авторизация наверное в него не войдет, но может позже сделаем. А может и там сделаю, посмотрим как пойдет :)
 

Dihan48

Middle Developer
Скриптер
Сообщения
61
Полностью готовая и изолированная игровая механика для любого проекта написанная максимально просто в +- 100 строчек, да ты крут чёрт возьми!

по поводу рандома хотел свои 3 копейки добавить

JavaScript:
function random(max) {
    return Math.floor(Math.random() * max);
}

let loadIndex = random(pickPoints.length);
let loadPoint = pickPoints[loadIndex];

let restPickPoints = pickPoints.filter((item, index) => index !== loadIndex);

let destIndex = random(restPickPoints.length)
let destPoint = restPickPoints[destIndex];

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

Lev Angel

Developer
Команда форума
Скриптер
Сообщения
795
Спасибо за обратную связь :)

Немного не согласен на счет while vs filter. Если я правильно понимаю как работает filter, то ему нужно пройти по всему массиву (линейная сложность). Если же мы берем while, то с большой долей вероятности там будет 1-2 итерации и на достаточно большом массиве будет работать быстрее. В плане эффективности работы кода проблем быть не должно.
Что касается читаемости кода, то для новичков мне кажется while будет понятнее. Но твой пример явно лаконичнее и красивее выглядит;)
 

Lev Angel

Developer
Команда форума
Скриптер
Сообщения
795
Если можешь привести аргументы против while - будет круто. Буду знать на будущее.
 

Dihan48

Middle Developer
Скриптер
Сообщения
61
ахаха а вот и нет :LOL:
я напишу аргументы и за и против

цикл while в последствии может привести к проблемам, которые новичку будет очень сложно найти и исправить. например если в дальнейшем пользователь кода будет манипулировать массивом pickPoints и допустит его уменьшение до 1 элемента или приведение ко всем одинаковым элементам из-за ошибки в логике нового пользовательского кода, то цикл уйдет в бесконечность прихватив с собой сервер и ни оставит ни одной красной строчки об этом инциденте. Такие баги сложно даже отловить, а исправить подавно.


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

по поводу скорости работы, да именно с while такая выборка работает безусловно быстрее
 

Lev Angel

Developer
Команда форума
Скриптер
Сообщения
795
Согласен, с 1 элементом в массиве оно будет работать сильно дольше😄
Еще возникла идея, а что если через slice просто сделать копию pickPoints без первой точки loadPoint и потом просто брать random. По идее slice не будет пробегать по всему массиву как filter. И в бесконечный цикл не попадем. Но это уже точно будет менее читаемо чем while или filter😕
 

desh804

New member
Сообщения
3
а как сделать чтобы точки загрузки были не рандомны и разгрузки тоже
 

Lev Angel

Developer
Команда форума
Скриптер
Сообщения
795
Нужно переделать скрипт. Добавить какую-то менюшку где можно будет выбирать из списка заказов, например. Ну или как ты себе это увидишь ;)
 

desh804

New member
Сообщения
3
Нужно переделать скрипт. Добавить какую-то менюшку где можно будет выбирать из списка заказов, например. Ну или как ты себе это увидишь ;)
mp.events.addCommand('tw', (player) => {
let loadPoint =
let destPoint; Подскажите что написать дальше я могу сделать так, чтоб точка Загрузки была одна , но я пытаюсь сделать чтоб сразу было 3 точки загрузки но одна разгрузки . Подскажите пожалуйста в заранее Спасибо
 

Lev Angel

Developer
Команда форума
Скриптер
Сообщения
795
Сделай отдельную переменную с координатами где будет выгрузка
JavaScript:
const destPoint = { x: -412.3080749511719, y: 1218.32763671875, z: 324.7181396484375};
Соответственно в pickPoints у тебя будут только координаты точек загрузки.

И в самой команде tw меняешь часть где определяется точка выгрузки. Тебе не нужно будет ее искать и сравнивать с точкой загрузки - она уже у тебя определена в destPoint
JavaScript:
mp.events.addCommand('tw', (player) => { // команда старта работы
    let loadPoint = getRandomPoint();
    player.call('playerStartTruckWork', [loadPoint, destPoint]);
});
Как-то так ;)
 

desh804

New member
Сообщения
3
Сделай отдельную переменную с координатами где будет выгрузка
JavaScript:
const destPoint = { x: -412.3080749511719, y: 1218.32763671875, z: 324.7181396484375};
Соответственно в pickPoints у тебя будут только координаты точек загрузки.

И в самой команде tw меняешь часть где определяется точка выгрузки. Тебе не нужно будет ее искать и сравнивать с точкой загрузки - она уже у тебя определена в destPoint
JavaScript:
mp.events.addCommand('tw', (player) => { // команда старта работы
    let loadPoint = getRandomPoint();
    player.call('playerStartTruckWork', [loadPoint, destPoint]);
});
Как-то так ;)
спасибо . Новы меня не так поняли, я пытаюсь сделать чтоб loadPoint был не рандом а 3(или больше) точек появлялись на карте и я решал куда ехать . а getRandomPoint он будет выбирать за меня и давать одну из 3 точек которые я написал в pickPoints
 

Lev Angel

Developer
Команда форума
Скриптер
Сообщения
795
Понял. Ну получится немного сложнее, но тоже реально. Получается у тебя работа будет стартовать не по команде, а при достижении одной из точек загрузки. Соответственно вначале нужно как-то эти точки расставлять на карте. Т. е. какая-то функция которая будет ставить на карте блипы и маркеры (или пикапы). Эту функцию можно запускать по команде, при входе игрока на сервер или при посадке в грузовик.
В остальном будет плюс-минус тоже самое.
 

Proger

Trainee
Сообщения
10
Хеллп... Как сделать удаление груза после наезда на метку помдеднию... Ну куда вставлять понял а вот сам процесс удаления
 

Proger

Trainee
Сообщения
10
Понял. Ну получится немного сложнее, но тоже реально. Получается у тебя работа будет стартовать не по команде, а при достижении одной из точек загрузки. Соответственно вначале нужно как-то эти точки расставлять на карте. Т. е. какая-то функция которая будет ставить на карте блипы и маркеры (или пикапы). Эту функцию можно запускать по команде, при входе игрока на сервер или при посадке в грузовик.
В остальном будет плюс-минус тоже самое.
Хеллп... Как сделать удаление груза после наезда на метку помдеднию... Ну куда вставлять понял а вот сам процесс удаления
 

Lev Angel

Developer
Команда форума
Скриптер
Сообщения
795
Ты хочешь удалять трейлер, после того как груз доставлен?
В playerReachDestPoint() в таймере отправляешь какой-нибудь event на сервер. А на сервере уже удаляешь трейлер. Нужно будет его как-то получить вначале. Но по идее если ты найдешь vehicle игрока, то либо через свойство vehicle.trailer, либо там какой-то метод должен быть типо vehicle.getTrailer()
 
Верх