Хоть генератор и поддерживает SCM-функции, есть возможность добавления собственных команд. Сначала расскажу о них детальнее в теории.
Принцип работы
Если говорить о работе команд, то следует запомнить, что команды — это не SCM-функции! Следовательно работать и компилироваться они будут иначе. Если сравнивать их работу с Sanny Buidler, то они напоминают работу директивы{$include}: сначала создаётся отдельный файл с кодом, после чего они подключаются в нужном месте. Команды пишутся в похожем стиле, только вместо файлов код хранится в отдельном методе, а вместо директивы идёт вызов этого метода.
Главное отличие команды генератора от директивы SB в том, что команда может принимать параметры, что делает её намного гибче в плане разработки.
Как создать команду?
Давайте рассмотрим примеры как они пишутся. Лучшим решением на текущий момент — использовать статические методы внутри класса MAIN. Функции пишутся как обычные методы класса. Напишем простой пример такой функции:
publicpartialclassMAIN:Thread { // наша новая функция и её реализацияpublicstaticvoiddestroy_actor( Actor hActor ) {hActor.destroy(); }publicoverridevoidSTART( LabelJump label ) { }}// ...publicpartialclassMAIN {publicclassTEST:Thread { // класс "TEST" внутри класса "MAIN"Actor tempActor;publicoverridevoidSTART( LabelJump label ) {tempActor.create_random( 0.0,0.0,0.0 ); // ...destroy_actor( tempActor ); // вызов нашей функцииend_thread(); } }}
Давайте теперь напишем функции, которые регулируют трафик:
Использовать можно любые типы данных в качестве параметров. Но есть одно правило, которое следует помнить. Значение типа float не всегда правильно конвертируются. Вместо него нужно писать double. Это максимально приближает нас к синтаксису Sanny Builder и не нужно будет писать суффикс F для значений в C#.
Многие команды могут принимать значения переменных. В этом случае мы можем использовать типы данных, которые используются в генераторе. Следующий пример даёт возможность передавать литерал и переменную в команду.
Как видим, в зависимости от параметра on, в команду будет подставляться нужное значение автоматически. Кроме стандартных типов, мы можем передавать и другие типы. Сложнее всего писать команду, которая требует любой транспорт:
usingGTA.Core; // <-- подключаем пространство имён, где хранится класс Vehicle<T>publicpartialclassMAIN:Thread {publicstaticvoid__explode_car( Car hCar ) {hCar.explode(); }publicstaticvoid__explode_vehicle<T>( T hVehicle ) whereT:Vehicle<T> {hVehicle.explode(); }publicoverridevoidSTART( LabelJump label ) { }}// ...publicpartialclassMAIN:Thread {publicclassTEST:Thread {publicoverridevoidSTART( LabelJump label ) { __explode_car( Car.empty ); //__explode_car( Boat.empty ); // <- ошибка: можно передать только транспорт типа "Car"__explode_vehicle( Boat.empty ); // можно передать любой транспорт__explode_vehicle( Car.empty ); // можно передать любой транспортend_thread(); } }}
Обратите внимание, что некоторые классы имеют статическое свойство empty. Когда мы обращаемся к нему, вместо переменной будет указано значение -1. Ну и последний пример — реализация команд-условий. В этом случае нам нужно возвращать объект Condition в нашей команде:
//------------- THREAD TEST ---------------
:TEST
03A4: name_thread 'TEST'
0001: wait 0 ms
00D6: if
0256: player $2 defined
004D: jump_if_false @TEST
00D6: if
03EE: player $2 controllable
004D: jump_if_false @TEST
00D6: if
8A0C: not player $2 on_jetpack
004D: jump_if_false @TEST
004E: end_thread
Неудобством подхода будет разве что дополнительный отступ.
Очень важно! Пользовательские команды должны возвращать типы void, Condition или Condition[]. Использование других типов может привести к путанице и неправильной работе генератора!
Недостатки
Пользовательские команды не лишены недостатков. Они отлично подходят в ситуациях, когда нам нужно объединить последовательный вызов базовых функций. Но совершенно уязвимы к декларации локальных переменных внутри себя! В основном это связано с тем, что при вызове команды она станет частью текущего скрипта (потока, миссии, внешного скрипта и SCM-функции) и все локальные переменные будут общими. Это может привести к ненужной перезаписи переменной или превышения лимита на эти же переменные.
Если в команде нужно использовать локальные переменные, то лучше указывать их как часть аргументов метода:
publicpartialclassMAIN:Thread {publicstaticvoid__add_ammo_to_current_weapon( Actor hActor,Int ammo,Out<Int> tempWeapon ) {hActor.get_current_weapon( tempWeapon ) .give_weapon( tempWeapon, ammo ); }}
В этом случае переменная tempWeapon будет передана из основного скрипта, не нарушая работы команды.
Выводы
В целом, написание собственных команд сокращает код. Использовать его очень полезно. Некоторые вещи придётся тренировать, но результаты того стоят!