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

В этом уроке мы с нуля напишем работу дальнобойщика для сервера 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 КБ · Просмотры: 38

test

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

Lev Angel

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

shturman

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

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

Lev Angel

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

Dihan48

Middle Developer
Полностью готовая и изолированная игровая механика для любого проекта написанная максимально просто в +- 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
Команда форума
Спасибо за обратную связь :)

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

Lev Angel

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

Dihan48

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

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


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

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

Lev Angel

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

desh804

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

Lev Angel

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

desh804

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

Lev Angel

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

Proger

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

Proger

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

Lev Angel

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