Автор Тема: Написание плагина. Настройка проекта  (Прочитано 75082 раз)

Оффлайн kenking

  • Новичок
  • **
  • Сообщений: 237
  • Репутация: +16/-0
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #45 : Август 09, 2016, 08:55:17 am »
Цитировать
CCheat я особо не разбирал.
В sdk была только функция VehicleChear для спавна авто.
А добавил-то я правильно?


Оффлайн DK

  • Новичок
  • **
  • Сообщений: 234
  • Репутация: +328/-0
    • dk22pac
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #46 : Август 09, 2016, 09:19:20 pm »
Цитировать
CCheat я особо не разбирал.
В sdk была только функция VehicleChear для спавна авто.
А добавил-то я правильно?
Да.
Plugin-SDK https://github.com/DK22Pac/plugin-sdk

Discord-сервер по plugin-sdk и программированию в GTA
RU https://discord.gg/QEesDGb
ENG https://discord.gg/zaVqFQv

Оффлайн kenking

  • Новичок
  • **
  • Сообщений: 237
  • Репутация: +16/-0
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #47 : Август 10, 2016, 09:40:39 am »
1)
Цитировать
Можно было бы вообще разделить процессы открытия/закрытия и обработки клавиш (чтобы эти процессы выполнялись одновременно).
Т.е. нажал клавишу, дверь открывается\закрывается и в это же время нажал другую клавишу и одновременно пошло открытие\закрытие другой двери? И т.д. Можно это показать для этого же примера? (SA_OpenDoorExample) https://github.com/DK22Pac/plugin-sdk/blob/master/examples/SA_OpenDoorExample/Main.cpp

2) Какой загрузчик asi использовать для VC, чтобы скрипты загружались не из корневого каталога, а из \scripts ?

3) Будет ли в плагине VC реализован поиск компонента по имени?
Код: C++
  1. RwFrame *component = CClumpModelInfo::GetFrameFromName(automobile->m_pRwClump, "bonnet_dummy");

Оффлайн Shagg_E

  • Главный Модератор
  • Постоялец
  • *****
  • Сообщений: 696
  • Репутация: +23/-4
  • Изобретательный Рукожопъ
    • Просмотр профиля
    • NewRockstar
Re: Написание плагина. Настройка проекта
« Ответ #48 : Август 10, 2016, 04:23:52 pm »
Ох - наконец-то я что-то полезное оставлю в этой теме :D
Цитировать
2) Какой загрузчик asi использовать для VC, чтобы скрипты загружались не из корневого каталога, а из \scripts ?
Ultimate ASI Loader

Оффлайн DK

  • Новичок
  • **
  • Сообщений: 234
  • Репутация: +328/-0
    • dk22pac
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #49 : Август 10, 2016, 10:51:45 pm »
kenking, покажу позже.

По VC ничего не могу сказать. Если есть свободное время - дорабатываю версию для SA.
« Последнее редактирование: Август 10, 2016, 10:56:55 pm от DK »
Plugin-SDK https://github.com/DK22Pac/plugin-sdk

Discord-сервер по plugin-sdk и программированию в GTA
RU https://discord.gg/QEesDGb
ENG https://discord.gg/zaVqFQv

Оффлайн kenking

  • Новичок
  • **
  • Сообщений: 237
  • Репутация: +16/-0
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #50 : Август 11, 2016, 06:47:53 pm »
kenking, покажу позже.
Хорошо. Ещё интересует создание спецактёра. Там же отличается (в клео по-крайне мере) загрузка модели, создание актёра, выгрузка модели и т.д. Также интересна установка (и удаление) деталей тюнинга на транспорт. Такие примеры были бы тоже кстати. 

Ultimate ASI Loader
Спасибо.

Оффлайн DK

  • Новичок
  • **
  • Сообщений: 234
  • Репутация: +328/-0
    • dk22pac
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #51 : Август 11, 2016, 11:51:29 pm »
Т.е. нажал клавишу, дверь открывается\закрывается и в это же время нажал другую клавишу и одновременно пошло открытие\закрытие другой двери? И т.д. Можно это показать для этого же примера?
Код: C++
  1. #include <plugin.h>
  2. #include "game_sa\common.h"
  3. #include "game_sa\CAutomobile.h"
  4. #include "game_sa\CTimer.h"
  5.  
  6. using namespace plugin;
  7.  
  8. const float ACTION_TIME_STEP = 0.005f;
  9. const unsigned int TIME_FOR_KEYPRESS = 500;
  10.  
  11. class DoorsExample {
  12. public:
  13.     static int componentByDoorId[6]; // Таблица перевода eDoors в Id компонента
  14.  
  15.     static int m_nLastTimeWhenAnyActionWasEnabled; // Последнее время запуска события
  16.  
  17.     enum eDoorEventType { // Тип события
  18.         DOOR_EVENT_OPEN,
  19.         DOOR_EVENT_CLOSE
  20.     };
  21.  
  22.     class DoorEvent { // Класс события
  23.     public:
  24.         bool m_active;
  25.         eDoorEventType m_type;
  26.         float m_openingState;
  27.        
  28.         DoorEvent() {
  29.             m_active = false;
  30.             m_type = DOOR_EVENT_CLOSE;
  31.         }
  32.     };
  33.  
  34.     class VehicleDoors {
  35.     public:
  36.         DoorEvent events[6]; // События для всех 6 дверей
  37.  
  38.         VehicleDoors(CVehicle *) {}
  39.     };
  40.  
  41.     static VehicleExtendedData<VehicleDoors> VehDoors; // Наше расширение
  42.  
  43.     static void EnableDoorEvent(CAutomobile *automobile, eDoors doorId) { // Включить событие двери
  44.         if (automobile->IsComponentPresent(componentByDoorId[doorId])) {
  45.             if (automobile->m_damageManager.GetDoorStatus(doorId) != DAMSTATE_NOTPRESENT) {
  46.                 DoorEvent &event = VehDoors.Get(automobile).events[doorId];
  47.                 if (event.m_type == DOOR_EVENT_OPEN)
  48.                     event.m_type = DOOR_EVENT_CLOSE; // Если последнее событие - открытие, то закрываем
  49.                 else
  50.                     event.m_type = DOOR_EVENT_OPEN; // Если последнее событие закрытие - то открываем
  51.                 event.m_active = true; // Включаем обработку
  52.                 m_nLastTimeWhenAnyActionWasEnabled = CTimer::m_snTimeInMilliseconds;
  53.             }
  54.         }
  55.     }
  56.  
  57.     static void ProcessDoors(CVehicle *vehicle) { // Обработка событий для конкретного авто
  58.         if (vehicle->m_dwVehicleSubClass == VEHICLE_AUTOMOBILE) {
  59.             CAutomobile *automobile = reinterpret_cast<CAutomobile *>(vehicle);
  60.             for (unsigned int i = 0; i < 6; i++) { // Обрабатываем все события
  61.                 eDoors doorId = static_cast<eDoors>(i);
  62.                 DoorEvent &event = VehDoors.Get(automobile).events[doorId];
  63.                 if (event.m_active) { // Если событие активно
  64.                     if (event.m_type == DOOR_EVENT_OPEN) {
  65.                         event.m_openingState += ACTION_TIME_STEP;
  66.                         if (event.m_openingState > 1.0f) { // Если полностью открыли
  67.                             event.m_active = false; // Отключаем обработку
  68.                             automobile->OpenDoor(0, componentByDoorId[doorId], doorId, 1.0f, true); // Полностью открываем
  69.                             event.m_openingState = 1.0f;
  70.                         }
  71.                         else
  72.                             automobile->OpenDoor(0, componentByDoorId[doorId], doorId, event.m_openingState, true);
  73.                     }
  74.                     else {
  75.                         event.m_openingState -= ACTION_TIME_STEP;
  76.                         if (event.m_openingState < 0.0f) { // Если полностью открыли
  77.                             event.m_active = false; // Отключаем обработку
  78.                             automobile->OpenDoor(0, componentByDoorId[doorId], doorId, 0.0f, true); // Полностью открываем
  79.                             event.m_openingState = 0.0f;
  80.                         }
  81.                         else
  82.                             automobile->OpenDoor(0, componentByDoorId[doorId], doorId, event.m_openingState, true);
  83.                     }
  84.                 }
  85.             }
  86.         }
  87.     }
  88.  
  89.     static void MainProcess() { // Обработка нажатия клавиш и запуск событий
  90.         if (CTimer::m_snTimeInMilliseconds > (m_nLastTimeWhenAnyActionWasEnabled + TIME_FOR_KEYPRESS)) { // если прошло 500 мс с того времени, как мы начали открывать/закрывать что-то
  91.             CVehicle *vehicle = FindPlayerVehicle(0, false);
  92.             if (vehicle && vehicle->m_dwVehicleClass == VEHICLE_AUTOMOBILE) {
  93.                 CAutomobile *automobile = reinterpret_cast<CAutomobile *>(vehicle); // опять же, приведение типов. Т.к. мы будет юзать damageManager, нам нужно убедиться, что транспорт - это автомобиль (CAutomobile)
  94.                 if (KeyPressed(219)) // [
  95.                     EnableDoorEvent(automobile, BONNET); // капот
  96.                 else if (KeyPressed(221)) // ]
  97.                     EnableDoorEvent(automobile, BOOT); // багажник
  98.                 else if (KeyPressed(186) && KeyPressed(187)) // ; =
  99.                     EnableDoorEvent(automobile, DOOR_FRONT_LEFT); // левая передняя дверь
  100.                 else if (KeyPressed(222) && KeyPressed(187)) // ' =
  101.                     EnableDoorEvent(automobile, DOOR_FRONT_RIGHT); // правая передняя дверь
  102.                 else if (KeyPressed(186) && KeyPressed(189)) // ; -
  103.                     EnableDoorEvent(automobile, DOOR_REAR_LEFT); // левая задняя дверь
  104.                 else if (KeyPressed(222) && KeyPressed(189)) // ' -
  105.                     EnableDoorEvent(automobile, DOOR_REAR_RIGHT); // правая задняя дверь
  106.             }
  107.         }
  108.     }
  109.  
  110.     DoorsExample() {
  111.         Events::gameProcessEvent += MainProcess; // Тут обрабатываем нажатия и запускаем события
  112.         Events::vehicleRenderEvent += ProcessDoors; // Тут обрабатываем события, а также выключаем их
  113.     }
  114. } example;
  115.  
  116. int DoorsExample::componentByDoorId[6] = { CAR_BONNET, CAR_BOOT, CAR_DOOR_LF, CAR_DOOR_RF, CAR_DOOR_LR, CAR_DOOR_RR };
  117. int DoorsExample::m_nLastTimeWhenAnyActionWasEnabled = 0;
  118. VehicleExtendedData<DoorsExample::VehicleDoors> DoorsExample::VehDoors;
В этом примере процессы обработки нажатия клавиш и открытия/закрытия компонентов разделены.
Plugin-SDK https://github.com/DK22Pac/plugin-sdk

Discord-сервер по plugin-sdk и программированию в GTA
RU https://discord.gg/QEesDGb
ENG https://discord.gg/zaVqFQv

Оффлайн kenking

  • Новичок
  • **
  • Сообщений: 237
  • Репутация: +16/-0
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #52 : Август 12, 2016, 03:58:18 pm »
В этом примере процессы обработки нажатия клавиш и открытия/закрытия компонентов разделены.
Круто! Спасибо за пример.

Вопросы:
1) Названия классов могут совпадать в разных плагинах? Никакого конфликта не будет? Или всё, что делается в одном плагине остальных никак не касается?
2) В конце кода это зачем?
Код: C++
  1. int DoorsExample::componentByDoorId[6] = { CAR_BONNET, CAR_BOOT, CAR_DOOR_LF, CAR_DOOR_RF, CAR_DOOR_LR, CAR_DOOR_RR };
  2. int DoorsExample::m_nLastTimeWhenAnyActionWasEnabled = 0;
  3. VehicleExtendedData<DoorsExample::VehicleDoors> DoorsExample::VehDoors;
3) В примере спаунера транспорта https://github.com/DK22Pac/plugin-sdk/blob/master/examples/SA_VehicleSpawner/Main.cpp при вызове модели типа train происходит вылет игры. Видимо эта функция не предназначена для работы с таким типом транспорта.

Прошло две недели с начала моего обучения, пора показать первый результат. Переписал скрипт Дениса Car Spawner 3 на С++. Посмотри пожалуйста. Можно ли, что-то улучшить, оптимизировать?
Код клео
{$CLEO}  // 0 - 48 , 9 -57
var
    0@: array 5 of Integer  // digits     0 1 2 3 4
    17@: Integer             // index
    6@: Integer             // current_digit
    7@: Integer             // SUM
    8@: Integer             // KEY_HOLD
    15@: Integer             // KEY
end
const
    DIGIT = 0@
    i = 17@
    CURRENT_DIGIT = 6@
    SUM = 7@
    KEY_HOLD = 8@
    KEY = 15@
end
CURRENT_DIGIT = 1
while true
    wait 0
    if or
        not player.Defined($PLAYER_CHAR)
        $ONMISSION <> 0
    then   
        CURRENT_DIGIT = 1
        SUM = 0
        KEY_HOLD = False
        for i = 0 to 4
            0006: DIGIT[i] = 0 
        end
        continue
    end
    if
        8AB0: not key_pressed 2
    then
        if and
            SUM > 0
            SUM < 19010
        then
            if
                07DE: model SUM exists // versionB
            then
                0AA7: call_function 0x4C5AA0 num_params 1 pop 1 SUM 9@ // isModelCar
                0AA7: call_function 0x4C5B60 num_params 1 pop 1 SUM 10@ // isModelBike
                0AA7: call_function 0x4C5C20 num_params 1 pop 1 SUM 11@ // isModelBmx
                0AA7: call_function 0x4C5BF0 num_params 1 pop 1 SUM 12@ // isModelQuad
                0AA7: call_function 0x4C5C50 num_params 1 pop 1 SUM 13@ // isModelTrailer
                0AA7: call_function 0x4C5BC0 num_params 1 pop 1 SUM 14@ // isModelMTruck
                if or
                    081E:   model SUM boat
                    081F:   model SUM plane
                    0820:   model SUM heli
                    9@ == True
                then
                    0ADD: spawn_car_with_model SUM at_player_location
                else
                    if or
                        10@ == 8766721 // True
                        11@ == 8766721 // True
                        12@ == 8766721 // True
                        13@ == 8766721 // True
                        14@ == True
                    then
                        0ADD: spawn_car_with_model SUM at_player_location
                    end
                end
            end
        end
        CURRENT_DIGIT = 1
        SUM = 0
        KEY_HOLD = False
        for i = 0 to 4
            0006: DIGIT[i] = 0 
        end
        continue
    end    // key pressed 2
    if
        KEY_HOLD == False
    then
        if
            0AB0: key_pressed 8
        then
            KEY_HOLD = True
            if
                CURRENT_DIGIT > 1
            then
                CURRENT_DIGIT /= 10
                008F: 16@ = integer CURRENT_DIGIT to_float
                0AEF: 16@ = log 16@ base 10.0 //all floats
                SUM = 0
                0092: 16@ = float 16@ to_integer
                16@ -= 1
                for i = 0 to 16@
                    0016: DIGIT[i] /= 10
                    005A: SUM += DIGIT[i] // (int)
                end
            end
        else
            if
                SUM >= 19010
            then
                continue
            end
            KEY = 48
            for i = 0 to 9
                if
                    0AB0: key_pressed KEY
                then
                    008F: 16@ = integer CURRENT_DIGIT to_float
                    0AEF: 16@ = log 16@ base 10.0 //all floats
                    SUM = 0
                    0092: 16@ = float 16@ to_integer
                    0085: DIGIT[16@] = i   // int
                    for i = 0 to 16@
                        if
                            001D:   16@ > i // (int)
                        then
                            0012: DIGIT[i] *= 10
                        end
                        005A: SUM += DIGIT[i] // (int)
                    end
                    CURRENT_DIGIT *= 10
                    KEY_HOLD = True
                    break
                end
                KEY += 1
            end
        end
    else  //  KEY_HOLD == True
        if and
            8AB0: not key_pressed 48
            8AB0: not key_pressed 49
            8AB0: not key_pressed 50
            8AB0: not key_pressed 51
            8AB0: not key_pressed 52
        then
            if and
                8AB0: not key_pressed 53
                8AB0: not key_pressed 54
                8AB0: not key_pressed 55
                8AB0: not key_pressed 56
                8AB0: not key_pressed 57
                8AB0: not key_pressed 8
            then
                KEY_HOLD = False
            end
        end
    end  // KEY_HOLD == False
    if
        CURRENT_DIGIT > 1
    then
        03F0: enable_text_draw 1
        045A: draw_text_1number 320.0 240.0 GXT 'NUMBER' number SUM
    end
end

Код: C++
  1. #include <plugin.h>
  2. #include "game_sa\common.h"
  3. #include "game_sa\CFont.h"
  4. #include "game_sa\CText.h"
  5. #include "game_sa\CCheat.h"
  6. #include "game_sa\eModelID.h"
  7. #include "game_sa\CModelInfo.h"
  8.  
  9. // originally made by Den_spb
  10.  
  11. using namespace plugin;
  12.  
  13. class MoreVehiclesSpawner {
  14. public:
  15.     MoreVehiclesSpawner() {
  16.     static int digits[] = { 0, 0, 0, 0, 0 };
  17.     static int current_digit = 1;
  18.     static unsigned int sum = 0;
  19.     static bool key_hold = false;
  20.     static int key = 48;
  21.     static int i_16 = 0;
  22.     static float f_16 = 0.0f;
  23.  
  24.         Events::drawingEvent += [] {
  25.             CPed *playa = FindPlayerPed();
  26.             if (playa && playa->IsAlive()) {
  27.                 if (KeyPressed(2)) {
  28.                     // вывод на экран
  29.                     if (current_digit > 1) {
  30.                         CFont::SetScale(0.5f, 1.0f);
  31.                         CFont::SetColor(CRGBA(255, 255, 255, 255));
  32.                         CFont::SetAlignment(ALIGN_LEFT);
  33.                         CFont::SetOutlinePosition(1);
  34.                         CFont::SetDropColor(CRGBA(0, 0, 0, 255));
  35.                         CFont::SetBackground(false, false);
  36.                         CFont::SetFontStyle(FONT_SUBTITLES);
  37.                         CFont::SetProp(true);
  38.                         CFont::SetWrapx(600.0f);
  39.                         char text[16];
  40.                         sprintf(text, "%d", sum);
  41.                         CFont::PrintString(300.0f, 10.0f, text);
  42.                     }
  43.                     if (key_hold == false) {
  44.                         // корректировка цифр
  45.                         if (KeyPressed(8)) {
  46.                             key_hold = true;
  47.                             if (current_digit > 1) {
  48.                                 current_digit /= 10;
  49.                                 f_16 = log10(static_cast<float>(current_digit));
  50.                                 sum = 0;
  51.                                 i_16 = static_cast<int>(f_16);
  52.                                 i_16 -= 1;
  53.                                 for (int i = 0; i <= i_16; i++) {
  54.                                     digits[i] /= 10;
  55.                                     sum += digits[i];
  56.                                 }
  57.                             }
  58.                         }
  59.                         // набор цифр
  60.                         else {
  61.                             if (sum >= 19010) {
  62.                                 sum = 0;
  63.                                 current_digit = 1;
  64.                                 key_hold = false;
  65.                                 for (int i = 0; i <= 4; i++) {
  66.                                     digits[i] = 0;
  67.                                 }
  68.                             }
  69.                             key = 48;
  70.                             for (int i = 0; i <= 9; i++) {
  71.                                 if (KeyPressed(key)) {
  72.                                     f_16 = log10(static_cast<float>(current_digit));
  73.                                     sum = 0;
  74.                                     i_16 = static_cast<int>(f_16);
  75.                                     digits[i_16] = i;
  76.                                     for (int i = 0; i <= i_16; i++) {
  77.                                         if (i_16 > i)
  78.                                             digits[i] *= 10;
  79.                                         sum += digits[i];
  80.                                     }
  81.                                     current_digit *= 10;
  82.                                     key_hold = true;
  83.                                     break;
  84.                                 }
  85.                                 key++;
  86.                             }
  87.                         }
  88.                     }
  89.                     else {
  90.                         if (!KeyPressed(8) && !KeyPressed(48) && !KeyPressed(49)
  91.                             && !KeyPressed(50) && !KeyPressed(51) && !KeyPressed(52)
  92.                             && !KeyPressed(53) && !KeyPressed(54) && !KeyPressed(55)
  93.                             && !KeyPressed(56) && !KeyPressed(57))
  94.                             key_hold = false;
  95.                     }
  96.                 }
  97.                 // спавн транспорта
  98.                 else {
  99.                     if (sum > 0 && sum < 19010) {
  100.                         CVehicleModelInfo *typModel = reinterpret_cast<CVehicleModelInfo *>(CModelInfo::IsVehicleModelType(sum));
  101.                         if (reinterpret_cast<int>(typModel) != -1
  102.                             && reinterpret_cast<int>(typModel) != 6)
  103.                             CCheat::VehicleCheat(sum);
  104.                     }
  105.                                                
  106.                     sum = 0;
  107.                     current_digit = 1;
  108.                     key_hold = false;
  109.                     for (int i = 0; i <= 4; i++) {
  110.                         digits[i] = 0;
  111.                     }
  112.                 }
  113.             }
  114.         };
  115.     }
  116. } moreVehiclesSpawner;
  117.  
   

Оффлайн DK

  • Новичок
  • **
  • Сообщений: 234
  • Репутация: +328/-0
    • dk22pac
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #53 : Август 12, 2016, 08:03:45 pm »
Цитировать
Круто! Спасибо за пример.
Пример не самый простой получился... Для полного понимания нужно ознакомиться с понятиями
-класса
-конструктора класса
-ссылками
в С++.
В plugin-sdk реализована возможность "прицепить" свой класс к классу транспорта (CVehicle).
Для наглядности я сделал ещё один пример.
Код: C++
  1. #include <plugin.h>
  2. #include "game_sa\CSprite.h"
  3. #include "game_sa\CFont.h"
  4.  
  5. using namespace plugin;
  6.  
  7. class DistanceExample {
  8. public:
  9.     class DistanceInfo {
  10.     public:
  11.         // данные нашего класса
  12.         float distance; // пройденная дистанция
  13.         bool startedPositionRecording; // если уже начали отслеживать позицию
  14.         CVector lastPosition; // последняя позиция
  15.  
  16.         DistanceInfo(CVehicle *vehicle) {
  17.             // Контсруктор нашего класса. Должен быть обязательно обьявлен. Принимает один параметр -
  18.             // транспорт, к которому "прицепляется" этот класс.
  19.             // Этот конструктор будет вызван на этапе конструирования CVehicle (CVehicle::CVehicle).
  20.             // При этом, обращаться к каким-либо членам CVehicle запрещено (обьект CVehicle ещё не построен до конца).
  21.             distance = 0.0f; // устанавливаем начальное значение
  22.             startedPositionRecording = false;
  23.         }
  24.     };
  25.  
  26.     DistanceExample() {
  27.         static VehicleExtendedData<DistanceInfo> VehDistance; // Наше расширение
  28.  
  29.         Events::vehicleRenderEvent += [](CVehicle *vehicle) {
  30.             DistanceInfo &info = VehDistance.Get(vehicle); // Получаем наши данные для этого транспорта
  31.             if (info.startedPositionRecording) // если info.lastPosition не "пустой"
  32.                 info.distance += DistanceBetweenPoints(info.lastPosition, vehicle->GetPosition()); // добавляем расстояние
  33.             else
  34.                 info.startedPositionRecording = true;
  35.             info.lastPosition = vehicle->GetPosition();
  36.         };
  37.  
  38.         Events::drawingEvent += [] {
  39.             for (int i = 0; i < CPools::ms_pVehiclePool->m_Size; i++) {
  40.                 CVehicle *vehicle = CPools::ms_pVehiclePool->GetAt(i);
  41.                 if (vehicle && vehicle->GetIsOnScreen()) {
  42.                     CVector &posn = vehicle->GetPosition();
  43.                     RwV3d rwp = { posn.x, posn.y, posn.z + 1.0f };
  44.                     RwV3d screenCoors; float w, h;
  45.                     if (CSprite::CalcScreenCoors(rwp, &screenCoors, &w, &h, true, true) && w > 10.0f) {
  46.                         CFont::SetAlignment(ALIGN_CENTER);
  47.                         CFont::SetColor(CRGBA(255, 255, 255, 255));
  48.                         CFont::SetOutlinePosition(1);
  49.                         CFont::SetDropColor(CRGBA(0, 0, 0, 255));
  50.                         CFont::SetBackground(false, false);
  51.                         CFont::SetCentreSize(300.0f);
  52.                         CFont::SetScale(w * 0.015f, h * 0.03f);
  53.                         CFont::SetFontStyle(FONT_PRICEDOWN);
  54.                         CFont::SetProp(true);
  55.                         static char text[16];
  56.                         sprintf(text, "%.2f", VehDistance.Get(vehicle).distance);
  57.                         CFont::PrintString(screenCoors.x, screenCoors.y, text);
  58.                     }
  59.                 }
  60.             }
  61.         };
  62.     }
  63. } example;
Цитировать
1) Названия классов могут совпадать в разных плагинах? Никакого конфликта не будет? Или всё, что делается в одном плагине остальных никак не касается?
Да, можешь использовать одинаковые названия.
Цитировать
2) В конце кода это зачем?
Эти переменные были обьявлены как статические внутри класса.
Такие переменные надо дополнительно определить за пределами класса, в глобальной области видимости.
https://msdn.microsoft.com/ru-ru/library/b1b5y48f.aspx
Код: C++
  1. #include <plugin.h>
  2. #include "game_sa\CMessages.h"
  3.  
  4. using namespace plugin;
  5.  
  6. class ShowValue {
  7. public:
  8.     static unsigned int value; // Переменная обьявлена внутри класса.
  9.                                // Внутри класса мы можем обращаться к этой переменной вот так: value
  10.  
  11.     ShowValue() {
  12.         Events::gameProcessEvent += [] {
  13.             CMessages::AddMessageJumpQWithNumber("~1~", 100, 0, value, -1, -1, -1, -1, -1, false);
  14.         };
  15.     }
  16. } example;
  17.  
  18. unsigned int ShowValue::value = 12345; // А определяем в глобальной области.
  19.                                        // За пределами класса мы можем обращаться к этой переменной
  20.                                        // вот так: ShowValue::value
  21.  
  22. class AnotherClass {
  23. public:
  24.     AnotherClass() {
  25.         Events::gameProcessEvent += [] {
  26.             CMessages::AddBigMessageWithNumberQ("~1~", 100, 0, ShowValue::value, -1, -1, -1, -1, -1);
  27.         };
  28.     }
  29. } another;
Вообще, это всё - на твоё личное усмотрение :) Мне просто удобнее так - когда всё "упаковано" в класс, а конкретные переменные ассоциируются с конкретным классом.
Никто не запрещает обьявлять глобальные переменные вне класса.
Код: C++
  1. #include <plugin.h>
  2. #include "game_sa\CMessages.h"
  3.  
  4. using namespace plugin;
  5.  
  6. // Переменная обьявлена и определена в глобальной области.
  7. // Мы можем использовать эту переменную где угодно.
  8. unsigned int value = 12345;
  9.  
  10. class ShowValue {
  11. public:
  12.     ShowValue() {
  13.         Events::gameProcessEvent += [] {
  14.             CMessages::AddMessageJumpQWithNumber("~1~", 100, 0, value, -1, -1, -1, -1, -1, false);
  15.         };
  16.     }
  17. } example;
Можно обьявлять статические переменные и в теле функций. Такие переменные не нужно определять вне класса. Но обращаться к таким переменным можно только внутри функции (в которой они обьявлены)!
Код: C++
  1. #include <plugin.h>
  2. #include "game_sa\CMessages.h"
  3.  
  4. using namespace plugin;
  5.  
  6. class ShowValue {
  7. public:
  8.     ShowValue() {
  9.         static unsigned int value = 12345; // Переменная обьявлена и определена внутри конструктора.
  10.                                            // Мы можем обращаться к этой переменной только в конструкторе.
  11.  
  12.         Events::gameProcessEvent += [] { // Здесь мы используем лямба-выражение - формируем функцию "на лету"
  13.             CMessages::AddMessageJumpQWithNumber("~1~", 100, 0, value, -1, -1, -1, -1, -1, false);
  14.         };
  15.     }
  16.  
  17.     static void MyDrawingFunction() {
  18.         // а вот тут мы уже не можем получить доступ к value
  19.     }
  20. } example;

По коду Den_spb могу точно сказать, что здесь у тебя что-то не так:
Код: C++
  1. CVehicleModelInfo *typModel = reinterpret_cast<CVehicleModelInfo *>(CModelInfo::IsVehicleModelType(sum));
  2. if (reinterpret_cast<int>(typModel) != -1
  3.     && reinterpret_cast<int>(typModel) != 6)
CModelInfo::IsVehicleModelType возвращает boolean (true/false), зачем переводить его в VehicleModelInfo, и сравнивать с -1 и 6 - непонятно.
Plugin-SDK https://github.com/DK22Pac/plugin-sdk

Discord-сервер по plugin-sdk и программированию в GTA
RU https://discord.gg/QEesDGb
ENG https://discord.gg/zaVqFQv

Оффлайн kenking

  • Новичок
  • **
  • Сообщений: 237
  • Репутация: +16/-0
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #54 : Август 13, 2016, 08:02:44 am »
Спасибо за очередной пример и пояснения!

Цитировать
CModelInfo::IsVehicleModelType возвращает boolean (true/false), зачем переводить его в VehicleModelInfo, и сравнивать с -1 и 6 - непонятно.
После перевода получается тип модели (0=car, 9=bike, 11=trailer и т.д.). А если модель не является транспортом или вообще нет такой модели, то получается значение -1, а 6 - это модель типа train. Вот я и проверяю, что набранное число - это ID модели транспорта и при этом не является моделью train. Потому как, если модель train, то происходит вылет игры. По другому я не придумал, как проверить. Денис проверял, что модель есть (в моём коде сравнение с -1) и она является любой из моделей, кроме train (в моём коде сравнение с 6). Так-то плагин в работе я проверил, всё работает. Просто по самому коду - может, где-то можно, что-то оптимизировать?
                if
                07DE: model SUM exists // versionB
            then
                0AA7: call_function 0x4C5AA0 num_params 1 pop 1 SUM 9@ // isModelCar
                0AA7: call_function 0x4C5B60 num_params 1 pop 1 SUM 10@ // isModelBike
                0AA7: call_function 0x4C5C20 num_params 1 pop 1 SUM 11@ // isModelBmx
                0AA7: call_function 0x4C5BF0 num_params 1 pop 1 SUM 12@ // isModelQuad
                0AA7: call_function 0x4C5C50 num_params 1 pop 1 SUM 13@ // isModelTrailer
                0AA7: call_function 0x4C5BC0 num_params 1 pop 1 SUM 14@ // isModelMTruck
                if or
                    081E:   model SUM boat
                    081F:   model SUM plane
                    0820:   model SUM heli
                    9@ == True
                then
                    0ADD: spawn_car_with_model SUM at_player_location
                else
                    if or
                        10@ == 8766721 // True
                        11@ == 8766721 // True
                        12@ == 8766721 // True
                        13@ == 8766721 // True
                        14@ == True
                    then
                        0ADD: spawn_car_with_model SUM at_player_location
                    end
                end
            end


UPD:
Что-то я действительно намудрил.  :)
CModelInfo::IsVehicleModelType(sum) как раз и вернёт тип модели (0=car, 9=bike, 11=trailer и т.д.). А если модель не является транспортом или вообще нет такой модели, то получается значение -1.
Вот так надо:
Код: C++
  1. if (CModelInfo::IsVehicleModelType(sum) != -1 && CModelInfo::IsVehicleModelType(sum) != 6)
« Последнее редактирование: Август 13, 2016, 03:39:06 pm от kenking »

Оффлайн DK

  • Новичок
  • **
  • Сообщений: 234
  • Репутация: +328/-0
    • dk22pac
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #55 : Август 13, 2016, 06:23:22 pm »
Да, это я не досмотрел.
CModelInfo::IsVehicleModelType возвращает тип модели транспорта.
Но переводить его CVehicleModelInfo* не надо.
Код: C++
  1. if (sum > 0 && sum < 19010) {
  2.     int modelType = CModelInfo::IsVehicleModelType(sum);
  3.     if (modelType != -1 && modelType != 6)
  4.         CCheat::VehicleCheat(sum);
  5. }
Цитировать
Видимо эта функция не предназначена для работы с таким типом транспорта.

Действительно, в CCheat::VehicleCheat (которая также используется в опкоде 0ADD), нет варианта с созданием поезда. Вместо этого можно поэкспериментировать с функцией CTrain::CreateMissionTrain.
По поводу скрипта Дениса:
Я бы вообще переделал обработку набора числа.
В C++ можно сделать всё намного "чище".

KeyCheck.h
Код: C++
  1. #pragma once
  2.  
  3. class KeyCheck {
  4.     static unsigned char currStates[256];
  5.     static unsigned char prevStates[256];
  6.     static unsigned int timeDelayPressed[256];
  7. public:
  8.     static void Update(); // апдейт клавиш. Нужно вызывать эту функцию один раз за фрейм, перед тем, как проверять клавиши
  9.  
  10.     static bool Check(unsigned int key); // Проверить, нажата ли сейчас клавиша
  11.     static bool CheckJustDown(unsigned int key); // Проверить, была ли нажата клавиша прямо сейчас
  12.     static bool CheckJustUp(unsigned int key); // Проверить, была ли отпущена клавиша прямо сейчас
  13.     static bool CheckWithDelay(unsigned int key, unsigned int time); // Проверить нажата ли клавиша, с интервалом проверки
  14. };

KeyCheck.cpp
Код: C++
  1. #include "KeyCheck.h"
  2. #include "plugin.h"
  3. #include "game_sa\CTimer.h"
  4.  
  5. unsigned char KeyCheck::currStates[256] = {};
  6. unsigned char KeyCheck::prevStates[256] = {};
  7. unsigned int KeyCheck::timeDelayPressed[256] = {};
  8.  
  9. void KeyCheck::Update() {
  10.     memcpy(prevStates, currStates, 256);
  11.     GetKeyboardState(currStates);
  12. }
  13.  
  14. bool KeyCheck::Check(unsigned int key) {
  15.     return key < 256 && (currStates[key] & 0x80);
  16. }
  17.  
  18. bool KeyCheck::CheckJustDown(unsigned int key) {
  19.     return key < 256 && (currStates[key] & 0x80) && !(prevStates[key] & 0x80);
  20. }
  21.  
  22. bool KeyCheck::CheckJustUp(unsigned int key) {
  23.     return key < 256 && !(currStates[key] & 0x80) && (prevStates[key] & 0x80);
  24. }
  25.  
  26. bool KeyCheck::CheckWithDelay(unsigned int key, unsigned int time) {
  27.     if (key < 256 && (currStates[key] & 0x80)) {
  28.         if (!(prevStates[key] & 0x80) || CTimer::m_snTimeInMilliseconds >(timeDelayPressed[key] + time)) {
  29.             timeDelayPressed[key] = CTimer::m_snTimeInMilliseconds;
  30.             return true;
  31.         }
  32.     }
  33.     return false;
  34. }

Main.cpp
Код: C++
  1. #include <string>
  2. #include "plugin.h"
  3. #include "KeyCheck.h"
  4. #include "game_sa\common.h"
  5. #include "game_sa\CModelInfo.h"
  6. #include "game_sa\CCheat.h"
  7. #include "game_sa\CTimer.h"
  8. #include "game_sa\CFont.h"
  9. #include "game_sa\CSprite2d.h"
  10.  
  11. using namespace plugin;
  12.  
  13. class MoreVehiclesSpawner {
  14. public:
  15.     static std::string typedBuffer;
  16.     static std::string errorMessage;
  17.     static std::string errorMessageBuffer;
  18.     static unsigned int errorMessageTimer;
  19.     static bool enabled;
  20.  
  21.     static void ReportAudioEvent(int audioEventId, float volume, float speed) { // с классами, связанными со звуком, в sdk пока что не очень
  22.         CallMethod<NoRet, 0x506EA0, unsigned int, int, float, float>(0xB6BC90, audioEventId, volume, speed);
  23.     }
  24.  
  25.     static void Update() {
  26.         KeyCheck::Update(); // апдейтим состояния клавиш
  27.         if (FindPlayerPed() && FindPlayerPed()->IsAlive()) {
  28.             if (KeyCheck::CheckJustDown(VK_TAB)) { // Если нажата Tab - включаем или выключаем консоль
  29.                 enabled = !enabled;
  30.                 typedBuffer.clear();
  31.                 errorMessageBuffer.clear();
  32.             }
  33.             if (enabled) {
  34.                 errorMessage.clear();
  35.                 if (KeyCheck::CheckWithDelay(VK_BACK, 200)) { // Если нажат Backspace - убираем последний символ в строке
  36.                     if (typedBuffer.size() > 0) {
  37.                         typedBuffer.pop_back();
  38.                         ReportAudioEvent(3, 0.0f, 1.0f);
  39.                     }
  40.                 }
  41.                 else {
  42.                     for (int i = 0; i <= 9; i++) {
  43.                         if (KeyCheck::CheckWithDelay(i + 48, 200)) {
  44.                             if (typedBuffer.size() == 5)
  45.                                 errorMessage = "Too many digits!";
  46.                             else {
  47.                                 typedBuffer.push_back(i + 48); // Добавляем символ в конец строки
  48.                                 ReportAudioEvent(3, 0.0f, 1.0f);
  49.                             }
  50.                             break;
  51.                         }
  52.                     }
  53.                 }
  54.                 if (KeyCheck::CheckJustDown(VK_RETURN)) { // Если нажата Return - спавним транспорт
  55.                     if (typedBuffer.size() > 0) {
  56.                         unsigned int modelId = std::stoi(typedBuffer);
  57.                         if (modelId < 19010) {
  58.                             int modelType = CModelInfo::IsVehicleModelType(modelId);
  59.                             if (modelType != -1) {
  60.                                 if (modelType != 6) {
  61.                                     CCheat::VehicleCheat(modelId);
  62.                                     ReportAudioEvent(12, 0.0f, 1.0f);
  63.                                     errorMessageBuffer.clear(); // убираем надпись об ошибке (если она была на экране)
  64.                                 }
  65.                                 else
  66.                                     errorMessage = "Can't spawn a train model";
  67.                             }
  68.                             else
  69.                                 errorMessage = "This model is not a vehicle!";
  70.                         }
  71.                         else
  72.                             errorMessage = "ID is too big!";
  73.                     }
  74.                     else
  75.                         errorMessage = "Please enter model Id!";
  76.                 }
  77.             }
  78.         }
  79.         else
  80.             enabled = false;
  81.     }
  82.  
  83.     static void Render() {
  84.         if (enabled) {
  85.             CSprite2d::DrawRect(CRect(100.0f, 100.0f, 470.0f, 200.0f), CRGBA(0, 0, 0, 100));
  86.             CSprite2d::DrawRect(CRect(250.0f, 140.0f, 340.0f, 142.0f), CRGBA(255, 255, 255, 255));
  87.             CFont::SetScale(0.8f, 1.9f);
  88.             CFont::SetColor(CRGBA(255, 255, 255, 255));
  89.             CFont::SetAlignment(ALIGN_LEFT);
  90.             CFont::SetOutlinePosition(0);
  91.             CFont::SetBackground(false, false);
  92.             CFont::SetFontStyle(FONT_SUBTITLES);
  93.             CFont::SetProp(true);
  94.             CFont::SetWrapx(600.0f);
  95.             CFont::PrintString(105.0f, 105.0f, "Model ID:");
  96.             if (typedBuffer.size() > 0)
  97.                 CFont::PrintString(250.0f, 105.0f, const_cast<char*>(typedBuffer.c_str()));
  98.             if (errorMessage.size() > 0) {
  99.                 errorMessageBuffer = errorMessage;
  100.                 errorMessageTimer = CTimer::m_snTimeInMilliseconds;
  101.                 ReportAudioEvent(4, 0.0f, 1.0f);
  102.             }
  103.             if (errorMessageBuffer.size() > 0 && CTimer::m_snTimeInMilliseconds < (errorMessageTimer + 2000)) {
  104.                 CFont::SetColor(CRGBA(255, 0, 0, 255));
  105.                 CFont::PrintString(105.0f, 150.0f, const_cast<char*>(errorMessageBuffer.c_str()));
  106.             }
  107.         }
  108.     }
  109.  
  110.     MoreVehiclesSpawner() {
  111.         Events::gameProcessEvent += Update;
  112.         Events::drawingEvent += Render;
  113.     };
  114. } moreVehiclesSpawner;
  115.  
  116. std::string MoreVehiclesSpawner::typedBuffer;
  117. std::string MoreVehiclesSpawner::errorMessage;
  118. std::string MoreVehiclesSpawner::errorMessageBuffer;
  119. unsigned int MoreVehiclesSpawner::errorMessageTimer = 0;
  120. bool MoreVehiclesSpawner::enabled = false;

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

Для прямой работы с памятью в sdk есть класс patch.
Для подмены функций можно использовать:
plugin::patch::RedirectCall(адрес_места_вызова, своя_функция);
plugin::patch::RedirectJump(адрес_функции, своя_функция);
При этом, новая функция должна совпадать с оригинальной - параметрами и соглашением вызова.
Выбор способа патча зависит от ситуации.
В большинстве случаев, если нужно полностью заменить тело функции, я использую RedirectJump.

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



Вот что получилось:
Код: C++
  1. #include "plugin.h"
  2. #include "game_sa\CStreaming.h"
  3. #include "game_sa\CCheat.h"
  4. #include "game_sa\CTrailer.h"
  5. #include "game_sa\CWorld.h"
  6.  
  7. using namespace plugin;
  8.  
  9. unsigned int truckModelIds[3] = { MODEL_LINERUN, MODEL_PETRO, MODEL_RDTRAIN };
  10. unsigned int trailerModelIds[4] = { MODEL_ARTICT1, MODEL_ARTICT2, MODEL_PETROTR, MODEL_ARTICT3 };
  11.  
  12. class SpawnTruckCheat {
  13. public:
  14.     static void Spawn() {
  15.         CVehicle *vehicle = CCheat::VehicleCheat(truckModelIds[rand() % 3]); // спавним тягач
  16.         if (vehicle) {
  17.             unsigned int modelId = trailerModelIds[rand() % 4]; // получаем случайный ID
  18.             CStreaming::RequestModel(modelId, 0); // 0 - значит, что игра может удалить эту модель когда она больше нигде не будет нужна
  19.             CStreaming::LoadAllRequestedModels(false);
  20.             if (CStreaming::ms_aInfoForModel[modelId].m_loadState == LOADSTATE_LOADED) {
  21.                 CTrailer *trailer = new CTrailer(modelId, 1); // создаём прицеп //--+
  22.                 if (trailer) {                                                  //  | По сути, функция CCheat::VehicleCheat
  23.                     trailer->SetPosn(vehicle->GetPosition());                   //  | делает всё то же самое - создаёт обьект
  24.                     trailer->SetOrientation(0.0f, 0.0f, 3.4906585f);            //  | CAutomobile/CBike/CBoat/CHeli и т.д.,
  25.                     trailer->m_nStatus = 4; // что это за статус - без понятия  //  | устанавливает ему позицию и вращение,
  26.                     CWorld::Add(trailer);                                       //  | добавляет в "мир", и т.д.
  27.                     trailer->SetTowLink(vehicle, true);                         //--+
  28.                 }
  29.             }
  30.         }
  31.     }
  32.    
  33.     SpawnTruckCheat() {
  34.         patch::RedirectJump(0x43A570, Spawn);
  35.     };
  36. } spawnTruck;
« Последнее редактирование: Август 14, 2016, 12:42:39 am от DK »
Plugin-SDK https://github.com/DK22Pac/plugin-sdk

Discord-сервер по plugin-sdk и программированию в GTA
RU https://discord.gg/QEesDGb
ENG https://discord.gg/zaVqFQv

Оффлайн kenking

  • Новичок
  • **
  • Сообщений: 237
  • Репутация: +16/-0
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #56 : Август 14, 2016, 11:13:48 am »
Цитировать
KeyCheck.h
KeyCheck.cpp
Добавил в \plugin_sa\game_sa или надо было в другое место добавлять?

Цитировать
По поводу скрипта Дениса:
Я бы вообще переделал обработку набора числа.
В C++ можно сделать всё намного "чище".
Ну да, так лучше, чем в моём варианте.  :)

Цитировать
Вот что получилось:
Класс! Спасибо за пример и пояснения! Есть один момент - при повторном спауне первый тягач благополучно удаляется, а вот первый прицеп нет и получается ко второму тягачу цепляются два прицепа, через некоторое время они взрываются. Для прицепа нужен RemoveReferences. Как добавить?

И ещё сразу по прицепам есть вопрос - можно ли в трафике в процессе создания транспорта проверить, если модель определённого ID (тягач), то цеплять к нему прицеп. Цеплять не на весь транспорт с таким ID, а выборочно, ну скажем через определённое время? Цеплять разные прицепы?


Оффлайн DK

  • Новичок
  • **
  • Сообщений: 234
  • Репутация: +328/-0
    • dk22pac
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #57 : Август 14, 2016, 11:34:17 am »
Добавил в \plugin_sa\game_sa или надо было в другое место добавлять?

Добавляй в проект твоего плагина.


Есть один момент - при повторном спауне первый тягач благополучно удаляется, а вот первый прицеп нет и получается ко второму тягачу цепляются два прицепа, через некоторое время они взрываются. Для прицепа нужен RemoveReferences. Как добавить?

Тут нужен не RemoveReference.
В функции CCheat::VehicleCheat ещё вызывается CTheScripts::ClearSpaceForMissionEntity, которая удаляет все обьекты на месте спавна транспорта.
После
Код: C++
  1. trailer->SetTowLink(vehicle, true);
Добавь
Код: C++
  1. Call<NoRet, 0x486B00, CVector const&, CEntity *>(trailer->GetPosition(), trailer);
Класс CTheScripts я постараюсь добавить позже.

И ещё сразу по прицепам есть вопрос - можно ли в трафике в процессе создания транспорта проверить, если модель определённого ID (тягач), то цеплять к нему прицеп. Цеплять не на весь транспорт с таким ID, а выборочно, ну скажем через определённое время? Цеплять разные прицепы?

Можно, надо разбирать CCarCtrl::GenerateOneRandomCar.
Plugin-SDK https://github.com/DK22Pac/plugin-sdk

Discord-сервер по plugin-sdk и программированию в GTA
RU https://discord.gg/QEesDGb
ENG https://discord.gg/zaVqFQv

Оффлайн kenking

  • Новичок
  • **
  • Сообщений: 237
  • Репутация: +16/-0
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #58 : Август 14, 2016, 12:23:45 pm »
Цитировать
Добавь
Код: C++
  1. Call<NoRet, 0x486B00, CVector const&, CEntity *>(trailer->GetPosition(), trailer);
Теперь нормально, спасибо!

Цитировать
trailer->m_nStatus = 4; // что это за статус - без понятия
Проверил, с значениями 0-2 прицеп становится поверх тягача, 3 тоже, что и 4 получился результат.

Цитировать
Добавляй в проект твоего плагина.
Упс.. а я добавил в \plugin_sa\game_sa да ещё и коммит сделал...  поторопился... Убрать из \plugin_sa\game_sa и сделать новый коммит или ты сам исправишь?

Оффлайн DK

  • Новичок
  • **
  • Сообщений: 234
  • Репутация: +328/-0
    • dk22pac
    • Просмотр профиля
Re: Написание плагина. Настройка проекта
« Ответ #59 : Август 14, 2016, 12:54:03 pm »
Надо удалить файлы из папки, а также убрать из проекта (в Solution-explorer'e).
Можешь сделать, я позже буду коммиты делать.
Plugin-SDK https://github.com/DK22Pac/plugin-sdk

Discord-сервер по plugin-sdk и программированию в GTA
RU https://discord.gg/QEesDGb
ENG https://discord.gg/zaVqFQv