Поскольку генератор в большей степени работает с MAIN.SCM, то в нём отсутствует возможность создавать SCM-функций. Это связано не так с реализацией, как со специфичностью их работы в мэйне: неправильная копиляция и вылеты в момент вызова.
Тем не менее есть возможность добавлять свои команды, которые основаны на базовых командах. Их можно использовать в любом месте Вашего кода. Ниже я дам пример реализации такой команды:
void __traffic_off() {
set_car_traffic_density_multiplier( 0.0 );
set_ped_traffic_density_multiplier( 0.0 );
}
void __traffic_on() {
set_car_traffic_density_multiplier( 1.0 );
set_ped_traffic_density_multiplier( 1.0 );
}
[Thread]
public void TEST() {
__traffic_off();
wait( 2000 );
__traffic_on();
end_thread();
}
Я добавил два метода, которые регулируют трафик. На его основе я буду делать примеры далее. При компиляции мы будем иметь такой код:
//------------- TEST --------------- :TEST 03A4: name_thread 'TEST' 01EB: set_traffic_density_multiplier_to 0.0 03DE: set_pedestrians_density_multiplier_to 0.0 0001: wait 2000 ms 01EB: set_traffic_density_multiplier_to 1.0 03DE: set_pedestrians_density_multiplier_to 1.0 004E: end_thread
Мы можем указать параметры команды:
void __toggle_traffic( double val ) {
set_car_traffic_density_multiplier( val );
set_ped_traffic_density_multiplier( val );
}
[Thread]
public void TEST() {
__toggle_traffic( 0.0 );
wait( 2000 );
__toggle_traffic( 1.0 );
end_thread();
}
Использовать можно любые типы данных в качестве параметров. Но есть одно правило, которое следует помнить. Генератор построен таким образом, что тип float не принимается в качестве параметра команды. Вместо него нужно писать double. Это максимально приближает нас к синтаксису Sanny Builder и не нужно будет писать префикс "F" для значений в C#.
Некоторые команды генератора могут принимать значения от переменных. В этом случае мы можем использовать объект:
void __toggle_traffic( object val_float ) {
set_car_traffic_density_multiplier( val_float );
set_ped_traffic_density_multiplier( val_float );
}
[Thread]
public void TEST() {
__toggle_traffic( 0.0 );
wait( 2000 );
var anyFloat = Float.local( 0 );
anyFloat.value = 1.0;
__toggle_traffic( anyFloat );
end_thread();
}
Последний пример даёт возможность передавать значение и переменную в команду. Кроме этого, мы можем писать некую логику, используя язык C#:
void __toggle_traffic( bool on ) {
set_car_traffic_density_multiplier( on ? 1.0 : 0.0 );
set_ped_traffic_density_multiplier( on ? 1.0 : 0.0 );
}
[Thread]
public void TEST() {
__toggle_traffic( false );
wait( 2000 );
__toggle_traffic( true );
end_thread();
}
Как видим, в зависимости от параметра "on", в команду будет подставляться нужное значение автоматически. Кроме стандартных типов, мы можем передавать и другие типы. Сложнее всего писать команду, которая требует любой транспорт:
void __explode_car( Car hCar ) {
hCar.explode();
}
void __explode_vehicle<T>( T hCar ) where T : GTA.Generator.Vehicle<T> {
hCar.explode();
}
[Thread]
public void TEST() {
__explode_car( Car.empty );
//__explode_car( Boat.empty ); // <- ошибка: можно передать только транспорт "Car"
__explode_vehicle( Boat.empty ); // можно передать любой транспорт
__explode_vehicle( Car.empty ); // можно передать любой транспорт
end_thread();
}
Обратите внимание, что некоторые классы имеют статическое свойство "empty". Когда мы обращаемся к нему, вместо переменной будет указано значение -1.
Ну и последний пример - реализация команд-условий. В этом случае нам нужно возвращать объект "Condition" в нашей команде:
GTA.Generator.Condition check_player( Player hPlayer ) {
return hPlayer.is_autoaiming();
}
[Thread]
public void TEST() {
wait( 0 );
jf( check_player( PlayerChar ) );
end_thread();
}
Для проверки нескольких условий мы должны возвращать массив "Condition":
using System.Collections.Generic;
using GTA.Generator;
// --- --- ---
Condition[] check_player( Player hPlayer ) {
List<Condition> allConditions = new List<Condition>();
allConditions.Add( hPlayer.is_defined() );
allConditions.Add( hPlayer.is_controllable() );
allConditions.Add( !hPlayer.is_on_jetpack() );
return allConditions.ToArray();
}
[Thread]
public void TEST() {
wait( 0 );
jf( check_player( PlayerChar ) );
end_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
В целом, написание собственных команд сокращает код. Использовать его очень полезно! Некоторые вещи придётся тренировать, но результаты того стоят.