Добавление команд

Поскольку генератор в большей степени работает с 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

В целом, написание собственных команд сокращает код. Использовать его очень полезно! Некоторые вещи придётся тренировать, но результаты того стоят.